@askrjs/askr 0.0.31 → 0.0.34

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.
@@ -227,7 +227,7 @@ 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
- let vnodeChildren = vnode.children || vnode.props?.children;
230
+ let vnodeChildren = vnode.props?.children ?? vnode.children;
231
231
  if (vnodeChildren && _isDOMElement(vnodeChildren) && vnodeChildren.type === __FOR_BOUNDARY__) {
232
232
  updateElementChildren(element, vnodeChildren);
233
233
  updateElementFromVnode(element, vnode, false);
@@ -368,7 +368,7 @@ function isFragment(vnode) {
368
368
  * Unwrap Fragment to get children array
369
369
  */
370
370
  function getFragmentChildren(vnode) {
371
- const fragmentChildren = vnode.props?.children || vnode.children || [];
371
+ const fragmentChildren = vnode.props?.children ?? vnode.children ?? [];
372
372
  return Array.isArray(fragmentChildren) ? fragmentChildren : [fragmentChildren];
373
373
  }
374
374
  function evaluate(node, target, context) {
@@ -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.children || vnode.props?.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 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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"}
@@ -6,7 +6,7 @@ import "./env.js";
6
6
  import { _reconcilerRecordedParents } from "./keyed.js";
7
7
  import { isBulkCommitActive, markFastPathApplied } from "../runtime/fastlane.js";
8
8
  import { cleanupInstanceIfPresent, removeAllListeners } from "./cleanup.js";
9
- import { createDOMNode } from "./dom.js";
9
+ import { createDOMNode, updateElementFromVnode } from "./dom.js";
10
10
  function applyRendererFastPath(parent, keyedVnodes, oldKeyMap, unkeyedVnodes) {
11
11
  if (typeof document === "undefined") return null;
12
12
  const totalKeyed = keyedVnodes.length;
@@ -55,6 +55,7 @@ function applyRendererFastPath(parent, keyedVnodes, oldKeyMap, unkeyedVnodes) {
55
55
  if (!el) el = oldKeyMap?.get(key);
56
56
  } else el = localOldKeyMap?.get(key) ?? oldKeyMap?.get(key);
57
57
  if (el) {
58
+ updateElementFromVnode(el, vnode);
58
59
  finalNodes.push(el);
59
60
  reusedCount++;
60
61
  } else {
@@ -1 +1 @@
1
- {"version":3,"file":"fastpath.js","names":[],"sources":["../../src/renderer/fastpath.ts"],"sourcesContent":["import type { VNode } from './types';\nimport { createDOMNode } 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 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;AAEtC,KAAI,OAAO,aAAa,YAAa,QAAO;CAE5C,MAAM,aAAa,YAAY;AAC/B,KAAI,eAAe,MAAM,CAAC,iBAAiB,cAAc,WAAW,GAClE,QAAO;AAGT,KAAI,CAAC,sBAAsB,CACzB,QAAO,KACL,qFACD;CAIH,IAAI;CACJ,IAAI;AAEJ,KAAI,cAAc,GAEhB,KAAI;AACF,sBAAoB,MAAM,KACxB,OAAO,WACN,UAAU,MACZ;UACM,GAAG;AACV,sBAAoB;;UAGb,CAAC,aAAa,UAAU,SAAS,GAAG;AAE7C,mCAAiB,IAAI,KAA+B;AACpD,MAAI;AACF,QAAK,IAAI,KAAK,OAAO,mBAAmB,IAAI,KAAK,GAAG,oBAAoB;IACtE,MAAM,IAAI,GAAG,aAAa,WAAW;AACrC,QAAI,MAAM,MAAM;AACd,oBAAe,IAAI,GAAG,GAAG;KACzB,MAAM,IAAI,OAAO,EAAE;AACnB,SAAI,CAAC,OAAO,MAAM,EAAE,CAAE,gBAAe,IAAI,GAAG,GAAG;;;WAG5C,GAAG;AACV,oBAAiB;;;CAMrB,MAAM,aAAqB,EAAE;CAC7B,IAAI,aAAa;CACjB,IAAI,eAAe;CACnB,IAAI,cAAc;AAElB,MAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;EAC3C,MAAM,EAAE,KAAK,UAAU,YAAY;AACnC;EAEA,IAAI;AACJ,MAAI,cAAc,MAAM,mBAAmB;GACzC,MAAM,KAAK,OAAO,IAAI;AACtB,QAAK,IAAI,IAAI,GAAG,IAAI,kBAAkB,QAAQ,KAAK;IACjD,MAAM,KAAK,kBAAkB;IAC7B,MAAM,IAAI,GAAG,aAAa,WAAW;AACrC,QAAI,MAAM,SAAS,MAAM,MAAM,OAAO,EAAE,KAAM,MAAiB;AAC7D,UAAK;AACL;;;AAGJ,OAAI,CAAC,GAAI,MAAK,WAAW,IAAI,IAAI;QAEjC,MAAK,gBAAgB,IAAI,IAAuB,IAAI,WAAW,IAAI,IAAI;AAGzE,MAAI,IAAI;AACN,cAAW,KAAK,GAAG;AACnB;SACK;GACL,MAAM,QAAQ,cAAc,MAAM;AAClC,OAAI,OAAO;AACT,eAAW,KAAK,MAAM;AACtB;;;;AAMN,KAAI,iBAAiB,cAAc,OACjC,MAAK,MAAM,SAAS,eAAe;EACjC,MAAM,QAAQ,cAAc,MAAM;AAClC,MAAI,OAAO;AACT,cAAW,KAAK,MAAM;AACtB;;;AAMN,KAAI;EACF,MAAM,iBAAiB,KAAK,KAAK;EACjC,MAAM,WAAW,SAAS,wBAAwB;EAClD,IAAI,sBAAsB;AAC1B,OAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,YAAS,YAAY,WAAW,GAAG;AACnC;;AAIF,MAAI;AAGF,QAAK,IAAI,IAAI,OAAO,YAAY,IAAK;IACnC,MAAM,OAAO,EAAE;AACf,QAAI,aAAa,QAAS,oBAAmB,EAAE;AAC/C,6BAAyB,EAAE;AAC3B,QAAI;;WAEC,GAAG;AAIZ,MAAI;AACF,iBAAc,sBAAsB;AACpC,eAAY,sDAAqC,IAAI,OAAO,EAAC,MAAM;WAC5D,GAAG;AAIZ,SAAO,gBAAgB,SAAS;AAGhC,MAAI;AACF,eAAY,gCAAgC,EAAE;WACvC,GAAG;AAKZ,MAAI;AACF,OAAI,oBAAoB,CAAE,qBAAoB,OAAO;WAC9C,GAAG;EAKZ,MAAM,4BAAY,IAAI,KAA+B;AACrD,OAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;GAC3C,MAAM,MAAM,YAAY,GAAG;GAC3B,MAAM,OAAO,WAAW;AACxB,OAAI,gBAAgB,QAAS,WAAU,IAAI,KAAK,KAAgB;;AAIlE,MAAI;GACF,MAAM,QAAQ;IACZ,GAAG;IACH,OAAO;IACP,QAAQ;IACR,UAAU;IACV,YAAY,KAAK,KAAK,GAAG;IACzB,UAAU;IACV,eAAe;IACf;IACA;IACA;IACA;IACD;AACD,OAAI,OAAO,eAAe,aAAa;AACrC,gBAAY,yBAAyB,MAAM;AAC3C,gBAAY,0BAA0B,cAAc,EAAE;AACtD,kBAAc,sBAAsB;;GAEtC,MAAM,MAAM,eAAe;AAC3B,OACE,IAAI,wBAAwB,OAC5B,IAAI,wBAAwB,OAE5B,QAAO,KACL,oBACA,KAAK,UAAU;IAAE,GAAG;IAAY;IAAc;IAAa,CAAC,CAC7D;WAEI,GAAG;AAKZ,MAAI;AACF,8BAA2B,IAAI,OAAO;WAC/B,GAAG;AAIZ,SAAO;UACA,GAAG;AAEV,SAAO"}
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,3 +1,4 @@
1
1
  import { getSignal } from "../runtime/component.js";
2
2
  import { ResourceResult, capture, on, resource, stream, task, timer } from "../runtime/operations.js";
3
- export { type ResourceResult, capture, getSignal, on, resource, stream, task, timer };
3
+ import { Mutation, Query, QueryConsistency, createMutation, createQuery, invalidate } from "../data/index.js";
4
+ export { type Mutation, type Query, type QueryConsistency, type ResourceResult, capture, createMutation, createQuery, getSignal, invalidate, on, resource, stream, task, timer };
@@ -1,3 +1,4 @@
1
1
  import { getSignal } from "../runtime/component.js";
2
2
  import { capture, on, resource, stream, task, timer } from "../runtime/operations.js";
3
- export { capture, getSignal, on, resource, stream, task, timer };
3
+ import { createMutation, createQuery, invalidate } from "../data/index.js";
4
+ export { capture, createMutation, createQuery, getSignal, invalidate, on, resource, stream, task, timer };
@@ -50,7 +50,7 @@ function unwrapFragmentForFastPath(vnode) {
50
50
  if (!vnode || typeof vnode !== "object" || !("type" in vnode)) return vnode;
51
51
  const v = vnode;
52
52
  if (typeof v.type === "symbol" && (v.type === Fragment || String(v.type) === "Symbol(askr.fragment)")) {
53
- const children = v.children || v.props?.children;
53
+ const children = v.props?.children ?? v.children;
54
54
  if (Array.isArray(children) && children.length > 0) {
55
55
  for (const child of children) if (child && typeof child === "object" && "type" in child) {
56
56
  if (typeof child.type === "string") return child;
@@ -84,7 +84,7 @@ function classifyUpdate(instance, result) {
84
84
  useFastPath: false,
85
85
  reason: "root-tag-mismatch"
86
86
  };
87
- const children = vnode.children || vnode.props?.children;
87
+ const children = vnode.props?.children ?? vnode.children;
88
88
  if (!Array.isArray(children)) return {
89
89
  useFastPath: false,
90
90
  reason: "no-children-array"
@@ -1 +1 @@
1
- {"version":3,"file":"fastlane.js","names":[],"sources":["../../src/runtime/fastlane.ts"],"sourcesContent":["import { isDevelopmentEnvironment } from '../common/env';\nimport { globalScheduler } from './scheduler';\nimport { logger } from '../dev/logger';\nimport type { ComponentInstance } from './component';\nimport { finalizeReadableSubscriptions } from './readable';\nimport {\n getKeyMapForElement,\n isKeyedReorderFastPathEligible,\n populateKeyMapForElement,\n} from '../renderer/keyed';\nimport { Fragment } from '../common/jsx';\nimport { setDevValue, getDevValue, getDevNamespace } from './dev-namespace';\n\nlet _bulkCommitActive = false;\nlet _appliedParents: WeakSet<Element> | null = null;\n\nexport function enterBulkCommit(): void {\n _bulkCommitActive = true;\n // Initialize registry of parents that had fast-path applied during this bulk commit\n _appliedParents = new WeakSet<Element>();\n\n // Clear any previously scheduled synchronous scheduler tasks so they don't\n // retrigger evaluations during the committed fast-path.\n try {\n const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n setDevValue('__ASKR_FASTLANE_CLEARED_TASKS', cleared);\n } catch (err) {\n if (isDevelopmentEnvironment()) throw err;\n }\n}\n\nexport function exitBulkCommit(): void {\n _bulkCommitActive = false;\n // Clear registry to avoid leaking across commits\n _appliedParents = null;\n}\n\nexport function isBulkCommitActive(): boolean {\n return _bulkCommitActive;\n}\n\n// Mark that a fast-path was applied on a parent element during the active\n// bulk commit. No-op if there is no active bulk commit.\nexport function markFastPathApplied(parent: Element): void {\n if (!_appliedParents) return;\n try {\n _appliedParents.add(parent);\n } catch (e) {\n void e;\n }\n}\n\nexport function isFastPathApplied(parent: Element): boolean {\n return !!(_appliedParents && _appliedParents.has(parent));\n}\n\nfunction finalizeReadSubscriptions(instance: ComponentInstance): void {\n finalizeReadableSubscriptions(instance);\n}\n\n/**\n * Attempt to execute a runtime fast-lane for a single component's synchronous\n * render result. Returns true if the fast-lane was used and commit was done.\n *\n * Preconditions (checked conservatively):\n * - The render result is an intrinsic element root with keyed children\n * - The renderer's fast-path heuristics indicate to use the fast-path\n * - No mount operations are pending on the component instance\n * - No child vnodes are component functions (avoid async/component mounts)\n */\n\n// Helper to unwrap Fragment vnodes to get the first intrinsic element child\nfunction unwrapFragmentForFastPath(vnode: unknown): unknown {\n if (!vnode || typeof vnode !== 'object' || !('type' in vnode)) return vnode;\n const v = vnode as {\n type: unknown;\n children?: unknown;\n props?: { children?: unknown };\n };\n // Check if it's a Fragment\n if (\n typeof v.type === 'symbol' &&\n (v.type === Fragment || String(v.type) === 'Symbol(askr.fragment)')\n ) {\n const children = v.children || v.props?.children;\n if (Array.isArray(children) && children.length > 0) {\n // Return the first child that's an intrinsic element\n for (const child of children) {\n if (child && typeof child === 'object' && 'type' in child) {\n const c = child as { type: unknown };\n if (typeof c.type === 'string') {\n return child;\n }\n }\n }\n }\n }\n return vnode;\n}\n\nexport function classifyUpdate(instance: ComponentInstance, result: unknown) {\n // Returns a classification describing whether this update is eligible for\n // the reorder-only fast-lane. The classifier mirrors renderer-level\n // heuristics and performs runtime-level checks (mounts, effects, component\n // children) that the renderer cannot reason about.\n\n // Unwrap Fragment to get the actual element vnode for classification\n const unwrappedResult = unwrapFragmentForFastPath(result);\n\n if (\n !unwrappedResult ||\n typeof unwrappedResult !== 'object' ||\n !('type' in unwrappedResult)\n )\n return { useFastPath: false, reason: 'not-vnode' };\n\n const vnode = unwrappedResult as {\n type: unknown;\n children?: unknown;\n props?: { children?: unknown };\n };\n if (vnode == null || typeof vnode.type !== 'string')\n return { useFastPath: false, reason: 'not-intrinsic' };\n\n const parent = instance.target;\n if (!parent) return { useFastPath: false, reason: 'no-root' };\n\n const firstChild = parent.children[0] as Element | undefined;\n if (!firstChild) return { useFastPath: false, reason: 'no-first-child' };\n if (firstChild.tagName.toLowerCase() !== String(vnode.type).toLowerCase())\n return { useFastPath: false, reason: 'root-tag-mismatch' };\n\n const children = vnode.children || vnode.props?.children;\n if (!Array.isArray(children))\n return { useFastPath: false, reason: 'no-children-array' };\n\n // Avoid component child vnodes (they may mount/unmount or trigger async)\n for (const c of children) {\n if (\n typeof c === 'object' &&\n c !== null &&\n 'type' in c &&\n typeof (c as { type?: unknown }).type === 'function'\n ) {\n return { useFastPath: false, reason: 'component-child-present' };\n }\n }\n\n if ((instance.mountOperations?.length ?? 0) > 0)\n return { useFastPath: false, reason: 'pending-mounts' };\n\n // Ask renderer for keyed reorder eligibility (prop differences & heuristics)\n // Ensure a keyed map is available for the first child by populating it proactively.\n try {\n populateKeyMapForElement(firstChild);\n } catch {\n // ignore\n }\n\n const oldKeyMap = getKeyMapForElement(firstChild);\n const decision = isKeyedReorderFastPathEligible(\n firstChild,\n children,\n oldKeyMap\n );\n\n if (!decision.useFastPath || decision.totalKeyed < 128)\n return { ...decision, useFastPath: false, reason: 'renderer-declined' };\n\n return { ...decision, useFastPath: true } as const;\n}\n\nexport function commitReorderOnly(\n instance: ComponentInstance,\n result: unknown\n): boolean {\n // Performs the minimal, synchronous reorder-only commit.\n const evaluate = (\n globalThis as {\n __ASKR_RENDERER?: {\n evaluate?: (node: unknown, target: Element | null) => void;\n };\n }\n ).__ASKR_RENDERER?.evaluate;\n\n if (typeof evaluate !== 'function') {\n logger.warn(\n '[Tempo][FASTPATH][DEV] renderer.evaluate not available; declining fast-lane'\n );\n return false;\n }\n\n const schedBefore = isDevelopmentEnvironment()\n ? globalScheduler.getState()\n : null;\n\n enterBulkCommit();\n\n try {\n globalScheduler.runWithSyncProgress(() => {\n evaluate(result, instance.target);\n\n // Finalize runtime bookkeeping (read subscriptions / tokens)\n try {\n finalizeReadSubscriptions(instance);\n } catch (e) {\n if (isDevelopmentEnvironment()) throw e;\n }\n });\n\n // Clear any synchronous tasks scheduled during the commit\n const clearedAfter = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n setDevValue('__FASTLANE_CLEARED_AFTER', clearedAfter);\n\n // Dev-only invariant checks\n if (isDevelopmentEnvironment()) {\n validateFastLaneInvariants(instance, schedBefore);\n }\n\n return true;\n } finally {\n // Clear bulk commit flag first\n exitBulkCommit();\n }\n // Note: The original code had a check here that was dead code because\n // exitBulkCommit() already ran in the finally block. This comment serves\n // as documentation that we've intentionally removed that unreachable code.\n}\n\n/**\n * Validates fast-lane invariants in dev mode.\n * Extracted to reduce complexity in commitReorderOnly.\n */\nfunction validateFastLaneInvariants(\n instance: ComponentInstance,\n schedBefore: ReturnType<typeof globalScheduler.getState> | null\n): void {\n const commitCount = getDevValue<number>('__LAST_FASTPATH_COMMIT_COUNT') ?? 0;\n const invariants = {\n commitCount,\n mountOps: instance.mountOperations?.length ?? 0,\n cleanupFns: instance.cleanupFns?.length ?? 0,\n };\n setDevValue('__LAST_FASTLANE_INVARIANTS', invariants);\n\n if (commitCount !== 1) {\n console.error(\n '[FASTLANE][INV] commitCount',\n commitCount,\n 'diag',\n getDevNamespace()\n );\n throw new Error(\n 'Fast-lane invariant violated: expected exactly one DOM commit during reorder-only commit'\n );\n }\n\n if (invariants.mountOps > 0) {\n throw new Error(\n 'Fast-lane invariant violated: mount operations were registered during bulk commit'\n );\n }\n\n if (invariants.cleanupFns > 0) {\n throw new Error(\n 'Fast-lane invariant violated: cleanup functions were added during bulk commit'\n );\n }\n\n const schedAfter = globalScheduler.getState();\n if (\n schedBefore &&\n schedAfter &&\n schedAfter.taskCount > schedBefore.taskCount\n ) {\n console.error(\n '[FASTLANE] schedBefore, schedAfter',\n schedBefore,\n schedAfter\n );\n console.error('[FASTLANE] enqueue logs', getDevValue('__ENQUEUE_LOGS'));\n throw new Error(\n 'Fast-lane invariant violated: scheduler enqueued leftover work during bulk commit'\n );\n }\n\n // Final quiescence assertion\n let finalState = globalScheduler.getState();\n const executing = globalScheduler.isExecuting();\n let outstandingAfter = Math.max(\n 0,\n finalState.taskCount - (executing ? 1 : 0)\n );\n\n if (outstandingAfter !== 0) {\n // Attempt to clear newly enqueued synchronous tasks\n let attempts = 0;\n while (attempts < 5) {\n const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n if (cleared === 0) break;\n attempts++;\n }\n finalState = globalScheduler.getState();\n outstandingAfter = Math.max(\n 0,\n finalState.taskCount - (globalScheduler.isExecuting() ? 1 : 0)\n );\n if (outstandingAfter !== 0) {\n console.error(\n '[FASTLANE] Post-commit enqueue logs:',\n getDevValue('__ENQUEUE_LOGS')\n );\n console.error(\n '[FASTLANE] Cleared counts:',\n getDevValue('__FASTLANE_CLEARED_TASKS'),\n getDevValue('__FASTLANE_CLEARED_AFTER')\n );\n throw new Error(\n `Fast-lane invariant violated: scheduler has ${finalState.taskCount} pending task(s) after commit`\n );\n }\n }\n}\n\nexport function tryRuntimeFastLaneSync(\n instance: ComponentInstance,\n result: unknown\n): boolean {\n const cls = classifyUpdate(instance, result);\n if (!cls.useFastPath) {\n // Clear stale fast-path diagnostics\n setDevValue('__LAST_FASTPATH_STATS', undefined);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 0);\n return false;\n }\n\n try {\n return commitReorderOnly(instance, result);\n } catch (err) {\n // Surface dev-only invariant failures, otherwise decline silently\n if (isDevelopmentEnvironment()) throw err;\n return false;\n }\n}\n\n// Expose fastlane bridge on globalThis for environments/tests\nif (typeof globalThis !== 'undefined') {\n (globalThis as Record<string, unknown>).__ASKR_FASTLANE = {\n isBulkCommitActive,\n enterBulkCommit,\n exitBulkCommit,\n tryRuntimeFastLaneSync,\n markFastPathApplied,\n isFastPathApplied,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fastlane.js","names":[],"sources":["../../src/runtime/fastlane.ts"],"sourcesContent":["import { isDevelopmentEnvironment } from '../common/env';\nimport { globalScheduler } from './scheduler';\nimport { logger } from '../dev/logger';\nimport type { ComponentInstance } from './component';\nimport { finalizeReadableSubscriptions } from './readable';\nimport {\n getKeyMapForElement,\n isKeyedReorderFastPathEligible,\n populateKeyMapForElement,\n} from '../renderer/keyed';\nimport { Fragment } from '../common/jsx';\nimport { setDevValue, getDevValue, getDevNamespace } from './dev-namespace';\n\nlet _bulkCommitActive = false;\nlet _appliedParents: WeakSet<Element> | null = null;\n\nexport function enterBulkCommit(): void {\n _bulkCommitActive = true;\n // Initialize registry of parents that had fast-path applied during this bulk commit\n _appliedParents = new WeakSet<Element>();\n\n // Clear any previously scheduled synchronous scheduler tasks so they don't\n // retrigger evaluations during the committed fast-path.\n try {\n const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n setDevValue('__ASKR_FASTLANE_CLEARED_TASKS', cleared);\n } catch (err) {\n if (isDevelopmentEnvironment()) throw err;\n }\n}\n\nexport function exitBulkCommit(): void {\n _bulkCommitActive = false;\n // Clear registry to avoid leaking across commits\n _appliedParents = null;\n}\n\nexport function isBulkCommitActive(): boolean {\n return _bulkCommitActive;\n}\n\n// Mark that a fast-path was applied on a parent element during the active\n// bulk commit. No-op if there is no active bulk commit.\nexport function markFastPathApplied(parent: Element): void {\n if (!_appliedParents) return;\n try {\n _appliedParents.add(parent);\n } catch (e) {\n void e;\n }\n}\n\nexport function isFastPathApplied(parent: Element): boolean {\n return !!(_appliedParents && _appliedParents.has(parent));\n}\n\nfunction finalizeReadSubscriptions(instance: ComponentInstance): void {\n finalizeReadableSubscriptions(instance);\n}\n\n/**\n * Attempt to execute a runtime fast-lane for a single component's synchronous\n * render result. Returns true if the fast-lane was used and commit was done.\n *\n * Preconditions (checked conservatively):\n * - The render result is an intrinsic element root with keyed children\n * - The renderer's fast-path heuristics indicate to use the fast-path\n * - No mount operations are pending on the component instance\n * - No child vnodes are component functions (avoid async/component mounts)\n */\n\n// Helper to unwrap Fragment vnodes to get the first intrinsic element child\nfunction unwrapFragmentForFastPath(vnode: unknown): unknown {\n if (!vnode || typeof vnode !== 'object' || !('type' in vnode)) return vnode;\n const v = vnode as {\n type: unknown;\n children?: unknown;\n props?: { children?: unknown };\n };\n // Check if it's a Fragment\n if (\n typeof v.type === 'symbol' &&\n (v.type === Fragment || String(v.type) === 'Symbol(askr.fragment)')\n ) {\n const children = v.props?.children ?? v.children;\n if (Array.isArray(children) && children.length > 0) {\n // Return the first child that's an intrinsic element\n for (const child of children) {\n if (child && typeof child === 'object' && 'type' in child) {\n const c = child as { type: unknown };\n if (typeof c.type === 'string') {\n return child;\n }\n }\n }\n }\n }\n return vnode;\n}\n\nexport function classifyUpdate(instance: ComponentInstance, result: unknown) {\n // Returns a classification describing whether this update is eligible for\n // the reorder-only fast-lane. The classifier mirrors renderer-level\n // heuristics and performs runtime-level checks (mounts, effects, component\n // children) that the renderer cannot reason about.\n\n // Unwrap Fragment to get the actual element vnode for classification\n const unwrappedResult = unwrapFragmentForFastPath(result);\n\n if (\n !unwrappedResult ||\n typeof unwrappedResult !== 'object' ||\n !('type' in unwrappedResult)\n )\n return { useFastPath: false, reason: 'not-vnode' };\n\n const vnode = unwrappedResult as {\n type: unknown;\n children?: unknown;\n props?: { children?: unknown };\n };\n if (vnode == null || typeof vnode.type !== 'string')\n return { useFastPath: false, reason: 'not-intrinsic' };\n\n const parent = instance.target;\n if (!parent) return { useFastPath: false, reason: 'no-root' };\n\n const firstChild = parent.children[0] as Element | undefined;\n if (!firstChild) return { useFastPath: false, reason: 'no-first-child' };\n if (firstChild.tagName.toLowerCase() !== String(vnode.type).toLowerCase())\n return { useFastPath: false, reason: 'root-tag-mismatch' };\n\n const children = vnode.props?.children ?? vnode.children;\n if (!Array.isArray(children))\n return { useFastPath: false, reason: 'no-children-array' };\n\n // Avoid component child vnodes (they may mount/unmount or trigger async)\n for (const c of children) {\n if (\n typeof c === 'object' &&\n c !== null &&\n 'type' in c &&\n typeof (c as { type?: unknown }).type === 'function'\n ) {\n return { useFastPath: false, reason: 'component-child-present' };\n }\n }\n\n if ((instance.mountOperations?.length ?? 0) > 0)\n return { useFastPath: false, reason: 'pending-mounts' };\n\n // Ask renderer for keyed reorder eligibility (prop differences & heuristics)\n // Ensure a keyed map is available for the first child by populating it proactively.\n try {\n populateKeyMapForElement(firstChild);\n } catch {\n // ignore\n }\n\n const oldKeyMap = getKeyMapForElement(firstChild);\n const decision = isKeyedReorderFastPathEligible(\n firstChild,\n children,\n oldKeyMap\n );\n\n if (!decision.useFastPath || decision.totalKeyed < 128)\n return { ...decision, useFastPath: false, reason: 'renderer-declined' };\n\n return { ...decision, useFastPath: true } as const;\n}\n\nexport function commitReorderOnly(\n instance: ComponentInstance,\n result: unknown\n): boolean {\n // Performs the minimal, synchronous reorder-only commit.\n const evaluate = (\n globalThis as {\n __ASKR_RENDERER?: {\n evaluate?: (node: unknown, target: Element | null) => void;\n };\n }\n ).__ASKR_RENDERER?.evaluate;\n\n if (typeof evaluate !== 'function') {\n logger.warn(\n '[Tempo][FASTPATH][DEV] renderer.evaluate not available; declining fast-lane'\n );\n return false;\n }\n\n const schedBefore = isDevelopmentEnvironment()\n ? globalScheduler.getState()\n : null;\n\n enterBulkCommit();\n\n try {\n globalScheduler.runWithSyncProgress(() => {\n evaluate(result, instance.target);\n\n // Finalize runtime bookkeeping (read subscriptions / tokens)\n try {\n finalizeReadSubscriptions(instance);\n } catch (e) {\n if (isDevelopmentEnvironment()) throw e;\n }\n });\n\n // Clear any synchronous tasks scheduled during the commit\n const clearedAfter = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n setDevValue('__FASTLANE_CLEARED_AFTER', clearedAfter);\n\n // Dev-only invariant checks\n if (isDevelopmentEnvironment()) {\n validateFastLaneInvariants(instance, schedBefore);\n }\n\n return true;\n } finally {\n // Clear bulk commit flag first\n exitBulkCommit();\n }\n // Note: The original code had a check here that was dead code because\n // exitBulkCommit() already ran in the finally block. This comment serves\n // as documentation that we've intentionally removed that unreachable code.\n}\n\n/**\n * Validates fast-lane invariants in dev mode.\n * Extracted to reduce complexity in commitReorderOnly.\n */\nfunction validateFastLaneInvariants(\n instance: ComponentInstance,\n schedBefore: ReturnType<typeof globalScheduler.getState> | null\n): void {\n const commitCount = getDevValue<number>('__LAST_FASTPATH_COMMIT_COUNT') ?? 0;\n const invariants = {\n commitCount,\n mountOps: instance.mountOperations?.length ?? 0,\n cleanupFns: instance.cleanupFns?.length ?? 0,\n };\n setDevValue('__LAST_FASTLANE_INVARIANTS', invariants);\n\n if (commitCount !== 1) {\n console.error(\n '[FASTLANE][INV] commitCount',\n commitCount,\n 'diag',\n getDevNamespace()\n );\n throw new Error(\n 'Fast-lane invariant violated: expected exactly one DOM commit during reorder-only commit'\n );\n }\n\n if (invariants.mountOps > 0) {\n throw new Error(\n 'Fast-lane invariant violated: mount operations were registered during bulk commit'\n );\n }\n\n if (invariants.cleanupFns > 0) {\n throw new Error(\n 'Fast-lane invariant violated: cleanup functions were added during bulk commit'\n );\n }\n\n const schedAfter = globalScheduler.getState();\n if (\n schedBefore &&\n schedAfter &&\n schedAfter.taskCount > schedBefore.taskCount\n ) {\n console.error(\n '[FASTLANE] schedBefore, schedAfter',\n schedBefore,\n schedAfter\n );\n console.error('[FASTLANE] enqueue logs', getDevValue('__ENQUEUE_LOGS'));\n throw new Error(\n 'Fast-lane invariant violated: scheduler enqueued leftover work during bulk commit'\n );\n }\n\n // Final quiescence assertion\n let finalState = globalScheduler.getState();\n const executing = globalScheduler.isExecuting();\n let outstandingAfter = Math.max(\n 0,\n finalState.taskCount - (executing ? 1 : 0)\n );\n\n if (outstandingAfter !== 0) {\n // Attempt to clear newly enqueued synchronous tasks\n let attempts = 0;\n while (attempts < 5) {\n const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n if (cleared === 0) break;\n attempts++;\n }\n finalState = globalScheduler.getState();\n outstandingAfter = Math.max(\n 0,\n finalState.taskCount - (globalScheduler.isExecuting() ? 1 : 0)\n );\n if (outstandingAfter !== 0) {\n console.error(\n '[FASTLANE] Post-commit enqueue logs:',\n getDevValue('__ENQUEUE_LOGS')\n );\n console.error(\n '[FASTLANE] Cleared counts:',\n getDevValue('__FASTLANE_CLEARED_TASKS'),\n getDevValue('__FASTLANE_CLEARED_AFTER')\n );\n throw new Error(\n `Fast-lane invariant violated: scheduler has ${finalState.taskCount} pending task(s) after commit`\n );\n }\n }\n}\n\nexport function tryRuntimeFastLaneSync(\n instance: ComponentInstance,\n result: unknown\n): boolean {\n const cls = classifyUpdate(instance, result);\n if (!cls.useFastPath) {\n // Clear stale fast-path diagnostics\n setDevValue('__LAST_FASTPATH_STATS', undefined);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 0);\n return false;\n }\n\n try {\n return commitReorderOnly(instance, result);\n } catch (err) {\n // Surface dev-only invariant failures, otherwise decline silently\n if (isDevelopmentEnvironment()) throw err;\n return false;\n }\n}\n\n// Expose fastlane bridge on globalThis for environments/tests\nif (typeof globalThis !== 'undefined') {\n (globalThis as Record<string, unknown>).__ASKR_FASTLANE = {\n isBulkCommitActive,\n enterBulkCommit,\n exitBulkCommit,\n tryRuntimeFastLaneSync,\n markFastPathApplied,\n isFastPathApplied,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"for.d.ts","names":[],"sources":["../../src/runtime/for.ts"],"mappings":";;;;;;;;;UAoDiB,eAAA;EACf,GAAA;EACA,IAAA,EAAM,CAAA;EACN,YAAA,EAAc,CAAA;EACd,UAAA,EAAY,aAAA,CAAc,CAAA;EAC1B,mBAAA,EAAqB,GAAA,CAAI,WAAA,EAAa,qBAAA;EACtC,WAAA,EAAa,cAAA;EACb,KAAA,EAAO,UAAA;AAAA;AAAA,KAGJ,aAAA,MAAmB,cAAA,CAAe,CAAA,WAC9B,CAAA;EACL,IAAA,IAAQ,CAAA;EACR,GAAA,CAAI,QAAA,EAAU,CAAA,EAAG,aAAA;AAAA;AAAA,KAGhB,qBAAA,GAAwB,cAAA;EAEzB,IAAA;EACA,GAAA,CAAI,QAAA,WAAmB,aAAA;AAAA;AAAA,KAGtB,cAAA,GAAiB,cAAA;EAElB,IAAA;EACA,GAAA,CAAI,QAAA,aAAqB,IAAA;AAAA;AAAA,KAGjB,iBAAA;AAAA,UAOK,QAAA;EACf,IAAA;EACA,YAAA,EAAc,CAAA;EACd,UAAA,EAAY,aAAA,CAAc,CAAA;EAC1B,QAAA,EAAU,KAAA;EACV,aAAA,EAAe,UAAA;EACf,KAAA,EAAO,GAAA,kBAAqB,eAAA,CAAgB,CAAA;EAC5C,WAAA,EAAa,KAAA;EACb,aAAA,EAAe,KAAA;EACf,IAAA,EAAM,WAAA,CAAY,QAAA,CAAS,CAAA,aAAc,IAAA,EAAM,CAAA,EAAG,KAAA;EAClD,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA;EAC5C,cAAA,EAAgB,iBAAA;EAChB,kBAAA,EAAoB,iBAAA;EACpB,gBAAA,EAAkB,IAAA;EAClB,mBAAA;EACA,kBAAA;EACA,eAAA;EACA,mBAAA;EACA,qBAAA;EACA,aAAA,EAAe,uBAAA,CAAwB,CAAA;EACvC,oBAAA;EACA,sBAAA;EACA,yBAAA;EACA,WAAA,GAAc,GAAA;AAAA;AAAA,iBAmBA,cAAA,GAAA,CACd,UAAA,EAAY,aAAA,CAAc,CAAA,GAC1B,IAAA,EAAM,QAAA,CAAS,CAAA,WACf,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA,EAC5C,QAAA,EAAU,KAAA,UACT,QAAA,CAAS,CAAA;AAAA,iBA6BI,WAAA,GAAA,CACd,UAAA,EAAY,aAAA,CAAc,CAAA,GAC1B,IAAA,EAAM,QAAA,CAAS,CAAA,WACf,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA,EAC5C,QAAA,EAAU,KAAA,UACT,QAAA,CAAS,CAAA;AAAA,iBA4SI,kBAAA,GAAA,CACd,GAAA,mBACA,IAAA,EAAM,CAAA,EACN,KAAA,UACA,QAAA,EAAU,QAAA,CAAS,CAAA,IAClB,eAAA,CAAgB,CAAA;AAAA,iBA+KH,iBAAA,GAAA,CACd,QAAA,EAAU,QAAA,CAAS,CAAA,GACnB,QAAA,EAAU,CAAA,KACT,KAAA;AAAA,iBAygBa,gBAAA,GAAA,CAAoB,QAAA,EAAU,QAAA,CAAS,CAAA,IAAK,KAAA;AAAA,iBAS5C,sBAAA,GAAA,CAA0B,QAAA,EAAU,QAAA,CAAS,CAAA"}
1
+ {"version":3,"file":"for.d.ts","names":[],"sources":["../../src/runtime/for.ts"],"mappings":";;;;;;;;;UAoDiB,eAAA;EACf,GAAA;EACA,IAAA,EAAM,CAAA;EACN,YAAA,EAAc,CAAA;EACd,UAAA,EAAY,aAAA,CAAc,CAAA;EAC1B,mBAAA,EAAqB,GAAA,CAAI,WAAA,EAAa,qBAAA;EACtC,WAAA,EAAa,cAAA;EACb,KAAA,EAAO,UAAA;AAAA;AAAA,KAGJ,aAAA,MAAmB,cAAA,CAAe,CAAA,WAC9B,CAAA;EACL,IAAA,IAAQ,CAAA;EACR,GAAA,CAAI,QAAA,EAAU,CAAA,EAAG,aAAA;AAAA;AAAA,KAGhB,qBAAA,GAAwB,cAAA;EAEzB,IAAA;EACA,GAAA,CAAI,QAAA,WAAmB,aAAA;AAAA;AAAA,KAGtB,cAAA,GAAiB,cAAA;EAElB,IAAA;EACA,GAAA,CAAI,QAAA,aAAqB,IAAA;AAAA;AAAA,KAGjB,iBAAA;AAAA,UAOK,QAAA;EACf,IAAA;EACA,YAAA,EAAc,CAAA;EACd,UAAA,EAAY,aAAA,CAAc,CAAA;EAC1B,QAAA,EAAU,KAAA;EACV,aAAA,EAAe,UAAA;EACf,KAAA,EAAO,GAAA,kBAAqB,eAAA,CAAgB,CAAA;EAC5C,WAAA,EAAa,KAAA;EACb,aAAA,EAAe,KAAA;EACf,IAAA,EAAM,WAAA,CAAY,QAAA,CAAS,CAAA,aAAc,IAAA,EAAM,CAAA,EAAG,KAAA;EAClD,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA;EAC5C,cAAA,EAAgB,iBAAA;EAChB,kBAAA,EAAoB,iBAAA;EACpB,gBAAA,EAAkB,IAAA;EAClB,mBAAA;EACA,kBAAA;EACA,eAAA;EACA,mBAAA;EACA,qBAAA;EACA,aAAA,EAAe,uBAAA,CAAwB,CAAA;EACvC,oBAAA;EACA,sBAAA;EACA,yBAAA;EACA,WAAA,GAAc,GAAA;AAAA;AAAA,iBAmBA,cAAA,GAAA,CACd,UAAA,EAAY,aAAA,CAAc,CAAA,GAC1B,IAAA,EAAM,QAAA,CAAS,CAAA,WACf,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA,EAC5C,QAAA,EAAU,KAAA,UACT,QAAA,CAAS,CAAA;AAAA,iBA6BI,WAAA,GAAA,CACd,UAAA,EAAY,aAAA,CAAc,CAAA,GAC1B,IAAA,EAAM,QAAA,CAAS,CAAA,WACf,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA,EAC5C,QAAA,EAAU,KAAA,UACT,QAAA,CAAS,CAAA;AAAA,iBAiTI,kBAAA,GAAA,CACd,GAAA,mBACA,IAAA,EAAM,CAAA,EACN,KAAA,UACA,QAAA,EAAU,QAAA,CAAS,CAAA,IAClB,eAAA,CAAgB,CAAA;AAAA,iBAgLH,iBAAA,GAAA,CACd,QAAA,EAAU,QAAA,CAAS,CAAA,GACnB,QAAA,EAAU,CAAA,KACT,KAAA;AAAA,iBAygBa,gBAAA,GAAA,CAAoB,QAAA,EAAU,QAAA,CAAS,CAAA,IAAK,KAAA;AAAA,iBAS5C,sBAAA,GAAA,CAA0B,QAAA,EAAU,QAAA,CAAS,CAAA"}
@@ -151,22 +151,27 @@ function createReactiveForItem(itemSignal, propertySignals) {
151
151
  if (Reflect.getOwnPropertyDescriptor(target, prop)) return Reflect.get(target, prop, receiver);
152
152
  const currentItem = itemSignal.peek();
153
153
  if (typeof prop !== "symbol") return getOrCreateForItemPropertySignal(currentItem, propertySignals, prop)();
154
+ recordReadableRead(itemSignal);
154
155
  return Reflect.get(Object(currentItem), prop, receiver);
155
156
  },
156
157
  has(target, prop) {
158
+ recordReadableRead(itemSignal);
157
159
  return prop in target || prop in Object(itemSignal.peek());
158
160
  },
159
161
  ownKeys(target) {
162
+ recordReadableRead(itemSignal);
160
163
  const keys = new Set(Reflect.ownKeys(target));
161
164
  for (const key of Reflect.ownKeys(Object(itemSignal.peek()))) keys.add(key);
162
165
  return Array.from(keys);
163
166
  },
164
167
  getOwnPropertyDescriptor(target, prop) {
168
+ recordReadableRead(itemSignal);
165
169
  const ownDescriptor = Reflect.getOwnPropertyDescriptor(target, prop);
166
170
  if (ownDescriptor) return ownDescriptor;
167
171
  return Object.getOwnPropertyDescriptor(Object(itemSignal.peek()), prop);
168
172
  },
169
173
  getPrototypeOf() {
174
+ recordReadableRead(itemSignal);
170
175
  return Object.getPrototypeOf(Object(itemSignal.peek()));
171
176
  }
172
177
  });
@@ -253,25 +258,25 @@ function updateItemInstance(forState, itemInstance, item) {
253
258
  if (itemInstance.item === item) return false;
254
259
  const previousItem = itemInstance.item;
255
260
  itemInstance.item = item;
261
+ let needsRerender = false;
256
262
  const itemSignal = itemInstance.itemSignal;
257
263
  if (!itemSignal) {
258
264
  rerenderItemInstance(forState, itemInstance, item);
259
265
  return true;
260
266
  }
261
- let hasPropertyRenderReaders = false;
262
267
  const propertySignals = itemInstance.itemPropertySignals;
263
268
  if (propertySignals && propertySignals.size > 0) for (const [prop, propertySignal] of propertySignals) {
264
269
  const previousValue = readForItemProperty(previousItem, prop);
265
270
  const nextValue = readForItemProperty(item, prop);
266
271
  if (Object.is(previousValue, nextValue)) continue;
267
272
  const propertyHasRenderReaders = (propertySignal._readers?.size ?? 0) > 0;
268
- if (propertyHasRenderReaders) hasPropertyRenderReaders = true;
273
+ if (propertyHasRenderReaders) needsRerender = true;
269
274
  propertySignal.set(nextValue, !propertyHasRenderReaders);
270
275
  }
271
276
  const hasRenderReaders = (itemSignal._readers?.size ?? 0) > 0;
277
+ if (hasRenderReaders) needsRerender = true;
272
278
  itemSignal.set(item, hasRenderReaders);
273
- if (!hasRenderReaders && !hasPropertyRenderReaders) return false;
274
- rerenderItemInstance(forState, itemInstance, itemInstance.reactiveItem);
279
+ if (needsRerender) rerenderItemInstance(forState, itemInstance, itemInstance.reactiveItem);
275
280
  return true;
276
281
  }
277
282
  const FOR_FALLBACK_SCOPE_KEY = "__for-fallback__";