import type {} from '../../types/globals'; /* Snapshot/restore for JS-modified DOM state across HMR updates. * Before patching, captures text and dynamic children of elements with IDs. * After patching, restores values that were changed by user scripts. */ type DOMSnapshot = { children: Map; text: Map; }; export const restoreDOMChanges = ( root: HTMLElement, snapshot: DOMSnapshot, newHTML: string ) => { const tempDiv = document.createElement('div'); tempDiv.innerHTML = newHTML; /* Restore JS-modified text on leaf elements */ snapshot.text.forEach((liveText, elId) => { const newEl = tempDiv.querySelector(`#${CSS.escape(elId)}`); const newText = newEl ? newEl.textContent || '' : ''; if (liveText === newText) return; const liveEl = root.querySelector(`#${CSS.escape(elId)}`); if (liveEl) { liveEl.textContent = liveText; } }); /* Restore JS-added children (e.g. dynamically appended list items) */ snapshot.children.forEach((liveHTML, elId) => { const newEl = tempDiv.querySelector(`#${CSS.escape(elId)}`); const newInner = newEl ? newEl.innerHTML : ''; if (liveHTML === newInner || liveHTML.length <= newInner.length) return; const liveEl = root.querySelector(`#${CSS.escape(elId)}`); if (liveEl) { liveEl.innerHTML = liveHTML; } }); }; export const snapshotDOMChanges = (root: HTMLElement): DOMSnapshot => { const text = new Map(); const children = new Map(); root.querySelectorAll('[id]').forEach((elem) => { const { childNodes } = elem; const isTextLeaf = Array.from(childNodes).every( (child) => child.nodeType === Node.TEXT_NODE ); if (isTextLeaf && childNodes.length > 0) { text.set(elem.id, elem.textContent || ''); } else if (elem.children.length > 0) { children.set(elem.id, elem.innerHTML); } }); return { children, text }; };