From fc085b9481065b18b47fb8fe13f7a7a74e3b7d3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:29:42 +0000 Subject: [PATCH 1/4] Initial plan From 26517d530322b3bb9367ec2654fa1a8310e5890d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:36:25 +0000 Subject: [PATCH 2/4] Add debug assertion enforcing hook_dom in node_map and simplify reordering code Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> --- README.md | 3 +++ src/lui.js | 67 ++++++------------------------------------------------ 2 files changed, 10 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 64624d1..37938c8 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,9 @@ The component you pass to `node_map` gets mounted for each item of the array you > [!IMPORTANT] > Allowed array items are numbers, strings and objects. If you pass objects, they must have an unique `id` property. There must not be two items of the same value or id. +> [!IMPORTANT] +> Components used with `node_map` must use `hook_dom` to define their root element. This is required for efficient reordering of list items. + ### DOM components The leaves of your component tree are mostly made out of native dom elements. To use such a component, use `node_dom` instead of `node`. The signature is the same, except for the first argument being a descriptor, similar to css selectors: `tagName[attr1=value][attr2][...]` diff --git a/src/lui.js b/src/lui.js index 344e1ff..52ea4f3 100644 --- a/src/lui.js +++ b/src/lui.js @@ -976,6 +976,10 @@ const instance_render = (dom_parent, dom_first) => { dom_first ); + DEBUG && + !child.dom && + error('node_map item component must use hook_dom'); + child.dom && dom_parent.insertBefore( child.dom_first = child.dom, @@ -983,14 +987,11 @@ const instance_render = (dom_parent, dom_first) => { ); } else { - // TODO this algorithm still sucks - const dom_last = instance_dom_last_get(child); if ( - dom_last && - dom_last.nextSibling !== dom_first + child.dom.nextSibling !== dom_first ) { - VERBOSE && log('instance_reinsert ' + key); - instance_reinsert(child, dom_parent, dom_first); + VERBOSE && log('item reinsert ' + key); + dom_parent.insertBefore(child.dom, dom_first); } if ( @@ -1181,60 +1182,6 @@ const list_data_index = (list_data, items_map, items_order) => { return items_objects; } -/** - gets last node of an instance (only an ugly workaround!) - @param {TYPE_INSTANCE} instance - @return {?HTMLElement} -*/ -const instance_dom_last_get = instance => { - if (instance.dom) return instance.dom; - let instance_childs; - let i = ( - (instance_childs = instance.childs) - ? instance_childs.length - : 0 - ); - let itm, itm_dom; - while (i > 0) { - if ( - ( - itm_dom = instance_childs[--i] - ) && - ( - itm = instance_dom_last_get(itm_dom) - ) - ) return itm; - } - return null_; -}; - -/** - reinsert all dom nodes of an instance - @param {TYPE_INSTANCE} instance - @param {HTMLElement} dom_parent - @param {?HTMLElement} dom_first - @return {?HTMLElement} -*/ -const instance_reinsert = (instance, dom_parent, dom_first) => { - if (instance.dom) { - return /** @type {HTMLElement} */ (dom_parent.insertBefore(instance.dom, dom_first)); - } - if (instance.dom_first) { - let childs_index = instance.childs.length; - do { - instance.childs[--childs_index] && ( - dom_first = instance_reinsert( - instance.childs[childs_index], - dom_parent, - dom_first - ) - ); - } - while (childs_index > 0); - } - return dom_first; -} - /** return instance for slots @param {TYPE_SLOTS} slots From 0c1d9893c9aa2370de4c5f9d75276495a8b53372 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:37:59 +0000 Subject: [PATCH 3/4] Improve error message and add null safety check for non-DEBUG builds Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> --- src/lui.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lui.js b/src/lui.js index 52ea4f3..7371063 100644 --- a/src/lui.js +++ b/src/lui.js @@ -978,7 +978,7 @@ const instance_render = (dom_parent, dom_first) => { DEBUG && !child.dom && - error('node_map item component must use hook_dom'); + error('node_map item components must call hook_dom() to define their root DOM element'); child.dom && dom_parent.insertBefore( @@ -988,6 +988,7 @@ const instance_render = (dom_parent, dom_first) => { } else { if ( + child.dom && child.dom.nextSibling !== dom_first ) { VERBOSE && log('item reinsert ' + key); From b916bd842ed799d211612ff1e3347ff741b9a5c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:27:19 +0000 Subject: [PATCH 4/4] Remove redundant child.dom checks since hook_dom is now required Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> --- src/lui.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/lui.js b/src/lui.js index 7371063..112b92f 100644 --- a/src/lui.js +++ b/src/lui.js @@ -980,15 +980,13 @@ const instance_render = (dom_parent, dom_first) => { !child.dom && error('node_map item components must call hook_dom() to define their root DOM element'); - child.dom && - dom_parent.insertBefore( - child.dom_first = child.dom, - dom_first - ); + dom_parent.insertBefore( + child.dom_first = child.dom, + dom_first + ); } else { if ( - child.dom && child.dom.nextSibling !== dom_first ) { VERBOSE && log('item reinsert ' + key); @@ -1025,11 +1023,8 @@ const instance_render = (dom_parent, dom_first) => { } } - ( - childs[child.parent_index = items_index] = child - ).dom_first && ( - dom_first = child.dom_first - ); + childs[child.parent_index = items_index] = child; + dom_first = child.dom_first; } instance.dom_first =