Precise DOM morphing
morphing typescript dom

Use an array for matches

+41 -31
+41 -31
src/morphlex.ts
··· 286 const candidateNodes: Set<ChildNode> = new Set() 287 const candidateElements: Set<Element> = new Set() 288 289 - const unmatchedNodes: Set<ChildNode> = new Set() 290 - const unmatchedElements: Set<Element> = new Set() 291 - 292 - const matches: Map<ChildNode, ChildNode> = new Map() 293 294 for (const candidate of fromChildNodes) { 295 if (isElement(candidate)) candidateElements.add(candidate) 296 else candidateNodes.add(candidate) 297 } 298 299 - for (const node of toChildNodes) { 300 - if (isElement(node)) unmatchedElements.add(node) 301 - else unmatchedNodes.add(node) 302 - } 303 304 - // Match elements by isEqualNode 305 - for (const element of unmatchedElements) { 306 for (const candidate of candidateElements) { 307 if (candidate.isEqualNode(element)) { 308 - matches.set(element, candidate) 309 - unmatchedElements.delete(element) 310 candidateElements.delete(candidate) 311 break 312 } ··· 314 } 315 316 // Match by exact id 317 - for (const element of unmatchedElements) { 318 const id = element.id 319 if (id === "") continue 320 321 for (const candidate of candidateElements) { 322 if (element.localName === candidate.localName && id === candidate.id) { 323 - matches.set(element, candidate) 324 - unmatchedElements.delete(element) 325 candidateElements.delete(candidate) 326 break 327 } ··· 329 } 330 331 // Match by idSet 332 - for (const element of unmatchedElements) { 333 if (!isElement(element)) continue 334 const idSet = this.idMap.get(element) 335 if (!idSet) continue 336 ··· 340 if (candidateIdSet) { 341 for (const id of idSet) { 342 if (candidateIdSet.has(id)) { 343 - matches.set(element, candidate) 344 - unmatchedElements.delete(element) 345 candidateElements.delete(candidate) 346 break candidateLoop 347 } ··· 352 } 353 354 // Match by huristics 355 - for (const element of unmatchedElements) { 356 if (!isElement(element)) continue 357 const name = element.getAttribute("name") 358 const href = element.getAttribute("href") 359 const src = element.getAttribute("src") ··· 366 (href !== "" && href === candidate.getAttribute("href")) || 367 (src !== "" && src === candidate.getAttribute("src"))) 368 ) { 369 - matches.set(element, candidate) 370 - unmatchedElements.delete(element) 371 candidateElements.delete(candidate) 372 break 373 } ··· 375 } 376 377 // Match by tagName 378 - for (const element of unmatchedElements) { 379 const localName = element.localName 380 381 for (const candidate of candidateElements) { ··· 384 // Treat inputs with different type as though they are different tags. 385 continue 386 } 387 - matches.set(element, candidate) 388 - unmatchedElements.delete(element) 389 candidateElements.delete(candidate) 390 break 391 } ··· 393 } 394 395 // Match nodes by isEqualNode 396 - for (const node of unmatchedNodes) { 397 for (const candidate of candidateNodes) { 398 if (candidate.isEqualNode(node)) { 399 - matches.set(node, candidate) 400 - unmatchedNodes.delete(node) 401 candidateNodes.delete(candidate) 402 break 403 } ··· 405 } 406 407 // Match by nodeType 408 - for (const node of unmatchedNodes) { 409 const nodeType = node.nodeType 410 411 for (const candidate of candidateNodes) { 412 if (nodeType === candidate.nodeType) { 413 - matches.set(node, candidate) 414 - unmatchedNodes.delete(node) 415 candidateNodes.delete(candidate) 416 break 417 } ··· 422 let insertionPoint: ChildNode | null = parent.firstChild 423 for (let i = 0; i < toChildNodes.length; i++) { 424 const node = toChildNodes[i]! 425 - const match = matches.get(node) 426 if (match) { 427 moveBefore(parent, match, insertionPoint) 428 this.morphOneToOne(match, node)
··· 286 const candidateNodes: Set<ChildNode> = new Set() 287 const candidateElements: Set<Element> = new Set() 288 289 + const matches: Array<ChildNode | null> = new Array(toChildNodes.length).fill(null) 290 291 for (const candidate of fromChildNodes) { 292 if (isElement(candidate)) candidateElements.add(candidate) 293 else candidateNodes.add(candidate) 294 } 295 296 + // Match elements by isEqualNode 297 + for (let i = 0; i < toChildNodes.length; i++) { 298 + const element = toChildNodes[i]! 299 + if (!isElement(element)) continue 300 301 for (const candidate of candidateElements) { 302 if (candidate.isEqualNode(element)) { 303 + matches[i] = candidate 304 candidateElements.delete(candidate) 305 break 306 } ··· 308 } 309 310 // Match by exact id 311 + for (let i = 0; i < toChildNodes.length; i++) { 312 + if (matches[i]) continue 313 + const element = toChildNodes[i]! 314 + if (!isElement(element)) continue 315 + 316 const id = element.id 317 if (id === "") continue 318 319 for (const candidate of candidateElements) { 320 if (element.localName === candidate.localName && id === candidate.id) { 321 + matches[i] = candidate 322 candidateElements.delete(candidate) 323 break 324 } ··· 326 } 327 328 // Match by idSet 329 + for (let i = 0; i < toChildNodes.length; i++) { 330 + if (matches[i]) continue 331 + const element = toChildNodes[i]! 332 if (!isElement(element)) continue 333 + 334 const idSet = this.idMap.get(element) 335 if (!idSet) continue 336 ··· 340 if (candidateIdSet) { 341 for (const id of idSet) { 342 if (candidateIdSet.has(id)) { 343 + matches[i] = candidate 344 candidateElements.delete(candidate) 345 break candidateLoop 346 } ··· 351 } 352 353 // Match by huristics 354 + for (let i = 0; i < toChildNodes.length; i++) { 355 + if (matches[i]) continue 356 + const element = toChildNodes[i]! 357 if (!isElement(element)) continue 358 + 359 const name = element.getAttribute("name") 360 const href = element.getAttribute("href") 361 const src = element.getAttribute("src") ··· 368 (href !== "" && href === candidate.getAttribute("href")) || 369 (src !== "" && src === candidate.getAttribute("src"))) 370 ) { 371 + matches[i] = candidate 372 candidateElements.delete(candidate) 373 break 374 } ··· 376 } 377 378 // Match by tagName 379 + for (let i = 0; i < toChildNodes.length; i++) { 380 + if (matches[i]) continue 381 + const element = toChildNodes[i]! 382 + if (!isElement(element)) continue 383 + 384 const localName = element.localName 385 386 for (const candidate of candidateElements) { ··· 389 // Treat inputs with different type as though they are different tags. 390 continue 391 } 392 + matches[i] = candidate 393 candidateElements.delete(candidate) 394 break 395 } ··· 397 } 398 399 // Match nodes by isEqualNode 400 + for (let i = 0; i < toChildNodes.length; i++) { 401 + if (matches[i]) continue 402 + const node = toChildNodes[i]! 403 + if (isElement(node)) continue 404 + 405 for (const candidate of candidateNodes) { 406 if (candidate.isEqualNode(node)) { 407 + matches[i] = candidate 408 candidateNodes.delete(candidate) 409 break 410 } ··· 412 } 413 414 // Match by nodeType 415 + for (let i = 0; i < toChildNodes.length; i++) { 416 + if (matches[i]) continue 417 + const node = toChildNodes[i]! 418 + if (isElement(node)) continue 419 + 420 const nodeType = node.nodeType 421 422 for (const candidate of candidateNodes) { 423 if (nodeType === candidate.nodeType) { 424 + matches[i] = candidate 425 candidateNodes.delete(candidate) 426 break 427 } ··· 432 let insertionPoint: ChildNode | null = parent.firstChild 433 for (let i = 0; i < toChildNodes.length; i++) { 434 const node = toChildNodes[i]! 435 + const match = matches[i] 436 if (match) { 437 moveBefore(parent, match, insertionPoint) 438 this.morphOneToOne(match, node)