Fix morph-children: empty-id keying, consumed-set cleanup, island attr sync
Three bugs in the DOM morph algorithm (web/engine.sx): 1. Empty-id keying: dom-id returns "" (not nil) for elements without an id attribute, and "" is truthy in SX. Every id-less element was stored under key "" in old-by-id, causing all new children to match the same old element via the keyed branch — collapsing all children into one. Fix: guard with (and id (not (empty? id))) in map building and matching. 2. Cleanup bug: the oi-cursor cleanup (range oi len) removed keyed elements that were matched and moved from positions >= oi, and failed to remove unmatched elements at positions < oi. Fix: track consumed indices in a dict and remove all unconsumed elements regardless of position. 3. Island attr sync: morph-node delegated to morph-island-children without first syncing the island element's own attributes (e.g. data-sx-state). Fix: call sync-attrs before morph-island-children. Also: pass explicit `true` to all dom-clone calls (deep clone parameter). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -342,11 +342,16 @@
|
|||||||
(=
|
(=
|
||||||
(dom-get-attr old-node "data-sx-island")
|
(dom-get-attr old-node "data-sx-island")
|
||||||
(dom-get-attr new-node "data-sx-island")))
|
(dom-get-attr new-node "data-sx-island")))
|
||||||
(morph-island-children old-node new-node)
|
(do
|
||||||
|
(sync-attrs old-node new-node)
|
||||||
|
(morph-island-children old-node new-node))
|
||||||
(or
|
(or
|
||||||
(not (= (dom-node-type old-node) (dom-node-type new-node)))
|
(not (= (dom-node-type old-node) (dom-node-type new-node)))
|
||||||
(not (= (dom-node-name old-node) (dom-node-name new-node))))
|
(not (= (dom-node-name old-node) (dom-node-name new-node))))
|
||||||
(dom-replace-child (dom-parent old-node) (dom-clone new-node) old-node)
|
(dom-replace-child
|
||||||
|
(dom-parent old-node)
|
||||||
|
(dom-clone new-node true)
|
||||||
|
old-node)
|
||||||
(or (= (dom-node-type old-node) 3) (= (dom-node-type old-node) 8))
|
(or (= (dom-node-type old-node) 3) (= (dom-node-type old-node) 8))
|
||||||
(when
|
(when
|
||||||
(not (= (dom-text-content old-node) (dom-text-content new-node)))
|
(not (= (dom-text-content old-node) (dom-text-content new-node)))
|
||||||
@@ -411,25 +416,37 @@
|
|||||||
(let
|
(let
|
||||||
((old-kids (dom-child-list old-parent))
|
((old-kids (dom-child-list old-parent))
|
||||||
(new-kids (dom-child-list new-parent))
|
(new-kids (dom-child-list new-parent))
|
||||||
(old-by-id
|
(old-by-id (dict))
|
||||||
(reduce
|
(old-idx-by-id (dict))
|
||||||
(fn
|
(consumed (dict))
|
||||||
((acc :as dict) kid)
|
(oi 0)
|
||||||
(let
|
(idx 0))
|
||||||
((id (dom-id kid)))
|
(for-each
|
||||||
(if id (do (dict-set! acc id kid) acc) acc)))
|
(fn
|
||||||
(dict)
|
(kid)
|
||||||
old-kids))
|
(let
|
||||||
(oi 0))
|
((id (dom-id kid)))
|
||||||
|
(when
|
||||||
|
(and id (not (empty? id)))
|
||||||
|
(dict-set! old-by-id id kid)
|
||||||
|
(dict-set! old-idx-by-id id idx)))
|
||||||
|
(set! idx (inc idx)))
|
||||||
|
old-kids)
|
||||||
(for-each
|
(for-each
|
||||||
(fn
|
(fn
|
||||||
(new-child)
|
(new-child)
|
||||||
(let
|
(let
|
||||||
((match-id (dom-id new-child))
|
((raw-id (dom-id new-child))
|
||||||
|
(match-id (if (and raw-id (not (empty? raw-id))) raw-id nil))
|
||||||
(match-by-id (if match-id (dict-get old-by-id match-id) nil)))
|
(match-by-id (if match-id (dict-get old-by-id match-id) nil)))
|
||||||
(cond
|
(cond
|
||||||
(and match-by-id (not (nil? match-by-id)))
|
(and match-by-id (not (nil? match-by-id)))
|
||||||
(do
|
(do
|
||||||
|
(let
|
||||||
|
((matched-idx (dict-get old-idx-by-id match-id)))
|
||||||
|
(when
|
||||||
|
matched-idx
|
||||||
|
(dict-set! consumed (str matched-idx) true)))
|
||||||
(when
|
(when
|
||||||
(and
|
(and
|
||||||
(< oi (len old-kids))
|
(< oi (len old-kids))
|
||||||
@@ -443,20 +460,25 @@
|
|||||||
(< oi (len old-kids))
|
(< oi (len old-kids))
|
||||||
(let
|
(let
|
||||||
((old-child (nth old-kids oi)))
|
((old-child (nth old-kids oi)))
|
||||||
(if
|
(let
|
||||||
(and (dom-id old-child) (not match-id))
|
((old-id (dom-id old-child)))
|
||||||
(dom-insert-before
|
(if
|
||||||
old-parent
|
(and old-id (not (empty? old-id)) (not match-id))
|
||||||
(dom-clone new-child)
|
(dom-insert-before
|
||||||
old-child)
|
old-parent
|
||||||
(do (morph-node old-child new-child) (set! oi (inc oi)))))
|
(dom-clone new-child true)
|
||||||
:else (dom-append old-parent (dom-clone new-child)))))
|
old-child)
|
||||||
|
(do
|
||||||
|
(dict-set! consumed (str oi) true)
|
||||||
|
(morph-node old-child new-child)
|
||||||
|
(set! oi (inc oi))))))
|
||||||
|
:else (dom-append old-parent (dom-clone new-child true)))))
|
||||||
new-kids)
|
new-kids)
|
||||||
(for-each
|
(for-each
|
||||||
(fn
|
(fn
|
||||||
((i :as number))
|
(i)
|
||||||
(when
|
(when
|
||||||
(>= i oi)
|
(not (dict-get consumed (str i)))
|
||||||
(let
|
(let
|
||||||
((leftover (nth old-kids i)))
|
((leftover (nth old-kids i)))
|
||||||
(when
|
(when
|
||||||
@@ -465,7 +487,7 @@
|
|||||||
(not (dom-has-attr? leftover "sx-preserve"))
|
(not (dom-has-attr? leftover "sx-preserve"))
|
||||||
(not (dom-has-attr? leftover "sx-ignore")))
|
(not (dom-has-attr? leftover "sx-ignore")))
|
||||||
(dom-remove-child old-parent leftover)))))
|
(dom-remove-child old-parent leftover)))))
|
||||||
(range oi (len old-kids))))))
|
(range 0 (len old-kids))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
morph-island-children
|
morph-island-children
|
||||||
@@ -588,7 +610,7 @@
|
|||||||
(morph-children target wrapper)))
|
(morph-children target wrapper)))
|
||||||
"outerHTML"
|
"outerHTML"
|
||||||
(let
|
(let
|
||||||
((parent (dom-parent target)) (new-el (dom-clone new-nodes)))
|
((parent (dom-parent target)) (new-el (dom-clone new-nodes true)))
|
||||||
(if
|
(if
|
||||||
(dom-is-fragment? new-nodes)
|
(dom-is-fragment? new-nodes)
|
||||||
(let
|
(let
|
||||||
@@ -596,7 +618,7 @@
|
|||||||
(if
|
(if
|
||||||
fc
|
fc
|
||||||
(do
|
(do
|
||||||
(set! new-el (dom-clone fc))
|
(set! new-el (dom-clone fc true))
|
||||||
(dom-replace-child parent new-el target)
|
(dom-replace-child parent new-el target)
|
||||||
(let
|
(let
|
||||||
((sib (dom-next-sibling fc)))
|
((sib (dom-next-sibling fc)))
|
||||||
|
|||||||
Reference in New Issue
Block a user