@fictjs/runtime 0.7.0 → 0.9.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.
Files changed (74) hide show
  1. package/README.md +46 -0
  2. package/dist/advanced.cjs +9 -9
  3. package/dist/advanced.d.cts +4 -4
  4. package/dist/advanced.d.ts +4 -4
  5. package/dist/advanced.js +4 -4
  6. package/dist/{effect-DAzpH7Mm.d.cts → binding-BWchH3Kp.d.cts} +33 -24
  7. package/dist/{effect-DAzpH7Mm.d.ts → binding-BWchH3Kp.d.ts} +33 -24
  8. package/dist/{chunk-7YQK3XKY.js → chunk-DXG3TARY.js} +520 -518
  9. package/dist/chunk-DXG3TARY.js.map +1 -0
  10. package/dist/{chunk-TLDT76RV.js → chunk-FVX77557.js} +3 -3
  11. package/dist/{chunk-WRU3IZOA.js → chunk-JVYH76ZX.js} +3 -3
  12. package/dist/chunk-LBE6DC3V.cjs +768 -0
  13. package/dist/chunk-LBE6DC3V.cjs.map +1 -0
  14. package/dist/chunk-N6ODUM2Y.js +768 -0
  15. package/dist/chunk-N6ODUM2Y.js.map +1 -0
  16. package/dist/{chunk-PRF4QG73.cjs → chunk-OAM7HABA.cjs} +423 -246
  17. package/dist/chunk-OAM7HABA.cjs.map +1 -0
  18. package/dist/{chunk-CEV6TO5U.cjs → chunk-PD6IQY2Y.cjs} +8 -8
  19. package/dist/{chunk-CEV6TO5U.cjs.map → chunk-PD6IQY2Y.cjs.map} +1 -1
  20. package/dist/{chunk-HHDHQGJY.cjs → chunk-PG4QX2I2.cjs} +17 -17
  21. package/dist/{chunk-HHDHQGJY.cjs.map → chunk-PG4QX2I2.cjs.map} +1 -1
  22. package/dist/{chunk-4LCHQ7U4.js → chunk-T2LNV5Q5.js} +271 -94
  23. package/dist/chunk-T2LNV5Q5.js.map +1 -0
  24. package/dist/{chunk-FSCBL7RI.cjs → chunk-UBFDB6OL.cjs} +521 -519
  25. package/dist/chunk-UBFDB6OL.cjs.map +1 -0
  26. package/dist/{context-C4vBQbb4.d.ts → devtools-5AipK9CX.d.cts} +35 -35
  27. package/dist/{context-BFbHf9nC.d.cts → devtools-BDp76luf.d.ts} +35 -35
  28. package/dist/index.cjs +42 -42
  29. package/dist/index.d.cts +4 -4
  30. package/dist/index.d.ts +4 -4
  31. package/dist/index.dev.js +3 -3
  32. package/dist/index.dev.js.map +1 -1
  33. package/dist/index.js +3 -3
  34. package/dist/internal-list.cjs +12 -0
  35. package/dist/internal-list.cjs.map +1 -0
  36. package/dist/internal-list.d.cts +2 -0
  37. package/dist/internal-list.d.ts +2 -0
  38. package/dist/internal-list.js +12 -0
  39. package/dist/internal-list.js.map +1 -0
  40. package/dist/internal.cjs +6 -746
  41. package/dist/internal.cjs.map +1 -1
  42. package/dist/internal.d.cts +6 -74
  43. package/dist/internal.d.ts +6 -74
  44. package/dist/internal.js +12 -752
  45. package/dist/internal.js.map +1 -1
  46. package/dist/list-DL5DOFcO.d.ts +71 -0
  47. package/dist/list-hP7hQ9Vk.d.cts +71 -0
  48. package/dist/loader.cjs +94 -15
  49. package/dist/loader.cjs.map +1 -1
  50. package/dist/loader.d.cts +16 -2
  51. package/dist/loader.d.ts +16 -2
  52. package/dist/loader.js +87 -8
  53. package/dist/loader.js.map +1 -1
  54. package/dist/{props-84UJeWO8.d.cts → props-BpZz0AOq.d.cts} +2 -2
  55. package/dist/{props-BRhFK50f.d.ts → props-CjLH0JE-.d.ts} +2 -2
  56. package/dist/{resume-i-A3EFox.d.cts → resume-BJ4oHLi_.d.cts} +3 -1
  57. package/dist/{resume-CqeQ3v_q.d.ts → resume-CuyJWXP_.d.ts} +3 -1
  58. package/dist/{scope-DlCBL1Ft.d.cts → scope-BJCtq8hJ.d.cts} +1 -1
  59. package/dist/{scope-D3DpsfoG.d.ts → scope-jPt5DHRT.d.ts} +1 -1
  60. package/package.json +8 -1
  61. package/src/binding.ts +113 -36
  62. package/src/cycle-guard.ts +3 -3
  63. package/src/internal/list.ts +7 -0
  64. package/src/internal.ts +1 -0
  65. package/src/list-helpers.ts +1 -1
  66. package/src/loader.ts +119 -9
  67. package/src/resume.ts +6 -3
  68. package/src/signal.ts +8 -1
  69. package/dist/chunk-4LCHQ7U4.js.map +0 -1
  70. package/dist/chunk-7YQK3XKY.js.map +0 -1
  71. package/dist/chunk-FSCBL7RI.cjs.map +0 -1
  72. package/dist/chunk-PRF4QG73.cjs.map +0 -1
  73. /package/dist/{chunk-TLDT76RV.js.map → chunk-FVX77557.js.map} +0 -0
  74. /package/dist/{chunk-WRU3IZOA.js.map → chunk-JVYH76ZX.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reconcile.ts","../src/list-helpers.ts"],"sourcesContent":["/**\n * Fict DOM Reconciliation\n *\n * Efficient array reconciliation algorithm based on udomdiff.\n * https://github.com/WebReflection/udomdiff\n *\n * This algorithm uses a 5-step strategy:\n * 1. Common prefix - skip matching nodes at start\n * 2. Common suffix - skip matching nodes at end\n * 3. Append - insert remaining new nodes\n * 4. Remove - remove remaining old nodes\n * 5. Swap/Map fallback - handle complex rearrangements\n *\n * Most real-world updates (95%+) use fast paths without building a Map.\n */\n\n/**\n * Reconcile two arrays of DOM nodes, efficiently updating the DOM.\n *\n * @param parentNode - The parent element containing the nodes\n * @param a - The old array of nodes (currently in DOM)\n * @param b - The new array of nodes (target state)\n *\n * **Note:** This function may mutate the input array `a` during the swap\n * optimization (step 5a). If you need to preserve the original array,\n * pass a shallow copy: `reconcileArrays(parent, [...oldNodes], newNodes)`.\n *\n * @example\n * ```ts\n * const oldNodes = [node1, node2, node3]\n * const newNodes = [node1, node4, node3] // node2 replaced with node4\n * reconcileArrays(parent, oldNodes, newNodes)\n * ```\n */\nexport default function reconcileArrays(parentNode: ParentNode, a: Node[], b: Node[]): void {\n const bLength = b.length\n let aEnd = a.length\n let bEnd = bLength\n let aStart = 0\n let bStart = 0\n const after = aEnd > 0 ? a[aEnd - 1]!.nextSibling : null\n let map: Map<Node, number> | null = null\n\n while (aStart < aEnd || bStart < bEnd) {\n // 1. Common prefix - nodes match at start\n if (a[aStart] === b[bStart]) {\n aStart++\n bStart++\n continue\n }\n\n // 2. Common suffix - nodes match at end\n while (a[aEnd - 1] === b[bEnd - 1]) {\n aEnd--\n bEnd--\n }\n\n // 3. Append - old array exhausted, insert remaining new nodes\n if (aEnd === aStart) {\n const node: Node | null =\n bEnd < bLength ? (bStart ? b[bStart - 1]!.nextSibling : (b[bEnd - bStart] ?? null)) : after\n\n const count = bEnd - bStart\n const doc = (parentNode as Node).ownerDocument\n if (count > 1 && doc) {\n const frag = doc.createDocumentFragment()\n for (let i = bStart; i < bEnd; i++) {\n frag.appendChild(b[i]!)\n }\n parentNode.insertBefore(frag, node)\n bStart = bEnd\n } else {\n while (bStart < bEnd) {\n parentNode.insertBefore(b[bStart++]!, node)\n }\n }\n }\n // 4. Remove - new array exhausted, remove remaining old nodes\n else if (bEnd === bStart) {\n while (aStart < aEnd) {\n const nodeToRemove = a[aStart]!\n if (!map || !map.has(nodeToRemove)) {\n nodeToRemove.parentNode?.removeChild(nodeToRemove)\n }\n aStart++\n }\n }\n // 5a. Swap backward - detect backward swap pattern\n else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {\n const node = a[--aEnd]!.nextSibling\n parentNode.insertBefore(b[bStart++]!, a[aStart++]!.nextSibling)\n parentNode.insertBefore(b[--bEnd]!, node)\n // Update reference in old array for potential future matches\n a[aEnd] = b[bEnd]!\n }\n // 5b. Map fallback - use Map for complex rearrangements\n else {\n // Build map on first use (lazy initialization)\n if (!map) {\n map = new Map()\n let i = bStart\n while (i < bEnd) {\n map.set(b[i]!, i++)\n }\n }\n\n const index = map.get(a[aStart]!)\n\n if (index != null) {\n if (bStart < index && index < bEnd) {\n // Check for longest increasing subsequence\n let i = aStart\n let sequence = 1\n let t: number | undefined\n\n while (++i < aEnd && i < bEnd) {\n t = map.get(a[i]!)\n if (t == null || t !== index + sequence) break\n sequence++\n }\n\n // Use optimal strategy based on sequence length\n if (sequence > index - bStart) {\n // Sequence is long enough - insert nodes before current\n const node = a[aStart]!\n while (bStart < index) {\n parentNode.insertBefore(b[bStart++]!, node)\n }\n } else {\n // Short sequence - replace\n parentNode.replaceChild(b[bStart++]!, a[aStart++]!)\n }\n } else {\n aStart++\n }\n } else {\n // Node not in new array - remove it\n const nodeToRemove = a[aStart++]!\n nodeToRemove.parentNode?.removeChild(nodeToRemove)\n }\n }\n }\n}\n\n/**\n * Simple reconciliation for keyed lists.\n * Uses the same algorithm but works with keyed blocks.\n *\n * @param parentNode - The parent element\n * @param oldNodes - Old nodes in DOM order\n * @param newNodes - New nodes in target order\n */\nexport function reconcileNodes(parentNode: ParentNode, oldNodes: Node[], newNodes: Node[]): void {\n reconcileArrays(parentNode, oldNodes, newNodes)\n}\n","/**\n * List Helpers for Compiler-Generated Fine-Grained Updates\n *\n * These helpers are used by the compiler to generate efficient keyed list rendering.\n * They provide low-level primitives for DOM node manipulation without rebuilding.\n */\n\nimport { createElement } from './dom'\nimport { createRenderEffect } from './effect'\nimport { isHydratingActive, withHydrationRange } from './hydration'\nimport {\n createRootContext,\n destroyRoot,\n flushOnMount,\n getCurrentRoot,\n popRoot,\n pushRoot,\n type RootContext,\n} from './lifecycle'\nimport { insertNodesBefore, removeNodes, toNodeArray } from './node-ops'\nimport reconcileArrays from './reconcile'\nimport { __fictIsHydrating, __fictIsSSR } from './resume'\nimport { batch } from './scheduler'\nimport { createSignal, effectScope, flush, setActiveSub, type Signal } from './signal'\nimport type { FictNode } from './types'\n\n// Re-export shared DOM helpers for compiler-generated code\nexport { insertNodesBefore, removeNodes, toNodeArray }\n\nconst isDev =\n typeof __DEV__ !== 'undefined'\n ? __DEV__\n : typeof process === 'undefined' || process.env?.NODE_ENV !== 'production'\n\nconst isShadowRoot = (node: Node): node is ShadowRoot =>\n typeof ShadowRoot !== 'undefined' && node instanceof ShadowRoot\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A keyed block represents a single item in a list with its associated DOM nodes and state\n */\ninterface KeyedBlock<T = unknown> {\n /** Unique key for this block */\n key: string | number\n /** DOM nodes belonging to this block */\n nodes: Node[]\n /** Root context for lifecycle management */\n root: RootContext\n /** Signal containing the current item value */\n item: Signal<T>\n /** Signal containing the current index */\n index: Signal<number>\n /** Last raw item value assigned to this block */\n rawItem: T\n /** Last raw index value assigned to this block */\n rawIndex: number\n}\n\n/**\n * Container for managing keyed list blocks\n */\ninterface KeyedListContainer<T = unknown> {\n /** Start marker comment node */\n startMarker: Comment\n /** End marker comment node */\n endMarker: Comment\n /** Map of key to block */\n blocks: Map<string | number, KeyedBlock<T>>\n /** Scratch map reused for the next render */\n nextBlocks: Map<string | number, KeyedBlock<T>>\n /** Current nodes in DOM order (including markers) */\n currentNodes: Node[]\n /** Next-frame node buffer to avoid reallocations */\n nextNodes: Node[]\n /** Ordered blocks in current DOM order */\n orderedBlocks: KeyedBlock<T>[]\n /** Next-frame ordered block buffer to avoid reallocations */\n nextOrderedBlocks: KeyedBlock<T>[]\n /** Track position of keys in the ordered buffer to handle duplicates */\n orderedIndexByKey: Map<string | number, number>\n /** Cleanup function */\n dispose: () => void\n}\n\n/**\n * Binding handle returned by createKeyedList for compiler-generated code\n */\nexport interface KeyedListBinding {\n /** Document fragment placeholder inserted by the compiler/runtime */\n marker: Comment | DocumentFragment\n /** Start marker comment node */\n startMarker: Comment\n /** End marker comment node */\n endMarker: Comment\n /** Flush pending items - call after markers are inserted into DOM */\n flush?: () => void\n /** Cleanup function */\n dispose: () => void\n}\n\ntype FineGrainedRenderItem<T> = (\n itemSig: Signal<T>,\n indexSig: Signal<number>,\n key: string | number,\n) => Node[]\n\n// ============================================================================\n// DOM Manipulation Primitives\n// ============================================================================\n\n/**\n * Move nodes to a position before the anchor node.\n * This is optimized to avoid unnecessary DOM operations.\n *\n * @param parent - Parent node to move nodes within\n * @param nodes - Array of nodes to move\n * @param anchor - Node to insert before (or null for end)\n */\nexport function moveNodesBefore(parent: Node, nodes: Node[], anchor: Node | null): void {\n // Insert in reverse order to maintain correct sequence\n // This way each node becomes the new anchor for the next\n for (let i = nodes.length - 1; i >= 0; i--) {\n const node = nodes[i]!\n if (!node || !(node instanceof Node)) {\n const message = isDev ? 'Invalid node in moveNodesBefore' : 'FICT:E_NODE'\n throw new Error(message)\n }\n // Only move if not already in correct position\n if (node.nextSibling !== anchor) {\n if (node.ownerDocument !== parent.ownerDocument && parent.ownerDocument) {\n parent.ownerDocument.adoptNode(node)\n }\n try {\n parent.insertBefore(node, anchor)\n } catch (e: any) {\n if (parent.ownerDocument) {\n try {\n const clone = parent.ownerDocument.importNode(node, true)\n parent.insertBefore(clone, anchor)\n // Update the nodes array with the clone to maintain correct references.\n // This ensures future operations (like removal or reordering) work correctly.\n nodes[i] = clone\n if (isDev) {\n console.warn(\n `[fict] Node cloning fallback triggered during list reordering. ` +\n `This may indicate cross-document node insertion. ` +\n `The node reference has been updated to the clone.`,\n )\n }\n anchor = clone\n continue\n } catch {\n // Clone fallback failed\n }\n }\n throw e\n }\n }\n anchor = node\n }\n}\n\n/**\n * Remove an array of nodes from the DOM\n *\n * @param nodes - Array of nodes to remove\n */\n// Number.MAX_SAFE_INTEGER is 2^53 - 1, but we reset earlier to avoid any precision issues\nconst MAX_SAFE_VERSION = 0x1fffffffffffff // 2^53 - 1\n\nexport function createVersionedSignalAccessor<T>(initialValue: T): Signal<T> {\n let current = initialValue\n let version = 0\n const track = createSignal(version)\n\n function accessor(value?: T): T | void {\n if (arguments.length === 0) {\n track()\n return current\n }\n current = value as T\n // This is safe because we only care about version changes, not absolute values\n version = version >= MAX_SAFE_VERSION ? 1 : version + 1\n track(version)\n }\n\n return accessor as Signal<T>\n}\n\n// ============================================================================\n// Keyed List Container\n// ============================================================================\n\n/**\n * Create a container for managing a keyed list.\n * This sets up the marker nodes and provides cleanup.\n *\n * @returns Container object with markers, blocks map, and dispose function\n */\nfunction createKeyedListContainer<T = unknown>(\n startOverride?: Comment,\n endOverride?: Comment,\n): KeyedListContainer<T> {\n const startMarker = startOverride ?? document.createComment('fict:list:start')\n const endMarker = endOverride ?? document.createComment('fict:list:end')\n\n const dispose = () => {\n // Clean up all blocks\n for (const block of container.blocks.values()) {\n destroyRoot(block.root)\n // Nodes are removed by parent disposal or specific cleanup if needed\n // But for list disposal, we just clear the container\n }\n container.blocks.clear()\n container.nextBlocks.clear()\n\n // Remove nodes (including markers)\n // Check if markers are still in DOM before using Range\n if (!startMarker.parentNode || !endMarker.parentNode) {\n // Markers already removed, nothing to do\n container.currentNodes = []\n container.nextNodes = []\n container.orderedBlocks.length = 0\n container.nextOrderedBlocks.length = 0\n container.orderedIndexByKey.clear()\n return\n }\n const range = document.createRange()\n range.setStartBefore(startMarker)\n range.setEndAfter(endMarker)\n range.deleteContents()\n\n // Clear cache\n container.currentNodes = []\n container.nextNodes = []\n container.nextBlocks.clear()\n container.orderedBlocks.length = 0\n container.nextOrderedBlocks.length = 0\n container.orderedIndexByKey.clear()\n }\n\n const container: KeyedListContainer<T> = {\n startMarker,\n endMarker,\n blocks: new Map<string | number, KeyedBlock<T>>(),\n nextBlocks: new Map<string | number, KeyedBlock<T>>(),\n currentNodes: [startMarker, endMarker],\n nextNodes: [],\n orderedBlocks: [],\n nextOrderedBlocks: [],\n orderedIndexByKey: new Map<string | number, number>(),\n dispose,\n }\n\n return container\n}\n\n// ============================================================================\n// Block Creation Helpers\n// ============================================================================\n\n/**\n * Create a new keyed block with the given render function\n *\n * @param key - Unique key for this block\n * @param item - Initial item value\n * @param index - Initial index\n * @param render - Function that creates the DOM nodes and sets up bindings\n * @returns New KeyedBlock\n */\nfunction createKeyedBlock<T>(\n key: string | number,\n item: T,\n index: number,\n render: (item: Signal<T>, index: Signal<number>, key: string | number) => Node[],\n needsIndex = true,\n hostRoot?: RootContext,\n): KeyedBlock<T> {\n // Use versioned signal for all item types; avoid diffing proxy overhead for objects\n const itemSig = createVersionedSignalAccessor(item)\n\n const indexSig = needsIndex\n ? createSignal<number>(index)\n : (((next?: number) => {\n if (arguments.length === 0) return index\n index = next as number\n return index\n }) as Signal<number>)\n const root = createRootContext(hostRoot)\n const prevRoot = pushRoot(root)\n // maintaining proper cleanup chain. The scope will be disposed when\n // the root is destroyed, ensuring nested effects are properly cleaned up.\n let nodes: Node[] = []\n let scopeDispose: (() => void) | undefined\n\n // First, isolate from parent effect to prevent child effects from being\n // purged when the outer effect (e.g., performDiff) re-runs\n const prevSub = setActiveSub(undefined)\n\n try {\n // Create an effectScope that will track all effects created during render\n scopeDispose = effectScope(() => {\n const rendered = render(itemSig, indexSig, key)\n // If render returns real DOM nodes/arrays, preserve them to avoid\n // reparenting side-effects (tests may pre-insert them).\n if (\n rendered instanceof Node ||\n (Array.isArray(rendered) && rendered.every(n => n instanceof Node))\n ) {\n nodes = toNodeArray(rendered)\n } else {\n const element = createElement(rendered as unknown as FictNode)\n nodes = toNodeArray(element)\n }\n })\n\n // Register the scope cleanup with the root so effects are cleaned up\n // when the block is destroyed\n if (scopeDispose) {\n root.cleanups.push(scopeDispose)\n }\n } finally {\n setActiveSub(prevSub)\n popRoot(prevRoot)\n }\n\n return {\n key,\n nodes,\n root,\n item: itemSig,\n index: indexSig,\n rawItem: item,\n rawIndex: index,\n }\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Check if a node is between two markers\n */\nexport function isNodeBetweenMarkers(\n node: Node,\n startMarker: Comment,\n endMarker: Comment,\n): boolean {\n let current: Node | null = startMarker.nextSibling\n while (current && current !== endMarker) {\n if (current === node) return true\n current = current.nextSibling\n }\n return false\n}\n\nfunction reorderBySwap<T>(\n parent: ParentNode & Node,\n first: KeyedBlock<T>,\n second: KeyedBlock<T>,\n): boolean {\n if (first === second) return false\n const firstNodes = first.nodes\n const secondNodes = second.nodes\n if (firstNodes.length === 0 || secondNodes.length === 0) return false\n const lastFirst = firstNodes[firstNodes.length - 1]!\n const lastSecond = secondNodes[secondNodes.length - 1]!\n const afterFirst = lastFirst.nextSibling\n const afterSecond = lastSecond.nextSibling\n moveNodesBefore(parent, firstNodes, afterSecond)\n moveNodesBefore(parent, secondNodes, afterFirst)\n return true\n}\n\nfunction getLISIndices(sequence: number[]): number[] {\n const predecessors = new Array<number>(sequence.length)\n const result: number[] = []\n\n for (let i = 0; i < sequence.length; i++) {\n const value = sequence[i]!\n if (value < 0) {\n predecessors[i] = -1\n continue\n }\n\n let low = 0\n let high = result.length\n while (low < high) {\n const mid = (low + high) >> 1\n if (sequence[result[mid]!]! < value) {\n low = mid + 1\n } else {\n high = mid\n }\n }\n\n predecessors[i] = low > 0 ? result[low - 1]! : -1\n if (low === result.length) {\n result.push(i)\n } else {\n result[low] = i\n }\n }\n\n const lis: number[] = new Array(result.length)\n let k = result.length > 0 ? result[result.length - 1]! : -1\n for (let i = result.length - 1; i >= 0; i--) {\n lis[i] = k\n k = predecessors[k]!\n }\n return lis\n}\n\nfunction reorderByLIS<T>(\n parent: ParentNode & Node,\n endMarker: Comment,\n prev: KeyedBlock<T>[],\n next: KeyedBlock<T>[],\n): boolean {\n const positions = new Map<KeyedBlock<T>, number>()\n for (let i = 0; i < prev.length; i++) {\n positions.set(prev[i]!, i)\n }\n\n const sequence = new Array<number>(next.length)\n for (let i = 0; i < next.length; i++) {\n const position = positions.get(next[i]!)\n if (position === undefined) return false\n sequence[i] = position\n }\n\n const lisIndices = getLISIndices(sequence)\n if (lisIndices.length === sequence.length) return true\n\n const inLIS = new Array<boolean>(sequence.length).fill(false)\n for (let i = 0; i < lisIndices.length; i++) {\n inLIS[lisIndices[i]!] = true\n }\n\n let anchor: Node | null = endMarker\n let moved = false\n for (let i = next.length - 1; i >= 0; i--) {\n const block = next[i]!\n const nodes = block.nodes\n if (nodes.length === 0) continue\n if (inLIS[i]) {\n anchor = nodes[0]!\n continue\n }\n moveNodesBefore(parent, nodes, anchor)\n anchor = nodes[0]!\n moved = true\n }\n\n return moved\n}\n\n// ============================================================================\n// High-Level List Binding (for compiler-generated code)\n// ============================================================================\n\n/**\n * Create a keyed list binding with automatic diffing and DOM updates.\n * This is used by compiler-generated code for efficient list rendering.\n *\n * @param getItems - Function that returns the current array of items\n * @param keyFn - Function to extract unique key from each item\n * @param renderItem - Function that creates DOM nodes for each item\n * @returns Binding handle with markers and dispose function\n */\nexport function createKeyedList<T>(\n getItems: () => T[],\n keyFn: (item: T, index: number) => string | number,\n renderItem: FineGrainedRenderItem<T>,\n needsIndex?: boolean,\n startMarker?: Comment,\n endMarker?: Comment,\n): KeyedListBinding {\n const resolvedNeedsIndex =\n arguments.length >= 4 ? !!needsIndex : renderItem.length > 1 /* has index param */\n return createFineGrainedKeyedList(\n getItems,\n keyFn,\n renderItem,\n resolvedNeedsIndex,\n startMarker,\n endMarker,\n )\n}\n\nfunction createFineGrainedKeyedList<T>(\n getItems: () => T[],\n keyFn: (item: T, index: number) => string | number,\n renderItem: FineGrainedRenderItem<T>,\n needsIndex: boolean,\n startOverride?: Comment,\n endOverride?: Comment,\n): KeyedListBinding {\n const container = createKeyedListContainer<T>(startOverride, endOverride)\n const hostRoot = getCurrentRoot()\n const useProvided = !!(startOverride && endOverride)\n const fragment = useProvided ? container.startMarker : document.createDocumentFragment()\n if (!useProvided) {\n ;(fragment as DocumentFragment).append(container.startMarker, container.endMarker)\n }\n let disposed = false\n let effectDispose: (() => void) | undefined\n let connectObserver: MutationObserver | null = null\n let effectStarted = false\n let startScheduled = false\n let initialHydrating = __fictIsHydrating()\n\n const collectBetween = (): Node[] => {\n const nodes: Node[] = []\n let cursor = container.startMarker.nextSibling\n while (cursor && cursor !== container.endMarker) {\n nodes.push(cursor)\n cursor = cursor.nextSibling\n }\n return nodes\n }\n\n const getConnectedParent = (): (ParentNode & Node) | null => {\n const endParent = container.endMarker.parentNode\n const startParent = container.startMarker.parentNode\n if (\n endParent &&\n startParent &&\n endParent === startParent &&\n (endParent as Node).nodeType !== 11\n ) {\n const parentNode = endParent as ParentNode & Node\n if ('isConnected' in parentNode && !parentNode.isConnected) return null\n return parentNode\n }\n if (endParent && startParent && endParent === startParent && isShadowRoot(endParent as Node)) {\n const shadowRoot = endParent as ShadowRoot\n const host = shadowRoot.host\n if ('isConnected' in host && !host.isConnected) return null\n return shadowRoot as unknown as ParentNode & Node\n }\n return null\n }\n\n const performDiff = () => {\n if (disposed) return\n // During SSR, render synchronously without waiting for DOM connection\n const isSSR = __fictIsSSR()\n const parent = isSSR\n ? (container.startMarker.parentNode as (ParentNode & Node) | null)\n : getConnectedParent()\n if (!parent) return\n batch(() => {\n const oldBlocks = container.blocks\n const newBlocks = container.nextBlocks\n const prevOrderedBlocks = container.orderedBlocks\n const nextOrderedBlocks = container.nextOrderedBlocks\n const orderedIndexByKey = container.orderedIndexByKey\n const newItems = getItems()\n\n if (initialHydrating && isHydratingActive()) {\n initialHydrating = false\n newBlocks.clear()\n nextOrderedBlocks.length = 0\n orderedIndexByKey.clear()\n\n if (newItems.length === 0) {\n oldBlocks.clear()\n prevOrderedBlocks.length = 0\n container.currentNodes = [container.startMarker, container.endMarker]\n container.nextNodes.length = 0\n return\n }\n\n const createdBlocks: KeyedBlock<T>[] = []\n withHydrationRange(\n container.startMarker.nextSibling,\n container.endMarker,\n parent.ownerDocument ?? document,\n () => {\n for (let index = 0; index < newItems.length; index++) {\n const item = newItems[index]!\n const key = keyFn(item, index)\n if (newBlocks.has(key)) {\n if (isDev) {\n console.warn(\n `[fict] Duplicate key \"${String(key)}\" detected in list hydration. ` +\n `Each item should have a unique key.`,\n )\n }\n const existing = newBlocks.get(key)\n if (existing) {\n destroyRoot(existing.root)\n removeNodes(existing.nodes)\n }\n }\n const block = createKeyedBlock<T>(key, item, index, renderItem, needsIndex, hostRoot)\n createdBlocks.push(block)\n newBlocks.set(key, block)\n orderedIndexByKey.set(key, nextOrderedBlocks.length)\n nextOrderedBlocks.push(block)\n }\n },\n )\n\n container.blocks = newBlocks\n container.nextBlocks = oldBlocks\n container.orderedBlocks = nextOrderedBlocks\n container.nextOrderedBlocks = prevOrderedBlocks\n oldBlocks.clear()\n prevOrderedBlocks.length = 0\n container.currentNodes = [container.startMarker, ...collectBetween(), container.endMarker]\n container.nextNodes.length = 0\n\n for (const block of createdBlocks) {\n if (newBlocks.get(block.key) === block) {\n flushOnMount(block.root)\n }\n }\n\n return\n }\n\n if (newItems.length === 0) {\n if (oldBlocks.size > 0) {\n // Destroy all block roots first\n for (const block of oldBlocks.values()) {\n destroyRoot(block.root)\n }\n // Use Range.deleteContents for efficient bulk DOM removal\n const range = document.createRange()\n range.setStartAfter(container.startMarker)\n range.setEndBefore(container.endMarker)\n range.deleteContents()\n }\n oldBlocks.clear()\n newBlocks.clear()\n prevOrderedBlocks.length = 0\n nextOrderedBlocks.length = 0\n orderedIndexByKey.clear()\n container.currentNodes.length = 0\n container.currentNodes.push(container.startMarker, container.endMarker)\n container.nextNodes.length = 0\n return\n }\n\n const prevCount = prevOrderedBlocks.length\n if (prevCount > 0 && newItems.length === prevCount && orderedIndexByKey.size === prevCount) {\n let stableOrder = true\n const seen = new Set<string | number>()\n for (let i = 0; i < prevCount; i++) {\n const item = newItems[i]!\n const key = keyFn(item, i)\n if (seen.has(key) || prevOrderedBlocks[i]!.key !== key) {\n stableOrder = false\n break\n }\n seen.add(key)\n }\n if (stableOrder) {\n for (let i = 0; i < prevCount; i++) {\n const item = newItems[i]!\n const block = prevOrderedBlocks[i]!\n if (block.rawItem !== item) {\n block.rawItem = item\n block.item(item)\n }\n if (needsIndex && block.rawIndex !== i) {\n block.rawIndex = i\n block.index(i)\n }\n }\n return\n }\n }\n\n newBlocks.clear()\n nextOrderedBlocks.length = 0\n orderedIndexByKey.clear()\n const createdBlocks: KeyedBlock<T>[] = []\n let appendCandidate = prevCount > 0 && newItems.length >= prevCount\n const appendedBlocks: KeyedBlock<T>[] = []\n let mismatchCount = 0\n let mismatchFirst = -1\n let mismatchSecond = -1\n let hasDuplicateKey = false\n\n // Phase 1: Build new blocks map (reuse or create)\n newItems.forEach((item, index) => {\n const key = keyFn(item, index)\n // Micro-optimization: single Map.get instead of has+get\n let block = oldBlocks.get(key)\n const existed = block !== undefined\n\n if (block) {\n if (block.rawItem !== item) {\n block.rawItem = item\n block.item(item)\n }\n if (needsIndex && block.rawIndex !== index) {\n block.rawIndex = index\n block.index(index)\n }\n }\n\n if (block) {\n // Reusing existing block from oldBlocks\n newBlocks.set(key, block)\n oldBlocks.delete(key)\n } else {\n // If newBlocks already has this key (duplicate key case), clean up the previous block\n const existingBlock = newBlocks.get(key)\n if (existingBlock) {\n if (isDev) {\n console.warn(\n `[fict] Duplicate key \"${String(key)}\" detected in list rendering. ` +\n `Each item should have a unique key. The previous item with this key will be replaced.`,\n )\n }\n destroyRoot(existingBlock.root)\n removeNodes(existingBlock.nodes)\n }\n // Create new block\n block = createKeyedBlock<T>(key, item, index, renderItem, needsIndex, hostRoot)\n createdBlocks.push(block)\n }\n\n const resolvedBlock = block\n\n newBlocks.set(key, resolvedBlock)\n\n // Micro-optimization: single Map.get instead of checking position multiple times\n const position = orderedIndexByKey.get(key)\n if (position !== undefined) {\n appendCandidate = false\n hasDuplicateKey = true\n const prior = nextOrderedBlocks[position]\n if (prior && prior !== resolvedBlock) {\n destroyRoot(prior.root)\n removeNodes(prior.nodes)\n }\n nextOrderedBlocks[position] = resolvedBlock\n } else {\n if (appendCandidate) {\n if (index < prevCount) {\n if (!prevOrderedBlocks[index] || prevOrderedBlocks[index]!.key !== key) {\n appendCandidate = false\n }\n } else if (existed) {\n appendCandidate = false\n }\n }\n const nextIndex = nextOrderedBlocks.length\n orderedIndexByKey.set(key, nextIndex)\n nextOrderedBlocks.push(resolvedBlock)\n if (\n mismatchCount < 3 &&\n (nextIndex >= prevCount || prevOrderedBlocks[nextIndex] !== resolvedBlock)\n ) {\n if (mismatchCount === 0) {\n mismatchFirst = nextIndex\n } else if (mismatchCount === 1) {\n mismatchSecond = nextIndex\n }\n mismatchCount++\n }\n }\n\n if (appendCandidate && index >= prevCount) {\n appendedBlocks.push(resolvedBlock)\n }\n })\n\n const canAppend =\n appendCandidate &&\n prevCount > 0 &&\n newItems.length > prevCount &&\n oldBlocks.size === 0 &&\n appendedBlocks.length > 0\n if (canAppend) {\n const appendedNodes: Node[] = []\n for (const block of appendedBlocks) {\n for (let i = 0; i < block.nodes.length; i++) {\n appendedNodes.push(block.nodes[i]!)\n }\n }\n if (appendedNodes.length > 0) {\n insertNodesBefore(parent, appendedNodes, container.endMarker)\n const currentNodes = container.currentNodes\n currentNodes.pop()\n for (let i = 0; i < appendedNodes.length; i++) {\n currentNodes.push(appendedNodes[i]!)\n }\n currentNodes.push(container.endMarker)\n }\n\n container.blocks = newBlocks\n container.nextBlocks = oldBlocks\n container.orderedBlocks = nextOrderedBlocks\n container.nextOrderedBlocks = prevOrderedBlocks\n for (const block of createdBlocks) {\n if (newBlocks.get(block.key) === block) {\n flushOnMount(block.root)\n }\n }\n return\n }\n\n // Phase 2: Remove old blocks that are no longer in the list\n if (oldBlocks.size > 0) {\n for (const block of oldBlocks.values()) {\n destroyRoot(block.root)\n removeNodes(block.nodes)\n }\n oldBlocks.clear()\n }\n\n const canReorderInPlace =\n createdBlocks.length === 0 &&\n oldBlocks.size === 0 &&\n nextOrderedBlocks.length === prevOrderedBlocks.length\n\n let skipReconcile = false\n let updateNodeBuffer = true\n\n if (canReorderInPlace && nextOrderedBlocks.length > 0 && !hasDuplicateKey) {\n if (mismatchCount === 0) {\n skipReconcile = true\n updateNodeBuffer = false\n } else if (\n mismatchCount === 2 &&\n prevOrderedBlocks[mismatchFirst] === nextOrderedBlocks[mismatchSecond] &&\n prevOrderedBlocks[mismatchSecond] === nextOrderedBlocks[mismatchFirst]\n ) {\n if (\n reorderBySwap(\n parent,\n prevOrderedBlocks[mismatchFirst]!,\n prevOrderedBlocks[mismatchSecond]!,\n )\n ) {\n skipReconcile = true\n }\n } else if (\n reorderByLIS(parent, container.endMarker, prevOrderedBlocks, nextOrderedBlocks)\n ) {\n skipReconcile = true\n }\n }\n\n // Phase 3: Reconcile DOM with buffered node arrays\n if (!skipReconcile && (newBlocks.size > 0 || container.currentNodes.length > 0)) {\n const prevNodes = container.currentNodes\n const nextNodes = container.nextNodes\n nextNodes.length = 0\n nextNodes.push(container.startMarker)\n\n for (let i = 0; i < nextOrderedBlocks.length; i++) {\n const nodes = nextOrderedBlocks[i]!.nodes\n for (let j = 0; j < nodes.length; j++) {\n nextNodes.push(nodes[j]!)\n }\n }\n\n nextNodes.push(container.endMarker)\n\n reconcileArrays(parent, prevNodes, nextNodes)\n\n // Swap buffers to reuse arrays on next diff\n container.currentNodes = nextNodes\n container.nextNodes = prevNodes\n } else if (skipReconcile && updateNodeBuffer) {\n const prevNodes = container.currentNodes\n const nextNodes = container.nextNodes\n nextNodes.length = 0\n nextNodes.push(container.startMarker)\n for (let i = 0; i < nextOrderedBlocks.length; i++) {\n const nodes = nextOrderedBlocks[i]!.nodes\n for (let j = 0; j < nodes.length; j++) {\n nextNodes.push(nodes[j]!)\n }\n }\n nextNodes.push(container.endMarker)\n container.currentNodes = nextNodes\n container.nextNodes = prevNodes\n }\n\n // Swap block maps for reuse\n container.blocks = newBlocks\n container.nextBlocks = oldBlocks\n container.orderedBlocks = nextOrderedBlocks\n container.nextOrderedBlocks = prevOrderedBlocks\n for (const block of createdBlocks) {\n if (newBlocks.get(block.key) === block) {\n flushOnMount(block.root)\n }\n }\n })\n }\n\n const disconnectObserver = () => {\n connectObserver?.disconnect()\n connectObserver = null\n }\n\n const ensureEffectStarted = (): boolean => {\n if (disposed || effectStarted) return effectStarted\n // During SSR, render synchronously without waiting for DOM connection\n const isSSR = __fictIsSSR()\n const parent = isSSR\n ? (container.startMarker.parentNode as (ParentNode & Node) | null)\n : getConnectedParent()\n if (!parent) return false\n const start = () => {\n effectDispose = createRenderEffect(performDiff)\n effectStarted = true\n }\n if (hostRoot) {\n const prev = pushRoot(hostRoot)\n try {\n start()\n } finally {\n popRoot(prev)\n }\n } else {\n start()\n }\n return true\n }\n\n const waitForConnection = () => {\n if (connectObserver || typeof MutationObserver === 'undefined') return\n const root = container.startMarker.getRootNode?.() ?? document\n const shadowRoot =\n root && root.nodeType === 11 && isShadowRoot(root as Node) ? (root as ShadowRoot) : null\n connectObserver = new MutationObserver(() => {\n if (disposed) return\n if (getConnectedParent()) {\n disconnectObserver()\n if (ensureEffectStarted()) {\n flush()\n }\n }\n })\n connectObserver.observe(document, { childList: true, subtree: true })\n if (shadowRoot) {\n connectObserver.observe(shadowRoot, { childList: true, subtree: true })\n }\n }\n\n const scheduleStart = () => {\n if (startScheduled || disposed || effectStarted) return\n startScheduled = true\n const run = () => {\n startScheduled = false\n if (!ensureEffectStarted()) {\n waitForConnection()\n }\n }\n if (typeof queueMicrotask === 'function') {\n queueMicrotask(run)\n } else {\n Promise.resolve()\n .then(run)\n .catch(() => undefined)\n }\n }\n\n scheduleStart()\n\n return {\n get marker() {\n scheduleStart()\n return fragment\n },\n startMarker: container.startMarker,\n endMarker: container.endMarker,\n // Flush pending items - call after markers are inserted into DOM\n flush: () => {\n if (disposed) return\n scheduleStart()\n if (ensureEffectStarted()) {\n flush()\n } else {\n waitForConnection()\n }\n },\n dispose: () => {\n disposed = true\n effectDispose?.()\n disconnectObserver()\n container.dispose()\n },\n }\n}\n"],"mappings":"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","names":["render","createdBlocks"]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,71 @@
1
+ import { S as SignalAccessor } from './signal-C4ISF17w.js';
2
+
3
+ /**
4
+ * Low-level DOM node helpers shared across runtime modules.
5
+ * Keep this file dependency-free to avoid module cycles.
6
+ */
7
+ /**
8
+ * Convert a value to a flat array of DOM nodes.
9
+ * Defensively handles proxies and non-DOM values.
10
+ */
11
+ declare function toNodeArray(node: Node | Node[] | unknown): Node[];
12
+ /**
13
+ * Insert nodes before an anchor node, preserving order.
14
+ * Uses DocumentFragment for batch insertion when inserting multiple nodes.
15
+ */
16
+ declare function insertNodesBefore(parent: ParentNode & Node, nodes: Node[], anchor: Node | null): void;
17
+ /**
18
+ * Remove an array of nodes from the DOM.
19
+ */
20
+ declare function removeNodes(nodes: Node[]): void;
21
+ declare function getSlotEnd(start: Comment): Comment;
22
+ declare function resolvePath(root: Node, path: number[]): Node | null;
23
+
24
+ /**
25
+ * List Helpers for Compiler-Generated Fine-Grained Updates
26
+ *
27
+ * These helpers are used by the compiler to generate efficient keyed list rendering.
28
+ * They provide low-level primitives for DOM node manipulation without rebuilding.
29
+ */
30
+
31
+ /**
32
+ * Binding handle returned by createKeyedList for compiler-generated code
33
+ */
34
+ interface KeyedListBinding {
35
+ /** Document fragment placeholder inserted by the compiler/runtime */
36
+ marker: Comment | DocumentFragment;
37
+ /** Start marker comment node */
38
+ startMarker: Comment;
39
+ /** End marker comment node */
40
+ endMarker: Comment;
41
+ /** Flush pending items - call after markers are inserted into DOM */
42
+ flush?: () => void;
43
+ /** Cleanup function */
44
+ dispose: () => void;
45
+ }
46
+ type FineGrainedRenderItem<T> = (itemSig: SignalAccessor<T>, indexSig: SignalAccessor<number>, key: string | number) => Node[];
47
+ /**
48
+ * Move nodes to a position before the anchor node.
49
+ * This is optimized to avoid unnecessary DOM operations.
50
+ *
51
+ * @param parent - Parent node to move nodes within
52
+ * @param nodes - Array of nodes to move
53
+ * @param anchor - Node to insert before (or null for end)
54
+ */
55
+ declare function moveNodesBefore(parent: Node, nodes: Node[], anchor: Node | null): void;
56
+ /**
57
+ * Check if a node is between two markers
58
+ */
59
+ declare function isNodeBetweenMarkers(node: Node, startMarker: Comment, endMarker: Comment): boolean;
60
+ /**
61
+ * Create a keyed list binding with automatic diffing and DOM updates.
62
+ * This is used by compiler-generated code for efficient list rendering.
63
+ *
64
+ * @param getItems - Function that returns the current array of items
65
+ * @param keyFn - Function to extract unique key from each item
66
+ * @param renderItem - Function that creates DOM nodes for each item
67
+ * @returns Binding handle with markers and dispose function
68
+ */
69
+ declare function createKeyedList<T>(getItems: () => T[], keyFn: (item: T, index: number) => string | number, renderItem: FineGrainedRenderItem<T>, needsIndex?: boolean, startMarker?: Comment, endMarker?: Comment): KeyedListBinding;
70
+
71
+ export { type KeyedListBinding as K, removeNodes as a, isNodeBetweenMarkers as b, createKeyedList as c, getSlotEnd as g, insertNodesBefore as i, moveNodesBefore as m, resolvePath as r, toNodeArray as t };
@@ -0,0 +1,71 @@
1
+ import { S as SignalAccessor } from './signal-C4ISF17w.cjs';
2
+
3
+ /**
4
+ * Low-level DOM node helpers shared across runtime modules.
5
+ * Keep this file dependency-free to avoid module cycles.
6
+ */
7
+ /**
8
+ * Convert a value to a flat array of DOM nodes.
9
+ * Defensively handles proxies and non-DOM values.
10
+ */
11
+ declare function toNodeArray(node: Node | Node[] | unknown): Node[];
12
+ /**
13
+ * Insert nodes before an anchor node, preserving order.
14
+ * Uses DocumentFragment for batch insertion when inserting multiple nodes.
15
+ */
16
+ declare function insertNodesBefore(parent: ParentNode & Node, nodes: Node[], anchor: Node | null): void;
17
+ /**
18
+ * Remove an array of nodes from the DOM.
19
+ */
20
+ declare function removeNodes(nodes: Node[]): void;
21
+ declare function getSlotEnd(start: Comment): Comment;
22
+ declare function resolvePath(root: Node, path: number[]): Node | null;
23
+
24
+ /**
25
+ * List Helpers for Compiler-Generated Fine-Grained Updates
26
+ *
27
+ * These helpers are used by the compiler to generate efficient keyed list rendering.
28
+ * They provide low-level primitives for DOM node manipulation without rebuilding.
29
+ */
30
+
31
+ /**
32
+ * Binding handle returned by createKeyedList for compiler-generated code
33
+ */
34
+ interface KeyedListBinding {
35
+ /** Document fragment placeholder inserted by the compiler/runtime */
36
+ marker: Comment | DocumentFragment;
37
+ /** Start marker comment node */
38
+ startMarker: Comment;
39
+ /** End marker comment node */
40
+ endMarker: Comment;
41
+ /** Flush pending items - call after markers are inserted into DOM */
42
+ flush?: () => void;
43
+ /** Cleanup function */
44
+ dispose: () => void;
45
+ }
46
+ type FineGrainedRenderItem<T> = (itemSig: SignalAccessor<T>, indexSig: SignalAccessor<number>, key: string | number) => Node[];
47
+ /**
48
+ * Move nodes to a position before the anchor node.
49
+ * This is optimized to avoid unnecessary DOM operations.
50
+ *
51
+ * @param parent - Parent node to move nodes within
52
+ * @param nodes - Array of nodes to move
53
+ * @param anchor - Node to insert before (or null for end)
54
+ */
55
+ declare function moveNodesBefore(parent: Node, nodes: Node[], anchor: Node | null): void;
56
+ /**
57
+ * Check if a node is between two markers
58
+ */
59
+ declare function isNodeBetweenMarkers(node: Node, startMarker: Comment, endMarker: Comment): boolean;
60
+ /**
61
+ * Create a keyed list binding with automatic diffing and DOM updates.
62
+ * This is used by compiler-generated code for efficient list rendering.
63
+ *
64
+ * @param getItems - Function that returns the current array of items
65
+ * @param keyFn - Function to extract unique key from each item
66
+ * @param renderItem - Function that creates DOM nodes for each item
67
+ * @returns Binding handle with markers and dispose function
68
+ */
69
+ declare function createKeyedList<T>(getItems: () => T[], keyFn: (item: T, index: number) => string | number, renderItem: FineGrainedRenderItem<T>, needsIndex?: boolean, startMarker?: Comment, endMarker?: Comment): KeyedListBinding;
70
+
71
+ export { type KeyedListBinding as K, removeNodes as a, isNodeBetweenMarkers as b, createKeyedList as c, getSlotEnd as g, insertNodesBefore as i, moveNodesBefore as m, resolvePath as r, toNodeArray as t };
package/dist/loader.cjs CHANGED
@@ -7,7 +7,8 @@
7
7
 
8
8
 
9
9
 
10
- var _chunkFSCBL7RIcjs = require('./chunk-FSCBL7RI.cjs');
10
+
11
+ var _chunkUBFDB6OLcjs = require('./chunk-UBFDB6OL.cjs');
11
12
 
12
13
  // src/loader.ts
13
14
  function resolveModuleUrl(url) {
@@ -26,6 +27,8 @@ var prefetchCleanup = null;
26
27
  var eventListenerCleanup = null;
27
28
  var snapshotObserver = null;
28
29
  var processedSnapshots = /* @__PURE__ */ new Set();
30
+ var snapshotIssueHandler = null;
31
+ var emittedIssueKeys = /* @__PURE__ */ new Set();
29
32
  function resetHydratedScopes() {
30
33
  hydratedScopes.clear();
31
34
  }
@@ -46,9 +49,12 @@ function cleanupEventListeners() {
46
49
  function installResumableLoader(options = {}) {
47
50
  const doc = _nullishCoalesce(options.document, () => ( window.document));
48
51
  const scriptId = _nullishCoalesce(options.snapshotScriptId, () => ( "__FICT_SNAPSHOT__"));
52
+ snapshotIssueHandler = _nullishCoalesce(options.onSnapshotIssue, () => ( null));
49
53
  hydratedScopes.clear();
50
54
  prefetchedUrls.clear();
51
55
  processedSnapshots.clear();
56
+ emittedIssueKeys.clear();
57
+ _chunkUBFDB6OLcjs.__fictSetSSRState.call(void 0, null);
52
58
  if (eventListenerCleanup) {
53
59
  eventListenerCleanup();
54
60
  eventListenerCleanup = null;
@@ -63,10 +69,9 @@ function installResumableLoader(options = {}) {
63
69
  }
64
70
  const snapshotEl = doc.getElementById(scriptId);
65
71
  if (_optionalChain([snapshotEl, 'optionalAccess', _ => _.textContent])) {
66
- try {
67
- const state = JSON.parse(snapshotEl.textContent);
68
- _chunkFSCBL7RIcjs.__fictSetSSRState.call(void 0, state);
69
- } catch (e) {
72
+ const state = parseSnapshotText(snapshotEl.textContent, `#${scriptId}`);
73
+ if (state) {
74
+ _chunkUBFDB6OLcjs.__fictSetSSRState.call(void 0, state);
70
75
  }
71
76
  }
72
77
  const snapshotScripts = doc.querySelectorAll(
@@ -99,8 +104,8 @@ function installResumableLoader(options = {}) {
99
104
  });
100
105
  snapshotObserver.observe(_nullishCoalesce(doc.documentElement, () => ( doc)), { childList: true, subtree: true });
101
106
  }
102
- _chunkFSCBL7RIcjs.__fictEnableResumable.call(void 0, );
103
- const events = _nullishCoalesce(options.events, () => ( Array.from(_chunkFSCBL7RIcjs.DelegatedEvents)));
107
+ _chunkUBFDB6OLcjs.__fictEnableResumable.call(void 0, );
108
+ const events = _nullishCoalesce(options.events, () => ( Array.from(_chunkUBFDB6OLcjs.DelegatedEvents)));
104
109
  for (const eventName of events) {
105
110
  doc.addEventListener(eventName, handleResumableEvent, true);
106
111
  }
@@ -121,10 +126,76 @@ function parseSnapshotScript(script) {
121
126
  processedSnapshots.add(script);
122
127
  const text = script.textContent;
123
128
  if (!text) return;
129
+ const source = script.id ? `#${script.id}` : "<script[data-fict-snapshot]>";
130
+ const state = parseSnapshotText(text, source);
131
+ if (state) {
132
+ _chunkUBFDB6OLcjs.__fictMergeSSRState.call(void 0, state);
133
+ }
134
+ }
135
+ function parseSnapshotText(text, source) {
136
+ let parsed;
124
137
  try {
125
- const state = JSON.parse(text);
126
- _chunkFSCBL7RIcjs.__fictMergeSSRState.call(void 0, state);
127
- } catch (e2) {
138
+ parsed = JSON.parse(text);
139
+ } catch (e) {
140
+ emitSnapshotIssue({
141
+ code: "snapshot_parse_error",
142
+ message: "[fict/loader] Failed to parse SSR snapshot JSON.",
143
+ source,
144
+ expectedVersion: _chunkUBFDB6OLcjs.FICT_SSR_SNAPSHOT_SCHEMA_VERSION
145
+ });
146
+ return null;
147
+ }
148
+ return normalizeSnapshotState(parsed, source);
149
+ }
150
+ function normalizeSnapshotState(value, source) {
151
+ if (!isRecord(value)) {
152
+ emitSnapshotIssue({
153
+ code: "snapshot_invalid_shape",
154
+ message: "[fict/loader] Snapshot payload must be an object.",
155
+ source,
156
+ expectedVersion: _chunkUBFDB6OLcjs.FICT_SSR_SNAPSHOT_SCHEMA_VERSION
157
+ });
158
+ return null;
159
+ }
160
+ const rawVersion = value.v;
161
+ const version = rawVersion === void 0 ? _chunkUBFDB6OLcjs.FICT_SSR_SNAPSHOT_SCHEMA_VERSION : rawVersion;
162
+ if (!Number.isInteger(version) || version !== _chunkUBFDB6OLcjs.FICT_SSR_SNAPSHOT_SCHEMA_VERSION) {
163
+ const versionIssue = {
164
+ code: "snapshot_unsupported_version",
165
+ message: `[fict/loader] Snapshot schema version ${String(version)} is not supported by this runtime.`,
166
+ source,
167
+ expectedVersion: _chunkUBFDB6OLcjs.FICT_SSR_SNAPSHOT_SCHEMA_VERSION
168
+ };
169
+ if (typeof version === "number") {
170
+ versionIssue.actualVersion = version;
171
+ }
172
+ emitSnapshotIssue({
173
+ ...versionIssue
174
+ });
175
+ return null;
176
+ }
177
+ const scopes = value.scopes;
178
+ if (!isRecord(scopes)) {
179
+ emitSnapshotIssue({
180
+ code: "snapshot_invalid_shape",
181
+ message: "[fict/loader] Snapshot payload is missing a valid `scopes` object.",
182
+ source,
183
+ expectedVersion: _chunkUBFDB6OLcjs.FICT_SSR_SNAPSHOT_SCHEMA_VERSION
184
+ });
185
+ return null;
186
+ }
187
+ return { v: _chunkUBFDB6OLcjs.FICT_SSR_SNAPSHOT_SCHEMA_VERSION, scopes };
188
+ }
189
+ function isRecord(value) {
190
+ return typeof value === "object" && value !== null && !Array.isArray(value);
191
+ }
192
+ function emitSnapshotIssue(issue) {
193
+ const key = `${issue.code}|${issue.source}|${_nullishCoalesce(issue.scopeId, () => ( ""))}|${_nullishCoalesce(issue.actualVersion, () => ( ""))}|${issue.expectedVersion}`;
194
+ if (emittedIssueKeys.has(key)) return;
195
+ emittedIssueKeys.add(key);
196
+ _optionalChain([snapshotIssueHandler, 'optionalCall', _4 => _4(issue)]);
197
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
198
+ console.warn(issue.message);
128
199
  }
129
200
  }
130
201
  function setupPrefetch(doc, strategy) {
@@ -261,10 +332,18 @@ async function handleResumableEventAsync(event) {
261
332
  if (!host) continue;
262
333
  const scopeId = host.getAttribute("data-fict-s");
263
334
  if (!scopeId) continue;
264
- const snapshot = _chunkFSCBL7RIcjs.__fictGetSSRScope.call(void 0, scopeId);
265
- if (snapshot) {
266
- _chunkFSCBL7RIcjs.__fictEnsureScope.call(void 0, scopeId, host, snapshot);
335
+ const snapshot = _chunkUBFDB6OLcjs.__fictGetSSRScope.call(void 0, scopeId);
336
+ if (!snapshot) {
337
+ emitSnapshotIssue({
338
+ code: "scope_snapshot_missing",
339
+ message: `[fict/loader] Missing scope snapshot for ${scopeId}; skipping resumable handler execution.`,
340
+ source: "event",
341
+ expectedVersion: _chunkUBFDB6OLcjs.FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
342
+ scopeId
343
+ });
344
+ return;
267
345
  }
346
+ _chunkUBFDB6OLcjs.__fictEnsureScope.call(void 0, scopeId, host, snapshot);
268
347
  const { url, exportName } = parseQrl(qrl);
269
348
  if (event.cancelable && (event.type === "click" || event.type === "submit")) {
270
349
  const tag = node.tagName.toLowerCase();
@@ -281,7 +360,7 @@ async function handleResumableEventAsync(event) {
281
360
  /* @vite-ignore */
282
361
  resolvedResumeUrl
283
362
  )));
284
- const resumeFn = _chunkFSCBL7RIcjs.__fictGetResume.call(void 0, resumeExport);
363
+ const resumeFn = _chunkUBFDB6OLcjs.__fictGetResume.call(void 0, resumeExport);
285
364
  if (typeof resumeFn === "function") {
286
365
  await resumeFn(scopeId, host);
287
366
  hydratedScopes.add(scopeId);
@@ -328,5 +407,5 @@ function buildEventPath(event) {
328
407
 
329
408
 
330
409
 
331
- exports.__fictUseLexicalScope = _chunkFSCBL7RIcjs.__fictUseLexicalScope; exports.cleanupEventListeners = cleanupEventListeners; exports.installResumableLoader = installResumableLoader; exports.resetHydratedScopes = resetHydratedScopes; exports.resetPrefetchedUrls = resetPrefetchedUrls; exports.waitForPendingHandlers = waitForPendingHandlers;
410
+ exports.__fictUseLexicalScope = _chunkUBFDB6OLcjs.__fictUseLexicalScope; exports.cleanupEventListeners = cleanupEventListeners; exports.installResumableLoader = installResumableLoader; exports.resetHydratedScopes = resetHydratedScopes; exports.resetPrefetchedUrls = resetPrefetchedUrls; exports.waitForPendingHandlers = waitForPendingHandlers;
332
411
  //# sourceMappingURL=loader.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/fict/fict/packages/runtime/dist/loader.cjs","../src/loader.ts"],"names":[],"mappings":"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","file":"/home/runner/work/fict/fict/packages/runtime/dist/loader.cjs","sourcesContent":[null,"import { DelegatedEvents } from './constants'\nimport {\n __fictEnableResumable,\n __fictEnsureScope,\n __fictGetResume,\n __fictGetSSRScope,\n __fictMergeSSRState,\n __fictSetSSRState,\n __fictUseLexicalScope,\n} from './resume'\n\n// ============================================================================\n// Module Resolution\n// ============================================================================\n\n/**\n * Resolve a module URL through the manifest if available.\n * In production, virtual module URLs (virtual:fict-handler:...) are mapped\n * to their built chunk URLs through the manifest.\n */\nfunction resolveModuleUrl(url: string): string {\n const manifest = (globalThis as Record<string, unknown>).__FICT_MANIFEST__ as\n | Record<string, string>\n | undefined\n\n if (manifest) {\n // Check if the URL (without #fragment) is in the manifest\n const resolved = manifest[url]\n if (resolved) {\n return resolved\n }\n }\n\n return url\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PrefetchStrategy {\n /**\n * Enable visibility-based prefetch using IntersectionObserver.\n * Prefetches modules when interactive elements come into view.\n * @default true\n */\n visibility?: boolean\n /**\n * Root margin for IntersectionObserver (e.g., '200px' to prefetch earlier).\n * @default '200px'\n */\n visibilityMargin?: string\n /**\n * Enable hover-based prefetch using pointerover events.\n * Prefetches modules when user hovers over interactive elements.\n * @default true\n */\n hover?: boolean\n /**\n * Delay in ms before prefetching on hover (debounce rapid movements).\n * @default 50\n */\n hoverDelay?: number\n}\n\nexport interface ResumableLoaderOptions {\n document?: Document\n snapshotScriptId?: string\n events?: string[]\n /**\n * Prefetch strategy configuration.\n * Set to false to disable all prefetching.\n * @default { visibility: true, hover: true }\n */\n prefetch?: PrefetchStrategy | false\n}\n\n// ============================================================================\n// State\n// ============================================================================\n\nconst hydratedScopes = new Set<string>()\nconst prefetchedUrls = new Set<string>()\nlet prefetchCleanup: (() => void) | null = null\nlet eventListenerCleanup: (() => void) | null = null\nlet snapshotObserver: MutationObserver | null = null\nconst processedSnapshots = new Set<HTMLScriptElement>()\n\n/**\n * Reset the hydrated scopes set. Useful for testing.\n */\nexport function resetHydratedScopes(): void {\n hydratedScopes.clear()\n}\n\n/**\n * Reset the prefetched URLs set. Useful for testing.\n */\nexport function resetPrefetchedUrls(): void {\n prefetchedUrls.clear()\n}\n\n/**\n * Set of pending handler promises. Used for testing to wait for all handlers to complete.\n */\nconst pendingHandlers = new Set<Promise<void>>()\n\n/**\n * Wait for all pending event handlers to complete. Useful for testing.\n */\nexport async function waitForPendingHandlers(): Promise<void> {\n if (pendingHandlers.size === 0) return\n await Promise.allSettled([...pendingHandlers])\n}\n\n/**\n * Clean up all registered event listeners. Useful for testing.\n */\nexport function cleanupEventListeners(): void {\n if (eventListenerCleanup) {\n eventListenerCleanup()\n eventListenerCleanup = null\n }\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\nexport function installResumableLoader(options: ResumableLoaderOptions = {}): void {\n const doc = options.document ?? window.document\n const scriptId = options.snapshotScriptId ?? '__FICT_SNAPSHOT__'\n\n // Reset hydrated scopes for fresh loader installation\n hydratedScopes.clear()\n prefetchedUrls.clear()\n processedSnapshots.clear()\n\n // Clean up previous event listeners\n if (eventListenerCleanup) {\n eventListenerCleanup()\n eventListenerCleanup = null\n }\n\n // Clean up previous prefetch handlers\n if (prefetchCleanup) {\n prefetchCleanup()\n prefetchCleanup = null\n }\n\n if (snapshotObserver) {\n snapshotObserver.disconnect()\n snapshotObserver = null\n }\n\n const snapshotEl = doc.getElementById(scriptId)\n if (snapshotEl?.textContent) {\n try {\n const state = JSON.parse(snapshotEl.textContent)\n __fictSetSSRState(state)\n } catch {\n // Ignore parse errors\n }\n }\n\n const snapshotScripts = doc.querySelectorAll(\n 'script[type=\"application/json\"][data-fict-snapshot]',\n )\n for (const script of Array.from(snapshotScripts)) {\n parseSnapshotScript(script as HTMLScriptElement)\n }\n\n if (typeof MutationObserver !== 'undefined') {\n snapshotObserver = new MutationObserver(mutations => {\n for (const mutation of mutations) {\n for (const node of Array.from(mutation.addedNodes)) {\n if (!(node instanceof Element)) continue\n if (node.tagName === 'SCRIPT') {\n const script = node as HTMLScriptElement\n if (isSnapshotScript(script)) {\n parseSnapshotScript(script)\n }\n }\n const nested = node.querySelectorAll?.(\n 'script[type=\"application/json\"][data-fict-snapshot]',\n )\n if (nested && nested.length) {\n for (const script of Array.from(nested)) {\n parseSnapshotScript(script as HTMLScriptElement)\n }\n }\n }\n }\n })\n snapshotObserver.observe(doc.documentElement ?? doc, { childList: true, subtree: true })\n }\n\n __fictEnableResumable()\n\n const events = options.events ?? Array.from(DelegatedEvents)\n for (const eventName of events) {\n doc.addEventListener(eventName, handleResumableEvent, true)\n }\n\n // Store cleanup function for event listeners\n eventListenerCleanup = () => {\n for (const eventName of events) {\n doc.removeEventListener(eventName, handleResumableEvent, true)\n }\n }\n\n // Setup prefetch if enabled\n if (options.prefetch !== false) {\n prefetchCleanup = setupPrefetch(doc, options.prefetch ?? {})\n }\n}\n\nfunction isSnapshotScript(script: HTMLScriptElement): boolean {\n return script.type === 'application/json' && script.hasAttribute('data-fict-snapshot')\n}\n\nfunction parseSnapshotScript(script: HTMLScriptElement): void {\n if (processedSnapshots.has(script)) return\n processedSnapshots.add(script)\n const text = script.textContent\n if (!text) return\n try {\n const state = JSON.parse(text)\n __fictMergeSSRState(state)\n } catch {\n // Ignore parse errors\n }\n}\n\n// ============================================================================\n// Prefetch Implementation\n// ============================================================================\n\nfunction setupPrefetch(doc: Document, strategy: PrefetchStrategy): () => void {\n const cleanupFns: (() => void)[] = []\n\n // Visibility-based prefetch\n if (strategy.visibility !== false) {\n const cleanup = setupVisibilityPrefetch(doc, strategy.visibilityMargin ?? '200px')\n cleanupFns.push(cleanup)\n }\n\n // Hover-based prefetch\n if (strategy.hover !== false) {\n const cleanup = setupHoverPrefetch(doc, strategy.hoverDelay ?? 50)\n cleanupFns.push(cleanup)\n }\n\n return () => {\n for (const cleanup of cleanupFns) {\n cleanup()\n }\n }\n}\n\nfunction setupVisibilityPrefetch(doc: Document, rootMargin: string): () => void {\n // Check if IntersectionObserver is available\n if (typeof IntersectionObserver === 'undefined') {\n return () => {}\n }\n\n const observer = new IntersectionObserver(\n entries => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const el = entry.target as Element\n prefetchElementQrls(el)\n // Stop observing after prefetch\n observer.unobserve(el)\n }\n }\n },\n { rootMargin },\n )\n\n // Observe all elements with on:* attributes\n const interactiveElements = doc.querySelectorAll(\n '[on\\\\:click], [on\\\\:input], [on\\\\:change], [on\\\\:submit], [on\\\\:keydown], [on\\\\:keyup]',\n )\n interactiveElements.forEach(el => observer.observe(el))\n\n // Also observe elements with data-fict-h (resumable components)\n const resumableHosts = doc.querySelectorAll('[data-fict-h]')\n resumableHosts.forEach(el => observer.observe(el))\n\n return () => {\n observer.disconnect()\n }\n}\n\nfunction setupHoverPrefetch(doc: Document, delay: number): () => void {\n let hoverTimeout: ReturnType<typeof setTimeout> | null = null\n let lastHoveredElement: Element | null = null\n\n const handlePointerOver = (event: Event) => {\n const target = event.target\n if (!(target instanceof Element)) return\n\n // Find the closest element with interactive attributes\n const interactiveEl =\n target.closest('[on\\\\:click]') ||\n target.closest('[on\\\\:input]') ||\n target.closest('[on\\\\:change]') ||\n target.closest('[on\\\\:submit]') ||\n target.closest('[data-fict-h]')\n\n if (!interactiveEl || interactiveEl === lastHoveredElement) return\n\n lastHoveredElement = interactiveEl\n\n // Clear previous timeout\n if (hoverTimeout) {\n clearTimeout(hoverTimeout)\n }\n\n // Debounce prefetch\n hoverTimeout = setTimeout(() => {\n prefetchElementQrls(interactiveEl)\n }, delay)\n }\n\n const handlePointerOut = () => {\n if (hoverTimeout) {\n clearTimeout(hoverTimeout)\n hoverTimeout = null\n }\n lastHoveredElement = null\n }\n\n doc.addEventListener('pointerover', handlePointerOver, { passive: true })\n doc.addEventListener('pointerout', handlePointerOut, { passive: true })\n\n return () => {\n doc.removeEventListener('pointerover', handlePointerOver)\n doc.removeEventListener('pointerout', handlePointerOut)\n if (hoverTimeout) {\n clearTimeout(hoverTimeout)\n }\n }\n}\n\nfunction prefetchElementQrls(el: Element): void {\n // Prefetch event handler QRLs\n const eventAttrs = ['on:click', 'on:input', 'on:change', 'on:submit', 'on:keydown', 'on:keyup']\n for (const attr of eventAttrs) {\n const qrl = el.getAttribute(attr)\n if (qrl) {\n prefetchQrl(qrl)\n }\n }\n\n // Prefetch resume handler QRL\n const resumeQrl = el.getAttribute('data-fict-h')\n if (resumeQrl) {\n prefetchQrl(resumeQrl)\n }\n\n // Also check children for nested QRLs\n const children = el.querySelectorAll(\n '[on\\\\:click], [on\\\\:input], [on\\\\:change], [on\\\\:submit], [data-fict-h]',\n )\n children.forEach(child => {\n for (const attr of eventAttrs) {\n const qrl = child.getAttribute(attr)\n if (qrl) {\n prefetchQrl(qrl)\n }\n }\n const childResumeQrl = child.getAttribute('data-fict-h')\n if (childResumeQrl) {\n prefetchQrl(childResumeQrl)\n }\n })\n}\n\nfunction prefetchQrl(qrl: string): void {\n const { url } = parseQrl(qrl)\n if (!url || prefetchedUrls.has(url)) return\n\n prefetchedUrls.add(url)\n\n // Resolve through manifest for production builds\n const resolvedUrl = resolveModuleUrl(url)\n\n // Use modulepreload link for best browser support\n if (typeof document !== 'undefined') {\n const link = document.createElement('link')\n link.rel = 'modulepreload'\n link.href = resolvedUrl\n link.crossOrigin = 'anonymous'\n document.head.appendChild(link)\n }\n}\n\n// ============================================================================\n\n/**\n * Wrapper that tracks the async handler promise for testing.\n */\nfunction handleResumableEvent(event: Event): void {\n const promise = handleResumableEventAsync(event)\n pendingHandlers.add(promise)\n promise.finally(() => {\n pendingHandlers.delete(promise)\n })\n}\n\nasync function handleResumableEventAsync(event: Event): Promise<void> {\n const path =\n typeof event.composedPath === 'function' ? event.composedPath() : buildEventPath(event)\n\n for (const node of path) {\n if (!(node instanceof Element)) continue\n const qrl = node.getAttribute(`on:${event.type}`)\n if (!qrl) continue\n\n const host = node.closest('[data-fict-s]') as Element | null\n if (!host) continue\n const scopeId = host.getAttribute('data-fict-s')\n if (!scopeId) continue\n\n const snapshot = __fictGetSSRScope(scopeId)\n if (snapshot) {\n __fictEnsureScope(scopeId, host, snapshot)\n }\n\n const { url, exportName } = parseQrl(qrl)\n\n // Pre-emptively prevent default on navigations/forms while we await modules\n if (event.cancelable && (event.type === 'click' || event.type === 'submit')) {\n const tag = node.tagName.toLowerCase()\n if (tag === 'a' || tag === 'form') {\n event.preventDefault()\n }\n }\n\n // Resume FIRST to set up reactive bindings BEFORE the handler runs\n if (!hydratedScopes.has(scopeId)) {\n const resumeQrl = host.getAttribute('data-fict-h')\n if (resumeQrl) {\n const { url: resumeUrl, exportName: resumeExport } = parseQrl(resumeQrl)\n const resolvedResumeUrl = resolveModuleUrl(resumeUrl)\n // Load the module to ensure resume functions are registered\n await import(/* @vite-ignore */ resolvedResumeUrl)\n // Get resume function from registry (not module exports)\n const resumeFn = __fictGetResume(resumeExport)\n if (typeof resumeFn === 'function') {\n await (resumeFn as (scopeId: string, host: Element) => unknown)(scopeId, host)\n hydratedScopes.add(scopeId)\n }\n }\n }\n\n // THEN run the handler - now signal updates will trigger DOM updates\n const resolvedUrl = resolveModuleUrl(url)\n const mod = await import(/* @vite-ignore */ resolvedUrl)\n const handler = (mod as Record<string, unknown>)[exportName]\n if (typeof handler === 'function') {\n await (handler as (scopeId: string, ev: Event, el: Element) => unknown)(scopeId, event, node)\n }\n\n return\n }\n}\n\nfunction parseQrl(qrl: string): { url: string; exportName: string } {\n const [ref] = qrl.split('[')\n if (!ref) {\n return { url: '', exportName: 'default' }\n }\n const hashIndex = ref.lastIndexOf('#')\n if (hashIndex === -1) {\n return { url: ref, exportName: 'default' }\n }\n return { url: ref.slice(0, hashIndex), exportName: ref.slice(hashIndex + 1) }\n}\n\nfunction buildEventPath(event: Event): EventTarget[] {\n const path: EventTarget[] = []\n let node: EventTarget | null = event.target\n while (node) {\n path.push(node)\n node = (node as Node).parentNode\n }\n path.push(window)\n return path\n}\n\n// Re-export for handler authors (optional)\nexport { __fictUseLexicalScope } from './resume'\n"]}
1
+ {"version":3,"sources":["/home/runner/work/fict/fict/packages/runtime/dist/loader.cjs","../src/loader.ts"],"names":[],"mappings":"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","file":"/home/runner/work/fict/fict/packages/runtime/dist/loader.cjs","sourcesContent":[null,"import { DelegatedEvents } from './constants'\nimport {\n FICT_SSR_SNAPSHOT_SCHEMA_VERSION,\n type SSRState,\n __fictEnableResumable,\n __fictEnsureScope,\n __fictGetResume,\n __fictGetSSRScope,\n __fictMergeSSRState,\n __fictSetSSRState,\n __fictUseLexicalScope,\n} from './resume'\n\n// ============================================================================\n// Module Resolution\n// ============================================================================\n\n/**\n * Resolve a module URL through the manifest if available.\n * In production, virtual module URLs (virtual:fict-handler:...) are mapped\n * to their built chunk URLs through the manifest.\n */\nfunction resolveModuleUrl(url: string): string {\n const manifest = (globalThis as Record<string, unknown>).__FICT_MANIFEST__ as\n | Record<string, string>\n | undefined\n\n if (manifest) {\n // Check if the URL (without #fragment) is in the manifest\n const resolved = manifest[url]\n if (resolved) {\n return resolved\n }\n }\n\n return url\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PrefetchStrategy {\n /**\n * Enable visibility-based prefetch using IntersectionObserver.\n * Prefetches modules when interactive elements come into view.\n * @default true\n */\n visibility?: boolean\n /**\n * Root margin for IntersectionObserver (e.g., '200px' to prefetch earlier).\n * @default '200px'\n */\n visibilityMargin?: string\n /**\n * Enable hover-based prefetch using pointerover events.\n * Prefetches modules when user hovers over interactive elements.\n * @default true\n */\n hover?: boolean\n /**\n * Delay in ms before prefetching on hover (debounce rapid movements).\n * @default 50\n */\n hoverDelay?: number\n}\n\nexport interface ResumableLoaderOptions {\n document?: Document\n snapshotScriptId?: string\n events?: string[]\n /**\n * Receives structured snapshot/resume issues detected by the loader.\n * Useful for telemetry and fail-safe fallback orchestration.\n */\n onSnapshotIssue?: (issue: SnapshotIssue) => void\n /**\n * Prefetch strategy configuration.\n * Set to false to disable all prefetching.\n * @default { visibility: true, hover: true }\n */\n prefetch?: PrefetchStrategy | false\n}\n\nexport type SnapshotIssueCode =\n | 'snapshot_parse_error'\n | 'snapshot_invalid_shape'\n | 'snapshot_unsupported_version'\n | 'scope_snapshot_missing'\n\nexport interface SnapshotIssue {\n code: SnapshotIssueCode\n message: string\n source: string\n expectedVersion: number\n actualVersion?: number\n scopeId?: string\n}\n\n// ============================================================================\n// State\n// ============================================================================\n\nconst hydratedScopes = new Set<string>()\nconst prefetchedUrls = new Set<string>()\nlet prefetchCleanup: (() => void) | null = null\nlet eventListenerCleanup: (() => void) | null = null\nlet snapshotObserver: MutationObserver | null = null\nconst processedSnapshots = new Set<HTMLScriptElement>()\nlet snapshotIssueHandler: ((issue: SnapshotIssue) => void) | null = null\nconst emittedIssueKeys = new Set<string>()\n\n/**\n * Reset the hydrated scopes set. Useful for testing.\n */\nexport function resetHydratedScopes(): void {\n hydratedScopes.clear()\n}\n\n/**\n * Reset the prefetched URLs set. Useful for testing.\n */\nexport function resetPrefetchedUrls(): void {\n prefetchedUrls.clear()\n}\n\n/**\n * Set of pending handler promises. Used for testing to wait for all handlers to complete.\n */\nconst pendingHandlers = new Set<Promise<void>>()\n\n/**\n * Wait for all pending event handlers to complete. Useful for testing.\n */\nexport async function waitForPendingHandlers(): Promise<void> {\n if (pendingHandlers.size === 0) return\n await Promise.allSettled([...pendingHandlers])\n}\n\n/**\n * Clean up all registered event listeners. Useful for testing.\n */\nexport function cleanupEventListeners(): void {\n if (eventListenerCleanup) {\n eventListenerCleanup()\n eventListenerCleanup = null\n }\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\nexport function installResumableLoader(options: ResumableLoaderOptions = {}): void {\n const doc = options.document ?? window.document\n const scriptId = options.snapshotScriptId ?? '__FICT_SNAPSHOT__'\n snapshotIssueHandler = options.onSnapshotIssue ?? null\n\n // Reset hydrated scopes for fresh loader installation\n hydratedScopes.clear()\n prefetchedUrls.clear()\n processedSnapshots.clear()\n emittedIssueKeys.clear()\n __fictSetSSRState(null)\n\n // Clean up previous event listeners\n if (eventListenerCleanup) {\n eventListenerCleanup()\n eventListenerCleanup = null\n }\n\n // Clean up previous prefetch handlers\n if (prefetchCleanup) {\n prefetchCleanup()\n prefetchCleanup = null\n }\n\n if (snapshotObserver) {\n snapshotObserver.disconnect()\n snapshotObserver = null\n }\n\n const snapshotEl = doc.getElementById(scriptId)\n if (snapshotEl?.textContent) {\n const state = parseSnapshotText(snapshotEl.textContent, `#${scriptId}`)\n if (state) {\n __fictSetSSRState(state)\n }\n }\n\n const snapshotScripts = doc.querySelectorAll(\n 'script[type=\"application/json\"][data-fict-snapshot]',\n )\n for (const script of Array.from(snapshotScripts)) {\n parseSnapshotScript(script as HTMLScriptElement)\n }\n\n if (typeof MutationObserver !== 'undefined') {\n snapshotObserver = new MutationObserver(mutations => {\n for (const mutation of mutations) {\n for (const node of Array.from(mutation.addedNodes)) {\n if (!(node instanceof Element)) continue\n if (node.tagName === 'SCRIPT') {\n const script = node as HTMLScriptElement\n if (isSnapshotScript(script)) {\n parseSnapshotScript(script)\n }\n }\n const nested = node.querySelectorAll?.(\n 'script[type=\"application/json\"][data-fict-snapshot]',\n )\n if (nested && nested.length) {\n for (const script of Array.from(nested)) {\n parseSnapshotScript(script as HTMLScriptElement)\n }\n }\n }\n }\n })\n snapshotObserver.observe(doc.documentElement ?? doc, { childList: true, subtree: true })\n }\n\n __fictEnableResumable()\n\n const events = options.events ?? Array.from(DelegatedEvents)\n for (const eventName of events) {\n doc.addEventListener(eventName, handleResumableEvent, true)\n }\n\n // Store cleanup function for event listeners\n eventListenerCleanup = () => {\n for (const eventName of events) {\n doc.removeEventListener(eventName, handleResumableEvent, true)\n }\n }\n\n // Setup prefetch if enabled\n if (options.prefetch !== false) {\n prefetchCleanup = setupPrefetch(doc, options.prefetch ?? {})\n }\n}\n\nfunction isSnapshotScript(script: HTMLScriptElement): boolean {\n return script.type === 'application/json' && script.hasAttribute('data-fict-snapshot')\n}\n\nfunction parseSnapshotScript(script: HTMLScriptElement): void {\n if (processedSnapshots.has(script)) return\n processedSnapshots.add(script)\n const text = script.textContent\n if (!text) return\n const source = script.id ? `#${script.id}` : '<script[data-fict-snapshot]>'\n const state = parseSnapshotText(text, source)\n if (state) {\n __fictMergeSSRState(state)\n }\n}\n\nfunction parseSnapshotText(text: string, source: string): SSRState | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch {\n emitSnapshotIssue({\n code: 'snapshot_parse_error',\n message: '[fict/loader] Failed to parse SSR snapshot JSON.',\n source,\n expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,\n })\n return null\n }\n\n return normalizeSnapshotState(parsed, source)\n}\n\nfunction normalizeSnapshotState(value: unknown, source: string): SSRState | null {\n if (!isRecord(value)) {\n emitSnapshotIssue({\n code: 'snapshot_invalid_shape',\n message: '[fict/loader] Snapshot payload must be an object.',\n source,\n expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,\n })\n return null\n }\n\n const rawVersion = value.v\n const version = rawVersion === undefined ? FICT_SSR_SNAPSHOT_SCHEMA_VERSION : rawVersion\n if (!Number.isInteger(version) || version !== FICT_SSR_SNAPSHOT_SCHEMA_VERSION) {\n const versionIssue: SnapshotIssue = {\n code: 'snapshot_unsupported_version',\n message: `[fict/loader] Snapshot schema version ${String(version)} is not supported by this runtime.`,\n source,\n expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,\n }\n if (typeof version === 'number') {\n versionIssue.actualVersion = version\n }\n emitSnapshotIssue({\n ...versionIssue,\n })\n return null\n }\n\n const scopes = value.scopes\n if (!isRecord(scopes)) {\n emitSnapshotIssue({\n code: 'snapshot_invalid_shape',\n message: '[fict/loader] Snapshot payload is missing a valid `scopes` object.',\n source,\n expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,\n })\n return null\n }\n\n return { v: FICT_SSR_SNAPSHOT_SCHEMA_VERSION, scopes: scopes as SSRState['scopes'] }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction emitSnapshotIssue(issue: SnapshotIssue): void {\n const key =\n `${issue.code}|${issue.source}|${issue.scopeId ?? ''}|` +\n `${issue.actualVersion ?? ''}|${issue.expectedVersion}`\n if (emittedIssueKeys.has(key)) return\n emittedIssueKeys.add(key)\n\n snapshotIssueHandler?.(issue)\n\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(issue.message)\n }\n}\n\n// ============================================================================\n// Prefetch Implementation\n// ============================================================================\n\nfunction setupPrefetch(doc: Document, strategy: PrefetchStrategy): () => void {\n const cleanupFns: (() => void)[] = []\n\n // Visibility-based prefetch\n if (strategy.visibility !== false) {\n const cleanup = setupVisibilityPrefetch(doc, strategy.visibilityMargin ?? '200px')\n cleanupFns.push(cleanup)\n }\n\n // Hover-based prefetch\n if (strategy.hover !== false) {\n const cleanup = setupHoverPrefetch(doc, strategy.hoverDelay ?? 50)\n cleanupFns.push(cleanup)\n }\n\n return () => {\n for (const cleanup of cleanupFns) {\n cleanup()\n }\n }\n}\n\nfunction setupVisibilityPrefetch(doc: Document, rootMargin: string): () => void {\n // Check if IntersectionObserver is available\n if (typeof IntersectionObserver === 'undefined') {\n return () => {}\n }\n\n const observer = new IntersectionObserver(\n entries => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const el = entry.target as Element\n prefetchElementQrls(el)\n // Stop observing after prefetch\n observer.unobserve(el)\n }\n }\n },\n { rootMargin },\n )\n\n // Observe all elements with on:* attributes\n const interactiveElements = doc.querySelectorAll(\n '[on\\\\:click], [on\\\\:input], [on\\\\:change], [on\\\\:submit], [on\\\\:keydown], [on\\\\:keyup]',\n )\n interactiveElements.forEach(el => observer.observe(el))\n\n // Also observe elements with data-fict-h (resumable components)\n const resumableHosts = doc.querySelectorAll('[data-fict-h]')\n resumableHosts.forEach(el => observer.observe(el))\n\n return () => {\n observer.disconnect()\n }\n}\n\nfunction setupHoverPrefetch(doc: Document, delay: number): () => void {\n let hoverTimeout: ReturnType<typeof setTimeout> | null = null\n let lastHoveredElement: Element | null = null\n\n const handlePointerOver = (event: Event) => {\n const target = event.target\n if (!(target instanceof Element)) return\n\n // Find the closest element with interactive attributes\n const interactiveEl =\n target.closest('[on\\\\:click]') ||\n target.closest('[on\\\\:input]') ||\n target.closest('[on\\\\:change]') ||\n target.closest('[on\\\\:submit]') ||\n target.closest('[data-fict-h]')\n\n if (!interactiveEl || interactiveEl === lastHoveredElement) return\n\n lastHoveredElement = interactiveEl\n\n // Clear previous timeout\n if (hoverTimeout) {\n clearTimeout(hoverTimeout)\n }\n\n // Debounce prefetch\n hoverTimeout = setTimeout(() => {\n prefetchElementQrls(interactiveEl)\n }, delay)\n }\n\n const handlePointerOut = () => {\n if (hoverTimeout) {\n clearTimeout(hoverTimeout)\n hoverTimeout = null\n }\n lastHoveredElement = null\n }\n\n doc.addEventListener('pointerover', handlePointerOver, { passive: true })\n doc.addEventListener('pointerout', handlePointerOut, { passive: true })\n\n return () => {\n doc.removeEventListener('pointerover', handlePointerOver)\n doc.removeEventListener('pointerout', handlePointerOut)\n if (hoverTimeout) {\n clearTimeout(hoverTimeout)\n }\n }\n}\n\nfunction prefetchElementQrls(el: Element): void {\n // Prefetch event handler QRLs\n const eventAttrs = ['on:click', 'on:input', 'on:change', 'on:submit', 'on:keydown', 'on:keyup']\n for (const attr of eventAttrs) {\n const qrl = el.getAttribute(attr)\n if (qrl) {\n prefetchQrl(qrl)\n }\n }\n\n // Prefetch resume handler QRL\n const resumeQrl = el.getAttribute('data-fict-h')\n if (resumeQrl) {\n prefetchQrl(resumeQrl)\n }\n\n // Also check children for nested QRLs\n const children = el.querySelectorAll(\n '[on\\\\:click], [on\\\\:input], [on\\\\:change], [on\\\\:submit], [data-fict-h]',\n )\n children.forEach(child => {\n for (const attr of eventAttrs) {\n const qrl = child.getAttribute(attr)\n if (qrl) {\n prefetchQrl(qrl)\n }\n }\n const childResumeQrl = child.getAttribute('data-fict-h')\n if (childResumeQrl) {\n prefetchQrl(childResumeQrl)\n }\n })\n}\n\nfunction prefetchQrl(qrl: string): void {\n const { url } = parseQrl(qrl)\n if (!url || prefetchedUrls.has(url)) return\n\n prefetchedUrls.add(url)\n\n // Resolve through manifest for production builds\n const resolvedUrl = resolveModuleUrl(url)\n\n // Use modulepreload link for best browser support\n if (typeof document !== 'undefined') {\n const link = document.createElement('link')\n link.rel = 'modulepreload'\n link.href = resolvedUrl\n link.crossOrigin = 'anonymous'\n document.head.appendChild(link)\n }\n}\n\n// ============================================================================\n\n/**\n * Wrapper that tracks the async handler promise for testing.\n */\nfunction handleResumableEvent(event: Event): void {\n const promise = handleResumableEventAsync(event)\n pendingHandlers.add(promise)\n promise.finally(() => {\n pendingHandlers.delete(promise)\n })\n}\n\nasync function handleResumableEventAsync(event: Event): Promise<void> {\n const path =\n typeof event.composedPath === 'function' ? event.composedPath() : buildEventPath(event)\n\n for (const node of path) {\n if (!(node instanceof Element)) continue\n const qrl = node.getAttribute(`on:${event.type}`)\n if (!qrl) continue\n\n const host = node.closest('[data-fict-s]') as Element | null\n if (!host) continue\n const scopeId = host.getAttribute('data-fict-s')\n if (!scopeId) continue\n\n const snapshot = __fictGetSSRScope(scopeId)\n if (!snapshot) {\n emitSnapshotIssue({\n code: 'scope_snapshot_missing',\n message: `[fict/loader] Missing scope snapshot for ${scopeId}; skipping resumable handler execution.`,\n source: 'event',\n expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,\n scopeId,\n })\n return\n }\n __fictEnsureScope(scopeId, host, snapshot)\n\n const { url, exportName } = parseQrl(qrl)\n\n // Pre-emptively prevent default on navigations/forms while we await modules\n if (event.cancelable && (event.type === 'click' || event.type === 'submit')) {\n const tag = node.tagName.toLowerCase()\n if (tag === 'a' || tag === 'form') {\n event.preventDefault()\n }\n }\n\n // Resume FIRST to set up reactive bindings BEFORE the handler runs\n if (!hydratedScopes.has(scopeId)) {\n const resumeQrl = host.getAttribute('data-fict-h')\n if (resumeQrl) {\n const { url: resumeUrl, exportName: resumeExport } = parseQrl(resumeQrl)\n const resolvedResumeUrl = resolveModuleUrl(resumeUrl)\n // Load the module to ensure resume functions are registered\n await import(/* @vite-ignore */ resolvedResumeUrl)\n // Get resume function from registry (not module exports)\n const resumeFn = __fictGetResume(resumeExport)\n if (typeof resumeFn === 'function') {\n await (resumeFn as (scopeId: string, host: Element) => unknown)(scopeId, host)\n hydratedScopes.add(scopeId)\n }\n }\n }\n\n // THEN run the handler - now signal updates will trigger DOM updates\n const resolvedUrl = resolveModuleUrl(url)\n const mod = await import(/* @vite-ignore */ resolvedUrl)\n const handler = (mod as Record<string, unknown>)[exportName]\n if (typeof handler === 'function') {\n await (handler as (scopeId: string, ev: Event, el: Element) => unknown)(scopeId, event, node)\n }\n\n return\n }\n}\n\nfunction parseQrl(qrl: string): { url: string; exportName: string } {\n const [ref] = qrl.split('[')\n if (!ref) {\n return { url: '', exportName: 'default' }\n }\n const hashIndex = ref.lastIndexOf('#')\n if (hashIndex === -1) {\n return { url: ref, exportName: 'default' }\n }\n return { url: ref.slice(0, hashIndex), exportName: ref.slice(hashIndex + 1) }\n}\n\nfunction buildEventPath(event: Event): EventTarget[] {\n const path: EventTarget[] = []\n let node: EventTarget | null = event.target\n while (node) {\n path.push(node)\n node = (node as Node).parentNode\n }\n path.push(window)\n return path\n}\n\n// Re-export for handler authors (optional)\nexport { __fictUseLexicalScope } from './resume'\n"]}
package/dist/loader.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as __fictUseLexicalScope } from './resume-i-A3EFox.cjs';
1
+ export { _ as __fictUseLexicalScope } from './resume-BJ4oHLi_.cjs';
2
2
  import './signal-C4ISF17w.cjs';
3
3
 
4
4
  interface PrefetchStrategy {
@@ -29,6 +29,11 @@ interface ResumableLoaderOptions {
29
29
  document?: Document;
30
30
  snapshotScriptId?: string;
31
31
  events?: string[];
32
+ /**
33
+ * Receives structured snapshot/resume issues detected by the loader.
34
+ * Useful for telemetry and fail-safe fallback orchestration.
35
+ */
36
+ onSnapshotIssue?: (issue: SnapshotIssue) => void;
32
37
  /**
33
38
  * Prefetch strategy configuration.
34
39
  * Set to false to disable all prefetching.
@@ -36,6 +41,15 @@ interface ResumableLoaderOptions {
36
41
  */
37
42
  prefetch?: PrefetchStrategy | false;
38
43
  }
44
+ type SnapshotIssueCode = 'snapshot_parse_error' | 'snapshot_invalid_shape' | 'snapshot_unsupported_version' | 'scope_snapshot_missing';
45
+ interface SnapshotIssue {
46
+ code: SnapshotIssueCode;
47
+ message: string;
48
+ source: string;
49
+ expectedVersion: number;
50
+ actualVersion?: number;
51
+ scopeId?: string;
52
+ }
39
53
  /**
40
54
  * Reset the hydrated scopes set. Useful for testing.
41
55
  */
@@ -54,4 +68,4 @@ declare function waitForPendingHandlers(): Promise<void>;
54
68
  declare function cleanupEventListeners(): void;
55
69
  declare function installResumableLoader(options?: ResumableLoaderOptions): void;
56
70
 
57
- export { type PrefetchStrategy, type ResumableLoaderOptions, cleanupEventListeners, installResumableLoader, resetHydratedScopes, resetPrefetchedUrls, waitForPendingHandlers };
71
+ export { type PrefetchStrategy, type ResumableLoaderOptions, type SnapshotIssue, type SnapshotIssueCode, cleanupEventListeners, installResumableLoader, resetHydratedScopes, resetPrefetchedUrls, waitForPendingHandlers };