diff options
author | Hayleigh Thompson <me@hayleigh.dev> | 2023-08-19 22:21:19 +0100 |
---|---|---|
committer | Hayleigh Thompson <me@hayleigh.dev> | 2023-08-19 22:21:19 +0100 |
commit | 9919bc2702c89168d1805eaa0db9e4baff091260 (patch) | |
tree | ef5f1fd360d90ec8433aa0fccc2709bed0e8e9d2 /src | |
parent | e4aa0e04e54105395d5f6f5e3f7e2d9a4f7851e0 (diff) | |
download | lustre-9919bc2702c89168d1805eaa0db9e4baff091260.tar.gz lustre-9919bc2702c89168d1805eaa0db9e4baff091260.zip |
:truck: Shift things around to accomodate a monorepo.
Diffstat (limited to 'src')
-rw-r--r-- | src/lustre.ffi.mjs | 206 | ||||
-rw-r--r-- | src/lustre.gleam | 254 | ||||
-rw-r--r-- | src/lustre/attribute.gleam | 408 | ||||
-rw-r--r-- | src/lustre/effect.gleam | 67 | ||||
-rw-r--r-- | src/lustre/element.gleam | 126 | ||||
-rw-r--r-- | src/lustre/element/html.gleam | 1197 | ||||
-rw-r--r-- | src/lustre/element/svg.gleam | 351 | ||||
-rw-r--r-- | src/lustre/event.gleam | 184 | ||||
-rw-r--r-- | src/runtime.ffi.mjs | 230 |
9 files changed, 0 insertions, 3023 deletions
diff --git a/src/lustre.ffi.mjs b/src/lustre.ffi.mjs deleted file mode 100644 index b99f6e2..0000000 --- a/src/lustre.ffi.mjs +++ /dev/null @@ -1,206 +0,0 @@ -import { ElementNotFound, ComponentAlreadyRegistered } from "./lustre.mjs"; -import { from } from "./lustre/effect.mjs"; -import { map } from "./lustre/element.mjs"; -import { morph } from "./runtime.ffi.mjs"; -import { Ok, Error, isEqual } from "./gleam.mjs"; - -// RUNTIME --------------------------------------------------------------------- - -/// -/// -export class App { - #root = null; - #state = null; - #queue = []; - #effects = []; - #didUpdate = false; - - #init = null; - #update = null; - #view = null; - - constructor(init, update, render) { - this.#init = init; - this.#update = update; - this.#view = render; - } - - start(selector = "body") { - if (this.#root) return this; - - try { - const el = - selector instanceof HTMLElement - ? selector - : document.querySelector(selector); - const [next, effects] = this.#init(); - - this.#root = el; - this.#state = next; - this.#effects = effects[0].toArray(); - this.#didUpdate = true; - - window.requestAnimationFrame(() => this.#tick()); - - return new Ok((msg) => this.dispatch(msg)); - } catch (_) { - return new Error(new ElementNotFound()); - } - } - - dispatch(msg) { - this.#queue.push(msg); - this.#tick(); - } - - emit(name, event = null) { - this.#root.dispatchEvent( - new CustomEvent(name, { - bubbles: true, - detail: event, - composed: true, - }) - ); - } - - destroy() { - this.#root.remove(); - this.#state = null; - this.#queue = []; - this.#effects = []; - this.#didUpdate = false; - this.#update = () => {}; - this.#view = () => {}; - } - - #render() { - const node = this.#view(this.#state); - const vdom = map(node, (msg) => this.dispatch(msg)); - - morph(this.#root, vdom); - } - - #tick() { - this.#flush(); - this.#didUpdate && this.#render(); - this.#didUpdate = false; - } - - #flush(times = 0) { - if (this.#queue.length) { - while (this.#queue.length) { - const [next, effects] = this.#update(this.#state, this.#queue.shift()); - - this.#state = next; - this.#effects = this.#effects.concat(effects[0].toArray()); - } - this.#didUpdate = true; - } - - // Each update can produce effects which must now be executed. - while (this.#effects[0]) - this.#effects.shift()( - (msg) => this.dispatch(msg), - (name, data) => this.emit(name, data) - ); - - // Synchronous effects will immediately queue a message to be processed. If - // it is reasonable, we can process those updates too before proceeding to - // the next render. - if (this.#queue.length) { - times >= 5 ? console.warn(tooManyUpdates) : this.#flush(++times); - } - } -} - -export const setup = (init, update, render) => new App(init, update, render); -export const start = (app, selector) => app.start(selector); - -export const emit = (name, data) => - // Normal `Effect`s constructed in Gleam from `effect.from` don't get told - // about the second argument, but it's there 👀. - from((_, emit) => { - emit(name, data); - }); - -// HTML EVENTS ----------------------------------------------------------------- - -export const prevent_default = (e) => e.preventDefault?.(); -export const stop_propagation = (e) => e.stopPropagation?.(); - -// CUSTOM ELEMENTS ------------------------------------------------------------- - -export const setup_component = ( - name, - init, - update, - render, - on_attribute_change -) => { - if (customElements.get(name)) { - return new Error(new ComponentAlreadyRegistered()); - } - - customElements.define( - name, - class extends HTMLElement { - static get observedAttributes() { - return on_attribute_change.entries().map(([name, _]) => name); - } - - #container = document.createElement("div"); - #app = null; - #dispatch = null; - - constructor() { - super(); - - this.#app = new App(init, update, render); - const dispatch = this.#app.start(this.#container); - this.#dispatch = dispatch[0]; - - on_attribute_change.forEach((decoder, name) => { - Object.defineProperty(this, name, { - get: () => { - return this[`_${name}`] || this.getAttribute(name); - }, - - set: (value) => { - const prev = this[name]; - const decoded = decoder(value); - - // We need this equality check to prevent constantly dispatching - // messages when the value is an object or array: it might not have - // changed but its reference might have and we don't want to trigger - // useless updates. - if (decoded.isOk() && !isEqual(prev, decoded[0])) { - this.#dispatch(decoded[0]); - } - - if (typeof value === "string") { - this.setAttribute(name, value); - } else { - this[`_${name}`] = value; - } - }, - }); - }); - } - - connectedCallback() { - this.appendChild(this.#container); - } - - attributeChangedCallback(name, prev, next) { - if (prev !== next) { - this[name] = next; - } - } - - disconnectedCallback() { - this.#app.destroy(); - } - } - ); - return new Ok(null); -}; diff --git a/src/lustre.gleam b/src/lustre.gleam deleted file mode 100644 index 673f982..0000000 --- a/src/lustre.gleam +++ /dev/null @@ -1,254 +0,0 @@ -//// Lustre is a declarative framework for building Web apps in Gleam. - -// IMPORTS --------------------------------------------------------------------- - -import gleam/dynamic.{Decoder} -import gleam/map.{Map} -import lustre/effect.{Effect} -import lustre/element.{Element} - -// TYPES ----------------------------------------------------------------------- - -/// An `App` describes a Lustre application: what state it holds and what kind -/// of actions get dispatched to update that model. The only useful thing you can -/// do with an `App` is pass it to [`start`](#start). -/// -/// You can construct an `App` from the two constructors exposed in this module: -/// [`basic`](#basic) and [`application`](#application). Although you can't do -/// anything but [`start`](#start) them, the constructors are separated in case -/// you want to set up an application but defer starting it until some later point -/// in time. -/// -/// ```text -/// +--------+ -/// | | -/// | update | -/// | | -/// +--------+ -/// ^ | -/// | | -/// Msg | | #(Model, Effect(Msg)) -/// | | -/// | v -/// +------+ +------------------------+ -/// | | #(Model, Effect(Msg)) | | -/// | init |------------------------>| Lustre Runtime | -/// | | | | -/// +------+ +------------------------+ -/// ^ | -/// | | -/// Msg | | Model -/// | | -/// | v -/// +--------+ -/// | | -/// | render | -/// | | -/// +--------+ -/// ``` -/// -pub type App(model, msg) - -pub type Error { - ElementNotFound - ComponentAlreadyRegistered -} - -// These types aren't exposed, but they're just here to try and shrink the type -// annotations for `App` and `application` a little bit. When generating docs, -// Gleam automatically expands type aliases so this is purely for the benefit of -// those reading the source. -// - -type Update(model, msg) = - fn(model, msg) -> #(model, Effect(msg)) - -type Render(model, msg) = - fn(model) -> Element(msg) - -// CONSTRUCTORS ---------------------------------------------------------------- - -@target(javascript) -/// Create a basic lustre app that just renders some element on the page. -/// Note that this doesn't mean the content is static! With `element.stateful` -/// you can still create components with local state. -/// -/// Basic lustre apps don't have any *global* application state and so the -/// plumbing is a lot simpler. If you find yourself passing lots of state around, -/// you might want to consider using [`simple`](#simple) or [`application`](#application) -/// instead. -/// -/// ```gleam -/// import lustre -/// import lustre/element -/// -/// pub fn main () { -/// let app = lustre.element( -/// element.h1([], [ -/// element.text("Hello, world!") -/// ]) -/// ) -/// -/// assert Ok(_) = lustre.start(app, "#root") -/// } -/// ``` -/// -pub fn element(element: Element(msg)) -> App(Nil, msg) { - let init = fn() { #(Nil, effect.none()) } - let update = fn(_, _) { #(Nil, effect.none()) } - let render = fn(_) { element } - - application(init, update, render) -} - -@target(javascript) -/// If you start off with a simple `[element`](#element) app, you may find -/// yourself leaning on [`stateful`](./lustrel/element.html#stateful) elements -/// to manage model used throughout your app. If that's the case or if you know -/// you need some global model from the get-go, you might want to construct a -/// [`simple`](#simple) app instead. -/// -/// This is one app constructor that allows your HTML elements to dispatch actions -/// to update your program model. -/// -/// ```gleam -/// import gleam/int -/// import lustre -/// import lustre/element -/// import lustre/event -/// -/// type Msg { -/// Decr -/// Incr -/// } -/// -/// pub fn main () { -/// let init = 0 -/// -/// let update = fn (model, msg) { -/// case msg { -/// Decr -> model - 1 -/// Incr -> model + 1 -/// } -/// } -/// -/// let render = fn (model) { -/// element.div([], [ -/// element.button([ event.on_click(Decr) ], [ -/// element.text("-") -/// ]), -/// -/// element.text(int.to_string(model)), -/// -/// element.button([ event.on_click(Incr) ], [ -/// element.text("+") -/// ]) -/// ]) -/// } -/// -/// let app = lustre.simple(init, update, render) -/// assert Ok(_) = lustre.start(app, "#root") -/// } -/// ``` -/// -pub fn simple( - init: fn() -> model, - update: fn(model, msg) -> model, - render: fn(model) -> Element(msg), -) -> App(model, msg) { - let init = fn() { #(init(), effect.none()) } - let update = fn(model, msg) { #(update(model, msg), effect.none()) } - - application(init, update, render) -} - -@target(javascript) -/// An evolution of a [`simple`](#simple) app that allows you to return a -/// [`Effect`](./lustre/effect.html#Effect) from your `init` and `update`s. Commands give -/// us a way to perform side effects like sending an HTTP request or running a -/// timer and then dispatch actions back to the runtime to trigger an `update`. -/// -///``` -/// import lustre -/// import lustre/effect -/// import lustre/element -/// -/// pub fn main () { -/// let init = #(0, tick()) -/// -/// let update = fn (model, msg) { -/// case msg { -/// Tick -> #(model + 1, tick()) -/// } -/// } -/// -/// let render = fn (model) { -/// element.div([], [ -/// element.text("Time elapsed: ") -/// element.text(int.to_string(model)) -/// ]) -/// } -/// -/// let app = lustre.simple(init, update, render) -/// assert Ok(_) = lustre.start(app, "#root") -/// } -/// -/// fn tick () -> Effect(Msg) { -/// effect.from(fn (dispatch) { -/// setInterval(fn () { -/// dispatch(Tick) -/// }, 1000) -/// }) -/// } -/// -/// external fn set_timeout (f: fn () -> a, delay: Int) -> Nil -/// = "" "window.setTimeout" -///``` -@external(javascript, "./lustre.ffi.mjs", "setup") -pub fn application(init: fn() -> #(model, Effect(msg)), update: Update( - model, - msg, - ), render: Render(model, msg)) -> App(model, msg) - -@target(javascript) -@external(javascript, "./lustre.ffi.mjs", "setup_component") -pub fn component(name: String, init: fn() -> #(model, Effect(msg)), update: Update( - model, - msg, - ), render: Render(model, msg), on_attribute_change: Map(String, Decoder(msg))) -> Result( - Nil, - Error, -) - -// EFFECTS --------------------------------------------------------------------- - -@target(javascript) -/// Once you have created a app with either `basic` or `application`, you -/// need to actually start it! This function will mount your app to the DOM -/// node that matches the query selector you provide. -/// -/// If everything mounted OK, we'll get back a dispatch function that you can -/// call to send actions to your app and trigger an update. -/// -///``` -/// import lustre -/// -/// pub fn main () { -/// let app = lustre.appliation(init, update, render) -/// assert Ok(dispatch) = lustre.start(app, "#root") -/// -/// dispatch(Incr) -/// dispatch(Incr) -/// dispatch(Incr) -/// } -///``` -/// -/// This may not seem super useful at first, but by returning this dispatch -/// function from your `main` (or elsewhere) you can get events into your Lustre -/// app from the outside world. -/// -@external(javascript, "./lustre.ffi.mjs", "start") -pub fn start(app: App(model, msg), selector: String) -> Result( - fn(msg) -> Nil, - Error, -) diff --git a/src/lustre/attribute.gleam b/src/lustre/attribute.gleam deleted file mode 100644 index 459a86e..0000000 --- a/src/lustre/attribute.gleam +++ /dev/null @@ -1,408 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import gleam/dynamic.{Dynamic} -import gleam/int -import gleam/list -import gleam/option.{Option} -import gleam/string -import gleam/string_builder.{StringBuilder} - -// TYPES ----------------------------------------------------------------------- - -/// Attributes are attached to specific elements. They're either key/value pairs -/// or event handlers. -/// -pub opaque type Attribute(msg) { - Attribute(String, Dynamic) - Event(String, fn(Dynamic) -> Option(msg)) -} - -// CONSTRUCTORS ---------------------------------------------------------------- - -/// -/// Lustre does some work internally to convert common Gleam values into ones that -/// make sense for JavaScript. Here are the types that are converted: -/// -/// - `List(a)` -> `Array(a)` -/// - `Some(a)` -> `a` -/// - `None` -> `undefined` -/// -pub fn attribute(name: String, value: String) -> Attribute(msg) { - escape("", value) - |> dynamic.from - |> Attribute(name, _) -} - -/// -pub fn property(name: String, value: any) -> Attribute(msg) { - Attribute(name, dynamic.from(value)) -} - -fn escape(escaped: String, content: String) -> String { - case string.pop_grapheme(content) { - Ok(#("<", xs)) -> escape(escaped <> "<", xs) - Ok(#(">", xs)) -> escape(escaped <> ">", xs) - Ok(#("&", xs)) -> escape(escaped <> "&", xs) - Ok(#("\"", xs)) -> escape(escaped <> """, xs) - Ok(#("'", xs)) -> escape(escaped <> "'", xs) - Ok(#(x, xs)) -> escape(escaped <> x, xs) - Error(_) -> escaped <> content - } -} - -/// Attach custom event handlers to an element. A number of helper functions exist -/// in this module to cover the most common events and use-cases, so you should -/// check those out first. -/// -/// If you need to handle an event that isn't covered by the helper functions, -/// then you can use `on` to attach a custom event handler. The callback is given -/// the event object as a `Dynamic`. -/// -/// As a simple example, you can implement `on_click` like so: -/// -/// ```gleam -/// import gleam/option.{Some} -/// import lustre/attribute.{Attribute} -/// import lustre/event -/// -/// pub fn on_click(msg: msg) -> Attribute(msg) { -/// use _ <- event.on("click") -/// Some(msg) -/// } -/// ``` -/// -/// By using `gleam/dynamic` you can decode the event object and pull out all sorts -/// of useful data. This is how `on_input` is implemented: -/// -/// ```gleam -/// import gleam/dynamic -/// import gleam/option.{None, Some} -/// import gleam/result -/// import lustre/attribute.{Attribute} -/// import lustre/event -/// -/// pub fn on_input(msg: fn(String) -> msg) -> Attribute(msg) { -/// use event, dispatch <- on("input") -/// let decode = dynamic.field("target", dynamic.field("value", dynamic.string)) -/// -/// case decode(event) { -/// Ok(value) -> Some(msg(value)) -/// Error(_) -> None -/// } -/// } -/// ``` -/// -/// You can take a look at the MDN reference for events -/// [here](https://developer.mozilla.org/en-US/docs/Web/API/Event) to see what -/// you can decode. -/// -/// Unlike the helpers in the rest of this module, it is possible to simply ignore -/// the dispatch function and not dispatch a message at all. In fact, we saw this -/// with the `on_input` example above: if we can't decode the event object, we -/// simply return `None` and emit nothing. -/// -/// Beyond ignoring errors, this can be used to perform side effects we don't need -/// to observe in our main application loop, such as logging... -/// -/// ```gleam -/// import gleam/io -/// import gleam/option.{None} -/// import lustre/attribute.{Attribute} -/// import lustre/event -/// -/// pub fn log_on_click(msg: String) -> Attribute(msg) { -/// use _ <- event.on("click") -/// io.println(msg) -/// None -/// } -/// ``` -/// -pub fn on(name: String, handler: fn(Dynamic) -> Option(msg)) -> Attribute(msg) { - Event("on" <> name, handler) -} - -// MANIPULATIONS --------------------------------------------------------------- - -/// -/// -pub fn map(attr: Attribute(a), f: fn(a) -> b) -> Attribute(b) { - case attr { - Attribute(name, value) -> Attribute(name, value) - Event(on, handler) -> Event(on, fn(e) { option.map(handler(e), f) }) - } -} - -// CONVERSIONS ----------------------------------------------------------------- - -/// -/// -pub fn to_string(attr: Attribute(msg)) -> String { - case attr { - Attribute(name, value) -> { - case dynamic.classify(value) { - "String" -> name <> "=\"" <> dynamic.unsafe_coerce(value) <> "\"" - - // Boolean attributes are determined based on their presence, eg we don't - // want to render `disabled="false"` if the value is `false` we simply - // want to omit the attribute altogether. - "Boolean" -> - case dynamic.unsafe_coerce(value) { - True -> name - False -> "" - } - - // For everything else we'll just make a best-effort serialisation. - _ -> name <> "=\"" <> string.inspect(value) <> "\"" - } - } - Event(on, _) -> "data-lustre-on:" <> on - } -} - -/// -/// -pub fn to_string_builder(attr: Attribute(msg)) -> StringBuilder { - case attr { - Attribute(name, value) -> { - case dynamic.classify(value) { - "String" -> - [name, "=\"", dynamic.unsafe_coerce(value), "\""] - |> string_builder.from_strings - - // Boolean attributes are determined based on their presence, eg we don't - // want to render `disabled="false"` if the value is `false` we simply - // want to omit the attribute altogether. - "Boolean" -> - case dynamic.unsafe_coerce(value) { - True -> string_builder.from_string(name) - False -> string_builder.new() - } - - // For everything else we'll just make a best-effort serialisation. - _ -> - [name, "=\"", string.inspect(value), "\""] - |> string_builder.from_strings - } - } - Event(on, _) -> - ["data-lustre-on:", on] - |> string_builder.from_strings - } -} - -// COMMON ATTRIBUTES ----------------------------------------------------------- - -/// -pub fn style(properties: List(#(String, String))) -> Attribute(msg) { - attribute( - "style", - { - use styles, #(name, value) <- list.fold(properties, "") - styles <> name <> ":" <> value <> ";" - }, - ) -} - -/// -pub fn class(name: String) -> Attribute(msg) { - attribute("class", name) -} - -/// -pub fn classes(names: List(#(String, Bool))) -> Attribute(msg) { - attribute( - "class", - names - |> list.filter_map(fn(class) { - case class.1 { - True -> Ok(class.0) - False -> Error(Nil) - } - }) - |> string.join(" "), - ) -} - -/// -pub fn id(name: String) -> Attribute(msg) { - attribute("id", name) -} - -// INPUTS ---------------------------------------------------------------------- - -/// -pub fn type_(name: String) -> Attribute(msg) { - attribute("type", name) -} - -/// -pub fn value(val: Dynamic) -> Attribute(msg) { - property("value", val) -} - -/// -pub fn checked(is_checked: Bool) -> Attribute(msg) { - property("checked", is_checked) -} - -/// -pub fn placeholder(text: String) -> Attribute(msg) { - attribute("placeholder", text) -} - -/// -pub fn selected(is_selected: Bool) -> Attribute(msg) { - property("selected", is_selected) -} - -// INPUT HELPERS --------------------------------------------------------------- - -/// -pub fn accept(types: List(String)) -> Attribute(msg) { - attribute("accept", string.join(types, " ")) -} - -/// -pub fn accept_charset(types: List(String)) -> Attribute(msg) { - attribute("acceptCharset", string.join(types, " ")) -} - -/// -pub fn msg(uri: String) -> Attribute(msg) { - attribute("msg", uri) -} - -/// -pub fn autocomplete(name: String) -> Attribute(msg) { - attribute("autocomplete", name) -} - -/// -pub fn autofocus(should_autofocus: Bool) -> Attribute(msg) { - property("autoFocus", should_autofocus) -} - -/// -pub fn disabled(is_disabled: Bool) -> Attribute(msg) { - property("disabled", is_disabled) -} - -/// -pub fn name(name: String) -> Attribute(msg) { - attribute("name", name) -} - -/// -pub fn pattern(regex: String) -> Attribute(msg) { - attribute("pattern", regex) -} - -/// -pub fn readonly(is_readonly: Bool) -> Attribute(msg) { - property("readonly", is_readonly) -} - -/// -pub fn required(is_required: Bool) -> Attribute(msg) { - property("required", is_required) -} - -/// -pub fn for(id: String) -> Attribute(msg) { - attribute("for", id) -} - -// INPUT RANGES ---------------------------------------------------------------- - -/// -pub fn max(val: String) -> Attribute(msg) { - attribute("max", val) -} - -/// -pub fn min(val: String) -> Attribute(msg) { - attribute("min", val) -} - -/// -pub fn step(val: String) -> Attribute(msg) { - attribute("step", val) -} - -// INPUT TEXT AREAS ------------------------------------------------------------ - -/// -pub fn cols(val: Int) -> Attribute(msg) { - attribute("cols", int.to_string(val)) -} - -/// -pub fn rows(val: Int) -> Attribute(msg) { - attribute("rows", int.to_string(val)) -} - -/// -pub fn wrap(mode: String) -> Attribute(msg) { - attribute("wrap", mode) -} - -// LINKS AND AREAS ------------------------------------------------------------- - -/// -pub fn href(uri: String) -> Attribute(msg) { - attribute("href", uri) -} - -/// -pub fn target(target: String) -> Attribute(msg) { - attribute("target", target) -} - -/// -pub fn download(filename: String) -> Attribute(msg) { - attribute("download", filename) -} - -/// -pub fn rel(relationship: String) -> Attribute(msg) { - attribute("rel", relationship) -} - -// EMBEDDED CONTENT ------------------------------------------------------------ - -/// -pub fn src(uri: String) -> Attribute(msg) { - attribute("src", uri) -} - -/// -pub fn height(val: Int) -> Attribute(msg) { - property("height", int.to_string(val)) -} - -/// -pub fn width(val: Int) -> Attribute(msg) { - property("width", int.to_string(val)) -} - -/// -pub fn alt(text: String) -> Attribute(msg) { - attribute("alt", text) -} - -// AUDIO AND VIDEO ------------------------------------------------------------- - -/// -pub fn autoplay(should_autoplay: Bool) -> Attribute(msg) { - property("autoplay", should_autoplay) -} - -/// -pub fn controls(visible: Bool) -> Attribute(msg) { - property("controls", visible) -} - -/// -pub fn loop(should_loop: Bool) -> Attribute(msg) { - property("loop", should_loop) -} diff --git a/src/lustre/effect.gleam b/src/lustre/effect.gleam deleted file mode 100644 index 19f54b0..0000000 --- a/src/lustre/effect.gleam +++ /dev/null @@ -1,67 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import gleam/list - -// TYPES ----------------------------------------------------------------------- - -/// A `Effect` represents some side effect we want the Lustre runtime to perform. -/// It is parameterised by our app's `action` type because some effects need to -/// get information back into your program. -/// -pub opaque type Effect(action) { - Effect(List(fn(fn(action) -> Nil) -> Nil)) -} - -// CONSTRUCTORS ---------------------------------------------------------------- - -/// Create a `Effect` from some custom side effect. This is mostly useful for -/// package authors, or for integrating other libraries into your Lustre app. -/// -/// We pass in a function that recieves a `dispatch` callback that can be used -/// to send messages to the Lustre runtime. We could, for example, create a `tick` -/// command that uses the `setTimeout` JavaScript API to send a message to the -/// runtime every second: -/// -/// ```gleam -/// import lustre/effect.{Effect} -/// -/// external fn set_interval(callback: fn() -> any, interval: Int) = -/// "" "window.setInterval" -/// -/// pub fn every_second(msg: msg) -> Effect(msg) { -/// use dispatch <- effect.from -/// -/// set_interval(fn() { dispatch(msg) }, 1000) -/// } -/// ``` -/// -pub fn from(effect: fn(fn(action) -> Nil) -> Nil) -> Effect(action) { - Effect([effect]) -} - -/// Typically our app's `update` function needs to return a tuple of -/// `#(model, Effect(action))`. When we don't need to perform any side effects we -/// can just return `none()`! -/// -pub fn none() -> Effect(action) { - Effect([]) -} - -// MANIPULATIONS --------------------------------------------------------------- - -/// -/// -pub fn batch(cmds: List(Effect(action))) -> Effect(action) { - Effect({ - use b, Effect(a) <- list.fold(cmds, []) - list.append(b, a) - }) -} - -pub fn map(effect: Effect(a), f: fn(a) -> b) -> Effect(b) { - let Effect(l) = effect - Effect(list.map( - l, - fn(effect) { fn(dispatch) { effect(fn(a) { dispatch(f(a)) }) } }, - )) -} diff --git a/src/lustre/element.gleam b/src/lustre/element.gleam deleted file mode 100644 index 4e8abee..0000000 --- a/src/lustre/element.gleam +++ /dev/null @@ -1,126 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import gleam/list -import gleam/string -import gleam/string_builder.{StringBuilder} -import lustre/attribute.{Attribute} - -// TYPES ----------------------------------------------------------------------- - -/// -/// -pub opaque type Element(msg) { - Text(String) - Element(String, List(Attribute(msg)), List(Element(msg))) - ElementNs(String, List(Attribute(msg)), List(Element(msg)), String) -} - -// CONSTRUCTORS ---------------------------------------------------------------- - -/// -/// -pub fn element( - tag: String, - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - Element(tag, attrs, children) -} - -/// -/// -pub fn namespaced( - namespace: String, - tag: String, - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - ElementNs(tag, attrs, children, namespace) -} - -/// -/// -pub fn text(content: String) -> Element(msg) { - Text(content) -} - -fn escape(escaped: String, content: String) -> String { - case string.pop_grapheme(content) { - Ok(#("<", xs)) -> escape(escaped <> "<", xs) - Ok(#(">", xs)) -> escape(escaped <> ">", xs) - Ok(#("&", xs)) -> escape(escaped <> "&", xs) - Ok(#("\"", xs)) -> escape(escaped <> """, xs) - Ok(#("'", xs)) -> escape(escaped <> "'", xs) - Ok(#(x, xs)) -> escape(escaped <> x, xs) - Error(_) -> escaped <> content - } -} - -// MANIPULATIONS --------------------------------------------------------------- - -/// -/// -pub fn map(element: Element(a), f: fn(a) -> b) -> Element(b) { - case element { - Text(content) -> Text(content) - Element(tag, attrs, children) -> - Element( - tag, - list.map(attrs, attribute.map(_, f)), - list.map(children, map(_, f)), - ) - ElementNs(tag, attrs, children, namespace) -> - ElementNs( - tag, - list.map(attrs, attribute.map(_, f)), - list.map(children, map(_, f)), - namespace, - ) - } -} - -// CONVERSIONS ----------------------------------------------------------------- - -/// -/// -pub fn to_string(element: Element(msg)) -> String { - to_string_builder(element) - |> string_builder.to_string -} - -/// -/// -pub fn to_string_builder(element: Element(msg)) -> StringBuilder { - case element { - Text(content) -> string_builder.from_string(escape("", content)) - Element(tag, attrs, children) -> - string_builder.from_string("<" <> tag) - |> attrs_to_string_builder(attrs) - |> string_builder.append(">") - |> children_to_string_builder(children) - |> string_builder.append("</" <> tag <> ">") - ElementNs(tag, attrs, children, namespace) -> - string_builder.from_string("<" <> tag) - |> attrs_to_string_builder(attrs) - |> string_builder.append(" xmlns=\"" <> namespace <> "\"") - |> string_builder.append(">") - |> children_to_string_builder(children) - |> string_builder.append("</" <> tag <> ">") - } -} - -fn attrs_to_string_builder( - html: StringBuilder, - attrs: List(Attribute(msg)), -) -> StringBuilder { - use html, attr <- list.fold(attrs, html) - string_builder.append_builder(html, attribute.to_string_builder(attr)) -} - -fn children_to_string_builder( - html: StringBuilder, - children: List(Element(msg)), -) -> StringBuilder { - use html, child <- list.fold(children, html) - string_builder.append_builder(html, to_string_builder(child)) -} diff --git a/src/lustre/element/html.gleam b/src/lustre/element/html.gleam deleted file mode 100644 index 9eb4f5e..0000000 --- a/src/lustre/element/html.gleam +++ /dev/null @@ -1,1197 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/element.{Element, element, namespaced, text} -import lustre/attribute.{Attribute} - -// The doc comments (and order) for functions in this module are taken from the -// MDN Element reference: -// -// https://developer.mozilla.org/en-US/docs/Web/HTML/Element -// - -// HTML ELEMENTS: MAIN ROOT ---------------------------------------------------- - -/// Represents the root (top-level element) of an HTML document, so it is also -/// referred to as the root element. All other elements must be descendants of -/// this element. -/// -pub fn html( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("html", attrs, children) -} - -// HTML ELEMENTS: DOCUMENT METADATA -------------------------------------------- - -/// Specifies the base URL to use for all relative URLs in a document. There can -/// be only one such element in a document. -/// -pub fn base(attrs: List(Attribute(msg))) -> Element(msg) { - element("base", attrs, []) -} - -/// Contains machine-readable information (metadata) about the document, like its -/// title, scripts, and style sheets. -/// -pub fn head(attrs: List(Attribute(msg))) -> Element(msg) { - element("head", attrs, []) -} - -/// Specifies relationships between the current document and an external resource. -/// This element is most commonly used to link to CSS but is also used to establish -/// site icons (both "favicon" style icons and icons for the home screen and apps -/// on mobile devices) among other things. -/// -pub fn link(attrs: List(Attribute(msg))) -> Element(msg) { - element("link", attrs, []) -} - -/// Represents metadata that cannot be represented by other HTML meta-related -/// elements, like <base>, <link>, <script>, <style> and <title>. -/// -pub fn meta(attrs: List(Attribute(msg))) -> Element(msg) { - element("meta", attrs, []) -} - -/// Contains style information for a document or part of a document. It contains -/// CSS, which is applied to the contents of the document containing this element. -/// -pub fn style(attrs: List(Attribute(msg)), css: String) -> Element(msg) { - element("style", attrs, [text(css)]) -} - -/// Defines the document's title that is shown in a browser's title bar or a -/// page's tab. It only contains text; tags within the element are ignored. -/// -pub fn title(attrs: List(Attribute(msg)), content: String) -> Element(msg) { - element("title", attrs, [text(content)]) -} - -// HTML ELEMENTS: SECTIONING ROOT ----------------------------------------------- - -/// Represents the content of an HTML document. There can be only one such element -/// in a document. -/// -pub fn body( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("body", attrs, children) -} - -// HTML ELEMENTS: CONTENT SECTIONING ------------------------------------------- - -/// Indicates that the enclosed HTML provides contact information for a person or -/// people, or for an organization. -/// -pub fn address( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("address", attrs, children) -} - -/// Represents a self-contained composition in a document, page, application, or -/// site, which is intended to be independently distributable or reusable (e.g., -/// in syndication). Examples include a forum post, a magazine or newspaper article, -/// a blog entry, a product card, a user-submitted comment, an interactive widget -/// or gadget, or any other independent item of content. -/// -pub fn article( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("article", attrs, children) -} - -/// Represents a portion of a document whose content is only indirectly related -/// to the document's main content. Asides are frequently presented as sidebars -/// or call-out boxes. -/// -pub fn aside( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("aside", attrs, children) -} - -/// Represents a footer for its nearest ancestor sectioning content or sectioning -/// root element. A <footer> typically contains information about the author of -/// the section, copyright data, or links to related documents. -/// -pub fn footer( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("footer", attrs, children) -} - -/// Represents introductory content, typically a group of introductory or navigational -/// aids. It may contain some heading elements but also a logo, a search form, an -/// author name, and other elements. -/// -pub fn header( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("header", attrs, children) -} - -/// Represent six levels of section headings. <h1> is the highest section level -/// and <h6> is the lowest. -/// -pub fn h1( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("h1", attrs, children) -} - -/// Represent six levels of section headings. <h1> is the highest section level -/// and <h6> is the lowest. -/// -pub fn h2( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("h2", attrs, children) -} - -/// Represent six levels of section headings. <h1> is the highest section level -/// and <h6> is the lowest. -/// -pub fn h3( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("h3", attrs, children) -} - -/// Represent six levels of section headings. <h1> is the highest section level -/// and <h6> is the lowest. -/// -pub fn h4( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("h4", attrs, children) -} - -/// Represent six levels of section headings. <h1> is the highest section level -/// and <h6> is the lowest. -/// -pub fn h5( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("h5", attrs, children) -} - -/// Represent six levels of section headings. <h1> is the highest section level -/// and <h6> is the lowest. -/// -pub fn h6( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("h6", attrs, children) -} - -/// Represents a heading grouped with any secondary content, such as subheadings, -/// an alternative title, or a tagline. -/// -pub fn hgroup( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("hgroup", attrs, children) -} - -/// Represents the dominant content of the body of a document. The main content -/// area consists of content that is directly related to or expands upon the -/// central topic of a document, or the central functionality of an application. -/// -pub fn main( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("main", attrs, children) -} - -/// Represents a section of a page whose purpose is to provide navigation links, -/// either within the current document or to other documents. Common examples of -/// navigation sections are menus, tables of contents, and indexes. -/// -pub fn nav( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("nav", attrs, children) -} - -/// Represents a generic standalone section of a document, which doesn't have a -/// more specific semantic element to represent it. Sections should always have -/// a heading, with very few exceptions. -/// -pub fn section( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("section", attrs, children) -} - -/// Represents a part that contains a set of form controls or other content related -/// to performing a search or filtering operation. -/// -pub fn search( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("search", attrs, children) -} - -// HTML ELEMENTS: TEXT CONTENT ------------------------------------------------- - -/// Indicates that the enclosed text is an extended quotation. Usually, this is -/// rendered visually by indentation. A URL for the source of the quotation may -/// be given using the cite attribute, while a text representation of the source -/// can be given using the <cite> element. -/// -pub fn blockquote( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("blockquote", attrs, children) -} - -/// Provides the description, definition, or value for the preceding term (<dt>) -/// in a description list (<dl>). -/// -pub fn dd( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("dd", attrs, children) -} - -/// The generic container for flow content. It has no effect on the content or -/// layout until styled in some way using CSS (e.g., styling is directly applied -/// to it, or some kind of layout model like flexbox is applied to its parent -/// element). -/// -pub fn div( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("div", attrs, children) -} - -/// Represents a description list. The element encloses a list of groups of terms -/// (specified using the <dt> element) and descriptions (provided by <dd> elements). -/// Common uses for this element are to implement a glossary or to display metadata -/// (a list of key-value pairs). -/// -pub fn dl( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("dl", attrs, children) -} - -/// Specifies a term in a description or definition list, and as such must be -/// used inside a <dl> element. It is usually followed by a <dd> element; -/// however, multiple <dt> elements in a row indicate several terms that are -/// all defined by the immediate next <dd> element. -/// -pub fn dt( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("dt", attrs, children) -} - -/// Represents a caption or legend describing the rest of the contents of its -/// parent <figure> element. -/// -pub fn figcaption( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("figcaption", attrs, children) -} - -/// Represents self-contained content, potentially with an optional caption, -/// which is specified using the <figcaption> element. The figure, its caption, -/// and its contents are referenced as a single unit. -/// -pub fn figure( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("figure", attrs, children) -} - -/// Represents a thematic break between paragraph-level elements: for example, -/// a change of scene in a story, or a shift of topic within a section. -/// -pub fn hr(attrs: List(Attribute(msg))) -> Element(msg) { - element("hr", attrs, []) -} - -/// Represents an item in a list. It must be contained in a parent element: an -/// ordered list (<ol>), an unordered list (<ul>), or a menu (<menu>). In menus -/// and unordered lists, list items are usually displayed using bullet points. -/// In ordered lists, they are usually displayed with an ascending counter on -/// the left, such as a number or letter. -/// -pub fn li( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("li", attrs, children) -} - -/// A semantic alternative to <ul>, but treated by browsers (and exposed through -/// the accessibility tree) as no different than <ul>. It represents an unordered -/// list of items (which are represented by <li> elements). -/// -pub fn menu( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("menu", attrs, children) -} - -/// Represents an ordered list of items — typically rendered as a numbered list. -/// -pub fn ol( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("ol", attrs, children) -} - -/// Represents a paragraph. Paragraphs are usually represented in visual media -/// as blocks of text separated from adjacent blocks by blank lines and/or -/// first-line indentation, but HTML paragraphs can be any structural grouping -/// of related content, such as images or form fields. -/// -pub fn p( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("p", attrs, children) -} - -/// Represents preformatted text which is to be presented exactly as written in -/// the HTML file. The text is typically rendered using a non-proportional, or -/// monospaced, font. Whitespace inside this element is displayed as written. -/// -pub fn pre( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("pre", attrs, children) -} - -/// Represents an unordered list of items, typically rendered as a bulleted list. -/// -pub fn ul( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("ul", attrs, children) -} - -// HTML ELEMENTS: INLINE TEXT SEMANTICS ---------------------------------------- - -/// Together with its href attribute, creates a hyperlink to web pages, files, -/// email addresses, locations within the current page, or anything else a URL -/// can address. -/// -pub fn a( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("a", attrs, children) -} - -/// Represents an abbreviation or acronym. -/// -pub fn abbr( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("abbr", attrs, children) -} - -/// Used to draw the reader's attention to the element's contents, which are not -/// otherwise granted special importance. This was formerly known as the Boldface -/// element, and most browsers still draw the text in boldface. However, you -/// should not use <b> for styling text or granting importance. If you wish to -/// create boldface text, you should use the CSS font-weight property. If you -/// wish to indicate an element is of special importance, you should use the -/// strong element. -/// -pub fn b( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("b", attrs, children) -} - -/// Tells the browser's bidirectional algorithm to treat the text it contains in -/// isolation from its surrounding text. It's particularly useful when a website -/// dynamically inserts some text and doesn't know the directionality of the -/// text being inserted. -/// -pub fn bdi( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("bdi", attrs, children) -} - -/// Overrides the current directionality of text, so that the text within is -/// rendered in a different direction. -/// -pub fn bdo( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("bdo", attrs, children) -} - -/// Produces a line break in text (carriage-return). It is useful for writing a -/// poem or an address, where the division of lines is significant. -/// -pub fn br(attrs: List(Attribute(msg))) -> Element(msg) { - element("br", attrs, []) -} - -/// Used to mark up the title of a cited creative work. The reference may be in -/// an abbreviated form according to context-appropriate conventions related to -/// citation metadata. -/// -pub fn cite( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("cite", attrs, children) -} - -/// Displays its contents styled in a fashion intended to indicate that the text -/// is a short fragment of computer code. By default, the content text is -/// displayed using the user agent's default monospace font. -/// -pub fn code( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("code", attrs, children) -} - -/// Links a given piece of content with a machine-readable translation. If the -/// content is time- or date-related, the<time> element must be used. -/// -pub fn data( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("data", attrs, children) -} - -/// Used to indicate the term being defined within the context of a definition -/// phrase or sentence. The ancestor <p> element, the <dt>/<dd> pairing, or the -/// nearest section ancestor of the <dfn> element, is considered to be the -/// definition of the term. -/// -pub fn dfn( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("dfn", attrs, children) -} - -/// Marks text that has stress emphasis. The <em> element can be nested, with -/// each nesting level indicating a greater degree of emphasis. -/// -pub fn em( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("em", attrs, children) -} - -/// Represents a range of text that is set off from the normal text for some -/// reason, such as idiomatic text, technical terms, and taxonomical designations, -/// among others. Historically, these have been presented using italicized type, -/// which is the original source of the <i> naming of this element. -/// -pub fn i( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("i", attrs, children) -} - -/// Represents a span of inline text denoting textual user input from a keyboard, -/// voice input, or any other text entry device. By convention, the user agent -/// defaults to rendering the contents of a <kbd> element using its default -/// monospace font, although this is not mandated by the HTML standard. -/// -pub fn kbd( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("kbd", attrs, children) -} - -/// Represents text which is marked or highlighted for reference or notation -/// purposes due to the marked passage's relevance in the enclosing context. -/// -pub fn mark( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("mark", attrs, children) -} - -/// Indicates that the enclosed text is a short inline quotation. Most modern -/// browsers implement this by surrounding the text in quotation marks. This -/// element is intended for short quotations that don't require paragraph -/// breaks; for long quotations use the <blockquote> element. -/// -pub fn q( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("q", attrs, children) -} - -/// Used to provide fall-back parentheses for browsers that do not support the -/// display of ruby annotations using the <ruby> element. One <rp> element -/// should enclose each of the opening and closing parentheses that wrap the -/// <rt> element that contains the annotation's text. -/// -pub fn rp( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("rp", attrs, children) -} - -/// Specifies the ruby text component of a ruby annotation, which is used to -/// provide pronunciation, translation, or transliteration information for East -/// Asian typography. The <rt> element must always be contained within a <ruby> -/// element. -/// -pub fn rt( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("rt", attrs, children) -} - -/// Represents small annotations that are rendered above, below, or next to base -/// text, usually used for showing the pronunciation of East Asian characters. -/// It can also be used for annotating other kinds of text, but this usage is -/// less common. -/// -pub fn ruby( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("ruby", attrs, children) -} - -/// Renders text with a strikethrough, or a line through it. Use the <s> element -/// to represent things that are no longer relevant or no longer accurate. -/// However, <s> is not appropriate when indicating document edits; for that, -/// use the del and ins elements, as appropriate. -/// -pub fn s( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("s", attrs, children) -} - -/// Used to enclose inline text which represents sample (or quoted) output from -/// a computer program. Its contents are typically rendered using the browser's -/// default monospaced font (such as Courier or Lucida Console). -/// -pub fn samp( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("samp", attrs, children) -} - -/// Represents side-comments and small print, like copyright and legal text, -/// independent of its styled presentation. By default, it renders text within -/// it one font size smaller, such as from small to x-small. -/// -pub fn small( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("small", attrs, children) -} - -/// A generic inline container for phrasing content, which does not inherently -/// represent anything. It can be used to group elements for styling purposes -/// (using the class or id attributes), or because they share attribute values, -/// such as lang. It should be used only when no other semantic element is -/// appropriate. <span> is very much like a div element, but div is a block-level -/// element whereas a <span> is an inline-level element. -/// -pub fn span( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("span", attrs, children) -} - -/// Indicates that its contents have strong importance, seriousness, or urgency. -/// Browsers typically render the contents in bold type. -/// -pub fn strong( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("strong", attrs, children) -} - -/// Specifies inline text which should be displayed as subscript for solely -/// typographical reasons. Subscripts are typically rendered with a lowered -/// baseline using smaller text. -/// -pub fn sub( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("sub", attrs, children) -} - -/// Specifies inline text which is to be displayed as superscript for solely -/// typographical reasons. Superscripts are usually rendered with a raised -/// baseline using smaller text. -/// -pub fn sup( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("sup", attrs, children) -} - -/// Represents a specific period in time. It may include the datetime attribute -/// to translate dates into machine-readable format, allowing for better search -/// engine results or custom features such as reminders. -/// -pub fn time( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("time", attrs, children) -} - -/// Represents a span of inline text which should be rendered in a way that -/// indicates that it has a non-textual annotation. This is rendered by default -/// as a simple solid underline but may be altered using CSS. -/// -pub fn u( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("u", attrs, children) -} - -/// Represents the name of a variable in a mathematical expression or a -/// programming context. It's typically presented using an italicized version of -/// the current typeface, although that behavior is browser-dependent. -/// -pub fn var( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("var", attrs, children) -} - -/// Represents the name of a variable in a mathematical expression or a -/// programming context. It's typically presented using an italicized version of -/// the current typeface, although that behavior is browser-dependent. -/// -pub fn wbr(attrs: List(Attribute(msg))) -> Element(msg) { - element("wbr", attrs, []) -} - -// HTML ELEMENTS: IMAGE AND MULTIMEDIA ----------------------------------------- - -/// Represents a word break opportunity—a position within text where the browser -/// may optionally break a line, though its line-breaking rules would not -/// otherwise create a break at that location. -/// -pub fn area(attrs: List(Attribute(msg))) -> Element(msg) { - element("area", attrs, []) -} - -/// Defines an area inside an image map that has predefined clickable areas. An -/// image map allows geometric areas on an image to be associated with hyperlink. -/// -pub fn audio( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("audio", attrs, children) -} - -/// Used to embed sound content in documents. It may contain one or more audio -/// sources, represented using the src attribute or the source element: the -/// browser will choose the most suitable one. It can also be the destination -/// for streamed media, using a MediaStream. -/// -pub fn img(attrs: List(Attribute(msg))) -> Element(msg) { - element("img", attrs, []) -} - -/// Used with <area> elements to define an image map (a clickable link area). -/// -pub fn map( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("map", attrs, children) -} - -/// Used as a child of the media elements, audio and video. It lets you specify -/// timed text tracks (or time-based data), for example to automatically handle -/// subtitles. The tracks are formatted in WebVTT format (.vtt files)—Web Video -/// Text Tracks. -/// -pub fn track(attrs: List(Attribute(msg))) -> Element(msg) { - element("track", attrs, []) -} - -/// Embeds a media player which supports video playback into the document. You -/// can also use <video> for audio content, but the audio element may provide a -/// more appropriate user experience. -/// -pub fn video( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("video", attrs, children) -} - -// HTML ELEMENTS: EMBEDDED CONTENT --------------------------------------------- - -/// Embeds external content at the specified point in the document. This content -///is provided by an external application or other source of interactive content -/// such as a browser plug-in. -/// -pub fn embed(attrs: List(Attribute(msg))) -> Element(msg) { - element("embed", attrs, []) -} - -/// Represents a nested browsing context, embedding another HTML page into the -/// current one. -/// -pub fn iframe(attrs: List(Attribute(msg))) -> Element(msg) { - element("iframe", attrs, []) -} - -/// Represents an external resource, which can be treated as an image, a nested -/// browsing context, or a resource to be handled by a plugin. -/// -pub fn object(attrs: List(Attribute(msg))) -> Element(msg) { - element("object", attrs, []) -} - -/// Contains zero or more <source> elements and one <img> element to offer -/// alternative versions of an image for different display/device scenarios. -/// -pub fn picture( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("picture", attrs, children) -} - -/// Enables the embedding of another HTML page into the current one to enable -/// smoother navigation into new pages. -/// -pub fn portal(attrs: List(Attribute(msg))) -> Element(msg) { - element("portal", attrs, []) -} - -/// Specifies multiple media resources for the picture, the audio element, or -/// the video element. It is a void element, meaning that it has no content and -/// does not have a closing tag. It is commonly used to offer the same media -/// content in multiple file formats in order to provide compatibility with a -/// broad range of browsers given their differing support for image file formats -/// and media file formats. -/// -pub fn source(attrs: List(Attribute(msg))) -> Element(msg) { - element("source", attrs, []) -} - -// HTML ELEMENTS: SVG AND MATHML ----------------------------------------------- - -/// Container defining a new coordinate system and viewport. It is used as the -/// outermost element of SVG documents, but it can also be used to embed an SVG -/// fragment inside an SVG or HTML document. -/// -pub fn svg( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced("http://www.w3.org/2000/svg", "svg", attrs, children) -} - -/// The top-level element in MathML. Every valid MathML instance must be wrapped -/// in it. In addition, you must not nest a second <math> element in another, -/// but you can have an arbitrary number of other child elements in it. -/// -pub fn math( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("math", attrs, children) -} - -// HTML ELEMENTS: SCRIPTING ---------------------------------------------------- - -/// Container element to use with either the canvas scripting API or the WebGL -/// API to draw graphics and animations. -/// -pub fn canvas(attrs: List(Attribute(msg))) -> Element(msg) { - element("canvas", attrs, []) -} - -/// Defines a section of HTML to be inserted if a script type on the page is -/// unsupported or if scripting is currently turned off in the browser. -/// -pub fn noscript( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element("noscript", attrs, children) -} - -/// Used to embed executable code or data; this is typically used to embed or -/// refer to JavaScript code. The <script> element can also be used with other -/// languages, such as WebGL's GLSL shader programming language and JSON. -/// -pub fn script(attrs: List(Attribute(msg)), js: String) -> Element(msg) { - element("script", attrs, [text(js)]) -} - -// HTML ELEMENTS: DEMARCATING EDITS --------------------------------------------- - -/// Represents a range of text that has been deleted from a document. This can -/// be used when rendering "track changes" or source code diff information, for -/// example. The <ins> element can be used for the opposite purpose: to indicate -/// text that has been added to the document. -/// -pub fn del( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("del", attrs, children) -} - -/// Represents a range of text that has been added to a document. You can use the -/// <del> element to similarly represent a range of text that has been deleted -/// from the document. -/// -pub fn ins( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("ins", attrs, children) -} - -// HTML ELEMENTS: TABLE CONTENT ------------------------------------------------ - -/// Specifies the caption (or title) of a table. -/// -pub fn caption( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("caption", attrs, children) -} - -/// Defines a column within a table and is used for defining common semantics on -/// all common cells. It is generally found within a <colgroup> element. -/// -pub fn col(attrs: List(Attribute(msg))) -> Element(msg) { - element.element("col", attrs, []) -} - -/// Defines a group of columns within a table. -/// -pub fn colgroup( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("colgroup", attrs, children) -} - -/// Represents tabular data — that is, information presented in a two-dimensional -/// table comprised of rows and columns of cells containing data. -/// -pub fn table( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("table", attrs, children) -} - -/// Encapsulates a set of table rows (<tr> elements), indicating that they -/// comprise the body of the table (<table>). -/// -pub fn tbody( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("tbody", attrs, children) -} - -/// Defines a cell of a table that contains data. It participates in the table -/// model. -/// -pub fn td( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("td", attrs, children) -} - -/// Defines a set of rows summarizing the columns of the table. -/// -pub fn tfoot( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("tfoot", attrs, children) -} - -/// Defines a cell as a header of a group of table cells. The exact nature of -/// this group is defined by the scope and headers attributes. -/// -pub fn th( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("th", attrs, children) -} - -/// Defines a set of rows defining the head of the columns of the table. -/// -pub fn thead( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("thead", attrs, children) -} - -/// Defines a row of cells in a table. The row's cells can then be established -/// using a mix of <td> (data cell) and <th> (header cell) elements. -/// -pub fn tr( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("tr", attrs, children) -} - -// HTML ELEMENTS: FORMS -------------------------------------------------------- - -/// An interactive element activated by a user with a mouse, keyboard, finger, -/// voice command, or other assistive technology. Once activated, it performs an -/// action, such as submitting a form or opening a dialog. -/// -pub fn button( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("button", attrs, children) -} - -/// Contains a set of <option> elements that represent the permissible or -/// recommended options available to choose from within other controls. -/// -pub fn datalist( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("datalist", attrs, children) -} - -/// Used to group several controls as well as labels (<label>) within a web form. -/// -pub fn fieldset( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("fieldset", attrs, children) -} - -/// Represents a document section containing interactive controls for submitting -/// information. -/// -pub fn form( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("form", attrs, children) -} - -/// Used to create interactive controls for web-based forms to accept data from -/// he user; a wide variety of types of input data and control widgets are -/// available, depending on the device and user agent. The <input> element is -/// one of the most powerful and complex in all of HTML due to the sheer number -/// of combinations of input types and attributes. -/// -pub fn input(attrs: List(Attribute(msg))) -> Element(msg) { - element.element("input", attrs, []) -} - -/// Represents a caption for an item in a user interface. -/// -pub fn label( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("label", attrs, children) -} - -/// Represents a caption for the content of its parent <fieldset>. -/// -pub fn legend( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("legend", attrs, children) -} - -/// Represents either a scalar value within a known range or a fractional value. -/// -pub fn meter( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("meter", attrs, children) -} - -/// Creates a grouping of options within a <select> element. -/// -pub fn optgroup( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("optgroup", attrs, children) -} - -/// Used to define an item contained in a select, an <optgroup>, or a <datalist> -/// element. As such, <option> can represent menu items in popups and other lists -/// of items in an HTML document. -/// -pub fn option(attrs: List(Attribute(msg))) -> Element(msg) { - element.element("option", attrs, []) -} - -/// Container element into which a site or app can inject the results of a -/// calculation or the outcome of a user action. -/// -pub fn output( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("output", attrs, children) -} - -/// Displays an indicator showing the completion progress of a task, typically -/// displayed as a progress bar. -/// -pub fn progress( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("progress", attrs, children) -} - -/// Represents a control that provides a menu of options. -/// -pub fn select( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("select", attrs, children) -} - -/// Represents a multi-line plain-text editing control, useful when you want to -/// allow users to enter a sizeable amount of free-form text, for example, a -/// comment on a review or feedback form. -/// -pub fn textarea(attrs: List(Attribute(msg))) -> Element(msg) { - element.element("textarea", attrs, []) -} - -// HTML ELEMENTS: INTERACTIVE ELEMENTS ----------------------------------------- - -/// Creates a disclosure widget in which information is visible only when the -/// widget is toggled into an "open" state. A summary or label must be provided -/// using the <summary> element. -/// -pub fn details( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("details", attrs, children) -} - -/// Represents a dialog box or other interactive component, such as a dismissible -/// alert, inspector, or subwindow. -/// -pub fn dialog( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("dialog", attrs, children) -} - -/// Specifies a summary, caption, or legend for a details element's disclosure box. -/// Clicking the <summary> element toggles the state of the parent <details> element -/// open and closed. -/// -pub fn summary( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("summary", attrs, children) -} - -// HTML ELEMENTS: WEB COMPONENTS ----------------------------------------------- - -/// Part of the Web Components technology suite, this element is a placeholder -/// inside a web component that you can fill with your own markup, which lets you -/// create separate DOM trees and present them together. -/// -pub fn slot(attrs: List(Attribute(msg))) -> Element(msg) { - element.element("slot", attrs, []) -} - -/// A mechanism for holding HTML that is not to be rendered immediately when a -/// page is loaded but may be instantiated subsequently during runtime using -/// JavaScript. -/// -pub fn template( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - element.element("template", attrs, children) -} diff --git a/src/lustre/element/svg.gleam b/src/lustre/element/svg.gleam deleted file mode 100644 index f9776ca..0000000 --- a/src/lustre/element/svg.gleam +++ /dev/null @@ -1,351 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/element.{Element, namespaced, text as inline_text} -import lustre/attribute.{Attribute} - -// CONSTANTS ------------------------------------------------------------------- - -const namespace = "http://www.w3.org/2000/svg" - -// The doc comments (and order) for functions in this module are taken from the -// MDN Element reference: -// -// https://developer.mozilla.org/en-US/docs/Web/SVG/Element -// - -// SVG ELEMENTS: ANIMATION ELEMENTS -------------------------------------------- - -pub fn animate(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "animate", attrs, []) -} - -pub fn animate_motion(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "animateMotion", attrs, []) -} - -pub fn animate_transform(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "animateTransform", attrs, []) -} - -pub fn mpath(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "mpath", attrs, []) -} - -pub fn set(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "set", attrs, []) -} - -// SVG ELEMENTS: BASIC SHAPES -------------------------------------------------- - -pub fn circle(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "circle", attrs, []) -} - -pub fn ellipse(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "ellipse", attrs, []) -} - -pub fn line(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "line", attrs, []) -} - -pub fn polygon(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "polygon", attrs, []) -} - -pub fn polyline(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "polyline", attrs, []) -} - -pub fn rect(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "rect", attrs, []) -} - -// SVG ELEMENTS: CONTAINER ELEMENTS -------------------------------------------- - -pub fn a( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "a", attrs, children) -} - -pub fn defs( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "defs", attrs, children) -} - -pub fn g( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "g", attrs, children) -} - -pub fn marker( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "marker", attrs, children) -} - -pub fn mask( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "mask", attrs, children) -} - -pub fn missing_glyph( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "missing-glyph", attrs, children) -} - -pub fn pattern( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "pattern", attrs, children) -} - -pub fn svg( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "svg", attrs, children) -} - -pub fn switch( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "switch", attrs, children) -} - -pub fn symbol( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "symbol", attrs, children) -} - -// SVG ELEMENTS: DESCRIPTIVE ELEMENTS ------------------------------------------ - -pub fn desc( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "desc", attrs, children) -} - -pub fn metadata( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "metadata", attrs, children) -} - -pub fn title( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "title", attrs, children) -} - -// SVG ELEMENTS: FILTER EFFECTS ------------------------------------------------ - -pub fn fe_blend(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feBlend", attrs, []) -} - -pub fn fe_color_matrix(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feColorMatrix", attrs, []) -} - -pub fn fe_component_transfer(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feComponentTransfer", attrs, []) -} - -pub fn fe_composite(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feComposite", attrs, []) -} - -pub fn fe_convolve_matrix(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feConvolveMatrix", attrs, []) -} - -pub fn fe_diffuse_lighting( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "feDiffuseLighting", attrs, children) -} - -pub fn fe_displacement_map(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feDisplacementMap", attrs, []) -} - -pub fn fe_drop_shadow(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feDropShadow", attrs, []) -} - -pub fn fe_flood(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feFlood", attrs, []) -} - -pub fn fe_func_a(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feFuncA", attrs, []) -} - -pub fn fe_func_b(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feFuncB", attrs, []) -} - -pub fn fe_func_g(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feFuncG", attrs, []) -} - -pub fn fe_func_r(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feFuncR", attrs, []) -} - -pub fn fe_gaussian_blur(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feGaussianBlur", attrs, []) -} - -pub fn fe_image(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feImage", attrs, []) -} - -pub fn fe_merge( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "feMerge", attrs, children) -} - -pub fn fe_merge_node(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feMergeNode", attrs, []) -} - -pub fn fe_morphology(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feMorphology", attrs, []) -} - -pub fn fe_offset(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feOffset", attrs, []) -} - -pub fn fe_specular_lighting( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "feSpecularLighting", attrs, children) -} - -pub fn fe_tile( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "feTile", attrs, children) -} - -pub fn fe_turbulence(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feTurbulence", attrs, []) -} - -// SVG ELEMENTS: GRADIENT ELEMENTS --------------------------------------------- - -pub fn linear_gradient( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "linearGradient", attrs, children) -} - -pub fn radial_gradient( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "radialGradient", attrs, children) -} - -pub fn stop(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "stop", attrs, []) -} - -// SVG ELEMENTS: GRAPHICAL ELEMENTS -------------------------------------------- - -pub fn image(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "image", attrs, []) -} - -pub fn path(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "path", attrs, []) -} - -pub fn text(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "text", attrs, []) -} - -pub fn use_(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "use", attrs, []) -} - -// SVG ELEMENTS: LIGHTING ELEMENTS --------------------------------------------- - -pub fn fe_distant_light(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feDistantLight", attrs, []) -} - -pub fn fe_point_light(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "fePointLight", attrs, []) -} - -pub fn fe_spot_light(attrs: List(Attribute(msg))) -> Element(msg) { - namespaced(namespace, "feSpotLight", attrs, []) -} - -// SVG ELEMENTS: NEVER-RENDERED ELEMENTS --------------------------------------- - -pub fn clip_path( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "clipPath", attrs, children) -} - -pub fn script(attrs: List(Attribute(msg)), js: String) -> Element(msg) { - namespaced(namespace, "script", attrs, [inline_text(js)]) -} - -pub fn style(attrs: List(Attribute(msg)), css: String) -> Element(msg) { - namespaced(namespace, "style", attrs, [inline_text(css)]) -} - -// SVG ELEMENTS: RENDERABLE ELEMENTS ------------------------------------------- - -pub fn foreign_object( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "foreignObject", attrs, children) -} - -pub fn text_path( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "textPath", attrs, children) -} - -pub fn tspan( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - namespaced(namespace, "tspan", attrs, children) -} diff --git a/src/lustre/event.gleam b/src/lustre/event.gleam deleted file mode 100644 index 8ee2f4f..0000000 --- a/src/lustre/event.gleam +++ /dev/null @@ -1,184 +0,0 @@ -//// - -// IMPORTS --------------------------------------------------------------------- - -import gleam/dynamic.{DecodeError, Dynamic} -import gleam/option.{None, Option, Some} -import gleam/result -import lustre/attribute.{Attribute} -import lustre/effect.{Effect} - -// TYPES ----------------------------------------------------------------------- - -type Decoded(a) = - Result(a, List(DecodeError)) - -// EFFECTS --------------------------------------------------------------------- - -@target(javascript) -@external(javascript, "../lustre.ffi.mjs", "emit") -pub fn emit(event: String, data: any) -> Effect(msg) - -// CUSTOM EVENTS --------------------------------------------------------------- - -pub fn on(name: String, handler: fn(Dynamic) -> Option(msg)) -> Attribute(msg) { - attribute.on(name, handler) -} - -// MOUSE EVENTS ---------------------------------------------------------------- - -/// -pub fn on_click(msg: msg) -> Attribute(msg) { - use _ <- on("click") - Some(msg) -} - -/// -pub fn on_mouse_down(msg: msg) -> Attribute(msg) { - use _ <- on("mousedown") - Some(msg) -} - -/// -pub fn on_mouse_up(msg: msg) -> Attribute(msg) { - use _ <- on("mouseup") - Some(msg) -} - -/// -pub fn on_mouse_enter(msg: msg) -> Attribute(msg) { - use _ <- on("mouseenter") - Some(msg) -} - -/// -pub fn on_mouse_leave(msg: msg) -> Attribute(msg) { - use _ <- on("mouseleave") - Some(msg) -} - -/// -pub fn on_mouse_over(msg: msg) -> Attribute(msg) { - use _ <- on("mouseover") - Some(msg) -} - -/// -pub fn on_mouse_out(msg: msg) -> Attribute(msg) { - use _ <- on("mouseout") - Some(msg) -} - -// KEYBOARD EVENTS ------------------------------------------------------------- - -/// Listens for key presses on an element, and dispatches a message with the -/// current key being pressed. -/// -pub fn on_keypress(msg: fn(String) -> msg) -> Attribute(msg) { - use event <- on("keypress") - - case dynamic.field("key", dynamic.string)(event) { - Ok(key) -> Some(msg(key)) - Error(_) -> None - } -} - -/// Listens for key dow events on an element, and dispatches a message with the -/// current key being pressed. -/// -pub fn on_keydown(msg: fn(String) -> msg) -> Attribute(msg) { - use event <- on("keydown") - - case dynamic.field("key", dynamic.string)(event) { - Ok(key) -> Some(msg(key)) - Error(_) -> None - } -} - -/// Listens for key up events on an element, and dispatches a message with the -/// current key being released. -/// -pub fn on_keyup(msg: fn(String) -> msg) -> Attribute(msg) { - use event <- on("keyup") - - case dynamic.field("key", dynamic.string)(event) { - Ok(key) -> Some(msg(key)) - Error(_) -> None - } -} - -// FORM EVENTS ----------------------------------------------------------------- - -/// -pub fn on_input(msg: fn(String) -> msg) -> Attribute(msg) { - use event <- on("input") - - case value(event) { - Ok(val) -> Some(msg(val)) - Error(_) -> None - } -} - -pub fn on_check(msg: fn(Bool) -> msg) -> Attribute(msg) { - use event <- on("change") - - case checked(event) { - Ok(val) -> Some(msg(val)) - Error(_) -> None - } -} - -pub fn on_submit(msg: msg) -> Attribute(msg) { - use _ <- on("submit") - Some(msg) -} - -// FOCUS EVENTS ---------------------------------------------------------------- - -pub fn on_focus(msg: msg) -> Attribute(msg) { - use _ <- on("focus") - Some(msg) -} - -pub fn on_blur(msg: msg) -> Attribute(msg) { - use _ <- on("blur") - Some(msg) -} - -// DECODERS -------------------------------------------------------------------- - -/// A helpful decoder to extract the `value` from an event object. This is handy -/// for getting the value as a string from an input event, for example. -/// -pub fn value(event: Dynamic) -> Decoded(String) { - event - |> dynamic.field("target", dynamic.field("value", dynamic.string)) -} - -/// A helpful decoder to extract the `checked` property from an event triggered -/// by a checkbox. -/// -pub fn checked(event: Dynamic) -> Decoded(Bool) { - event - |> dynamic.field("target", dynamic.field("checked", dynamic.bool)) -} - -/// A helpful decoder to grab the mouse's current x and y position in the -/// viewport from an event object. -/// -pub fn mouse_position(event: Dynamic) -> Decoded(#(Float, Float)) { - use x <- result.then(dynamic.field("clientX", dynamic.float)(event)) - use y <- result.then(dynamic.field("clientY", dynamic.float)(event)) - - Ok(#(x, y)) -} - -// UTILS ----------------------------------------------------------------------- - -@target(javascript) -@external(javascript, "../lustre.ffi.mjs", "prevent_default") -pub fn prevent_default(event: Dynamic) -> Nil - -@target(javascript) -@external(javascript, "../lustre.ffi.mjs", "stop_propagation") -pub fn stop_propagation(event: Dynamic) -> Nil diff --git a/src/runtime.ffi.mjs b/src/runtime.ffi.mjs deleted file mode 100644 index 7325583..0000000 --- a/src/runtime.ffi.mjs +++ /dev/null @@ -1,230 +0,0 @@ -import { element, namespaced, text } from "./lustre/element.mjs"; -import { List, Empty } from "./gleam.mjs"; -import { Some, None } from "../gleam_stdlib/gleam/option.mjs"; - -const Element = element("").constructor; -const ElementNs = namespaced("", "").constructor; -const Text = text("").constructor; - -export function morph(prev, curr, parent) { - if (curr instanceof ElementNs) - return prev?.nodeType === 1 && - prev.nodeName === curr[0].toUpperCase() && - prev.namespaceURI === curr[3] - ? morphElement(prev, curr, curr[3], parent) - : createElement(prev, curr, curr[3], parent); - - if (curr instanceof Element) { - return prev?.nodeType === 1 && prev.nodeName === curr[0].toUpperCase() - ? morphElement(prev, curr, null, parent) - : createElement(prev, curr, null, parent); - } - - if (curr instanceof Text) { - return prev?.nodeType === 3 - ? morphText(prev, curr) - : createText(prev, curr); - } - - return document.createComment( - [ - "[internal lustre error] I couldn't work out how to render this element. This", - "function should only be called internally by lustre's runtime: if you think", - "this is an error, please open an issue at", - "https://github.come/hayleigh-dot-dev/lustre/issues/new.", - ].join(" ") - ); -} - -// ELEMENTS -------------------------------------------------------------------- - -function createElement(prev, curr, ns, parent = null) { - const el = ns - ? document.createElementNS(ns, curr[0]) - : document.createElement(curr[0]); - - let attr = curr[1]; - while (attr.head) { - morphAttr(el, attr.head[0], attr.head[1]); - attr = attr.tail; - } - - if (customElements.get(curr[0])) { - el._slot = curr[2]; - } else if (curr[0] === "slot") { - let child = new Empty(); - let parentWithSlot = parent; - - while (parentWithSlot) { - if (parentWithSlot._slot) { - child = parentWithSlot._slot; - break; - } else { - parentWithSlot = parentWithSlot.parentNode; - } - } - - while (child.head) { - el.appendChild(morph(null, child.head, el)); - child = child.tail; - } - } else { - let child = curr[2]; - while (child.head) { - el.appendChild(morph(null, child.head, el)); - child = child.tail; - } - - if (prev) prev.replaceWith(el); - } - return el; -} - -function morphElement(prev, curr, ns, parent) { - const prevAttrs = prev.attributes; - const currAttrs = new Map(); - - let currAttr = curr[1]; - while (currAttr.head) { - currAttrs.set(currAttr.head[0], currAttr.head[1]); - currAttr = currAttr.tail; - } - - for (const { name, value: prevValue } of prevAttrs) { - if (!currAttrs.has(name)) { - prev.removeAttribute(name); - } else { - const value = currAttrs.get(name); - - if (value !== prevValue) { - morphAttr(prev, name, value); - currAttrs.delete(name); - } - } - } - - for (const [name, value] of currAttrs) { - morphAttr(prev, name, value); - } - - if (customElements.get(curr[0])) { - prev._slot = curr[2]; - } else if (curr[0] === "slot") { - let prevChild = prev.firstChild; - let currChild = new Empty(); - let parentWithSlot = parent; - - while (parentWithSlot) { - if (parentWithSlot._slot) { - currChild = parentWithSlot._slot; - break; - } else { - parentWithSlot = parentWithSlot.parentNode; - } - } - - while (prevChild) { - if (currChild.head) { - morph(prevChild, currChild.head, prev); - currChild = currChild.tail; - } - - prevChild = prevChild.nextSibling; - } - - while (currChild.head) { - prev.appendChild(morph(null, currChild.head, prev)); - currChild = currChild.tail; - } - } else { - let prevChild = prev.firstChild; - let currChild = curr[2]; - - while (prevChild) { - if (currChild.head) { - const next = prevChild.nextSibling; - morph(prevChild, currChild.head, prev); - currChild = currChild.tail; - prevChild = next; - } else { - const next = prevChild.nextSibling; - prevChild.remove(); - prevChild = next; - } - } - - while (currChild.head) { - prev.appendChild(morph(null, currChild.head, prev)); - currChild = currChild.tail; - } - } - - return prev; -} - -// ATTRIBUTES ------------------------------------------------------------------ - -function morphAttr(el, name, value) { - switch (typeof value) { - case "string": - el.setAttribute(name, value); - break; - - // Boolean attributes work a bit differently in HTML. Their presence always - // implies true: to set an attribute to false you need to remove it entirely. - case "boolean": - value ? el.setAttribute(name, name) : el.removeAttribute(name); - break; - - // Event listeners need to be handled slightly differently because we need - // to be able to support custom events. We - case name.startsWith("on") && "function": { - const event = name.slice(2).toLowerCase(); - - if (el[`_${name}`] === value) break; - - el.removeEventListener(event, el[`_${name}`]); - el.addEventListener(event, value); - el[`_${name}`] = value; - break; - } - - default: { - el[name] = toJsValue(value); - } - } -} - -function toJsValue(value) { - if (value instanceof List) { - return value.toArray().map(toJsValue); - } - - if (value instanceof Some) { - return toJsValue(value[0]); - } - - if (value instanceof None) { - return null; - } - - return value; -} - -// TEXT ------------------------------------------------------------------------ - -function createText(prev, curr) { - const el = document.createTextNode(curr[0]); - - if (prev) prev.replaceWith(el); - return el; -} - -function morphText(prev, curr) { - const prevValue = prev.nodeValue; - const currValue = curr[0]; - - if (prevValue !== currValue) prev.nodeValue = currValue; - - return prev; -} |