diff --git a/packages/nuedom/src/compiler/html5.js b/packages/nuedom/src/compiler/html5.js index 5263854d..351772ad 100644 --- a/packages/nuedom/src/compiler/html5.js +++ b/packages/nuedom/src/compiler/html5.js @@ -5,7 +5,7 @@ export const JS = '_ $e document window location localStorage sessionStorage ale export const EVENTS = 'click submit change input focus blur keydown keyup keypress mouseover mouseout mousedown mouseup mousemove mouseenter mouseleave wheel scroll resize load unload beforeunload error abort touchstart touchend touchmove touchcancel drag dragstart dragend dragenter dragleave dragover drop animationstart animationend animationiteration transitionend contextmenu dblclick pointerdown pointermove cut copy paste'.split(' ') -export const BOOLEAN = 'disabled checked selected hidden readonly required autofocus autoplay async controls defer loop multiple muted nowrap open reversed scoped seamless sorted translate visibility pointer-events draggable contenteditable'.split(' ') +export const BOOLEAN = 'disabled checked selected hidden readonly required autofocus autoplay async controls defer loop multiple muted nowrap open reversed scoped seamless sorted translate visibility pointer-events contenteditable'.split(' ') export const HTML5_TAGS = 'a abbr address area article aside audio b base bdi bdo blockquote body br button canvas caption cite code col colgroup data datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hr html i iframe img input ins kbd label legend li link main map mark meta meter nav noscript object ol optgroup option output p param picture pre progress q rp rt ruby s samp script section select small source span strong style sub summary sup table tbody td template textarea tfoot th thead time title tr track u ul var video wbr slot portal'.split(' ') diff --git a/packages/nuedom/src/dom/diff.js b/packages/nuedom/src/dom/diff.js index 81042322..043b32aa 100644 --- a/packages/nuedom/src/dom/diff.js +++ b/packages/nuedom/src/dom/diff.js @@ -1,4 +1,3 @@ - // Nue • (c) 2025 Tero Piirainen & contributors, MIT Licensed /* @@ -27,13 +26,17 @@ export function domdiff(prev, next) { const parent = prev.parentNode if (prev == next) return prev if (!prev && next) return parent?.appendChild(next) - if (!next && prev) return parent?.removeChild(prev) + if (!next && prev){ + cleanupObserver(prev) + return parent?.removeChild(prev) + } if (prev.nodeType == 3 && next.nodeType == 3) { prev.textContent = next.textContent return prev } if (prev.nodeType != next.nodeType || prev.tagName != next.tagName) { + cleanupObserver(prev) return parent.replaceChild(next, prev) } @@ -61,7 +64,11 @@ function diffChildren(prev, kids) { const prevKid = prevKids[i] const kid = kids[i] if (!prevKid) prev.appendChild(kid) - else if (!kid) prev.removeChild(prevKids[i]) + else if (!kid) + { + cleanupObserver(prevKids[i]) + prev.removeChild(prevKids[i]) + } else domdiff(prevKid, kid, prev) } } @@ -70,10 +77,32 @@ function diffChildrenByKey(prev, kids) { const prevKids = Array.from(prev.childNodes) const keyMap = {} for (let kid of prevKids) keyMap[kid.getAttribute('key')] = kid + + + for (let kid of prevKids) { + const key = kid.getAttribute('key') + if (!kids.find(k => k.getAttribute('key') === key)) { + cleanupObserver(kid) + } + } + while (prev.firstChild) prev.removeChild(prev.firstChild) for (let kid of kids) { const key = kid.getAttribute('key') const prevKid = keyMap[key] prev.appendChild(prevKid ? domdiff(prevKid, kid, prev) : kid) } -} \ No newline at end of file +} + + +function cleanupObserver(node) { + if (node._nueObserver) { + node._nueObserver.disconnect() + delete node._nueObserver + } + + + if (node.childNodes) { + Array.from(node.childNodes).forEach(child => cleanupObserver(child)) + } +} diff --git a/packages/nuedom/src/dom/node.js b/packages/nuedom/src/dom/node.js index 1a1fb23a..a8b14298 100644 --- a/packages/nuedom/src/dom/node.js +++ b/packages/nuedom/src/dom/node.js @@ -1,11 +1,9 @@ - // Nue • (c) 2025 Tero Piirainen & contributors, MIT Licensed import { domdiff } from './diff.js' const is_browser = typeof window == 'object' - export function createNode(ast, data={}, opts={}, parent) { const { script } = ast let root @@ -25,7 +23,7 @@ export function createNode(ast, data={}, opts={}, parent) { } // Object.assign(self, getAttrData(ast, self)) - const self = { ...data, ...opts.globals, ...getAttrData(ast, data), update, parent } + const self = { ...data, ...opts.globals, ...getAttrData(ast, data), update, cleanup,parent } if (script) { @@ -80,7 +78,31 @@ export function createNode(ast, data={}, opts={}, parent) { setAttributes(tag, ast, self) if (parent && ast.is_child) setAttributes(tag, parent.ast, parent.self) - + + + if (is_browser) { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes') { + const attrName = mutation.attributeName + const attr = ast.attr?.find(a => a.name === attrName) + + if (attr && !attr.is_data) { + const newValue = tag.getAttribute(attrName) + if (attr.bool) { + self[attrName] = tag.hasAttribute(attrName) + } else { + self[attrName] = newValue + } + } + } + }) + }) + + observer.observe(tag, { attributes: true }) + + tag._nueObserver = observer + } const am = tag.classList.length if (am > (opts.max_class_names || 3)) { console.error(`More than ${am} class names in class="${tag.classList }"`) @@ -202,6 +224,13 @@ export function createNode(ast, data={}, opts={}, parent) { return frag } + function cleanup() { + if (root) { + cleanupObserver(root) + } + fire('oncleanup') + } + function findComponent(ast, self) { let { mount, tag } = ast if (mount) tag = mount.fn ? exec(mount.fn, self) : mount.val @@ -209,7 +238,18 @@ export function createNode(ast, data={}, opts={}, parent) { } return { - mount, update, render, ast, self, fire, get root() { return root }, + mount, update, render, ast, self, fire,cleanup, get root() { return root }, + } +} + +function cleanupObserver(node) { + if (node._nueObserver) { + node._nueObserver.disconnect() + delete node._nueObserver + } + + if (node.childNodes) { + Array.from(node.childNodes).forEach(child => cleanupObserver(child)) } }