diff options
author | Hayleigh Thompson <me@hayleigh.dev> | 2023-07-20 22:52:07 +0100 |
---|---|---|
committer | Hayleigh Thompson <me@hayleigh.dev> | 2023-07-20 22:52:07 +0100 |
commit | 3a60c2a1f44baf6c2d2cad2f329600e63d99f6c2 (patch) | |
tree | 6c53a71ca85809a357293df7beb6b1d9f6690b9a | |
parent | b6311d242f6775c973bd005e33f4cbb99653bd54 (diff) | |
download | lustre-3a60c2a1f44baf6c2d2cad2f329600e63d99f6c2.tar.gz lustre-3a60c2a1f44baf6c2d2cad2f329600e63d99f6c2.zip |
:recycle: Explicitly handle namespaced elements in the vdom tree.
-rw-r--r-- | src/lustre/element.gleam | 29 | ||||
-rw-r--r-- | src/lustre/html.gleam | 8 | ||||
-rw-r--r-- | src/runtime.ffi.mjs | 30 | ||||
-rw-r--r-- | test/examples/svg.gleam | 21 | ||||
-rw-r--r-- | test/examples/svg.html | 2 |
5 files changed, 59 insertions, 31 deletions
diff --git a/src/lustre/element.gleam b/src/lustre/element.gleam index 02fb41d..4e8abee 100644 --- a/src/lustre/element.gleam +++ b/src/lustre/element.gleam @@ -12,6 +12,7 @@ import lustre/attribute.{Attribute} 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 ---------------------------------------------------------------- @@ -27,6 +28,17 @@ pub fn element( } /// +/// +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) @@ -57,6 +69,13 @@ pub fn map(element: Element(a), f: fn(a) -> b) -> Element(b) { 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, + ) } } @@ -74,13 +93,19 @@ pub fn to_string(element: Element(msg)) -> String { pub fn to_string_builder(element: Element(msg)) -> StringBuilder { case element { Text(content) -> string_builder.from_string(escape("", content)) - Element(tag, attrs, children) -> { + 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 <> ">") - } } } diff --git a/src/lustre/html.gleam b/src/lustre/html.gleam index 4c7cf8c..9eb4f5e 100644 --- a/src/lustre/html.gleam +++ b/src/lustre/html.gleam @@ -1,6 +1,6 @@ // IMPORTS --------------------------------------------------------------------- -import lustre/element.{Element, element, text} +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 @@ -847,11 +847,7 @@ pub fn svg( attrs: List(Attribute(msg)), children: List(Element(msg)), ) -> Element(msg) { - element( - "svg", - [attribute.attribute("xmlns", "http://www.w3.org/2000/svg"), ..attrs], - children, - ) + namespaced("http://www.w3.org/2000/svg", "svg", attrs, children) } /// The top-level element in MathML. Every valid MathML instance must be wrapped diff --git a/src/runtime.ffi.mjs b/src/runtime.ffi.mjs index babd0b8..c7f6668 100644 --- a/src/runtime.ffi.mjs +++ b/src/runtime.ffi.mjs @@ -1,11 +1,20 @@ -import { element, text } from "./lustre/element.mjs"; +import { element, element_ns, text } from "./lustre/element.mjs"; import { List } from "./gleam.mjs"; +import { find } from "../gleam_stdlib/gleam/list.mjs"; import { Some, None } from "../gleam_stdlib/gleam/option.mjs"; const Element = element("").constructor; +const ElementNs = element_ns("", "").constructor; const Text = text("").constructor; export function morph(prev, curr) { + if (curr instanceof ElementNs) + return prev?.nodeType === 1 && + prev.nodeName === curr[0].toUpperCase() && + prev.namespaceURI === curr[3] + ? morphElement(prev, curr, curr[3]) + : createElement(prev, curr, curr[3]); + if (curr instanceof Element) { return prev?.nodeType === 1 && prev.nodeName === curr[0].toUpperCase() ? morphElement(prev, curr) @@ -30,11 +39,10 @@ export function morph(prev, curr) { // ELEMENTS -------------------------------------------------------------------- -function createElement(prev, curr) { - const el = - curr[0] === "svg" - ? document.createElementNS("http://www.w3.org/2000/svg", "svg") - : document.createElement(curr[0]); +function createElement(prev, curr, ns) { + const el = ns + ? document.createElementNS(ns, curr[0]) + : document.createElement(curr[0]); let attr = curr[1]; while (attr.head) { @@ -42,10 +50,6 @@ function createElement(prev, curr) { attr = attr.tail; } - if (curr[0] === "svg" && !el.getAttribute("xmlns")) { - el.setAttribute("xmlns", "http://www.w3.org/2000/svg"); - } - let child = curr[2]; while (child.head) { el.appendChild(morph(null, child.head)); @@ -56,7 +60,7 @@ function createElement(prev, curr) { return el; } -function morphElement(prev, curr) { +function morphElement(prev, curr, ns) { const prevAttrs = prev.attributes; const currAttrs = new Map(); @@ -66,10 +70,6 @@ function morphElement(prev, curr) { currAttr = currAttr.tail; } - if (curr[0] === "svg" && !currAttrs.has("xmlns")) { - currAttrs.set("xmlns", "http://www.w3.org/2000/svg"); - } - for (const { name, value: prevValue } of prevAttrs) { if (!currAttrs.has(name)) prev.removeAttribute(name); const value = currAttrs.get(name); diff --git a/test/examples/svg.gleam b/test/examples/svg.gleam index f1859d4..19c7652 100644 --- a/test/examples/svg.gleam +++ b/test/examples/svg.gleam @@ -3,7 +3,7 @@ import gleam/int import lustre import lustre/attribute.{attribute} -import lustre/element.{Element, element, text} +import lustre/element.{Element, namespaced, text} import lustre/html.{button, div, p, svg} import lustre/event @@ -48,24 +48,29 @@ pub fn render(model: Model) -> Element(Msg) { div( [], [ - button([event.on_click(Incr)], [plus()]), - button([event.on_click(Decr)], [minus()]), + button( + [event.on_click(Incr)], + [plus([attribute.style([#("color", "red")])])], + ), + button([event.on_click(Decr)], [minus([])]), button([event.on_click(Reset)], [text("Reset")]), p([], [text(int.to_string(model))]), ], ) } -fn plus() { +fn plus(attrs) { svg( [ attribute("width", "15"), attribute("height", "15"), attribute("viewBox", "0 0 15 15"), attribute("fill", "none"), + ..attrs ], [ - element( + namespaced( + "http://www.w3.org/2000/svg", "path", [ attribute( @@ -82,16 +87,18 @@ fn plus() { ) } -fn minus() { +fn minus(attrs) { svg( [ attribute("width", "15"), attribute("height", "15"), attribute("viewBox", "0 0 15 15"), attribute("fill", "none"), + ..attrs ], [ - element( + namespaced( + "http://www.w3.org/2000/svg", "path", [ attribute( diff --git a/test/examples/svg.html b/test/examples/svg.html index 10daddd..12af526 100644 --- a/test/examples/svg.html +++ b/test/examples/svg.html @@ -6,7 +6,7 @@ <title>lustre | svg</title> <script type="module"> - import { main } from "../../build/dev/javascript/lustre/examples/counter.mjs"; + import { main } from "../../build/dev/javascript/lustre/examples/svg.mjs"; document.addEventListener("DOMContentLoaded", main); </script> |