Precise DOM morphing
morphing
typescript
dom
1export function dom(html: string): HTMLElement {
2 const tmp = document.createElement("div")
3 tmp.innerHTML = html.trim()
4 return tmp.firstChild as HTMLElement
5}
6
7export class Mutations {
8 records: Array<MutationRecord> = []
9
10 push(...records: MutationRecord[]) {
11 this.records.push(...records)
12 }
13
14 get count(): number {
15 return this.records.length
16 }
17
18 get childListChanges(): number {
19 return this.records.filter((m) => m.type === "childList").length
20 }
21
22 get elementsAdded(): number {
23 return this.records.filter(
24 (m) => m.type === "childList" && Array.from(m.addedNodes).some((n) => n.nodeType === Node.ELEMENT_NODE),
25 ).length
26 }
27
28 get elementsRemoved(): number {
29 return this.records.filter(
30 (m) => m.type === "childList" && Array.from(m.removedNodes).some((n) => n.nodeType === Node.ELEMENT_NODE),
31 ).length
32 }
33
34 get textNodesAdded(): number {
35 return this.records.filter(
36 (m) => m.type === "childList" && Array.from(m.addedNodes).some((n) => n.nodeType === Node.TEXT_NODE),
37 ).length
38 }
39
40 get textNodesRemoved(): number {
41 return this.records.filter(
42 (m) => m.type === "childList" && Array.from(m.removedNodes).some((n) => n.nodeType === Node.TEXT_NODE),
43 ).length
44 }
45
46 get nodesAdded(): number {
47 return this.records.filter((m) => m.type === "childList" && m.addedNodes.length > 0).length
48 }
49
50 get nodesRemoved(): number {
51 return this.records.filter((m) => m.type === "childList" && m.removedNodes.length > 0).length
52 }
53
54 get attributeChanges(): number {
55 return this.records.filter((m) => m.type === "attributes").length
56 }
57
58 get characterDataChanges(): number {
59 return this.records.filter((m) => m.type === "characterData").length
60 }
61}
62
63export function observeMutations(target: Node, callback: () => void): Mutations {
64 const mutations = new Mutations()
65 const observer = new MutationObserver((records) => {
66 mutations.push(...records)
67 })
68
69 observer.observe(target, {
70 childList: true,
71 attributes: true,
72 characterData: true,
73 subtree: true,
74 })
75
76 callback()
77
78 // Flush any pending mutations
79 const records = observer.takeRecords()
80 mutations.push(...records)
81
82 observer.disconnect()
83 return mutations
84}