export function dom(html: string): HTMLElement { const tmp = document.createElement("div") tmp.innerHTML = html.trim() return tmp.firstChild as HTMLElement } export class Mutations { records: Array = [] push(...records: MutationRecord[]) { this.records.push(...records) } get count(): number { return this.records.length } get childListChanges(): number { return this.records.filter((m) => m.type === "childList").length } get elementsAdded(): number { return this.records.filter( (m) => m.type === "childList" && Array.from(m.addedNodes).some((n) => n.nodeType === Node.ELEMENT_NODE), ).length } get elementsRemoved(): number { return this.records.filter( (m) => m.type === "childList" && Array.from(m.removedNodes).some((n) => n.nodeType === Node.ELEMENT_NODE), ).length } get textNodesAdded(): number { return this.records.filter( (m) => m.type === "childList" && Array.from(m.addedNodes).some((n) => n.nodeType === Node.TEXT_NODE), ).length } get textNodesRemoved(): number { return this.records.filter( (m) => m.type === "childList" && Array.from(m.removedNodes).some((n) => n.nodeType === Node.TEXT_NODE), ).length } get nodesAdded(): number { return this.records.filter((m) => m.type === "childList" && m.addedNodes.length > 0).length } get nodesRemoved(): number { return this.records.filter((m) => m.type === "childList" && m.removedNodes.length > 0).length } get attributeChanges(): number { return this.records.filter((m) => m.type === "attributes").length } get characterDataChanges(): number { return this.records.filter((m) => m.type === "characterData").length } } export function observeMutations(target: Node, callback: () => void): Mutations { const mutations = new Mutations() const observer = new MutationObserver((records) => { mutations.push(...records) }) observer.observe(target, { childList: true, attributes: true, characterData: true, subtree: true, }) callback() // Flush any pending mutations const records = observer.takeRecords() mutations.push(...records) observer.disconnect() return mutations }