aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHayleigh Thompson <me@hayleigh.dev>2023-07-23 18:17:59 +0100
committerHayleigh Thompson <me@hayleigh.dev>2023-07-23 18:17:59 +0100
commit796642e3037053245b8fc05fc89da3d41d5425c2 (patch)
treee47e4807d6dedcf350280406eb6a21d75dbe3147
parent069846f09022a3a978e09f1a25f452850c104ddd (diff)
downloadlustre-796642e3037053245b8fc05fc89da3d41d5425c2.tar.gz
lustre-796642e3037053245b8fc05fc89da3d41d5425c2.zip
:bug: Fix bugs with vdom patching when removing elements or rendering into components.
-rw-r--r--src/lustre.ffi.mjs6
-rw-r--r--src/runtime.ffi.mjs105
2 files changed, 82 insertions, 29 deletions
diff --git a/src/lustre.ffi.mjs b/src/lustre.ffi.mjs
index 39ba9ec..38b3a95 100644
--- a/src/lustre.ffi.mjs
+++ b/src/lustre.ffi.mjs
@@ -10,7 +10,6 @@ import { Ok, Error, isEqual } from "./gleam.mjs";
///
export class App {
#root = null;
- #el = null;
#state = null;
#queue = [];
#effects = [];
@@ -68,8 +67,7 @@ export class App {
}
destroy() {
- this.#root = null;
- this.#el.remove();
+ this.#root.remove();
this.#state = null;
this.#queue = [];
this.#effects = [];
@@ -83,7 +81,7 @@ export class App {
const node = this.#view(this.#state);
const vdom = map(node, (msg) => this.dispatch(msg));
- this.#el = morph(this.#root, vdom);
+ morph(this.#root, vdom);
}
#tick() {
diff --git a/src/runtime.ffi.mjs b/src/runtime.ffi.mjs
index 1bdc1f8..1a1af4b 100644
--- a/src/runtime.ffi.mjs
+++ b/src/runtime.ffi.mjs
@@ -1,23 +1,23 @@
import { element, namespaced, text } from "./lustre/element.mjs";
-import { List } from "./gleam.mjs";
+import { List, Empty } from "./gleam.mjs";
import { Some, None } from "../gleam_stdlib/gleam/option.mjs";
const Element = element("").constructor;
const ElementNs = namespaced("", "").constructor;
const Text = text("").constructor;
-export function morph(prev, curr) {
+export function morph(prev, curr, parent) {
if (curr instanceof ElementNs)
return prev?.nodeType === 1 &&
prev.nodeName === curr[0].toUpperCase() &&
prev.namespaceURI === curr[3]
- ? morphElement(prev, curr, curr[3])
- : createElement(prev, curr, curr[3]);
+ ? morphElement(prev, curr, curr[3], parent)
+ : createElement(prev, curr, curr[3], parent);
if (curr instanceof Element) {
return prev?.nodeType === 1 && prev.nodeName === curr[0].toUpperCase()
- ? morphElement(prev, curr)
- : createElement(prev, curr);
+ ? morphElement(prev, curr, null, parent)
+ : createElement(prev, curr, null, parent);
}
if (curr instanceof Text) {
@@ -38,7 +38,7 @@ export function morph(prev, curr) {
// ELEMENTS --------------------------------------------------------------------
-function createElement(prev, curr, ns) {
+function createElement(prev, curr, ns, parent = null) {
const el = ns
? document.createElementNS(ns, curr[0])
: document.createElement(curr[0]);
@@ -49,17 +49,38 @@ function createElement(prev, curr, ns) {
attr = attr.tail;
}
- let child = curr[2];
- while (child.head) {
- el.appendChild(morph(null, child.head));
- child = child.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;
+ }
+ }
- if (prev) prev.replaceWith(el);
+ while (child.head) {
+ el.appendChild(morph(null, child.head, el));
+ child = child.tail;
+ }
+ } else {
+ let child = curr[2];
+ while (child.head) {
+ el.appendChild(morph(null, child.head, el));
+ child = child.tail;
+ }
+
+ if (prev) prev.replaceWith(el);
+ }
return el;
}
-function morphElement(prev, curr, ns) {
+function morphElement(prev, curr, ns, parent) {
const prevAttrs = prev.attributes;
const currAttrs = new Map();
@@ -83,21 +104,55 @@ function morphElement(prev, curr, ns) {
morphAttr(prev, name, value);
}
- let prevChild = prev.firstChild;
- let currChild = curr[2];
+ 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, prev);
+ currChild = currChild.tail;
+ }
- while (prevChild) {
- if (currChild.head) {
- morph(prevChild, currChild.head);
- currChild = currChild.tail;
+ prevChild = prevChild.nextSibling;
}
- prevChild = prevChild.nextSibling;
- }
+ while (currChild.head) {
+ prev.appendChild(morph(null, currChild.head, prev));
+ currChild = currChild.tail;
+ }
+ } else {
+ let prevChild = prev.firstChild;
+ let currChild = curr[2];
+
+ while (prevChild) {
+ if (currChild.head) {
+ morph(prevChild, currChild.head, prev);
+ currChild = currChild.tail;
+ prevChild = prevChild.nextSibling;
+ } else {
+ const next = prevChild.nextSibling;
+ prevChild.remove();
+ prevChild = next;
+ }
+ }
- while (currChild.head) {
- prev.appendChild(morph(null, currChild.head));
- currChild = currChild.tail;
+ while (currChild.head) {
+ prev.appendChild(morph(null, currChild.head, prev));
+ currChild = currChild.tail;
+ }
}
return prev;