@askrjs/askr 0.0.39 → 0.0.40

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 (250) hide show
  1. package/dist/bench/components/benchmark-row.d.ts.map +1 -1
  2. package/dist/bench/components/benchmark-row.js.map +1 -1
  3. package/dist/bench/components/benchmark-table.js.map +1 -1
  4. package/dist/benchmark.d.ts.map +1 -1
  5. package/dist/benchmark.js +2 -2
  6. package/dist/benchmark.js.map +1 -1
  7. package/dist/bin/askr-ssg.d.ts.map +1 -1
  8. package/dist/bin/askr-ssg.js.map +1 -1
  9. package/dist/boot/index.d.ts +9 -6
  10. package/dist/boot/index.d.ts.map +1 -1
  11. package/dist/boot/index.js +210 -75
  12. package/dist/boot/index.js.map +1 -1
  13. package/dist/common/attr-names.js +17 -0
  14. package/dist/common/attr-names.js.map +1 -0
  15. package/dist/common/component.d.ts.map +1 -1
  16. package/dist/common/control.d.ts.map +1 -1
  17. package/dist/common/control.js.map +1 -1
  18. package/dist/common/env.js.map +1 -1
  19. package/dist/common/jsx.d.ts.map +1 -1
  20. package/dist/common/jsx.js.map +1 -1
  21. package/dist/common/promise.js +8 -0
  22. package/dist/common/promise.js.map +1 -0
  23. package/dist/common/props.d.ts +286 -1
  24. package/dist/common/props.d.ts.map +1 -1
  25. package/dist/common/router-internal.js +6 -0
  26. package/dist/common/router-internal.js.map +1 -0
  27. package/dist/common/router.d.ts +45 -36
  28. package/dist/common/router.d.ts.map +1 -1
  29. package/dist/common/ssr-errors.d.ts.map +1 -1
  30. package/dist/common/ssr-errors.js.map +1 -1
  31. package/dist/common/ssr.d.ts +1 -0
  32. package/dist/common/ssr.d.ts.map +1 -1
  33. package/dist/common/ssr.js +6 -0
  34. package/dist/common/ssr.js.map +1 -0
  35. package/dist/common/vnode.d.ts +2 -1
  36. package/dist/common/vnode.d.ts.map +1 -1
  37. package/dist/common/vnode.js.map +1 -1
  38. package/dist/components/error-boundary.d.ts +7 -4
  39. package/dist/components/error-boundary.d.ts.map +1 -1
  40. package/dist/components/error-boundary.js.map +1 -1
  41. package/dist/components/link.d.ts +2 -1
  42. package/dist/components/link.d.ts.map +1 -1
  43. package/dist/components/link.js +10 -1
  44. package/dist/components/link.js.map +1 -1
  45. package/dist/control/case.d.ts +4 -2
  46. package/dist/control/case.d.ts.map +1 -1
  47. package/dist/control/case.js.map +1 -1
  48. package/dist/control/for.d.ts +2 -1
  49. package/dist/control/for.d.ts.map +1 -1
  50. package/dist/control/for.js.map +1 -1
  51. package/dist/control/shared.d.ts +9 -0
  52. package/dist/control/shared.d.ts.map +1 -0
  53. package/dist/control/shared.js.map +1 -1
  54. package/dist/control/show.d.ts +4 -3
  55. package/dist/control/show.d.ts.map +1 -1
  56. package/dist/control/show.js.map +1 -1
  57. package/dist/data/index.d.ts +94 -14
  58. package/dist/data/index.d.ts.map +1 -1
  59. package/dist/data/index.js +65 -14
  60. package/dist/data/index.js.map +1 -1
  61. package/dist/dev/invariant.js.map +1 -1
  62. package/dist/dev/logger.js.map +1 -1
  63. package/dist/foundations/icon/icon.d.ts.map +1 -1
  64. package/dist/foundations/icon/icon.js.map +1 -1
  65. package/dist/foundations/icon/icon.types.d.ts.map +1 -1
  66. package/dist/foundations/interactions/dismissable.d.ts +9 -3
  67. package/dist/foundations/interactions/dismissable.d.ts.map +1 -1
  68. package/dist/foundations/interactions/dismissable.js.map +1 -1
  69. package/dist/foundations/interactions/focusable.d.ts.map +1 -1
  70. package/dist/foundations/interactions/focusable.js.map +1 -1
  71. package/dist/foundations/interactions/hoverable.d.ts.map +1 -1
  72. package/dist/foundations/interactions/hoverable.js.map +1 -1
  73. package/dist/foundations/interactions/interaction-policy.d.ts +1 -1
  74. package/dist/foundations/interactions/interaction-policy.d.ts.map +1 -1
  75. package/dist/foundations/interactions/interaction-policy.js +3 -3
  76. package/dist/foundations/interactions/interaction-policy.js.map +1 -1
  77. package/dist/foundations/interactions/pressable.d.ts.map +1 -1
  78. package/dist/foundations/interactions/pressable.js.map +1 -1
  79. package/dist/foundations/interactions/roving-focus.d.ts.map +1 -1
  80. package/dist/foundations/interactions/roving-focus.js +8 -8
  81. package/dist/foundations/interactions/roving-focus.js.map +1 -1
  82. package/dist/foundations/state/controllable.d.ts.map +1 -1
  83. package/dist/foundations/state/controllable.js.map +1 -1
  84. package/dist/foundations/structures/collection.d.ts.map +1 -1
  85. package/dist/foundations/structures/collection.js.map +1 -1
  86. package/dist/foundations/structures/layer.d.ts.map +1 -1
  87. package/dist/foundations/structures/layer.js.map +1 -1
  88. package/dist/foundations/structures/layout.d.ts +4 -2
  89. package/dist/foundations/structures/layout.d.ts.map +1 -1
  90. package/dist/foundations/structures/layout.js.map +1 -1
  91. package/dist/foundations/structures/portal.d.ts +9 -25
  92. package/dist/foundations/structures/portal.d.ts.map +1 -1
  93. package/dist/foundations/structures/portal.js +98 -17
  94. package/dist/foundations/structures/portal.js.map +1 -1
  95. package/dist/foundations/structures/presence.d.ts +4 -3
  96. package/dist/foundations/structures/presence.d.ts.map +1 -1
  97. package/dist/foundations/structures/presence.js +2 -2
  98. package/dist/foundations/structures/presence.js.map +1 -1
  99. package/dist/foundations/structures/slot.d.ts +2 -1
  100. package/dist/foundations/structures/slot.d.ts.map +1 -1
  101. package/dist/foundations/structures/slot.js.map +1 -1
  102. package/dist/foundations/utilities/aria.d.ts.map +1 -1
  103. package/dist/foundations/utilities/aria.js.map +1 -1
  104. package/dist/foundations/utilities/compose-handlers.d.ts.map +1 -1
  105. package/dist/foundations/utilities/compose-handlers.js.map +1 -1
  106. package/dist/foundations/utilities/compose-ref.d.ts.map +1 -1
  107. package/dist/foundations/utilities/compose-ref.js.map +1 -1
  108. package/dist/foundations/utilities/event-types.d.ts.map +1 -1
  109. package/dist/foundations/utilities/merge-props.js.map +1 -1
  110. package/dist/foundations/utilities/use-id.d.ts.map +1 -1
  111. package/dist/foundations/utilities/use-id.js.map +1 -1
  112. package/dist/fx/fx.d.ts.map +1 -1
  113. package/dist/fx/fx.js.map +1 -1
  114. package/dist/fx/noop.js.map +1 -1
  115. package/dist/fx/timing.d.ts +5 -4
  116. package/dist/fx/timing.d.ts.map +1 -1
  117. package/dist/fx/timing.js +7 -4
  118. package/dist/fx/timing.js.map +1 -1
  119. package/dist/index.js.map +1 -1
  120. package/dist/jsx/types.d.ts +3 -3
  121. package/dist/jsx/types.d.ts.map +1 -1
  122. package/dist/jsx/utils.d.ts.map +1 -1
  123. package/dist/jsx/utils.js.map +1 -1
  124. package/dist/jsx-dev-runtime.d.ts +6 -2
  125. package/dist/jsx-dev-runtime.d.ts.map +1 -1
  126. package/dist/jsx-dev-runtime.js.map +1 -1
  127. package/dist/jsx-runtime.d.ts +16 -6
  128. package/dist/jsx-runtime.d.ts.map +1 -1
  129. package/dist/jsx-runtime.js.map +1 -1
  130. package/dist/renderer/children.js.map +1 -1
  131. package/dist/renderer/cleanup.js +40 -13
  132. package/dist/renderer/cleanup.js.map +1 -1
  133. package/dist/renderer/dom.js +163 -63
  134. package/dist/renderer/dom.js.map +1 -1
  135. package/dist/renderer/evaluate.js +9 -6
  136. package/dist/renderer/evaluate.js.map +1 -1
  137. package/dist/renderer/fastpath.js.map +1 -1
  138. package/dist/renderer/for-commit.js +1 -0
  139. package/dist/renderer/for-commit.js.map +1 -1
  140. package/dist/renderer/index.js.map +1 -1
  141. package/dist/renderer/keyed.js.map +1 -1
  142. package/dist/renderer/reconcile.js +34 -0
  143. package/dist/renderer/reconcile.js.map +1 -1
  144. package/dist/renderer/utils.js +34 -3
  145. package/dist/renderer/utils.js.map +1 -1
  146. package/dist/router/index.d.ts +2 -2
  147. package/dist/router/match.js +16 -2
  148. package/dist/router/match.js.map +1 -1
  149. package/dist/router/navigate.d.ts +9 -1
  150. package/dist/router/navigate.d.ts.map +1 -1
  151. package/dist/router/navigate.js +235 -81
  152. package/dist/router/navigate.js.map +1 -1
  153. package/dist/router/policy.d.ts +7 -7
  154. package/dist/router/policy.d.ts.map +1 -1
  155. package/dist/router/policy.js +5 -7
  156. package/dist/router/policy.js.map +1 -1
  157. package/dist/router/route-context.js.map +1 -1
  158. package/dist/router/route.d.ts +20 -9
  159. package/dist/router/route.d.ts.map +1 -1
  160. package/dist/router/route.js +95 -29
  161. package/dist/router/route.js.map +1 -1
  162. package/dist/runtime/child-scope.d.ts.map +1 -1
  163. package/dist/runtime/child-scope.js +5 -1
  164. package/dist/runtime/child-scope.js.map +1 -1
  165. package/dist/runtime/component.d.ts +5 -2
  166. package/dist/runtime/component.d.ts.map +1 -1
  167. package/dist/runtime/component.js +85 -25
  168. package/dist/runtime/component.js.map +1 -1
  169. package/dist/runtime/context.d.ts +5 -2
  170. package/dist/runtime/context.d.ts.map +1 -1
  171. package/dist/runtime/context.js +5 -2
  172. package/dist/runtime/context.js.map +1 -1
  173. package/dist/runtime/control.d.ts.map +1 -1
  174. package/dist/runtime/control.js.map +1 -1
  175. package/dist/runtime/derive.d.ts.map +1 -1
  176. package/dist/runtime/derive.js +3 -1
  177. package/dist/runtime/derive.js.map +1 -1
  178. package/dist/runtime/dev-namespace.js.map +1 -1
  179. package/dist/runtime/effect.d.ts.map +1 -1
  180. package/dist/runtime/effect.js +17 -11
  181. package/dist/runtime/effect.js.map +1 -1
  182. package/dist/runtime/events.js +43 -1
  183. package/dist/runtime/events.js.map +1 -1
  184. package/dist/runtime/execution-model.js.map +1 -1
  185. package/dist/runtime/fastlane.js +2 -23
  186. package/dist/runtime/fastlane.js.map +1 -1
  187. package/dist/runtime/for-bench.d.ts.map +1 -1
  188. package/dist/runtime/for-bench.js.map +1 -1
  189. package/dist/runtime/for.d.ts +1 -1
  190. package/dist/runtime/for.d.ts.map +1 -1
  191. package/dist/runtime/for.js +36 -12
  192. package/dist/runtime/for.js.map +1 -1
  193. package/dist/runtime/operations.d.ts +3 -13
  194. package/dist/runtime/operations.d.ts.map +1 -1
  195. package/dist/runtime/operations.js +47 -39
  196. package/dist/runtime/operations.js.map +1 -1
  197. package/dist/runtime/perf-metrics.js.map +1 -1
  198. package/dist/runtime/readable.d.ts +1 -0
  199. package/dist/runtime/readable.d.ts.map +1 -1
  200. package/dist/runtime/readable.js +6 -1
  201. package/dist/runtime/readable.js.map +1 -1
  202. package/dist/runtime/resource-cell.js +13 -2
  203. package/dist/runtime/resource-cell.js.map +1 -1
  204. package/dist/runtime/scheduler.d.ts.map +1 -1
  205. package/dist/runtime/scheduler.js +8 -4
  206. package/dist/runtime/scheduler.js.map +1 -1
  207. package/dist/runtime/selector.d.ts.map +1 -1
  208. package/dist/runtime/selector.js +8 -2
  209. package/dist/runtime/selector.js.map +1 -1
  210. package/dist/runtime/snapshot-source.d.ts.map +1 -1
  211. package/dist/runtime/snapshot-source.js.map +1 -1
  212. package/dist/runtime/ssr-bridge.js.map +1 -1
  213. package/dist/runtime/state.d.ts +4 -3
  214. package/dist/runtime/state.d.ts.map +1 -1
  215. package/dist/runtime/state.js.map +1 -1
  216. package/dist/ssg/batch-render.js +4 -8
  217. package/dist/ssg/batch-render.js.map +1 -1
  218. package/dist/ssg/create-static-gen.d.ts +10 -2
  219. package/dist/ssg/create-static-gen.d.ts.map +1 -1
  220. package/dist/ssg/create-static-gen.js +5 -4
  221. package/dist/ssg/create-static-gen.js.map +1 -1
  222. package/dist/ssg/generate-metadata.js.map +1 -1
  223. package/dist/ssg/incremental-manifest.js.map +1 -1
  224. package/dist/ssg/resolve-ssg-data.js +54 -12
  225. package/dist/ssg/resolve-ssg-data.js.map +1 -1
  226. package/dist/ssg/route-utils.js.map +1 -1
  227. package/dist/ssg/types.d.ts +8 -7
  228. package/dist/ssg/types.d.ts.map +1 -1
  229. package/dist/ssg/write-static-files.js.map +1 -1
  230. package/dist/ssr/attrs.js +3 -2
  231. package/dist/ssr/attrs.js.map +1 -1
  232. package/dist/ssr/context.d.ts +1 -0
  233. package/dist/ssr/context.d.ts.map +1 -1
  234. package/dist/ssr/context.js +1 -0
  235. package/dist/ssr/context.js.map +1 -1
  236. package/dist/ssr/errors.d.ts.map +1 -1
  237. package/dist/ssr/escape.js.map +1 -1
  238. package/dist/ssr/index.d.ts +8 -12
  239. package/dist/ssr/index.d.ts.map +1 -1
  240. package/dist/ssr/index.js +55 -30
  241. package/dist/ssr/index.js.map +1 -1
  242. package/dist/ssr/render-keys.js +15 -4
  243. package/dist/ssr/render-keys.js.map +1 -1
  244. package/dist/ssr/render-resolved.js +33 -0
  245. package/dist/ssr/render-resolved.js.map +1 -0
  246. package/dist/ssr/sink.js.map +1 -1
  247. package/dist/ssr/types.d.ts.map +1 -1
  248. package/dist/ssr/verify-hydration.js +5 -6
  249. package/dist/ssr/verify-hydration.js.map +1 -1
  250. package/package.json +12 -4
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.js","names":[],"sources":["../../src/renderer/reconcile.ts"],"sourcesContent":["/**\n * ─────────────────────────────────────────────────────────────────────────────\n * RENDERER & RECONCILIATION INVARIANTS (LOCKED)\n * ─────────────────────────────────────────────────────────────────────────────\n *\n * These invariants are NON-NEGOTIABLE. Any optimization or fast-path MUST\n * preserve them. Violations WILL produce incorrect DOM.\n *\n * 1. DOM ORDER DERIVES ONLY FROM CURRENT VNODE ORDER\n * --------------------------------------------------\n * - Final DOM child order MUST be reconstructed from the current vnode list.\n * - Reusing an existing DOM node does NOT imply it stays in the same position.\n * - Appending an existing Node to a DocumentFragment is the ONLY valid way\n * to express reordering (it moves the node).\n *\n * ❌ NEVER skip fragment insertion because a node already has a parent.\n * ✅ ALWAYS append reused nodes into the fragment to establish order.\n *\n *\n * 2. VNODE IDENTITY ≠ VNODE STABILITY\n * ----------------------------------\n * - VNodes are mutable.\n * - `vnodeA === vnodeB` does NOT imply semantic equality.\n * - DOM reuse MUST NOT be gated on vnode identity alone.\n *\n * DOM reuse is allowed ONLY when:\n * - element type is unchanged\n * - structural shape is compatible\n * - updates are applied explicitly via updateElementFromVnode\n *\n * ❌ NEVER assume \"same object\" means \"no changes\".\n *\n *\n * 3. KEYED RECONCILIATION IS ELEMENT-ONLY\n * --------------------------------------\n * - Keyed reconciliation assumes ELEMENT nodes, not Text or Comment nodes.\n * - Any fast-path using `parent.children[i]` MUST prove:\n * - all children are elements\n * - no text nodes are present\n *\n * ❌ NEVER index `parent.children` when text nodes may exist.\n * ✅ Use `parent.childNodes` or bail out to full reconciliation.\n *\n *\n * 4. PRIMITIVES MAP TO TEXT NODES\n * -------------------------------\n * - string/number children represent Text nodes, not Elements.\n * - Reconciliation MUST attempt Text-to-Text reuse before replacement.\n *\n * ❌ NEVER update element.textContent as a substitute for text reconciliation\n * unless the shape is explicitly guaranteed.\n *\n *\n * 5. FAST-PATHS MUST BE STRICTLY SAFE\n * ----------------------------------\n * - Fast-paths are OPTIONAL.\n * - Correctness always beats performance.\n *\n * A fast-path MUST:\n * - prove its eligibility\n * - fall back cleanly on ANY ambiguity\n * - never partially apply\n *\n *\n * 6. CLEANUP IS POSITION-INDEPENDENT\n * ---------------------------------\n * - Cleanup is based on removal from the DOM, not vnode position.\n * - Any node removed or replaced MUST:\n * - remove listeners\n * - cleanup component instances\n *\n *\n * 7. FOR-BOUNDARY & KEYED LISTS OBEY THE SAME RULES\n * ------------------------------------------------\n * - For-boundaries are NOT special in ordering semantics.\n * - Cached DOM nodes MUST still be reordered via fragment insertion.\n * - Cache is an optimization, not an ownership claim.\n *\n *\n * If you are unsure whether an optimization preserves these invariants:\n * DO NOT APPLY IT.\n *\n * ─────────────────────────────────────────────────────────────────────────────\n */\n\nimport type { DOMElement, VNode } from './types';\nimport {\n createDOMNode,\n syncComponentElement,\n updateElementFromVnode,\n performBulkPositionalKeyedTextUpdate,\n} from './dom';\nimport type { Props } from '../common/props';\nimport {\n keyedElements,\n _reconcilerRecordedParents,\n planKeyedReorderFastPath,\n} from './keyed';\nimport { teardownNodeSubtree } from './cleanup';\nimport { applyRendererFastPath } from './fastpath';\nimport { getRuntimeEnv } from './env';\nimport type { ComponentFunction } from '../runtime/component';\nimport {\n extractKey,\n checkPropChanges,\n recordFastPathStats,\n recordDOMReplace,\n tagNamesEqualIgnoreCase,\n} from './utils';\n\nexport const IS_DOM_AVAILABLE = typeof document !== 'undefined';\n\n// Helper type for narrowings\ntype VnodeObj = VNode & { type?: unknown; props?: Record<string, unknown> };\ntype ComponentVNode = DOMElement & { type: ComponentFunction };\n\nexport function reconcileKeyedChildren(\n parent: Element,\n newChildren: VNode[],\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> {\n const keyedVnodes = extractKeyedChildren(newChildren);\n\n // Ensure we have a key map before reconciliation to avoid O(n) DOM scans\n // during O(n) reconciliation loop (which would be O(n²))\n const ensuredOldKeyMap = oldKeyMap || buildKeyMapFromDOM(parent);\n\n // Try fast paths first\n const fastPathResult = tryFastPaths(\n parent,\n newChildren,\n keyedVnodes,\n ensuredOldKeyMap\n );\n if (fastPathResult) {\n return fastPathResult;\n }\n\n // Full reconciliation\n return performFullReconciliation(\n parent,\n newChildren,\n keyedVnodes,\n ensuredOldKeyMap\n );\n}\n\n/** Build key map from DOM children */\nfunction buildKeyMapFromDOM(parent: Element): Map<string | number, Element> {\n const keyMap = new Map<string | number, Element>();\n try {\n for (let el = parent.firstElementChild; el; el = el.nextElementSibling) {\n const k = el.getAttribute('data-key');\n if (k !== null) {\n keyMap.set(k, el);\n const n = Number(k);\n if (!Number.isNaN(n)) keyMap.set(n, el);\n }\n }\n } catch {\n // Ignore\n }\n return keyMap;\n}\n\n/** Extract keyed children in a single pass. */\nfunction extractKeyedChildren(\n newChildren: VNode[]\n): Array<{ key: string | number; vnode: VNode }> {\n const keyedVnodes: Array<{ key: string | number; vnode: VNode }> = [];\n\n for (let i = 0; i < newChildren.length; i++) {\n const child = newChildren[i];\n const key = extractKey(child);\n if (key !== undefined) {\n keyedVnodes.push({ key, vnode: child });\n }\n }\n\n return keyedVnodes;\n}\n\n/** Try fast paths before full reconciliation */\nfunction tryFastPaths(\n parent: Element,\n newChildren: VNode[],\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> | null {\n try {\n const forcedResult = tryForcedPositionalBulkUpdate(\n parent,\n newChildren,\n keyedVnodes\n );\n if (forcedResult) {\n return forcedResult;\n }\n\n // Try renderer fast-path for large keyed reorder-only updates\n const rendererResult = tryRendererFastPath(\n parent,\n keyedVnodes,\n newChildren.length,\n oldKeyMap\n );\n if (rendererResult) {\n return rendererResult;\n }\n\n // Try positional bulk update for medium-sized lists\n const positionalResult = tryPositionalBulkUpdate(parent, keyedVnodes);\n if (positionalResult) {\n return positionalResult;\n }\n } catch {\n // Fall through to full reconciliation\n }\n\n return null;\n}\n\n/** Try renderer fast-path */\nfunction tryRendererFastPath(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n totalChildren: number,\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> | null {\n const decision = planKeyedReorderFastPath(\n parent,\n keyedVnodes,\n totalChildren,\n oldKeyMap\n );\n\n if (decision.useFastPath) {\n try {\n const map = applyRendererFastPath(parent, keyedVnodes, oldKeyMap);\n if (map) {\n keyedElements.set(parent, map);\n return map;\n }\n } catch {\n // Fall through\n }\n }\n\n return null;\n}\n\n/** Try the explicit test/bench forced positional keyed path. */\nfunction tryForcedPositionalBulkUpdate(\n parent: Element,\n newChildren: VNode[],\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): Map<string | number, Element> | null {\n if (getRuntimeEnv().ASKR_FORCE_BULK_POSREUSE !== '1') return null;\n if (keyedVnodes.length === 0 || keyedVnodes.length !== newChildren.length) {\n return null;\n }\n\n try {\n const stats = performBulkPositionalKeyedTextUpdate(parent, keyedVnodes);\n recordFastPathStats(stats, 'bulkKeyedPositionalForced');\n\n rebuildKeyedMap(parent);\n return keyedElements.get(parent) as Map<string | number, Element>;\n } catch {\n return null;\n }\n}\n\n/** Try positional bulk update for medium-sized lists */\nfunction tryPositionalBulkUpdate(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): Map<string | number, Element> | null {\n const total = keyedVnodes.length;\n if (total < 10) return null;\n\n // CRITICAL INVARIANT: Only use children[] indexing if element-only is guaranteed\n // If parent has text nodes or comment nodes, bail to full reconciliation\n if (parent.children.length !== total) {\n return null;\n }\n\n const matchCount = countPositionalMatches(parent, keyedVnodes);\n\n // For keyed lists, the positional bulk update path makes sense in two cases:\n // 1. Perfect match (100%): All keys are in the right positions, just update text\n // 2. Very low match (<10%): Keys changed en-masse, treat as bulk re-key, reuse nodes by position\n // ANY mismatch at all means we have a reorder and need full reconciliation\n // to preserve DOM node identity correctly.\n const matchFraction = matchCount / total;\n\n if (keyedVnodes.length > 0) {\n // For keyed lists: require perfect match or very low match\n // matchCount !== total catches ANY reordering, even just 2 swapped elements\n if (matchCount !== total && matchFraction >= 0.1) {\n return null;\n }\n } else {\n // For unkeyed lists: use original threshold\n if (matchFraction < 0.9) {\n return null;\n }\n }\n\n // Check for prop changes that would prevent positional update\n if (hasPositionalPropChanges(parent, keyedVnodes)) {\n return null;\n }\n\n // Perform positional update\n try {\n const stats = performBulkPositionalKeyedTextUpdate(parent, keyedVnodes);\n recordFastPathStats(stats, 'bulkKeyedPositionalHits');\n\n rebuildKeyedMap(parent);\n return keyedElements.get(parent) as Map<string | number, Element>;\n } catch {\n return null;\n }\n}\n\n/** Count how many vnodes match parent children by position and tag */\nfunction countPositionalMatches(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): number {\n let matchCount = 0;\n\n try {\n // For keyed children, use children (elements only) since keyed nodes are elements\n for (let i = 0; i < keyedVnodes.length; i++) {\n const vnode = keyedVnodes[i].vnode as VnodeObj;\n const expectedKey = keyedVnodes[i].key;\n\n if (!vnode || typeof vnode !== 'object' || typeof vnode.type !== 'string')\n continue;\n\n const el = parent.children[i] as Element | undefined;\n if (!el) continue;\n\n // For keyed lists, check BOTH tag name AND key match\n if (tagNamesEqualIgnoreCase(el.tagName, vnode.type)) {\n // Check if the element at this position has the expected key\n const actualKey = el.getAttribute('data-key');\n const keyMatches =\n actualKey === String(expectedKey) ||\n (actualKey !== null &&\n !Number.isNaN(Number(actualKey)) &&\n Number(actualKey) === expectedKey);\n\n if (keyMatches) {\n matchCount++;\n }\n }\n }\n } catch {\n // Ignore\n }\n\n return matchCount;\n}\n\n/** Check if positional prop changes would prevent bulk update */\nfunction hasPositionalPropChanges(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): boolean {\n try {\n // For keyed children, use children (elements only) since keyed nodes are elements\n for (let i = 0; i < keyedVnodes.length; i++) {\n const vnode = keyedVnodes[i].vnode as VnodeObj;\n const el = parent.children[i] as Element | undefined;\n if (!el || !vnode || typeof vnode !== 'object') continue;\n\n if (checkPropChanges(el, vnode.props || {})) {\n return true;\n }\n }\n } catch {\n return true;\n }\n\n return false;\n}\n\n/** Rebuild keyed map from parent children */\nfunction rebuildKeyedMap(parent: Element): void {\n try {\n const map = new Map<string | number, Element>();\n for (let el = parent.firstElementChild; el; el = el.nextElementSibling) {\n const k = el.getAttribute('data-key');\n if (k !== null) {\n map.set(k, el);\n const n = Number(k);\n if (!Number.isNaN(n)) map.set(n, el);\n }\n }\n keyedElements.set(parent, map);\n } catch {\n // Ignore\n }\n}\n\n/** Perform full reconciliation when fast paths don't apply */\nfunction performFullReconciliation(\n parent: Element,\n newChildren: VNode[],\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> {\n const newKeyMap = new Map<string | number, Element>();\n const finalNodes: Node[] = [];\n const usedOldEls = new WeakSet<Node>();\n\n const resolveOldElOnce = createOldElResolver(parent, oldKeyMap, usedOldEls);\n const unkeyedEls = collectUnkeyedElements(parent);\n let unkeyedIndex = 0;\n const resolveUnkeyedOnce = (): Element | undefined => {\n while (unkeyedIndex < unkeyedEls.length) {\n const candidate = unkeyedEls[unkeyedIndex++];\n if (!usedOldEls.has(candidate)) return candidate;\n }\n return undefined;\n };\n\n // Positional reconciliation\n for (let i = 0; i < newChildren.length; i++) {\n const child = newChildren[i];\n const node = reconcileSingleChild(\n child,\n i,\n parent,\n resolveOldElOnce,\n resolveUnkeyedOnce,\n usedOldEls,\n newKeyMap\n );\n if (node) finalNodes.push(node);\n }\n\n // SSR guard\n if (typeof document === 'undefined') return newKeyMap;\n\n commitReconciliation(parent, finalNodes);\n keyedElements.delete(parent);\n\n return newKeyMap;\n}\n\n/** Create resolver for finding old elements by key */\nfunction createOldElResolver(\n parent: Element,\n oldKeyMap: Map<string | number, Element> | undefined,\n usedOldEls: WeakSet<Node>\n): (k: string | number) => Element | undefined {\n return (k: string | number) => {\n if (!oldKeyMap) return undefined;\n\n // Fast-path: directly from oldKeyMap\n const direct = oldKeyMap.get(k);\n if (direct && !usedOldEls.has(direct)) {\n usedOldEls.add(direct);\n return direct;\n }\n\n // Try string form\n const s = String(k);\n const byString = oldKeyMap.get(s);\n if (byString && !usedOldEls.has(byString)) {\n usedOldEls.add(byString);\n return byString;\n }\n\n // Try numeric form\n const n = Number(s);\n if (!Number.isNaN(n)) {\n const byNum = oldKeyMap.get(n);\n if (byNum && !usedOldEls.has(byNum)) {\n usedOldEls.add(byNum);\n return byNum;\n }\n }\n\n // Fallback: scan parent children\n return scanForElementByKey(parent, k, s, usedOldEls);\n };\n}\n\n/** Scan parent children for element with matching key */\nfunction scanForElementByKey(\n parent: Element,\n k: string | number,\n keyStr: string,\n usedOldEls: WeakSet<Node>\n): Element | undefined {\n try {\n for (let ch = parent.firstElementChild; ch; ch = ch.nextElementSibling) {\n if (usedOldEls.has(ch)) continue;\n const attr = ch.getAttribute('data-key');\n if (attr === keyStr) {\n usedOldEls.add(ch);\n return ch;\n }\n if (attr !== null) {\n const numAttr = Number(attr);\n if (!Number.isNaN(numAttr) && numAttr === (k as number)) {\n usedOldEls.add(ch);\n return ch;\n }\n }\n }\n } catch {\n // Ignore\n }\n return undefined;\n}\n\n/** Reconcile a single child */\nfunction reconcileSingleChild(\n child: VNode,\n index: number,\n parent: Element,\n resolveOldElOnce: (k: string | number) => Element | undefined,\n resolveUnkeyedOnce: () => Element | undefined,\n usedOldEls: WeakSet<Node>,\n newKeyMap: Map<string | number, Element>\n): Node | null {\n // Keyed child\n const key = extractKey(child);\n\n if (key !== undefined) {\n return reconcileKeyedChild(child, key, parent, resolveOldElOnce, newKeyMap);\n }\n\n // Unkeyed or primitive child\n return reconcileUnkeyedChild(\n child,\n index,\n parent,\n resolveUnkeyedOnce,\n usedOldEls\n );\n}\n\n/** Reconcile a keyed child */\nfunction reconcileKeyedChild(\n child: VNode,\n key: string | number,\n parent: Element,\n resolveOldElOnce: (k: string | number) => Element | undefined,\n newKeyMap: Map<string | number, Element>\n): Node | null {\n const el = resolveOldElOnce(key);\n\n if (el && el.parentElement === parent) {\n // Strict keyed guarantee: if the element tag changes for an existing key,\n // replace the DOM node rather than mutating in place.\n try {\n const childObj = child as VnodeObj;\n if (\n childObj &&\n typeof childObj === 'object' &&\n typeof childObj.type === 'string'\n ) {\n if (tagNamesEqualIgnoreCase(el.tagName, childObj.type)) {\n updateElementFromVnode(el, child);\n newKeyMap.set(key, el);\n return el;\n }\n }\n if (isComponentVNode(child)) {\n const synced = syncComponentElement(\n el,\n child,\n child.type,\n ((child.props ?? {}) as Props) || {}\n );\n if (synced) {\n if (synced instanceof Element) newKeyMap.set(key, synced);\n return synced;\n }\n }\n } catch {\n // Fall through to replacement\n }\n }\n\n const dom = createDOMNode(child);\n if (dom) {\n if (dom instanceof Element) newKeyMap.set(key, dom);\n return dom;\n }\n\n return null;\n}\n\n/** Reconcile an unkeyed or primitive child */\nfunction reconcileUnkeyedChild(\n child: VNode,\n index: number,\n parent: Element,\n resolveUnkeyedOnce: () => Element | undefined,\n usedOldEls: WeakSet<Node>\n): Node | null {\n try {\n // Use childNodes (includes Text nodes) instead of children (elements only)\n const existing = parent.childNodes[index] as Node | undefined;\n\n // Primitive child: try to reuse Text node if available\n if (typeof child === 'string' || typeof child === 'number') {\n if (existing && existing.nodeType === 3) {\n // Text node: reuse in-place\n (existing as Text).data = String(child);\n usedOldEls.add(existing);\n return existing;\n }\n return createDOMNode(child);\n }\n\n if (existing instanceof Element) {\n const synced = trySyncComponentChild(existing, child, usedOldEls);\n if (synced) return synced;\n }\n\n // Element child matching existing unkeyed element\n if (existing instanceof Element && canReuseElement(existing, child)) {\n updateElementFromVnode(existing, child);\n usedOldEls.add(existing);\n return existing;\n }\n\n // Try to find available unkeyed element elsewhere\n const avail = resolveUnkeyedOnce();\n if (avail) {\n const reuseResult = tryReuseElement(avail, child, usedOldEls);\n if (reuseResult) return reuseResult;\n }\n } catch {\n // Fall through to create new\n }\n\n const dom = createDOMNode(child);\n return dom;\n}\n\nfunction isComponentVNode(child: VNode): child is ComponentVNode {\n return (\n typeof child === 'object' &&\n child !== null &&\n 'type' in child &&\n typeof (child as VnodeObj).type === 'function'\n );\n}\n\nfunction trySyncComponentChild(\n existing: Element,\n child: VNode,\n usedOldEls: WeakSet<Node>\n): Node | null {\n if (!isComponentVNode(child)) return null;\n\n const synced = syncComponentElement(\n existing,\n child,\n child.type,\n ((child.props ?? {}) as Props) || {}\n );\n if (!synced) return null;\n\n usedOldEls.add(existing);\n usedOldEls.add(synced);\n return synced;\n}\n\n/** Check if existing element can be reused for child */\nfunction canReuseElement(existing: Element | undefined, child: VNode): boolean {\n if (!existing) return false;\n if (typeof child !== 'object' || child === null || !('type' in child))\n return false;\n\n const childObj = child as VnodeObj;\n const existingKey = existing.getAttribute('data-key');\n const hasNoKey = existingKey === null || existingKey === undefined;\n\n return (\n hasNoKey &&\n typeof childObj.type === 'string' &&\n tagNamesEqualIgnoreCase(existing.tagName, childObj.type)\n );\n}\n\n/** Collect unkeyed element children in DOM order. */\nfunction collectUnkeyedElements(parent: Element): Element[] {\n const elements: Element[] = [];\n for (let ch = parent.firstElementChild; ch; ch = ch.nextElementSibling) {\n if (ch.getAttribute('data-key') === null) elements.push(ch);\n }\n return elements;\n}\n\n/** Try to reuse available element for child */\nfunction tryReuseElement(\n avail: Element,\n child: VNode,\n usedOldEls: WeakSet<Node>\n): Node | null {\n if (typeof child === 'string' || typeof child === 'number') {\n return null;\n }\n\n const synced = trySyncComponentChild(avail, child, usedOldEls);\n if (synced) {\n return synced;\n }\n\n if (typeof child === 'object' && child !== null && 'type' in child) {\n const childObj = child as VnodeObj;\n if (\n typeof childObj.type === 'string' &&\n tagNamesEqualIgnoreCase(avail.tagName, childObj.type)\n ) {\n updateElementFromVnode(avail, child);\n usedOldEls.add(avail);\n return avail;\n }\n }\n\n return null;\n}\n\n/** Commit reconciliation by replacing parent children */\nfunction commitReconciliation(parent: Element, finalNodes: Node[]): void {\n try {\n const finalSet = new Set<Node>(finalNodes);\n\n for (let n = parent.firstChild; n; ) {\n const next = n.nextSibling;\n if (!finalSet.has(n)) {\n teardownNodeSubtree(n);\n parent.removeChild(n);\n }\n n = next;\n }\n\n for (let i = 0; i < finalNodes.length; i++) {\n const desiredNode = finalNodes[i];\n const anchor = parent.childNodes[i] ?? null;\n if (desiredNode !== anchor) {\n parent.insertBefore(desiredNode, anchor);\n }\n }\n } catch {\n const fragment = document.createDocumentFragment();\n for (let i = 0; i < finalNodes.length; i++) {\n fragment.appendChild(finalNodes[i]);\n }\n\n try {\n for (let n = parent.firstChild; n; ) {\n const next = n.nextSibling;\n teardownNodeSubtree(n);\n n = next;\n }\n } catch {\n // Ignore fallback cleanup failures.\n }\n\n recordDOMReplace('reconcile');\n parent.replaceChildren(fragment);\n return;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reconcile.js","names":[],"sources":["../../src/renderer/reconcile.ts"],"sourcesContent":["/**\n * ─────────────────────────────────────────────────────────────────────────────\n * RENDERER & RECONCILIATION INVARIANTS (LOCKED)\n * ─────────────────────────────────────────────────────────────────────────────\n *\n * These invariants are NON-NEGOTIABLE. Any optimization or fast-path MUST\n * preserve them. Violations WILL produce incorrect DOM.\n *\n * 1. DOM ORDER DERIVES ONLY FROM CURRENT VNODE ORDER\n * --------------------------------------------------\n * - Final DOM child order MUST be reconstructed from the current vnode list.\n * - Reusing an existing DOM node does NOT imply it stays in the same position.\n * - Appending an existing Node to a DocumentFragment is the ONLY valid way\n * to express reordering (it moves the node).\n *\n * ❌ NEVER skip fragment insertion because a node already has a parent.\n * ✅ ALWAYS append reused nodes into the fragment to establish order.\n *\n *\n * 2. VNODE IDENTITY ≠ VNODE STABILITY\n * ----------------------------------\n * - VNodes are mutable.\n * - `vnodeA === vnodeB` does NOT imply semantic equality.\n * - DOM reuse MUST NOT be gated on vnode identity alone.\n *\n * DOM reuse is allowed ONLY when:\n * - element type is unchanged\n * - structural shape is compatible\n * - updates are applied explicitly via updateElementFromVnode\n *\n * ❌ NEVER assume \"same object\" means \"no changes\".\n *\n *\n * 3. KEYED RECONCILIATION IS ELEMENT-ONLY\n * --------------------------------------\n * - Keyed reconciliation assumes ELEMENT nodes, not Text or Comment nodes.\n * - Any fast-path using `parent.children[i]` MUST prove:\n * - all children are elements\n * - no text nodes are present\n *\n * ❌ NEVER index `parent.children` when text nodes may exist.\n * ✅ Use `parent.childNodes` or bail out to full reconciliation.\n *\n *\n * 4. PRIMITIVES MAP TO TEXT NODES\n * -------------------------------\n * - string/number children represent Text nodes, not Elements.\n * - Reconciliation MUST attempt Text-to-Text reuse before replacement.\n *\n * ❌ NEVER update element.textContent as a substitute for text reconciliation\n * unless the shape is explicitly guaranteed.\n *\n *\n * 5. FAST-PATHS MUST BE STRICTLY SAFE\n * ----------------------------------\n * - Fast-paths are OPTIONAL.\n * - Correctness always beats performance.\n *\n * A fast-path MUST:\n * - prove its eligibility\n * - fall back cleanly on ANY ambiguity\n * - never partially apply\n *\n *\n * 6. CLEANUP IS POSITION-INDEPENDENT\n * ---------------------------------\n * - Cleanup is based on removal from the DOM, not vnode position.\n * - Any node removed or replaced MUST:\n * - remove listeners\n * - cleanup component instances\n *\n *\n * 7. FOR-BOUNDARY & KEYED LISTS OBEY THE SAME RULES\n * ------------------------------------------------\n * - For-boundaries are NOT special in ordering semantics.\n * - Cached DOM nodes MUST still be reordered via fragment insertion.\n * - Cache is an optimization, not an ownership claim.\n *\n *\n * If you are unsure whether an optimization preserves these invariants:\n * DO NOT APPLY IT.\n *\n * ─────────────────────────────────────────────────────────────────────────────\n */\n\nimport type { DOMElement, VNode } from './types';\nimport {\n createDOMNode,\n syncComponentElement,\n updateElementFromVnode,\n performBulkPositionalKeyedTextUpdate,\n} from './dom';\nimport type { Props } from '../common/props';\nimport {\n keyedElements,\n _reconcilerRecordedParents,\n planKeyedReorderFastPath,\n} from './keyed';\nimport { teardownNodeSubtree } from './cleanup';\nimport { applyRendererFastPath } from './fastpath';\nimport { getRuntimeEnv } from './env';\nimport type { ComponentFunction } from '../runtime/component';\nimport {\n evaluateCaseState,\n evaluateShowState,\n type ControlBoundaryState,\n} from '../runtime/control';\nimport { evaluateForState } from '../runtime/for';\nimport { __FOR_BOUNDARY__ } from '../common/vnode';\nimport {\n extractKey,\n checkPropChanges,\n recordFastPathStats,\n recordDOMReplace,\n tagNamesEqualIgnoreCase,\n} from './utils';\n\nexport const IS_DOM_AVAILABLE = typeof document !== 'undefined';\n\n// Helper type for narrowings\ntype VnodeObj = VNode & { type?: unknown; props?: Record<string, unknown> };\ntype ComponentVNode = DOMElement & { type: ComponentFunction };\n\nexport function reconcileKeyedChildren(\n parent: Element,\n newChildren: VNode[],\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> {\n const keyedVnodes = extractKeyedChildren(newChildren);\n\n // Ensure we have a key map before reconciliation to avoid O(n) DOM scans\n // during O(n) reconciliation loop (which would be O(n²))\n const ensuredOldKeyMap = oldKeyMap || buildKeyMapFromDOM(parent);\n\n // Try fast paths first\n const fastPathResult = tryFastPaths(\n parent,\n newChildren,\n keyedVnodes,\n ensuredOldKeyMap\n );\n if (fastPathResult) {\n return fastPathResult;\n }\n\n // Full reconciliation\n return performFullReconciliation(\n parent,\n newChildren,\n keyedVnodes,\n ensuredOldKeyMap\n );\n}\n\n/** Build key map from DOM children */\nfunction buildKeyMapFromDOM(parent: Element): Map<string | number, Element> {\n const keyMap = new Map<string | number, Element>();\n try {\n for (let el = parent.firstElementChild; el; el = el.nextElementSibling) {\n const k = el.getAttribute('data-key');\n if (k !== null) {\n keyMap.set(k, el);\n const n = Number(k);\n if (!Number.isNaN(n)) keyMap.set(n, el);\n }\n }\n } catch {\n // Ignore\n }\n return keyMap;\n}\n\n/** Extract keyed children in a single pass. */\nfunction extractKeyedChildren(\n newChildren: VNode[]\n): Array<{ key: string | number; vnode: VNode }> {\n const keyedVnodes: Array<{ key: string | number; vnode: VNode }> = [];\n\n for (let i = 0; i < newChildren.length; i++) {\n const child = newChildren[i];\n const key = extractKey(child);\n if (key !== undefined) {\n keyedVnodes.push({ key, vnode: child });\n }\n }\n\n return keyedVnodes;\n}\n\n/** Try fast paths before full reconciliation */\nfunction tryFastPaths(\n parent: Element,\n newChildren: VNode[],\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> | null {\n try {\n const forcedResult = tryForcedPositionalBulkUpdate(\n parent,\n newChildren,\n keyedVnodes\n );\n if (forcedResult) {\n return forcedResult;\n }\n\n // Try renderer fast-path for large keyed reorder-only updates\n const rendererResult = tryRendererFastPath(\n parent,\n keyedVnodes,\n newChildren.length,\n oldKeyMap\n );\n if (rendererResult) {\n return rendererResult;\n }\n\n // Try positional bulk update for medium-sized lists\n const positionalResult = tryPositionalBulkUpdate(parent, keyedVnodes);\n if (positionalResult) {\n return positionalResult;\n }\n } catch {\n // Fall through to full reconciliation\n }\n\n return null;\n}\n\n/** Try renderer fast-path */\nfunction tryRendererFastPath(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n totalChildren: number,\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> | null {\n const decision = planKeyedReorderFastPath(\n parent,\n keyedVnodes,\n totalChildren,\n oldKeyMap\n );\n\n if (decision.useFastPath) {\n try {\n const map = applyRendererFastPath(parent, keyedVnodes, oldKeyMap);\n if (map) {\n keyedElements.set(parent, map);\n return map;\n }\n } catch {\n // Fall through\n }\n }\n\n return null;\n}\n\n/** Try the explicit test/bench forced positional keyed path. */\nfunction tryForcedPositionalBulkUpdate(\n parent: Element,\n newChildren: VNode[],\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): Map<string | number, Element> | null {\n if (getRuntimeEnv().ASKR_FORCE_BULK_POSREUSE !== '1') return null;\n if (keyedVnodes.length === 0 || keyedVnodes.length !== newChildren.length) {\n return null;\n }\n\n try {\n const stats = performBulkPositionalKeyedTextUpdate(parent, keyedVnodes);\n recordFastPathStats(stats, 'bulkKeyedPositionalForced');\n\n rebuildKeyedMap(parent);\n return keyedElements.get(parent) as Map<string | number, Element>;\n } catch {\n return null;\n }\n}\n\n/** Try positional bulk update for medium-sized lists */\nfunction tryPositionalBulkUpdate(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): Map<string | number, Element> | null {\n const total = keyedVnodes.length;\n if (total < 10) return null;\n\n // CRITICAL INVARIANT: Only use children[] indexing if element-only is guaranteed\n // If parent has text nodes or comment nodes, bail to full reconciliation\n if (parent.children.length !== total) {\n return null;\n }\n\n const matchCount = countPositionalMatches(parent, keyedVnodes);\n\n // For keyed lists, the positional bulk update path makes sense in two cases:\n // 1. Perfect match (100%): All keys are in the right positions, just update text\n // 2. Very low match (<10%): Keys changed en-masse, treat as bulk re-key, reuse nodes by position\n // ANY mismatch at all means we have a reorder and need full reconciliation\n // to preserve DOM node identity correctly.\n const matchFraction = matchCount / total;\n\n if (keyedVnodes.length > 0) {\n // For keyed lists: require perfect match or very low match\n // matchCount !== total catches ANY reordering, even just 2 swapped elements\n if (matchCount !== total && matchFraction >= 0.1) {\n return null;\n }\n } else {\n // For unkeyed lists: use original threshold\n if (matchFraction < 0.9) {\n return null;\n }\n }\n\n // Check for prop changes that would prevent positional update\n if (hasPositionalPropChanges(parent, keyedVnodes)) {\n return null;\n }\n\n // Perform positional update\n try {\n const stats = performBulkPositionalKeyedTextUpdate(parent, keyedVnodes);\n recordFastPathStats(stats, 'bulkKeyedPositionalHits');\n\n rebuildKeyedMap(parent);\n return keyedElements.get(parent) as Map<string | number, Element>;\n } catch {\n return null;\n }\n}\n\n/** Count how many vnodes match parent children by position and tag */\nfunction countPositionalMatches(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): number {\n let matchCount = 0;\n\n try {\n // For keyed children, use children (elements only) since keyed nodes are elements\n for (let i = 0; i < keyedVnodes.length; i++) {\n const vnode = keyedVnodes[i].vnode as VnodeObj;\n const expectedKey = keyedVnodes[i].key;\n\n if (!vnode || typeof vnode !== 'object' || typeof vnode.type !== 'string')\n continue;\n\n const el = parent.children[i] as Element | undefined;\n if (!el) continue;\n\n // For keyed lists, check BOTH tag name AND key match\n if (tagNamesEqualIgnoreCase(el.tagName, vnode.type)) {\n // Check if the element at this position has the expected key\n const actualKey = el.getAttribute('data-key');\n const keyMatches =\n actualKey === String(expectedKey) ||\n (actualKey !== null &&\n !Number.isNaN(Number(actualKey)) &&\n Number(actualKey) === expectedKey);\n\n if (keyMatches) {\n matchCount++;\n }\n }\n }\n } catch {\n // Ignore\n }\n\n return matchCount;\n}\n\n/** Check if positional prop changes would prevent bulk update */\nfunction hasPositionalPropChanges(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): boolean {\n try {\n // For keyed children, use children (elements only) since keyed nodes are elements\n for (let i = 0; i < keyedVnodes.length; i++) {\n const vnode = keyedVnodes[i].vnode as VnodeObj;\n const el = parent.children[i] as Element | undefined;\n if (!el || !vnode || typeof vnode !== 'object') continue;\n\n if (checkPropChanges(el, vnode.props || {})) {\n return true;\n }\n }\n } catch {\n return true;\n }\n\n return false;\n}\n\n/** Rebuild keyed map from parent children */\nfunction rebuildKeyedMap(parent: Element): void {\n try {\n const map = new Map<string | number, Element>();\n for (let el = parent.firstElementChild; el; el = el.nextElementSibling) {\n const k = el.getAttribute('data-key');\n if (k !== null) {\n map.set(k, el);\n const n = Number(k);\n if (!Number.isNaN(n)) map.set(n, el);\n }\n }\n keyedElements.set(parent, map);\n } catch {\n // Ignore\n }\n}\n\n/** Perform full reconciliation when fast paths don't apply */\nfunction performFullReconciliation(\n parent: Element,\n newChildren: VNode[],\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> {\n const newKeyMap = new Map<string | number, Element>();\n const finalNodes: Node[] = [];\n const usedOldEls = new WeakSet<Node>();\n\n const resolveOldElOnce = createOldElResolver(parent, oldKeyMap, usedOldEls);\n const unkeyedEls = collectUnkeyedElements(parent);\n let unkeyedIndex = 0;\n const resolveUnkeyedOnce = (): Element | undefined => {\n while (unkeyedIndex < unkeyedEls.length) {\n const candidate = unkeyedEls[unkeyedIndex++];\n if (!usedOldEls.has(candidate)) return candidate;\n }\n return undefined;\n };\n\n // Positional reconciliation\n for (let i = 0; i < newChildren.length; i++) {\n const child = newChildren[i];\n const node = reconcileSingleChild(\n child,\n i,\n parent,\n resolveOldElOnce,\n resolveUnkeyedOnce,\n usedOldEls,\n newKeyMap\n );\n if (node) finalNodes.push(node);\n }\n\n // SSR guard\n if (typeof document === 'undefined') return newKeyMap;\n\n commitReconciliation(parent, finalNodes);\n keyedElements.delete(parent);\n\n return newKeyMap;\n}\n\n/** Create resolver for finding old elements by key */\nfunction createOldElResolver(\n parent: Element,\n oldKeyMap: Map<string | number, Element> | undefined,\n usedOldEls: WeakSet<Node>\n): (k: string | number) => Element | undefined {\n return (k: string | number) => {\n if (!oldKeyMap) return undefined;\n\n // Fast-path: directly from oldKeyMap\n const direct = oldKeyMap.get(k);\n if (direct && !usedOldEls.has(direct)) {\n usedOldEls.add(direct);\n return direct;\n }\n\n // Try string form\n const s = String(k);\n const byString = oldKeyMap.get(s);\n if (byString && !usedOldEls.has(byString)) {\n usedOldEls.add(byString);\n return byString;\n }\n\n // Try numeric form\n const n = Number(s);\n if (!Number.isNaN(n)) {\n const byNum = oldKeyMap.get(n);\n if (byNum && !usedOldEls.has(byNum)) {\n usedOldEls.add(byNum);\n return byNum;\n }\n }\n\n // Fallback: scan parent children\n return scanForElementByKey(parent, k, s, usedOldEls);\n };\n}\n\n/** Scan parent children for element with matching key */\nfunction scanForElementByKey(\n parent: Element,\n k: string | number,\n keyStr: string,\n usedOldEls: WeakSet<Node>\n): Element | undefined {\n try {\n for (let ch = parent.firstElementChild; ch; ch = ch.nextElementSibling) {\n if (usedOldEls.has(ch)) continue;\n const attr = ch.getAttribute('data-key');\n if (attr === keyStr) {\n usedOldEls.add(ch);\n return ch;\n }\n if (attr !== null) {\n const numAttr = Number(attr);\n if (!Number.isNaN(numAttr) && numAttr === (k as number)) {\n usedOldEls.add(ch);\n return ch;\n }\n }\n }\n } catch {\n // Ignore\n }\n return undefined;\n}\n\n/** Reconcile a single child */\nfunction reconcileSingleChild(\n child: VNode,\n index: number,\n parent: Element,\n resolveOldElOnce: (k: string | number) => Element | undefined,\n resolveUnkeyedOnce: () => Element | undefined,\n usedOldEls: WeakSet<Node>,\n newKeyMap: Map<string | number, Element>\n): Node | null {\n const resolvedControlBoundary = prepareControlBoundaryResolution(child);\n if (resolvedControlBoundary !== null) {\n if (resolvedControlBoundary.remount) {\n return createDOMNode(resolvedControlBoundary.vnode);\n }\n\n return reconcileSingleChild(\n resolvedControlBoundary.vnode,\n index,\n parent,\n resolveOldElOnce,\n resolveUnkeyedOnce,\n usedOldEls,\n newKeyMap\n );\n }\n\n // Keyed child\n const key = extractKey(child);\n\n if (key !== undefined) {\n return reconcileKeyedChild(child, key, parent, resolveOldElOnce, newKeyMap);\n }\n\n return reconcileUnkeyedChild(\n child,\n index,\n parent,\n resolveUnkeyedOnce,\n usedOldEls\n );\n}\n\n/** Reconcile a keyed child */\nfunction reconcileKeyedChild(\n child: VNode,\n key: string | number,\n parent: Element,\n resolveOldElOnce: (k: string | number) => Element | undefined,\n newKeyMap: Map<string | number, Element>\n): Node | null {\n const el = resolveOldElOnce(key);\n\n if (el && el.parentElement === parent) {\n // Strict keyed guarantee: if the element tag changes for an existing key,\n // replace the DOM node rather than mutating in place.\n try {\n const childObj = child as VnodeObj;\n if (\n childObj &&\n typeof childObj === 'object' &&\n typeof childObj.type === 'string'\n ) {\n if (tagNamesEqualIgnoreCase(el.tagName, childObj.type)) {\n updateElementFromVnode(el, child);\n newKeyMap.set(key, el);\n return el;\n }\n }\n if (isComponentVNode(child)) {\n const synced = syncComponentElement(\n el,\n child,\n child.type,\n ((child.props ?? {}) as Props) || {}\n );\n if (synced) {\n if (synced instanceof Element) newKeyMap.set(key, synced);\n return synced;\n }\n }\n } catch {\n // Fall through to replacement\n }\n }\n\n const dom = createDOMNode(child);\n if (dom) {\n if (dom instanceof Element) newKeyMap.set(key, dom);\n return dom;\n }\n\n return null;\n}\n\n/** Reconcile an unkeyed or primitive child */\nfunction reconcileUnkeyedChild(\n child: VNode,\n index: number,\n parent: Element,\n resolveUnkeyedOnce: () => Element | undefined,\n usedOldEls: WeakSet<Node>\n): Node | null {\n try {\n // Use childNodes (includes Text nodes) instead of children (elements only)\n const existing = parent.childNodes[index] as Node | undefined;\n\n // Primitive child: try to reuse Text node if available\n if (typeof child === 'string' || typeof child === 'number') {\n if (existing && existing.nodeType === 3) {\n // Text node: reuse in-place\n (existing as Text).data = String(child);\n usedOldEls.add(existing);\n return existing;\n }\n return createDOMNode(child);\n }\n\n if (existing instanceof Element) {\n const synced = trySyncComponentChild(existing, child, usedOldEls);\n if (synced) return synced;\n }\n\n // Element child matching existing unkeyed element\n if (existing instanceof Element && canReuseElement(existing, child)) {\n updateElementFromVnode(existing, child);\n usedOldEls.add(existing);\n return existing;\n }\n\n // Try to find available unkeyed element elsewhere\n const avail = resolveUnkeyedOnce();\n if (avail) {\n const reuseResult = tryReuseElement(avail, child, usedOldEls);\n if (reuseResult) return reuseResult;\n }\n } catch {\n // Fall through to create new\n }\n\n const dom = createDOMNode(child);\n return dom;\n}\n\nfunction isComponentVNode(child: VNode): child is ComponentVNode {\n return (\n typeof child === 'object' &&\n child !== null &&\n 'type' in child &&\n typeof (child as VnodeObj).type === 'function'\n );\n}\n\nfunction isControlBoundaryVNode(child: VNode): child is DOMElement {\n return (\n typeof child === 'object' &&\n child !== null &&\n 'type' in child &&\n (child as VnodeObj).type === __FOR_BOUNDARY__\n );\n}\n\nfunction prepareControlBoundaryResolution(child: VNode): {\n remount: boolean;\n vnode: VNode | null;\n} | null {\n if (!isControlBoundaryVNode(child)) {\n return null;\n }\n\n const controlState = child._controlState ?? child._forState;\n if (!controlState) {\n return null;\n }\n if (controlState.kind === 'for') {\n const childrenVNodes = evaluateControlBoundaryChildren(controlState);\n return childrenVNodes.length === 1\n ? { remount: false, vnode: childrenVNodes[0] ?? null }\n : null;\n }\n\n const previousActiveKey = controlState.activeKey;\n const childrenVNodes = evaluateControlBoundaryChildren(controlState);\n\n return {\n remount:\n previousActiveKey !== null &&\n controlState.activeKey !== previousActiveKey,\n vnode: childrenVNodes[0] ?? null,\n };\n}\n\nfunction evaluateControlBoundaryChildren(\n controlState: ControlBoundaryState\n): VNode[] {\n if (controlState.kind === 'for') {\n return evaluateForState(controlState);\n }\n\n if (controlState.kind === 'show') {\n return evaluateShowState(controlState);\n }\n\n return evaluateCaseState(controlState);\n}\n\nfunction trySyncComponentChild(\n existing: Element,\n child: VNode,\n usedOldEls: WeakSet<Node>\n): Node | null {\n if (!isComponentVNode(child)) return null;\n\n const synced = syncComponentElement(\n existing,\n child,\n child.type,\n ((child.props ?? {}) as Props) || {}\n );\n if (!synced) return null;\n\n usedOldEls.add(existing);\n usedOldEls.add(synced);\n return synced;\n}\n\n/** Check if existing element can be reused for child */\nfunction canReuseElement(existing: Element | undefined, child: VNode): boolean {\n if (!existing) return false;\n if (typeof child !== 'object' || child === null || !('type' in child))\n return false;\n\n const childObj = child as VnodeObj;\n const existingKey = existing.getAttribute('data-key');\n const hasNoKey = existingKey === null || existingKey === undefined;\n\n return (\n hasNoKey &&\n typeof childObj.type === 'string' &&\n tagNamesEqualIgnoreCase(existing.tagName, childObj.type)\n );\n}\n\n/** Collect unkeyed element children in DOM order. */\nfunction collectUnkeyedElements(parent: Element): Element[] {\n const elements: Element[] = [];\n for (let ch = parent.firstElementChild; ch; ch = ch.nextElementSibling) {\n if (ch.getAttribute('data-key') === null) elements.push(ch);\n }\n return elements;\n}\n\n/** Try to reuse available element for child */\nfunction tryReuseElement(\n avail: Element,\n child: VNode,\n usedOldEls: WeakSet<Node>\n): Node | null {\n if (typeof child === 'string' || typeof child === 'number') {\n return null;\n }\n\n const synced = trySyncComponentChild(avail, child, usedOldEls);\n if (synced) {\n return synced;\n }\n\n if (typeof child === 'object' && child !== null && 'type' in child) {\n const childObj = child as VnodeObj;\n if (\n typeof childObj.type === 'string' &&\n tagNamesEqualIgnoreCase(avail.tagName, childObj.type)\n ) {\n updateElementFromVnode(avail, child);\n usedOldEls.add(avail);\n return avail;\n }\n }\n\n return null;\n}\n\n/** Commit reconciliation by replacing parent children */\nfunction commitReconciliation(parent: Element, finalNodes: Node[]): void {\n try {\n const finalSet = new Set<Node>(finalNodes);\n\n for (let n = parent.firstChild; n; ) {\n const next = n.nextSibling;\n if (!finalSet.has(n)) {\n teardownNodeSubtree(n);\n parent.removeChild(n);\n }\n n = next;\n }\n\n for (let i = 0; i < finalNodes.length; i++) {\n const desiredNode = finalNodes[i];\n const anchor = parent.childNodes[i] ?? null;\n if (desiredNode !== anchor) {\n parent.insertBefore(desiredNode, anchor);\n }\n }\n } catch {\n const fragment = document.createDocumentFragment();\n for (let i = 0; i < finalNodes.length; i++) {\n fragment.appendChild(finalNodes[i]);\n }\n\n try {\n for (let n = parent.firstChild; n; ) {\n const next = n.nextSibling;\n teardownNodeSubtree(n);\n n = next;\n }\n } catch {\n // Ignore fallback cleanup failures.\n }\n\n recordDOMReplace('reconcile');\n parent.replaceChildren(fragment);\n return;\n }\n}\n"],"mappings":"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"}
@@ -2,12 +2,14 @@ import { getRuntimeEnv } from "../common/env.js";
2
2
  import { logger } from "../dev/logger.js";
3
3
  import { globalScheduler } from "../runtime/scheduler.js";
4
4
  import { incDevCounter, setDevValue } from "../runtime/dev-namespace.js";
5
+ import { getPublicAttributeName } from "../common/attr-names.js";
5
6
  import "./env.js";
6
7
  //#region src/renderer/utils.ts
7
8
  /**
8
9
  * Shared utilities for the renderer module.
9
10
  * Consolidates common patterns to reduce code duplication.
10
11
  */
12
+ const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
11
13
  const DIRECT_REPLACE_CHILDREN_SPREAD_LIMIT = 4096;
12
14
  function canUseDirectReplaceChildrenSpread(count) {
13
15
  return count <= DIRECT_REPLACE_CHILDREN_SPREAD_LIMIT;
@@ -19,8 +21,27 @@ const MAX_CACHE_SIZE = 1e3;
19
21
  * Parse an event prop name (e.g., 'onClick') to its DOM event name (e.g., 'click')
20
22
  */
21
23
  function parseEventName(propName) {
24
+ return parseEventProp(propName)?.eventName ?? null;
25
+ }
26
+ function parseEventProp(propName) {
22
27
  if (!propName.startsWith("on") || propName.length <= 2) return null;
23
- return propName.slice(2).charAt(0).toLowerCase() + propName.slice(3).toLowerCase();
28
+ const capture = propName.endsWith("Capture") && propName.length > 9;
29
+ const normalizedPropName = capture ? propName.slice(0, -7) : propName;
30
+ return normalizedPropName.length <= 2 ? null : {
31
+ eventName: normalizedPropName.slice(2).charAt(0).toLowerCase() + normalizedPropName.slice(3).toLowerCase(),
32
+ capture
33
+ };
34
+ }
35
+ function getEventListenerKey(eventName, capture = false) {
36
+ return capture ? `${eventName}:capture` : eventName;
37
+ }
38
+ function getEventListenerOptions(eventName, capture = false) {
39
+ const passiveOptions = getPassiveOptions(eventName);
40
+ if (!capture) return passiveOptions;
41
+ return passiveOptions ? {
42
+ ...passiveOptions,
43
+ capture: true
44
+ } : { capture: true };
24
45
  }
25
46
  /**
26
47
  * Get default event listener options for passive events
@@ -106,7 +127,7 @@ function hasPropChanged(el, key, value) {
106
127
  try {
107
128
  if (key === "class" || key === "className") return readElementClassName(el) !== String(value);
108
129
  if (key === "value" || key === "checked") return el[key] !== value;
109
- const attr = el.getAttribute(key);
130
+ const attr = el.getAttribute(getRenderedAttributeName(el, key));
110
131
  if (value === void 0 || value === null || value === false) return attr !== null;
111
132
  return String(value) !== attr;
112
133
  } catch {
@@ -116,6 +137,16 @@ function hasPropChanged(el, key, value) {
116
137
  function isSVGDomElement(el) {
117
138
  return typeof SVGElement !== "undefined" && el instanceof SVGElement;
118
139
  }
140
+ function getRenderedAttributeName(el, propName) {
141
+ const attributeName = getPublicAttributeName(propName);
142
+ return el.namespaceURI === SVG_NAMESPACE ? attributeName : attributeName.toLowerCase();
143
+ }
144
+ function setRenderedAttribute(el, propName, value) {
145
+ el.setAttribute(getRenderedAttributeName(el, propName), value);
146
+ }
147
+ function removeRenderedAttribute(el, propName) {
148
+ el.removeAttribute(getRenderedAttributeName(el, propName));
149
+ }
119
150
  function readElementClassName(el) {
120
151
  if (isSVGDomElement(el)) return el.getAttribute("class") ?? "";
121
152
  return el.className;
@@ -219,6 +250,6 @@ function now() {
219
250
  return typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
220
251
  }
221
252
  //#endregion
222
- export { buildKeyMapFromChildren, canUseDirectReplaceChildrenSpread, checkPropChanges, createMutableWrappedHandler, createWrappedHandler, extractKey, getPassiveOptions, hasNonTrivialProps, hasPropChanged, isIgnoredForPropChanges, isSkippedProp, logFastPathDebug, now, parseEventName, readElementClassName, recordDOMReplace, recordFastPathStats, tagNamesEqualIgnoreCase, writeElementClassName };
253
+ export { buildKeyMapFromChildren, canUseDirectReplaceChildrenSpread, checkPropChanges, createMutableWrappedHandler, createWrappedHandler, extractKey, getEventListenerKey, getEventListenerOptions, getRenderedAttributeName, hasNonTrivialProps, hasPropChanged, isIgnoredForPropChanges, isSkippedProp, logFastPathDebug, now, parseEventName, parseEventProp, readElementClassName, recordDOMReplace, recordFastPathStats, removeRenderedAttribute, setRenderedAttribute, tagNamesEqualIgnoreCase, writeElementClassName };
223
254
 
224
255
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../src/renderer/utils.ts"],"sourcesContent":["/**\n * Shared utilities for the renderer module.\n * Consolidates common patterns to reduce code duplication.\n */\n\nimport { globalScheduler } from '../runtime/scheduler';\nimport { logger } from '../dev/logger';\nimport { getRuntimeEnv } from './env';\nimport { setDevValue, incDevCounter } from '../runtime/dev-namespace';\n\n// Keep direct replaceChildren(...nodes) commits below a conservative argument\n// count until browser benchmarks prove larger spreads are safe.\nexport const DIRECT_REPLACE_CHILDREN_SPREAD_LIMIT = 4096;\n\nexport function canUseDirectReplaceChildrenSpread(count: number): boolean {\n return count <= DIRECT_REPLACE_CHILDREN_SPREAD_LIMIT;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ListenerEntry {\n handler: EventListener;\n original: EventListener;\n options?: boolean | AddEventListenerOptions;\n updateHandler?: (nextHandler: EventListener) => void;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Event Handler Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Handler pooling cache to avoid recreating wrapped handlers\n// Key: handler reference, Value: { wrapped: EventListener, flushAfter: boolean }\nconst handlerCache = new WeakMap<EventListener, Map<boolean, EventListener>>();\nlet cacheSize = 0;\nconst MAX_CACHE_SIZE = 1000;\n\n/**\n * Parse an event prop name (e.g., 'onClick') to its DOM event name (e.g., 'click')\n */\nexport function parseEventName(propName: string): string | null {\n if (!propName.startsWith('on') || propName.length <= 2) return null;\n return (\n propName.slice(2).charAt(0).toLowerCase() + propName.slice(3).toLowerCase()\n );\n}\n\n/**\n * Get default event listener options for passive events\n */\nexport function getPassiveOptions(\n eventName: string\n): AddEventListenerOptions | undefined {\n if (\n eventName === 'wheel' ||\n eventName === 'scroll' ||\n eventName.startsWith('touch')\n ) {\n return { passive: true };\n }\n return undefined;\n}\n\n/**\n * Create a wrapped event handler that integrates with the scheduler\n * Uses caching to avoid recreating wrappers for the same handler\n */\nexport function createWrappedHandler(\n handler: EventListener,\n flushAfter = false\n): EventListener {\n // Check cache first\n const cachedByFlush = handlerCache.get(handler);\n if (cachedByFlush) {\n const cached = cachedByFlush.get(flushAfter);\n if (cached) return cached;\n }\n\n const wrapped: EventListener = (event: Event) => {\n try {\n globalScheduler.runInHandlerScope(\n () => {\n try {\n handler(event);\n } catch (error) {\n logger.error('[Askr] Event handler error:', error);\n }\n },\n flushAfter ? 'sync' : 'defer'\n );\n } catch (err) {\n if (flushAfter) {\n queueMicrotask(() => {\n throw err;\n });\n } else {\n logger.error('[Askr] Event handler error:', err);\n }\n }\n };\n\n // Cache the wrapped handler\n try {\n if (!handlerCache.has(handler)) {\n // Limit cache size to prevent memory leaks\n if (cacheSize >= MAX_CACHE_SIZE) {\n // Simple eviction - just don't cache new entries when full\n } else {\n cacheSize++;\n handlerCache.set(handler, new Map());\n }\n }\n handlerCache.get(handler)?.set(flushAfter, wrapped);\n } catch {\n // WeakMap can throw if handler is not a valid key\n }\n\n return wrapped;\n}\n\nexport function createMutableWrappedHandler(\n handler: EventListener,\n flushAfter = false\n): {\n handler: EventListener;\n updateHandler: (nextHandler: EventListener) => void;\n} {\n let currentHandler = handler;\n\n const wrapped: EventListener = (event: Event) => {\n try {\n globalScheduler.runInHandlerScope(\n () => {\n try {\n currentHandler(event);\n } catch (error) {\n logger.error('[Askr] Event handler error:', error);\n }\n },\n flushAfter ? 'sync' : 'defer'\n );\n } catch (err) {\n if (flushAfter) {\n queueMicrotask(() => {\n throw err;\n });\n } else {\n logger.error('[Askr] Event handler error:', err);\n }\n }\n };\n\n return {\n handler: wrapped,\n updateHandler(nextHandler: EventListener) {\n currentHandler = nextHandler;\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Prop/Attribute Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Props that should be skipped during attribute processing */\nexport function isSkippedProp(key: string): boolean {\n return key === 'children' || key === 'key' || key === 'ref';\n}\n\n/** Check if prop should be ignored for prop-change detection */\nexport function isIgnoredForPropChanges(key: string): boolean {\n if (key === 'children' || key === 'key') return true;\n if (key.startsWith('on') && key.length > 2) return true;\n if (key.startsWith('data-')) return true;\n return false;\n}\n\n/**\n * Check if an element's current attribute value differs from vnode value\n */\nexport function hasPropChanged(\n el: Element,\n key: string,\n value: unknown\n): boolean {\n try {\n if (key === 'class' || key === 'className') {\n return readElementClassName(el) !== String(value);\n }\n if (key === 'value' || key === 'checked') {\n return (el as HTMLElement & Record<string, unknown>)[key] !== value;\n }\n const attr = el.getAttribute(key);\n if (value === undefined || value === null || value === false) {\n return attr !== null;\n }\n return String(value) !== attr;\n } catch {\n return true;\n }\n}\n\nexport function isSVGDomElement(el: Element): el is SVGElement {\n return typeof SVGElement !== 'undefined' && el instanceof SVGElement;\n}\n\nexport function readElementClassName(el: Element): string {\n if (isSVGDomElement(el)) {\n return el.getAttribute('class') ?? '';\n }\n\n return (el as HTMLElement).className;\n}\n\nexport function writeElementClassName(el: Element, value: string): void {\n if (isSVGDomElement(el)) {\n if (value.length > 0) {\n el.setAttribute('class', value);\n } else {\n el.removeAttribute('class');\n }\n return;\n }\n\n (el as HTMLElement).className = value;\n}\n\nexport function tagNamesEqualIgnoreCase(a: string, b: string): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n\n for (let index = 0; index < a.length; index += 1) {\n const aCode = a.charCodeAt(index);\n const bCode = b.charCodeAt(index);\n if (aCode === bCode) continue;\n\n const normalizedA = aCode >= 65 && aCode <= 90 ? aCode + 32 : aCode;\n const normalizedB = bCode >= 65 && bCode <= 90 ? bCode + 32 : bCode;\n if (normalizedA !== normalizedB) return false;\n }\n\n return true;\n}\n\n/**\n * Check if a vnode has non-trivial props (excluding events and data-*)\n */\nexport function hasNonTrivialProps(props: Record<string, unknown>): boolean {\n for (const k of Object.keys(props)) {\n if (isIgnoredForPropChanges(k)) continue;\n return true;\n }\n return false;\n}\n\n/**\n * Check for prop changes between vnode and existing element\n */\nexport function checkPropChanges(\n el: Element,\n props: Record<string, unknown>\n): boolean {\n for (const k of Object.keys(props)) {\n if (isIgnoredForPropChanges(k)) continue;\n if (hasPropChanged(el, k, props[k])) {\n return true;\n }\n }\n return false;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Key Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Extract key from a vnode object\n */\nexport function extractKey(vnode: unknown): string | number | undefined {\n if (typeof vnode !== 'object' || vnode === null) return undefined;\n const obj = vnode as Record<string, unknown>;\n const rawKey =\n obj.key ?? (obj.props as Record<string, unknown> | undefined)?.key;\n if (rawKey === undefined || rawKey === null) return undefined;\n return typeof rawKey === 'symbol'\n ? String(rawKey)\n : (rawKey as string | number);\n}\n\n/**\n * Build a key map from element's children\n */\nexport function buildKeyMapFromChildren(\n parent: Element\n): Map<string | number, Element> {\n const map = new Map<string | number, Element>();\n for (let ch = parent.firstElementChild; ch; ch = ch.nextElementSibling) {\n const k = ch.getAttribute('data-key');\n if (k !== null) {\n map.set(k, ch);\n const n = Number(k);\n if (!Number.isNaN(n)) map.set(n, ch);\n }\n }\n return map;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Diagnostic Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Record DOM replace operation for diagnostics\n */\nexport function recordDOMReplace(source: string): void {\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(`__LAST_DOM_REPLACE_STACK_${source}`, new Error().stack);\n } catch {\n // ignore\n }\n}\n\n/**\n * Record fast-path stats for diagnostics\n */\nexport function recordFastPathStats(\n stats: Record<string, unknown>,\n counterName?: string\n): void {\n try {\n setDevValue('__LAST_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 1);\n if (counterName) {\n incDevCounter(counterName);\n }\n } catch {\n // ignore\n }\n}\n\n/**\n * Conditionally log debug info for fast-path operations\n */\nexport function logFastPathDebug(\n message: string,\n indexOrData?: number | unknown,\n data?: unknown\n): void {\n const env = getRuntimeEnv();\n if (env.ASKR_FASTPATH_DEBUG === '1' || env.ASKR_FASTPATH_DEBUG === 'true') {\n if (data !== undefined) {\n logger.warn(`[Askr][FASTPATH] ${message}`, indexOrData, data);\n } else if (indexOrData !== undefined) {\n logger.warn(`[Askr][FASTPATH] ${message}`, indexOrData);\n } else {\n logger.warn(`[Askr][FASTPATH] ${message}`);\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Performance Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Get current high-resolution timestamp\n */\nexport function now(): number {\n return typeof performance !== 'undefined' && performance.now\n ? performance.now()\n : Date.now();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../src/renderer/utils.ts"],"sourcesContent":["/**\n * Shared utilities for the renderer module.\n * Consolidates common patterns to reduce code duplication.\n */\n\nimport { globalScheduler } from '../runtime/scheduler';\nimport { logger } from '../dev/logger';\nimport { getPublicAttributeName } from '../common/attr-names';\nimport { getRuntimeEnv } from './env';\nimport { setDevValue, incDevCounter } from '../runtime/dev-namespace';\n\nconst SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\n// Keep direct replaceChildren(...nodes) commits below a conservative argument\n// count until browser benchmarks prove larger spreads are safe.\nexport const DIRECT_REPLACE_CHILDREN_SPREAD_LIMIT = 4096;\n\nexport function canUseDirectReplaceChildrenSpread(count: number): boolean {\n return count <= DIRECT_REPLACE_CHILDREN_SPREAD_LIMIT;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ListenerEntry {\n handler: EventListener;\n original: EventListener;\n eventName: string;\n options?: boolean | AddEventListenerOptions;\n updateHandler?: (nextHandler: EventListener) => void;\n}\n\nexport interface ParsedEventProp {\n eventName: string;\n capture: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Event Handler Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Handler pooling cache to avoid recreating wrapped handlers\n// Key: handler reference, Value: { wrapped: EventListener, flushAfter: boolean }\nconst handlerCache = new WeakMap<EventListener, Map<boolean, EventListener>>();\nlet cacheSize = 0;\nconst MAX_CACHE_SIZE = 1000;\n\n/**\n * Parse an event prop name (e.g., 'onClick') to its DOM event name (e.g., 'click')\n */\nexport function parseEventName(propName: string): string | null {\n return parseEventProp(propName)?.eventName ?? null;\n}\n\nexport function parseEventProp(propName: string): ParsedEventProp | null {\n if (!propName.startsWith('on') || propName.length <= 2) return null;\n const capture =\n propName.endsWith('Capture') && propName.length > 'onCapture'.length;\n const normalizedPropName = capture\n ? propName.slice(0, -'Capture'.length)\n : propName;\n\n return normalizedPropName.length <= 2\n ? null\n : {\n eventName:\n normalizedPropName.slice(2).charAt(0).toLowerCase() +\n normalizedPropName.slice(3).toLowerCase(),\n capture,\n };\n}\n\nexport function getEventListenerKey(\n eventName: string,\n capture = false\n): string {\n return capture ? `${eventName}:capture` : eventName;\n}\n\nexport function getEventListenerOptions(\n eventName: string,\n capture = false\n): AddEventListenerOptions | undefined {\n const passiveOptions = getPassiveOptions(eventName);\n if (!capture) {\n return passiveOptions;\n }\n\n return passiveOptions\n ? { ...passiveOptions, capture: true }\n : { capture: true };\n}\n\n/**\n * Get default event listener options for passive events\n */\nexport function getPassiveOptions(\n eventName: string\n): AddEventListenerOptions | undefined {\n if (\n eventName === 'wheel' ||\n eventName === 'scroll' ||\n eventName.startsWith('touch')\n ) {\n return { passive: true };\n }\n return undefined;\n}\n\n/**\n * Create a wrapped event handler that integrates with the scheduler\n * Uses caching to avoid recreating wrappers for the same handler\n */\nexport function createWrappedHandler(\n handler: EventListener,\n flushAfter = false\n): EventListener {\n // Check cache first\n const cachedByFlush = handlerCache.get(handler);\n if (cachedByFlush) {\n const cached = cachedByFlush.get(flushAfter);\n if (cached) return cached;\n }\n\n const wrapped: EventListener = (event: Event) => {\n try {\n globalScheduler.runInHandlerScope(\n () => {\n try {\n handler(event);\n } catch (error) {\n logger.error('[Askr] Event handler error:', error);\n }\n },\n flushAfter ? 'sync' : 'defer'\n );\n } catch (err) {\n if (flushAfter) {\n queueMicrotask(() => {\n throw err;\n });\n } else {\n logger.error('[Askr] Event handler error:', err);\n }\n }\n };\n\n // Cache the wrapped handler\n try {\n if (!handlerCache.has(handler)) {\n // Limit cache size to prevent memory leaks\n if (cacheSize >= MAX_CACHE_SIZE) {\n // Simple eviction - just don't cache new entries when full\n } else {\n cacheSize++;\n handlerCache.set(handler, new Map());\n }\n }\n handlerCache.get(handler)?.set(flushAfter, wrapped);\n } catch {\n // WeakMap can throw if handler is not a valid key\n }\n\n return wrapped;\n}\n\nexport function createMutableWrappedHandler(\n handler: EventListener,\n flushAfter = false\n): {\n handler: EventListener;\n updateHandler: (nextHandler: EventListener) => void;\n} {\n let currentHandler = handler;\n\n const wrapped: EventListener = (event: Event) => {\n try {\n globalScheduler.runInHandlerScope(\n () => {\n try {\n currentHandler(event);\n } catch (error) {\n logger.error('[Askr] Event handler error:', error);\n }\n },\n flushAfter ? 'sync' : 'defer'\n );\n } catch (err) {\n if (flushAfter) {\n queueMicrotask(() => {\n throw err;\n });\n } else {\n logger.error('[Askr] Event handler error:', err);\n }\n }\n };\n\n return {\n handler: wrapped,\n updateHandler(nextHandler: EventListener) {\n currentHandler = nextHandler;\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Prop/Attribute Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Props that should be skipped during attribute processing */\nexport function isSkippedProp(key: string): boolean {\n return key === 'children' || key === 'key' || key === 'ref';\n}\n\n/** Check if prop should be ignored for prop-change detection */\nexport function isIgnoredForPropChanges(key: string): boolean {\n if (key === 'children' || key === 'key') return true;\n if (key.startsWith('on') && key.length > 2) return true;\n if (key.startsWith('data-')) return true;\n return false;\n}\n\n/**\n * Check if an element's current attribute value differs from vnode value\n */\nexport function hasPropChanged(\n el: Element,\n key: string,\n value: unknown\n): boolean {\n try {\n if (key === 'class' || key === 'className') {\n return readElementClassName(el) !== String(value);\n }\n if (key === 'value' || key === 'checked') {\n return (el as HTMLElement & Record<string, unknown>)[key] !== value;\n }\n const attr = el.getAttribute(getRenderedAttributeName(el, key));\n if (value === undefined || value === null || value === false) {\n return attr !== null;\n }\n return String(value) !== attr;\n } catch {\n return true;\n }\n}\n\nexport function isSVGDomElement(el: Element): el is SVGElement {\n return typeof SVGElement !== 'undefined' && el instanceof SVGElement;\n}\n\nexport function getRenderedAttributeName(\n el: Element,\n propName: string\n): string {\n const attributeName = getPublicAttributeName(propName);\n\n return el.namespaceURI === SVG_NAMESPACE\n ? attributeName\n : attributeName.toLowerCase();\n}\n\nexport function setRenderedAttribute(\n el: Element,\n propName: string,\n value: string\n): void {\n el.setAttribute(getRenderedAttributeName(el, propName), value);\n}\n\nexport function removeRenderedAttribute(el: Element, propName: string): void {\n el.removeAttribute(getRenderedAttributeName(el, propName));\n}\n\nexport function readElementClassName(el: Element): string {\n if (isSVGDomElement(el)) {\n return el.getAttribute('class') ?? '';\n }\n\n return (el as HTMLElement).className;\n}\n\nexport function writeElementClassName(el: Element, value: string): void {\n if (isSVGDomElement(el)) {\n if (value.length > 0) {\n el.setAttribute('class', value);\n } else {\n el.removeAttribute('class');\n }\n return;\n }\n\n (el as HTMLElement).className = value;\n}\n\nexport function tagNamesEqualIgnoreCase(a: string, b: string): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n\n for (let index = 0; index < a.length; index += 1) {\n const aCode = a.charCodeAt(index);\n const bCode = b.charCodeAt(index);\n if (aCode === bCode) continue;\n\n const normalizedA = aCode >= 65 && aCode <= 90 ? aCode + 32 : aCode;\n const normalizedB = bCode >= 65 && bCode <= 90 ? bCode + 32 : bCode;\n if (normalizedA !== normalizedB) return false;\n }\n\n return true;\n}\n\n/**\n * Check if a vnode has non-trivial props (excluding events and data-*)\n */\nexport function hasNonTrivialProps(props: Record<string, unknown>): boolean {\n for (const k of Object.keys(props)) {\n if (isIgnoredForPropChanges(k)) continue;\n return true;\n }\n return false;\n}\n\n/**\n * Check for prop changes between vnode and existing element\n */\nexport function checkPropChanges(\n el: Element,\n props: Record<string, unknown>\n): boolean {\n for (const k of Object.keys(props)) {\n if (isIgnoredForPropChanges(k)) continue;\n if (hasPropChanged(el, k, props[k])) {\n return true;\n }\n }\n return false;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Key Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Extract key from a vnode object\n */\nexport function extractKey(vnode: unknown): string | number | undefined {\n if (typeof vnode !== 'object' || vnode === null) return undefined;\n const obj = vnode as Record<string, unknown>;\n const rawKey =\n obj.key ?? (obj.props as Record<string, unknown> | undefined)?.key;\n if (rawKey === undefined || rawKey === null) return undefined;\n return typeof rawKey === 'symbol'\n ? String(rawKey)\n : (rawKey as string | number);\n}\n\n/**\n * Build a key map from element's children\n */\nexport function buildKeyMapFromChildren(\n parent: Element\n): Map<string | number, Element> {\n const map = new Map<string | number, Element>();\n for (let ch = parent.firstElementChild; ch; ch = ch.nextElementSibling) {\n const k = ch.getAttribute('data-key');\n if (k !== null) {\n map.set(k, ch);\n const n = Number(k);\n if (!Number.isNaN(n)) map.set(n, ch);\n }\n }\n return map;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Diagnostic Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Record DOM replace operation for diagnostics\n */\nexport function recordDOMReplace(source: string): void {\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(`__LAST_DOM_REPLACE_STACK_${source}`, new Error().stack);\n } catch {\n // ignore\n }\n}\n\n/**\n * Record fast-path stats for diagnostics\n */\nexport function recordFastPathStats(\n stats: Record<string, unknown>,\n counterName?: string\n): void {\n try {\n setDevValue('__LAST_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 1);\n if (counterName) {\n incDevCounter(counterName);\n }\n } catch {\n // ignore\n }\n}\n\n/**\n * Conditionally log debug info for fast-path operations\n */\nexport function logFastPathDebug(\n message: string,\n indexOrData?: number | unknown,\n data?: unknown\n): void {\n const env = getRuntimeEnv();\n if (env.ASKR_FASTPATH_DEBUG === '1' || env.ASKR_FASTPATH_DEBUG === 'true') {\n if (data !== undefined) {\n logger.warn(`[Askr][FASTPATH] ${message}`, indexOrData, data);\n } else if (indexOrData !== undefined) {\n logger.warn(`[Askr][FASTPATH] ${message}`, indexOrData);\n } else {\n logger.warn(`[Askr][FASTPATH] ${message}`);\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Performance Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Get current high-resolution timestamp\n */\nexport function now(): number {\n return typeof performance !== 'undefined' && performance.now\n ? performance.now()\n : Date.now();\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { AccessDecision, AccessDenyDecision, AccessRedirectDecision, GroupHelperOptions, LayoutScopeRecord, PageHelperOptions, PageScopeRecord, ParsedSegment, RegisterRoutesOptions, Route, RouteAuthMode, RouteAuthOptions, RouteAuthResolver, RouteAuthState, RouteComponent, RouteContext, RouteDefinition, RouteHandler, RouteManifest, RouteMatch, RouteMode, RouteOptions, RoutePolicy, RouteQuery, RouteRecord, RouteRenderResult, RouteRequestOptions, RouteRequestResult, RouteSnapshot } from "../common/router.js";
1
+ import { AccessDecision, AccessDenyDecision, AccessRedirectDecision, GroupHelperOptions, LayoutScopeRecord, PageHelperOptions, PageScopeRecord, ParsedSegment, RegisterRoutesOptions, Route, RouteAuthMode, RouteAuthOptions, RouteAuthResolver, RouteAuthState, RouteComponent, RouteContext, RouteDefinition, RouteHandler, RouteManifest, RouteMatch, RouteMode, RouteOptions, RouteParams, RoutePathParams, RoutePolicy, RouteQuery, RouteRecord, RouteRenderResult, RouteRequestOptions, RouteRequestResult, RouteSnapshot } from "../common/router.js";
2
2
  import { HistoryScrollBehavior, NavigateOptions, NavigationScrollBehavior, ScrollRestorationOptions, navigate } from "./navigate.js";
3
3
  import { Outlet, clearRoutes, currentRoute, fallback, getManifest, getRoutes, group, index, lazy, page, registerRoutes, route } from "./route.js";
4
4
  import { allow, deny, forbidden, notFound, redirect, requireAuth, requirePermission, requireRole, unauthorized } from "./policy.js";
5
5
  import { Link, LinkProps } from "../components/link.js";
6
- export { type AccessDecision, type AccessDenyDecision, type AccessRedirectDecision, type GroupHelperOptions, type HistoryScrollBehavior, type LayoutScopeRecord, Link, type LinkProps, type NavigateOptions, type NavigationScrollBehavior, Outlet, type PageHelperOptions, type PageScopeRecord, type ParsedSegment, type RegisterRoutesOptions, type Route, type RouteAuthMode, type RouteAuthOptions, type RouteAuthResolver, type RouteAuthState, type RouteComponent, type RouteContext, type RouteDefinition, type RouteHandler, type RouteManifest, type RouteMatch, type RouteMode, type RouteOptions, type RoutePolicy, type RouteQuery, type RouteRecord, type RouteRenderResult, type RouteRequestOptions, type RouteRequestResult, type RouteSnapshot, type ScrollRestorationOptions, allow, clearRoutes, currentRoute, deny, fallback, forbidden, getManifest, getRoutes, group, index, lazy, navigate, notFound, page, redirect, registerRoutes, requireAuth, requirePermission, requireRole, route, unauthorized };
6
+ export { type AccessDecision, type AccessDenyDecision, type AccessRedirectDecision, type GroupHelperOptions, type HistoryScrollBehavior, type LayoutScopeRecord, Link, type LinkProps, type NavigateOptions, type NavigationScrollBehavior, Outlet, type PageHelperOptions, type PageScopeRecord, type ParsedSegment, type RegisterRoutesOptions, type Route, type RouteAuthMode, type RouteAuthOptions, type RouteAuthResolver, type RouteAuthState, type RouteComponent, type RouteContext, type RouteDefinition, type RouteHandler, type RouteManifest, type RouteMatch, type RouteMode, type RouteOptions, type RouteParams, type RoutePathParams, type RoutePolicy, type RouteQuery, type RouteRecord, type RouteRenderResult, type RouteRequestOptions, type RouteRequestResult, type RouteSnapshot, type ScrollRestorationOptions, allow, clearRoutes, currentRoute, deny, fallback, forbidden, getManifest, getRoutes, group, index, lazy, navigate, notFound, page, redirect, registerRoutes, requireAuth, requirePermission, requireRole, route, unauthorized };
@@ -1,4 +1,10 @@
1
1
  //#region src/router/match.ts
2
+ function splitPathSegments(path) {
3
+ const normalized = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
4
+ if (!normalized || normalized === "/") return [];
5
+ const withoutLeadingSlash = normalized.startsWith("/") ? normalized.slice(1) : normalized;
6
+ return withoutLeadingSlash.length === 0 ? [] : withoutLeadingSlash.split("/");
7
+ }
2
8
  /**
3
9
  * Parse a route template path into typed segments.
4
10
  *
@@ -48,6 +54,14 @@ Object.freeze({
48
54
  matched: false,
49
55
  params: emptyParams
50
56
  });
57
+ function decodeRouteParam(part) {
58
+ if (!part.includes("%")) return part;
59
+ try {
60
+ return decodeURIComponent(part);
61
+ } catch {
62
+ return part;
63
+ }
64
+ }
51
65
  /**
52
66
  * Match pre-split URL parts against pre-parsed route segments.
53
67
  *
@@ -71,13 +85,13 @@ function matchSegments(urlParts, segments) {
71
85
  if (seg.value !== part) return null;
72
86
  } else {
73
87
  if (params === null) params = {};
74
- if (seg.kind === "param") params[seg.value] = part.includes("%") ? decodeURIComponent(part) : part;
88
+ if (seg.kind === "param") params[seg.value] = decodeRouteParam(part);
75
89
  else params["*"] = part;
76
90
  }
77
91
  }
78
92
  return params ?? emptyParams;
79
93
  }
80
94
  //#endregion
81
- export { computeRank, matchSegments, parseSegments };
95
+ export { computeRank, matchSegments, parseSegments, splitPathSegments };
82
96
 
83
97
  //# sourceMappingURL=match.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"match.js","names":[],"sources":["../../src/router/match.ts"],"sourcesContent":["/**\n * Path matching, segment parsing, and specificity scoring.\n */\n\nimport type { ParsedSegment } from '../common/router';\n\n/**\n * Parse a route template path into typed segments.\n *\n * @example\n * parseSegments('/users/{id}') // [{kind:'static',value:'users'},{kind:'param',value:'id'}]\n * parseSegments('/*') // [{kind:'catchall',value:'*'}]\n * parseSegments('/posts/*') // [{kind:'static',value:'posts'},{kind:'wildcard',value:'*'}]\n */\nexport function parseSegments(path: string): ParsedSegment[] {\n const normalized =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n\n const parts = normalized.split('/').filter(Boolean);\n\n // Bare catch-all: /*\n if (parts.length === 1 && parts[0] === '*') {\n return [{ kind: 'catchall', value: '*' }];\n }\n\n return parts.map((segment): ParsedSegment => {\n if (segment.startsWith('{') && segment.endsWith('}')) {\n return { kind: 'param', value: segment.slice(1, -1) };\n }\n if (segment === '*') {\n return { kind: 'wildcard', value: '*' };\n }\n return { kind: 'static', value: segment };\n });\n}\n\n/**\n * Compute a numeric specificity rank from a parsed segment list.\n *\n * Scoring: static = 3, param = 2, wildcard = 1, catchall = 0.\n * Higher rank wins when multiple routes match the same path.\n */\nexport function computeRank(segments: ParsedSegment[]): number {\n if (segments.length === 1 && segments[0].kind === 'catchall') return -1;\n let score = 0;\n for (const seg of segments) {\n if (seg.kind === 'static') score += 3;\n else if (seg.kind === 'param') score += 2;\n else if (seg.kind === 'wildcard') score += 1;\n // catchall contributes 0 per segment but is handled above\n }\n return score;\n}\n\n/** Reused frozen empty params object — returned for purely-static (no-capture) routes. */\nconst emptyParams: Record<string, string> = Object.freeze(\n Object.create(null) as Record<string, string>\n);\n\n/** Returned for every failed match — avoids per-call allocation. */\nconst noMatch: MatchResult = Object.freeze({\n matched: false,\n params: emptyParams,\n});\n\n/**\n * Match pre-split URL parts against pre-parsed route segments.\n *\n * This is the hot-path matcher used by `resolveRoute` and\n * `resolveRouteFromRoutes`. Callers split the URL path **once** and reuse\n * `urlParts` across all route comparisons in a single resolution call.\n *\n * - Returns a params object (possibly the shared `{}`) on match.\n * - Returns `null` on no match.\n * - Params are allocated lazily — purely-static routes return the frozen\n * empty sentinel without any heap allocation.\n */\nexport function matchSegments(\n urlParts: string[],\n segments: ParsedSegment[]\n): Record<string, string> | null {\n // catch-all /* — matches every URL at any depth\n if (segments.length === 1 && segments[0].kind === 'catchall') {\n return {\n '*':\n urlParts.length === 0\n ? '/'\n : urlParts.length === 1\n ? urlParts[0]\n : '/' + urlParts.join('/'),\n };\n }\n\n // non-catchall: part count must equal segment count\n if (urlParts.length !== segments.length) return null;\n\n // Walk segments; allocate the params object lazily on first capture\n let params: Record<string, string> | null = null;\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n const part = urlParts[i];\n if (seg.kind === 'static') {\n if (seg.value !== part) return null;\n } else {\n if (params === null) params = {};\n if (seg.kind === 'param') {\n // Avoid decodeURIComponent when no percent-encoding is present\n params[seg.value] = part.includes('%')\n ? decodeURIComponent(part)\n : part;\n } else {\n // wildcard\n params['*'] = part;\n }\n }\n }\n return params ?? emptyParams;\n}\n\nexport interface MatchResult {\n matched: boolean;\n params: Record<string, string>;\n}\n\n/**\n * Match a path against a route pattern and extract params\n *\n * @example\n * match('/users/123', '/users/{id}')\n * // → { matched: true, params: { id: '123' } }\n *\n * match('/posts/hello-world/edit', '/posts/{slug}/{action}')\n * // → { matched: true, params: { slug: 'hello-world', action: 'edit' } }\n *\n * match('/users', '/posts/{id}')\n * // → { matched: false, params: {} }\n */\nexport function match(path: string, pattern: string): MatchResult {\n // Normalize trailing slashes\n const normalizedPath =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n const normalizedPattern =\n pattern.endsWith('/') && pattern !== '/' ? pattern.slice(0, -1) : pattern;\n\n // Split into segments\n const pathSegments = normalizedPath.split('/').filter(Boolean);\n const patternSegments = normalizedPattern.split('/').filter(Boolean);\n\n // Support catch-all route: /* matches any path at any depth\n if (patternSegments.length === 1 && patternSegments[0] === '*') {\n // For multi-segment paths, preserve the leading slash\n // For single-segment paths, return just the segment\n return {\n matched: true,\n params: {\n '*': pathSegments.length > 1 ? normalizedPath : pathSegments[0],\n },\n };\n }\n\n // Check if lengths match (wildcard segments still need to match one segment)\n if (pathSegments.length !== patternSegments.length) {\n return { matched: false, params: {} };\n }\n\n const params: Record<string, string> = {};\n\n // Match each segment\n for (let i = 0; i < patternSegments.length; i++) {\n const patternSegment = patternSegments[i];\n const pathSegment = pathSegments[i];\n\n // Parameter: {paramName}\n if (patternSegment.startsWith('{') && patternSegment.endsWith('}')) {\n const paramName = patternSegment.slice(1, -1);\n params[paramName] = pathSegment.includes('%')\n ? decodeURIComponent(pathSegment)\n : pathSegment;\n } else if (patternSegment === '*') {\n // Wildcard: match single segment\n params['*'] = pathSegment;\n } else if (patternSegment !== pathSegment) {\n // Literal segment mismatch\n return noMatch;\n }\n }\n\n return { matched: true, params };\n}\n"],"mappings":";;;;;;;;;AAcA,SAAgB,cAAc,MAA+B;CAI3D,MAAM,SAFJ,KAAK,SAAS,IAAI,IAAI,SAAS,MAAM,KAAK,MAAM,GAAG,GAAG,GAAG,MAElC,MAAM,IAAI,CAAC,OAAO,QAAQ;CAGnD,IAAI,MAAM,WAAW,KAAK,MAAM,OAAO,KACrC,OAAO,CAAC;EAAE,MAAM;EAAY,OAAO;EAAK,CAAC;CAG3C,OAAO,MAAM,KAAK,YAA2B;EAC3C,IAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,SAAS,IAAI,EAClD,OAAO;GAAE,MAAM;GAAS,OAAO,QAAQ,MAAM,GAAG,GAAG;GAAE;EAEvD,IAAI,YAAY,KACd,OAAO;GAAE,MAAM;GAAY,OAAO;GAAK;EAEzC,OAAO;GAAE,MAAM;GAAU,OAAO;GAAS;GACzC;;;;;;;;AASJ,SAAgB,YAAY,UAAmC;CAC7D,IAAI,SAAS,WAAW,KAAK,SAAS,GAAG,SAAS,YAAY,OAAO;CACrE,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,UAChB,IAAI,IAAI,SAAS,UAAU,SAAS;MAC/B,IAAI,IAAI,SAAS,SAAS,SAAS;MACnC,IAAI,IAAI,SAAS,YAAY,SAAS;CAG7C,OAAO;;;AAIT,MAAM,cAAsC,OAAO,OACjD,OAAO,OAAO,KAAK,CACpB;AAG4B,OAAO,OAAO;CACzC,SAAS;CACT,QAAQ;CACT,CAAC;;;;;;;;;;;;;AAcF,SAAgB,cACd,UACA,UAC+B;CAE/B,IAAI,SAAS,WAAW,KAAK,SAAS,GAAG,SAAS,YAChD,OAAO,EACL,KACE,SAAS,WAAW,IAChB,MACA,SAAS,WAAW,IAClB,SAAS,KACT,MAAM,SAAS,KAAK,IAAI,EACjC;CAIH,IAAI,SAAS,WAAW,SAAS,QAAQ,OAAO;CAGhD,IAAI,SAAwC;CAC5C,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;EACrB,MAAM,OAAO,SAAS;EACtB,IAAI,IAAI,SAAS,UACf;OAAI,IAAI,UAAU,MAAM,OAAO;SAC1B;GACL,IAAI,WAAW,MAAM,SAAS,EAAE;GAChC,IAAI,IAAI,SAAS,SAEf,OAAO,IAAI,SAAS,KAAK,SAAS,IAAI,GAClC,mBAAmB,KAAK,GACxB;QAGJ,OAAO,OAAO;;;CAIpB,OAAO,UAAU"}
1
+ {"version":3,"file":"match.js","names":[],"sources":["../../src/router/match.ts"],"sourcesContent":["/**\n * Path matching, segment parsing, and specificity scoring.\n */\n\nimport type { ParsedSegment } from '../common/router';\n\nexport function splitPathSegments(path: string): string[] {\n const normalized =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n\n if (!normalized || normalized === '/') {\n return [];\n }\n\n const withoutLeadingSlash = normalized.startsWith('/')\n ? normalized.slice(1)\n : normalized;\n\n return withoutLeadingSlash.length === 0 ? [] : withoutLeadingSlash.split('/');\n}\n\n/**\n * Parse a route template path into typed segments.\n *\n * @example\n * parseSegments('/users/{id}') // [{kind:'static',value:'users'},{kind:'param',value:'id'}]\n * parseSegments('/*') // [{kind:'catchall',value:'*'}]\n * parseSegments('/posts/*') // [{kind:'static',value:'posts'},{kind:'wildcard',value:'*'}]\n */\nexport function parseSegments(path: string): ParsedSegment[] {\n const normalized =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n\n const parts = normalized.split('/').filter(Boolean);\n\n // Bare catch-all: /*\n if (parts.length === 1 && parts[0] === '*') {\n return [{ kind: 'catchall', value: '*' }];\n }\n\n return parts.map((segment): ParsedSegment => {\n if (segment.startsWith('{') && segment.endsWith('}')) {\n return { kind: 'param', value: segment.slice(1, -1) };\n }\n if (segment === '*') {\n return { kind: 'wildcard', value: '*' };\n }\n return { kind: 'static', value: segment };\n });\n}\n\n/**\n * Compute a numeric specificity rank from a parsed segment list.\n *\n * Scoring: static = 3, param = 2, wildcard = 1, catchall = 0.\n * Higher rank wins when multiple routes match the same path.\n */\nexport function computeRank(segments: ParsedSegment[]): number {\n if (segments.length === 1 && segments[0].kind === 'catchall') return -1;\n let score = 0;\n for (const seg of segments) {\n if (seg.kind === 'static') score += 3;\n else if (seg.kind === 'param') score += 2;\n else if (seg.kind === 'wildcard') score += 1;\n // catchall contributes 0 per segment but is handled above\n }\n return score;\n}\n\n/** Reused frozen empty params object — returned for purely-static (no-capture) routes. */\nconst emptyParams: Record<string, string> = Object.freeze(\n Object.create(null) as Record<string, string>\n);\n\n/** Returned for every failed match — avoids per-call allocation. */\nconst noMatch: MatchResult = Object.freeze({\n matched: false,\n params: emptyParams,\n});\n\nfunction decodeRouteParam(part: string): string {\n if (!part.includes('%')) {\n return part;\n }\n\n try {\n return decodeURIComponent(part);\n } catch {\n return part;\n }\n}\n\n/**\n * Match pre-split URL parts against pre-parsed route segments.\n *\n * This is the hot-path matcher used by `resolveRoute` and\n * `resolveRouteFromRoutes`. Callers split the URL path **once** and reuse\n * `urlParts` across all route comparisons in a single resolution call.\n *\n * - Returns a params object (possibly the shared `{}`) on match.\n * - Returns `null` on no match.\n * - Params are allocated lazily — purely-static routes return the frozen\n * empty sentinel without any heap allocation.\n */\nexport function matchSegments(\n urlParts: string[],\n segments: ParsedSegment[]\n): Record<string, string> | null {\n // catch-all /* — matches every URL at any depth\n if (segments.length === 1 && segments[0].kind === 'catchall') {\n return {\n '*':\n urlParts.length === 0\n ? '/'\n : urlParts.length === 1\n ? urlParts[0]\n : '/' + urlParts.join('/'),\n };\n }\n\n // non-catchall: part count must equal segment count\n if (urlParts.length !== segments.length) return null;\n\n // Walk segments; allocate the params object lazily on first capture\n let params: Record<string, string> | null = null;\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n const part = urlParts[i];\n if (seg.kind === 'static') {\n if (seg.value !== part) return null;\n } else {\n if (params === null) params = {};\n if (seg.kind === 'param') {\n params[seg.value] = decodeRouteParam(part);\n } else {\n // wildcard\n params['*'] = part;\n }\n }\n }\n return params ?? emptyParams;\n}\n\nexport interface MatchResult {\n matched: boolean;\n params: Record<string, string>;\n}\n\n/**\n * Match a path against a route pattern and extract params\n *\n * @example\n * match('/users/123', '/users/{id}')\n * // → { matched: true, params: { id: '123' } }\n *\n * match('/posts/hello-world/edit', '/posts/{slug}/{action}')\n * // → { matched: true, params: { slug: 'hello-world', action: 'edit' } }\n *\n * match('/users', '/posts/{id}')\n * // → { matched: false, params: {} }\n */\nexport function match(path: string, pattern: string): MatchResult {\n // Normalize trailing slashes\n const normalizedPath =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n const normalizedPattern =\n pattern.endsWith('/') && pattern !== '/' ? pattern.slice(0, -1) : pattern;\n\n // Split into segments\n const pathSegments = splitPathSegments(normalizedPath);\n const patternSegments = splitPathSegments(normalizedPattern);\n\n // Support catch-all route: /* matches any path at any depth\n if (patternSegments.length === 1 && patternSegments[0] === '*') {\n // For multi-segment paths, preserve the leading slash\n // For single-segment paths, return just the segment\n return {\n matched: true,\n params: {\n '*':\n pathSegments.length === 0\n ? '/'\n : pathSegments.length > 1\n ? normalizedPath\n : pathSegments[0],\n },\n };\n }\n\n // Check if lengths match (wildcard segments still need to match one segment)\n if (pathSegments.length !== patternSegments.length) {\n return { matched: false, params: {} };\n }\n\n const params: Record<string, string> = {};\n\n // Match each segment\n for (let i = 0; i < patternSegments.length; i++) {\n const patternSegment = patternSegments[i];\n const pathSegment = pathSegments[i];\n\n // Parameter: {paramName}\n if (patternSegment.startsWith('{') && patternSegment.endsWith('}')) {\n const paramName = patternSegment.slice(1, -1);\n params[paramName] = decodeRouteParam(pathSegment);\n } else if (patternSegment === '*') {\n // Wildcard: match single segment\n params['*'] = pathSegment;\n } else if (patternSegment !== pathSegment) {\n // Literal segment mismatch\n return noMatch;\n }\n }\n\n return { matched: true, params };\n}\n"],"mappings":";AAMA,SAAgB,kBAAkB,MAAwB;CACxD,MAAM,aACJ,KAAK,SAAS,GAAG,KAAK,SAAS,MAAM,KAAK,MAAM,GAAG,EAAE,IAAI;CAE3D,IAAI,CAAC,cAAc,eAAe,KAChC,OAAO,CAAC;CAGV,MAAM,sBAAsB,WAAW,WAAW,GAAG,IACjD,WAAW,MAAM,CAAC,IAClB;CAEJ,OAAO,oBAAoB,WAAW,IAAI,CAAC,IAAI,oBAAoB,MAAM,GAAG;AAC9E;;;;;;;;;AAUA,SAAgB,cAAc,MAA+B;CAI3D,MAAM,SAFJ,KAAK,SAAS,GAAG,KAAK,SAAS,MAAM,KAAK,MAAM,GAAG,EAAE,IAAI,MAElC,MAAM,GAAG,EAAE,OAAO,OAAO;CAGlD,IAAI,MAAM,WAAW,KAAK,MAAM,OAAO,KACrC,OAAO,CAAC;EAAE,MAAM;EAAY,OAAO;CAAI,CAAC;CAG1C,OAAO,MAAM,KAAK,YAA2B;EAC3C,IAAI,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,GACjD,OAAO;GAAE,MAAM;GAAS,OAAO,QAAQ,MAAM,GAAG,EAAE;EAAE;EAEtD,IAAI,YAAY,KACd,OAAO;GAAE,MAAM;GAAY,OAAO;EAAI;EAExC,OAAO;GAAE,MAAM;GAAU,OAAO;EAAQ;CAC1C,CAAC;AACH;;;;;;;AAQA,SAAgB,YAAY,UAAmC;CAC7D,IAAI,SAAS,WAAW,KAAK,SAAS,GAAG,SAAS,YAAY,OAAO;CACrE,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,UAChB,IAAI,IAAI,SAAS,UAAU,SAAS;MAC/B,IAAI,IAAI,SAAS,SAAS,SAAS;MACnC,IAAI,IAAI,SAAS,YAAY,SAAS;CAG7C,OAAO;AACT;;AAGA,MAAM,cAAsC,OAAO,OACjD,OAAO,OAAO,IAAI,CACpB;AAG6B,OAAO,OAAO;CACzC,SAAS;CACT,QAAQ;AACV,CAAC;AAED,SAAS,iBAAiB,MAAsB;CAC9C,IAAI,CAAC,KAAK,SAAS,GAAG,GACpB,OAAO;CAGT,IAAI;EACF,OAAO,mBAAmB,IAAI;CAChC,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;AAcA,SAAgB,cACd,UACA,UAC+B;CAE/B,IAAI,SAAS,WAAW,KAAK,SAAS,GAAG,SAAS,YAChD,OAAO,EACL,KACE,SAAS,WAAW,IAChB,MACA,SAAS,WAAW,IAClB,SAAS,KACT,MAAM,SAAS,KAAK,GAAG,EACjC;CAIF,IAAI,SAAS,WAAW,SAAS,QAAQ,OAAO;CAGhD,IAAI,SAAwC;CAC5C,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;EACrB,MAAM,OAAO,SAAS;EACtB,IAAI,IAAI,SAAS,UACf;OAAI,IAAI,UAAU,MAAM,OAAO;EAAA,OAC1B;GACL,IAAI,WAAW,MAAM,SAAS,CAAC;GAC/B,IAAI,IAAI,SAAS,SACf,OAAO,IAAI,SAAS,iBAAiB,IAAI;QAGzC,OAAO,OAAO;EAElB;CACF;CACA,OAAO,UAAU;AACnB"}
@@ -1,6 +1,12 @@
1
1
  import { ComponentInstance } from "../runtime/component.js";
2
+ import { Route, RouteAuthOptions, RouteManifest } from "../common/router.js";
2
3
 
3
4
  //#region src/router/navigate.d.ts
5
+ type AppNavigationSource = {
6
+ manifest?: RouteManifest;
7
+ routes?: readonly Route[];
8
+ auth?: RouteAuthOptions;
9
+ };
4
10
  type NavigationScrollBehavior = 'top' | 'preserve';
5
11
  type HistoryScrollBehavior = 'restore' | 'top' | 'preserve';
6
12
  type ScrollRestorationOptions = {
@@ -9,11 +15,13 @@ type ScrollRestorationOptions = {
9
15
  };
10
16
  type NavigateOptions = {
11
17
  history?: 'push' | 'replace';
18
+ replace?: boolean;
12
19
  scroll?: NavigationScrollBehavior;
13
20
  };
14
21
  declare function configureScrollRestoration(options?: boolean | ScrollRestorationOptions): void;
15
22
  /** Register the current app instance (called by createSPA/hydrateSPA). */
16
- declare function registerAppInstance(instance: ComponentInstance, path: string): void;
23
+ declare function registerAppInstance(instance: ComponentInstance, path: string, source?: AppNavigationSource): void;
24
+ declare function unregisterAppInstance(instance: ComponentInstance): void;
17
25
  /**
18
26
  * Navigate to a new path
19
27
  * Updates URL, resolves route, and re-mounts app with new handler
@@ -1 +1 @@
1
- {"version":3,"file":"navigate.d.ts","names":[],"sources":["../../src/router/navigate.ts"],"mappings":";;;KA2BY,wBAAA;AAAA,KACA,qBAAA;AAAA,KAEA,wBAAA;EACV,UAAA,GAAa,wBAAA;EACb,OAAA,GAAU,qBAAA;AAAA;AAAA,KAqBA,eAAA;EACV,OAAA;EACA,MAAA,GAAS,wBAAA;AAAA;AAAA,iBAsIK,0BAAA,CACd,OAAA,aAAoB,wBAAA;;iBAkPN,mBAAA,CACd,QAAA,EAAU,iBAAA,EACV,IAAA;;;;;iBAqBc,QAAA,CAAS,IAAA,UAAc,OAAA,GAAS,eAAA;;AAlZhD;;iBAoegB,oBAAA,CAAA;;;;iBAYA,iBAAA,CAAA"}
1
+ {"version":3,"file":"navigate.d.ts","names":[],"sources":["../../src/router/navigate.ts"],"mappings":";;;;KA6CK,mBAAA;EACH,QAAA,GAAW,aAAA;EACX,MAAA,YAAkB,KAAA;EAClB,IAAA,GAAO,gBAAA;AAAA;AAAA,KAoBG,wBAAA;AAAA,KACA,qBAAA;AAAA,KAEA,wBAAA;EACV,UAAA,GAAa,wBAAA;EACb,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAqBrB,eAAA;EACV,OAAA;EACA,OAAA;EACA,MAAA,GAAS,wBAAwB;AAAA;AAAA,iBAsInB,0BAAA,CACd,OAA4C,aAAxB,wBAAwB;;iBA4Y9B,mBAAA,CACd,QAAA,EAAU,iBAAA,EACV,IAAA,UACA,MAAA,GAAQ,mBAAwB;AAAA,iBAgClB,qBAAA,CAAsB,QAA2B,EAAjB,iBAAiB;AAllBjE;;;;AAAA,iBAonBgB,QAAA,CAAS,IAAA,UAAc,OAAA,GAAS,eAAoB;AAlnBpE;;;AAAA,iBAsyBgB,oBAAA,CAAA;;;;iBAYA,iBAAA,CAAA"}