import gleam/list import gleam/option import gleam/pair import lustre/attribute.{type Attribute} import lustre/element as el import lustre/internals/vdom import sketch.{type Cache} pub opaque type Element(msg) { Nothing Text(content: String) Map(subtree: fn() -> Element(msg)) Element( key: String, namespace: String, tag: String, class: option.Option(sketch.Class), attributes: List(Attribute(msg)), children: List(Element(msg)), ) } pub fn none() { Nothing } pub fn text(content) { Text(content) } pub fn element( tag tag: String, class class: sketch.Class, attributes attributes: List(Attribute(msg)), children children: List(Element(msg)), ) { let class = option.Some(class) Element("", "", tag, class, attributes, children) } pub fn element_( tag tag: String, attributes attributes: List(Attribute(msg)), children children: List(Element(msg)), ) { Element("", "", tag, option.None, attributes, children) } pub fn namespaced( namespace namespace: String, tag tag: String, class class: sketch.Class, attributes attributes: List(Attribute(msg)), children children: List(Element(msg)), ) { let class = option.Some(class) Element("", namespace, tag, class, attributes, children) } pub fn namespaced_( namespace namespace: String, tag tag: String, attributes attributes: List(Attribute(msg)), children children: List(Element(msg)), ) { Element("", namespace, tag, option.None, attributes, children) } pub fn fragment(children: List(Element(msg))) { let attrs = [attribute.style([#("display", "contents")])] Element("", "", "lustre-fragment", option.None, attrs, children) } pub fn keyed( element: fn(List(Element(msg))) -> Element(msg), children: List(#(String, Element(msg))), ) { element({ use #(key, child) <- list.map(children) do_keyed(child, key) }) } fn do_keyed(element: Element(msg), key: String) { case element { Nothing -> Nothing Text(content) -> Text(content) Map(subtree) -> Map(fn() { do_keyed(subtree(), key) }) Element(_, namespace, tag, attributes, children, styles) -> Element(key, namespace, tag, attributes, children, styles) } } pub fn map(element: Element(a), mapper: fn(a) -> b) { case element { Nothing -> Nothing Text(content) -> Text(content) Map(subtree) -> Map(fn() { map(subtree(), mapper) }) Element(key, namespace, tag, class, attributes, children) -> { let attributes = list.map(attributes, attribute.map(_, mapper)) let children = list.map(children, map(_, mapper)) Element(key, namespace, tag, class, attributes, children) } } } pub fn styled(element: el.Element(msg)) { case element { vdom.Map(subtree) -> Map(fn() { styled(subtree()) }) vdom.Text(content) -> Text(content) vdom.Element(key, namespace, tag, attrs, children, _, _) -> { let class = option.None Element(key, namespace, tag, class, attrs, list.map(children, styled)) } } } pub fn unstyled(cache: Cache, element: Element(msg)) { case element { Nothing -> #(cache, el.none()) Text(content) -> #(cache, el.text(content)) Map(subtree) -> unstyled(cache, subtree()) Element(key, namespace, tag, class, attributes, children) -> { let class = option.map(class, sketch.class_name(_, cache)) let class_name = option.map(class, pair.second) let cache = option.map(class, pair.first) |> option.unwrap(cache) let #(cache, children) = unstyled_children(cache, children) let attributes = case class_name { option.None -> attributes option.Some(class_name) -> { let class_name = attribute.class(class_name) [class_name, ..attributes] } } #(cache, case el.element(tag, attributes, children) { vdom.Element(_, _, t, a, c, s, v) -> vdom.Element(key, namespace, t, a, c, s, v) e -> e }) } } } fn unstyled_children(cache, children) { list.fold(list.reverse(children), #(cache, []), fn(acc, child) { let #(cache, children) = acc let #(cache, child) = unstyled(cache, child) #(cache, [child, ..children]) }) }