From aa787bf3b4a5f2045efbe982696e26403a3ddf8c Mon Sep 17 00:00:00 2001 From: David Marrs Date: Wed, 14 Aug 2024 17:38:44 +0100 Subject: [PATCH 1/2] Expose roots to afterRefresh hook. This allows for direct manipulation of the DOM after you are done rendering it. Useful for focusing elements and other things. --- domchanger.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domchanger.js b/domchanger.js index 6775ad2..36ef303 100644 --- a/domchanger.js +++ b/domchanger.js @@ -88,7 +88,7 @@ function createComponent(component, parent, owner) { roots.__keys.forEach(function (key) { var node = roots[key]; if (node) { // FIXED BUG: node could be removed at this moment already - if (node.el) node.el.parentNode.removeChild(node.el); + if (node.el && node.el.parentNode) node.el.parentNode.removeChild(node.el); else if (node.destroy) node.destroy(); delete roots[key]; roots.__keys = roots.__keys.filter(function(rmKey) { return rmKey !== key; }); @@ -101,7 +101,7 @@ function createComponent(component, parent, owner) { if (!render) return; var tree = nameNodes(render.apply(null, data)); apply(parent, tree, roots); - afterRefresh(); + afterRefresh(roots); } function removeItem(item) { From 0e29d49a2698600a1677e4f9bcc18fbabd48e58e Mon Sep 17 00:00:00 2001 From: David Marrs Date: Fri, 15 Nov 2024 19:24:45 +0000 Subject: [PATCH 2/2] Render text nodes via their parent element This patch fixes a bug I experienced when working with contenteditable elements. It turns out that deleting the text of a contenteditable element causes the text node to be removed from the DOM and replaced with a
element instead. domChanger apparently does not expect text nodes to ever be removed by the browser, presumably because the contenteditable attribute either didn't exist or wasn't considered at the time of writing. So it continues to store a reference to the text node in its tree after it has been removed from the DOM. It will continue to update this node on subsequent renders, but the user will never see them as the node is no longer attached. This patch fixes that issue by not storing any references to text nodes at all, but instead by altering their contents using their parents' innerText property. This should work fine as there is only ever one text node per element and its value should always be the same as the value of innerText. --- domchanger.js | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/domchanger.js b/domchanger.js index 36ef303..a407a61 100644 --- a/domchanger.js +++ b/domchanger.js @@ -138,29 +138,8 @@ function createComponent(component, parent, owner) { var item = oldTree[key]; var newItem = newTree[key]; - // Handle text nodes - if ("text" in newItem) { - if (item) { - // Update the text if it's changed. - if (newItem.text !== item.text) { - item.el.nodeValue = item.text = newItem.text; - // console.log("updated") - } - } - else { - // Otherwise create a new text node. - item = oldTree[key] = { - text: newItem.text, - el: document.createTextNode(newItem.text) - }; - oldTree.__keys.push(key) - top.appendChild(item.el); - // console.log("created") - } - } - // Handle tag nodes - else if (newItem.tagName) { + if (newItem.tagName) { // Create a new item if there isn't one if (!item) { item = oldTree[key] = { @@ -182,7 +161,31 @@ function createComponent(component, parent, owner) { updateAttrs(item.el, newItem.props, item.props); if (newItem.children) { - apply(item.el, newItem.children, item.children); + // Handle text nodes: + // Text nodes can be removed from the DOM by the browser, + // e.g. by deleting text from a contenteditable element. + // We therefore edit text nodes via the element containing them, + // otherwise the vDOM may update a node that is no longer attached + // to the DOM, causing missing text on screen. + if (newItem.children.text) { + if (item.children && item.children.text) { + // Update the text via its parent if it has changed. + if (item.children.text.text !== newItem.children.text.text) { + item.el.innerText = item.children.text.text = newItem.children.text.text; + } + } else { + // Otherwise create a new text node. + item.children = item.children || {} + item.children.text = { + text: newItem.children.text.text, + }; + item.children.__keys = item.children.__keys || []; + item.children.__keys.push("text"); + item.el.innerText = item.children.text.text; + } + } else { + apply(item.el, newItem.children, item.children); + } } }