Packages
lustre
3.0.1
5.7.1
5.7.0
5.6.0
5.5.2
5.5.1
5.5.0
5.4.0
5.3.5
5.3.4
5.3.3
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.1
5.1.0
5.0.3
5.0.2
5.0.1
5.0.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.0
4.0.0-rc1
4.0.0-rc.2
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.2
3.0.0-rc.1
2.0.1
2.0.0
1.3.0
1.2.0
1.1.0
1.0.0
Create HTML templates, single page applications, Web Components, and real-time server components in Gleam!
Current section
Files
Jump to
Current section
Files
src/runtime.ffi.mjs
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);
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);
console.log(el.dataset);
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;
}