diff options
author | Hayleigh Thompson <me@hayleigh.dev> | 2023-07-20 21:56:25 +0100 |
---|---|---|
committer | Hayleigh Thompson <me@hayleigh.dev> | 2023-07-20 21:56:25 +0100 |
commit | b6311d242f6775c973bd005e33f4cbb99653bd54 (patch) | |
tree | 962ba27b5623a230af31bf7caa6f54855c3ddb93 | |
parent | c6e25797328a167c986f9e8503b028157a0105aa (diff) | |
download | lustre-b6311d242f6775c973bd005e33f4cbb99653bd54.tar.gz lustre-b6311d242f6775c973bd005e33f4cbb99653bd54.zip |
:sparkles: Add proper svg support.
-rw-r--r-- | src/lustre/html.gleam | 6 | ||||
-rw-r--r-- | src/runtime.ffi.mjs | 19 | ||||
-rw-r--r-- | test/examples/index.html | 6 | ||||
-rw-r--r-- | test/examples/svg.gleam | 109 | ||||
-rw-r--r-- | test/examples/svg.html | 17 |
5 files changed, 152 insertions, 5 deletions
diff --git a/src/lustre/html.gleam b/src/lustre/html.gleam index f925d95..4c7cf8c 100644 --- a/src/lustre/html.gleam +++ b/src/lustre/html.gleam @@ -847,7 +847,11 @@ pub fn svg( attrs: List(Attribute(msg)), children: List(Element(msg)), ) -> Element(msg) { - element("svg", attrs, children) + element( + "svg", + [attribute.attribute("xmlns", "http://www.w3.org/2000/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 b76a29f..babd0b8 100644 --- a/src/runtime.ffi.mjs +++ b/src/runtime.ffi.mjs @@ -1,9 +1,9 @@ -import { h, t } from "./lustre/element.mjs"; +import { element, text } from "./lustre/element.mjs"; import { List } from "./gleam.mjs"; import { Some, None } from "../gleam_stdlib/gleam/option.mjs"; -const Element = h("").constructor; -const Text = t("").constructor; +const Element = element("").constructor; +const Text = text("").constructor; export function morph(prev, curr) { if (curr instanceof Element) { @@ -31,7 +31,10 @@ export function morph(prev, curr) { // ELEMENTS -------------------------------------------------------------------- function createElement(prev, curr) { - const el = document.createElement(curr[0]); + const el = + curr[0] === "svg" + ? document.createElementNS("http://www.w3.org/2000/svg", "svg") + : document.createElement(curr[0]); let attr = curr[1]; while (attr.head) { @@ -39,6 +42,10 @@ 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)); @@ -59,6 +66,10 @@ 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/index.html b/test/examples/index.html index 1dda01b..70d4196 100644 --- a/test/examples/index.html +++ b/test/examples/index.html @@ -16,6 +16,12 @@ <li> <a href="nested.html">nested</a> </li> + <li> + <a href="svg.html">svg</a> + </li> + <li> + <a href="components.html">components</a> + </li> </menu> </body> </html> diff --git a/test/examples/svg.gleam b/test/examples/svg.gleam new file mode 100644 index 0000000..f1859d4 --- /dev/null +++ b/test/examples/svg.gleam @@ -0,0 +1,109 @@ +// IMPORTS --------------------------------------------------------------------- + +import gleam/int +import lustre +import lustre/attribute.{attribute} +import lustre/element.{Element, element, text} +import lustre/html.{button, div, p, svg} +import lustre/event + +// MAIN ------------------------------------------------------------------------ + +pub fn main() { + // A `simple` lustre application doesn't produce `Effect`s. These are best to + // start with if you're just getting started with lustre or you know you don't + // need the runtime to manage any side effects. + let app = lustre.simple(init, update, render) + let assert Ok(_) = lustre.start(app, "[data-lustre-app]") +} + +// MODEL ----------------------------------------------------------------------- + +pub type Model = + Int + +pub fn init() -> Model { + 0 +} + +// UPDATE ---------------------------------------------------------------------- + +pub opaque type Msg { + Incr + Decr + Reset +} + +pub fn update(model: Model, msg: Msg) -> Model { + case msg { + Incr -> model + 1 + Decr -> model - 1 + Reset -> 0 + } +} + +// VIEW ------------------------------------------------------------------------ + +pub fn render(model: Model) -> Element(Msg) { + div( + [], + [ + button([event.on_click(Incr)], [plus()]), + button([event.on_click(Decr)], [minus()]), + button([event.on_click(Reset)], [text("Reset")]), + p([], [text(int.to_string(model))]), + ], + ) +} + +fn plus() { + svg( + [ + attribute("width", "15"), + attribute("height", "15"), + attribute("viewBox", "0 0 15 15"), + attribute("fill", "none"), + ], + [ + element( + "path", + [ + attribute( + "d", + "M8 2.75C8 2.47386 7.77614 2.25 7.5 2.25C7.22386 2.25 7 2.47386 7 2.75V7H2.75C2.47386 7 2.25 7.22386 2.25 7.5C2.25 7.77614 2.47386 8 2.75 8H7V12.25C7 12.5261 7.22386 12.75 7.5 12.75C7.77614 12.75 8 12.5261 8 12.25V8H12.25C12.5261 8 12.75 7.77614 12.75 7.5C12.75 7.22386 12.5261 7 12.25 7H8V2.75Z", + ), + attribute("fill", "currentColor"), + attribute("fill-rule", "evenodd"), + attribute("clip-rule", "evenodd"), + ], + [], + ), + ], + ) +} + +fn minus() { + svg( + [ + attribute("width", "15"), + attribute("height", "15"), + attribute("viewBox", "0 0 15 15"), + attribute("fill", "none"), + ], + [ + element( + "path", + [ + attribute( + "d", + "M2.25 7.5C2.25 7.22386 2.47386 7 2.75 7H12.25C12.5261 7 12.75 7.22386 12.75 7.5C12.75 7.77614 12.5261 8 12.25 8H2.75C2.47386 8 2.25 7.77614 2.25 7.5Z", + ), + attribute("fill", "currentColor"), + attribute("fill-rule", "evenodd"), + attribute("clip-rule", "evenodd"), + ], + [], + ), + ], + ) +} diff --git a/test/examples/svg.html b/test/examples/svg.html new file mode 100644 index 0000000..10daddd --- /dev/null +++ b/test/examples/svg.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>lustre | svg</title> + + <script type="module"> + import { main } from "../../build/dev/javascript/lustre/examples/counter.mjs"; + + document.addEventListener("DOMContentLoaded", main); + </script> + </head> + <body> + <div data-lustre-app></div> + </body> +</html> |