···454454 if (!(this.#options.beforeChildrenVisited?.(from) ?? true)) return
455455 const parent = from
456456457457- const fromChildNodes = from.childNodes
457457+ const fromChildNodes = Array.from(from.childNodes)
458458 const toChildNodes = Array.from(to.childNodes)
459459460460- const candidateNodes: Set<ChildNode> = new Set()
461461- const candidateElements: Set<Element> = new Set()
460460+ const candidateNodes: Set<number> = new Set()
461461+ const candidateElements: Set<number> = new Set()
462462463463 const matches: Array<ChildNode | null> = Array.from({ length: toChildNodes.length }, () => null)
464464465465- for (const candidate of fromChildNodes) {
466466- if (isElement(candidate)) candidateElements.add(candidate)
467467- else candidateNodes.add(candidate)
465465+ for (let i = 0; i < fromChildNodes.length; i++) {
466466+ const candidate = fromChildNodes[i]!
467467+ if (isElement(candidate)) candidateElements.add(i)
468468+ else candidateNodes.add(i)
468469 }
469470470470- // Match elements by isEqualNode
471471+ const unmatchedElements: Set<number> = new Set()
472472+ const unmatchedNodes: Set<number> = new Set()
473473+471474 for (let i = 0; i < toChildNodes.length; i++) {
472472- const element = toChildNodes[i]!
473473- if (!isElement(element)) continue
475475+ const node = toChildNodes[i]!
476476+ if (isElement(node)) unmatchedElements.add(i)
477477+ else unmatchedNodes.add(i)
478478+ }
479479+480480+ // Match elements by isEqualNode
481481+ for (const i of unmatchedElements) {
482482+ const element = toChildNodes[i] as Element
474483475475- for (const candidate of candidateElements) {
484484+ for (const candidateIndex of candidateElements) {
485485+ const candidate = fromChildNodes[candidateIndex]!
476486 if (candidate.isEqualNode(element)) {
477487 matches[i] = candidate
478478- candidateElements.delete(candidate)
488488+ candidateElements.delete(candidateIndex)
489489+ unmatchedElements.delete(i)
479490 break
480491 }
481492 }
482493 }
483494484495 // Match by exact id
485485- for (let i = 0; i < toChildNodes.length; i++) {
486486- if (matches[i]) continue
487487- const element = toChildNodes[i]!
488488- if (!isElement(element)) continue
496496+ for (const i of unmatchedElements) {
497497+ const element = toChildNodes[i] as Element
489498490499 const id = element.id
491500 if (id === "") continue
492501493493- for (const candidate of candidateElements) {
502502+ for (const candidateIndex of candidateElements) {
503503+ const candidate = fromChildNodes[candidateIndex] as Element
494504 if (element.localName === candidate.localName && id === candidate.id) {
495505 matches[i] = candidate
496496- candidateElements.delete(candidate)
506506+ candidateElements.delete(candidateIndex)
507507+ unmatchedElements.delete(i)
497508 break
498509 }
499510 }
500511 }
501512502513 // Match by idSet
503503- for (let i = 0; i < toChildNodes.length; i++) {
504504- if (matches[i]) continue
505505- const element = toChildNodes[i]!
506506- if (!isElement(element)) continue
514514+ for (const i of unmatchedElements) {
515515+ const element = toChildNodes[i] as Element
507516508517 const idSet = this.#idMap.get(element)
509518 if (!idSet) continue
510519511511- candidateLoop: for (const candidate of candidateElements) {
512512- if (isElement(candidate)) {
513513- const candidateIdSet = this.#idMap.get(candidate)
514514- if (candidateIdSet) {
515515- for (const id of idSet) {
516516- if (candidateIdSet.has(id)) {
517517- matches[i] = candidate
518518- candidateElements.delete(candidate)
519519- break candidateLoop
520520- }
520520+ candidateLoop: for (const candidateIndex of candidateElements) {
521521+ const candidate = fromChildNodes[candidateIndex] as Element
522522+ const candidateIdSet = this.#idMap.get(candidate)
523523+ if (candidateIdSet) {
524524+ for (const id of idSet) {
525525+ if (candidateIdSet.has(id)) {
526526+ matches[i] = candidate
527527+ candidateElements.delete(candidateIndex)
528528+ unmatchedElements.delete(i)
529529+ break candidateLoop
521530 }
522531 }
523532 }
···525534 }
526535527536 // Match by heuristics
528528- for (let i = 0; i < toChildNodes.length; i++) {
529529- if (matches[i]) continue
530530- const element = toChildNodes[i]!
531531- if (!isElement(element)) continue
537537+ for (const i of unmatchedElements) {
538538+ const element = toChildNodes[i] as Element
532539533540 const name = element.getAttribute("name")
534541 const href = element.getAttribute("href")
535542 const src = element.getAttribute("src")
536543537537- for (const candidate of candidateElements) {
544544+ for (const candidateIndex of candidateElements) {
545545+ const candidate = fromChildNodes[candidateIndex] as Element
538546 if (
539539- isElement(candidate) &&
540547 element.localName === candidate.localName &&
541548 ((name && name === candidate.getAttribute("name")) ||
542549 (href && href === candidate.getAttribute("href")) ||
543550 (src && src === candidate.getAttribute("src")))
544551 ) {
545552 matches[i] = candidate
546546- candidateElements.delete(candidate)
553553+ candidateElements.delete(candidateIndex)
554554+ unmatchedElements.delete(i)
547555 break
548556 }
549557 }
550558 }
551559552560 // Match by tagName
553553- for (let i = 0; i < toChildNodes.length; i++) {
554554- if (matches[i]) continue
555555- const element = toChildNodes[i]!
556556- if (!isElement(element)) continue
561561+ for (const i of unmatchedElements) {
562562+ const element = toChildNodes[i] as Element
557563558564 const localName = element.localName
559565560560- for (const candidate of candidateElements) {
566566+ for (const candidateIndex of candidateElements) {
567567+ const candidate = fromChildNodes[candidateIndex] as Element
561568 if (localName === candidate.localName) {
562569 if (isInputElement(candidate) && isInputElement(element) && candidate.type !== element.type) {
563570 // Treat inputs with different type as though they are different tags.
564571 continue
565572 }
566573 matches[i] = candidate
567567- candidateElements.delete(candidate)
574574+ candidateElements.delete(candidateIndex)
575575+ unmatchedElements.delete(i)
568576 break
569577 }
570578 }
571579 }
572580573581 // Match nodes by isEqualNode (skip whitespace-only text nodes)
574574- for (let i = 0; i < toChildNodes.length; i++) {
575575- if (matches[i]) continue
582582+ for (const i of unmatchedNodes) {
576583 const node = toChildNodes[i]!
577577- if (isElement(node)) continue
578584 if (isWhitespace(node)) continue
579585580580- for (const candidate of candidateNodes) {
586586+ for (const candidateIndex of candidateNodes) {
587587+ const candidate = fromChildNodes[candidateIndex]!
581588 if (candidate.isEqualNode(node)) {
582589 matches[i] = candidate
583583- candidateNodes.delete(candidate)
590590+ candidateNodes.delete(candidateIndex)
591591+ unmatchedNodes.delete(i)
584592 break
585593 }
586594 }
587595 }
588596589597 // Match by nodeType (skip whitespace-only text nodes)
590590- for (let i = 0; i < toChildNodes.length; i++) {
591591- if (matches[i]) continue
598598+ for (const i of unmatchedNodes) {
592599 const node = toChildNodes[i]!
593593- if (isElement(node)) continue
594600 if (isWhitespace(node)) continue
595601596602 const nodeType = node.nodeType
597603598598- for (const candidate of candidateNodes) {
604604+ for (const candidateIndex of candidateNodes) {
605605+ const candidate = fromChildNodes[candidateIndex]!
599606 if (nodeType === candidate.nodeType) {
600607 matches[i] = candidate
601601- candidateNodes.delete(candidate)
608608+ candidateNodes.delete(candidateIndex)
609609+ unmatchedNodes.delete(i)
602610 break
603611 }
604612 }
605613 }
606614607607- // Remove unmatched nodes from candidate sets (they were matched and should not be removed)
608608- for (const match of matches) {
609609- if (match) {
610610- candidateNodes.delete(match)
611611- if (isElement(match)) {
612612- candidateElements.delete(match)
613613- }
614614- }
615615- }
616616-617615 // Remove any unmatched candidates first, before calculating LIS and repositioning
618618- for (const candidate of candidateNodes) {
619619- this.#removeNode(candidate)
616616+ for (const candidateIndex of candidateNodes) {
617617+ this.#removeNode(fromChildNodes[candidateIndex]!)
620618 }
621619622622- for (const candidate of candidateElements) {
623623- this.#removeNode(candidate)
620620+ for (const candidateIndex of candidateElements) {
621621+ this.#removeNode(fromChildNodes[candidateIndex]!)
624622 }
625623626624 // Build sequence of current indices for LIS calculation (after removals)