Precise DOM morphing
morphing typescript dom

Fix new algorithm

+19 -57
+19 -57
src/morphlex.ts
··· 286 286 matches.set(node, candidate) 287 287 unmatched.delete(node) 288 288 candidates.delete(candidate) 289 + break 289 290 } 290 291 } 291 292 } ··· 301 302 matches.set(node, candidate) 302 303 unmatched.delete(node) 303 304 candidates.delete(candidate) 305 + break 304 306 } 305 307 } 306 308 } ··· 319 321 matches.set(node, candidate) 320 322 unmatched.delete(node) 321 323 candidates.delete(candidate) 324 + break 322 325 } 323 326 } 324 327 } ··· 336 339 matches.set(node, candidate) 337 340 unmatched.delete(node) 338 341 candidates.delete(candidate) 342 + break 339 343 } 340 344 } 341 345 } else { 342 346 for (const candidate of candidates) { 343 - if (nodeType === candidate.nodeType && node.nodeValue === candidate.nodeValue) { 347 + if (nodeType === candidate.nodeType) { 344 348 matches.set(node, candidate) 345 349 unmatched.delete(node) 346 350 candidates.delete(candidate) 351 + break 347 352 } 348 353 } 349 354 } 350 355 } 351 356 352 - let prevNode: ChildNode | null = null 353 - // remove remaining nodes in reverse order 354 - for (let i = toChildNodes.length - 1; i >= 0; i--) { 357 + // Process nodes in forward order to maintain proper positioning 358 + let insertionPoint: ChildNode | null = parent.firstChild 359 + for (let i = 0; i < toChildNodes.length; i++) { 355 360 const node = toChildNodes[i]! 356 361 const match = matches.get(node) 357 362 if (match) { 358 - moveBefore(parent, match, prevNode) 363 + moveBefore(parent, match, insertionPoint) 359 364 this.morphOneToOne(match, node) 360 - prevNode = match 365 + insertionPoint = match.nextSibling 361 366 } else { 362 - moveBefore(parent, node, prevNode) 363 - prevNode = node 367 + if (this.options.beforeNodeAdded?.(node) ?? true) { 368 + moveBefore(parent, node, insertionPoint) 369 + this.options.afterNodeAdded?.(node) 370 + insertionPoint = node.nextSibling 371 + } 364 372 } 365 373 } 366 374 367 - const numberOfNodesToRemove = from.childNodes.length - toChildNodes.length 368 - for (let i = fromChildNodes.length - 1; i >= numberOfNodesToRemove; i--) { 369 - this.removeNode(from.childNodes[i]!) 375 + // Remove any remaining unmatched candidates 376 + for (const candidate of candidates) { 377 + this.removeNode(candidate) 370 378 } 371 379 } 372 - 373 - // private searchSiblingsToMorphChildElement(from: ChildNode, to: Element, parent: ParentNode): void { 374 - // const id = to.id 375 - // const idSet = this.idMap.get(to) 376 - // const idSetArray = idSet ? [...idSet] : [] 377 - 378 - // let currentNode: ChildNode | null = from 379 - // let bestMatch: Element | null = null 380 - // let idSetMatches: number = 0 381 - 382 - // while (currentNode) { 383 - // if (isElement(currentNode) && currentNode.localName === to.localName) { 384 - // // If we found an exact match, this is the best option. 385 - // if (id && id !== "" && id === currentNode.id) { 386 - // bestMatch = currentNode 387 - // break 388 - // } 389 - 390 - // // Try to find the node with the best idSet match 391 - // const currentIdSet = this.idMap.get(currentNode) 392 - // if (currentIdSet) { 393 - // const numberOfMatches = idSetArray.filter((id) => currentIdSet.has(id)).length 394 - // if (numberOfMatches > idSetMatches) { 395 - // bestMatch = currentNode 396 - // idSetMatches = numberOfMatches 397 - // } 398 - // } 399 - 400 - // // The fallback is to just use the next element with the same localName 401 - // if (!bestMatch) { 402 - // bestMatch = currentNode 403 - // } 404 - // } 405 - 406 - // currentNode = currentNode.nextSibling 407 - // } 408 - 409 - // if (bestMatch) { 410 - // if (bestMatch !== from) { 411 - // moveBefore(parent, bestMatch, from) 412 - // } 413 - // this.morphOneToOne(bestMatch, to) 414 - // } else { 415 - // this.morphOneToOne(from, to) 416 - // } 417 - // } 418 380 419 381 private updateProperty<N extends Node, P extends keyof N>(node: N, propertyName: P, newValue: N[P]): void { 420 382 const oldValue = node[propertyName]