aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHayleigh Thompson <me@hayleigh.dev>2023-09-19 23:40:03 +0100
committerHayleigh Thompson <me@hayleigh.dev>2023-09-19 23:40:03 +0100
commit92e8596b78982885803994b50c6b35f73f7a403e (patch)
tree13428243987317da540495215ed4d9e3938fb5cb /lib
parent985a9b0aa469cbe94fb95c433c97e2b321014341 (diff)
downloadlustre-92e8596b78982885803994b50c6b35f73f7a403e.tar.gz
lustre-92e8596b78982885803994b50c6b35f73f7a403e.zip
:recycle: So long monorepo.
Diffstat (limited to 'lib')
-rw-r--r--lib/README.md62
-rw-r--r--lib/gleam.toml10
-rw-r--r--lib/manifest.toml9
-rw-r--r--lib/package-lock.json201
-rw-r--r--lib/package.json10
-rw-r--r--lib/src/lustre.ffi.mjs231
-rw-r--r--lib/src/lustre.gleam97
-rw-r--r--lib/src/lustre/attribute.gleam326
-rw-r--r--lib/src/lustre/effect.gleam47
-rw-r--r--lib/src/lustre/element.gleam139
-rw-r--r--lib/src/lustre/element/html.gleam889
-rw-r--r--lib/src/lustre/element/svg.gleam416
-rw-r--r--lib/src/lustre/event.gleam178
-rw-r--r--lib/src/runtime.ffi.mjs239
-rw-r--r--lib/test/examples/components.gleam100
-rw-r--r--lib/test/examples/components.html17
-rw-r--r--lib/test/examples/counter.gleam56
-rw-r--r--lib/test/examples/counter.html17
-rw-r--r--lib/test/examples/index.html27
-rw-r--r--lib/test/examples/input.gleam132
-rw-r--r--lib/test/examples/input.html54
-rw-r--r--lib/test/examples/nested.gleam57
-rw-r--r--lib/test/examples/nested.html17
-rw-r--r--lib/test/examples/svg.gleam107
-rw-r--r--lib/test/examples/svg.html17
25 files changed, 0 insertions, 3455 deletions
diff --git a/lib/README.md b/lib/README.md
deleted file mode 100644
index 9f393f5..0000000
--- a/lib/README.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# Lustre
-
-[![Package Version](https://img.shields.io/hexpm/v/lustre)](https://hex.pm/packages/lustre)
-
-An Elm-inspired framework for building web apps in Gleam!
-
-```gleam
-import gleam/int
-import lustre
-import lustre/element.{text}
-import lustre/element/html.{div, button, p}
-import lustre/event.{on_click}
-
-pub fn main() {
- let app = lustre.simple(init, update, view)
- let assert Ok(_) = lustre.start("[data-lustre-app]", Nil)
-
- Nil
-}
-
-fn init(_) {
- 0
-}
-
-type Msg {
- Incr
- Decr
-}
-
-fn update(model, msg) {
- case msg {
- Incr -> model + 1
- Decr -> model - 1
- }
-}
-
-fn view(model) {
- let count = int.to_string(model)
-
- div([], [
- button([on_click(Decr)], [text(" + ")]),
- p([], [text(count)]),
- button([on_click(Incr)], [text(" - ")])
- ])
-}
-```
-
-## Documentation
-
-You can find the official documentation over at [pkg.hayleigh.dev/lustre](https://pkg.hayleigh.dev/lustre).
-Note that if you're viewing the documentation published on Hexdocs, you may find
-that things are missing! Because of the way Gleam's documentation is generated,
-packages and functions that target JavaScript don't get documented.
-
-## Installation
-
-Lustre is available on [Hex](https://hex.pm/packages/lustre). You can install
-it like any other Hex package:
-
-```sh
-$ gleam add lustre
-```
diff --git a/lib/gleam.toml b/lib/gleam.toml
deleted file mode 100644
index f301d46..0000000
--- a/lib/gleam.toml
+++ /dev/null
@@ -1,10 +0,0 @@
-name = "lustre"
-version = "3.0.3"
-
-description = "An Elm-inspired framework for building web apps in Gleam!"
-licences = ["MIT"]
-links = [{ title = "Buy me a coffee?", href = "https://github.com/sponsors/hayleigh-dot-dev" }]
-repository = { type = "github", user = "hayleigh-dot-dev", repo = "lustre/lib" }
-
-[dependencies]
-gleam_stdlib = "~> 0.30"
diff --git a/lib/manifest.toml b/lib/manifest.toml
deleted file mode 100644
index b711276..0000000
--- a/lib/manifest.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file was generated by Gleam
-# You typically do not need to edit this file
-
-packages = [
- { name = "gleam_stdlib", version = "0.30.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "03710B3DA047A3683117591707FCA19D32B980229DD8CE8B0603EB5B5144F6C3" },
-]
-
-[requirements]
-gleam_stdlib = { version = "~> 0.30" }
diff --git a/lib/package-lock.json b/lib/package-lock.json
deleted file mode 100644
index 7a63f7a..0000000
--- a/lib/package-lock.json
+++ /dev/null
@@ -1,201 +0,0 @@
-{
- "name": "lib",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "devDependencies": {
- "vite": "^4.4.2"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.18.11",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/esbuild": {
- "version": "0.18.11",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/android-arm": "0.18.11",
- "@esbuild/android-arm64": "0.18.11",
- "@esbuild/android-x64": "0.18.11",
- "@esbuild/darwin-arm64": "0.18.11",
- "@esbuild/darwin-x64": "0.18.11",
- "@esbuild/freebsd-arm64": "0.18.11",
- "@esbuild/freebsd-x64": "0.18.11",
- "@esbuild/linux-arm": "0.18.11",
- "@esbuild/linux-arm64": "0.18.11",
- "@esbuild/linux-ia32": "0.18.11",
- "@esbuild/linux-loong64": "0.18.11",
- "@esbuild/linux-mips64el": "0.18.11",
- "@esbuild/linux-ppc64": "0.18.11",
- "@esbuild/linux-riscv64": "0.18.11",
- "@esbuild/linux-s390x": "0.18.11",
- "@esbuild/linux-x64": "0.18.11",
- "@esbuild/netbsd-x64": "0.18.11",
- "@esbuild/openbsd-x64": "0.18.11",
- "@esbuild/sunos-x64": "0.18.11",
- "@esbuild/win32-arm64": "0.18.11",
- "@esbuild/win32-ia32": "0.18.11",
- "@esbuild/win32-x64": "0.18.11"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.2",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.6",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/picocolors": {
- "version": "1.0.0",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/postcss": {
- "version": "8.4.25",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/rollup": {
- "version": "3.26.2",
- "dev": true,
- "license": "MIT",
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=14.18.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.0.2",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/vite": {
- "version": "4.4.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.18.10",
- "postcss": "^8.4.24",
- "rollup": "^3.25.2"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- },
- "peerDependencies": {
- "@types/node": ">= 14",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- }
- }
- }
- }
-}
diff --git a/lib/package.json b/lib/package.json
deleted file mode 100644
index fdc091a..0000000
--- a/lib/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "private": true,
- "type": "module",
- "scripts": {
- "dev": "gleam build && vite serve ./test/examples"
- },
- "devDependencies": {
- "vite": "^4.4.2"
- }
-}
diff --git a/lib/src/lustre.ffi.mjs b/lib/src/lustre.ffi.mjs
deleted file mode 100644
index f29e7ea..0000000
--- a/lib/src/lustre.ffi.mjs
+++ /dev/null
@@ -1,231 +0,0 @@
-import {
- AppAlreadyStarted,
- AppNotYetStarted,
- BadComponentName,
- ComponentAlreadyRegistered,
- ElementNotFound,
- NotABrowser,
-} from "./lustre.mjs";
-import { from } from "./lustre/effect.mjs";
-import { morph } from "./runtime.ffi.mjs";
-import { Ok, Error, isEqual } from "./gleam.mjs";
-
-// RUNTIME ---------------------------------------------------------------------
-
-///
-///
-export class App {
- #root = null;
- #state = null;
- #queue = [];
- #effects = [];
- #didUpdate = false;
-
- #init = null;
- #update = null;
- #view = null;
-
- constructor(init, update, render) {
- this.#init = init;
- this.#update = update;
- this.#view = render;
- }
-
- start(selector, flags) {
- if (!is_browser()) return new Error(new NotABrowser());
- if (this.#root) return new Error(new AppAlreadyStarted());
-
- this.#root =
- selector instanceof HTMLElement
- ? selector
- : document.querySelector(selector);
-
- if (!this.#root) return new Error(new ElementNotFound());
-
- const [next, effects] = this.#init(flags);
-
- this.#state = next;
- this.#effects = effects[0].toArray();
- this.#didUpdate = true;
-
- window.requestAnimationFrame(() => this.#tick());
-
- return new Ok((msg) => this.dispatch(msg));
- }
-
- dispatch(msg) {
- this.#queue.push(msg);
- this.#tick();
- }
-
- emit(name, event = null) {
- this.#root.dispatchEvent(
- new CustomEvent(name, {
- bubbles: true,
- detail: event,
- composed: true,
- })
- );
- }
-
- destroy() {
- if (!this.#root) return new Error(new AppNotYetStarted());
-
- this.#root.remove();
- this.#root = null;
- this.#state = null;
- this.#queue = [];
- this.#effects = [];
- this.#didUpdate = false;
- this.#update = () => {};
- this.#view = () => {};
- }
-
- #tick() {
- this.#flush();
-
- if (this.#didUpdate) {
- const vdom = this.#view(this.#state);
-
- this.#root = morph(this.#root, vdom, (msg) => this.dispatch(msg));
- this.#didUpdate = false;
- }
- }
-
- #flush(times = 0) {
- if (!this.#root) return;
- if (this.#queue.length) {
- while (this.#queue.length) {
- const [next, effects] = this.#update(this.#state, this.#queue.shift());
- // If the user returned their model unchanged and not reconstructed then
- // we don't need to trigger a re-render.
- this.#didUpdate ||= this.#state !== next;
- this.#state = next;
- this.#effects = this.#effects.concat(effects[0].toArray());
- }
- }
-
- // Each update can produce effects which must now be executed.
- while (this.#effects.length)
- this.#effects.shift()(
- (msg) => this.dispatch(msg),
- (name, data) => this.emit(name, data)
- );
-
- // Synchronous effects will immediately queue a message to be processed. If
- // it is reasonable, we can process those updates too before proceeding to
- // the next render.
- if (this.#queue.length) {
- times >= 5 ? console.warn(tooManyUpdates) : this.#flush(++times);
- }
- }
-}
-
-export const setup = (init, update, render) => new App(init, update, render);
-export const start = (app, selector, flags) => app.start(selector, flags);
-export const destroy = (app) => app.destroy();
-
-export const emit = (name, data) =>
- // Normal `Effect`s constructed in Gleam from `effect.from` don't get told
- // about the second argument, but it's there 👀.
- from((_, emit) => {
- emit(name, data);
- });
-
-// HTML EVENTS -----------------------------------------------------------------
-
-export const prevent_default = (e) => e.preventDefault?.();
-export const stop_propagation = (e) => e.stopPropagation?.();
-
-// CUSTOM ELEMENTS -------------------------------------------------------------
-
-export const setup_component = (
- name,
- init,
- update,
- render,
- on_attribute_change
-) => {
- if (!name.includes("-")) return new Error(new BadComponentName());
- if (!is_browser()) return new Error(new NotABrowser());
- if (customElements.get(name)) {
- return new Error(new ComponentAlreadyRegistered());
- }
-
- customElements.define(
- name,
- class extends HTMLElement {
- static get observedAttributes() {
- return on_attribute_change.entries().map(([name, _]) => name);
- }
-
- #container = document.createElement("div");
- #app = null;
- #dispatch = null;
-
- constructor() {
- super();
-
- this.#app = new App(init, update, render);
- // This is necessary for ✨ reasons ✨. Clearly there's a bug in the
- // implementation of either the `App` or the runtime but I con't work it
- // out.
- //
- // If we pass the container to the app directly then the component fails
- // to render anything to the ODM.
- this.#container.appendChild(document.createElement("div"));
-
- const dispatch = this.#app.start(this.#container.firstChild);
- this.#dispatch = dispatch[0];
-
- on_attribute_change.forEach((decoder, name) => {
- Object.defineProperty(this, name, {
- get: () => {
- return this[`_${name}`] || this.getAttribute(name);
- },
-
- set: (value) => {
- const prev = this[name];
- const decoded = decoder(value);
-
- // We need this equality check to prevent constantly dispatching
- // messages when the value is an object or array: it might not have
- // changed but its reference might have and we don't want to trigger
- // useless updates.
- if (decoded.isOk() && !isEqual(prev, decoded[0])) {
- this.#dispatch(decoded[0]);
- }
-
- if (typeof value === "string") {
- this.setAttribute(name, value);
- } else {
- this[`_${name}`] = value;
- }
- },
- });
- });
- }
-
- connectedCallback() {
- this.appendChild(this.#container.firstChild);
- }
-
- attributeChangedCallback(name, prev, next) {
- if (prev !== next) {
- this[name] = next;
- }
- }
-
- disconnectedCallback() {
- this.#app.destroy();
- }
- }
- );
-
- return new Ok(null);
-};
-
-// UTLS ------------------------------------------------------------------------
-
-export const is_browser = () => window && window.document;
-export const is_registered = (name) => !!customElements.get(name);
diff --git a/lib/src/lustre.gleam b/lib/src/lustre.gleam
deleted file mode 100644
index 4f8249a..0000000
--- a/lib/src/lustre.gleam
+++ /dev/null
@@ -1,97 +0,0 @@
-//// To read the full documentation for this module, please visit
-//// [https://pkg.hayleigh.dev/lustre/api/lustre](https://pkg.hayleigh.dev/lustre/api/lustre)
-
-// IMPORTS ---------------------------------------------------------------------
-
-import gleam/dynamic.{Decoder}
-import gleam/map.{Map}
-import lustre/effect.{Effect}
-import lustre/element.{Element}
-
-// TYPES -----------------------------------------------------------------------
-
-///
-pub type App(flags, model, msg)
-
-pub type Error {
- AppAlreadyStarted
- AppNotYetStarted
- BadComponentName
- ComponentAlreadyRegistered
- ElementNotFound
- NotABrowser
-}
-
-// CONSTRUCTORS ----------------------------------------------------------------
-
-@target(javascript)
-///
-pub fn element(element: Element(msg)) -> App(Nil, Nil, msg) {
- let init = fn(_) { #(Nil, effect.none()) }
- let update = fn(_, _) { #(Nil, effect.none()) }
- let view = fn(_) { element }
-
- application(init, update, view)
-}
-
-@target(javascript)
-///
-pub fn simple(
- init: fn(flags) -> model,
- update: fn(model, msg) -> model,
- view: fn(model) -> Element(msg),
-) -> App(flags, model, msg) {
- let init = fn(flags) { #(init(flags), effect.none()) }
- let update = fn(model, msg) { #(update(model, msg), effect.none()) }
-
- application(init, update, view)
-}
-
-@target(javascript)
-///
-@external(javascript, "./lustre.ffi.mjs", "setup")
-pub fn application(
- init: fn(flags) -> #(model, Effect(msg)),
- update: fn(model, msg) -> #(model, Effect(msg)),
- view: fn(model) -> Element(msg),
-) -> App(flags, model, msg)
-
-@target(javascript)
-@external(javascript, "./lustre.ffi.mjs", "setup_component")
-pub fn component(
- name: String,
- init: fn() -> #(model, Effect(msg)),
- update: fn(model, msg) -> #(model, Effect(msg)),
- view: fn(model) -> Element(msg),
- on_attribute_change: Map(String, Decoder(msg)),
-) -> Result(Nil, Error)
-
-// EFFECTS ---------------------------------------------------------------------
-
-@target(javascript)
-///
-@external(javascript, "./lustre.ffi.mjs", "start")
-pub fn start(
- app: App(flags, model, msg),
- selector: String,
- flags: flags,
-) -> Result(fn(msg) -> Nil, Error)
-
-@target(javascript)
-///
-@external(javascript, "./lustre.ffi.mjs", "destroy")
-pub fn destroy(app: App(flags, model, msg)) -> Result(Nil, Error)
-
-// UTILS -----------------------------------------------------------------------
-
-///
-@external(javascript, "./lustre.ffi.mjs", "is_browser")
-pub fn is_browser() -> Bool {
- False
-}
-
-///
-@external(javascript, "./lustre.ffi.mjs", "is_registered")
-pub fn is_registered(_name: String) -> Bool {
- False
-}
diff --git a/lib/src/lustre/attribute.gleam b/lib/src/lustre/attribute.gleam
deleted file mode 100644
index 0c79692..0000000
--- a/lib/src/lustre/attribute.gleam
+++ /dev/null
@@ -1,326 +0,0 @@
-//// To read the full documentation for this module, please visit
-//// [https://pkg.hayleigh.dev/lustre/api/lustre/attribute](https://pkg.hayleigh.dev/lustre/api/lustre/attribute)
-
-// IMPORTS ---------------------------------------------------------------------
-
-import gleam/dynamic.{Dynamic}
-import gleam/function
-import gleam/int
-import gleam/list
-import gleam/result
-import gleam/string
-import gleam/string_builder.{StringBuilder}
-
-// TYPES -----------------------------------------------------------------------
-
-///
-pub opaque type Attribute(msg) {
- Attribute(String, Dynamic, as_property: Bool)
- Event(String, fn(Dynamic) -> Result(msg, Nil))
-}
-
-// CONSTRUCTORS ----------------------------------------------------------------
-
-///
-pub fn attribute(name: String, value: String) -> Attribute(msg) {
- Attribute(name, dynamic.from(value), as_property: False)
-}
-
-///
-pub fn property(name: String, value: any) -> Attribute(msg) {
- Attribute(name, dynamic.from(value), as_property: True)
-}
-
-///
-pub fn on(
- name: String,
- handler: fn(Dynamic) -> Result(msg, error),
-) -> Attribute(msg) {
- Event("on" <> name, function.compose(handler, result.replace_error(_, Nil)))
-}
-
-// MANIPULATIONS ---------------------------------------------------------------
-
-///
-pub fn map(attr: Attribute(a), f: fn(a) -> b) -> Attribute(b) {
- case attr {
- Attribute(name, value, as_property) -> Attribute(name, value, as_property)
- Event(on, handler) -> Event(on, fn(e) { result.map(handler(e), f) })
- }
-}
-
-// CONVERSIONS -----------------------------------------------------------------
-
-///
-///
-pub fn to_string(attr: Attribute(msg)) -> String {
- case attr {
- Attribute(name, value, as_property: False) -> {
- case dynamic.classify(value) {
- "String" -> name <> "=\"" <> dynamic.unsafe_coerce(value) <> "\""
-
- // Boolean attributes are determined based on their presence, eg we don't
- // want to render `disabled="false"` if the value is `false` we simply
- // want to omit the attribute altogether.
- "Boolean" ->
- case dynamic.unsafe_coerce(value) {
- True -> name
- False -> ""
- }
-
- // For everything else we'll just make a best-effort serialisation.
- _ -> name <> "=\"" <> string.inspect(value) <> "\""
- }
- }
- Attribute(_, _, as_property: True) -> ""
- Event(on, _) -> "data-lustre-on:" <> on
- }
-}
-
-///
-pub fn to_string_builder(attr: Attribute(msg)) -> StringBuilder {
- case attr {
- Attribute(name, value, as_property: False) -> {
- case dynamic.classify(value) {
- "String" ->
- [name, "=\"", dynamic.unsafe_coerce(value), "\""]
- |> string_builder.from_strings
-
- // Boolean attributes are determined based on their presence, eg we don't
- // want to render `disabled="false"` if the value is `false` we simply
- // want to omit the attribute altogether.
- "Boolean" ->
- case dynamic.unsafe_coerce(value) {
- True -> string_builder.from_string(name)
- False -> string_builder.new()
- }
-
- // For everything else we'll just make a best-effort serialisation.
- _ ->
- [name, "=\"", string.inspect(value), "\""]
- |> string_builder.from_strings
- }
- }
- Attribute(_, _, as_property: True) -> string_builder.new()
- Event(on, _) ->
- ["data-lustre-on:", on]
- |> string_builder.from_strings
- }
-}
-
-// COMMON ATTRIBUTES -----------------------------------------------------------
-
-///
-pub fn style(properties: List(#(String, String))) -> Attribute(msg) {
- attribute(
- "style",
- {
- use styles, #(name, value) <- list.fold(properties, "")
- styles <> name <> ":" <> value <> ";"
- },
- )
-}
-
-///
-pub fn class(name: String) -> Attribute(msg) {
- attribute("class", name)
-}
-
-///
-pub fn classes(names: List(#(String, Bool))) -> Attribute(msg) {
- attribute(
- "class",
- names
- |> list.filter_map(fn(class) {
- case class.1 {
- True -> Ok(class.0)
- False -> Error(Nil)
- }
- })
- |> string.join(" "),
- )
-}
-
-///
-pub fn id(name: String) -> Attribute(msg) {
- attribute("id", name)
-}
-
-// INPUTS ----------------------------------------------------------------------
-
-///
-pub fn type_(name: String) -> Attribute(msg) {
- attribute("type", name)
-}
-
-///
-pub fn value(val: Dynamic) -> Attribute(msg) {
- property("value", val)
-}
-
-///
-pub fn checked(is_checked: Bool) -> Attribute(msg) {
- property("checked", is_checked)
-}
-
-///
-pub fn placeholder(text: String) -> Attribute(msg) {
- attribute("placeholder", text)
-}
-
-///
-pub fn selected(is_selected: Bool) -> Attribute(msg) {
- property("selected", is_selected)
-}
-
-// INPUT HELPERS ---------------------------------------------------------------
-
-///
-pub fn accept(types: List(String)) -> Attribute(msg) {
- attribute("accept", string.join(types, " "))
-}
-
-///
-pub fn accept_charset(types: List(String)) -> Attribute(msg) {
- attribute("acceptCharset", string.join(types, " "))
-}
-
-///
-pub fn msg(uri: String) -> Attribute(msg) {
- attribute("msg", uri)
-}
-
-///
-pub fn autocomplete(name: String) -> Attribute(msg) {
- attribute("autocomplete", name)
-}
-
-///
-pub fn autofocus(should_autofocus: Bool) -> Attribute(msg) {
- property("autoFocus", should_autofocus)
-}
-
-///
-pub fn disabled(is_disabled: Bool) -> Attribute(msg) {
- property("disabled", is_disabled)
-}
-
-///
-pub fn name(name: String) -> Attribute(msg) {
- attribute("name", name)
-}
-
-///
-pub fn pattern(regex: String) -> Attribute(msg) {
- attribute("pattern", regex)
-}
-
-///
-pub fn readonly(is_readonly: Bool) -> Attribute(msg) {
- property("readonly", is_readonly)
-}
-
-///
-pub fn required(is_required: Bool) -> Attribute(msg) {
- property("required", is_required)
-}
-
-///
-pub fn for(id: String) -> Attribute(msg) {
- attribute("for", id)
-}
-
-// INPUT RANGES ----------------------------------------------------------------
-
-///
-pub fn max(val: String) -> Attribute(msg) {
- attribute("max", val)
-}
-
-///
-pub fn min(val: String) -> Attribute(msg) {
- attribute("min", val)
-}
-
-///
-pub fn step(val: String) -> Attribute(msg) {
- attribute("step", val)
-}
-
-// INPUT TEXT AREAS ------------------------------------------------------------
-
-///
-pub fn cols(val: Int) -> Attribute(msg) {
- attribute("cols", int.to_string(val))
-}
-
-///
-pub fn rows(val: Int) -> Attribute(msg) {
- attribute("rows", int.to_string(val))
-}
-
-///
-pub fn wrap(mode: String) -> Attribute(msg) {
- attribute("wrap", mode)
-}
-
-// LINKS AND AREAS -------------------------------------------------------------
-
-///
-pub fn href(uri: String) -> Attribute(msg) {
- attribute("href", uri)
-}
-
-///
-pub fn target(target: String) -> Attribute(msg) {
- attribute("target", target)
-}
-
-///
-pub fn download(filename: String) -> Attribute(msg) {
- attribute("download", filename)
-}
-
-///
-pub fn rel(relationship: String) -> Attribute(msg) {
- attribute("rel", relationship)
-}
-
-// EMBEDDED CONTENT ------------------------------------------------------------
-
-///
-pub fn src(uri: String) -> Attribute(msg) {
- attribute("src", uri)
-}
-
-///
-pub fn height(val: Int) -> Attribute(msg) {
- property("height", int.to_string(val))
-}
-
-///
-pub fn width(val: Int) -> Attribute(msg) {
- property("width", int.to_string(val))
-}
-
-///
-pub fn alt(text: String) -> Attribute(msg) {
- attribute("alt", text)
-}
-
-// AUDIO AND VIDEO -------------------------------------------------------------
-
-///
-pub fn autoplay(should_autoplay: Bool) -> Attribute(msg) {
- property("autoplay", should_autoplay)
-}
-
-///
-pub fn controls(visible: Bool) -> Attribute(msg) {
- property("controls", visible)
-}
-
-///
-pub fn loop(should_loop: Bool) -> Attribute(msg) {
- property("loop", should_loop)
-}
diff --git a/lib/src/lustre/effect.gleam b/lib/src/lustre/effect.gleam
deleted file mode 100644
index 964ddd2..0000000
--- a/lib/src/lustre/effect.gleam
+++ /dev/null
@@ -1,47 +0,0 @@
-//// To read the full documentation for this module, please visit
-//// [https://pkg.hayleigh.dev/lustre/api/lustre/effect](https://pkg.hayleigh.dev/lustre/api/lustre/effect)
-
-// IMPORTS ---------------------------------------------------------------------
-
-import gleam/list
-
-// TYPES -----------------------------------------------------------------------
-
-///
-pub opaque type Effect(msg) {
- Effect(List(fn(fn(msg) -> Nil) -> Nil))
-}
-
-// CONSTRUCTORS ----------------------------------------------------------------
-
-///
-pub fn from(effect: fn(fn(msg) -> Nil) -> Nil) -> Effect(msg) {
- Effect([effect])
-}
-
-/// Typically our app's `update` function needs to return a tuple of
-/// `#(model, Effect(msg))`. When we don't need to perform any side effects we
-/// can just return `none()`!
-///
-pub fn none() -> Effect(msg) {
- Effect([])
-}
-
-// MANIPULATIONS ---------------------------------------------------------------
-
-///
-pub fn batch(effects: List(Effect(msg))) -> Effect(msg) {
- Effect({
- use b, Effect(a) <- list.fold(effects, [])
- list.append(b, a)
- })
-}
-
-///
-pub fn map(effect: Effect(a), f: fn(a) -> b) -> Effect(b) {
- let Effect(l) = effect
- Effect(list.map(
- l,
- fn(effect) { fn(dispatch) { effect(fn(a) { dispatch(f(a)) }) } },
- ))
-}
diff --git a/lib/src/lustre/element.gleam b/lib/src/lustre/element.gleam
deleted file mode 100644
index 4608db7..0000000
--- a/lib/src/lustre/element.gleam
+++ /dev/null
@@ -1,139 +0,0 @@
-//// To read the full documentation for this module, please visit
-//// [https://pkg.hayleigh.dev/lustre/api/lustre/element](https://pkg.hayleigh.dev/lustre/api/lustre/element)
-
-// IMPORTS ---------------------------------------------------------------------
-
-import gleam/list
-import gleam/string
-import gleam/string_builder.{StringBuilder}
-import lustre/attribute.{Attribute}
-
-// TYPES -----------------------------------------------------------------------
-
-///
-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 ----------------------------------------------------------------
-
-///
-pub fn element(
- tag: String,
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- Element(tag, attrs, children)
-}
-
-///
-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)
-}
-
-fn escape(escaped: String, content: String) -> String {
- case string.pop_grapheme(content) {
- Ok(#("<", xs)) -> escape(escaped <> "&lt;", xs)
- Ok(#(">", xs)) -> escape(escaped <> "&gt;", xs)
- Ok(#("&", xs)) -> escape(escaped <> "&amp;", xs)
- Ok(#("\"", xs)) -> escape(escaped <> "&quot;", xs)
- Ok(#("'", xs)) -> escape(escaped <> "&#x27;", xs)
- Ok(#(x, xs)) -> escape(escaped <> x, xs)
- Error(_) -> escaped <> content
- }
-}
-
-// MANIPULATIONS ---------------------------------------------------------------
-
-///
-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)),
- )
- ElementNs(tag, attrs, children, namespace) ->
- ElementNs(
- tag,
- list.map(attrs, attribute.map(_, f)),
- list.map(children, map(_, f)),
- namespace,
- )
- }
-}
-
-// CONVERSIONS -----------------------------------------------------------------
-
-///
-pub fn to_string(element: Element(msg)) -> String {
- to_string_builder(element)
- |> string_builder.to_string
-}
-
-///
-pub fn to_string_builder(element: Element(msg)) -> StringBuilder {
- case element {
- Text(content) -> string_builder.from_string(escape("", content))
- Element("area" as tag, attrs, _)
- | Element("base" as tag, attrs, _)
- | Element("br" as tag, attrs, _)
- | Element("col" as tag, attrs, _)
- | Element("embed" as tag, attrs, _)
- | Element("hr" as tag, attrs, _)
- | Element("img" as tag, attrs, _)
- | Element("input" as tag, attrs, _)
- | Element("link" as tag, attrs, _)
- | Element("meta" as tag, attrs, _)
- | Element("param" as tag, attrs, _)
- | Element("source" as tag, attrs, _)
- | Element("track" as tag, attrs, _)
- | Element("wbr" as tag, attrs, _) ->
- string_builder.from_string("<" <> tag)
- |> attrs_to_string_builder(attrs)
- |> string_builder.append(">")
- 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 <> ">")
- }
-}
-
-fn attrs_to_string_builder(
- html: StringBuilder,
- attrs: List(Attribute(msg)),
-) -> StringBuilder {
- use html, attr <- list.fold(attrs, html)
- string_builder.append_builder(html, attribute.to_string_builder(attr))
-}
-
-fn children_to_string_builder(
- html: StringBuilder,
- children: List(Element(msg)),
-) -> StringBuilder {
- use html, child <- list.fold(children, html)
- string_builder.append_builder(html, to_string_builder(child))
-}
diff --git a/lib/src/lustre/element/html.gleam b/lib/src/lustre/element/html.gleam
deleted file mode 100644
index a69a5fd..0000000
--- a/lib/src/lustre/element/html.gleam
+++ /dev/null
@@ -1,889 +0,0 @@
-//// To read the full documentation for this module, please visit
-//// [https://pkg.hayleigh.dev/lustre/api/lustre/element/html](https://pkg.hayleigh.dev/lustre/api/lustre/element/html)
-
-// IMPORTS ---------------------------------------------------------------------
-
-import lustre/element.{Element, element, namespaced, text}
-import lustre/attribute.{Attribute}
-
-// HTML ELEMENTS: MAIN ROOT ----------------------------------------------------
-
-///
-pub fn html(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("html", attrs, children)
-}
-
-// HTML ELEMENTS: DOCUMENT METADATA --------------------------------------------
-
-///
-pub fn base(attrs: List(Attribute(msg))) -> Element(msg) {
- element("base", attrs, [])
-}
-
-///
-pub fn head(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("head", attrs, children)
-}
-
-///
-pub fn link(attrs: List(Attribute(msg))) -> Element(msg) {
- element("link", attrs, [])
-}
-
-///
-pub fn meta(attrs: List(Attribute(msg))) -> Element(msg) {
- element("meta", attrs, [])
-}
-
-///
-pub fn style(attrs: List(Attribute(msg)), css: String) -> Element(msg) {
- element("style", attrs, [text(css)])
-}
-
-///
-pub fn title(attrs: List(Attribute(msg)), content: String) -> Element(msg) {
- element("title", attrs, [text(content)])
-}
-
-// HTML ELEMENTS: SECTIONING ROOT -----------------------------------------------
-
-///
-pub fn body(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("body", attrs, children)
-}
-
-// HTML ELEMENTS: CONTENT SECTIONING -------------------------------------------
-
-///
-pub fn address(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("address", attrs, children)
-}
-
-///
-pub fn article(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("article", attrs, children)
-}
-
-///
-pub fn aside(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("aside", attrs, children)
-}
-
-///
-pub fn footer(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("footer", attrs, children)
-}
-
-///
-pub fn header(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("header", attrs, children)
-}
-
-///
-pub fn h1(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("h1", attrs, children)
-}
-
-///
-pub fn h2(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("h2", attrs, children)
-}
-
-///
-pub fn h3(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("h3", attrs, children)
-}
-
-///
-pub fn h4(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("h4", attrs, children)
-}
-
-///
-pub fn h5(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("h5", attrs, children)
-}
-
-///
-pub fn h6(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("h6", attrs, children)
-}
-
-///
-pub fn hgroup(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("hgroup", attrs, children)
-}
-
-///
-pub fn main(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("main", attrs, children)
-}
-
-///
-pub fn nav(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("nav", attrs, children)
-}
-
-///
-pub fn section(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("section", attrs, children)
-}
-
-///
-pub fn search(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("search", attrs, children)
-}
-
-// HTML ELEMENTS: TEXT CONTENT -------------------------------------------------
-
-///
-pub fn blockquote(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("blockquote", attrs, children)
-}
-
-///
-pub fn dd(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("dd", attrs, children)
-}
-
-///
-pub fn div(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("div", attrs, children)
-}
-
-///
-pub fn dl(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("dl", attrs, children)
-}
-
-///
-pub fn dt(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("dt", attrs, children)
-}
-
-///
-pub fn figcaption(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("figcaption", attrs, children)
-}
-
-///
-pub fn figure(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("figure", attrs, children)
-}
-
-///
-pub fn hr(attrs: List(Attribute(msg))) -> Element(msg) {
- element("hr", attrs, [])
-}
-
-///
-pub fn li(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("li", attrs, children)
-}
-
-///
-pub fn menu(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("menu", attrs, children)
-}
-
-///
-pub fn ol(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("ol", attrs, children)
-}
-
-///
-pub fn p(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("p", attrs, children)
-}
-
-///
-pub fn pre(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("pre", attrs, children)
-}
-
-///
-pub fn ul(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("ul", attrs, children)
-}
-
-// HTML ELEMENTS: INLINE TEXT SEMANTICS ----------------------------------------
-
-///
-pub fn a(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("a", attrs, children)
-}
-
-///
-pub fn abbr(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("abbr", attrs, children)
-}
-
-///
-pub fn b(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("b", attrs, children)
-}
-
-///
-pub fn bdi(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("bdi", attrs, children)
-}
-
-///
-pub fn bdo(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("bdo", attrs, children)
-}
-
-///
-pub fn br(attrs: List(Attribute(msg))) -> Element(msg) {
- element("br", attrs, [])
-}
-
-///
-pub fn cite(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("cite", attrs, children)
-}
-
-///
-pub fn code(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("code", attrs, children)
-}
-
-///
-pub fn data(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("data", attrs, children)
-}
-
-///
-pub fn dfn(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("dfn", attrs, children)
-}
-
-///
-pub fn em(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("em", attrs, children)
-}
-
-///
-pub fn i(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("i", attrs, children)
-}
-
-///
-pub fn kbd(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("kbd", attrs, children)
-}
-
-///
-pub fn mark(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("mark", attrs, children)
-}
-
-///
-pub fn q(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("q", attrs, children)
-}
-
-///
-pub fn rp(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("rp", attrs, children)
-}
-
-///
-pub fn rt(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("rt", attrs, children)
-}
-
-///
-pub fn ruby(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("ruby", attrs, children)
-}
-
-///
-pub fn s(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("s", attrs, children)
-}
-
-///
-pub fn samp(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("samp", attrs, children)
-}
-
-///
-pub fn small(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("small", attrs, children)
-}
-
-///
-pub fn span(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("span", attrs, children)
-}
-
-///
-pub fn strong(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("strong", attrs, children)
-}
-
-///
-pub fn sub(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("sub", attrs, children)
-}
-
-///
-pub fn sup(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("sup", attrs, children)
-}
-
-///
-pub fn time(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("time", attrs, children)
-}
-
-///
-pub fn u(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("u", attrs, children)
-}
-
-///
-pub fn var(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("var", attrs, children)
-}
-
-///
-pub fn wbr(attrs: List(Attribute(msg))) -> Element(msg) {
- element("wbr", attrs, [])
-}
-
-// HTML ELEMENTS: IMAGE AND MULTIMEDIA -----------------------------------------
-
-///
-pub fn area(attrs: List(Attribute(msg))) -> Element(msg) {
- element("area", attrs, [])
-}
-
-///
-pub fn audio(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("audio", attrs, children)
-}
-
-///
-pub fn img(attrs: List(Attribute(msg))) -> Element(msg) {
- element("img", attrs, [])
-}
-
-/// Used with <area> elements to define an image map (a clickable link area).
-///
-pub fn map(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("map", attrs, children)
-}
-
-///
-pub fn track(attrs: List(Attribute(msg))) -> Element(msg) {
- element("track", attrs, [])
-}
-
-///
-pub fn video(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("video", attrs, children)
-}
-
-// HTML ELEMENTS: EMBEDDED CONTENT ---------------------------------------------
-
-///
-pub fn embed(attrs: List(Attribute(msg))) -> Element(msg) {
- element("embed", attrs, [])
-}
-
-///
-pub fn iframe(attrs: List(Attribute(msg))) -> Element(msg) {
- element("iframe", attrs, [])
-}
-
-///
-pub fn object(attrs: List(Attribute(msg))) -> Element(msg) {
- element("object", attrs, [])
-}
-
-///
-pub fn picture(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("picture", attrs, children)
-}
-
-///
-pub fn portal(attrs: List(Attribute(msg))) -> Element(msg) {
- element("portal", attrs, [])
-}
-
-///
-pub fn source(attrs: List(Attribute(msg))) -> Element(msg) {
- element("source", attrs, [])
-}
-
-// HTML ELEMENTS: SVG AND MATHML -----------------------------------------------
-
-///
-pub fn svg(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced("http://www.w3.org/2000/svg", "svg", attrs, children)
-}
-
-///
-pub fn math(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("math", attrs, children)
-}
-
-// HTML ELEMENTS: SCRIPTING ----------------------------------------------------
-
-///
-pub fn canvas(attrs: List(Attribute(msg))) -> Element(msg) {
- element("canvas", attrs, [])
-}
-
-///
-pub fn noscript(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element("noscript", attrs, children)
-}
-
-///
-pub fn script(attrs: List(Attribute(msg)), js: String) -> Element(msg) {
- element("script", attrs, [text(js)])
-}
-
-// HTML ELEMENTS: DEMARCATING EDITS ---------------------------------------------
-
-///
-pub fn del(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("del", attrs, children)
-}
-
-///
-pub fn ins(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("ins", attrs, children)
-}
-
-// HTML ELEMENTS: TABLE CONTENT ------------------------------------------------
-
-///
-pub fn caption(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("caption", attrs, children)
-}
-
-///
-pub fn col(attrs: List(Attribute(msg))) -> Element(msg) {
- element.element("col", attrs, [])
-}
-
-///
-pub fn colgroup(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("colgroup", attrs, children)
-}
-
-///
-pub fn table(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("table", attrs, children)
-}
-
-///
-pub fn tbody(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("tbody", attrs, children)
-}
-
-///
-pub fn td(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("td", attrs, children)
-}
-
-///
-pub fn tfoot(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("tfoot", attrs, children)
-}
-
-///
-pub fn th(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("th", attrs, children)
-}
-
-///
-pub fn thead(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("thead", attrs, children)
-}
-
-///
-pub fn tr(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("tr", attrs, children)
-}
-
-// HTML ELEMENTS: FORMS --------------------------------------------------------
-
-///
-pub fn button(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("button", attrs, children)
-}
-
-///
-pub fn datalist(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("datalist", attrs, children)
-}
-
-///
-pub fn fieldset(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("fieldset", attrs, children)
-}
-
-///
-pub fn form(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("form", attrs, children)
-}
-
-///
-pub fn input(attrs: List(Attribute(msg))) -> Element(msg) {
- element.element("input", attrs, [])
-}
-
-///
-pub fn label(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("label", attrs, children)
-}
-
-///
-pub fn legend(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("legend", attrs, children)
-}
-
-///
-pub fn meter(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("meter", attrs, children)
-}
-
-///
-pub fn optgroup(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("optgroup", attrs, children)
-}
-
-///
-pub fn option(attrs: List(Attribute(msg))) -> Element(msg) {
- element.element("option", attrs, [])
-}
-
-///
-pub fn output(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("output", attrs, children)
-}
-
-///
-pub fn progress(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("progress", attrs, children)
-}
-
-///
-pub fn select(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("select", attrs, children)
-}
-
-///
-pub fn textarea(attrs: List(Attribute(msg))) -> Element(msg) {
- element.element("textarea", attrs, [])
-}
-
-// HTML ELEMENTS: INTERACTIVE ELEMENTS -----------------------------------------
-
-///
-pub fn details(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("details", attrs, children)
-}
-
-///
-pub fn dialog(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("dialog", attrs, children)
-}
-
-///
-pub fn summary(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("summary", attrs, children)
-}
-
-// HTML ELEMENTS: WEB COMPONENTS -----------------------------------------------
-
-///
-pub fn slot(attrs: List(Attribute(msg))) -> Element(msg) {
- element.element("slot", attrs, [])
-}
-
-///
-pub fn template(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- element.element("template", attrs, children)
-}
diff --git a/lib/src/lustre/element/svg.gleam b/lib/src/lustre/element/svg.gleam
deleted file mode 100644
index a1fc0ce..0000000
--- a/lib/src/lustre/element/svg.gleam
+++ /dev/null
@@ -1,416 +0,0 @@
-//// To read the full documentation for this module, please visit
-//// [https://pkg.hayleigh.dev/lustre/api/lustre/element/svg](https://pkg.hayleigh.dev/lustre/api/lustre/element/svg)
-
-// IMPORTS ---------------------------------------------------------------------
-
-import lustre/element.{Element, namespaced, text as inline_text}
-import lustre/attribute.{Attribute}
-
-// CONSTANTS -------------------------------------------------------------------
-
-const namespace = "http://www.w3.org/2000/svg"
-
-// The doc comments (and order) for functions in this module are taken from the
-// MDN Element reference:
-//
-// https://developer.mozilla.org/en-US/docs/Web/SVG/Element
-//
-
-// SVG ELEMENTS: ANIMATION ELEMENTS --------------------------------------------
-
-///
-pub fn animate(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "animate", attrs, [])
-}
-
-///
-pub fn animate_motion(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "animateMotion", attrs, [])
-}
-
-///
-pub fn animate_transform(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "animateTransform", attrs, [])
-}
-
-///
-pub fn mpath(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "mpath", attrs, [])
-}
-
-///
-pub fn set(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "set", attrs, [])
-}
-
-// SVG ELEMENTS: BASIC SHAPES --------------------------------------------------
-
-///
-pub fn circle(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "circle", attrs, [])
-}
-
-///
-pub fn ellipse(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "ellipse", attrs, [])
-}
-
-///
-pub fn line(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "line", attrs, [])
-}
-
-///
-pub fn polygon(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "polygon", attrs, [])
-}
-
-///
-pub fn polyline(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "polyline", attrs, [])
-}
-
-///
-pub fn rect(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "rect", attrs, [])
-}
-
-// SVG ELEMENTS: CONTAINER ELEMENTS --------------------------------------------
-
-///
-pub fn a(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "a", attrs, children)
-}
-
-///
-pub fn defs(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "defs", attrs, children)
-}
-
-///
-pub fn g(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "g", attrs, children)
-}
-
-///
-pub fn marker(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "marker", attrs, children)
-}
-
-///
-pub fn mask(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "mask", attrs, children)
-}
-
-///
-pub fn missing_glyph(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "missing-glyph", attrs, children)
-}
-
-///
-pub fn pattern(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "pattern", attrs, children)
-}
-
-///
-pub fn svg(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "svg", attrs, children)
-}
-
-///
-pub fn switch(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "switch", attrs, children)
-}
-
-///
-pub fn symbol(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "symbol", attrs, children)
-}
-
-// SVG ELEMENTS: DESCRIPTIVE ELEMENTS ------------------------------------------
-
-///
-pub fn desc(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "desc", attrs, children)
-}
-
-///
-pub fn metadata(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "metadata", attrs, children)
-}
-
-///
-pub fn title(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "title", attrs, children)
-}
-
-// SVG ELEMENTS: FILTER EFFECTS ------------------------------------------------
-
-///
-pub fn fe_blend(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feBlend", attrs, [])
-}
-
-///
-pub fn fe_color_matrix(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feColorMatrix", attrs, [])
-}
-
-///
-pub fn fe_component_transfer(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feComponentTransfer", attrs, [])
-}
-
-///
-pub fn fe_composite(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feComposite", attrs, [])
-}
-
-///
-pub fn fe_convolve_matrix(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feConvolveMatrix", attrs, [])
-}
-
-///
-pub fn fe_diffuse_lighting(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "feDiffuseLighting", attrs, children)
-}
-
-///
-pub fn fe_displacement_map(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feDisplacementMap", attrs, [])
-}
-
-///
-pub fn fe_drop_shadow(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feDropShadow", attrs, [])
-}
-
-///
-pub fn fe_flood(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feFlood", attrs, [])
-}
-
-///
-pub fn fe_func_a(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feFuncA", attrs, [])
-}
-
-///
-pub fn fe_func_b(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feFuncB", attrs, [])
-}
-
-///
-pub fn fe_func_g(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feFuncG", attrs, [])
-}
-
-///
-pub fn fe_func_r(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feFuncR", attrs, [])
-}
-
-///
-pub fn fe_gaussian_blur(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feGaussianBlur", attrs, [])
-}
-
-///
-pub fn fe_image(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feImage", attrs, [])
-}
-
-///
-pub fn fe_merge(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "feMerge", attrs, children)
-}
-
-///
-pub fn fe_merge_node(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feMergeNode", attrs, [])
-}
-
-///
-pub fn fe_morphology(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feMorphology", attrs, [])
-}
-
-///
-pub fn fe_offset(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feOffset", attrs, [])
-}
-
-///
-pub fn fe_specular_lighting(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "feSpecularLighting", attrs, children)
-}
-
-///
-pub fn fe_tile(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "feTile", attrs, children)
-}
-
-///
-pub fn fe_turbulence(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feTurbulence", attrs, [])
-}
-
-// SVG ELEMENTS: GRADIENT ELEMENTS ---------------------------------------------
-
-///
-pub fn linear_gradient(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "linearGradient", attrs, children)
-}
-
-///
-pub fn radial_gradient(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "radialGradient", attrs, children)
-}
-
-///
-pub fn stop(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "stop", attrs, [])
-}
-
-// SVG ELEMENTS: GRAPHICAL ELEMENTS --------------------------------------------
-
-///
-pub fn image(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "image", attrs, [])
-}
-
-///
-pub fn path(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "path", attrs, [])
-}
-
-///
-pub fn text(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "text", attrs, [])
-}
-
-///
-pub fn use_(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "use", attrs, [])
-}
-
-// SVG ELEMENTS: LIGHTING ELEMENTS ---------------------------------------------
-
-///
-pub fn fe_distant_light(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feDistantLight", attrs, [])
-}
-
-///
-pub fn fe_point_light(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "fePointLight", attrs, [])
-}
-
-///
-pub fn fe_spot_light(attrs: List(Attribute(msg))) -> Element(msg) {
- namespaced(namespace, "feSpotLight", attrs, [])
-}
-
-// SVG ELEMENTS: NEVER-RENDERED ELEMENTS ---------------------------------------
-
-///
-pub fn clip_path(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "clipPath", attrs, children)
-}
-
-///
-pub fn script(attrs: List(Attribute(msg)), js: String) -> Element(msg) {
- namespaced(namespace, "script", attrs, [inline_text(js)])
-}
-
-///
-pub fn style(attrs: List(Attribute(msg)), css: String) -> Element(msg) {
- namespaced(namespace, "style", attrs, [inline_text(css)])
-}
-
-// SVG ELEMENTS: RENDERABLE ELEMENTS -------------------------------------------
-
-///
-pub fn foreign_object(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "foreignObject", attrs, children)
-}
-
-///
-pub fn text_path(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "textPath", attrs, children)
-}
-
-///
-pub fn tspan(
- attrs: List(Attribute(msg)),
- children: List(Element(msg)),
-) -> Element(msg) {
- namespaced(namespace, "tspan", attrs, children)
-}
diff --git a/lib/src/lustre/event.gleam b/lib/src/lustre/event.gleam
deleted file mode 100644
index 7913ef5..0000000
--- a/lib/src/lustre/event.gleam
+++ /dev/null
@@ -1,178 +0,0 @@
-//// To read the full documentation for this module, please visit
-//// [https://pkg.hayleigh.dev/lustre/api/lustre/event](https://pkg.hayleigh.dev/lustre/api/lustre/event)
-
-// IMPORTS ---------------------------------------------------------------------
-
-import gleam/dynamic.{DecodeError, Dynamic}
-import gleam/result
-import lustre/attribute.{Attribute}
-import lustre/effect.{Effect}
-
-// TYPES -----------------------------------------------------------------------
-
-type Decoded(a) =
- Result(a, List(DecodeError))
-
-// EFFECTS ---------------------------------------------------------------------
-
-@target(javascript)
-///
-@external(javascript, "../lustre.ffi.mjs", "emit")
-pub fn emit(event: String, data: any) -> Effect(msg)
-
-// CUSTOM EVENTS ---------------------------------------------------------------
-
-///
-pub fn on(
- name: String,
- handler: fn(Dynamic) -> Result(msg, error),
-) -> Attribute(msg) {
- attribute.on(name, handler)
-}
-
-// MOUSE EVENTS ----------------------------------------------------------------
-
-///
-pub fn on_click(msg: msg) -> Attribute(msg) {
- use _ <- on("click")
- Ok(msg)
-}
-
-///
-pub fn on_mouse_down(msg: msg) -> Attribute(msg) {
- use _ <- on("mousedown")
- Ok(msg)
-}
-
-///
-pub fn on_mouse_up(msg: msg) -> Attribute(msg) {
- use _ <- on("mouseup")
- Ok(msg)
-}
-
-///
-pub fn on_mouse_enter(msg: msg) -> Attribute(msg) {
- use _ <- on("mouseenter")
- Ok(msg)
-}
-
-///
-pub fn on_mouse_leave(msg: msg) -> Attribute(msg) {
- use _ <- on("mouseleave")
- Ok(msg)
-}
-
-///
-pub fn on_mouse_over(msg: msg) -> Attribute(msg) {
- use _ <- on("mouseover")
- Ok(msg)
-}
-
-///
-pub fn on_mouse_out(msg: msg) -> Attribute(msg) {
- use _ <- on("mouseout")
- Ok(msg)
-}
-
-// KEYBOARD EVENTS -------------------------------------------------------------
-
-/// Listens for key presses on an element, and dispatches a message with the
-/// current key being pressed.
-///
-pub fn on_keypress(msg: fn(String) -> msg) -> Attribute(msg) {
- use event <- on("keypress")
-
- event
- |> dynamic.field("key", dynamic.string)
- |> result.map(msg)
-}
-
-/// Listens for key dow events on an element, and dispatches a message with the
-/// current key being pressed.
-///
-pub fn on_keydown(msg: fn(String) -> msg) -> Attribute(msg) {
- use event <- on("keydown")
-
- event
- |> dynamic.field("key", dynamic.string)
- |> result.map(msg)
-}
-
-/// Listens for key up events on an element, and dispatches a message with the
-/// current key being released.
-///
-pub fn on_keyup(msg: fn(String) -> msg) -> Attribute(msg) {
- use event <- on("keyup")
-
- event
- |> dynamic.field("key", dynamic.string)
- |> result.map(msg)
-}
-
-// FORM EVENTS -----------------------------------------------------------------
-
-///
-pub fn on_input(msg: fn(String) -> msg) -> Attribute(msg) {
- use event <- on("input")
-
- value(event)
- |> result.map(msg)
-}
-
-pub fn on_check(msg: fn(Bool) -> msg) -> Attribute(msg) {
- use event <- on("change")
-
- checked(event)
- |> result.map(msg)
-}
-
-pub fn on_submit(msg: msg) -> Attribute(msg) {
- use event <- on("submit")
- let _ = prevent_default(event)
-
- Ok(msg)
-}
-
-// FOCUS EVENTS ----------------------------------------------------------------
-
-pub fn on_focus(msg: msg) -> Attribute(msg) {
- use _ <- on("focus")
- Ok(msg)
-}
-
-pub fn on_blur(msg: msg) -> Attribute(msg) {
- use _ <- on("blur")
- Ok(msg)
-}
-
-// DECODERS --------------------------------------------------------------------
-
-///
-pub fn value(event: Dynamic) -> Decoded(String) {
- event
- |> dynamic.field("target", dynamic.field("value", dynamic.string))
-}
-
-///
-pub fn checked(event: Dynamic) -> Decoded(Bool) {
- event
- |> dynamic.field("target", dynamic.field("checked", dynamic.bool))
-}
-
-///
-pub fn mouse_position(event: Dynamic) -> Decoded(#(Float, Float)) {
- use x <- result.then(dynamic.field("clientX", dynamic.float)(event))
- use y <- result.then(dynamic.field("clientY", dynamic.float)(event))
-
- Ok(#(x, y))
-}
-
-// UTILS -----------------------------------------------------------------------
-
-@target(javascript)
-@external(javascript, "../lustre.ffi.mjs", "prevent_default")
-pub fn prevent_default(event: Dynamic) -> Nil
-
-@target(javascript)
-@external(javascript, "../lustre.ffi.mjs", "stop_propagation")
-pub fn stop_propagation(event: Dynamic) -> Nil
diff --git a/lib/src/runtime.ffi.mjs b/lib/src/runtime.ffi.mjs
deleted file mode 100644
index dd02f8b..0000000
--- a/lib/src/runtime.ffi.mjs
+++ /dev/null
@@ -1,239 +0,0 @@
-import { Empty } from "./gleam.mjs";
-import { map as result_map } from "../gleam_stdlib/gleam/result.mjs";
-
-export function morph(prev, curr, dispatch, parent) {
- if (curr[3]) {
- return prev?.nodeType === 1 &&
- prev.nodeName === curr[0].toUpperCase() &&
- prev.namespaceURI === curr[3]
- ? morphElement(prev, curr, curr[3], dispatch, parent)
- : createElement(prev, curr, curr[3], dispatch, parent);
- }
-
- if (curr[2]) {
- return prev?.nodeType === 1 && prev.nodeName === curr[0].toUpperCase()
- ? morphElement(prev, curr, null, dispatch, parent)
- : createElement(prev, curr, null, dispatch, parent);
- }
-
- if (curr[0] && typeof curr[0] === "string") {
- return prev?.nodeType === 3
- ? morphText(prev, curr)
- : createText(prev, curr);
- }
-
- return document.createComment(
- [
- "[internal lustre error] I couldn't work out how to render this element. This",
- "function should only be called internally by lustre's runtime: if you think",
- "this is an error, please open an issue at",
- "https://github.com/hayleigh-dot-dev/gleam-lustre/issues/new",
- ].join(" ")
- );
-}
-
-// ELEMENTS --------------------------------------------------------------------
-
-function createElement(prev, curr, ns, dispatch, parent = null) {
- const el = ns
- ? document.createElementNS(ns, curr[0])
- : document.createElement(curr[0]);
-
- el.$lustre = {};
-
- let attr = curr[1];
- while (attr.head) {
- morphAttr(
- el,
- attr.head[0],
- attr.head[0] === "class" && el.className
- ? `${el.className} ${attr.head[1]}`
- : attr.head[1],
- dispatch
- );
-
- attr = attr.tail;
- }
-
- if (customElements.get(curr[0])) {
- el._slot = curr[2];
- } else if (curr[0] === "slot") {
- let child = new Empty();
- let parentWithSlot = parent;
-
- while (parentWithSlot) {
- if (parentWithSlot._slot) {
- child = parentWithSlot._slot;
- break;
- } else {
- parentWithSlot = parentWithSlot.parentNode;
- }
- }
-
- while (child.head) {
- el.appendChild(morph(null, child.head, dispatch, el));
- child = child.tail;
- }
- } else {
- let child = curr[2];
- while (child.head) {
- el.appendChild(morph(null, child.head, dispatch, el));
- child = child.tail;
- }
-
- if (prev) prev.replaceWith(el);
- }
-
- return el;
-}
-
-function morphElement(prev, curr, ns, dispatch, parent) {
- const prevAttrs = prev.attributes;
- const currAttrs = new Map();
-
- let currAttr = curr[1];
- while (currAttr.head) {
- currAttrs.set(
- currAttr.head[0],
- currAttr.head[0] === "class" && currAttrs.has("class")
- ? `${currAttrs.get("class")} ${currAttr.head[1]}`
- : currAttr.head[1]
- );
-
- currAttr = currAttr.tail;
- }
-
- for (const { name, value: prevValue } of prevAttrs) {
- if (!currAttrs.has(name)) {
- prev.removeAttribute(name);
- } else {
- const value = currAttrs.get(name);
-
- if (value !== prevValue) {
- morphAttr(prev, name, value, dispatch);
- currAttrs.delete(name);
- }
- }
- }
-
- for (const [name, value] of currAttrs) {
- morphAttr(prev, name, value, dispatch);
- }
-
- if (customElements.get(curr[0])) {
- prev._slot = curr[2];
- } else if (curr[0] === "slot") {
- let prevChild = prev.firstChild;
- let currChild = new Empty();
- let parentWithSlot = parent;
-
- while (parentWithSlot) {
- if (parentWithSlot._slot) {
- currChild = parentWithSlot._slot;
- break;
- } else {
- parentWithSlot = parentWithSlot.parentNode;
- }
- }
-
- while (prevChild) {
- if (currChild.head) {
- morph(prevChild, currChild.head, dispatch, prev);
- currChild = currChild.tail;
- }
-
- prevChild = prevChild.nextSibling;
- }
-
- while (currChild.head) {
- prev.appendChild(morph(null, currChild.head, dispatch, prev));
- currChild = currChild.tail;
- }
- } else {
- let prevChild = prev.firstChild;
- let currChild = curr[2];
-
- while (prevChild) {
- if (currChild.head) {
- const next = prevChild.nextSibling;
- morph(prevChild, currChild.head, dispatch, prev);
- currChild = currChild.tail;
- prevChild = next;
- } else {
- const next = prevChild.nextSibling;
- prevChild.remove();
- prevChild = next;
- }
- }
-
- while (currChild.head) {
- prev.appendChild(morph(null, currChild.head, dispatch, prev));
- currChild = currChild.tail;
- }
- }
-
- return prev;
-}
-
-// ATTRIBUTES ------------------------------------------------------------------
-
-function morphAttr(el, name, value, dispatch) {
- switch (typeof value) {
- case "string":
- if (el.getAttribute(name) !== value) el.setAttribute(name, value);
- if (value === "") el.removeAttribute(name);
- if (name === "value" && el.value !== value) el.value = value;
- break;
-
- // Event listeners need to be handled slightly differently because we need
- // to be able to support custom events. We
- case name.startsWith("on") && "function": {
- if (el.$lustre[name] === value) break;
-
- const event = name.slice(2).toLowerCase();
- const handler = (e) => result_map(value(e), dispatch);
-
- if (el.$lustre[`${name}Handler`]) {
- el.removeEventListener(event, el.$lustre[`${name}Handler`]);
- }
-
- el.addEventListener(event, handler);
-
- el.$lustre[name] = value;
- el.$lustre[`${name}Handler`] = handler;
-
- break;
- }
-
- default:
- el[name] = value;
- }
-}
-
-// TEXT ------------------------------------------------------------------------
-
-function createText(prev, curr) {
- if (!curr[0]) {
- prev?.remove();
- return null;
- }
-
- const el = document.createTextNode(curr[0]);
-
- if (prev) prev.replaceWith(el);
- return el;
-}
-
-function morphText(prev, curr) {
- const prevValue = prev.nodeValue;
- const currValue = curr[0];
-
- if (!currValue) {
- prev?.remove();
- return null;
- }
-
- if (prevValue !== currValue) prev.nodeValue = currValue;
-
- return prev;
-}
diff --git a/lib/test/examples/components.gleam b/lib/test/examples/components.gleam
deleted file mode 100644
index b87cf84..0000000
--- a/lib/test/examples/components.gleam
+++ /dev/null
@@ -1,100 +0,0 @@
-// IMPORTS ---------------------------------------------------------------------
-
-import gleam/dynamic
-import gleam/function
-import gleam/int
-import gleam/list
-import gleam/map
-import gleam/result
-import lustre
-import lustre/attribute
-import lustre/effect
-import lustre/element.{element, text}
-import lustre/element/html.{button, div, li, ol, p, slot}
-import lustre/event
-
-// MAIN ------------------------------------------------------------------------
-
-pub fn main() {
- let assert Ok(_) =
- lustre.component(
- "custom-counter",
- counter_init,
- counter_update,
- counter_view,
- map.from_list([
- #(
- "count",
- fn(attr) {
- dynamic.int(attr)
- |> result.map(GotCount)
- },
- ),
- ]),
- )
-
- // 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, view)
- let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
-
- Nil
-}
-
-fn init(_) {
- []
-}
-
-fn update(history, msg) {
- case msg {
- "reset" -> []
- _ -> [msg, ..history]
- }
-}
-
-fn view(history) {
- let on_custom_click = event.on("custom-click", function.constant(Ok("click")))
-
- div(
- [],
- [
- button([event.on_click("reset")], [text("Reset")]),
- ol([], list.map(history, fn(msg) { li([], [text(msg)]) })),
- element(
- "custom-counter",
- [on_custom_click, attribute.property("count", list.length(history))],
- [ol([], list.map(history, fn(msg) { li([], [text(msg)]) }))],
- ),
- ],
- )
-}
-
-// COUNTER ---------------------------------------------------------------------
-
-fn counter_init() {
- #(0, effect.none())
-}
-
-type CounterMsg {
- GotCount(Int)
- Clicked
-}
-
-fn counter_update(count, msg) {
- case msg {
- GotCount(count) -> #(count, effect.none())
- Clicked -> #(count, event.emit("custom-click", Nil))
- }
-}
-
-fn counter_view(count) {
- div(
- [],
- [
- button([event.on_click(Clicked)], [text("Click me!")]),
- p([], [text("Count: "), text(int.to_string(count))]),
- slot([]),
- ],
- )
-}
diff --git a/lib/test/examples/components.html b/lib/test/examples/components.html
deleted file mode 100644
index f4b4530..0000000
--- a/lib/test/examples/components.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>lustre | components</title>
-
- <script type="module">
- import { main } from "../../build/dev/javascript/lustre/examples/components.mjs";
-
- document.addEventListener("DOMContentLoaded", main);
- </script>
- </head>
- <body>
- <div data-lustre-app></div>
- </body>
-</html>
diff --git a/lib/test/examples/counter.gleam b/lib/test/examples/counter.gleam
deleted file mode 100644
index 4faf00c..0000000
--- a/lib/test/examples/counter.gleam
+++ /dev/null
@@ -1,56 +0,0 @@
-// IMPORTS ---------------------------------------------------------------------
-
-import gleam/int
-import lustre
-import lustre/element.{Element, text}
-import lustre/element/html.{button, div, p}
-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, view)
- let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
-}
-
-// 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 view(model: Model) -> Element(Msg) {
- div(
- [],
- [
- button([event.on_click(Incr)], [text("+")]),
- button([event.on_click(Decr)], [text("-")]),
- button([event.on_click(Reset)], [text("Reset")]),
- p([], [text(int.to_string(model))]),
- ],
- )
-}
diff --git a/lib/test/examples/counter.html b/lib/test/examples/counter.html
deleted file mode 100644
index 2b120fd..0000000
--- a/lib/test/examples/counter.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>lustre | counter</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>
diff --git a/lib/test/examples/index.html b/lib/test/examples/index.html
deleted file mode 100644
index 70d4196..0000000
--- a/lib/test/examples/index.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>lustre | examples</title>
- </head>
- <body>
- <menu>
- <li>
- <a href="input.html">input</a>
- </li>
- <li>
- <a href="counter.html">counter</a>
- </li>
- <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/lib/test/examples/input.gleam b/lib/test/examples/input.gleam
deleted file mode 100644
index ff4d794..0000000
--- a/lib/test/examples/input.gleam
+++ /dev/null
@@ -1,132 +0,0 @@
-// IMPORTS ---------------------------------------------------------------------
-
-import gleam/dynamic
-import gleam/string
-import lustre
-import lustre/attribute.{attribute}
-import lustre/element.{Element, text}
-import lustre/element/html.{div, input, label, pre}
-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, view)
- let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
-
- Nil
-}
-
-// MODEL -----------------------------------------------------------------------
-
-type Model {
- Model(email: String, password: String, remember_me: Bool)
-}
-
-fn init(_) -> Model {
- Model(email: "", password: "", remember_me: False)
-}
-
-// UPDATE ----------------------------------------------------------------------
-
-type Msg {
- Typed(Input, String)
- Toggled(Control, Bool)
-}
-
-type Input {
- Email
- Password
-}
-
-type Control {
- RememberMe
-}
-
-fn update(model: Model, msg: Msg) -> Model {
- case msg {
- Typed(Email, email) -> Model(..model, email: email)
- Typed(Password, password) -> Model(..model, password: password)
- Toggled(RememberMe, remember_me) -> Model(..model, remember_me: remember_me)
- }
-}
-
-// RENDER ----------------------------------------------------------------------
-
-fn view(model: Model) -> Element(Msg) {
- div(
- [attribute.class("container")],
- [
- card([
- email_input(model.email),
- password_input(model.password),
- remember_checkbox(model.remember_me),
- pre(
- [attribute.class("debug")],
- [
- string.inspect(model)
- |> string.replace("(", "(\n ")
- |> string.replace(", ", ",\n ")
- |> string.replace(")", "\n)")
- |> text,
- ],
- ),
- ]),
- ],
- )
-}
-
-fn card(content: List(Element(a))) -> Element(a) {
- div([attribute.class("card")], [div([], content)])
-}
-
-fn email_input(value: String) -> Element(Msg) {
- render_input(Email, "email", "email-input", value, "Email address")
-}
-
-fn password_input(value: String) -> Element(Msg) {
- render_input(Password, "password", "password-input", value, "Password")
-}
-
-fn render_input(
- field: Input,
- type_: String,
- id: String,
- value: String,
- label_: String,
-) -> Element(Msg) {
- div(
- [attribute.class("input")],
- [
- label([attribute.for(id)], [text(label_)]),
- input([
- attribute.id(id),
- attribute.name(id),
- attribute.type_(type_),
- attribute.required(True),
- attribute.value(dynamic.from(value)),
- event.on_input(fn(value) { Typed(field, value) }),
- ]),
- ],
- )
-}
-
-fn remember_checkbox(checked: Bool) -> Element(Msg) {
- div(
- [attribute.class("flex items-center")],
- [
- input([
- attribute.id("remember-me"),
- attribute.name("remember-me"),
- attribute.type_("checkbox"),
- attribute.checked(checked),
- attribute.class("checkbox"),
- event.on_click(Toggled(RememberMe, !checked)),
- ]),
- label([attribute.for("remember-me")], [text("Remember me")]),
- ],
- )
-}
diff --git a/lib/test/examples/input.html b/lib/test/examples/input.html
deleted file mode 100644
index 3bd6463..0000000
--- a/lib/test/examples/input.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" class="h-full bg-gray-50">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>lustre | forms</title>
-
- <script src="https://cdn.tailwindcss.com"></script>
- <style type="text/tailwindcss">
- @layer components {
- .container {
- @apply flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8;
- }
-
- .card {
- @apply mt-10 sm:mx-auto sm:w-full sm:max-w-[480px];
- }
-
- .card > div {
- @apply bg-white px-6 py-12 shadow sm:rounded-lg sm:px-12 space-y-6;
- }
-
- .debug {
- @apply text-sm text-gray-400;
- }
-
- .input label {
- @apply block text-sm font-medium leading-6 text-gray-900;
- }
-
- .input input {
- @apply block w-full rounded-md border-0 p-1.5 mt-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6;
- }
-
- .checkbox {
- @apply h-4 w-4 rounded border-gray-300 text-pink-600 focus:ring-pink-600;
- }
-
- .checkbox + label {
- @apply ml-3 block text-sm leading-6 text-gray-900;
- }
- }
- </style>
-
- <script type="module">
- import { main } from "../../build/dev/javascript/lustre/examples/input.mjs";
-
- document.addEventListener("DOMContentLoaded", main);
- </script>
- </head>
- <body class="h-full">
- <div data-lustre-app></div>
- </body>
-</html>
diff --git a/lib/test/examples/nested.gleam b/lib/test/examples/nested.gleam
deleted file mode 100644
index 91c2da4..0000000
--- a/lib/test/examples/nested.gleam
+++ /dev/null
@@ -1,57 +0,0 @@
-// IMPORTS ---------------------------------------------------------------------
-
-import examples/counter
-import gleam/list
-import gleam/map.{Map}
-import gleam/pair
-import lustre
-import lustre/element.{Element}
-import lustre/element/html.{div}
-
-// 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, view)
- let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
-
- Nil
-}
-
-// MODEL -----------------------------------------------------------------------
-
-type Model =
- Map(Int, counter.Model)
-
-fn init(_) -> Model {
- use counters, id <- list.fold(list.range(1, 10), map.new())
-
- map.insert(counters, id, counter.init(Nil))
-}
-
-// UPDATE ----------------------------------------------------------------------
-
-type Msg =
- #(Int, counter.Msg)
-
-fn update(model: Model, msg: Msg) -> Model {
- let #(id, counter_msg) = msg
- let assert Ok(counter) = map.get(model, id)
-
- map.insert(model, id, counter.update(counter, counter_msg))
-}
-
-// RENDER ----------------------------------------------------------------------
-
-fn view(model: Model) -> Element(Msg) {
- let counters = {
- use rest, id, counter <- map.fold(model, [])
- let el = element.map(counter.view(counter), pair.new(id, _))
-
- [el, ..rest]
- }
-
- div([], counters)
-}
diff --git a/lib/test/examples/nested.html b/lib/test/examples/nested.html
deleted file mode 100644
index 420b159..0000000
--- a/lib/test/examples/nested.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>lustre | nested</title>
-
- <script type="module">
- import { main } from "../../build/dev/javascript/lustre/examples/nested.mjs";
-
- document.addEventListener("DOMContentLoaded", main);
- </script>
- </head>
- <body>
- <div data-lustre-app></div>
- </body>
-</html>
diff --git a/lib/test/examples/svg.gleam b/lib/test/examples/svg.gleam
deleted file mode 100644
index e895f1a..0000000
--- a/lib/test/examples/svg.gleam
+++ /dev/null
@@ -1,107 +0,0 @@
-// IMPORTS ---------------------------------------------------------------------
-
-import gleam/int
-import lustre
-import lustre/attribute.{attribute}
-import lustre/element.{Element, text}
-import lustre/element/html.{button, div, p, svg}
-import lustre/element/svg.{path}
-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, view)
- let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
-}
-
-// 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 view(model: Model) -> Element(Msg) {
- div(
- [],
- [
- 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(attrs) {
- svg(
- [
- attribute("width", "15"),
- attribute("height", "15"),
- attribute("viewBox", "0 0 15 15"),
- attribute("fill", "none"),
- ..attrs
- ],
- [
- 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(attrs) {
- svg(
- [
- attribute("width", "15"),
- attribute("height", "15"),
- attribute("viewBox", "0 0 15 15"),
- attribute("fill", "none"),
- ..attrs
- ],
- [
- 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/lib/test/examples/svg.html b/lib/test/examples/svg.html
deleted file mode 100644
index 12af526..0000000
--- a/lib/test/examples/svg.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!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/svg.mjs";
-
- document.addEventListener("DOMContentLoaded", main);
- </script>
- </head>
- <body>
- <div data-lustre-app></div>
- </body>
-</html>