diff options
author | Hayleigh Thompson <me@hayleigh.dev> | 2023-07-18 00:18:52 +0100 |
---|---|---|
committer | Hayleigh Thompson <me@hayleigh.dev> | 2023-07-18 00:18:52 +0100 |
commit | 63b4cac40ee7315797174315be703816a1e2e743 (patch) | |
tree | f2c4fff3de30784d850fce19fce85a02578d70c1 | |
parent | f8dfa31c7abe3654f3a6dc9949424afea6eba4ce (diff) | |
download | lustre-63b4cac40ee7315797174315be703816a1e2e743.tar.gz lustre-63b4cac40ee7315797174315be703816a1e2e743.zip |
:recycle: Represent elements as a normal Gleam custom type.
-rw-r--r-- | src/lustre/element.gleam | 1084 |
1 files changed, 67 insertions, 1017 deletions
diff --git a/src/lustre/element.gleam b/src/lustre/element.gleam index ca73082..7f1479c 100644 --- a/src/lustre/element.gleam +++ b/src/lustre/element.gleam @@ -1,1057 +1,107 @@ -//// - // IMPORTS --------------------------------------------------------------------- -import lustre/attribute.{Attribute, attribute} +import gleam/list +import gleam/string +import gleam/string_builder.{StringBuilder} +import lustre/attribute.{Attribute} // TYPES ----------------------------------------------------------------------- -/// -/// -pub type Element(msg) - -// CONSTRUCTORS ---------------------------------------------------------------- - -/// Construct a plain HTML element or registered Web Component by providing the -/// tag name, a list of attributes (including event handlers), and a list of -/// child elements. -/// -@external(javascript, "../lustre.ffi.mjs", "node") -pub fn node(tag tag: String, attrs attrs: List(Attribute(msg)), children children: List( - Element(msg), - )) -> Element(msg) - -// pub external fn stateful( -// init: state, -// render: fn(state, fn(state) -> Nil) -> Element(msg), -// ) -> Element(msg) = -// "../lustre.ffi.mjs" "stateful" - -// pub external fn fragment(children: List(Element(msg))) -> Element(msg) = -// "../lustre.ffi.mjs" "fragment" - -/// A stateful element is exactly what it sounds like: an element with local -/// encapsulated state! The `render` function we must provide is called with the -/// element's current state as well as a function to set a new state. Whenever -/// that function is called, the element is re-rendered. -/// -/// You might be wondering where the `stateless` version of this function is. -/// Those are just regular Gleam functions that return `Element`s! /// -/// We can implement a counter whose current value is stored locally like so: -/// -/// ```gleam -/// import gleam/int -/// import lustre/attribute.{Attribute} -/// import lustre/element.{Element} -/// import lustre/event -/// -/// pub fn counter() -> Element(msg) { -/// use state, set_state = lustre.stateful(0) -/// -/// let decr = event.on("click", fn(_, _) { set_state(state - 1) }) -/// let incr = event.on("click", fn(_, _) { set_state(state + 1) }) /// -/// element.div([], [ -/// element.button([decr], [element.text("-")]), -/// element.text(int.to_string(state)), -/// element.button([incr], [element.text("+")]), -/// ]) -/// } -/// ``` -/// -/// We can use this `counter` element anywhere in our app, we don't have to worry -/// about passing state around or managing it in a parent component. -/// -/// When attaching event handlers to stateful elements, you'll typically have to -/// use the generic `event.on` function rather than the more specific helpders. -/// The helpers are designed to emit messages for your application, but stateful -/// elements don't necessarily produce messages: calling `set_state` is a side -/// effect. -/// -/// A fragment doesn't appear in the DOM, but allows us to treat a list of elements -/// as if it were a single one. -/// -/// Render a Gleam string as an HTML text node. -/// -@external(javascript, "../lustre.ffi.mjs", "text") -pub fn text(content content: String) -> Element(msg) - -// MANIPULATIONS --------------------------------------------------------------- - -/// Transforms the actions produced by an element. Our Lustre applications have -/// a single top-level `Msg` type and `update` function, and ultimately all -/// elements we create must produce actions of that type (or not produce any at -/// all). -/// -/// To save us having a monolothic `Msg` type with many constructors, we can crete -/// child types and then use `map` to wrap them up in the parent type. A simple -/// counter with its own message type my look like this: -/// -/// ```gleam -/// import gleam/int -/// import lustre/element.{Element} -/// import lustre/event -/// -/// type Msg { -/// Decr -/// Incr -/// } -/// -/// pub fn update(count: Int, msg: CounterMsg) -> Int { -/// case msg { -/// Decr -> count - 1 -/// Incr -> count + 1 -/// } -/// } -/// -/// pub fn render(count: Int) -> Element(CounterMsg) { -/// element.div([], [ -/// element.button([event.on_click(Decr)], [element.text("-")]), -/// element.text(int.to_string(count)), -/// element.button([event.on_click(Incr)], [element.text("+")]), -/// ]) -/// } -/// ``` -/// -/// To integrate this into a large Lustre application, we need to do some plumbing. -/// Here's how we might have a parent application that renders multiple counters: -/// -/// ```gleam -/// import counter.{Msg as CounterMsg} -/// import gleam/list -/// import gleam/map.{Map} -/// import gleam/result -/// import lustre -/// import lustre/element.{Element} -/// import lustre/event -/// -/// pub fn main() { -/// let app = lustre.simple(init(), update, render) -/// assert Ok(_) = lustre.start(app, "#root") -/// } -/// -/// type Model = Map(Int, Int) -/// -/// fn init() -> Model { -/// map.from_list([ -/// #(0, 0), -/// #(1, 0), -/// #(2, 0), -/// ]) -/// } -/// -/// type Msg { -/// UpdateCounter(Int, CounterMsg) -/// } -/// -/// pub fn update(model: Model, msg: Msg) -> Model { -/// case msg { -/// UpdateCounter(id, counter_msg) -> { -/// map.get(model, id) -/// // Call the counter's update function with the `counter_msg`. This will -/// // either be `counter.Decr` or `counter.Incr`. -/// |> result.map(counter.update(_, counter_msg)) -/// |> result.map(map.insert(model, id, _)) -/// |> result.unwrap(model) -/// } -/// } -/// } -/// -/// pub fn render(model: Model) -> Element(Msg) { -/// let counters = { -/// use els, id, count <- map.fold(model, []) -/// let counter = counter.render(counr) -/// -/// // Right now `counter` produces messages of type `counter.Msg`. Here we're -/// // calling `element.map` to wrap that message in our main app's `Msg` type. -/// [element.map(counter, UpdateCounter(id, _)), ...els] -/// } -/// -/// element.div([], list.reverse(counters)) -/// } -/// ``` -/// -/// If this feels like a lt of work... sometimes it is! Take a look at the docs -/// for [`stateful`](#stateful) elements to see how all this can be encapsulated. -/// -@external(javascript, "../lustre.ffi.mjs", "map") -pub fn map(element element: Element(a), f f: fn(a) -> b) -> Element(b) - -// CONSTRUCTING NODES ---------------------------------------------------------- -// This list and grouping of nodes has been taken from the MDN reference at: -// https://developer.mozilla.org/en-US/docs/Web/HTML/Element - -// MAIN ROOT ------------------------------------------------------------------- - -/// -pub fn html( - attrs: List(Attribute(msg)), - head: Element(msg), - body: Element(msg), -) -> Element(msg) { - node("html", attrs, [head, body]) -} - -// DOCUMENT METADATA ----------------------------------------------------------- - -/// -pub fn base(attrs: List(Attribute(msg))) -> Element(msg) { - node("base", attrs, []) -} - -/// -pub fn head( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("head", attrs, children) -} - -/// -pub fn meta(attrs: List(Attribute(msg))) -> Element(msg) { - node("meta", attrs, []) -} - -/// -pub fn style(attrs: List(Attribute(msg)), css: String) -> Element(msg) { - node("style", attrs, [text(css)]) -} - -/// -pub fn title(attrs: List(Attribute(msg)), name: String) -> Element(msg) { - node("title", attrs, [text(name)]) -} - -// SECTIONING ROOT ------------------------------------------------------------- - -/// -pub fn body( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("body", attrs, children) -} - -// CONTENT SECTIONING ---------------------------------------------------------- - -/// -pub fn address( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("address", attrs, children) -} - -/// -pub fn article( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("article", attrs, children) -} - -/// -pub fn aside( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("aside", attrs, children) -} - -/// -pub fn footer( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("footer", attrs, children) -} - -/// -pub fn header( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("header", attrs, children) -} - -/// -pub fn h1( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("h1", attrs, children) -} - -/// -pub fn h2( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("h2", attrs, children) -} - -/// -pub fn h3( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("h3", attrs, children) -} - -/// -pub fn h4( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("h4", attrs, children) -} - -/// -pub fn h5( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("h5", attrs, children) -} - -/// -pub fn h6( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("h6", attrs, children) -} - -/// -pub fn main( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("main", attrs, children) -} - -/// -pub fn nav( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("nav", attrs, children) -} - -/// -pub fn section( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("section", attrs, children) -} - -// TEXT CONTENT ---------------------------------------------------------------- - -/// -pub fn blockquote( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("blockquote", attrs, children) -} - -/// -pub fn dd( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("dd", attrs, children) -} - -/// -pub fn div( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("div", attrs, children) -} - -/// -pub fn dl( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("dl", attrs, children) -} - -/// -pub fn dt( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("dt", attrs, children) -} - -/// -pub fn figcaption( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("figcaption", attrs, children) -} - -/// -pub fn figure( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("figure", attrs, children) -} - -/// -pub fn hr(attrs: List(Attribute(msg))) -> Element(msg) { - node("hr", attrs, []) -} - -/// -pub fn li( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("li", attrs, children) -} - -/// -pub fn menu( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("menu", attrs, children) -} - -/// -pub fn ol( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("ol", attrs, children) -} - -/// -pub fn p( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("p", attrs, children) -} - -/// -pub fn pre( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("pre", attrs, children) -} - -/// -pub fn ul( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("ul", attrs, children) -} - -// INLINE TEXT SEMANTICS ------------------------------------------------------- - -/// -pub fn a( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("a", attrs, children) -} - -/// -pub fn abbr( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("abbr", attrs, children) -} - -/// -pub fn b( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("b", attrs, children) -} - -/// -pub fn bdi( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("bdi", attrs, children) -} - -/// -pub fn bdo( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("bdo", attrs, children) -} - -/// -pub fn br(attrs: List(Attribute(msg))) -> Element(msg) { - node("br", attrs, []) -} - -/// -pub fn cite( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("cite", attrs, children) -} - -/// -pub fn code( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("code", attrs, children) -} - -/// -pub fn dfn( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("dfn", attrs, children) -} - -/// -pub fn em( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("em", attrs, children) -} - -/// -pub fn i( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("i", attrs, children) -} - -/// -pub fn kbd( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("kbd", attrs, children) -} - -/// -pub fn mark( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("mark", attrs, children) -} - -/// -pub fn rp( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("rp", attrs, children) -} - -/// -pub fn rt( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("rt", attrs, children) -} - -/// -pub fn ruby( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("ruby", attrs, children) -} - -/// -pub fn s( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("s", attrs, children) -} - -/// -pub fn samp( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("samp", attrs, children) -} - -/// -pub fn small( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("small", attrs, children) -} - -/// -pub fn span( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("span", attrs, children) -} - -/// -pub fn strong( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("strong", attrs, children) -} - -/// -pub fn sub( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("sub", attrs, children) -} - -/// -pub fn sup( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("sup", attrs, children) -} - -/// -pub fn time( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("time", attrs, children) -} - -/// -pub fn u( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("u", attrs, children) -} - -/// -pub fn var_( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("var", attrs, children) -} - -/// -pub fn wbr(attrs: List(Attribute(msg))) -> Element(msg) { - node("wbr", attrs, []) -} - -// IMAGE AND MULTIMEDIA -------------------------------------------------------- - -/// -pub fn area(attrs: List(Attribute(msg))) -> Element(msg) { - node("area", attrs, []) -} - -/// -pub fn audio( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("audio", attrs, children) -} - -/// -pub fn img(attrs: List(Attribute(msg))) -> Element(msg) { - node("img", attrs, []) -} - -/// -pub fn map_( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("map", attrs, children) -} - -/// -pub fn track( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("track", attrs, children) -} - -/// -pub fn video( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("video", attrs, children) -} - -// EMBEDDED CONTENT ------------------------------------------------------------ - -/// -pub fn embed(attrs: List(Attribute(msg))) -> Element(msg) { - node("embed", attrs, []) -} - -/// -pub fn iframe(attrs: List(Attribute(msg))) -> Element(msg) { - node("iframe", attrs, []) -} - -/// -pub fn object( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("object", attrs, children) -} - -/// -pub fn param( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("param", attrs, children) -} - -/// -pub fn picture( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("picture", attrs, children) -} - -/// -pub fn portal(attrs: List(Attribute(msg))) -> Element(msg) { - node("portal", attrs, []) -} - -/// -pub fn source(attrs: List(Attribute(msg))) -> Element(msg) { - node("source", attrs, []) -} - -// SVG AND MATHML -------------------------------------------------------------- - -/// -pub fn svg( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node( - "svg", - [attribute("xmlns", "http://www.w3.org/2000/svg"), ..attrs], - children, - ) -} - -/// -pub fn mathml( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node( - "mathml", - [attribute("xmlns", "http://www.w3.org/1998/Math/MathML"), ..attrs], - children, - ) -} - -// SCRIPTING ------------------------------------------------------------------- - -/// -pub fn canvas( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("canvas", attrs, children) -} - -/// -pub fn noscript( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("noscript", attrs, children) +pub opaque type Element(msg) { + Text(String) + Element(String, List(Attribute(msg)), List(Element(msg))) } -// DEMARCATING EDITS ----------------------------------------------------------- - -/// -pub fn del( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("del", attrs, children) -} - -/// -pub fn ins( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("ins", attrs, children) -} - -// TABLE CONTENT --------------------------------------------------------------- - -/// -pub fn caption( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("caption", attrs, children) -} - -/// -pub fn col( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("col", attrs, children) -} - -/// -pub fn colgroup( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("colgroup", attrs, children) -} - -/// -pub fn table( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("table", attrs, children) -} - -/// -pub fn tbody( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("tbody", attrs, children) -} - -/// -pub fn td( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("td", attrs, children) -} - -/// -pub fn tfoot( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("tfoot", attrs, children) -} - -/// -pub fn th( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("th", attrs, children) -} - -/// -pub fn thead( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("thead", attrs, children) -} - -/// -pub fn tr( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("tr", attrs, children) -} - -// FORMS ----------------------------------------------------------------------- - -/// -pub fn button( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("button", attrs, children) -} - -/// -pub fn datalist( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("datalist", attrs, children) -} - -/// -pub fn fieldset( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("fieldset", attrs, children) -} - -/// -pub fn form( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("form", attrs, children) -} - -/// -pub fn input(attrs: List(Attribute(msg))) -> Element(msg) { - node("input", attrs, []) -} - -/// -pub fn label( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("label", attrs, children) -} - -/// -pub fn legend( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("legend", attrs, children) -} - -/// -pub fn meter( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("meter", attrs, children) -} +// CONSTRUCTORS ---------------------------------------------------------------- /// -pub fn optgroup( +/// +pub fn h( + tag: String, attrs: List(Attribute(msg)), children: List(Element(msg)), ) -> Element(msg) { - node("optgroup", attrs, children) + Element(tag, attrs, children) } /// -pub fn option( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("option", attrs, children) +/// +pub fn t(content: String) -> Element(msg) { + Text(escape("", content)) } -/// -pub fn output( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("output", attrs, children) +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 + } } /// -pub fn progress( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("progress", attrs, children) +/// +pub fn unescaped_text(content: String) -> Element(msg) { + Text(content) } -/// -pub fn select( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("select", attrs, children) -} +// MANIPULATIONS --------------------------------------------------------------- /// -pub fn textarea(attrs: List(Attribute(msg))) -> Element(msg) { - node("textarea", attrs, []) +/// +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)), + ) + } } -// INTERACTIVE ELEMENTS -------------------------------------------------------- - -/// -pub fn details( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("details", attrs, children) -} +// CONVERSIONS ----------------------------------------------------------------- /// -pub fn dialog( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("dialog", attrs, children) +/// +pub fn to_string(element: Element(msg)) -> String { + to_string_builder(element) + |> string_builder.to_string } /// -pub fn summary( - attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("summary", attrs, children) +/// +pub fn to_string_builder(element: Element(msg)) -> StringBuilder { + case element { + Text(content) -> string_builder.from_string(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 <> ">") + } + } } -// WEB COMPONENTS -------------------------------------------------------------- - -/// -pub fn slot( +fn attrs_to_string_builder( + html: StringBuilder, attrs: List(Attribute(msg)), - children: List(Element(msg)), -) -> Element(msg) { - node("slot", attrs, children) +) -> StringBuilder { + use html, attr <- list.fold(attrs, html) + string_builder.append_builder(html, attribute.to_string_builder(attr)) } -/// -pub fn template( - attrs: List(Attribute(msg)), +fn children_to_string_builder( + html: StringBuilder, children: List(Element(msg)), -) -> Element(msg) { - node("template", attrs, children) +) -> StringBuilder { + use html, child <- list.fold(children, html) + string_builder.append_builder(html, to_string_builder(child)) } |