@askrjs/askr 0.0.34 → 0.0.36
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.
- package/dist/bench/components/benchmark-row.js.map +1 -1
- package/dist/bench/components/benchmark-table.js.map +1 -1
- package/dist/benchmark.js +2 -2
- package/dist/benchmark.js.map +1 -1
- package/dist/bin/askr-ssg.js.map +1 -1
- package/dist/boot/index.d.ts.map +1 -1
- package/dist/boot/index.js +9 -29
- package/dist/boot/index.js.map +1 -1
- package/dist/common/control.js.map +1 -1
- package/dist/common/env.js.map +1 -1
- package/dist/common/router.d.ts +8 -1
- package/dist/common/router.d.ts.map +1 -1
- package/dist/common/ssr-errors.js.map +1 -1
- package/dist/common/vnode.js.map +1 -1
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/link.js.map +1 -1
- package/dist/control/case.js.map +1 -1
- package/dist/control/for.js.map +1 -1
- package/dist/control/shared.js.map +1 -1
- package/dist/control/show.js.map +1 -1
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +165 -23
- package/dist/data/index.js.map +1 -1
- package/dist/dev/invariant.js.map +1 -1
- package/dist/dev/logger.js.map +1 -1
- package/dist/foundations/icon/icon.js.map +1 -1
- package/dist/foundations/interactions/dismissable.js.map +1 -1
- package/dist/foundations/interactions/focusable.js.map +1 -1
- package/dist/foundations/interactions/hoverable.js.map +1 -1
- package/dist/foundations/interactions/interaction-policy.js.map +1 -1
- package/dist/foundations/interactions/pressable.js.map +1 -1
- package/dist/foundations/interactions/roving-focus.js.map +1 -1
- package/dist/foundations/state/controllable.js.map +1 -1
- package/dist/foundations/structures/collection.js.map +1 -1
- package/dist/foundations/structures/layer.js.map +1 -1
- package/dist/foundations/structures/layout.js.map +1 -1
- package/dist/foundations/structures/portal.js.map +1 -1
- package/dist/foundations/structures/presence.js.map +1 -1
- package/dist/foundations/structures/slot.js.map +1 -1
- package/dist/foundations/utilities/aria.js.map +1 -1
- package/dist/foundations/utilities/compose-handlers.js.map +1 -1
- package/dist/foundations/utilities/compose-ref.js.map +1 -1
- package/dist/foundations/utilities/merge-props.js.map +1 -1
- package/dist/foundations/utilities/use-id.js.map +1 -1
- package/dist/fx/fx.js.map +1 -1
- package/dist/fx/timing.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/jsx/utils.js.map +1 -1
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/renderer/children.js.map +1 -1
- package/dist/renderer/cleanup.js.map +1 -1
- package/dist/renderer/dom.js +128 -53
- package/dist/renderer/dom.js.map +1 -1
- package/dist/renderer/evaluate.js +7 -0
- package/dist/renderer/evaluate.js.map +1 -1
- package/dist/renderer/fastpath.js.map +1 -1
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/keyed.js +1 -1
- package/dist/renderer/keyed.js.map +1 -1
- package/dist/renderer/reconcile.js.map +1 -1
- package/dist/renderer/utils.js.map +1 -1
- package/dist/router/index.d.ts +3 -3
- package/dist/router/index.js +2 -2
- package/dist/router/match.js.map +1 -1
- package/dist/router/navigate.d.ts.map +1 -1
- package/dist/router/navigate.js +8 -3
- package/dist/router/navigate.js.map +1 -1
- package/dist/router/policy.js.map +1 -1
- package/dist/router/route-context.js.map +1 -1
- package/dist/router/route.d.ts +11 -2
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/route.js +268 -109
- package/dist/router/route.js.map +1 -1
- package/dist/runtime/child-scope.d.ts +1 -0
- package/dist/runtime/child-scope.d.ts.map +1 -1
- package/dist/runtime/child-scope.js +2 -0
- package/dist/runtime/child-scope.js.map +1 -1
- package/dist/runtime/component.js +1 -1
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/derive.d.ts +2 -1
- package/dist/runtime/derive.d.ts.map +1 -1
- package/dist/runtime/derive.js +15 -10
- package/dist/runtime/derive.js.map +1 -1
- package/dist/runtime/dev-namespace.js.map +1 -1
- package/dist/runtime/effect.d.ts.map +1 -1
- package/dist/runtime/effect.js +34 -9
- package/dist/runtime/effect.js.map +1 -1
- package/dist/runtime/events.js.map +1 -1
- package/dist/runtime/execution-model.js.map +1 -1
- package/dist/runtime/fastlane.js.map +1 -1
- package/dist/runtime/for-bench.d.ts +1 -1
- package/dist/runtime/for-bench.d.ts.map +1 -1
- package/dist/runtime/for-bench.js +15 -14
- package/dist/runtime/for-bench.js.map +1 -1
- package/dist/runtime/for.d.ts +1 -0
- package/dist/runtime/for.d.ts.map +1 -1
- package/dist/runtime/for.js +63 -16
- package/dist/runtime/for.js.map +1 -1
- package/dist/runtime/operations.d.ts.map +1 -1
- package/dist/runtime/operations.js +9 -7
- package/dist/runtime/operations.js.map +1 -1
- package/dist/runtime/perf-metrics.js +8 -2
- package/dist/runtime/perf-metrics.js.map +1 -1
- package/dist/runtime/readable.d.ts +1 -1
- package/dist/runtime/readable.d.ts.map +1 -1
- package/dist/runtime/readable.js +2 -1
- package/dist/runtime/readable.js.map +1 -1
- package/dist/runtime/resource-cell.js +3 -2
- package/dist/runtime/resource-cell.js.map +1 -1
- package/dist/runtime/scheduler.d.ts +0 -1
- package/dist/runtime/scheduler.d.ts.map +1 -1
- package/dist/runtime/scheduler.js +6 -19
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/selector.d.ts.map +1 -1
- package/dist/runtime/selector.js +6 -4
- package/dist/runtime/selector.js.map +1 -1
- package/dist/runtime/snapshot-source.d.ts +10 -0
- package/dist/runtime/snapshot-source.d.ts.map +1 -0
- package/dist/runtime/snapshot-source.js +17 -0
- package/dist/runtime/snapshot-source.js.map +1 -0
- package/dist/runtime/ssr-bridge.js.map +1 -1
- package/dist/runtime/state.d.ts.map +1 -1
- package/dist/runtime/state.js +1 -5
- package/dist/runtime/state.js.map +1 -1
- package/dist/ssg/batch-render.js.map +1 -1
- package/dist/ssg/create-static-gen.js.map +1 -1
- package/dist/ssg/generate-metadata.js.map +1 -1
- package/dist/ssg/incremental-manifest.js.map +1 -1
- package/dist/ssg/resolve-ssg-data.js.map +1 -1
- package/dist/ssg/route-utils.js.map +1 -1
- package/dist/ssg/write-static-files.js.map +1 -1
- package/dist/ssr/attrs.js.map +1 -1
- package/dist/ssr/context.d.ts +1 -0
- package/dist/ssr/context.d.ts.map +1 -1
- package/dist/ssr/context.js +3 -0
- package/dist/ssr/context.js.map +1 -1
- package/dist/ssr/escape.js +56 -16
- package/dist/ssr/escape.js.map +1 -1
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/ssr/index.js +15 -3
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/render-keys.js.map +1 -1
- package/dist/ssr/sink.js.map +1 -1
- package/dist/ssr/stream-render.js +14 -2
- package/dist/ssr/stream-render.js.map +1 -1
- package/package.json +8 -6
|
@@ -227,6 +227,13 @@ function updateElementChildren(element, vnodeChildren) {
|
|
|
227
227
|
* Tries text-in-place update first, then full child reconciliation
|
|
228
228
|
*/
|
|
229
229
|
function smartUpdateElement(element, vnode) {
|
|
230
|
+
if (vnode.key == null && element.hasAttribute("data-key")) {
|
|
231
|
+
const existingKey = element.getAttribute("data-key");
|
|
232
|
+
if (existingKey !== null) {
|
|
233
|
+
const numericKey = Number(existingKey);
|
|
234
|
+
vnode.key = Number.isNaN(numericKey) ? existingKey : numericKey;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
230
237
|
let vnodeChildren = vnode.props?.children ?? vnode.children;
|
|
231
238
|
if (vnodeChildren && _isDOMElement(vnodeChildren) && vnodeChildren.type === __FOR_BOUNDARY__) {
|
|
232
239
|
updateElementChildren(element, vnodeChildren);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate.js","names":["sharedTagNamesEqualIgnoreCase"],"sources":["../../src/renderer/evaluate.ts"],"sourcesContent":["import { logger } from '../dev/logger';\nimport { getRuntimeEnv } from './env';\nimport type { Props } from '../common/props';\nimport type { ComponentInstance } from '../runtime/component';\nimport {\n elementListeners,\n removeAllListeners,\n cleanupInstanceIfPresent,\n} from './cleanup';\nimport { keyedElements } from './keyed';\nimport { reconcileKeyedChildren } from './reconcile';\nimport { _isDOMElement, type DOMElement, type VNode } from './types';\nimport { __FOR_BOUNDARY__ } from '../common/vnode';\nimport { evaluateForState } from '../runtime/for';\nimport {\n evaluateCaseState,\n evaluateShowState,\n type ControlBoundaryState,\n} from '../runtime/control';\nimport {\n createDOMNode,\n commitForBoundaryChildren,\n syncComponentElement,\n updateElementFromVnode,\n updateUnkeyedChildren,\n performBulkPositionalKeyedTextUpdate,\n performBulkTextReplace,\n isBulkTextFastPathEligible,\n} from './dom';\nimport { setDevValue, incDevCounter } from '../runtime/dev-namespace';\nimport { Fragment } from '../common/jsx';\nimport {\n createWrappedHandler,\n extractKey,\n getPassiveOptions,\n parseEventName,\n tagNamesEqualIgnoreCase as sharedTagNamesEqualIgnoreCase,\n writeElementClassName,\n} from './utils';\n\n/**\n * Internal marker for component-owned DOM ranges\n * Allows efficient partial DOM updates instead of clearing entire target\n */\ninterface DOMRange {\n start: Node; // Start marker (comment node)\n end: Node; // End marker (comment node)\n}\n\nexport const IS_DOM_AVAILABLE = typeof document !== 'undefined';\n\nconst SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\nfunction resolveChildNamespace(\n type: string,\n parentNamespace?: string\n): string | undefined {\n if (type === 'svg') return SVG_NAMESPACE;\n if (parentNamespace === SVG_NAMESPACE && type !== 'foreignObject') {\n return SVG_NAMESPACE;\n }\n return undefined;\n}\n\nfunction createElementForNamespace(\n type: string,\n parentNamespace?: string\n): Element {\n const namespace = resolveChildNamespace(type, parentNamespace);\n return namespace\n ? document.createElementNS(namespace, type)\n : document.createElement(type);\n}\n\nconst domRanges = new WeakMap<object, DOMRange>();\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helper Types & Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface SimpleTextResult {\n isSimple: true;\n text: string;\n}\n\ninterface NotSimpleTextResult {\n isSimple: false;\n text?: undefined;\n}\n\ntype TextCheckResult = SimpleTextResult | NotSimpleTextResult;\n\nfunction tagNamesEqualIgnoreCase(a: string, b: string): boolean {\n return sharedTagNamesEqualIgnoreCase(a, b);\n}\n\n/**\n * Check if vnode children represent a simple text value\n */\nfunction checkSimpleText(vnodeChildren: unknown): TextCheckResult {\n if (!Array.isArray(vnodeChildren)) {\n if (\n typeof vnodeChildren === 'string' ||\n typeof vnodeChildren === 'number'\n ) {\n return { isSimple: true, text: String(vnodeChildren) };\n }\n } else if (vnodeChildren.length === 1) {\n const child = vnodeChildren[0];\n if (typeof child === 'string' || typeof child === 'number') {\n return { isSimple: true, text: String(child) };\n }\n }\n return { isSimple: false };\n}\n\n/**\n * Try to update a single text node in place\n * Returns true if update was performed, false otherwise\n */\nfunction tryUpdateTextInPlace(element: Element, text: string): boolean {\n if (\n element.childNodes.length === 1 &&\n element.firstChild?.nodeType === 3 // TEXT_NODE\n ) {\n (element.firstChild as Text).data = text;\n return true;\n }\n return false;\n}\n\n/**\n * Build a key map from existing DOM children\n */\nfunction buildKeyMapFromDOM(parent: Element): Map<string | number, Element> {\n const keyMap = new Map<string | number, Element>();\n for (\n let child = parent.firstElementChild;\n child;\n child = child.nextElementSibling\n ) {\n const k = child.getAttribute('data-key');\n if (k !== null) {\n keyMap.set(k, child);\n const n = Number(k);\n if (!Number.isNaN(n)) keyMap.set(n, child);\n }\n }\n return keyMap;\n}\n\n/**\n * Get or initialize key map for an element\n */\nfunction getOrBuildKeyMap(\n parent: Element\n): Map<string | number, Element> | undefined {\n let keyMap = keyedElements.get(parent);\n if (!keyMap) {\n keyMap = buildKeyMapFromDOM(parent);\n if (keyMap.size > 0) {\n keyedElements.set(parent, keyMap);\n }\n }\n return keyMap.size > 0 ? keyMap : undefined;\n}\n\n/**\n * Check if children array contains keyed elements\n */\nfunction hasKeyedChildren(children: unknown[]): boolean {\n for (let i = 0; i < children.length; i++) {\n if (extractKey(children[i]) !== undefined) return true;\n }\n return false;\n}\n\n/**\n * Track bulk text fast-path stats (dev only)\n */\nfunction trackBulkTextStats(\n stats: ReturnType<typeof performBulkTextReplace>\n): void {\n if (getRuntimeEnv().NODE_ENV !== 'production') {\n try {\n setDevValue('__LAST_BULK_TEXT_FASTPATH_STATS', stats);\n incDevCounter('bulkTextHits');\n } catch {\n // ignore\n }\n }\n}\n\n/**\n * Track bulk text miss (dev only)\n */\nfunction trackBulkTextMiss(): void {\n if (getRuntimeEnv().NODE_ENV !== 'production') {\n try {\n incDevCounter('bulkTextMisses');\n } catch {\n // ignore\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Child Reconciliation Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Reconcile keyed children with optional forced bulk path\n */\nfunction reconcileKeyed(\n parent: Element,\n children: VNode[],\n oldKeyMap: Map<string | number, Element> | undefined\n): void {\n // Optional forced positional bulk path for large keyed lists\n if (getRuntimeEnv().ASKR_FORCE_BULK_POSREUSE === '1') {\n const result = tryForcedBulkKeyedPath(parent, children);\n if (result) return;\n }\n\n // Standard keyed reconciliation\n const newKeyMap = reconcileKeyedChildren(parent, children, oldKeyMap);\n keyedElements.set(parent, newKeyMap);\n}\n\n/**\n * Try the forced bulk keyed positional path\n * Returns true if applied, false to fall back to normal reconciliation\n */\nfunction tryForcedBulkKeyedPath(parent: Element, children: VNode[]): boolean {\n try {\n const keyedVnodes: Array<{ key: string | number; vnode: VNode }> = [];\n for (const child of children) {\n const key = extractKey(child);\n if (_isDOMElement(child) && key !== undefined) {\n keyedVnodes.push({\n key,\n vnode: child,\n });\n }\n }\n\n // Only apply when all children are keyed and count matches\n if (keyedVnodes.length === 0 || keyedVnodes.length !== children.length) {\n return false;\n }\n\n const fastPathEnv = getRuntimeEnv();\n if (\n fastPathEnv.ASKR_FASTPATH_DEBUG === '1' ||\n fastPathEnv.ASKR_FASTPATH_DEBUG === 'true'\n ) {\n logger.warn(\n '[Askr][FASTPATH] forced positional bulk keyed reuse (evaluate-level)'\n );\n }\n\n const stats = performBulkPositionalKeyedTextUpdate(parent, keyedVnodes);\n\n const statsEnv = getRuntimeEnv();\n if (\n statsEnv.NODE_ENV !== 'production' ||\n statsEnv.ASKR_FASTPATH_DEBUG === '1'\n ) {\n try {\n setDevValue('__LAST_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 1);\n incDevCounter('bulkKeyedPositionalForced');\n } catch {\n // ignore\n }\n }\n\n // Rebuild keyed map from DOM\n const newMap = buildKeyMapFromDOM(parent);\n keyedElements.set(parent, newMap);\n return true;\n } catch (err) {\n const fallbackEnv = getRuntimeEnv();\n if (\n fallbackEnv.ASKR_FASTPATH_DEBUG === '1' ||\n fallbackEnv.ASKR_FASTPATH_DEBUG === 'true'\n ) {\n logger.warn(\n '[Askr][FASTPATH] forced bulk path failed, falling back',\n err\n );\n }\n return false;\n }\n}\n\n/**\n * Reconcile unkeyed children, using bulk fast-path when eligible\n */\nfunction reconcileUnkeyed(parent: Element, children: VNode[]): void {\n if (isBulkTextFastPathEligible(parent, children)) {\n const stats = performBulkTextReplace(parent, children);\n trackBulkTextStats(stats);\n } else {\n trackBulkTextMiss();\n updateUnkeyedChildren(parent, children);\n }\n keyedElements.delete(parent);\n}\n\n/**\n * Update element children from a For boundary vnode\n * Evaluates the For and reconciles the keyed children with existing DOM\n */\nfunction updateForBoundaryChildren(\n element: Element,\n forVnode: DOMElement\n): void {\n const controlState =\n forVnode._controlState ??\n (forVnode._forState as ControlBoundaryState | undefined);\n if (!controlState) return;\n\n const childrenVNodes =\n controlState.kind === 'for'\n ? evaluateForState(controlState)\n : controlState.kind === 'show'\n ? evaluateShowState(controlState)\n : evaluateCaseState(controlState);\n commitForBoundaryChildren(element, controlState, childrenVNodes);\n}\n\n/**\n * Update element children (handles keyed, unkeyed, For boundaries, and non-array cases)\n */\nfunction updateElementChildren(element: Element, vnodeChildren: unknown): void {\n // CRITICAL: Check for null/undefined explicitly, not falsy values\n // because 0, false, and '' are valid children\n if (vnodeChildren === null || vnodeChildren === undefined) {\n // Clean up all children before clearing\n for (let n = element.firstChild; n; ) {\n const next = n.nextSibling;\n if (n instanceof Element) {\n removeAllListeners(n);\n cleanupInstanceIfPresent(n);\n }\n n = next;\n }\n element.textContent = '';\n keyedElements.delete(element);\n return;\n }\n\n // Handle For boundary as a special single-child case (non-array)\n if (\n !Array.isArray(vnodeChildren) &&\n _isDOMElement(vnodeChildren) &&\n (vnodeChildren as DOMElement).type === __FOR_BOUNDARY__\n ) {\n updateForBoundaryChildren(element, vnodeChildren as DOMElement);\n return;\n }\n\n if (!Array.isArray(vnodeChildren) && isFragment(vnodeChildren)) {\n updateElementChildren(element, getFragmentChildren(vnodeChildren));\n return;\n }\n\n if (!Array.isArray(vnodeChildren)) {\n // Clean up all children before clearing\n for (let n = element.firstChild; n; ) {\n const next = n.nextSibling;\n if (n instanceof Element) {\n removeAllListeners(n);\n cleanupInstanceIfPresent(n);\n }\n n = next;\n }\n element.textContent = '';\n const dom = createDOMNode(vnodeChildren);\n if (dom) element.appendChild(dom);\n keyedElements.delete(element);\n return;\n }\n\n // Handle For boundary wrapped in a single-element array [forVnode]\n // This is common when JSX transpiles children as arrays\n if (\n vnodeChildren.length === 1 &&\n _isDOMElement(vnodeChildren[0]) &&\n (vnodeChildren[0] as DOMElement).type === __FOR_BOUNDARY__\n ) {\n updateForBoundaryChildren(element, vnodeChildren[0] as DOMElement);\n return;\n }\n\n if (hasKeyedChildren(vnodeChildren)) {\n const oldKeyMap = getOrBuildKeyMap(element);\n try {\n reconcileKeyed(element, vnodeChildren, oldKeyMap);\n } catch {\n // Fall back on error\n const newKeyMap = reconcileKeyedChildren(\n element,\n vnodeChildren,\n oldKeyMap\n );\n keyedElements.set(element, newKeyMap);\n }\n } else {\n reconcileUnkeyed(element, vnodeChildren);\n }\n}\n\n/**\n * Perform a smart update on an existing element\n * Tries text-in-place update first, then full child reconciliation\n */\nfunction smartUpdateElement(element: Element, vnode: DOMElement): void {\n let vnodeChildren = vnode.props?.children ?? vnode.children;\n\n // CRITICAL: For boundary vnodes must NOT be wrapped into an array\n // They need special handling in updateElementChildren to call updateForBoundaryChildren\n if (\n vnodeChildren &&\n _isDOMElement(vnodeChildren) &&\n (vnodeChildren as DOMElement).type === __FOR_BOUNDARY__\n ) {\n // Pass For boundary directly without wrapping\n updateElementChildren(element, vnodeChildren);\n updateElementFromVnode(element, vnode, false);\n return;\n }\n\n // Normalize: if children is a single vnode (not an array), wrap it\n if (vnodeChildren && !Array.isArray(vnodeChildren)) {\n vnodeChildren = [vnodeChildren];\n }\n\n const textCheck = checkSimpleText(vnodeChildren);\n\n if (textCheck.isSimple && tryUpdateTextInPlace(element, textCheck.text)) {\n // Text updated in place, nothing more to do for children\n } else {\n updateElementChildren(element, vnodeChildren);\n }\n\n updateElementFromVnode(element, vnode, false);\n}\n\n/**\n * Process Fragment children with smart updates for each child\n */\nfunction processFragmentChildren(target: Element, childArray: unknown[]): void {\n updateElementChildren(target, childArray);\n}\n\nfunction cleanupRangeNode(node: Node): void {\n if (node instanceof Element) {\n removeAllListeners(node);\n cleanupInstanceIfPresent(node);\n }\n}\n\nfunction updateDOMRange(\n target: Element,\n range: DOMRange,\n children: unknown[]\n): void {\n let current: Node | null = range.start.nextSibling;\n\n for (let i = 0; i < children.length; i++) {\n const nextChild = children[i];\n const currentNode = current === range.end ? null : current;\n const nextCurrent = currentNode?.nextSibling ?? null;\n\n if (nextChild === null || nextChild === undefined || nextChild === false) {\n if (currentNode) {\n cleanupRangeNode(currentNode);\n target.removeChild(currentNode);\n }\n current = nextCurrent;\n continue;\n }\n\n if (typeof nextChild === 'string' || typeof nextChild === 'number') {\n if (currentNode?.nodeType === 3) {\n (currentNode as Text).data = String(nextChild);\n } else {\n const textNode = document.createTextNode(String(nextChild));\n if (currentNode) {\n cleanupRangeNode(currentNode);\n target.replaceChild(textNode, currentNode);\n } else {\n target.insertBefore(textNode, range.end);\n }\n }\n current = nextCurrent;\n continue;\n }\n\n if (\n currentNode instanceof Element &&\n _isDOMElement(nextChild) &&\n typeof nextChild.type === 'string' &&\n tagNamesEqualIgnoreCase(currentNode.tagName, nextChild.type)\n ) {\n smartUpdateElement(currentNode, nextChild);\n current = nextCurrent;\n continue;\n }\n\n const newDom = createDOMNode(nextChild);\n if (!newDom) {\n if (currentNode) {\n cleanupRangeNode(currentNode);\n target.removeChild(currentNode);\n }\n current = nextCurrent;\n continue;\n }\n\n if (currentNode) {\n cleanupRangeNode(currentNode);\n target.replaceChild(newDom, currentNode);\n } else {\n target.insertBefore(newDom, range.end);\n }\n\n current = nextCurrent;\n }\n\n while (current && current !== range.end) {\n const next = current.nextSibling;\n cleanupRangeNode(current);\n target.removeChild(current);\n current = next;\n }\n}\n\n/**\n * Apply props/attributes to an element (used for first render with keyed children)\n */\nfunction applyPropsToElement(el: Element, props: Props): void {\n for (const [key, value] of Object.entries(props)) {\n if (key === 'children' || key === 'key') continue;\n if (value === undefined || value === null || value === false) continue;\n\n if (key === 'ref') {\n applyRef(el, value);\n continue;\n }\n\n const eventName = parseEventName(key);\n if (eventName) {\n const wrappedHandler = createWrappedHandler(value as EventListener, true);\n const options = getPassiveOptions(eventName);\n\n if (options !== undefined)\n el.addEventListener(eventName, wrappedHandler, options);\n else el.addEventListener(eventName, wrappedHandler);\n\n if (!elementListeners.has(el)) elementListeners.set(el, new Map());\n elementListeners.get(el)!.set(eventName, {\n handler: wrappedHandler,\n original: value as EventListener,\n options,\n });\n continue;\n }\n\n if (key === 'class' || key === 'className') {\n writeElementClassName(el, String(value));\n } else if (key === 'value' || key === 'checked') {\n (el as HTMLElement & Props)[key] = value;\n } else {\n el.setAttribute(key, String(value));\n }\n }\n}\n\ntype Ref<T> =\n | ((value: T | null) => void)\n | { current: T | null }\n | null\n | undefined;\n\nfunction applyRef<T>(el: T, ref: unknown): void {\n const r = ref as Ref<T>;\n if (!r) return;\n if (typeof r === 'function') {\n r(el);\n return;\n }\n try {\n (r as { current: T | null }).current = el;\n } catch {\n // Ignore write failures\n }\n}\n\n/**\n * Try to handle first render of element with keyed children\n * Returns true if handled, false to fall back to default rendering\n */\nfunction tryFirstRenderKeyedChildren(\n target: Element,\n vnode: DOMElement\n): boolean {\n const children = vnode.children;\n if (!Array.isArray(children) || !hasKeyedChildren(children)) {\n return false;\n }\n\n const el = createElementForNamespace(\n vnode.type as string,\n target.namespaceURI === SVG_NAMESPACE ? SVG_NAMESPACE : undefined\n );\n target.appendChild(el);\n\n applyPropsToElement(el, vnode.props || {});\n\n const newKeyMap = reconcileKeyedChildren(el, children, undefined);\n keyedElements.set(el, newKeyMap);\n return true;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fragment Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Check if a vnode is a Fragment\n */\nfunction isFragment(vnode: unknown): vnode is DOMElement {\n return (\n _isDOMElement(vnode) &&\n typeof (vnode as DOMElement).type === 'symbol' &&\n ((vnode as DOMElement).type === Fragment ||\n String((vnode as DOMElement).type) === 'Symbol(askr.fragment)')\n );\n}\n\n/**\n * Unwrap Fragment to get children array\n */\nfunction getFragmentChildren(vnode: DOMElement): unknown[] {\n const fragmentChildren = vnode.props?.children ?? vnode.children ?? [];\n return Array.isArray(fragmentChildren)\n ? fragmentChildren\n : [fragmentChildren];\n}\n\nexport function evaluate(\n node: unknown,\n target: Element | null,\n context?: object\n): void {\n if (!target) return;\n // SSR guard: avoid DOM ops when not in a browser-like environment\n if (typeof document === 'undefined') {\n if (getRuntimeEnv().NODE_ENV !== 'production') {\n try {\n // Keep this lightweight and non-throwing so test harnesses and SSR\n // imports don't crash at runtime; callers should avoid calling\n // `evaluate` in SSR, but we make it safe as a no-op.\n console.warn('[Askr] evaluate() called in non-DOM environment; no-op.');\n } catch (e) {\n void e;\n }\n }\n return;\n }\n // Debug tracing to help understand why initial mounts sometimes don't\n // result in DOM mutations during tests.\n\n // If context provided, use component-owned DOM range (only replace that range)\n if (context && domRanges.has(context)) {\n const range = domRanges.get(context)!;\n const normalizedChildren =\n node === null || node === undefined || node === false\n ? []\n : isFragment(node)\n ? getFragmentChildren(node as DOMElement)\n : Array.isArray(node)\n ? node\n : [node];\n\n updateDOMRange(target, range, normalizedChildren);\n } else if (context) {\n // First render with context: create range markers\n const start = document.createComment('component-start');\n const end = document.createComment('component-end');\n target.appendChild(start);\n target.appendChild(end);\n domRanges.set(context, { start, end });\n // Render into the range\n const dom = createDOMNode(node);\n if (dom) {\n target.insertBefore(dom, end);\n }\n } else {\n // Root render (no context): smart update strategy\n // If target has exactly one child of the same element type as the vnode,\n // reuse the element and just update its content.\n // This preserves the element reference and event handlers across renders.\n\n let vnode = node;\n\n // If vnode is a Fragment, unwrap it to get the actual content for the smart update path.\n // Fragments become invisible in the DOM - their children are placed directly in the parent.\n // So for smart updates, we need to compare against the Fragment's children, not the Fragment itself.\n if (isFragment(vnode)) {\n const childArray = getFragmentChildren(vnode as DOMElement);\n // If Fragment has exactly one child that's an element, unwrap to that child\n // This allows the smart update path to match against it\n if (\n childArray.length === 1 &&\n _isDOMElement(childArray[0]) &&\n typeof (childArray[0] as DOMElement).type === 'string'\n ) {\n vnode = childArray[0];\n } else {\n // Fragment with multiple children - process each child with full smart update logic\n processFragmentChildren(target, childArray);\n return;\n }\n }\n\n if (\n _isDOMElement(vnode) &&\n (vnode as DOMElement).type === __FOR_BOUNDARY__\n ) {\n updateForBoundaryChildren(target, vnode as DOMElement);\n return;\n }\n\n if (Array.isArray(vnode)) {\n updateElementChildren(target, vnode);\n return;\n }\n\n // CRITICAL FIX: Check if target itself matches the vnode type AND target is the component's own element\n // This handles inline-rendered components where target IS the component's element.\n // For root components, target is a container (not the component's rendered element), so we skip this path.\n // We detect this by checking if the instance's target property points to this same element.\n const targetWithInstance = target as Element & {\n __ASKR_INSTANCE?: ComponentInstance;\n };\n const targetInstance = targetWithInstance.__ASKR_INSTANCE;\n if (targetInstance && targetInstance.target === target) {\n // This is a nested component's own element.\n if (_isDOMElement(vnode) && typeof vnode.type === 'function') {\n const syncedDom = syncComponentElement(\n target,\n vnode as DOMElement,\n vnode.type as (props: Props) => unknown,\n (((vnode as DOMElement).props ?? {}) as Record<string, unknown>) || {}\n );\n\n if (syncedDom instanceof Element) {\n (\n syncedDom as Element & { __ASKR_INSTANCE?: ComponentInstance }\n ).__ASKR_INSTANCE = targetInstance;\n targetInstance.target = syncedDom;\n return;\n }\n }\n\n if (\n _isDOMElement(vnode) &&\n typeof vnode.type === 'string' &&\n tagNamesEqualIgnoreCase(target.tagName, vnode.type)\n ) {\n // Tag names match - update in place\n smartUpdateElement(target, vnode as DOMElement);\n return;\n }\n\n // Tag names don't match - need to replace the element\n // Create new element and replace old one in parent\n const newDom = createDOMNode(vnode);\n if (newDom && target.parentNode) {\n // Transfer the component instance to the new element\n if (newDom instanceof Element) {\n (\n newDom as Element & { __ASKR_INSTANCE?: ComponentInstance }\n ).__ASKR_INSTANCE = targetInstance;\n targetInstance.target = newDom as Element;\n }\n // Clean up old element\n removeAllListeners(target);\n target.parentNode.replaceChild(newDom, target);\n return;\n }\n }\n\n const firstChild = target.children[0] as Element | undefined;\n\n if (\n firstChild &&\n _isDOMElement(vnode) &&\n typeof vnode.type === 'string' &&\n tagNamesEqualIgnoreCase(firstChild.tagName, vnode.type)\n ) {\n // Reuse the existing element - it's the same type\n smartUpdateElement(firstChild, vnode as DOMElement);\n } else {\n // Clear and rebuild (first render or structure changed)\n target.textContent = '';\n\n // Check if this is an element with keyed children even on first render\n if (\n _isDOMElement(vnode) &&\n typeof vnode.type === 'string' &&\n tryFirstRenderKeyedChildren(target, vnode as DOMElement)\n ) {\n return;\n }\n\n // Default: create whole tree\n const dom = createDOMNode(vnode);\n if (dom) {\n target.appendChild(dom);\n }\n }\n }\n}\n\nexport function clearDOMRange(context: object): void {\n domRanges.delete(context);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"evaluate.js","names":["sharedTagNamesEqualIgnoreCase"],"sources":["../../src/renderer/evaluate.ts"],"sourcesContent":["import { logger } from '../dev/logger';\nimport { getRuntimeEnv } from './env';\nimport type { Props } from '../common/props';\nimport type { ComponentInstance } from '../runtime/component';\nimport {\n elementListeners,\n removeAllListeners,\n cleanupInstanceIfPresent,\n} from './cleanup';\nimport { keyedElements } from './keyed';\nimport { reconcileKeyedChildren } from './reconcile';\nimport { _isDOMElement, type DOMElement, type VNode } from './types';\nimport { __FOR_BOUNDARY__ } from '../common/vnode';\nimport { evaluateForState } from '../runtime/for';\nimport {\n evaluateCaseState,\n evaluateShowState,\n type ControlBoundaryState,\n} from '../runtime/control';\nimport {\n createDOMNode,\n commitForBoundaryChildren,\n syncComponentElement,\n updateElementFromVnode,\n updateUnkeyedChildren,\n performBulkPositionalKeyedTextUpdate,\n performBulkTextReplace,\n isBulkTextFastPathEligible,\n} from './dom';\nimport { setDevValue, incDevCounter } from '../runtime/dev-namespace';\nimport { Fragment } from '../common/jsx';\nimport {\n createWrappedHandler,\n extractKey,\n getPassiveOptions,\n parseEventName,\n tagNamesEqualIgnoreCase as sharedTagNamesEqualIgnoreCase,\n writeElementClassName,\n} from './utils';\n\n/**\n * Internal marker for component-owned DOM ranges\n * Allows efficient partial DOM updates instead of clearing entire target\n */\ninterface DOMRange {\n start: Node; // Start marker (comment node)\n end: Node; // End marker (comment node)\n}\n\nexport const IS_DOM_AVAILABLE = typeof document !== 'undefined';\n\nconst SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\nfunction resolveChildNamespace(\n type: string,\n parentNamespace?: string\n): string | undefined {\n if (type === 'svg') return SVG_NAMESPACE;\n if (parentNamespace === SVG_NAMESPACE && type !== 'foreignObject') {\n return SVG_NAMESPACE;\n }\n return undefined;\n}\n\nfunction createElementForNamespace(\n type: string,\n parentNamespace?: string\n): Element {\n const namespace = resolveChildNamespace(type, parentNamespace);\n return namespace\n ? document.createElementNS(namespace, type)\n : document.createElement(type);\n}\n\nconst domRanges = new WeakMap<object, DOMRange>();\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helper Types & Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface SimpleTextResult {\n isSimple: true;\n text: string;\n}\n\ninterface NotSimpleTextResult {\n isSimple: false;\n text?: undefined;\n}\n\ntype TextCheckResult = SimpleTextResult | NotSimpleTextResult;\n\nfunction tagNamesEqualIgnoreCase(a: string, b: string): boolean {\n return sharedTagNamesEqualIgnoreCase(a, b);\n}\n\n/**\n * Check if vnode children represent a simple text value\n */\nfunction checkSimpleText(vnodeChildren: unknown): TextCheckResult {\n if (!Array.isArray(vnodeChildren)) {\n if (\n typeof vnodeChildren === 'string' ||\n typeof vnodeChildren === 'number'\n ) {\n return { isSimple: true, text: String(vnodeChildren) };\n }\n } else if (vnodeChildren.length === 1) {\n const child = vnodeChildren[0];\n if (typeof child === 'string' || typeof child === 'number') {\n return { isSimple: true, text: String(child) };\n }\n }\n return { isSimple: false };\n}\n\n/**\n * Try to update a single text node in place\n * Returns true if update was performed, false otherwise\n */\nfunction tryUpdateTextInPlace(element: Element, text: string): boolean {\n if (\n element.childNodes.length === 1 &&\n element.firstChild?.nodeType === 3 // TEXT_NODE\n ) {\n (element.firstChild as Text).data = text;\n return true;\n }\n return false;\n}\n\n/**\n * Build a key map from existing DOM children\n */\nfunction buildKeyMapFromDOM(parent: Element): Map<string | number, Element> {\n const keyMap = new Map<string | number, Element>();\n for (\n let child = parent.firstElementChild;\n child;\n child = child.nextElementSibling\n ) {\n const k = child.getAttribute('data-key');\n if (k !== null) {\n keyMap.set(k, child);\n const n = Number(k);\n if (!Number.isNaN(n)) keyMap.set(n, child);\n }\n }\n return keyMap;\n}\n\n/**\n * Get or initialize key map for an element\n */\nfunction getOrBuildKeyMap(\n parent: Element\n): Map<string | number, Element> | undefined {\n let keyMap = keyedElements.get(parent);\n if (!keyMap) {\n keyMap = buildKeyMapFromDOM(parent);\n if (keyMap.size > 0) {\n keyedElements.set(parent, keyMap);\n }\n }\n return keyMap.size > 0 ? keyMap : undefined;\n}\n\n/**\n * Check if children array contains keyed elements\n */\nfunction hasKeyedChildren(children: unknown[]): boolean {\n for (let i = 0; i < children.length; i++) {\n if (extractKey(children[i]) !== undefined) return true;\n }\n return false;\n}\n\n/**\n * Track bulk text fast-path stats (dev only)\n */\nfunction trackBulkTextStats(\n stats: ReturnType<typeof performBulkTextReplace>\n): void {\n if (getRuntimeEnv().NODE_ENV !== 'production') {\n try {\n setDevValue('__LAST_BULK_TEXT_FASTPATH_STATS', stats);\n incDevCounter('bulkTextHits');\n } catch {\n // ignore\n }\n }\n}\n\n/**\n * Track bulk text miss (dev only)\n */\nfunction trackBulkTextMiss(): void {\n if (getRuntimeEnv().NODE_ENV !== 'production') {\n try {\n incDevCounter('bulkTextMisses');\n } catch {\n // ignore\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Child Reconciliation Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Reconcile keyed children with optional forced bulk path\n */\nfunction reconcileKeyed(\n parent: Element,\n children: VNode[],\n oldKeyMap: Map<string | number, Element> | undefined\n): void {\n // Optional forced positional bulk path for large keyed lists\n if (getRuntimeEnv().ASKR_FORCE_BULK_POSREUSE === '1') {\n const result = tryForcedBulkKeyedPath(parent, children);\n if (result) return;\n }\n\n // Standard keyed reconciliation\n const newKeyMap = reconcileKeyedChildren(parent, children, oldKeyMap);\n keyedElements.set(parent, newKeyMap);\n}\n\n/**\n * Try the forced bulk keyed positional path\n * Returns true if applied, false to fall back to normal reconciliation\n */\nfunction tryForcedBulkKeyedPath(parent: Element, children: VNode[]): boolean {\n try {\n const keyedVnodes: Array<{ key: string | number; vnode: VNode }> = [];\n for (const child of children) {\n const key = extractKey(child);\n if (_isDOMElement(child) && key !== undefined) {\n keyedVnodes.push({\n key,\n vnode: child,\n });\n }\n }\n\n // Only apply when all children are keyed and count matches\n if (keyedVnodes.length === 0 || keyedVnodes.length !== children.length) {\n return false;\n }\n\n const fastPathEnv = getRuntimeEnv();\n if (\n fastPathEnv.ASKR_FASTPATH_DEBUG === '1' ||\n fastPathEnv.ASKR_FASTPATH_DEBUG === 'true'\n ) {\n logger.warn(\n '[Askr][FASTPATH] forced positional bulk keyed reuse (evaluate-level)'\n );\n }\n\n const stats = performBulkPositionalKeyedTextUpdate(parent, keyedVnodes);\n\n const statsEnv = getRuntimeEnv();\n if (\n statsEnv.NODE_ENV !== 'production' ||\n statsEnv.ASKR_FASTPATH_DEBUG === '1'\n ) {\n try {\n setDevValue('__LAST_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 1);\n incDevCounter('bulkKeyedPositionalForced');\n } catch {\n // ignore\n }\n }\n\n // Rebuild keyed map from DOM\n const newMap = buildKeyMapFromDOM(parent);\n keyedElements.set(parent, newMap);\n return true;\n } catch (err) {\n const fallbackEnv = getRuntimeEnv();\n if (\n fallbackEnv.ASKR_FASTPATH_DEBUG === '1' ||\n fallbackEnv.ASKR_FASTPATH_DEBUG === 'true'\n ) {\n logger.warn(\n '[Askr][FASTPATH] forced bulk path failed, falling back',\n err\n );\n }\n return false;\n }\n}\n\n/**\n * Reconcile unkeyed children, using bulk fast-path when eligible\n */\nfunction reconcileUnkeyed(parent: Element, children: VNode[]): void {\n if (isBulkTextFastPathEligible(parent, children)) {\n const stats = performBulkTextReplace(parent, children);\n trackBulkTextStats(stats);\n } else {\n trackBulkTextMiss();\n updateUnkeyedChildren(parent, children);\n }\n keyedElements.delete(parent);\n}\n\n/**\n * Update element children from a For boundary vnode\n * Evaluates the For and reconciles the keyed children with existing DOM\n */\nfunction updateForBoundaryChildren(\n element: Element,\n forVnode: DOMElement\n): void {\n const controlState =\n forVnode._controlState ??\n (forVnode._forState as ControlBoundaryState | undefined);\n if (!controlState) return;\n\n const childrenVNodes =\n controlState.kind === 'for'\n ? evaluateForState(controlState)\n : controlState.kind === 'show'\n ? evaluateShowState(controlState)\n : evaluateCaseState(controlState);\n commitForBoundaryChildren(element, controlState, childrenVNodes);\n}\n\n/**\n * Update element children (handles keyed, unkeyed, For boundaries, and non-array cases)\n */\nfunction updateElementChildren(element: Element, vnodeChildren: unknown): void {\n // CRITICAL: Check for null/undefined explicitly, not falsy values\n // because 0, false, and '' are valid children\n if (vnodeChildren === null || vnodeChildren === undefined) {\n // Clean up all children before clearing\n for (let n = element.firstChild; n; ) {\n const next = n.nextSibling;\n if (n instanceof Element) {\n removeAllListeners(n);\n cleanupInstanceIfPresent(n);\n }\n n = next;\n }\n element.textContent = '';\n keyedElements.delete(element);\n return;\n }\n\n // Handle For boundary as a special single-child case (non-array)\n if (\n !Array.isArray(vnodeChildren) &&\n _isDOMElement(vnodeChildren) &&\n (vnodeChildren as DOMElement).type === __FOR_BOUNDARY__\n ) {\n updateForBoundaryChildren(element, vnodeChildren as DOMElement);\n return;\n }\n\n if (!Array.isArray(vnodeChildren) && isFragment(vnodeChildren)) {\n updateElementChildren(element, getFragmentChildren(vnodeChildren));\n return;\n }\n\n if (!Array.isArray(vnodeChildren)) {\n // Clean up all children before clearing\n for (let n = element.firstChild; n; ) {\n const next = n.nextSibling;\n if (n instanceof Element) {\n removeAllListeners(n);\n cleanupInstanceIfPresent(n);\n }\n n = next;\n }\n element.textContent = '';\n const dom = createDOMNode(vnodeChildren);\n if (dom) element.appendChild(dom);\n keyedElements.delete(element);\n return;\n }\n\n // Handle For boundary wrapped in a single-element array [forVnode]\n // This is common when JSX transpiles children as arrays\n if (\n vnodeChildren.length === 1 &&\n _isDOMElement(vnodeChildren[0]) &&\n (vnodeChildren[0] as DOMElement).type === __FOR_BOUNDARY__\n ) {\n updateForBoundaryChildren(element, vnodeChildren[0] as DOMElement);\n return;\n }\n\n if (hasKeyedChildren(vnodeChildren)) {\n const oldKeyMap = getOrBuildKeyMap(element);\n try {\n reconcileKeyed(element, vnodeChildren, oldKeyMap);\n } catch {\n // Fall back on error\n const newKeyMap = reconcileKeyedChildren(\n element,\n vnodeChildren,\n oldKeyMap\n );\n keyedElements.set(element, newKeyMap);\n }\n } else {\n reconcileUnkeyed(element, vnodeChildren);\n }\n}\n\n/**\n * Perform a smart update on an existing element\n * Tries text-in-place update first, then full child reconciliation\n */\nfunction smartUpdateElement(element: Element, vnode: DOMElement): void {\n if (vnode.key == null && element.hasAttribute('data-key')) {\n const existingKey = element.getAttribute('data-key');\n if (existingKey !== null) {\n const numericKey = Number(existingKey);\n vnode.key = Number.isNaN(numericKey) ? existingKey : numericKey;\n }\n }\n\n let vnodeChildren = vnode.props?.children ?? vnode.children;\n\n // CRITICAL: For boundary vnodes must NOT be wrapped into an array\n // They need special handling in updateElementChildren to call updateForBoundaryChildren\n if (\n vnodeChildren &&\n _isDOMElement(vnodeChildren) &&\n (vnodeChildren as DOMElement).type === __FOR_BOUNDARY__\n ) {\n // Pass For boundary directly without wrapping\n updateElementChildren(element, vnodeChildren);\n updateElementFromVnode(element, vnode, false);\n return;\n }\n\n // Normalize: if children is a single vnode (not an array), wrap it\n if (vnodeChildren && !Array.isArray(vnodeChildren)) {\n vnodeChildren = [vnodeChildren];\n }\n\n const textCheck = checkSimpleText(vnodeChildren);\n\n if (textCheck.isSimple && tryUpdateTextInPlace(element, textCheck.text)) {\n // Text updated in place, nothing more to do for children\n } else {\n updateElementChildren(element, vnodeChildren);\n }\n\n updateElementFromVnode(element, vnode, false);\n}\n\n/**\n * Process Fragment children with smart updates for each child\n */\nfunction processFragmentChildren(target: Element, childArray: unknown[]): void {\n updateElementChildren(target, childArray);\n}\n\nfunction cleanupRangeNode(node: Node): void {\n if (node instanceof Element) {\n removeAllListeners(node);\n cleanupInstanceIfPresent(node);\n }\n}\n\nfunction updateDOMRange(\n target: Element,\n range: DOMRange,\n children: unknown[]\n): void {\n let current: Node | null = range.start.nextSibling;\n\n for (let i = 0; i < children.length; i++) {\n const nextChild = children[i];\n const currentNode = current === range.end ? null : current;\n const nextCurrent = currentNode?.nextSibling ?? null;\n\n if (nextChild === null || nextChild === undefined || nextChild === false) {\n if (currentNode) {\n cleanupRangeNode(currentNode);\n target.removeChild(currentNode);\n }\n current = nextCurrent;\n continue;\n }\n\n if (typeof nextChild === 'string' || typeof nextChild === 'number') {\n if (currentNode?.nodeType === 3) {\n (currentNode as Text).data = String(nextChild);\n } else {\n const textNode = document.createTextNode(String(nextChild));\n if (currentNode) {\n cleanupRangeNode(currentNode);\n target.replaceChild(textNode, currentNode);\n } else {\n target.insertBefore(textNode, range.end);\n }\n }\n current = nextCurrent;\n continue;\n }\n\n if (\n currentNode instanceof Element &&\n _isDOMElement(nextChild) &&\n typeof nextChild.type === 'string' &&\n tagNamesEqualIgnoreCase(currentNode.tagName, nextChild.type)\n ) {\n smartUpdateElement(currentNode, nextChild);\n current = nextCurrent;\n continue;\n }\n\n const newDom = createDOMNode(nextChild);\n if (!newDom) {\n if (currentNode) {\n cleanupRangeNode(currentNode);\n target.removeChild(currentNode);\n }\n current = nextCurrent;\n continue;\n }\n\n if (currentNode) {\n cleanupRangeNode(currentNode);\n target.replaceChild(newDom, currentNode);\n } else {\n target.insertBefore(newDom, range.end);\n }\n\n current = nextCurrent;\n }\n\n while (current && current !== range.end) {\n const next = current.nextSibling;\n cleanupRangeNode(current);\n target.removeChild(current);\n current = next;\n }\n}\n\n/**\n * Apply props/attributes to an element (used for first render with keyed children)\n */\nfunction applyPropsToElement(el: Element, props: Props): void {\n for (const [key, value] of Object.entries(props)) {\n if (key === 'children' || key === 'key') continue;\n if (value === undefined || value === null || value === false) continue;\n\n if (key === 'ref') {\n applyRef(el, value);\n continue;\n }\n\n const eventName = parseEventName(key);\n if (eventName) {\n const wrappedHandler = createWrappedHandler(value as EventListener, true);\n const options = getPassiveOptions(eventName);\n\n if (options !== undefined)\n el.addEventListener(eventName, wrappedHandler, options);\n else el.addEventListener(eventName, wrappedHandler);\n\n if (!elementListeners.has(el)) elementListeners.set(el, new Map());\n elementListeners.get(el)!.set(eventName, {\n handler: wrappedHandler,\n original: value as EventListener,\n options,\n });\n continue;\n }\n\n if (key === 'class' || key === 'className') {\n writeElementClassName(el, String(value));\n } else if (key === 'value' || key === 'checked') {\n (el as HTMLElement & Props)[key] = value;\n } else {\n el.setAttribute(key, String(value));\n }\n }\n}\n\ntype Ref<T> =\n | ((value: T | null) => void)\n | { current: T | null }\n | null\n | undefined;\n\nfunction applyRef<T>(el: T, ref: unknown): void {\n const r = ref as Ref<T>;\n if (!r) return;\n if (typeof r === 'function') {\n r(el);\n return;\n }\n try {\n (r as { current: T | null }).current = el;\n } catch {\n // Ignore write failures\n }\n}\n\n/**\n * Try to handle first render of element with keyed children\n * Returns true if handled, false to fall back to default rendering\n */\nfunction tryFirstRenderKeyedChildren(\n target: Element,\n vnode: DOMElement\n): boolean {\n const children = vnode.children;\n if (!Array.isArray(children) || !hasKeyedChildren(children)) {\n return false;\n }\n\n const el = createElementForNamespace(\n vnode.type as string,\n target.namespaceURI === SVG_NAMESPACE ? SVG_NAMESPACE : undefined\n );\n target.appendChild(el);\n\n applyPropsToElement(el, vnode.props || {});\n\n const newKeyMap = reconcileKeyedChildren(el, children, undefined);\n keyedElements.set(el, newKeyMap);\n return true;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fragment Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Check if a vnode is a Fragment\n */\nfunction isFragment(vnode: unknown): vnode is DOMElement {\n return (\n _isDOMElement(vnode) &&\n typeof (vnode as DOMElement).type === 'symbol' &&\n ((vnode as DOMElement).type === Fragment ||\n String((vnode as DOMElement).type) === 'Symbol(askr.fragment)')\n );\n}\n\n/**\n * Unwrap Fragment to get children array\n */\nfunction getFragmentChildren(vnode: DOMElement): unknown[] {\n const fragmentChildren = vnode.props?.children ?? vnode.children ?? [];\n return Array.isArray(fragmentChildren)\n ? fragmentChildren\n : [fragmentChildren];\n}\n\nexport function evaluate(\n node: unknown,\n target: Element | null,\n context?: object\n): void {\n if (!target) return;\n // SSR guard: avoid DOM ops when not in a browser-like environment\n if (typeof document === 'undefined') {\n if (getRuntimeEnv().NODE_ENV !== 'production') {\n try {\n // Keep this lightweight and non-throwing so test harnesses and SSR\n // imports don't crash at runtime; callers should avoid calling\n // `evaluate` in SSR, but we make it safe as a no-op.\n console.warn('[Askr] evaluate() called in non-DOM environment; no-op.');\n } catch (e) {\n void e;\n }\n }\n return;\n }\n // Debug tracing to help understand why initial mounts sometimes don't\n // result in DOM mutations during tests.\n\n // If context provided, use component-owned DOM range (only replace that range)\n if (context && domRanges.has(context)) {\n const range = domRanges.get(context)!;\n const normalizedChildren =\n node === null || node === undefined || node === false\n ? []\n : isFragment(node)\n ? getFragmentChildren(node as DOMElement)\n : Array.isArray(node)\n ? node\n : [node];\n\n updateDOMRange(target, range, normalizedChildren);\n } else if (context) {\n // First render with context: create range markers\n const start = document.createComment('component-start');\n const end = document.createComment('component-end');\n target.appendChild(start);\n target.appendChild(end);\n domRanges.set(context, { start, end });\n // Render into the range\n const dom = createDOMNode(node);\n if (dom) {\n target.insertBefore(dom, end);\n }\n } else {\n // Root render (no context): smart update strategy\n // If target has exactly one child of the same element type as the vnode,\n // reuse the element and just update its content.\n // This preserves the element reference and event handlers across renders.\n\n let vnode = node;\n\n // If vnode is a Fragment, unwrap it to get the actual content for the smart update path.\n // Fragments become invisible in the DOM - their children are placed directly in the parent.\n // So for smart updates, we need to compare against the Fragment's children, not the Fragment itself.\n if (isFragment(vnode)) {\n const childArray = getFragmentChildren(vnode as DOMElement);\n // If Fragment has exactly one child that's an element, unwrap to that child\n // This allows the smart update path to match against it\n if (\n childArray.length === 1 &&\n _isDOMElement(childArray[0]) &&\n typeof (childArray[0] as DOMElement).type === 'string'\n ) {\n vnode = childArray[0];\n } else {\n // Fragment with multiple children - process each child with full smart update logic\n processFragmentChildren(target, childArray);\n return;\n }\n }\n\n if (\n _isDOMElement(vnode) &&\n (vnode as DOMElement).type === __FOR_BOUNDARY__\n ) {\n updateForBoundaryChildren(target, vnode as DOMElement);\n return;\n }\n\n if (Array.isArray(vnode)) {\n updateElementChildren(target, vnode);\n return;\n }\n\n // CRITICAL FIX: Check if target itself matches the vnode type AND target is the component's own element\n // This handles inline-rendered components where target IS the component's element.\n // For root components, target is a container (not the component's rendered element), so we skip this path.\n // We detect this by checking if the instance's target property points to this same element.\n const targetWithInstance = target as Element & {\n __ASKR_INSTANCE?: ComponentInstance;\n };\n const targetInstance = targetWithInstance.__ASKR_INSTANCE;\n if (targetInstance && targetInstance.target === target) {\n // This is a nested component's own element.\n if (_isDOMElement(vnode) && typeof vnode.type === 'function') {\n const syncedDom = syncComponentElement(\n target,\n vnode as DOMElement,\n vnode.type as (props: Props) => unknown,\n (((vnode as DOMElement).props ?? {}) as Record<string, unknown>) || {}\n );\n\n if (syncedDom instanceof Element) {\n (\n syncedDom as Element & { __ASKR_INSTANCE?: ComponentInstance }\n ).__ASKR_INSTANCE = targetInstance;\n targetInstance.target = syncedDom;\n return;\n }\n }\n\n if (\n _isDOMElement(vnode) &&\n typeof vnode.type === 'string' &&\n tagNamesEqualIgnoreCase(target.tagName, vnode.type)\n ) {\n // Tag names match - update in place\n smartUpdateElement(target, vnode as DOMElement);\n return;\n }\n\n // Tag names don't match - need to replace the element\n // Create new element and replace old one in parent\n const newDom = createDOMNode(vnode);\n if (newDom && target.parentNode) {\n // Transfer the component instance to the new element\n if (newDom instanceof Element) {\n (\n newDom as Element & { __ASKR_INSTANCE?: ComponentInstance }\n ).__ASKR_INSTANCE = targetInstance;\n targetInstance.target = newDom as Element;\n }\n // Clean up old element\n removeAllListeners(target);\n target.parentNode.replaceChild(newDom, target);\n return;\n }\n }\n\n const firstChild = target.children[0] as Element | undefined;\n\n if (\n firstChild &&\n _isDOMElement(vnode) &&\n typeof vnode.type === 'string' &&\n tagNamesEqualIgnoreCase(firstChild.tagName, vnode.type)\n ) {\n // Reuse the existing element - it's the same type\n smartUpdateElement(firstChild, vnode as DOMElement);\n } else {\n // Clear and rebuild (first render or structure changed)\n target.textContent = '';\n\n // Check if this is an element with keyed children even on first render\n if (\n _isDOMElement(vnode) &&\n typeof vnode.type === 'string' &&\n tryFirstRenderKeyedChildren(target, vnode as DOMElement)\n ) {\n return;\n }\n\n // Default: create whole tree\n const dom = createDOMNode(vnode);\n if (dom) {\n target.appendChild(dom);\n }\n }\n }\n}\n\nexport function clearDOMRange(context: object): void {\n domRanges.delete(context);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAmDA,MAAM,gBAAgB;AAEtB,SAAS,sBACP,MACA,iBACoB;CACpB,IAAI,SAAS,OAAO,OAAO;CAC3B,IAAI,oBAAoB,iBAAiB,SAAS,iBAChD,OAAO;;AAKX,SAAS,0BACP,MACA,iBACS;CACT,MAAM,YAAY,sBAAsB,MAAM,gBAAgB;CAC9D,OAAO,YACH,SAAS,gBAAgB,WAAW,KAAK,GACzC,SAAS,cAAc,KAAK;;AAGlC,MAAM,4BAAY,IAAI,SAA2B;AAkBjD,SAAS,wBAAwB,GAAW,GAAoB;CAC9D,OAAOA,0BAA8B,GAAG,EAAE;;;;;AAM5C,SAAS,gBAAgB,eAAyC;CAChE,IAAI,CAAC,MAAM,QAAQ,cAAc,EAC/B;MACE,OAAO,kBAAkB,YACzB,OAAO,kBAAkB,UAEzB,OAAO;GAAE,UAAU;GAAM,MAAM,OAAO,cAAc;GAAE;QAEnD,IAAI,cAAc,WAAW,GAAG;EACrC,MAAM,QAAQ,cAAc;EAC5B,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAChD,OAAO;GAAE,UAAU;GAAM,MAAM,OAAO,MAAM;GAAE;;CAGlD,OAAO,EAAE,UAAU,OAAO;;;;;;AAO5B,SAAS,qBAAqB,SAAkB,MAAuB;CACrE,IACE,QAAQ,WAAW,WAAW,KAC9B,QAAQ,YAAY,aAAa,GACjC;EACA,QAAS,WAAoB,OAAO;EACpC,OAAO;;CAET,OAAO;;;;;AAMT,SAAS,mBAAmB,QAAgD;CAC1E,MAAM,yBAAS,IAAI,KAA+B;CAClD,KACE,IAAI,QAAQ,OAAO,mBACnB,OACA,QAAQ,MAAM,oBACd;EACA,MAAM,IAAI,MAAM,aAAa,WAAW;EACxC,IAAI,MAAM,MAAM;GACd,OAAO,IAAI,GAAG,MAAM;GACpB,MAAM,IAAI,OAAO,EAAE;GACnB,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE,OAAO,IAAI,GAAG,MAAM;;;CAG9C,OAAO;;;;;AAMT,SAAS,iBACP,QAC2C;CAC3C,IAAI,SAAS,cAAc,IAAI,OAAO;CACtC,IAAI,CAAC,QAAQ;EACX,SAAS,mBAAmB,OAAO;EACnC,IAAI,OAAO,OAAO,GAChB,cAAc,IAAI,QAAQ,OAAO;;CAGrC,OAAO,OAAO,OAAO,IAAI,SAAS;;;;;AAMpC,SAAS,iBAAiB,UAA8B;CACtD,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KACnC,IAAI,WAAW,SAAS,GAAG,KAAK,QAAW,OAAO;CAEpD,OAAO;;;;;AAMT,SAAS,mBACP,OACM;CACN,IAAI,eAAe,CAAC,aAAa,cAC/B,IAAI;EACF,YAAY,mCAAmC,MAAM;EACrD,cAAc,eAAe;SACvB;;;;;AASZ,SAAS,oBAA0B;CACjC,IAAI,eAAe,CAAC,aAAa,cAC/B,IAAI;EACF,cAAc,iBAAiB;SACzB;;;;;AAaZ,SAAS,eACP,QACA,UACA,WACM;CAEN,IAAI,eAAe,CAAC,6BAA6B,KAE/C;MADe,uBAAuB,QAAQ,SAC1C,EAAQ;;CAId,MAAM,YAAY,uBAAuB,QAAQ,UAAU,UAAU;CACrE,cAAc,IAAI,QAAQ,UAAU;;;;;;AAOtC,SAAS,uBAAuB,QAAiB,UAA4B;CAC3E,IAAI;EACF,MAAM,cAA6D,EAAE;EACrE,KAAK,MAAM,SAAS,UAAU;GAC5B,MAAM,MAAM,WAAW,MAAM;GAC7B,IAAI,cAAc,MAAM,IAAI,QAAQ,QAClC,YAAY,KAAK;IACf;IACA,OAAO;IACR,CAAC;;EAKN,IAAI,YAAY,WAAW,KAAK,YAAY,WAAW,SAAS,QAC9D,OAAO;EAGT,MAAM,cAAc,eAAe;EACnC,IACE,YAAY,wBAAwB,OACpC,YAAY,wBAAwB,QAEpC,OAAO,KACL,uEACD;EAGH,MAAM,QAAQ,qCAAqC,QAAQ,YAAY;EAEvE,MAAM,WAAW,eAAe;EAChC,IACE,SAAS,aAAa,gBACtB,SAAS,wBAAwB,KAEjC,IAAI;GACF,YAAY,yBAAyB,MAAM;GAC3C,YAAY,gCAAgC,EAAE;GAC9C,cAAc,4BAA4B;UACpC;EAMV,MAAM,SAAS,mBAAmB,OAAO;EACzC,cAAc,IAAI,QAAQ,OAAO;EACjC,OAAO;UACA,KAAK;EACZ,MAAM,cAAc,eAAe;EACnC,IACE,YAAY,wBAAwB,OACpC,YAAY,wBAAwB,QAEpC,OAAO,KACL,0DACA,IACD;EAEH,OAAO;;;;;;AAOX,SAAS,iBAAiB,QAAiB,UAAyB;CAClE,IAAI,2BAA2B,QAAQ,SAAS,EAE9C,mBADc,uBAAuB,QAAQ,SAC1B,CAAM;MACpB;EACL,mBAAmB;EACnB,sBAAsB,QAAQ,SAAS;;CAEzC,cAAc,OAAO,OAAO;;;;;;AAO9B,SAAS,0BACP,SACA,UACM;CACN,MAAM,eACJ,SAAS,iBACR,SAAS;CACZ,IAAI,CAAC,cAAc;CAQnB,0BAA0B,SAAS,cALjC,aAAa,SAAS,QAClB,iBAAiB,aAAa,GAC9B,aAAa,SAAS,SACpB,kBAAkB,aAAa,GAC/B,kBAAkB,aAAa,CACyB;;;;;AAMlE,SAAS,sBAAsB,SAAkB,eAA8B;CAG7E,IAAI,kBAAkB,QAAQ,kBAAkB,QAAW;EAEzD,KAAK,IAAI,IAAI,QAAQ,YAAY,IAAK;GACpC,MAAM,OAAO,EAAE;GACf,IAAI,aAAa,SAAS;IACxB,mBAAmB,EAAE;IACrB,yBAAyB,EAAE;;GAE7B,IAAI;;EAEN,QAAQ,cAAc;EACtB,cAAc,OAAO,QAAQ;EAC7B;;CAIF,IACE,CAAC,MAAM,QAAQ,cAAc,IAC7B,cAAc,cAAc,IAC3B,cAA6B,SAAS,kBACvC;EACA,0BAA0B,SAAS,cAA4B;EAC/D;;CAGF,IAAI,CAAC,MAAM,QAAQ,cAAc,IAAI,WAAW,cAAc,EAAE;EAC9D,sBAAsB,SAAS,oBAAoB,cAAc,CAAC;EAClE;;CAGF,IAAI,CAAC,MAAM,QAAQ,cAAc,EAAE;EAEjC,KAAK,IAAI,IAAI,QAAQ,YAAY,IAAK;GACpC,MAAM,OAAO,EAAE;GACf,IAAI,aAAa,SAAS;IACxB,mBAAmB,EAAE;IACrB,yBAAyB,EAAE;;GAE7B,IAAI;;EAEN,QAAQ,cAAc;EACtB,MAAM,MAAM,cAAc,cAAc;EACxC,IAAI,KAAK,QAAQ,YAAY,IAAI;EACjC,cAAc,OAAO,QAAQ;EAC7B;;CAKF,IACE,cAAc,WAAW,KACzB,cAAc,cAAc,GAAG,IAC9B,cAAc,GAAkB,SAAS,kBAC1C;EACA,0BAA0B,SAAS,cAAc,GAAiB;EAClE;;CAGF,IAAI,iBAAiB,cAAc,EAAE;EACnC,MAAM,YAAY,iBAAiB,QAAQ;EAC3C,IAAI;GACF,eAAe,SAAS,eAAe,UAAU;UAC3C;GAEN,MAAM,YAAY,uBAChB,SACA,eACA,UACD;GACD,cAAc,IAAI,SAAS,UAAU;;QAGvC,iBAAiB,SAAS,cAAc;;;;;;AAQ5C,SAAS,mBAAmB,SAAkB,OAAyB;CACrE,IAAI,MAAM,OAAO,QAAQ,QAAQ,aAAa,WAAW,EAAE;EACzD,MAAM,cAAc,QAAQ,aAAa,WAAW;EACpD,IAAI,gBAAgB,MAAM;GACxB,MAAM,aAAa,OAAO,YAAY;GACtC,MAAM,MAAM,OAAO,MAAM,WAAW,GAAG,cAAc;;;CAIzD,IAAI,gBAAgB,MAAM,OAAO,YAAY,MAAM;CAInD,IACE,iBACA,cAAc,cAAc,IAC3B,cAA6B,SAAS,kBACvC;EAEA,sBAAsB,SAAS,cAAc;EAC7C,uBAAuB,SAAS,OAAO,MAAM;EAC7C;;CAIF,IAAI,iBAAiB,CAAC,MAAM,QAAQ,cAAc,EAChD,gBAAgB,CAAC,cAAc;CAGjC,MAAM,YAAY,gBAAgB,cAAc;CAEhD,IAAI,UAAU,YAAY,qBAAqB,SAAS,UAAU,KAAK,EAAE,QAGvE,sBAAsB,SAAS,cAAc;CAG/C,uBAAuB,SAAS,OAAO,MAAM;;;;;AAM/C,SAAS,wBAAwB,QAAiB,YAA6B;CAC7E,sBAAsB,QAAQ,WAAW;;AAG3C,SAAS,iBAAiB,MAAkB;CAC1C,IAAI,gBAAgB,SAAS;EAC3B,mBAAmB,KAAK;EACxB,yBAAyB,KAAK;;;AAIlC,SAAS,eACP,QACA,OACA,UACM;CACN,IAAI,UAAuB,MAAM,MAAM;CAEvC,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,YAAY,SAAS;EAC3B,MAAM,cAAc,YAAY,MAAM,MAAM,OAAO;EACnD,MAAM,cAAc,aAAa,eAAe;EAEhD,IAAI,cAAc,QAAQ,cAAc,UAAa,cAAc,OAAO;GACxE,IAAI,aAAa;IACf,iBAAiB,YAAY;IAC7B,OAAO,YAAY,YAAY;;GAEjC,UAAU;GACV;;EAGF,IAAI,OAAO,cAAc,YAAY,OAAO,cAAc,UAAU;GAClE,IAAI,aAAa,aAAa,GAC5B,YAAsB,OAAO,OAAO,UAAU;QACzC;IACL,MAAM,WAAW,SAAS,eAAe,OAAO,UAAU,CAAC;IAC3D,IAAI,aAAa;KACf,iBAAiB,YAAY;KAC7B,OAAO,aAAa,UAAU,YAAY;WAE1C,OAAO,aAAa,UAAU,MAAM,IAAI;;GAG5C,UAAU;GACV;;EAGF,IACE,uBAAuB,WACvB,cAAc,UAAU,IACxB,OAAO,UAAU,SAAS,YAC1B,wBAAwB,YAAY,SAAS,UAAU,KAAK,EAC5D;GACA,mBAAmB,aAAa,UAAU;GAC1C,UAAU;GACV;;EAGF,MAAM,SAAS,cAAc,UAAU;EACvC,IAAI,CAAC,QAAQ;GACX,IAAI,aAAa;IACf,iBAAiB,YAAY;IAC7B,OAAO,YAAY,YAAY;;GAEjC,UAAU;GACV;;EAGF,IAAI,aAAa;GACf,iBAAiB,YAAY;GAC7B,OAAO,aAAa,QAAQ,YAAY;SAExC,OAAO,aAAa,QAAQ,MAAM,IAAI;EAGxC,UAAU;;CAGZ,OAAO,WAAW,YAAY,MAAM,KAAK;EACvC,MAAM,OAAO,QAAQ;EACrB,iBAAiB,QAAQ;EACzB,OAAO,YAAY,QAAQ;EAC3B,UAAU;;;;;;AAOd,SAAS,oBAAoB,IAAa,OAAoB;CAC5D,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;EAChD,IAAI,QAAQ,cAAc,QAAQ,OAAO;EACzC,IAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,OAAO;EAE9D,IAAI,QAAQ,OAAO;GACjB,SAAS,IAAI,MAAM;GACnB;;EAGF,MAAM,YAAY,eAAe,IAAI;EACrC,IAAI,WAAW;GACb,MAAM,iBAAiB,qBAAqB,OAAwB,KAAK;GACzE,MAAM,UAAU,kBAAkB,UAAU;GAE5C,IAAI,YAAY,QACd,GAAG,iBAAiB,WAAW,gBAAgB,QAAQ;QACpD,GAAG,iBAAiB,WAAW,eAAe;GAEnD,IAAI,CAAC,iBAAiB,IAAI,GAAG,EAAE,iBAAiB,IAAI,oBAAI,IAAI,KAAK,CAAC;GAClE,iBAAiB,IAAI,GAAG,CAAE,IAAI,WAAW;IACvC,SAAS;IACT,UAAU;IACV;IACD,CAAC;GACF;;EAGF,IAAI,QAAQ,WAAW,QAAQ,aAC7B,sBAAsB,IAAI,OAAO,MAAM,CAAC;OACnC,IAAI,QAAQ,WAAW,QAAQ,WACpC,GAA4B,OAAO;OAEnC,GAAG,aAAa,KAAK,OAAO,MAAM,CAAC;;;AAWzC,SAAS,SAAY,IAAO,KAAoB;CAC9C,MAAM,IAAI;CACV,IAAI,CAAC,GAAG;CACR,IAAI,OAAO,MAAM,YAAY;EAC3B,EAAE,GAAG;EACL;;CAEF,IAAI;EACF,EAA6B,UAAU;SACjC;;;;;;AASV,SAAS,4BACP,QACA,OACS;CACT,MAAM,WAAW,MAAM;CACvB,IAAI,CAAC,MAAM,QAAQ,SAAS,IAAI,CAAC,iBAAiB,SAAS,EACzD,OAAO;CAGT,MAAM,KAAK,0BACT,MAAM,MACN,OAAO,iBAAiB,gBAAgB,gBAAgB,OACzD;CACD,OAAO,YAAY,GAAG;CAEtB,oBAAoB,IAAI,MAAM,SAAS,EAAE,CAAC;CAE1C,MAAM,YAAY,uBAAuB,IAAI,UAAU,OAAU;CACjE,cAAc,IAAI,IAAI,UAAU;CAChC,OAAO;;;;;AAUT,SAAS,WAAW,OAAqC;CACvD,OACE,cAAc,MAAM,IACpB,OAAQ,MAAqB,SAAS,aACpC,MAAqB,SAAS,YAC9B,OAAQ,MAAqB,KAAK,KAAK;;;;;AAO7C,SAAS,oBAAoB,OAA8B;CACzD,MAAM,mBAAmB,MAAM,OAAO,YAAY,MAAM,YAAY,EAAE;CACtE,OAAO,MAAM,QAAQ,iBAAiB,GAClC,mBACA,CAAC,iBAAiB;;AAGxB,SAAgB,SACd,MACA,QACA,SACM;CACN,IAAI,CAAC,QAAQ;CAEb,IAAI,OAAO,aAAa,aAAa;EACnC,IAAI,eAAe,CAAC,aAAa,cAC/B,IAAI;GAIF,QAAQ,KAAK,0DAA0D;WAChE,GAAG;EAId;;CAMF,IAAI,WAAW,UAAU,IAAI,QAAQ,EAWnC,eAAe,QAVD,UAAU,IAAI,QAUL,EARrB,SAAS,QAAQ,SAAS,UAAa,SAAS,QAC5C,EAAE,GACF,WAAW,KAAK,GACd,oBAAoB,KAAmB,GACvC,MAAM,QAAQ,KAAK,GACjB,OACA,CAAC,KAAK,CAEiC;MAC5C,IAAI,SAAS;EAElB,MAAM,QAAQ,SAAS,cAAc,kBAAkB;EACvD,MAAM,MAAM,SAAS,cAAc,gBAAgB;EACnD,OAAO,YAAY,MAAM;EACzB,OAAO,YAAY,IAAI;EACvB,UAAU,IAAI,SAAS;GAAE;GAAO;GAAK,CAAC;EAEtC,MAAM,MAAM,cAAc,KAAK;EAC/B,IAAI,KACF,OAAO,aAAa,KAAK,IAAI;QAE1B;EAML,IAAI,QAAQ;EAKZ,IAAI,WAAW,MAAM,EAAE;GACrB,MAAM,aAAa,oBAAoB,MAAoB;GAG3D,IACE,WAAW,WAAW,KACtB,cAAc,WAAW,GAAG,IAC5B,OAAQ,WAAW,GAAkB,SAAS,UAE9C,QAAQ,WAAW;QACd;IAEL,wBAAwB,QAAQ,WAAW;IAC3C;;;EAIJ,IACE,cAAc,MAAM,IACnB,MAAqB,SAAS,kBAC/B;GACA,0BAA0B,QAAQ,MAAoB;GACtD;;EAGF,IAAI,MAAM,QAAQ,MAAM,EAAE;GACxB,sBAAsB,QAAQ,MAAM;GACpC;;EAUF,MAAM,iBAAiB,OAAmB;EAC1C,IAAI,kBAAkB,eAAe,WAAW,QAAQ;GAEtD,IAAI,cAAc,MAAM,IAAI,OAAO,MAAM,SAAS,YAAY;IAC5D,MAAM,YAAY,qBAChB,QACA,OACA,MAAM,OACH,MAAqB,SAAS,EAAE,KAAiC,EAAE,CACvE;IAED,IAAI,qBAAqB,SAAS;KAChC,UAEE,kBAAkB;KACpB,eAAe,SAAS;KACxB;;;GAIJ,IACE,cAAc,MAAM,IACpB,OAAO,MAAM,SAAS,YACtB,wBAAwB,OAAO,SAAS,MAAM,KAAK,EACnD;IAEA,mBAAmB,QAAQ,MAAoB;IAC/C;;GAKF,MAAM,SAAS,cAAc,MAAM;GACnC,IAAI,UAAU,OAAO,YAAY;IAE/B,IAAI,kBAAkB,SAAS;KAC7B,OAEE,kBAAkB;KACpB,eAAe,SAAS;;IAG1B,mBAAmB,OAAO;IAC1B,OAAO,WAAW,aAAa,QAAQ,OAAO;IAC9C;;;EAIJ,MAAM,aAAa,OAAO,SAAS;EAEnC,IACE,cACA,cAAc,MAAM,IACpB,OAAO,MAAM,SAAS,YACtB,wBAAwB,WAAW,SAAS,MAAM,KAAK,EAGvD,mBAAmB,YAAY,MAAoB;OAC9C;GAEL,OAAO,cAAc;GAGrB,IACE,cAAc,MAAM,IACpB,OAAO,MAAM,SAAS,YACtB,4BAA4B,QAAQ,MAAoB,EAExD;GAIF,MAAM,MAAM,cAAc,MAAM;GAChC,IAAI,KACF,OAAO,YAAY,IAAI"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastpath.js","names":[],"sources":["../../src/renderer/fastpath.ts"],"sourcesContent":["import type { VNode } from './types';\nimport { createDOMNode, updateElementFromVnode } from './dom';\nimport { _reconcilerRecordedParents } from './keyed';\nimport { logger } from '../dev/logger';\nimport { getRuntimeEnv } from './env';\nimport { cleanupInstanceIfPresent, removeAllListeners } from './cleanup';\nimport { setDevValue, incDevCounter } from '../runtime/dev-namespace';\nimport { isSchedulerExecuting } from '../runtime/scheduler';\nimport { isBulkCommitActive, markFastPathApplied } from '../runtime/fastlane';\n\nexport const IS_DOM_AVAILABLE = typeof document !== 'undefined';\n\n// Apply the \"renderer\" fast-path: build final node list reusing existing\n// elements by key when possible, then perform a single atomic replaceChildren\n// commit. Returns a new key map when the fast-path is applied, otherwise\n// returns null to indicate the caller should continue with fallback paths.\nexport function applyRendererFastPath(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n oldKeyMap?: Map<string | number, Element>,\n unkeyedVnodes?: VNode[]\n): Map<string | number, Element> | null {\n // SSR guard: fast-path is DOM-specific\n if (typeof document === 'undefined') return null;\n\n const totalKeyed = keyedVnodes.length;\n if (totalKeyed === 0 && (!unkeyedVnodes || unkeyedVnodes.length === 0))\n return null;\n\n // Dev invariant: ensure we are executing inside the scheduler/commit flush\n if (!isSchedulerExecuting()) {\n logger.warn(\n '[Askr][FASTPATH][DEV] Fast-path reconciliation invoked outside scheduler execution'\n );\n }\n\n // Choose lookup strategy depending on size (linear scan for small lists)\n let parentChildrenArr: Element[] | undefined;\n let localOldKeyMap: Map<string | number, Element> | undefined;\n\n if (totalKeyed <= 20) {\n // Small lists: use array scan (faster than Map overhead for ≤20 items)\n try {\n parentChildrenArr = Array.from(\n parent.children,\n (child) => child as Element\n );\n } catch (e) {\n parentChildrenArr = undefined;\n void e;\n }\n } else if (!oldKeyMap || oldKeyMap.size === 0) {\n // Medium/large lists without existing key map: build from DOM\n localOldKeyMap = new Map<string | number, Element>();\n try {\n for (let ch = parent.firstElementChild; ch; ch = ch.nextElementSibling) {\n const k = ch.getAttribute('data-key');\n if (k !== null) {\n localOldKeyMap.set(k, ch);\n const n = Number(k);\n if (!Number.isNaN(n)) localOldKeyMap.set(n, ch);\n }\n }\n } catch (e) {\n localOldKeyMap = undefined;\n void e;\n }\n }\n // else: reuse oldKeyMap directly (most common case for repeat renders)\n\n const finalNodes: Node[] = [];\n let mapLookups = 0;\n let createdNodes = 0;\n let reusedCount = 0;\n\n for (let i = 0; i < keyedVnodes.length; i++) {\n const { key, vnode } = keyedVnodes[i];\n mapLookups++;\n\n let el: Element | undefined;\n if (totalKeyed <= 20 && parentChildrenArr) {\n const ks = String(key);\n for (let j = 0; j < parentChildrenArr.length; j++) {\n const ch = parentChildrenArr[j];\n const k = ch.getAttribute('data-key');\n if (k !== null && (k === ks || Number(k) === (key as number))) {\n el = ch;\n break;\n }\n }\n if (!el) el = oldKeyMap?.get(key);\n } else {\n el = localOldKeyMap?.get(key as string | number) ?? oldKeyMap?.get(key);\n }\n\n if (el) {\n updateElementFromVnode(el, vnode);\n finalNodes.push(el);\n reusedCount++;\n } else {\n const newEl = createDOMNode(vnode);\n if (newEl) {\n finalNodes.push(newEl);\n createdNodes++;\n }\n }\n }\n\n // Add unkeyed nodes (detached as well)\n if (unkeyedVnodes && unkeyedVnodes.length) {\n for (const vnode of unkeyedVnodes) {\n const newEl = createDOMNode(vnode);\n if (newEl) {\n finalNodes.push(newEl);\n createdNodes++;\n }\n }\n }\n\n // Atomic commit\n try {\n const tFragmentStart = Date.now();\n const fragment = document.createDocumentFragment();\n let fragmentAppendCount = 0;\n for (let i = 0; i < finalNodes.length; i++) {\n fragment.appendChild(finalNodes[i]);\n fragmentAppendCount++;\n }\n\n // Pre-cleanup: remove component instances that will be removed by replaceChildren\n try {\n // At this point, any reused nodes have been moved into the fragment,\n // so whatever remains under parent will be removed by replaceChildren.\n for (let n = parent.firstChild; n; ) {\n const next = n.nextSibling;\n if (n instanceof Element) removeAllListeners(n);\n cleanupInstanceIfPresent(n);\n n = next;\n }\n } catch (e) {\n void e;\n }\n\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue('__LAST_DOM_REPLACE_STACK_FASTPATH', new Error().stack);\n } catch (e) {\n void e;\n }\n\n parent.replaceChildren(fragment);\n\n // Record that we performed exactly one DOM commit.\n try {\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 1);\n } catch (e) {\n void e;\n }\n\n // If a runtime bulk commit is active, mark this parent as fast-path applied.\n try {\n if (isBulkCommitActive()) markFastPathApplied(parent);\n } catch (e) {\n void e;\n }\n\n // Phase: bookkeeping - populate newKeyMap\n const newKeyMap = new Map<string | number, Element>();\n for (let i = 0; i < keyedVnodes.length; i++) {\n const key = keyedVnodes[i].key;\n const node = finalNodes[i];\n if (node instanceof Element) newKeyMap.set(key, node as Element);\n }\n\n // Dev tracing\n try {\n const stats = {\n n: totalKeyed,\n moves: 0,\n lisLen: 0,\n t_lookup: 0,\n t_fragment: Date.now() - tFragmentStart,\n t_commit: 0,\n t_bookkeeping: 0,\n fragmentAppendCount,\n mapLookups,\n createdNodes,\n reusedCount,\n } as const;\n if (typeof globalThis !== 'undefined') {\n setDevValue('__LAST_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_REUSED', reusedCount > 0);\n incDevCounter('fastpathHistoryPush');\n }\n const env = getRuntimeEnv();\n if (\n env.ASKR_FASTPATH_DEBUG === '1' ||\n env.ASKR_FASTPATH_DEBUG === 'true'\n ) {\n logger.warn(\n '[Askr][FASTPATH]',\n JSON.stringify({ n: totalKeyed, createdNodes, reusedCount })\n );\n }\n } catch (e) {\n void e;\n }\n\n // Record that reconciler recorded stats for this parent in this pass\n try {\n _reconcilerRecordedParents.add(parent);\n } catch (e) {\n void e;\n }\n\n return newKeyMap;\n } catch (e) {\n void e;\n return null;\n }\n}\n"],"mappings":";;;;;;;;;AAgBA,SAAgB,sBACd,QACA,aACA,WACA,eACsC;
|
|
1
|
+
{"version":3,"file":"fastpath.js","names":[],"sources":["../../src/renderer/fastpath.ts"],"sourcesContent":["import type { VNode } from './types';\nimport { createDOMNode, updateElementFromVnode } from './dom';\nimport { _reconcilerRecordedParents } from './keyed';\nimport { logger } from '../dev/logger';\nimport { getRuntimeEnv } from './env';\nimport { cleanupInstanceIfPresent, removeAllListeners } from './cleanup';\nimport { setDevValue, incDevCounter } from '../runtime/dev-namespace';\nimport { isSchedulerExecuting } from '../runtime/scheduler';\nimport { isBulkCommitActive, markFastPathApplied } from '../runtime/fastlane';\n\nexport const IS_DOM_AVAILABLE = typeof document !== 'undefined';\n\n// Apply the \"renderer\" fast-path: build final node list reusing existing\n// elements by key when possible, then perform a single atomic replaceChildren\n// commit. Returns a new key map when the fast-path is applied, otherwise\n// returns null to indicate the caller should continue with fallback paths.\nexport function applyRendererFastPath(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n oldKeyMap?: Map<string | number, Element>,\n unkeyedVnodes?: VNode[]\n): Map<string | number, Element> | null {\n // SSR guard: fast-path is DOM-specific\n if (typeof document === 'undefined') return null;\n\n const totalKeyed = keyedVnodes.length;\n if (totalKeyed === 0 && (!unkeyedVnodes || unkeyedVnodes.length === 0))\n return null;\n\n // Dev invariant: ensure we are executing inside the scheduler/commit flush\n if (!isSchedulerExecuting()) {\n logger.warn(\n '[Askr][FASTPATH][DEV] Fast-path reconciliation invoked outside scheduler execution'\n );\n }\n\n // Choose lookup strategy depending on size (linear scan for small lists)\n let parentChildrenArr: Element[] | undefined;\n let localOldKeyMap: Map<string | number, Element> | undefined;\n\n if (totalKeyed <= 20) {\n // Small lists: use array scan (faster than Map overhead for ≤20 items)\n try {\n parentChildrenArr = Array.from(\n parent.children,\n (child) => child as Element\n );\n } catch (e) {\n parentChildrenArr = undefined;\n void e;\n }\n } else if (!oldKeyMap || oldKeyMap.size === 0) {\n // Medium/large lists without existing key map: build from DOM\n localOldKeyMap = new Map<string | number, Element>();\n try {\n for (let ch = parent.firstElementChild; ch; ch = ch.nextElementSibling) {\n const k = ch.getAttribute('data-key');\n if (k !== null) {\n localOldKeyMap.set(k, ch);\n const n = Number(k);\n if (!Number.isNaN(n)) localOldKeyMap.set(n, ch);\n }\n }\n } catch (e) {\n localOldKeyMap = undefined;\n void e;\n }\n }\n // else: reuse oldKeyMap directly (most common case for repeat renders)\n\n const finalNodes: Node[] = [];\n let mapLookups = 0;\n let createdNodes = 0;\n let reusedCount = 0;\n\n for (let i = 0; i < keyedVnodes.length; i++) {\n const { key, vnode } = keyedVnodes[i];\n mapLookups++;\n\n let el: Element | undefined;\n if (totalKeyed <= 20 && parentChildrenArr) {\n const ks = String(key);\n for (let j = 0; j < parentChildrenArr.length; j++) {\n const ch = parentChildrenArr[j];\n const k = ch.getAttribute('data-key');\n if (k !== null && (k === ks || Number(k) === (key as number))) {\n el = ch;\n break;\n }\n }\n if (!el) el = oldKeyMap?.get(key);\n } else {\n el = localOldKeyMap?.get(key as string | number) ?? oldKeyMap?.get(key);\n }\n\n if (el) {\n updateElementFromVnode(el, vnode);\n finalNodes.push(el);\n reusedCount++;\n } else {\n const newEl = createDOMNode(vnode);\n if (newEl) {\n finalNodes.push(newEl);\n createdNodes++;\n }\n }\n }\n\n // Add unkeyed nodes (detached as well)\n if (unkeyedVnodes && unkeyedVnodes.length) {\n for (const vnode of unkeyedVnodes) {\n const newEl = createDOMNode(vnode);\n if (newEl) {\n finalNodes.push(newEl);\n createdNodes++;\n }\n }\n }\n\n // Atomic commit\n try {\n const tFragmentStart = Date.now();\n const fragment = document.createDocumentFragment();\n let fragmentAppendCount = 0;\n for (let i = 0; i < finalNodes.length; i++) {\n fragment.appendChild(finalNodes[i]);\n fragmentAppendCount++;\n }\n\n // Pre-cleanup: remove component instances that will be removed by replaceChildren\n try {\n // At this point, any reused nodes have been moved into the fragment,\n // so whatever remains under parent will be removed by replaceChildren.\n for (let n = parent.firstChild; n; ) {\n const next = n.nextSibling;\n if (n instanceof Element) removeAllListeners(n);\n cleanupInstanceIfPresent(n);\n n = next;\n }\n } catch (e) {\n void e;\n }\n\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue('__LAST_DOM_REPLACE_STACK_FASTPATH', new Error().stack);\n } catch (e) {\n void e;\n }\n\n parent.replaceChildren(fragment);\n\n // Record that we performed exactly one DOM commit.\n try {\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 1);\n } catch (e) {\n void e;\n }\n\n // If a runtime bulk commit is active, mark this parent as fast-path applied.\n try {\n if (isBulkCommitActive()) markFastPathApplied(parent);\n } catch (e) {\n void e;\n }\n\n // Phase: bookkeeping - populate newKeyMap\n const newKeyMap = new Map<string | number, Element>();\n for (let i = 0; i < keyedVnodes.length; i++) {\n const key = keyedVnodes[i].key;\n const node = finalNodes[i];\n if (node instanceof Element) newKeyMap.set(key, node as Element);\n }\n\n // Dev tracing\n try {\n const stats = {\n n: totalKeyed,\n moves: 0,\n lisLen: 0,\n t_lookup: 0,\n t_fragment: Date.now() - tFragmentStart,\n t_commit: 0,\n t_bookkeeping: 0,\n fragmentAppendCount,\n mapLookups,\n createdNodes,\n reusedCount,\n } as const;\n if (typeof globalThis !== 'undefined') {\n setDevValue('__LAST_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_REUSED', reusedCount > 0);\n incDevCounter('fastpathHistoryPush');\n }\n const env = getRuntimeEnv();\n if (\n env.ASKR_FASTPATH_DEBUG === '1' ||\n env.ASKR_FASTPATH_DEBUG === 'true'\n ) {\n logger.warn(\n '[Askr][FASTPATH]',\n JSON.stringify({ n: totalKeyed, createdNodes, reusedCount })\n );\n }\n } catch (e) {\n void e;\n }\n\n // Record that reconciler recorded stats for this parent in this pass\n try {\n _reconcilerRecordedParents.add(parent);\n } catch (e) {\n void e;\n }\n\n return newKeyMap;\n } catch (e) {\n void e;\n return null;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_evaluate","_markReactivePropsDirtySource"],"sources":["../../src/renderer/index.ts"],"sourcesContent":["// Renderer barrel entrypoint.\n// Keep this file small: re-export the public surface and attach the runtime\n// fast-lane bridge on import.\n\nexport * from './types';\nexport * from './cleanup';\nexport * from './keyed';\nexport * from './dom';\nexport { evaluate, clearDOMRange } from './evaluate';\n\nimport { evaluate as _evaluate } from './evaluate';\nimport { isKeyedReorderFastPathEligible, getKeyMapForElement } from './keyed';\nimport { markReactivePropsDirtySource as _markReactivePropsDirtySource } from './dom';\n\nexport function installRendererBridge(): true {\n if (typeof globalThis !== 'undefined') {\n const _g = globalThis as Record<string, unknown>;\n _g.__ASKR_RENDERER = {\n evaluate: _evaluate,\n isKeyedReorderFastPathEligible,\n getKeyMapForElement,\n markReactivePropsDirtySource: _markReactivePropsDirtySource,\n };\n }\n return true;\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,wBAA8B;
|
|
1
|
+
{"version":3,"file":"index.js","names":["_evaluate","_markReactivePropsDirtySource"],"sources":["../../src/renderer/index.ts"],"sourcesContent":["// Renderer barrel entrypoint.\n// Keep this file small: re-export the public surface and attach the runtime\n// fast-lane bridge on import.\n\nexport * from './types';\nexport * from './cleanup';\nexport * from './keyed';\nexport * from './dom';\nexport { evaluate, clearDOMRange } from './evaluate';\n\nimport { evaluate as _evaluate } from './evaluate';\nimport { isKeyedReorderFastPathEligible, getKeyMapForElement } from './keyed';\nimport { markReactivePropsDirtySource as _markReactivePropsDirtySource } from './dom';\n\nexport function installRendererBridge(): true {\n if (typeof globalThis !== 'undefined') {\n const _g = globalThis as Record<string, unknown>;\n _g.__ASKR_RENDERER = {\n evaluate: _evaluate,\n isKeyedReorderFastPathEligible,\n getKeyMapForElement,\n markReactivePropsDirtySource: _markReactivePropsDirtySource,\n };\n }\n return true;\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,wBAA8B;CAC5C,IAAI,OAAO,eAAe,aAAa;EACrC,MAAM,KAAK;EACX,GAAG,kBAAkB;GACTA;GACV;GACA;GAC8BC;GAC/B;;CAEH,OAAO"}
|
package/dist/renderer/keyed.js
CHANGED
|
@@ -96,7 +96,7 @@ function isKeyedReorderFastPathEligible(parent, newChildren, oldKeyMap) {
|
|
|
96
96
|
const cheapMoveTrigger = totalKeyed >= LIS_THRESHOLD_MIN && oldKeyOrder.length > 0 && moveCount > Math.max(64, Math.floor(totalKeyed * .1));
|
|
97
97
|
let lisTrigger = false;
|
|
98
98
|
let lisLen = 0;
|
|
99
|
-
if (totalKeyed >= LIS_THRESHOLD_MIN) {
|
|
99
|
+
if (totalKeyed >= LIS_THRESHOLD_MIN && !cheapMoveTrigger) {
|
|
100
100
|
const parentChildren = Array.from(parent.children);
|
|
101
101
|
lisLen = computeLISLength(keyedVnodes.map(({ key }) => {
|
|
102
102
|
const el = oldKeyMap?.get(key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyed.js","names":[],"sources":["../../src/renderer/keyed.ts"],"sourcesContent":["import type { VNode } from './types';\nimport {\n extractKey,\n buildKeyMapFromChildren,\n isIgnoredForPropChanges,\n hasPropChanged,\n} from './utils';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Key Map Registry\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const keyedElements = new WeakMap<\n Element,\n Map<string | number, Element>\n>();\n\n/**\n * Retrieve existing keyed map for a parent element (runtime use)\n */\nexport function getKeyMapForElement(el: Element) {\n return keyedElements.get(el);\n}\n\n/**\n * Populate a keyed map for an element by scanning its immediate children\n * for `data-key` attributes. Proactive initialization for runtime layers.\n */\nexport function populateKeyMapForElement(parent: Element): void {\n try {\n if (keyedElements.has(parent)) return;\n\n let domMap = buildKeyMapFromChildren(parent);\n\n // Fallback: map by textContent when keys are not materialized as attrs\n if (domMap.size === 0) {\n domMap = new Map();\n const children = Array.from(parent.children);\n for (const ch of children) {\n const text = (ch.textContent || '').trim();\n if (text) {\n domMap.set(text, ch);\n const n = Number(text);\n if (!Number.isNaN(n)) domMap.set(n, ch);\n }\n }\n }\n\n if (domMap.size > 0) keyedElements.set(parent, domMap);\n } catch {\n // ignore\n }\n}\n\n// Track which parents had the reconciler record fast-path stats during the\n// current evaluation, so we can preserve diagnostics across additional\n// reconciliations within the same render pass without leaking between runs.\nexport const _reconcilerRecordedParents = new WeakSet<Element>();\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fast-Path Eligibility\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Configuration: LIS fast-path thresholds\nconst LIS_THRESHOLD_MIN = 64; // Minimum list size for LIS optimization\n\ninterface KeyedVnode {\n key: string | number;\n vnode: VNode;\n}\n\n/**\n * Extract keyed vnodes from children array\n */\nfunction extractKeyedVnodes(newChildren: VNode[]): KeyedVnode[] {\n const result: KeyedVnode[] = [];\n for (const child of newChildren) {\n const key = extractKey(child);\n if (key !== undefined) {\n result.push({ key, vnode: child });\n }\n }\n return result;\n}\n\n/**\n * Compute LIS (Longest Increasing Subsequence) length for positions\n */\nfunction computeLISLength(positions: number[]): number {\n const tails: number[] = [];\n for (const pos of positions) {\n if (pos === -1) continue;\n let lo = 0;\n let hi = tails.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (tails[mid] < pos) lo = mid + 1;\n else hi = mid;\n }\n if (lo === tails.length) tails.push(pos);\n else tails[lo] = pos;\n }\n return tails.length;\n}\n\n/**\n * Check for prop changes between vnodes and existing elements\n */\nfunction checkVnodePropChanges(\n keyedVnodes: KeyedVnode[],\n oldKeyMap: Map<string | number, Element> | undefined\n): boolean {\n for (const { key, vnode } of keyedVnodes) {\n const el = oldKeyMap?.get(key);\n if (!el || typeof vnode !== 'object' || vnode === null) continue;\n const vnodeObj = vnode as unknown as { props?: Record<string, unknown> };\n const props = vnodeObj.props || {};\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 }\n return false;\n}\n\n/**\n * Determine if keyed reorder fast-path should be used\n */\nexport function isKeyedReorderFastPathEligible(\n parent: Element,\n newChildren: VNode[],\n oldKeyMap: Map<string | number, Element> | undefined\n) {\n const keyedVnodes = extractKeyedVnodes(newChildren);\n const totalKeyed = keyedVnodes.length;\n const newKeyOrder = keyedVnodes.map((kv) => kv.key);\n const oldKeyOrder = oldKeyMap ? Array.from(oldKeyMap.keys()) : [];\n\n // Count moves needed\n let moveCount = 0;\n for (let i = 0; i < newKeyOrder.length; i++) {\n const k = newKeyOrder[i];\n if (i >= oldKeyOrder.length || oldKeyOrder[i] !== k || !oldKeyMap?.has(k)) {\n moveCount++;\n }\n }\n\n // Check move threshold triggers\n const FAST_MOVE_THRESHOLD_ABS = 64;\n const FAST_MOVE_THRESHOLD_REL = 0.1;\n const cheapMoveTrigger =\n totalKeyed >= LIS_THRESHOLD_MIN &&\n oldKeyOrder.length > 0 &&\n moveCount >\n Math.max(\n FAST_MOVE_THRESHOLD_ABS,\n Math.floor(totalKeyed * FAST_MOVE_THRESHOLD_REL)\n );\n\n // Compute LIS trigger for large lists\n let lisTrigger = false;\n let lisLen = 0;\n if (totalKeyed >= LIS_THRESHOLD_MIN) {\n const parentChildren = Array.from(parent.children);\n const positions = keyedVnodes.map(({ key }) => {\n const el = oldKeyMap?.get(key);\n return el?.parentElement === parent ? parentChildren.indexOf(el) : -1;\n });\n lisLen = computeLISLength(positions);\n lisTrigger = lisLen < Math.floor(totalKeyed * 0.5);\n }\n\n // Check for props that would prevent fast-path\n // Only block if props have CHANGED, not just if props exist\n const hasPropChanges = checkVnodePropChanges(keyedVnodes, oldKeyMap);\n\n // Allow fastpath even with props present, as long as props haven't changed\n // This enables fast-path for common patterns like <Row item={item} onClick={...} />\n const useFastPath = (cheapMoveTrigger || lisTrigger) && !hasPropChanges;\n\n return {\n useFastPath,\n totalKeyed,\n moveCount,\n lisLen,\n hasPropChanges,\n } as const;\n}\n"],"mappings":";;AAYA,MAAa,gCAAgB,IAAI,SAG9B;;;;AAKH,SAAgB,oBAAoB,IAAa;
|
|
1
|
+
{"version":3,"file":"keyed.js","names":[],"sources":["../../src/renderer/keyed.ts"],"sourcesContent":["import type { VNode } from './types';\nimport {\n extractKey,\n buildKeyMapFromChildren,\n isIgnoredForPropChanges,\n hasPropChanged,\n} from './utils';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Key Map Registry\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const keyedElements = new WeakMap<\n Element,\n Map<string | number, Element>\n>();\n\n/**\n * Retrieve existing keyed map for a parent element (runtime use)\n */\nexport function getKeyMapForElement(el: Element) {\n return keyedElements.get(el);\n}\n\n/**\n * Populate a keyed map for an element by scanning its immediate children\n * for `data-key` attributes. Proactive initialization for runtime layers.\n */\nexport function populateKeyMapForElement(parent: Element): void {\n try {\n if (keyedElements.has(parent)) return;\n\n let domMap = buildKeyMapFromChildren(parent);\n\n // Fallback: map by textContent when keys are not materialized as attrs\n if (domMap.size === 0) {\n domMap = new Map();\n const children = Array.from(parent.children);\n for (const ch of children) {\n const text = (ch.textContent || '').trim();\n if (text) {\n domMap.set(text, ch);\n const n = Number(text);\n if (!Number.isNaN(n)) domMap.set(n, ch);\n }\n }\n }\n\n if (domMap.size > 0) keyedElements.set(parent, domMap);\n } catch {\n // ignore\n }\n}\n\n// Track which parents had the reconciler record fast-path stats during the\n// current evaluation, so we can preserve diagnostics across additional\n// reconciliations within the same render pass without leaking between runs.\nexport const _reconcilerRecordedParents = new WeakSet<Element>();\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fast-Path Eligibility\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Configuration: LIS fast-path thresholds\nconst LIS_THRESHOLD_MIN = 64; // Minimum list size for LIS optimization\n\ninterface KeyedVnode {\n key: string | number;\n vnode: VNode;\n}\n\n/**\n * Extract keyed vnodes from children array\n */\nfunction extractKeyedVnodes(newChildren: VNode[]): KeyedVnode[] {\n const result: KeyedVnode[] = [];\n for (const child of newChildren) {\n const key = extractKey(child);\n if (key !== undefined) {\n result.push({ key, vnode: child });\n }\n }\n return result;\n}\n\n/**\n * Compute LIS (Longest Increasing Subsequence) length for positions\n */\nfunction computeLISLength(positions: number[]): number {\n const tails: number[] = [];\n for (const pos of positions) {\n if (pos === -1) continue;\n let lo = 0;\n let hi = tails.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (tails[mid] < pos) lo = mid + 1;\n else hi = mid;\n }\n if (lo === tails.length) tails.push(pos);\n else tails[lo] = pos;\n }\n return tails.length;\n}\n\n/**\n * Check for prop changes between vnodes and existing elements\n */\nfunction checkVnodePropChanges(\n keyedVnodes: KeyedVnode[],\n oldKeyMap: Map<string | number, Element> | undefined\n): boolean {\n for (const { key, vnode } of keyedVnodes) {\n const el = oldKeyMap?.get(key);\n if (!el || typeof vnode !== 'object' || vnode === null) continue;\n const vnodeObj = vnode as unknown as { props?: Record<string, unknown> };\n const props = vnodeObj.props || {};\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 }\n return false;\n}\n\n/**\n * Determine if keyed reorder fast-path should be used\n */\nexport function isKeyedReorderFastPathEligible(\n parent: Element,\n newChildren: VNode[],\n oldKeyMap: Map<string | number, Element> | undefined\n) {\n const keyedVnodes = extractKeyedVnodes(newChildren);\n const totalKeyed = keyedVnodes.length;\n const newKeyOrder = keyedVnodes.map((kv) => kv.key);\n const oldKeyOrder = oldKeyMap ? Array.from(oldKeyMap.keys()) : [];\n\n // Count moves needed\n let moveCount = 0;\n for (let i = 0; i < newKeyOrder.length; i++) {\n const k = newKeyOrder[i];\n if (i >= oldKeyOrder.length || oldKeyOrder[i] !== k || !oldKeyMap?.has(k)) {\n moveCount++;\n }\n }\n\n // Check move threshold triggers\n const FAST_MOVE_THRESHOLD_ABS = 64;\n const FAST_MOVE_THRESHOLD_REL = 0.1;\n const cheapMoveTrigger =\n totalKeyed >= LIS_THRESHOLD_MIN &&\n oldKeyOrder.length > 0 &&\n moveCount >\n Math.max(\n FAST_MOVE_THRESHOLD_ABS,\n Math.floor(totalKeyed * FAST_MOVE_THRESHOLD_REL)\n );\n\n // Compute LIS trigger for large lists\n let lisTrigger = false;\n let lisLen = 0;\n if (totalKeyed >= LIS_THRESHOLD_MIN && !cheapMoveTrigger) {\n const parentChildren = Array.from(parent.children);\n const positions = keyedVnodes.map(({ key }) => {\n const el = oldKeyMap?.get(key);\n return el?.parentElement === parent ? parentChildren.indexOf(el) : -1;\n });\n lisLen = computeLISLength(positions);\n lisTrigger = lisLen < Math.floor(totalKeyed * 0.5);\n }\n\n // Check for props that would prevent fast-path\n // Only block if props have CHANGED, not just if props exist\n const hasPropChanges = checkVnodePropChanges(keyedVnodes, oldKeyMap);\n\n // Allow fastpath even with props present, as long as props haven't changed\n // This enables fast-path for common patterns like <Row item={item} onClick={...} />\n const useFastPath = (cheapMoveTrigger || lisTrigger) && !hasPropChanges;\n\n return {\n useFastPath,\n totalKeyed,\n moveCount,\n lisLen,\n hasPropChanges,\n } as const;\n}\n"],"mappings":"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"}
|
|
@@ -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 isKeyedReorderFastPathEligible,\n} from './keyed';\nimport { teardownNodeSubtree } from './cleanup';\nimport { applyRendererFastPath } from './fastpath';\nimport { getRuntimeEnv } from './env';\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: (props: Props) => unknown };\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, unkeyedVnodes } = partitionChildren(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 unkeyedVnodes,\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/** Partition children into keyed and unkeyed */\nfunction partitionChildren(newChildren: VNode[]): {\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>;\n unkeyedVnodes: VNode[];\n} {\n const keyedVnodes: Array<{ key: string | number; vnode: VNode }> = [];\n const unkeyedVnodes: 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 } else {\n unkeyedVnodes.push(child);\n }\n }\n\n return { keyedVnodes, unkeyedVnodes };\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 unkeyedVnodes: 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 newChildren,\n keyedVnodes,\n unkeyedVnodes,\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 newChildren: VNode[],\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n unkeyedVnodes: VNode[],\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> | null {\n const decision = isKeyedReorderFastPathEligible(\n parent,\n newChildren,\n oldKeyMap\n );\n const isWholeKeyedList =\n keyedVnodes.length === newChildren.length && unkeyedVnodes.length === 0;\n const canUseFastPath =\n decision.useFastPath && keyedVnodes.length >= 64 && isWholeKeyedList;\n\n // Apply fast-path only for a whole keyed list. Mixed root fragments may\n // contain a keyed portal sibling, but they are not reorder-only lists.\n if (canUseFastPath) {\n try {\n const map = applyRendererFastPath(\n parent,\n keyedVnodes,\n oldKeyMap,\n unkeyedVnodes\n );\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 isKeyedReorderFastPathEligible,\n} from './keyed';\nimport { teardownNodeSubtree } from './cleanup';\nimport { applyRendererFastPath } from './fastpath';\nimport { getRuntimeEnv } from './env';\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: (props: Props) => unknown };\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, unkeyedVnodes } = partitionChildren(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 unkeyedVnodes,\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/** Partition children into keyed and unkeyed */\nfunction partitionChildren(newChildren: VNode[]): {\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>;\n unkeyedVnodes: VNode[];\n} {\n const keyedVnodes: Array<{ key: string | number; vnode: VNode }> = [];\n const unkeyedVnodes: 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 } else {\n unkeyedVnodes.push(child);\n }\n }\n\n return { keyedVnodes, unkeyedVnodes };\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 unkeyedVnodes: 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 newChildren,\n keyedVnodes,\n unkeyedVnodes,\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 newChildren: VNode[],\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>,\n unkeyedVnodes: VNode[],\n oldKeyMap: Map<string | number, Element> | undefined\n): Map<string | number, Element> | null {\n const decision = isKeyedReorderFastPathEligible(\n parent,\n newChildren,\n oldKeyMap\n );\n const isWholeKeyedList =\n keyedVnodes.length === newChildren.length && unkeyedVnodes.length === 0;\n const canUseFastPath =\n decision.useFastPath && keyedVnodes.length >= 64 && isWholeKeyedList;\n\n // Apply fast-path only for a whole keyed list. Mixed root fragments may\n // contain a keyed portal sibling, but they are not reorder-only lists.\n if (canUseFastPath) {\n try {\n const map = applyRendererFastPath(\n parent,\n keyedVnodes,\n oldKeyMap,\n unkeyedVnodes\n );\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 +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// ─────────────────────────────────────────────────────────────────────────────\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 { getRuntimeEnv } from './env';\nimport { setDevValue, incDevCounter } from '../runtime/dev-namespace';\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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"}
|
package/dist/router/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AccessDecision, AccessDenyDecision, AccessRedirectDecision, GroupHelperOptions, LayoutScopeRecord, 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, RoutePolicy, RouteQuery, RouteRecord, RouteRenderResult, RouteRequestOptions, RouteRequestResult, RouteSnapshot } from "../common/router.js";
|
|
2
2
|
import { HistoryScrollBehavior, NavigateOptions, NavigationScrollBehavior, ScrollRestorationOptions, navigate } from "./navigate.js";
|
|
3
|
-
import {
|
|
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, 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,
|
|
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 };
|
package/dist/router/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { allow, deny, forbidden, notFound, redirect, requireAuth, requirePermission, requireRole, unauthorized } from "./policy.js";
|
|
2
|
-
import {
|
|
2
|
+
import { Outlet, clearRoutes, currentRoute, fallback, getManifest, getRoutes, group, index, lazy, page, registerRoutes, route } from "./route.js";
|
|
3
3
|
import { navigate } from "./navigate.js";
|
|
4
4
|
import { Link } from "../components/link.js";
|
|
5
|
-
export { Link,
|
|
5
|
+
export { Link, Outlet, allow, clearRoutes, currentRoute, deny, fallback, forbidden, getManifest, getRoutes, group, index, lazy, navigate, notFound, page, redirect, registerRoutes, requireAuth, requirePermission, requireRole, route, unauthorized };
|