specnav-core 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/morpher.ts"],"names":[],"mappings":"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","file":"morpher.mjs","sourcesContent":["import type { MorphConfig } from \"./types\";\n\nconst DEFAULT_CONFIG: MorphConfig = {\n preserveScroll: true,\n preserveFocus: true,\n};\n\ninterface MorphContext {\n config: MorphConfig;\n focusedElement: Element | null;\n scrollPositions: Map<Element, { top: number; left: number }>;\n}\n\nexport class DOMmorpher {\n private config: MorphConfig;\n\n constructor(config: Partial<MorphConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n morph(from: Element, to: Element): void {\n const ctx = this.createContext();\n requestAnimationFrame(() => {\n this.morphElement(from, to, ctx);\n this.restoreContext(ctx);\n this.executeScripts(from);\n });\n }\n\n private executeScripts(element: Element): void {\n const scripts = element.querySelectorAll(\"script\");\n scripts.forEach((oldScript) => {\n try {\n // Only execute inline scripts (not external ones with src)\n if (!oldScript.src && oldScript.textContent) {\n const newScript = document.createElement(\"script\");\n \n // Copy attributes\n Array.from(oldScript.attributes).forEach((attr) => {\n newScript.setAttribute(attr.name, attr.value);\n });\n \n // Copy content\n newScript.textContent = oldScript.textContent;\n \n // Replace old script with new one to trigger execution\n oldScript.parentNode?.replaceChild(newScript, oldScript);\n }\n } catch (error) {\n console.warn(\"Failed to execute script:\", error);\n }\n });\n }\n\n private createContext(): MorphContext {\n const ctx: MorphContext = {\n config: this.config,\n focusedElement: null,\n scrollPositions: new Map(),\n };\n\n if (this.config.preserveFocus) {\n ctx.focusedElement = document.activeElement;\n }\n\n if (this.config.preserveScroll) {\n this.captureScrollPositions(document.body, ctx.scrollPositions);\n }\n\n return ctx;\n }\n\n private restoreContext(ctx: MorphContext): void {\n if (ctx.config.preserveFocus && ctx.focusedElement) {\n const element = ctx.focusedElement as HTMLElement;\n if (element.focus && document.body.contains(element)) {\n element.focus();\n }\n }\n\n if (ctx.config.preserveScroll) {\n ctx.scrollPositions.forEach((pos, element) => {\n element.scrollTop = pos.top;\n element.scrollLeft = pos.left;\n });\n }\n }\n\n private captureScrollPositions(\n element: Element,\n positions: Map<Element, { top: number; left: number }>\n ): void {\n if (element.scrollTop > 0 || element.scrollLeft > 0) {\n positions.set(element, {\n top: element.scrollTop,\n left: element.scrollLeft,\n });\n }\n\n Array.from(element.children).forEach((child) => {\n this.captureScrollPositions(child, positions);\n });\n }\n\n private morphElement(from: Element, to: Element, ctx: MorphContext): void {\n // Sync attributes\n this.syncAttributes(from, to);\n\n // Morph children\n this.morphChildren(from, to, ctx);\n }\n\n private syncAttributes(from: Element, to: Element): void {\n const fromAttrs = from.attributes;\n const toAttrs = to.attributes;\n\n // Remove attributes not in target\n for (let i = fromAttrs.length - 1; i >= 0; i--) {\n const attr = fromAttrs[i]!;\n if (!to.hasAttribute(attr.name)) {\n from.removeAttribute(attr.name);\n }\n }\n\n // Add/update attributes from target\n for (let i = 0; i < toAttrs.length; i++) {\n const attr = toAttrs[i]!;\n if (from.getAttribute(attr.name) !== attr.value) {\n from.setAttribute(attr.name, attr.value);\n }\n }\n }\n\n private morphChildren(from: Element, to: Element, ctx: MorphContext): void {\n const fromChildren = Array.from(from.childNodes);\n const toChildren = Array.from(to.childNodes);\n\n let fromIndex = 0;\n let toIndex = 0;\n\n while (toIndex < toChildren.length) {\n const toNode = toChildren[toIndex]!;\n const fromNode = fromChildren[fromIndex];\n\n if (!fromNode) {\n // Append new node\n from.appendChild(toNode.cloneNode(true));\n toIndex++;\n continue;\n }\n\n if (this.nodesMatch(fromNode, toNode)) {\n // Nodes match - recurse or update text\n if (fromNode.nodeType === Node.TEXT_NODE) {\n if (fromNode.nodeValue !== toNode.nodeValue) {\n fromNode.nodeValue = toNode.nodeValue;\n }\n } else if (fromNode.nodeType === Node.ELEMENT_NODE) {\n this.morphElement(fromNode as Element, toNode as Element, ctx);\n }\n fromIndex++;\n toIndex++;\n } else {\n // Try to find matching node ahead\n const matchIndex = this.findMatchingNode(\n fromChildren,\n fromIndex + 1,\n toNode\n );\n\n if (matchIndex !== -1) {\n // Remove nodes before match\n for (let i = fromIndex; i < matchIndex; i++) {\n from.removeChild(fromChildren[i]!);\n }\n fromIndex = matchIndex;\n } else {\n // Insert new node\n from.insertBefore(toNode.cloneNode(true), fromNode);\n toIndex++;\n }\n }\n }\n\n // Remove remaining old nodes\n while (fromIndex < fromChildren.length) {\n from.removeChild(fromChildren[fromIndex]!);\n fromIndex++;\n }\n }\n\n private nodesMatch(a: Node, b: Node): boolean {\n if (a.nodeType !== b.nodeType) return false;\n\n if (a.nodeType === Node.ELEMENT_NODE) {\n const aEl = a as Element;\n const bEl = b as Element;\n\n if (aEl.tagName !== bEl.tagName) return false;\n\n const aId = aEl.getAttribute(\"id\");\n const bId = bEl.getAttribute(\"id\");\n\n if (aId && bId) return aId === bId;\n\n return true;\n }\n\n return true;\n }\n\n private findMatchingNode(\n nodes: Node[],\n startIndex: number,\n target: Node\n ): number {\n for (let i = startIndex; i < nodes.length; i++) {\n if (this.nodesMatch(nodes[i]!, target)) {\n return i;\n }\n }\n return -1;\n }\n}\n\nexport function createMorpher(config?: Partial<MorphConfig>): DOMmorpher {\n return new DOMmorpher(config);\n}\n\nexport function morph(\n from: Element,\n to: Element,\n config?: Partial<MorphConfig>\n): void {\n const morpher = createMorpher(config);\n morpher.morph(from, to);\n}\n"]}
1
+ {"version":3,"sources":["../src/morpher/fingerprint.ts","../src/morpher/identity.ts","../src/morpher/matcher.ts","../src/morpher.ts"],"names":["fnv1a","_"],"mappings":";AAEA,SAAS,MAAM,GAAA,EAAqB;AAClC,EAAA,IAAI,IAAA,GAAO,UAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,IAAA,IAAQ,GAAA,CAAI,WAAW,CAAC,CAAA;AACxB,IAAA,IAAA,GAAQ,OAAO,QAAA,KAAgB,CAAA;AAAA,EACjC;AACA,EAAA,OAAO,IAAA,CAAK,SAAS,EAAE,CAAA;AACzB;AAEA,SAAS,gBAAgB,IAAA,EAAgC;AACvD,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAY;AACrC,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,UAAU,CAAA,CACrC,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,IAAA,CAAK,aAAA,CAAc,CAAA,CAAE,IAAI,CAAC,CAAA,CAC3C,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,EAAG,CAAA,CAAE,IAAI,CAAA,CAAA,EAAI,CAAA,CAAE,KAAK,CAAA,CAAE,CAAA,CACjC,KAAK,GAAG,CAAA;AACX,EAAA,MAAM,OAAO,IAAA,CAAK,UAAA,CAAW,WAAW,CAAA,GAAI,IAAA,CAAK,eAAe,EAAA,GAAK,EAAA;AACrE,EAAA,MAAM,WAAA,GAAc,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,QAAQ,EAAE,GAAA,CAAI,eAAe,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAE3E,EAAA,OAAO,KAAA,CAAM,GAAG,GAAG,CAAA,CAAA,EAAI,KAAK,CAAA,EAAA,EAAK,IAAI,CAAA,EAAA,EAAK,WAAW,CAAA,CAAA,CAAG,CAAA;AAC1D;AAEA,IAAM,eAAA,GAAkB,gBAAA;AAEjB,SAAS,kBAAkB,IAAA,EAAqB;AACrD,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,gBAAA,CAAiB,IAAA,EAAM,WAAW,YAAY,CAAA;AACtE,EAAA,IAAI,OAAO,MAAA,CAAO,WAAA;AAClB,EAAA,OAAO,IAAA,EAAM;AACX,IAAC,IAAA,CAAa,eAAe,CAAA,GAAI,eAAA,CAAgB,IAAI,CAAA;AACrD,IAAA,IAAA,GAAO,OAAO,QAAA,EAAS;AAAA,EACzB;AACF;AAEO,SAAS,kBAAA,CAAmB,SAAkB,QAAA,EAA4B;AAC/E,EAAA,MAAM,SAAA,GAAa,QAAgB,eAAe,CAAA;AAClD,EAAA,IAAI,CAAC,WAAW,OAAO,KAAA;AACvB,EAAA,OAAO,SAAA,KAAc,gBAAgB,QAAQ,CAAA;AAC/C;;;ACtCA,SAASA,OAAM,GAAA,EAAqB;AAClC,EAAA,IAAI,IAAA,GAAO,UAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,IAAA,IAAQ,GAAA,CAAI,WAAW,CAAC,CAAA;AACxB,IAAA,IAAA,GAAQ,OAAO,QAAA,KAAgB,CAAA;AAAA,EACjC;AACA,EAAA,OAAO,IAAA,CAAK,SAAS,EAAE,CAAA;AACzB;AAeO,SAAS,mBAAA,CAAoB,IAAA,EAAe,KAAA,GAAQ,CAAA,EAAmB;AAC5E,EAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,IAAA,CAAK,SAAS,CAAA,CAAE,IAAA,EAAK,CAAE,IAAA,CAAK,GAAG,CAAA;AAC1D,EAAA,MAAM,aAAA,uBAAoB,GAAA,CAAI,CAAC,SAAS,OAAA,EAAS,eAAA,EAAiB,eAAe,CAAC,CAAA;AAClF,EAAA,MAAM,cAAc,KAAA,CAAM,IAAA,CAAK,KAAK,UAAU,CAAA,CAC3C,OAAO,CAAC,CAAA,KAAM,CAAC,aAAA,CAAc,GAAA,CAAI,EAAE,IAAI,CAAA,IAAK,EAAE,IAAA,KAAS,IAAI,EAC3D,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,EAAE,IAAA,CAAK,aAAA,CAAc,EAAE,IAAI,CAAC,EAC3C,GAAA,CAAI,CAAC,MAAM,CAAA,EAAG,CAAA,CAAE,IAAI,CAAA,CAAA,EAAI,CAAA,CAAE,KAAK,CAAA,CAAE,CAAA,CACjC,KAAK,GAAG,CAAA;AAEX,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAY;AAAA,IAClC,EAAA,EAAI,KAAK,EAAA,IAAM,IAAA;AAAA,IACf,GAAA,EAAK,IAAA,CAAK,YAAA,CAAa,UAAU,CAAA;AAAA,IACjC,cAAA,EAAgBA,OAAM,OAAO,CAAA;AAAA,IAC7B,aAAA,EAAeA,OAAM,WAAW,CAAA;AAAA,IAChC,eAAA,EACE,IAAA,CAAK,QAAA,CAAS,MAAA,KAAW,CAAA,GAAA,CAAK,IAAA,CAAK,WAAA,IAAe,EAAA,EAAI,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,GAAI,IAAA;AAAA,IACvE,YAAY,IAAA,CAAK,iBAAA;AAAA,IACjB,kBAAkB,KAAA,CAAM,IAAA,CAAK,KAAK,QAAQ,CAAA,CACvC,MAAM,CAAA,EAAG,CAAC,EACV,GAAA,CAAI,CAAC,MAAM,CAAA,CAAE,OAAA,CAAQ,aAAa,CAAA,CAClC,KAAK,GAAG,CAAA;AAAA,IACX,WAAA,EAAa,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,aAAA,EAAe,YAAY,EAAE,CAAA,CAAE,OAAA,CAAQ,IAAI,CAAA;AAAA,IACxE,IAAA,EAAM,IAAA,CAAK,YAAA,CAAa,MAAM;AAAA,GAChC;AACF;AAEO,SAAS,aAAA,CAAc,GAAmB,CAAA,EAA2B;AAC1E,EAAA,IAAI,CAAA,CAAE,OAAA,KAAY,CAAA,CAAE,OAAA,EAAS,OAAO,CAAA;AAEpC,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,IAAI,WAAA,GAAc,CAAA;AAElB,EAAA,MAAM,MAAA,GAA4C;AAAA,IAChD,CAAC,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,IAAI,EAAE,CAAA;AAAA,IACf,CAAC,CAAA,CAAE,GAAA,EAAK,CAAA,CAAE,KAAK,EAAE,CAAA;AAAA,IACjB,CAAC,CAAA,CAAE,cAAA,EAAgB,CAAA,CAAE,gBAAgB,EAAE,CAAA;AAAA,IACvC,CAAC,CAAA,CAAE,aAAA,EAAe,CAAA,CAAE,eAAe,EAAE,CAAA;AAAA,IACrC,CAAC,CAAA,CAAE,eAAA,EAAiB,CAAA,CAAE,iBAAiB,EAAE,CAAA;AAAA,IACzC,CAAC,CAAA,CAAE,UAAA,EAAY,CAAA,CAAE,YAAY,EAAE,CAAA;AAAA,IAC/B,CAAC,CAAA,CAAE,gBAAA,EAAkB,CAAA,CAAE,kBAAkB,EAAE,CAAA;AAAA,IAC3C,CAAC,CAAA,CAAE,WAAA,EAAa,CAAA,CAAE,aAAa,CAAC,CAAA;AAAA,IAChC,CAAC,CAAA,CAAE,IAAA,EAAM,CAAA,CAAE,MAAM,EAAE;AAAA,GACrB;AAEA,EAAA,KAAA,MAAW,CAAC,EAAA,EAAI,EAAA,EAAI,MAAM,KAAK,MAAA,EAAQ;AACrC,IAAA,IAAI,EAAA,KAAO,IAAA,IAAQ,EAAA,KAAO,IAAA,EAAM;AAC9B,MAAA,WAAA,IAAe,MAAA;AACf,MAAA,IAAI,EAAA,KAAO,IAAI,KAAA,IAAS,MAAA;AAAA,IAC1B;AAAA,EACF;AAEA,EAAA,OAAO,WAAA,KAAgB,CAAA,GAAI,CAAA,GAAI,KAAA,GAAQ,WAAA;AACzC;;;ACzEO,SAAS,aAAA,CACd,iBACA,gBAAA,EAC8B;AAC9B,EAAA,MAAM,IAAI,eAAA,CAAgB,MAAA;AAC1B,EAAA,MAAM,IAAI,gBAAA,CAAiB,MAAA;AAC3B,EAAA,MAAM,SAAA,GAAY,GAAA;AAElB,EAAA,MAAM,SAAqB,KAAA,CAAM,IAAA;AAAA,IAAK,EAAE,QAAQ,CAAA,EAAE;AAAA,IAAG,CAAC,CAAA,EAAG,CAAA,KACvD,KAAA,CAAM,IAAA;AAAA,MAAK,EAAE,QAAQ,CAAA,EAAE;AAAA,MAAG,CAACC,IAAG,CAAA,KAC5B,aAAA;AAAA,QACE,mBAAA,CAAoB,eAAA,CAAgB,CAAC,CAAE,CAAA;AAAA,QACvC,mBAAA,CAAoB,gBAAA,CAAiB,CAAC,CAAE;AAAA;AAC1C;AACF,GACF;AAEA,EAAA,MAAM,OAAA,uBAAc,GAAA,EAA6B;AACjD,EAAA,MAAM,YAAA,uBAAmB,GAAA,EAAY;AAErC,EAAA,MAAM,KAAA,GAAQ,eAAA,CACX,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,KAAM,CAAC,CAAA,CACf,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM;AACd,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,GAAI,OAAO,CAAC,CAAA,IAAK,CAAC,CAAC,CAAE,CAAA;AAC5C,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,GAAI,OAAO,CAAC,CAAA,IAAK,CAAC,CAAC,CAAE,CAAA;AAC5C,IAAA,OAAO,KAAA,GAAQ,KAAA;AAAA,EACjB,CAAC,CAAA;AAEH,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,IAAA,MAAM,GAAA,GAAM,OAAO,CAAC,CAAA;AACpB,IAAA,IAAI,KAAA,GAAQ,EAAA;AACZ,IAAA,IAAI,SAAA,GAAY,SAAA;AAEhB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC1B,MAAA,IAAI,CAAC,aAAa,GAAA,CAAI,CAAC,MAAM,GAAA,CAAI,CAAC,CAAA,IAAK,CAAA,IAAK,SAAA,EAAW;AACrD,QAAA,SAAA,GAAY,IAAI,CAAC,CAAA;AACjB,QAAA,KAAA,GAAQ,CAAA;AAAA,MACV;AAAA,IACF;AAEA,IAAA,IAAI,SAAS,CAAA,EAAG;AACd,MAAA,OAAA,CAAQ,IAAI,eAAA,CAAgB,CAAC,CAAA,EAAI,gBAAA,CAAiB,KAAK,CAAE,CAAA;AACzD,MAAA,YAAA,CAAa,IAAI,KAAK,CAAA;AAAA,IACxB,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,GAAA,CAAI,eAAA,CAAgB,CAAC,CAAA,EAAI,IAAI,CAAA;AAAA,IACvC;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;AC/CA,IAAM,cAAA,GAA8B;AAAA,EAClC,cAAA,EAAgB,IAAA;AAAA,EAChB,aAAA,EAAe;AACjB,CAAA;AAQO,IAAM,aAAN,MAAiB;AAAA,EACd,MAAA;AAAA,EAER,WAAA,CAAY,MAAA,GAA+B,EAAC,EAAG;AAC7C,IAAA,IAAA,CAAK,MAAA,GAAS,EAAE,GAAG,cAAA,EAAgB,GAAG,MAAA,EAAO;AAAA,EAC/C;AAAA,EAEA,KAAA,CAAM,MAAe,EAAA,EAAmB;AACtC,IAAA,MAAM,GAAA,GAAM,KAAK,aAAA,EAAc;AAG/B,IAAA,IAAI,CAAE,KAAa,cAAA,EAAgB;AACjC,MAAA,iBAAA,CAAkB,IAAI,CAAA;AAAA,IACxB;AAEA,IAAA,qBAAA,CAAsB,MAAM;AAC1B,MAAA,IAAA,CAAK,YAAA,CAAa,IAAA,EAAM,EAAA,EAAI,GAAG,CAAA;AAC/B,MAAA,IAAA,CAAK,eAAe,GAAG,CAAA;AAAA,IACzB,CAAC,CAAA;AAAA,EACH;AAAA,EAEQ,aAAA,GAA8B;AACpC,IAAA,MAAM,GAAA,GAAoB;AAAA,MACxB,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,cAAA,EAAgB,IAAA;AAAA,MAChB,eAAA,sBAAqB,GAAA;AAAI,KAC3B;AAEA,IAAA,IAAI,IAAA,CAAK,OAAO,aAAA,EAAe;AAC7B,MAAA,GAAA,CAAI,iBAAiB,QAAA,CAAS,aAAA;AAAA,IAChC;AAEA,IAAA,IAAI,IAAA,CAAK,OAAO,cAAA,EAAgB;AAC9B,MAAA,IAAA,CAAK,sBAAA,CAAuB,QAAA,CAAS,IAAA,EAAM,GAAA,CAAI,eAAe,CAAA;AAAA,IAChE;AAEA,IAAA,OAAO,GAAA;AAAA,EACT;AAAA,EAEQ,eAAe,GAAA,EAAyB;AAC9C,IAAA,IAAI,GAAA,CAAI,MAAA,CAAO,aAAA,IAAiB,GAAA,CAAI,cAAA,EAAgB;AAClD,MAAA,MAAM,UAAU,GAAA,CAAI,cAAA;AACpB,MAAA,IAAI,QAAQ,KAAA,IAAS,QAAA,CAAS,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,EAAG;AACpD,QAAA,OAAA,CAAQ,KAAA,EAAM;AAAA,MAChB;AAAA,IACF;AAEA,IAAA,IAAI,GAAA,CAAI,OAAO,cAAA,EAAgB;AAC7B,MAAA,GAAA,CAAI,eAAA,CAAgB,OAAA,CAAQ,CAAC,GAAA,EAAK,OAAA,KAAY;AAC5C,QAAA,OAAA,CAAQ,YAAY,GAAA,CAAI,GAAA;AACxB,QAAA,OAAA,CAAQ,aAAa,GAAA,CAAI,IAAA;AAAA,MAC3B,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,sBAAA,CACN,SACA,SAAA,EACM;AACN,IAAA,IAAI,OAAA,CAAQ,SAAA,GAAY,CAAA,IAAK,OAAA,CAAQ,aAAa,CAAA,EAAG;AACnD,MAAA,SAAA,CAAU,IAAI,OAAA,EAAS;AAAA,QACrB,KAAK,OAAA,CAAQ,SAAA;AAAA,QACb,MAAM,OAAA,CAAQ;AAAA,OACf,CAAA;AAAA,IACH;AAEA,IAAA,KAAA,CAAM,KAAK,OAAA,CAAQ,QAAQ,CAAA,CAAE,OAAA,CAAQ,CAAC,KAAA,KAAU;AAC9C,MAAA,IAAA,CAAK,sBAAA,CAAuB,OAAO,SAAS,CAAA;AAAA,IAC9C,CAAC,CAAA;AAAA,EACH;AAAA,EAEQ,YAAA,CAAa,IAAA,EAAe,EAAA,EAAa,GAAA,EAAyB;AAExE,IAAA,IAAI,kBAAA,CAAmB,IAAA,EAAM,EAAE,CAAA,EAAG;AAChC,MAAA;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,cAAA,CAAe,MAAM,EAAE,CAAA;AAG5B,IAAA,IAAA,CAAK,aAAA,CAAc,IAAA,EAAM,EAAA,EAAI,GAAG,CAAA;AAAA,EAClC;AAAA,EAEQ,cAAA,CAAe,MAAe,EAAA,EAAmB;AACvD,IAAA,MAAM,YAAY,IAAA,CAAK,UAAA;AACvB,IAAA,MAAM,UAAU,EAAA,CAAG,UAAA;AAEnB,IAAA,KAAA,IAAS,IAAI,SAAA,CAAU,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC9C,MAAA,MAAM,IAAA,GAAO,UAAU,CAAC,CAAA;AACxB,MAAA,IAAI,CAAC,EAAA,CAAG,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA,EAAG;AAC/B,QAAA,IAAA,CAAK,eAAA,CAAgB,KAAK,IAAI,CAAA;AAAA,MAChC;AAAA,IACF;AAEA,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AACvC,MAAA,MAAM,IAAA,GAAO,QAAQ,CAAC,CAAA;AACtB,MAAA,IAAI,KAAK,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA,KAAM,KAAK,KAAA,EAAO;AAC/C,QAAA,IAAA,CAAK,YAAA,CAAa,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,KAAK,CAAA;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,aAAA,CAAc,IAAA,EAAe,EAAA,EAAa,GAAA,EAAyB;AACzE,IAAA,MAAM,YAAA,GAAe,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,QAAQ,CAAA;AAC7C,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,IAAA,CAAK,EAAA,CAAG,QAAQ,CAAA;AAGzC,IAAA,IAAI,YAAA,CAAa,MAAA,KAAW,CAAA,IAAK,UAAA,CAAW,WAAW,CAAA,EAAG;AACxD,MAAA,IAAI,IAAA,CAAK,WAAA,KAAgB,EAAA,CAAG,WAAA,EAAa;AACvC,QAAA,IAAA,CAAK,cAAc,EAAA,CAAG,WAAA;AAAA,MACxB;AACA,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,QAAA,GAAW,aAAA,CAAc,YAAA,EAAc,UAAU,CAAA;AAGvD,IAAA,MAAM,iBAAA,uBAAwB,GAAA,EAAa;AAE3C,IAAA,KAAA,MAAW,CAAC,YAAA,EAAc,aAAa,CAAA,IAAK,QAAA,EAAU;AACpD,MAAA,IAAI,CAAC,aAAA,EAAe;AAElB,QAAA,IAAA,CAAK,YAAY,YAAY,CAAA;AAAA,MAC/B,CAAA,MAAO;AAEL,QAAA,IAAA,CAAK,YAAA,CAAa,YAAA,EAAc,aAAA,EAAe,GAAG,CAAA;AAClD,QAAA,iBAAA,CAAkB,IAAI,aAAa,CAAA;AAAA,MACrC;AAAA,IACF;AAGA,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,UAAA,CAAW,QAAQ,CAAA,EAAA,EAAK;AAC1C,MAAA,MAAM,aAAA,GAAgB,WAAW,CAAC,CAAA;AAClC,MAAA,IAAI,CAAC,iBAAA,CAAkB,GAAA,CAAI,aAAa,CAAA,EAAG;AACzC,QAAA,MAAM,MAAA,GAAS,aAAA,CAAc,SAAA,CAAU,IAAI,CAAA;AAC3C,QAAA,MAAM,OAAA,GAAU,IAAA,CAAK,QAAA,CAAS,CAAC,CAAA,IAAK,IAAA;AACpC,QAAA,IAAA,CAAK,YAAA,CAAa,QAAQ,OAAO,CAAA;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,cAAc,MAAA,EAA2C;AACvE,EAAA,OAAO,IAAI,WAAW,MAAM,CAAA;AAC9B;AAEO,SAAS,KAAA,CACd,IAAA,EACA,EAAA,EACA,MAAA,EACM;AACN,EAAA,MAAM,OAAA,GAAU,cAAc,MAAM,CAAA;AACpC,EAAA,OAAA,CAAQ,KAAA,CAAM,MAAM,EAAE,CAAA;AACxB","file":"morpher.mjs","sourcesContent":["type NodeFingerprint = string;\n\nfunction fnv1a(str: string): string {\n let hash = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = (hash * 0x01000193) >>> 0;\n }\n return hash.toString(36);\n}\n\nfunction fingerprintNode(node: Element): NodeFingerprint {\n const tag = node.tagName.toLowerCase();\n const attrs = Array.from(node.attributes)\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((a) => `${a.name}=${a.value}`)\n .join(\"|\");\n const text = node.childNodes.length === 0 ? node.textContent ?? \"\" : \"\";\n const childHashes = Array.from(node.children).map(fingerprintNode).join(\",\");\n\n return fnv1a(`${tag}[${attrs}]{${text}}(${childHashes})`);\n}\n\nconst FINGERPRINT_KEY = \"__specnav_fp__\";\n\nexport function stampFingerprints(root: Element): void {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);\n let node = walker.currentNode as Element;\n while (node) {\n (node as any)[FINGERPRINT_KEY] = fingerprintNode(node);\n node = walker.nextNode() as Element;\n }\n}\n\nexport function fingerprintMatches(current: Element, incoming: Element): boolean {\n const currentFp = (current as any)[FINGERPRINT_KEY];\n if (!currentFp) return false;\n return currentFp === fingerprintNode(incoming);\n}\n\nexport { FINGERPRINT_KEY };\n","function fnv1a(str: string): string {\n let hash = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = (hash * 0x01000193) >>> 0;\n }\n return hash.toString(36);\n}\n\ninterface IdentityVector {\n tagName: string;\n id: string | null;\n key: string | null;\n classSignature: string;\n attrSignature: string;\n textFingerprint: string | null;\n childCount: number;\n childTagSequence: string;\n domPosition: number;\n role: string | null;\n}\n\nexport function buildIdentityVector(node: Element, depth = 0): IdentityVector {\n const classes = Array.from(node.classList).sort().join(\" \");\n const volatileAttrs = new Set([\"style\", \"class\", \"aria-expanded\", \"aria-selected\"]);\n const stableAttrs = Array.from(node.attributes)\n .filter((a) => !volatileAttrs.has(a.name) && a.name !== \"id\")\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((a) => `${a.name}=${a.value}`)\n .join(\"|\");\n\n return {\n tagName: node.tagName.toLowerCase(),\n id: node.id || null,\n key: node.getAttribute(\"data-key\"),\n classSignature: fnv1a(classes),\n attrSignature: fnv1a(stableAttrs),\n textFingerprint:\n node.children.length === 0 ? (node.textContent ?? \"\").slice(0, 64) : null,\n childCount: node.childElementCount,\n childTagSequence: Array.from(node.children)\n .slice(0, 5)\n .map((c) => c.tagName.toLowerCase())\n .join(\">\"),\n domPosition: Array.from(node.parentElement?.children ?? []).indexOf(node),\n role: node.getAttribute(\"role\"),\n };\n}\n\nexport function identityScore(a: IdentityVector, b: IdentityVector): number {\n if (a.tagName !== b.tagName) return 0;\n\n let score = 0;\n let totalWeight = 0;\n\n const checks: Array<[unknown, unknown, number]> = [\n [a.id, b.id, 40],\n [a.key, b.key, 35],\n [a.classSignature, b.classSignature, 15],\n [a.attrSignature, b.attrSignature, 20],\n [a.textFingerprint, b.textFingerprint, 15],\n [a.childCount, b.childCount, 10],\n [a.childTagSequence, b.childTagSequence, 15],\n [a.domPosition, b.domPosition, 5],\n [a.role, b.role, 10],\n ];\n\n for (const [av, bv, weight] of checks) {\n if (av !== null && bv !== null) {\n totalWeight += weight;\n if (av === bv) score += weight;\n }\n }\n\n return totalWeight === 0 ? 0 : score / totalWeight;\n}\n","import { buildIdentityVector, identityScore } from \"./identity\";\n\nexport function matchSiblings(\n currentChildren: Element[],\n incomingChildren: Element[]\n): Map<Element, Element | null> {\n const n = currentChildren.length;\n const m = incomingChildren.length;\n const THRESHOLD = 0.4;\n\n const scores: number[][] = Array.from({ length: n }, (_, i) =>\n Array.from({ length: m }, (_, j) =>\n identityScore(\n buildIdentityVector(currentChildren[i]!),\n buildIdentityVector(incomingChildren[j]!)\n )\n )\n );\n\n const matched = new Map<Element, Element | null>();\n const usedIncoming = new Set<number>();\n\n const order = currentChildren\n .map((_, i) => i)\n .sort((a, b) => {\n const bestA = Math.max(...(scores[a] ?? [0]));\n const bestB = Math.max(...(scores[b] ?? [0]));\n return bestB - bestA;\n });\n\n for (const i of order) {\n const row = scores[i]!;\n let bestJ = -1;\n let bestScore = THRESHOLD;\n\n for (let j = 0; j < m; j++) {\n if (!usedIncoming.has(j) && (row[j] ?? 0) > bestScore) {\n bestScore = row[j]!;\n bestJ = j;\n }\n }\n\n if (bestJ >= 0) {\n matched.set(currentChildren[i]!, incomingChildren[bestJ]!);\n usedIncoming.add(bestJ);\n } else {\n matched.set(currentChildren[i]!, null);\n }\n }\n\n return matched;\n}\n","import type { MorphConfig } from \"./types\";\nimport { stampFingerprints, fingerprintMatches } from \"./morpher/fingerprint\";\nimport { matchSiblings } from \"./morpher/matcher\";\n\nconst DEFAULT_CONFIG: MorphConfig = {\n preserveScroll: true,\n preserveFocus: true,\n};\n\ninterface MorphContext {\n config: MorphConfig;\n focusedElement: Element | null;\n scrollPositions: Map<Element, { top: number; left: number }>;\n}\n\nexport class DOMmorpher {\n private config: MorphConfig;\n\n constructor(config: Partial<MorphConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n morph(from: Element, to: Element): void {\n const ctx = this.createContext();\n \n // Stamp fingerprints on first use\n if (!(from as any).__specnav_fp__) {\n stampFingerprints(from);\n }\n \n requestAnimationFrame(() => {\n this.morphElement(from, to, ctx);\n this.restoreContext(ctx);\n });\n }\n\n private createContext(): MorphContext {\n const ctx: MorphContext = {\n config: this.config,\n focusedElement: null,\n scrollPositions: new Map(),\n };\n\n if (this.config.preserveFocus) {\n ctx.focusedElement = document.activeElement;\n }\n\n if (this.config.preserveScroll) {\n this.captureScrollPositions(document.body, ctx.scrollPositions);\n }\n\n return ctx;\n }\n\n private restoreContext(ctx: MorphContext): void {\n if (ctx.config.preserveFocus && ctx.focusedElement) {\n const element = ctx.focusedElement as HTMLElement;\n if (element.focus && document.body.contains(element)) {\n element.focus();\n }\n }\n\n if (ctx.config.preserveScroll) {\n ctx.scrollPositions.forEach((pos, element) => {\n element.scrollTop = pos.top;\n element.scrollLeft = pos.left;\n });\n }\n }\n\n private captureScrollPositions(\n element: Element,\n positions: Map<Element, { top: number; left: number }>\n ): void {\n if (element.scrollTop > 0 || element.scrollLeft > 0) {\n positions.set(element, {\n top: element.scrollTop,\n left: element.scrollLeft,\n });\n }\n\n Array.from(element.children).forEach((child) => {\n this.captureScrollPositions(child, positions);\n });\n }\n\n private morphElement(from: Element, to: Element, ctx: MorphContext): void {\n // O(1) fingerprint check - skip entire subtree if unchanged\n if (fingerprintMatches(from, to)) {\n return;\n }\n\n // Sync attributes\n this.syncAttributes(from, to);\n\n // Morph children using semantic matching\n this.morphChildren(from, to, ctx);\n }\n\n private syncAttributes(from: Element, to: Element): void {\n const fromAttrs = from.attributes;\n const toAttrs = to.attributes;\n\n for (let i = fromAttrs.length - 1; i >= 0; i--) {\n const attr = fromAttrs[i]!;\n if (!to.hasAttribute(attr.name)) {\n from.removeAttribute(attr.name);\n }\n }\n\n for (let i = 0; i < toAttrs.length; i++) {\n const attr = toAttrs[i]!;\n if (from.getAttribute(attr.name) !== attr.value) {\n from.setAttribute(attr.name, attr.value);\n }\n }\n }\n\n private morphChildren(from: Element, to: Element, ctx: MorphContext): void {\n const fromChildren = Array.from(from.children);\n const toChildren = Array.from(to.children);\n\n // Handle text-only nodes\n if (fromChildren.length === 0 && toChildren.length === 0) {\n if (from.textContent !== to.textContent) {\n from.textContent = to.textContent;\n }\n return;\n }\n\n // Use semantic matching instead of positional\n const matchMap = matchSiblings(fromChildren, toChildren);\n\n // Process matched pairs\n const processedIncoming = new Set<Element>();\n \n for (const [currentChild, incomingChild] of matchMap) {\n if (!incomingChild) {\n // Remove unmatched current node\n from.removeChild(currentChild);\n } else {\n // Recurse into matched pair\n this.morphElement(currentChild, incomingChild, ctx);\n processedIncoming.add(incomingChild);\n }\n }\n\n // Insert new incoming nodes that had no match\n for (let j = 0; j < toChildren.length; j++) {\n const incomingChild = toChildren[j]!;\n if (!processedIncoming.has(incomingChild)) {\n const cloned = incomingChild.cloneNode(true) as Element;\n const refNode = from.children[j] || null;\n from.insertBefore(cloned, refNode);\n }\n }\n }\n}\n\nexport function createMorpher(config?: Partial<MorphConfig>): DOMmorpher {\n return new DOMmorpher(config);\n}\n\nexport function morph(\n from: Element,\n to: Element,\n config?: Partial<MorphConfig>\n): void {\n const morpher = createMorpher(config);\n morpher.morph(from, to);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specnav-core",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "Framework-agnostic speculative navigation engine with trajectory prediction, 3-layer caching, and DOM morphing",
5
5
  "license": "MIT",
6
6
  "author": "Abraham",