aboutsummaryrefslogtreecommitdiff
path: root/src/lustre.ffi.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lustre.ffi.mjs')
-rw-r--r--src/lustre.ffi.mjs85
1 files changed, 23 insertions, 62 deletions
diff --git a/src/lustre.ffi.mjs b/src/lustre.ffi.mjs
index fa022d1..3a6556e 100644
--- a/src/lustre.ffi.mjs
+++ b/src/lustre.ffi.mjs
@@ -1,10 +1,6 @@
-import { innerHTML, createTree } from "./runtime.ffi.mjs";
-import { Ok, Error, List } from "./gleam.mjs";
-import {
- Some,
- None,
- map as option_map,
-} from "../gleam_stdlib/gleam/option.mjs";
+import { morphdom } from "./runtime.ffi.mjs";
+import { Ok, Error } from "./gleam.mjs";
+import { map } from "./lustre/element.mjs";
// RUNTIME ---------------------------------------------------------------------
@@ -18,33 +14,30 @@ export class App {
#willUpdate = false;
#didUpdate = false;
- // These are the three functions that the user provides to the runtime.
- #__init;
- #__update;
- #__render;
-
constructor(init, update, render) {
- this.#__init = init;
- this.#__update = update;
- this.#__render = render;
+ this.#init = init;
+ this.#update = update;
+ this.#view = render;
}
start(selector = "body") {
if (this.#root) return this;
try {
- this.#root = document.querySelector(selector);
+ const el = document.querySelector(selector);
+ const [next, cmds] = this.#init();
+
+ this.#root = el;
+ this.#state = next;
+ this.#commands = cmds[0].toArray();
+ this.#didUpdate = true;
+
+ window.requestAnimationFrame(() => this.#tick());
+
+ return new Ok((msg) => this.dispatch(msg));
} catch (_) {
return new Error(undefined);
}
-
- const [next, cmds] = this.#__init();
- this.#state = next;
- this.#commands = cmds[0].toArray();
- this.#didUpdate = true;
-
- window.requestAnimationFrame(() => this.#tick());
- return new Ok((msg) => this.dispatch(msg));
}
dispatch(msg) {
@@ -55,22 +48,23 @@ export class App {
}
#render() {
- const node = this.#__render(this.#state);
- const tree = createTree(map(node, (msg) => this.dispatch(msg)));
+ const node = this.#view(this.#state);
+ const vdom = map(node, (msg) => this.dispatch(msg));
- innerHTML(this.#root, tree);
+ morphdom(this.#root.firstChild, vdom);
}
#tick() {
this.#flush();
this.#didUpdate && this.#render();
+ this.#didUpdate = false;
this.#willUpdate = false;
}
#flush(times = 0) {
if (this.#queue.length) {
while (this.#queue.length) {
- const [next, cmds] = this.#__update(this.#state, this.#queue.shift());
+ const [next, cmds] = this.#update(this.#state, this.#queue.shift());
this.#state = next;
this.#commands.concat(cmds[0].toArray());
@@ -92,37 +86,4 @@ export class App {
}
export const setup = (init, update, render) => new App(init, update, render);
-export const start = (app, selector = "body") => app.start(selector);
-
-// VDOM ------------------------------------------------------------------------
-
-export const node = (tag, attrs, children) =>
- createTree(tag, Object.fromEntries(attrs.toArray()), children.toArray());
-export const text = (content) => content;
-export const attr = (key, value) => {
- if (value instanceof List) return [key, value.toArray()];
- if (value instanceof Some) return [key, value[0]];
- if (value instanceof None) return [key, undefined];
-
- return [key, value];
-};
-export const on = (event, handler) => [`on${event}`, handler];
-export const map = (node, f) => ({
- ...node,
- attributes: Object.entries(node.attributes).reduce((attrs, [key, value]) => {
- // It's safe to mutate the `attrs` object here because we created it at
- // the start of the reduce: it's not shared with any other code.
-
- // If the attribute is an event handler, wrap it in a function that
- // transforms
- if (key.startsWith("on") && typeof value === "function") {
- attrs[key] = (e) => option_map(value(e), f);
- } else {
- attrs[key] = value;
- }
-
- return attrs;
- }, {}),
- childNodes: node.childNodes.map((child) => map(child, f)),
-});
-export const styles = (list) => Object.fromEntries(list.toArray());
+export const start = (app, selector) => app.start(selector);