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 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
|
||||
(not (= (dom-node-type old-node) (dom-node-type 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))
|
||||
(when
|
||||
(not (= (dom-text-content old-node) (dom-text-content new-node)))
|
||||
@@ -411,25 +416,37 @@
|
||||
(let
|
||||
((old-kids (dom-child-list old-parent))
|
||||
(new-kids (dom-child-list new-parent))
|
||||
(old-by-id
|
||||
(reduce
|
||||
(fn
|
||||
((acc :as dict) kid)
|
||||
(let
|
||||
((id (dom-id kid)))
|
||||
(if id (do (dict-set! acc id kid) acc) acc)))
|
||||
(dict)
|
||||
old-kids))
|
||||
(oi 0))
|
||||
(old-by-id (dict))
|
||||
(old-idx-by-id (dict))
|
||||
(consumed (dict))
|
||||
(oi 0)
|
||||
(idx 0))
|
||||
(for-each
|
||||
(fn
|
||||
(kid)
|
||||
(let
|
||||
((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
|
||||
(fn
|
||||
(new-child)
|
||||
(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)))
|
||||
(cond
|
||||
(and match-by-id (not (nil? match-by-id)))
|
||||
(do
|
||||
(let
|
||||
((matched-idx (dict-get old-idx-by-id match-id)))
|
||||
(when
|
||||
matched-idx
|
||||
(dict-set! consumed (str matched-idx) true)))
|
||||
(when
|
||||
(and
|
||||
(< oi (len old-kids))
|
||||
@@ -443,20 +460,25 @@
|
||||
(< oi (len old-kids))
|
||||
(let
|
||||
((old-child (nth old-kids oi)))
|
||||
(if
|
||||
(and (dom-id old-child) (not match-id))
|
||||
(dom-insert-before
|
||||
old-parent
|
||||
(dom-clone new-child)
|
||||
old-child)
|
||||
(do (morph-node old-child new-child) (set! oi (inc oi)))))
|
||||
:else (dom-append old-parent (dom-clone new-child)))))
|
||||
(let
|
||||
((old-id (dom-id old-child)))
|
||||
(if
|
||||
(and old-id (not (empty? old-id)) (not match-id))
|
||||
(dom-insert-before
|
||||
old-parent
|
||||
(dom-clone new-child true)
|
||||
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)
|
||||
(for-each
|
||||
(fn
|
||||
((i :as number))
|
||||
(i)
|
||||
(when
|
||||
(>= i oi)
|
||||
(not (dict-get consumed (str i)))
|
||||
(let
|
||||
((leftover (nth old-kids i)))
|
||||
(when
|
||||
@@ -465,7 +487,7 @@
|
||||
(not (dom-has-attr? leftover "sx-preserve"))
|
||||
(not (dom-has-attr? leftover "sx-ignore")))
|
||||
(dom-remove-child old-parent leftover)))))
|
||||
(range oi (len old-kids))))))
|
||||
(range 0 (len old-kids))))))
|
||||
|
||||
(define
|
||||
morph-island-children
|
||||
@@ -588,7 +610,7 @@
|
||||
(morph-children target wrapper)))
|
||||
"outerHTML"
|
||||
(let
|
||||
((parent (dom-parent target)) (new-el (dom-clone new-nodes)))
|
||||
((parent (dom-parent target)) (new-el (dom-clone new-nodes true)))
|
||||
(if
|
||||
(dom-is-fragment? new-nodes)
|
||||
(let
|
||||
@@ -596,7 +618,7 @@
|
||||
(if
|
||||
fc
|
||||
(do
|
||||
(set! new-el (dom-clone fc))
|
||||
(set! new-el (dom-clone fc true))
|
||||
(dom-replace-child parent new-el target)
|
||||
(let
|
||||
((sib (dom-next-sibling fc)))
|
||||
|
||||
Reference in New Issue
Block a user