aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHayleigh Thompson <me@hayleigh.dev>2022-03-20 10:58:23 +0000
committerHayleigh Thompson <me@hayleigh.dev>2022-03-20 10:58:23 +0000
commit170a249f1bc5caae55ae5a3469f0fc6beef63568 (patch)
tree13a65659aa2aef065d1acf59af20a109df83eaa4 /src
parent06e3827589771da31bf033d304da4ba3a40c9cde (diff)
downloadlustre-170a249f1bc5caae55ae5a3469f0fc6beef63568.tar.gz
lustre-170a249f1bc5caae55ae5a3469f0fc6beef63568.zip
:construction: Morphdom is dead, long live React.
Diffstat (limited to 'src')
-rw-r--r--src/index.html17
-rw-r--r--src/js/test.js5
-rw-r--r--src/lustre.gleam46
-rw-r--r--src/lustre/attribute.gleam21
-rw-r--r--src/lustre/dom/html.ffi.mjs14
-rw-r--r--src/lustre/dom/html.gleam54
-rw-r--r--src/lustre/dom/html/attr.ffi.mjs6
-rw-r--r--src/lustre/dom/html/attr.gleam61
-rw-r--r--src/lustre/dom/svg.gleam32
-rw-r--r--src/lustre/dom/svg/attr.gleam92
-rw-r--r--src/lustre/element.gleam493
-rw-r--r--src/lustre/event.gleam24
-rw-r--r--src/lustre/ffi.mjs65
-rw-r--r--src/lustre/test.gleam11
14 files changed, 649 insertions, 292 deletions
diff --git a/src/index.html b/src/index.html
deleted file mode 100644
index e807f2a..0000000
--- a/src/index.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
-
- <script type="module" src="./js/test.js"></script>
-</head>
-
-<body>
-
-</body>
-
-</html> \ No newline at end of file
diff --git a/src/js/test.js b/src/js/test.js
deleted file mode 100644
index ef7923a..0000000
--- a/src/js/test.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import * as Lustre from 'lustre/test.mjs'
-
-document.documentElement.append(
- Lustre.html_test()
-) \ No newline at end of file
diff --git a/src/lustre.gleam b/src/lustre.gleam
new file mode 100644
index 0000000..2058da6
--- /dev/null
+++ b/src/lustre.gleam
@@ -0,0 +1,46 @@
+////
+
+
+// IMPORTS ---------------------------------------------------------------------
+import lustre/element
+import lustre/attribute
+
+
+// TYPES -----------------------------------------------------------------------
+///
+pub opaque type Program(state, action) {
+ Program(
+ init: state,
+ update: Update(state, action),
+ view: View(state, action)
+ )
+}
+
+
+///
+pub type Element(action) = element.Element(action)
+///
+pub type Attribute(action) = attribute.Attribute(action)
+
+
+///
+type Update(state, action) = fn (state, action) -> state
+type View(state, action) = fn (state) -> Element(action)
+
+
+// CONSTRUCTORS ----------------------------------------------------------------
+///
+pub fn create (init: state, update: Update(state, action), view: View(state, action)) -> Program(state, action) {
+ Program(init, update, view)
+}
+
+
+///
+pub external fn start (program: Program(state, action), selector: String) -> Nil
+ = "./lustre/ffi.mjs" "mount"
+
+
+// CONVERSIONS -----------------------------------------------------------------
+///
+pub external fn to_element (program: Program(state, action)) -> Element(a)
+ = "./lustre/ffi.mjs" "Program"
diff --git a/src/lustre/attribute.gleam b/src/lustre/attribute.gleam
new file mode 100644
index 0000000..c294b00
--- /dev/null
+++ b/src/lustre/attribute.gleam
@@ -0,0 +1,21 @@
+import gleam/dynamic.{ Dynamic }
+
+pub opaque type Attribute(action) {
+ Attribute(name: String, value: String)
+ Property(name: String, value: Dynamic)
+ Event(on: String, handler: fn (Dynamic) -> action)
+}
+
+// CONSTRUCTORS ----------------------------------------------------------------
+
+pub fn attribute (name: String, value: String) -> Attribute(action) {
+ Attribute(name, value)
+}
+
+pub fn property (name: String, value: Dynamic) -> Attribute(action) {
+ Property(name, value)
+}
+
+pub fn event (on: String, handler: fn (Dynamic) -> action) -> Attribute(action) {
+ Event(on, handler)
+} \ No newline at end of file
diff --git a/src/lustre/dom/html.ffi.mjs b/src/lustre/dom/html.ffi.mjs
deleted file mode 100644
index 2cbe58d..0000000
--- a/src/lustre/dom/html.ffi.mjs
+++ /dev/null
@@ -1,14 +0,0 @@
-export const createNode = (tag, namespace, attrs, children) => {
- const el = namespace == ''
- ? document.createElement(tag)
- : document.createElementNS(namespace, tag)
-
- for (const attr of attrs) el.setAttributeNode(attr)
- for (const node of children) el.append(node)
-
- return el
-}
-
-export const createText = (content) => {
- return document.createTextNode(content)
-} \ No newline at end of file
diff --git a/src/lustre/dom/html.gleam b/src/lustre/dom/html.gleam
deleted file mode 100644
index 12ac25a..0000000
--- a/src/lustre/dom/html.gleam
+++ /dev/null
@@ -1,54 +0,0 @@
-import lustre/dom/html/attr.{Attr}
-
-// TYPES -----------------------------------------------------------------------
-
-pub external type Html
-
-// GENERIC CONSTRUCTORS --------------------------------------------------------
-
-external fn create_node (String, String, List(Attr), List(Html)) -> Html = "./html.ffi.mjs" "createNode"
-
-external fn create_text (String) -> Html =
- "./html.ffi.mjs" "createText"
-
-pub fn node (tag: String, attrs: List(Attr), children: List(Html)) -> Html {
- create_node(tag, "", attrs, children)
-}
-
-pub fn node_ns (tag: String, namespace: String, attrs: List(Attr), children: List(Html)) -> Html {
- create_node(tag, namespace, attrs, children)
-}
-
-pub fn text (content: String) -> Html {
- create_text(content)
-}
-
-// COMMON CONSTRUCTORS ---------------------------------------------------------
-
-pub fn div (attrs: List(Attr), children: List(Html)) -> Html {
- node("div", attrs, children)
-}
-
-pub fn p (attrs: List(Attr), children: List(Html)) -> Html {
- node("p", attrs, children)
-}
-
-pub fn span (attrs: List(Attr), children: List(Html)) -> Html {
- node("span", attrs, children)
-}
-
-pub fn button (attrs: List(Attr), children: List(Html)) -> Html {
- node("button", attrs, children)
-}
-
-// INPUT CONSTRUCTORS ----------------------------------------------------------
-
-pub fn input (attrs: List(Attr)) -> Html {
- node("input", attrs, [])
-}
-
-// GRAPHICS CONSTRUCTORS -------------------------------------------------------
-
-pub fn img (attrs: List(Attr)) -> Html {
- node("img", attrs, [])
-} \ No newline at end of file
diff --git a/src/lustre/dom/html/attr.ffi.mjs b/src/lustre/dom/html/attr.ffi.mjs
deleted file mode 100644
index b75c37a..0000000
--- a/src/lustre/dom/html/attr.ffi.mjs
+++ /dev/null
@@ -1,6 +0,0 @@
-export const createAttr = (name, value) => {
- const attr = document.createAttribute(name)
- attr.value = value
-
- return attr
-} \ No newline at end of file
diff --git a/src/lustre/dom/html/attr.gleam b/src/lustre/dom/html/attr.gleam
deleted file mode 100644
index ac86cdf..0000000
--- a/src/lustre/dom/html/attr.gleam
+++ /dev/null
@@ -1,61 +0,0 @@
-import gleam/float
-import gleam/int
-
-// TYPES -----------------------------------------------------------------------
-
-pub external type Attr
-
-// GENERIC CONSTRUCTORS --------------------------------------------------------
-
-external fn create_attr (String, String) -> Attr =
- "./attr.ffi.mjs" "createAttr"
-
-pub fn from_string (name: String, val: String) -> Attr {
- create_attr(name, val)
-}
-
-pub fn from_float (name: String, val: Float) -> Attr {
- create_attr(name, float.to_string(val))
-}
-
-pub fn from_int (name: String, val: Int) -> Attr {
- create_attr(name, int.to_string(val))
-}
-
-pub fn from_bool (name: String, val: Bool) -> Attr {
- create_attr(name, case val {
- True ->
- "true"
-
- False ->
- "false"
- })
-}
-
-// COMMON ATTRIBUTES -----------------------------------------------------------
-
-pub fn style (styles: String) -> Attr {
- from_string("style", styles)
-}
-
-pub fn class (class: String) -> Attr {
- from_string("class", class)
-}
-
-pub fn id (id: String) -> Attr {
- from_string("id", id)
-}
-
-// INPUT ATTRIBUTES ------------------------------------------------------------
-
-pub fn type_ (type_: String) -> Attr {
- from_string("type", type_)
-}
-
-pub fn value (value: String) -> Attr {
- from_string("value", value)
-}
-
-pub fn checked (checked: Bool) -> Attr {
- from_bool("checked", checked)
-}
diff --git a/src/lustre/dom/svg.gleam b/src/lustre/dom/svg.gleam
deleted file mode 100644
index e356e42..0000000
--- a/src/lustre/dom/svg.gleam
+++ /dev/null
@@ -1,32 +0,0 @@
-import lustre/dom/html.{Html}
-import lustre/dom/html/attr.{Attr}
-
-// CONSTANTS -------------------------------------------------------------------
-
-const svg_namespace = "http://www.w3.org/2000/svg"
-
-// COMMON CONSTRUCTORS ---------------------------------------------------------
-
-pub fn svg (attrs: List(Attr), children: List(Html)) -> Html {
- html.node_ns("svg", svg_namespace, attrs, children)
-}
-
-pub fn rect (attrs: List(Attr), children: List(Html)) -> Html {
- html.node_ns("rect", svg_namespace, attrs, children)
-}
-
-pub fn circle (attrs: List(Attr), children: List(Html)) -> Html {
- html.node_ns("circle", svg_namespace, attrs, children)
-}
-
-pub fn ellipse (attrs: List(Attr), children: List(Html)) -> Html {
- html.node_ns("ellipse", svg_namespace, attrs, children)
-}
-
-pub fn line (attrs: List(Attr), children: List(Html)) -> Html {
- html.node_ns("line", svg_namespace, attrs, children)
-}
-
-pub fn text (attrs: List(Attr), children: List(Html)) -> Html {
- html.node_ns("text", svg_namespace, attrs, children)
-}
diff --git a/src/lustre/dom/svg/attr.gleam b/src/lustre/dom/svg/attr.gleam
deleted file mode 100644
index c2ce5cc..0000000
--- a/src/lustre/dom/svg/attr.gleam
+++ /dev/null
@@ -1,92 +0,0 @@
-import gleam/float
-import gleam/list
-import gleam/string
-import lustre/dom/html/attr
-
-// TYPES -----------------------------------------------------------------------
-
-pub type Attr = attr.Attr
-
-// COMMON CONSTRUCTORS ---------------------------------------------------------
-
-pub fn x (x: Float) -> Attr {
- attr.from_float("x", x)
-}
-
-pub fn x1 (x1: Float) -> Attr {
- attr.from_float("x1", x1)
-}
-
-pub fn x2 (x2: Float) -> Attr {
- attr.from_float("x2", x2)
-}
-
-pub fn y (y: Float) -> Attr {
- attr.from_float("y", y)
-}
-
-pub fn y1 (y1: Float) -> Attr {
- attr.from_float("y1", y1)
-}
-
-pub fn y2 (y2: Float) -> Attr {
- attr.from_float("y2", y2)
-}
-
-pub fn width (width: Float) -> Attr {
- attr.from_float("width", width)
-}
-
-pub fn height (height: Float) -> Attr {
- attr.from_float("height", height)
-}
-
-pub fn viewbox (min_x: Float, min_y: Float, width: Float, height: Float) -> Attr {
- attr.from_string("viewbox", [ min_x, min_y, width, height ] |> list.map(float.to_string) |> string.join(" "))
-}
-
-// STYLING CONSTRUCTORS --------------------------------------------------------
-
-pub fn rgb (r: Float, g: Float, b: Float) -> String {
- rgba(r, g, b, 1.0)
-}
-
-pub fn rgba (r: Float, g: Float, b: Float, a: Float) -> String {
- let r = clamp(r, between: 0.0, and: 255.0) |> float.to_string
- let g = clamp(g, between: 0.0, and: 255.0) |> float.to_string
- let b = clamp(b, between: 0.0, and: 255.0) |> float.to_string
- let a = clamp(a, between: 0.0, and: 1.0) |> float.to_string
-
- string.concat([ "rgba(", r, ",", g, ",", b, ",", a, ")" ])
-}
-
-pub fn fill (color: String) -> Attr {
- attr.from_string("fill", color)
-}
-
-pub fn stroke (color: String) -> Attr {
- attr.from_string("stroke", color)
-}
-
-pub fn stroke_width (width: Float) -> Attr {
- attr.from_float("stroke-width", width)
-}
-
-pub fn opacity (opacity: Float) -> Attr {
- attr.from_float("opacity", clamp(opacity, between: 0.0, and: 1.0))
-}
-
-// UTILS -----------------------------------------------------------------------
-
-fn clamp (val: Float, between min: Float, and max: Float) -> Float {
- case val {
- _ if val <. min ->
- min
-
- _else if val >. max ->
- max
-
- _else ->
- val
- }
-} \ No newline at end of file
diff --git a/src/lustre/element.gleam b/src/lustre/element.gleam
new file mode 100644
index 0000000..aa0d605
--- /dev/null
+++ b/src/lustre/element.gleam
@@ -0,0 +1,493 @@
+////
+
+// IMPORTS ---------------------------------------------------------------------
+import lustre/attribute.{ Attribute, attribute }
+
+// TYPES -----------------------------------------------------------------------
+
+///
+pub external type Element(action)
+
+// CONSTRUCTORS ----------------------------------------------------------------
+
+///
+pub external fn element (tag: String, attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action)
+ = "./ffi.mjs" "element"
+
+pub external fn fragment (children: List(Element(action))) -> Element(action)
+ = "./ffi.mjs" "fragment"
+
+pub external fn text (content: String) -> Element(action)
+ = "./ffi.mjs" "text"
+
+
+// CONSTRUCTING NODES ----------------------------------------------------------
+// This list and grouping of nodes has been taken from the MDN reference at:
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
+
+// MAIN ROOT:
+pub fn html (attributes: List(Attribute(action)), head: Element(action), body: Element(action)) -> Element(action) {
+ element("html", attributes, [ head, body ])
+}
+
+
+// DOCUMENT METADATA:
+pub fn base (attributes: List(Attribute(action))) -> Element(action) {
+ element("base", attributes, [])
+}
+
+pub fn head (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("head", attributes, children)
+}
+
+pub fn meta (attributes: List(Attribute(action))) -> Element(action) {
+ element("meta", attributes, [])
+}
+
+pub fn style (attributes: List(Attribute(action)), css: String) -> Element(action) {
+ element("style", attributes, [ text(css) ])
+}
+
+pub fn title (attributes: List(Attribute(action)), name: String) -> Element(action) {
+ element("title", attributes, [ text(name) ])
+}
+
+// SECTIONING ROOT:
+pub fn body (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("body", attributes, children)
+}
+
+
+// CONTENT SECTIONING:
+pub fn address (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("address", attributes, children)
+}
+
+pub fn article (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("article", attributes, children)
+}
+
+pub fn aside (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("aside", attributes, children)
+}
+
+pub fn footer (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("footer", attributes, children)
+}
+
+pub fn header (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("header", attributes, children)
+}
+
+pub fn h1 (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("h1", attributes, children)
+}
+
+pub fn h2 (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("h2", attributes, children)
+}
+
+pub fn h3 (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("h3", attributes, children)
+}
+
+pub fn h4 (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("h4", attributes, children)
+}
+
+pub fn h5 (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("h5", attributes, children)
+}
+
+pub fn h6 (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("h6", attributes, children)
+}
+
+pub fn main (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("main", attributes, children)
+}
+
+pub fn nav (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("nav", attributes, children)
+}
+
+pub fn section (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("section", attributes, children)
+}
+
+
+// TEXT CONTENT:
+pub fn blockquote (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("blockquote", attributes, children)
+}
+
+pub fn dd (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("dd", attributes, children)
+}
+
+pub fn div (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("div", attributes, children)
+}
+
+pub fn dl (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("dl", attributes, children)
+}
+
+pub fn dt (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("dt", attributes, children)
+}
+
+pub fn figcaption (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("figcaption", attributes, children)
+}
+
+pub fn figure (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("figure", attributes, children)
+}
+
+pub fn hr (attributes: List(Attribute(action))) -> Element(action) {
+ element("hr", attributes, [])
+}
+
+pub fn li (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("li", attributes, children)
+}
+
+pub fn menu (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("menu", attributes, children)
+}
+
+pub fn ol (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("ol", attributes, children)
+}
+
+pub fn p (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("p", attributes, children)
+}
+
+pub fn pre (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("pre", attributes, children)
+}
+
+pub fn ul (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("ul", attributes, children)
+}
+
+
+// INLINE TEXT SEMANTICS:
+pub fn a (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("a", attributes, children)
+}
+
+pub fn abbr (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("abbr", attributes, children)
+}
+
+pub fn b (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("b", attributes, children)
+}
+
+pub fn bdi (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("bdi", attributes, children)
+}
+
+pub fn bdo (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("bdo", attributes, children)
+}
+
+pub fn br (attributes: List(Attribute(action))) -> Element(action) {
+ element("br", attributes, [])
+}
+
+pub fn cite (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("cite", attributes, children)
+}
+
+pub fn code (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("code", attributes, children)
+}
+
+pub fn dfn (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("dfn", attributes, children)
+}
+
+pub fn em (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("em", attributes, children)
+}
+
+pub fn i (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("i", attributes, children)
+}
+
+pub fn kbd (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("kbd", attributes, children)
+}
+
+pub fn mark (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("mark", attributes, children)
+}
+
+pub fn rp (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("rp", attributes, children)
+}
+
+pub fn rt (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("rt", attributes, children)
+}
+
+pub fn ruby (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("ruby", attributes, children)
+}
+
+pub fn s (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("s", attributes, children)
+}
+
+pub fn samp (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("samp", attributes, children)
+}
+
+pub fn small (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("small", attributes, children)
+}
+
+pub fn span (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("span", attributes, children)
+}
+
+pub fn strong (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("strong", attributes, children)
+}
+
+pub fn sub (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("sub", attributes, children)
+}
+
+pub fn sup (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("sup", attributes, children)
+}
+
+pub fn time (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("time", attributes, children)
+}
+
+pub fn u (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("u", attributes, children)
+}
+
+pub fn var_ (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("var", attributes, children)
+}
+
+pub fn wbr (attributes: List(Attribute(action))) -> Element(action) {
+ element("wbr", attributes, [])
+}
+
+
+// IMAGE AND MULTIMEDIA:
+pub fn area (attributes: List(Attribute(action))) -> Element(action) {
+ element("area", attributes, [])
+}
+
+pub fn audio (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("audio", attributes, children)
+}
+
+pub fn img (attributes: List(Attribute(action))) -> Element(action) {
+ element("img", attributes, [])
+}
+
+pub fn map (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("map", attributes, children)
+}
+
+pub fn track (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("track", attributes, children)
+}
+
+pub fn video (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("video", attributes, children)
+}
+
+
+// EMBEDDED CONTENT:
+pub fn embed (attributes: List(Attribute(action))) -> Element(action) {
+ element("embed", attributes, [])
+}
+
+pub fn iframe (attributes: List(Attribute(action))) -> Element(action) {
+ element("iframe", attributes, [])
+}
+
+pub fn object (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("object", attributes, children)
+}
+
+pub fn param (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("param", attributes, children)
+}
+
+pub fn picture (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("picture", attributes, children)
+}
+
+pub fn portal (attributes: List(Attribute(action))) -> Element(action) {
+ element("portal", attributes, [])
+}
+
+pub fn source (attributes: List(Attribute(action))) -> Element(action) {
+ element("source", attributes, [])
+}
+
+
+// SVG AND MATHML:
+pub fn svg (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("svg", [attribute("xmlns", "http://www.w3.org/2000/svg"), ..attributes], children)
+}
+
+pub fn mathml (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("mathml", [attribute("xmlns", "http://www.w3.org/1998/Math/MathML"), ..attributes], children)
+}
+
+// SCRIPTING:
+pub fn canvas (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("canvas", attributes, children)
+}
+
+pub fn noscript (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("noscript", attributes, children)
+}
+
+
+// DEMARCATING EDITS:
+pub fn del (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("del", attributes, children)
+}
+
+pub fn ins (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("ins", attributes, children)
+}
+
+
+// TABLE CONTENT:
+pub fn caption (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("caption", attributes, children)
+}
+
+pub fn col (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("col", attributes, children)
+}
+
+pub fn colgroup (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("colgroup", attributes, children)
+}
+
+pub fn table (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("table", attributes, children)
+}
+
+pub fn tbody (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("tbody", attributes, children)
+}
+
+pub fn td (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("td", attributes, children)
+}
+
+pub fn tfoot (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("tfoot", attributes, children)
+}
+
+pub fn th (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("th", attributes, children)
+}
+
+pub fn thead (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("thead", attributes, children)
+}
+
+pub fn tr (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("tr", attributes, children)
+}
+
+
+// FORMS:
+pub fn button (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("button", attributes, children)
+}
+
+pub fn datalist (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("datalist", attributes, children)
+}
+
+pub fn fieldset (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("fieldset", attributes, children)
+}
+
+pub fn form (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("form", attributes, children)
+}
+
+pub fn input (attributes: List(Attribute(action))) -> Element(action) {
+ element("input", attributes, [])
+}
+
+pub fn label (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("label", attributes, children)
+}
+
+pub fn legend (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("legend", attributes, children)
+}
+
+pub fn meter (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("meter", attributes, children)
+}
+
+pub fn optgroup (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("optgroup", attributes, children)
+}
+
+pub fn option (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("option", attributes, children)
+}
+
+pub fn output (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("output", attributes, children)
+}
+
+pub fn progress (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("progress", attributes, children)
+}
+
+pub fn select (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("select", attributes, children)
+}
+
+pub fn textarea (attributes: List(Attribute(action))) -> Element(action) {
+ element("textarea", attributes, [])
+}
+
+
+// INTERACTIVE ELEMENTS:
+pub fn details (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("details", attributes, children)
+}
+
+pub fn dialog (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("dialog", attributes, children)
+}
+
+pub fn summary (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("summary", attributes, children)
+}
+
+
+// WEB COMPONENTS:
+pub fn slot (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("slot", attributes, children)
+}
+
+pub fn template (attributes: List(Attribute(action)), children: List(Element(action))) -> Element(action) {
+ element("template", attributes, children)
+}
diff --git a/src/lustre/event.gleam b/src/lustre/event.gleam
new file mode 100644
index 0000000..23c9f49
--- /dev/null
+++ b/src/lustre/event.gleam
@@ -0,0 +1,24 @@
+import gleam/dynamic.{ Dynamic }
+import lustre/attribute.{ Attribute }
+
+pub fn on (event: String, handler: fn (Dynamic) -> action) -> Attribute(action) {
+ attribute.event(event, handler)
+}
+
+//
+
+pub fn on_click (handler: action) -> Attribute(action) {
+ on("onClick", fn (_) { handler })
+}
+
+pub fn on_input (handler: fn (String) -> action) -> Attribute(action) {
+ let decoder = dynamic.field("target", dynamic.field("value", dynamic.string))
+
+ on("onInput", fn (e) {
+ // If this fails then there's probably some sort of hideous browser bug
+ // that is way beyond the concern of our Lustre apps!
+ assert Ok(value) = decoder(e)
+
+ handler(value)
+ })
+} \ No newline at end of file
diff --git a/src/lustre/ffi.mjs b/src/lustre/ffi.mjs
new file mode 100644
index 0000000..9d27c5e
--- /dev/null
+++ b/src/lustre/ffi.mjs
@@ -0,0 +1,65 @@
+import * as React from 'react'
+import * as ReactDOM from 'react-dom'
+
+export const Program = ({ init, update, view }) => () => {
+ return React.createElement(() => {
+ const [state, dispatch] = React.useReducer(update, init)
+
+ return view(state)(dispatch)
+ })
+}
+
+export const mount = (program, selector) => {
+ const root = document.querySelector(selector)
+ const App = Program(program)
+
+ ReactDOM.render(App(), root)
+}
+
+// -----------------------------------------------------------------------------
+
+export const element = (tag, attributes, children) => (dispatch) => {
+ const props = attributes.toArray().map(attr => {
+ switch (attr.constructor.name) {
+ case "Attribute":
+ case "Property":
+ return [attr.name, attr.value]
+
+ case "Event":
+ return [attr.on, (e) => dispatch(attr.handler(e))]
+
+ default:
+ throw new Error(`Unknown attribute type: ${attr.constructor.name}`)
+ }
+ })
+
+ return React.createElement(tag,
+ // React expects an object of "props" where the keys are attribute names
+ // like "class" or "onClick" and their corresponding values. Lustre's API
+ // works with lists, though.
+ //
+ // The snippet above converts our Gleam list of attributes to a JavaScript
+ // array of key/value pairs. This below turns that array into an object.
+ Object.fromEntries(props),
+
+ // Recursively pass down the dispatch function to all children. Text nodes
+ // – constructed below – aren't functions
+ ...children.toArray().map(child => typeof child === 'function'
+ ? child(dispatch)
+ : child
+ )
+ )
+}
+
+export const fragment = (children) => (dispatch) => {
+ return React.createElement(React.Fragment, null,
+ ...children.toArray().map(child => typeof child === 'function'
+ ? child(dispatch)
+ : child
+ )
+ )
+}
+
+// This is just an identity function! We need it because we want to trick Gleam
+// into converting a `String` into an `Element(action)` .
+export const text = (content) => content
diff --git a/src/lustre/test.gleam b/src/lustre/test.gleam
deleted file mode 100644
index f8f913b..0000000
--- a/src/lustre/test.gleam
+++ /dev/null
@@ -1,11 +0,0 @@
-import lustre/dom/html
-import lustre/dom/html/attr
-
-pub fn html_test () {
- html.div([ attr.class("foo") ], [
- html.p([], [
- html.text("testing")
- ]),
- html.input([ attr.value("hello") ])
- ])
-}