tangled
alpha
login
or
join now
yippee.fun
/
morphlex
0
fork
atom
Precise DOM morphing
morphing
typescript
dom
0
fork
atom
overview
issues
pulls
pipelines
Performance improvements
joel.drapper.me
4 months ago
1c1ce26b
5226c27d
+28
-19
1 changed file
expand all
collapse all
unified
split
src
morphlex.ts
+28
-19
src/morphlex.ts
···
1
1
+
const SupportsMoveBefore = "moveBefore" in Element.prototype
1
2
const ParentNodeTypes = new Set([1, 9, 11])
2
3
const DisablableElements = new Set(["input", "button", "select", "textarea", "option", "optgroup", "fieldset"])
3
4
const ValuableElements = new Set(["input", "select", "textarea"])
···
45
46
afterChildrenVisited?: (parent: ParentNode) => void
46
47
}
47
48
49
49
+
type NodeWithMoveBefore = ParentNode & {
50
50
+
moveBefore: (node: ChildNode, before: ChildNode | null) => void
51
51
+
}
52
52
+
48
53
export function morph(from: ChildNode, to: ChildNode | NodeListOf<ChildNode> | string, options: Options = {}): void {
49
54
if (typeof to === "string") to = parseString(to).childNodes
50
55
new Morph(options).morph(from, to)
···
78
83
79
84
function moveBefore(parent: ParentNode, node: ChildNode, insertionPoint: ChildNode | null): void {
80
85
if (node === insertionPoint) return
81
81
-
if (node.parentNode === parent && node.nextSibling === insertionPoint) return
82
82
-
83
83
-
// Use moveBefore when available and the node is already in the same parent
84
84
-
if ("moveBefore" in parent && typeof parent.moveBefore === "function" && node.parentNode === parent) {
85
85
-
parent.moveBefore(node, insertionPoint)
86
86
-
} else {
87
87
-
parent.insertBefore(node, insertionPoint)
86
86
+
if (node.parentNode === parent) {
87
87
+
if (node.nextSibling === insertionPoint) return
88
88
+
if (supportsMoveBefore(parent)) {
89
89
+
parent.moveBefore(node, insertionPoint)
90
90
+
return
91
91
+
}
88
92
}
93
93
+
94
94
+
parent.insertBefore(node, insertionPoint)
89
95
}
90
96
91
97
class Morph {
···
134
140
135
141
private morphOneToOne(from: ChildNode, to: ChildNode): void {
136
142
// Fast path: if nodes are exactly the same object, skip morphing
137
137
-
if (from.isSameNode?.(to)) return
143
143
+
if (from === to) return
138
144
if (from.isEqualNode?.(to)) return
139
145
140
146
if (!(this.options.beforeNodeVisited?.(from, to) ?? true)) return
···
298
304
if (!isElement(node)) continue
299
305
const idSet = this.idMap.get(node)
300
306
if (!idSet) continue
301
301
-
const idSetArray = [...idSet]
302
307
303
303
-
for (const candidate of candidates) {
308
308
+
candidateLoop: for (const candidate of candidates) {
304
309
if (isElement(candidate)) {
305
310
const candidateIdSet = this.idMap.get(candidate)
306
306
-
if (candidateIdSet && idSetArray.some((id) => candidateIdSet.has(id))) {
307
307
-
matches.set(node, candidate)
308
308
-
unmatched.delete(node)
309
309
-
candidates.delete(candidate)
310
310
-
break
311
311
+
if (candidateIdSet) {
312
312
+
for (const id of idSet) {
313
313
+
if (candidateIdSet.has(id)) {
314
314
+
matches.set(node, candidate)
315
315
+
unmatched.delete(node)
316
316
+
candidates.delete(candidate)
317
317
+
break candidateLoop
318
318
+
}
319
319
+
}
311
320
}
312
321
}
313
322
}
···
318
327
if (!isElement(node)) continue
319
328
const className = node.className
320
329
const name = node.getAttribute("name")
321
321
-
const ariaLabel = node.getAttribute("aria-label")
322
322
-
const ariaDescription = node.getAttribute("aria-description")
323
330
const href = node.getAttribute("href")
324
331
const src = node.getAttribute("src")
325
332
···
329
336
node.localName === candidate.localName &&
330
337
((className !== "" && className === candidate.className) ||
331
338
(name !== "" && name === candidate.getAttribute("name")) ||
332
332
-
(ariaLabel !== "" && ariaLabel === candidate.getAttribute("aria-label")) ||
333
333
-
(ariaDescription !== "" && ariaDescription === candidate.getAttribute("aria-description")) ||
334
339
(href !== "" && href === candidate.getAttribute("href")) ||
335
340
(src !== "" && src === candidate.getAttribute("src")))
336
341
) {
···
450
455
}
451
456
}
452
457
}
458
458
+
}
459
459
+
460
460
+
function supportsMoveBefore(_node: ParentNode): _node is NodeWithMoveBefore {
461
461
+
return SupportsMoveBefore
453
462
}
454
463
455
464
function isDisablableElement(element: Element): element is DisablableElement {