aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHayleigh Thompson <me@hayleigh.dev>2023-07-20 22:52:07 +0100
committerHayleigh Thompson <me@hayleigh.dev>2023-07-20 22:52:07 +0100
commit3a60c2a1f44baf6c2d2cad2f329600e63d99f6c2 (patch)
tree6c53a71ca85809a357293df7beb6b1d9f6690b9a
parentb6311d242f6775c973bd005e33f4cbb99653bd54 (diff)
downloadlustre-3a60c2a1f44baf6c2d2cad2f329600e63d99f6c2.tar.gz
lustre-3a60c2a1f44baf6c2d2cad2f329600e63d99f6c2.zip
:recycle: Explicitly handle namespaced elements in the vdom tree.
-rw-r--r--src/lustre/element.gleam29
-rw-r--r--src/lustre/html.gleam8
-rw-r--r--src/runtime.ffi.mjs30
-rw-r--r--test/examples/svg.gleam21
-rw-r--r--test/examples/svg.html2
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>