@@ -9,7 +9,8 @@ const unmatchedNodes: Set<number> = new Set()
99const unmatchedElements : Set < number > = new Set ( )
1010const whitespaceNodes : Set < number > = new Set ( )
1111
12- type IdMap = WeakMap < Node , Array < string > >
12+ type IdSetMap = WeakMap < Node , Set < string > >
13+ type IdArrayMap = WeakMap < Node , Array < string > >
1314
1415/**
1516 * Configuration options for morphing operations.
@@ -129,7 +130,7 @@ export function morphDocument(from: Document, to: Document | string, options?: O
129130export function morph ( from : ChildNode , to : ChildNode | NodeListOf < ChildNode > | string , options : Options = { } ) : void {
130131 if ( typeof to === "string" ) to = parseFragment ( to ) . childNodes
131132
132- if ( isParentNode ( from ) ) flagDirtyInputs ( from )
133+ if ( ! options . preserveChanges && isParentNode ( from ) ) flagDirtyInputs ( from )
133134
134135 new Morph ( options ) . morph ( from , to )
135136}
@@ -214,7 +215,8 @@ function moveBefore(parent: ParentNode, node: ChildNode, insertionPoint: ChildNo
214215}
215216
216217class Morph {
217- readonly #idMap: IdMap = new WeakMap ( )
218+ readonly #idArrayMap: IdArrayMap = new WeakMap ( )
219+ readonly #idSetMap: IdSetMap = new WeakMap ( )
218220 readonly #options: Options
219221
220222 constructor ( options : Options = { } ) {
@@ -284,10 +286,10 @@ class Morph {
284286 }
285287
286288 if ( to instanceof NodeList ) {
287- this . #mapIdSetsForEach ( to )
289+ this . #mapIdArraysForEach ( to )
288290 this . #morphOneToMany( from , to )
289291 } else if ( isParentNode ( to ) ) {
290- this . #mapIdSets ( to )
292+ this . #mapIdArrays ( to )
291293 this . #morphOneToOne( from , to )
292294 }
293295 }
@@ -365,9 +367,7 @@ class Morph {
365367 }
366368
367369 // First pass: update/add attributes from reference (iterate forwards)
368- const toAttributes = to . attributes
369- for ( let i = 0 ; i < toAttributes . length ; i ++ ) {
370- const { name, value } = toAttributes [ i ] !
370+ for ( const { name, value } of to . attributes ) {
371371 if ( name === "value" ) {
372372 if ( isInputElement ( from ) && from . value !== value ) {
373373 if ( ! this . #options. preserveChanges || from . value === from . defaultValue ) {
@@ -400,12 +400,8 @@ class Morph {
400400 }
401401 }
402402
403- const fromAttrs = from . attributes
404-
405- // Second pass: remove excess attributes (iterate backwards for efficiency)
406- for ( let i = fromAttrs . length - 1 ; i >= 0 ; i -- ) {
407- const { name, value } = fromAttrs [ i ] !
408-
403+ // Second pass: remove excess attributes
404+ for ( const { name, value } of from . attributes ) {
409405 if ( ! to . hasAttribute ( name ) ) {
410406 if ( name === "selected" ) {
411407 if ( isOptionElement ( from ) && from . selected ) {
@@ -509,9 +505,9 @@ class Morph {
509505 const element = toChildNodes [ unmatchedIndex ] as Element
510506
511507 const id = element . id
512- const idSet = this . #idMap . get ( element )
508+ const idArray = this . #idArrayMap . get ( element )
513509
514- if ( id === "" && ! idSet ) continue
510+ if ( id === "" && ! idArray ) continue
515511
516512 candidateLoop: for ( const candidateIndex of candidateElements ) {
517513 const candidate = fromChildNodes [ candidateIndex ] as Element
@@ -525,20 +521,18 @@ class Morph {
525521 break candidateLoop
526522 }
527523
528- // Match by idSet
529- if ( idSet ) {
530- const candidateIdSet = this . #idMap . get ( candidate )
524+ // Match by idArray (to) against idSet (from)
525+ if ( idArray ) {
526+ const candidateIdSet = this . #idSetMap . get ( candidate )
531527 if ( candidateIdSet ) {
532- for ( let i = 0 ; i < idSet . length ; i ++ ) {
533- const setId = idSet [ i ] !
534- for ( let k = 0 ; k < candidateIdSet . length ; k ++ ) {
535- if ( candidateIdSet [ k ] === setId ) {
536- matches [ unmatchedIndex ] = candidateIndex
537- seq [ candidateIndex ] = unmatchedIndex
538- candidateElements . delete ( candidateIndex )
539- unmatchedElements . delete ( unmatchedIndex )
540- break candidateLoop
541- }
528+ for ( let i = 0 ; i < idArray . length ; i ++ ) {
529+ const arrayId = idArray [ i ] !
530+ if ( candidateIdSet . has ( arrayId ) ) {
531+ matches [ unmatchedIndex ] = candidateIndex
532+ seq [ candidateIndex ] = unmatchedIndex
533+ candidateElements . delete ( candidateIndex )
534+ unmatchedElements . delete ( unmatchedIndex )
535+ break candidateLoop
542536 }
543537 }
544538 }
@@ -687,17 +681,41 @@ class Morph {
687681 }
688682 }
689683
690- #mapIdSetsForEach ( nodeList : NodeList ) : void {
684+ #mapIdArraysForEach ( nodeList : NodeList ) : void {
691685 for ( const childNode of nodeList ) {
692686 if ( isParentNode ( childNode ) ) {
693- this . #mapIdSets ( childNode )
687+ this . #mapIdArrays ( childNode )
694688 }
695689 }
696690 }
697691
698- // For each node with an ID, push that ID into the IdSet on the IdMap, for each of its parent elements.
692+ // For each node with an ID, push that ID into the IdArray on the IdArrayMap, for each of its parent elements.
693+ #mapIdArrays( node : ParentNode ) : void {
694+ const idArrayMap = this . #idArrayMap
695+
696+ for ( const element of node . querySelectorAll ( "[id]" ) ) {
697+ const id = element . id
698+
699+ if ( id === "" ) continue
700+
701+ let currentElement : Element | null = element
702+
703+ while ( currentElement ) {
704+ const idArray = idArrayMap . get ( currentElement )
705+ if ( idArray ) {
706+ idArray . push ( id )
707+ } else {
708+ idArrayMap . set ( currentElement , [ id ] )
709+ }
710+ if ( currentElement === node ) break
711+ currentElement = currentElement . parentElement
712+ }
713+ }
714+ }
715+
716+ // For each node with an ID, add that ID into the IdSet on the IdSetMap, for each of its parent elements.
699717 #mapIdSets( node : ParentNode ) : void {
700- const idMap = this . #idMap
718+ const idSetMap = this . #idSetMap
701719
702720 for ( const element of node . querySelectorAll ( "[id]" ) ) {
703721 const id = element . id
@@ -707,9 +725,12 @@ class Morph {
707725 let currentElement : Element | null = element
708726
709727 while ( currentElement ) {
710- const idSet : Array < string > | undefined = idMap . get ( currentElement )
711- if ( idSet ) idSet . push ( id )
712- else idMap . set ( currentElement , [ id ] )
728+ const idSet = idSetMap . get ( currentElement )
729+ if ( idSet ) {
730+ idSet . add ( id )
731+ } else {
732+ idSetMap . set ( currentElement , new Set ( [ id ] ) )
733+ }
713734 if ( currentElement === node ) break
714735 currentElement = currentElement . parentElement
715736 }
0 commit comments