@llui/dom 0.0.18 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/binding.d.ts.map +1 -1
  2. package/dist/binding.js +5 -0
  3. package/dist/binding.js.map +1 -1
  4. package/dist/devtools.d.ts +109 -0
  5. package/dist/devtools.d.ts.map +1 -1
  6. package/dist/devtools.js +401 -1
  7. package/dist/devtools.js.map +1 -1
  8. package/dist/hmr.d.ts.map +1 -1
  9. package/dist/hmr.js +2 -0
  10. package/dist/hmr.js.map +1 -1
  11. package/dist/index.d.ts +6 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/mount.d.ts.map +1 -1
  15. package/dist/mount.js +18 -2
  16. package/dist/mount.js.map +1 -1
  17. package/dist/primitives/branch.d.ts.map +1 -1
  18. package/dist/primitives/branch.js +10 -1
  19. package/dist/primitives/branch.js.map +1 -1
  20. package/dist/primitives/child.d.ts.map +1 -1
  21. package/dist/primitives/child.js +25 -5
  22. package/dist/primitives/child.js.map +1 -1
  23. package/dist/primitives/each.d.ts.map +1 -1
  24. package/dist/primitives/each.js +79 -6
  25. package/dist/primitives/each.js.map +1 -1
  26. package/dist/primitives/foreign.d.ts.map +1 -1
  27. package/dist/primitives/foreign.js +1 -0
  28. package/dist/primitives/foreign.js.map +1 -1
  29. package/dist/primitives/lazy.d.ts.map +1 -1
  30. package/dist/primitives/lazy.js +1 -0
  31. package/dist/primitives/lazy.js.map +1 -1
  32. package/dist/primitives/portal.d.ts.map +1 -1
  33. package/dist/primitives/portal.js +1 -0
  34. package/dist/primitives/portal.js.map +1 -1
  35. package/dist/primitives/show.d.ts.map +1 -1
  36. package/dist/primitives/show.js +4 -0
  37. package/dist/primitives/show.js.map +1 -1
  38. package/dist/primitives/virtual-each.d.ts.map +1 -1
  39. package/dist/primitives/virtual-each.js +3 -0
  40. package/dist/primitives/virtual-each.js.map +1 -1
  41. package/dist/render-context.d.ts.map +1 -1
  42. package/dist/render-context.js.map +1 -1
  43. package/dist/scope.d.ts.map +1 -1
  44. package/dist/scope.js +68 -1
  45. package/dist/scope.js.map +1 -1
  46. package/dist/ssr.d.ts.map +1 -1
  47. package/dist/ssr.js +5 -1
  48. package/dist/ssr.js.map +1 -1
  49. package/dist/tracking/coverage.d.ts +27 -0
  50. package/dist/tracking/coverage.d.ts.map +1 -0
  51. package/dist/tracking/coverage.js +39 -0
  52. package/dist/tracking/coverage.js.map +1 -0
  53. package/dist/tracking/disposer-log.d.ts +22 -0
  54. package/dist/tracking/disposer-log.d.ts.map +1 -0
  55. package/dist/tracking/disposer-log.js +3 -0
  56. package/dist/tracking/disposer-log.js.map +1 -0
  57. package/dist/tracking/each-diff.d.ts +62 -0
  58. package/dist/tracking/each-diff.d.ts.map +1 -0
  59. package/dist/tracking/each-diff.js +26 -0
  60. package/dist/tracking/each-diff.js.map +1 -0
  61. package/dist/tracking/effect-timeline.d.ts +73 -0
  62. package/dist/tracking/effect-timeline.d.ts.map +1 -0
  63. package/dist/tracking/effect-timeline.js +89 -0
  64. package/dist/tracking/effect-timeline.js.map +1 -0
  65. package/dist/types.d.ts +18 -1
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/types.js.map +1 -1
  68. package/dist/update-loop.d.ts.map +1 -1
  69. package/dist/update-loop.js +78 -0
  70. package/dist/update-loop.js.map +1 -1
  71. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"foreign.js","sourceRoot":"","sources":["../../src/primitives/foreign.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ForeignOptions, Send } from '../types.js'\nimport { getRenderContext } from '../render-context.js'\nimport { createBinding } from '../binding.js'\nimport { createScope, addDisposer } from '../scope.js'\n\nconst FULL_MASK = 0xffffffff\n\nexport function foreign<S, M, T extends Record<string, unknown>, Instance>(\n opts: ForeignOptions<S, M, T, Instance>,\n): Node[] {\n const ctx = getRenderContext('foreign')\n const parentScope = ctx.rootScope\n const foreignScope = createScope(parentScope)\n\n // Create container element\n const tag = opts.container?.tag ?? 'div'\n const container = document.createElement(tag)\n if (opts.container?.attrs) {\n for (const [key, value] of Object.entries(opts.container.attrs)) {\n container.setAttribute(key, value)\n }\n }\n\n // Mount the foreign instance\n const instance = opts.mount({ container, send: ctx.send as Send<M> })\n\n // Evaluate initial props and call sync\n let prevProps: T | undefined = undefined\n const initialProps = opts.props(ctx.state as S)\n\n if (typeof opts.sync === 'function') {\n opts.sync({ instance, props: initialProps, prev: undefined })\n } else {\n for (const key of Object.keys(initialProps) as Array<keyof T>) {\n const handler = opts.sync[key]\n if (handler) {\n handler({ instance, value: initialProps[key], prev: undefined })\n }\n }\n }\n prevProps = initialProps\n\n // Register a binding for the props accessor — fires when state changes\n createBinding(foreignScope, {\n mask: FULL_MASK,\n accessor: ((state: S) => {\n const newProps = opts.props(state)\n // Shallow-diff props\n let changed = false\n if (!prevProps) {\n changed = true\n } else {\n for (const key of Object.keys(newProps) as Array<keyof T>) {\n if (!Object.is(newProps[key], prevProps[key])) {\n changed = true\n break\n }\n }\n }\n\n if (changed) {\n if (typeof opts.sync === 'function') {\n opts.sync({ instance, props: newProps, prev: prevProps })\n } else {\n for (const key of Object.keys(newProps) as Array<keyof T>) {\n if (!prevProps || !Object.is(newProps[key], prevProps[key])) {\n const handler = opts.sync[key]\n if (handler) {\n handler({ instance, value: newProps[key], prev: prevProps?.[key] })\n }\n }\n }\n }\n prevProps = newProps\n }\n\n return newProps\n }) as (state: never) => unknown,\n kind: 'text', // kind doesn't matter — applyBinding won't be called\n node: container,\n perItem: false,\n })\n\n // Destroy on scope disposal\n addDisposer(foreignScope, () => {\n opts.destroy(instance)\n })\n\n return [container]\n}\n"]}
1
+ {"version":3,"file":"foreign.js","sourceRoot":"","sources":["../../src/primitives/foreign.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ForeignOptions, Send } from '../types.js'\nimport { getRenderContext } from '../render-context.js'\nimport { createBinding } from '../binding.js'\nimport { createScope, addDisposer } from '../scope.js'\n\nconst FULL_MASK = 0xffffffff\n\nexport function foreign<S, M, T extends Record<string, unknown>, Instance>(\n opts: ForeignOptions<S, M, T, Instance>,\n): Node[] {\n const ctx = getRenderContext('foreign')\n const parentScope = ctx.rootScope\n const foreignScope = createScope(parentScope)\n foreignScope._kind = 'foreign'\n\n // Create container element\n const tag = opts.container?.tag ?? 'div'\n const container = document.createElement(tag)\n if (opts.container?.attrs) {\n for (const [key, value] of Object.entries(opts.container.attrs)) {\n container.setAttribute(key, value)\n }\n }\n\n // Mount the foreign instance\n const instance = opts.mount({ container, send: ctx.send as Send<M> })\n\n // Evaluate initial props and call sync\n let prevProps: T | undefined = undefined\n const initialProps = opts.props(ctx.state as S)\n\n if (typeof opts.sync === 'function') {\n opts.sync({ instance, props: initialProps, prev: undefined })\n } else {\n for (const key of Object.keys(initialProps) as Array<keyof T>) {\n const handler = opts.sync[key]\n if (handler) {\n handler({ instance, value: initialProps[key], prev: undefined })\n }\n }\n }\n prevProps = initialProps\n\n // Register a binding for the props accessor — fires when state changes\n createBinding(foreignScope, {\n mask: FULL_MASK,\n accessor: ((state: S) => {\n const newProps = opts.props(state)\n // Shallow-diff props\n let changed = false\n if (!prevProps) {\n changed = true\n } else {\n for (const key of Object.keys(newProps) as Array<keyof T>) {\n if (!Object.is(newProps[key], prevProps[key])) {\n changed = true\n break\n }\n }\n }\n\n if (changed) {\n if (typeof opts.sync === 'function') {\n opts.sync({ instance, props: newProps, prev: prevProps })\n } else {\n for (const key of Object.keys(newProps) as Array<keyof T>) {\n if (!prevProps || !Object.is(newProps[key], prevProps[key])) {\n const handler = opts.sync[key]\n if (handler) {\n handler({ instance, value: newProps[key], prev: prevProps?.[key] })\n }\n }\n }\n }\n prevProps = newProps\n }\n\n return newProps\n }) as (state: never) => unknown,\n kind: 'text', // kind doesn't matter — applyBinding won't be called\n node: container,\n perItem: false,\n })\n\n // Destroy on scope disposal\n addDisposer(foreignScope, () => {\n opts.destroy(instance)\n })\n\n return [container]\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../../src/primitives/lazy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,OAAO,EAAE,MAAM,aAAa,CAAA;AAKxD,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAE1D,MAAM,WAAW,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAClC,qFAAqF;IACrF,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACjC,qCAAqC;IACrC,QAAQ,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAA;IACnC,6CAA6C;IAC7C,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAA;IAC7C,uFAAuF;IACvF,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAuF5E"}
1
+ {"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../../src/primitives/lazy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,OAAO,EAAE,MAAM,aAAa,CAAA;AAKxD,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAE1D,MAAM,WAAW,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAClC,qFAAqF;IACrF,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACjC,qCAAqC;IACrC,QAAQ,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAA;IACnC,6CAA6C;IAC7C,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAA;IAC7C,uFAAuF;IACvF,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAwF5E"}
@@ -84,6 +84,7 @@ export function lazy(opts) {
84
84
  setRenderContext({
85
85
  ...childInst,
86
86
  send: childInst.send,
87
+ instance: childInst,
87
88
  });
88
89
  const nodes = def.view(createView(childInst.send));
89
90
  clearRenderContext();
@@ -1 +1 @@
1
- {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../src/primitives/lazy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentDef, LazyDef } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createScope, disposeScope, addDisposer } from '../scope.js'\nimport { createComponentInstance } from '../update-loop.js'\nimport { setFlatBindings } from '../binding.js'\nimport { createView, type View } from '../view-helpers.js'\n\nexport interface LazyOptions<S, M, D> {\n /** Async loader — typically `() => import('./MyComponent').then(m => m.default)`. */\n loader: () => Promise<LazyDef<D>>\n /** Nodes to render while loading. */\n fallback: (h: View<S, M>) => Node[]\n /** Nodes to render if the loader rejects. */\n error?: (err: Error, h: View<S, M>) => Node[]\n /** Props passed as init data to the loaded component. Evaluated once at resolution. */\n data?: (s: S) => D\n}\n\n/**\n * Load a component asynchronously. Renders `fallback` immediately, then swaps\n * in the loaded component when the loader's Promise resolves. If the loader\n * rejects, renders `error` (or nothing if no error handler is provided).\n *\n * ```ts\n * view: ({ text }) => [\n * ...lazy({\n * loader: () => import('./Chart').then(m => m.default),\n * fallback: ({ text }) => [div([text('Loading chart...')])],\n * error: (err, { text }) => [div([text(`Failed: ${err.message}`)])],\n * }),\n * ]\n * ```\n *\n * The loaded component's S, M, E types are internal — `lazy()` only needs\n * the `D` (init data) type to match. `LazyDef<D>` is a type-erased shape\n * that any `ComponentDef<S, M, E, D>` satisfies structurally, avoiding the\n * `View<S, M>` invariance trap that would otherwise require user-side casts.\n *\n * If the parent scope is disposed before the loader resolves, the load is\n * cancelled — the loaded component is never mounted.\n */\nexport function lazy<S, M, D = undefined>(opts: LazyOptions<S, M, D>): Node[] {\n const ctx = getRenderContext('lazy')\n const parentScope = ctx.rootScope\n const send = ctx.send as (msg: M) => void\n\n // Anchor marks the insertion point; fallback nodes live between anchor and endAnchor\n const startAnchor = document.createComment('lazy')\n const endAnchor = document.createComment('/lazy')\n\n // Build fallback inside its own sub-scope (disposed when we swap in loaded component)\n let currentScope = createScope(parentScope)\n setRenderContext({ ...ctx, rootScope: currentScope })\n let currentNodes = opts.fallback(createView<S, M>(send))\n clearRenderContext()\n setRenderContext(ctx)\n\n let cancelled = false\n addDisposer(parentScope, () => {\n cancelled = true\n })\n\n const swap = (buildNew: () => Node[]): void => {\n if (cancelled) return\n const parent = startAnchor.parentNode\n if (!parent) return\n\n // Dispose old sub-scope (removes fallback bindings/listeners)\n if (currentScope) disposeScope(currentScope)\n for (const node of currentNodes) {\n if (node.parentNode === parent) parent.removeChild(node)\n }\n\n // Build new nodes in a fresh sub-scope\n currentScope = createScope(parentScope)\n setRenderContext({ ...ctx, rootScope: currentScope })\n currentNodes = buildNew()\n clearRenderContext()\n setRenderContext(ctx)\n\n // Insert after startAnchor (before endAnchor)\n for (const node of currentNodes) {\n parent.insertBefore(node, endAnchor)\n }\n }\n\n // Kick off the loader\n opts\n .loader()\n .then((def) => {\n if (cancelled) return\n swap(() => {\n // Mount loaded component as a nested instance (similar to child()).\n // Cast LazyDef back to ComponentDef — safe because the loader\n // returned a real ComponentDef; LazyDef only erased the types.\n const initialProps = opts.data ? opts.data(ctx.state as S) : undefined\n const childInst = createComponentInstance(\n def as unknown as ComponentDef<unknown, unknown, unknown>,\n initialProps,\n )\n\n // Render the loaded component's view inside its own render context\n setFlatBindings(childInst.allBindings)\n setRenderContext({\n ...childInst,\n send: childInst.send as (msg: unknown) => void,\n })\n const nodes = (def as { view: (h: unknown) => Node[] }).view(createView(childInst.send))\n clearRenderContext()\n setFlatBindings(ctx.allBindings)\n setRenderContext(ctx)\n\n // Dispose the loaded instance when our current sub-scope disposes\n addDisposer(currentScope, () => {\n disposeScope(childInst.rootScope)\n })\n\n return nodes\n })\n })\n .catch((err: unknown) => {\n if (cancelled) return\n if (!opts.error) return\n const e = err instanceof Error ? err : new Error(String(err))\n swap(() => opts.error!(e, createView<S, M>(send)))\n })\n\n return [startAnchor, ...currentNodes, endAnchor]\n}\n"]}
1
+ {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../src/primitives/lazy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentDef, LazyDef } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createScope, disposeScope, addDisposer } from '../scope.js'\nimport { createComponentInstance, type ComponentInstance } from '../update-loop.js'\nimport { setFlatBindings } from '../binding.js'\nimport { createView, type View } from '../view-helpers.js'\n\nexport interface LazyOptions<S, M, D> {\n /** Async loader — typically `() => import('./MyComponent').then(m => m.default)`. */\n loader: () => Promise<LazyDef<D>>\n /** Nodes to render while loading. */\n fallback: (h: View<S, M>) => Node[]\n /** Nodes to render if the loader rejects. */\n error?: (err: Error, h: View<S, M>) => Node[]\n /** Props passed as init data to the loaded component. Evaluated once at resolution. */\n data?: (s: S) => D\n}\n\n/**\n * Load a component asynchronously. Renders `fallback` immediately, then swaps\n * in the loaded component when the loader's Promise resolves. If the loader\n * rejects, renders `error` (or nothing if no error handler is provided).\n *\n * ```ts\n * view: ({ text }) => [\n * ...lazy({\n * loader: () => import('./Chart').then(m => m.default),\n * fallback: ({ text }) => [div([text('Loading chart...')])],\n * error: (err, { text }) => [div([text(`Failed: ${err.message}`)])],\n * }),\n * ]\n * ```\n *\n * The loaded component's S, M, E types are internal — `lazy()` only needs\n * the `D` (init data) type to match. `LazyDef<D>` is a type-erased shape\n * that any `ComponentDef<S, M, E, D>` satisfies structurally, avoiding the\n * `View<S, M>` invariance trap that would otherwise require user-side casts.\n *\n * If the parent scope is disposed before the loader resolves, the load is\n * cancelled — the loaded component is never mounted.\n */\nexport function lazy<S, M, D = undefined>(opts: LazyOptions<S, M, D>): Node[] {\n const ctx = getRenderContext('lazy')\n const parentScope = ctx.rootScope\n const send = ctx.send as (msg: M) => void\n\n // Anchor marks the insertion point; fallback nodes live between anchor and endAnchor\n const startAnchor = document.createComment('lazy')\n const endAnchor = document.createComment('/lazy')\n\n // Build fallback inside its own sub-scope (disposed when we swap in loaded component)\n let currentScope = createScope(parentScope)\n setRenderContext({ ...ctx, rootScope: currentScope })\n let currentNodes = opts.fallback(createView<S, M>(send))\n clearRenderContext()\n setRenderContext(ctx)\n\n let cancelled = false\n addDisposer(parentScope, () => {\n cancelled = true\n })\n\n const swap = (buildNew: () => Node[]): void => {\n if (cancelled) return\n const parent = startAnchor.parentNode\n if (!parent) return\n\n // Dispose old sub-scope (removes fallback bindings/listeners)\n if (currentScope) disposeScope(currentScope)\n for (const node of currentNodes) {\n if (node.parentNode === parent) parent.removeChild(node)\n }\n\n // Build new nodes in a fresh sub-scope\n currentScope = createScope(parentScope)\n setRenderContext({ ...ctx, rootScope: currentScope })\n currentNodes = buildNew()\n clearRenderContext()\n setRenderContext(ctx)\n\n // Insert after startAnchor (before endAnchor)\n for (const node of currentNodes) {\n parent.insertBefore(node, endAnchor)\n }\n }\n\n // Kick off the loader\n opts\n .loader()\n .then((def) => {\n if (cancelled) return\n swap(() => {\n // Mount loaded component as a nested instance (similar to child()).\n // Cast LazyDef back to ComponentDef — safe because the loader\n // returned a real ComponentDef; LazyDef only erased the types.\n const initialProps = opts.data ? opts.data(ctx.state as S) : undefined\n const childInst = createComponentInstance(\n def as unknown as ComponentDef<unknown, unknown, unknown>,\n initialProps,\n )\n\n // Render the loaded component's view inside its own render context\n setFlatBindings(childInst.allBindings)\n setRenderContext({\n ...childInst,\n send: childInst.send as (msg: unknown) => void,\n instance: childInst as ComponentInstance,\n })\n const nodes = (def as { view: (h: unknown) => Node[] }).view(createView(childInst.send))\n clearRenderContext()\n setFlatBindings(ctx.allBindings)\n setRenderContext(ctx)\n\n // Dispose the loaded instance when our current sub-scope disposes\n addDisposer(currentScope, () => {\n disposeScope(childInst.rootScope)\n })\n\n return nodes\n })\n })\n .catch((err: unknown) => {\n if (cancelled) return\n if (!opts.error) return\n const e = err instanceof Error ? err : new Error(String(err))\n swap(() => opts.error!(e, createView<S, M>(send)))\n })\n\n return [startAnchor, ...currentNodes, endAnchor]\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../src/primitives/portal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIhD,wBAAgB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,EAAE,CAiClD"}
1
+ {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../src/primitives/portal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIhD,wBAAgB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,EAAE,CAkClD"}
@@ -8,6 +8,7 @@ export function portal(opts) {
8
8
  return [];
9
9
  }
10
10
  const portalScope = createScope(parentScope);
11
+ portalScope._kind = 'portal';
11
12
  const buildCtx = { ...ctx, rootScope: portalScope, container: target };
12
13
  setRenderContext(buildCtx);
13
14
  const nodes = opts.render();
@@ -1 +1 @@
1
- {"version":3,"file":"portal.js","sourceRoot":"","sources":["../../src/primitives/portal.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC7F,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEtD,MAAM,UAAU,MAAM,CAAC,IAAmB;IACxC,MAAM,GAAG,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IACtC,MAAM,WAAW,GAAG,GAAG,CAAC,SAAS,CAAA;IAEjC,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAA;IAElG,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,WAAW,GAAG,WAAW,CAAC,WAAW,CAAC,CAAA;IAC5C,MAAM,QAAQ,GAAG,EAAE,GAAG,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAiB,EAAE,CAAA;IACjF,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;IAC3B,kBAAkB,EAAE,CAAA;IACpB,yBAAyB;IACzB,gBAAgB,CAAC,GAAG,CAAC,CAAA;IAErB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IAED,qDAAqD;IACrD,WAAW,CAAC,WAAW,EAAE,GAAG,EAAE;QAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,UAAU,KAAK,MAAM,EAAE,CAAC;gBAC/B,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;YAC1B,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,sEAAsE;IACtE,OAAO,EAAE,CAAA;AACX,CAAC","sourcesContent":["import type { PortalOptions } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createScope, addDisposer } from '../scope.js'\n\nexport function portal(opts: PortalOptions): Node[] {\n const ctx = getRenderContext('portal')\n const parentScope = ctx.rootScope\n\n const target = typeof opts.target === 'string' ? document.querySelector(opts.target) : opts.target\n\n if (!target) {\n return []\n }\n\n const portalScope = createScope(parentScope)\n const buildCtx = { ...ctx, rootScope: portalScope, container: target as Element }\n setRenderContext(buildCtx)\n const nodes = opts.render()\n clearRenderContext()\n // Restore parent context\n setRenderContext(ctx)\n\n for (const node of nodes) {\n target.appendChild(node)\n }\n\n // On scope disposal, remove portal nodes from target\n addDisposer(portalScope, () => {\n for (const node of nodes) {\n if (node.parentNode === target) {\n target.removeChild(node)\n }\n }\n })\n\n // Portal returns nothing to the parent DOM — nodes live in the target\n return []\n}\n"]}
1
+ {"version":3,"file":"portal.js","sourceRoot":"","sources":["../../src/primitives/portal.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC7F,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEtD,MAAM,UAAU,MAAM,CAAC,IAAmB;IACxC,MAAM,GAAG,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IACtC,MAAM,WAAW,GAAG,GAAG,CAAC,SAAS,CAAA;IAEjC,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAA;IAElG,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,WAAW,GAAG,WAAW,CAAC,WAAW,CAAC,CAAA;IAC5C,WAAW,CAAC,KAAK,GAAG,QAAQ,CAAA;IAC5B,MAAM,QAAQ,GAAG,EAAE,GAAG,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAiB,EAAE,CAAA;IACjF,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;IAC3B,kBAAkB,EAAE,CAAA;IACpB,yBAAyB;IACzB,gBAAgB,CAAC,GAAG,CAAC,CAAA;IAErB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IAED,qDAAqD;IACrD,WAAW,CAAC,WAAW,EAAE,GAAG,EAAE;QAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,UAAU,KAAK,MAAM,EAAE,CAAC;gBAC/B,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;YAC1B,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,sEAAsE;IACtE,OAAO,EAAE,CAAA;AACX,CAAC","sourcesContent":["import type { PortalOptions } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createScope, addDisposer } from '../scope.js'\n\nexport function portal(opts: PortalOptions): Node[] {\n const ctx = getRenderContext('portal')\n const parentScope = ctx.rootScope\n\n const target = typeof opts.target === 'string' ? document.querySelector(opts.target) : opts.target\n\n if (!target) {\n return []\n }\n\n const portalScope = createScope(parentScope)\n portalScope._kind = 'portal'\n const buildCtx = { ...ctx, rootScope: portalScope, container: target as Element }\n setRenderContext(buildCtx)\n const nodes = opts.render()\n clearRenderContext()\n // Restore parent context\n setRenderContext(ctx)\n\n for (const node of nodes) {\n target.appendChild(node)\n }\n\n // On scope disposal, remove portal nodes from target\n addDisposer(portalScope, () => {\n for (const node of nodes) {\n if (node.parentNode === target) {\n target.removeChild(node)\n }\n }\n })\n\n // Portal returns nothing to the parent DOM — nodes live in the target\n return []\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../src/primitives/show.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAK9C,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAQpE"}
1
+ {"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../src/primitives/show.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAK9C,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAYpE"}
@@ -1,12 +1,16 @@
1
1
  import { branch } from './branch.js';
2
2
  const EMPTY = () => [];
3
3
  export function show(opts) {
4
+ // `__disposalCause` is read by branch.ts when it disposes the leaving
5
+ // arm — it lets the disposer log distinguish show/hide transitions from
6
+ // multi-case branch swaps.
4
7
  return branch({
5
8
  on: opts.when,
6
9
  cases: { true: opts.render, false: opts.fallback ?? EMPTY },
7
10
  enter: opts.enter,
8
11
  leave: opts.leave,
9
12
  onTransition: opts.onTransition,
13
+ __disposalCause: 'show-hide',
10
14
  });
11
15
  }
12
16
  //# sourceMappingURL=show.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/primitives/show.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,EAAY,CAAA;AAEhC,MAAM,UAAU,IAAI,CAAiB,IAAuB;IAC1D,OAAO,MAAM,CAAO;QAClB,EAAE,EAAE,IAAI,CAAC,IAAI;QACb,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK,EAAE;QAC3D,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,YAAY,EAAE,IAAI,CAAC,YAAY;KAChC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { ShowOptions } from '../types.js'\nimport { branch } from './branch.js'\n\nconst EMPTY = () => [] as Node[]\n\nexport function show<S, M = unknown>(opts: ShowOptions<S, M>): Node[] {\n return branch<S, M>({\n on: opts.when,\n cases: { true: opts.render, false: opts.fallback ?? EMPTY },\n enter: opts.enter,\n leave: opts.leave,\n onTransition: opts.onTransition,\n })\n}\n"]}
1
+ {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/primitives/show.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,EAAY,CAAA;AAEhC,MAAM,UAAU,IAAI,CAAiB,IAAuB;IAC1D,sEAAsE;IACtE,wEAAwE;IACxE,2BAA2B;IAC3B,OAAO,MAAM,CAAO;QAClB,EAAE,EAAE,IAAI,CAAC,IAAI;QACb,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK,EAAE;QAC3D,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,eAAe,EAAE,WAAW;KAC7B,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { ShowOptions } from '../types.js'\nimport { branch } from './branch.js'\n\nconst EMPTY = () => [] as Node[]\n\nexport function show<S, M = unknown>(opts: ShowOptions<S, M>): Node[] {\n // `__disposalCause` is read by branch.ts when it disposes the leaving\n // arm — it lets the disposer log distinguish show/hide transitions from\n // multi-case branch swaps.\n return branch<S, M>({\n on: opts.when,\n cases: { true: opts.render, false: opts.fallback ?? EMPTY },\n enter: opts.enter,\n leave: opts.leave,\n onTransition: opts.onTransition,\n __disposalCause: 'show-hide',\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-each.d.ts","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAS,IAAI,EAAE,MAAM,aAAa,CAAA;AAY5D,MAAM,WAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO;IACnD,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAA;IACpB,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAA;IACjC,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,eAAe,EAAE,MAAM,CAAA;IACvB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,CAAC,IAAI,EAAE;QACb,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QACb,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;QACrB,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAA;QAC1C,KAAK,EAAE,MAAM,MAAM,CAAA;KACpB,KAAK,IAAI,EAAE,CAAA;CACb;AAiBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CA+LxF"}
1
+ {"version":3,"file":"virtual-each.d.ts","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAS,IAAI,EAAE,MAAM,aAAa,CAAA;AAY5D,MAAM,WAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO;IACnD,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAA;IACpB,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAA;IACjC,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,eAAe,EAAE,MAAM,CAAA;IACvB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,CAAC,IAAI,EAAE;QACb,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QACb,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;QACrB,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAA;QAC1C,KAAK,EAAE,MAAM,MAAM,CAAA;KACpB,KAAK,IAAI,EAAE,CAAA;CACb;AAiBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAkMxF"}
@@ -107,6 +107,7 @@ export function virtualEach(opts) {
107
107
  buildCtx.state = state;
108
108
  buildCtx.allBindings = ctx.allBindings;
109
109
  buildCtx.structuralBlocks = ctx.structuralBlocks;
110
+ buildCtx.instance = ctx.instance;
110
111
  const prevFlat = getFlatBindings();
111
112
  setFlatBindings(ctx.allBindings);
112
113
  setRenderContext(buildCtx);
@@ -131,6 +132,7 @@ export function virtualEach(opts) {
131
132
  if (items.length === 0) {
132
133
  // Dispose all entries
133
134
  for (const entry of entries.values()) {
135
+ entry.scope.disposalCause = 'each-remove';
134
136
  disposeScope(entry.scope);
135
137
  if (entry.wrapper.parentNode)
136
138
  entry.wrapper.parentNode.removeChild(entry.wrapper);
@@ -148,6 +150,7 @@ export function virtualEach(opts) {
148
150
  // Dispose entries no longer visible
149
151
  for (const [key, entry] of entries) {
150
152
  if (!visibleKeys.has(key)) {
153
+ entry.scope.disposalCause = 'each-remove';
151
154
  disposeScope(entry.scope);
152
155
  if (entry.wrapper.parentNode)
153
156
  entry.wrapper.parentNode.removeChild(entry.wrapper);
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-each.js","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ItemAccessor, Scope, Send } from '../types.js'\nimport {\n getRenderContext,\n setRenderContext,\n clearRenderContext,\n type RenderContext,\n} from '../render-context.js'\nimport { createScope, disposeScope, addDisposer } from '../scope.js'\nimport { getFlatBindings, setFlatBindings } from '../binding.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport interface VirtualEachOptions<S, T, M = unknown> {\n items: (s: S) => T[]\n key: (item: T) => string | number\n /** Fixed pixel height per item. Required — dynamic heights are not supported yet. */\n itemHeight: number\n /** Scrollable container height in pixels. */\n containerHeight: number\n /** Extra rows to render above/below the viewport for smooth scrolling. Default: 3. */\n overscan?: number\n /** Optional class for the scroll container. */\n class?: string\n render: (opts: {\n send: Send<M>\n item: ItemAccessor<T>\n acc: <R>(selector: (t: T) => R) => () => R\n index: () => number\n }) => Node[]\n}\n\ninterface VirtualEntry<T> {\n key: string | number\n current: T\n index: number\n scope: Scope\n wrapper: HTMLElement\n}\n\nconst buildCtx: RenderContext = {\n rootScope: null as unknown as Scope,\n state: null,\n allBindings: [],\n structuralBlocks: [],\n}\n\n/**\n * Virtualized list — renders only the rows visible in the scroll viewport.\n * Use for lists with 1k+ items where a regular `each()` would be too slow.\n *\n * Current limitations:\n * - Fixed row height (`itemHeight`) — dynamic heights not supported\n * - No transitions / animations\n * - No cross-container reuse (items outside view are fully disposed)\n *\n * ```ts\n * view: ({ text }) => [\n * ...virtualEach({\n * items: (s) => s.rows,\n * key: (r) => r.id,\n * itemHeight: 40,\n * containerHeight: 600,\n * render: ({ item }) => [\n * div({ class: 'row' }, [text(item.label)]),\n * ],\n * }),\n * ]\n * ```\n */\nexport function virtualEach<S, T, M = unknown>(opts: VirtualEachOptions<S, T, M>): Node[] {\n const ctx = getRenderContext('virtualEach')\n const parentScope = ctx.rootScope\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const overscan = opts.overscan ?? 3\n\n // Scroll container\n const scroll = document.createElement('div')\n scroll.style.overflow = 'auto'\n scroll.style.position = 'relative'\n scroll.style.height = `${opts.containerHeight}px`\n scroll.dataset.virtualContainer = ''\n if (opts.class) scroll.className = opts.class\n\n // Inner spacer sized to full content height\n const spacer = document.createElement('div')\n spacer.style.position = 'relative'\n spacer.style.width = '100%'\n spacer.dataset.virtualSpacer = ''\n scroll.appendChild(spacer)\n\n // Map of key → entry\n const entries = new Map<string | number, VirtualEntry<T>>()\n let lastItems: T[] = []\n let scrollTop = 0\n\n const computeRange = (length: number): [number, number] => {\n if (length === 0) return [0, 0]\n const start = Math.max(0, Math.floor(scrollTop / opts.itemHeight) - overscan)\n const end = Math.min(\n length,\n Math.ceil((scrollTop + opts.containerHeight) / opts.itemHeight) + overscan,\n )\n return [start, end]\n }\n\n const buildEntry = (item: T, index: number, state: S): VirtualEntry<T> => {\n const key = opts.key(item)\n const scope = createScope(parentScope)\n\n const wrapper = document.createElement('div')\n wrapper.style.position = 'absolute'\n wrapper.style.top = `${index * opts.itemHeight}px`\n wrapper.style.left = '0'\n wrapper.style.right = '0'\n wrapper.style.height = `${opts.itemHeight}px`\n wrapper.dataset.virtualItem = ''\n wrapper.dataset.virtualKey = String(key)\n\n const entry: VirtualEntry<T> = { key, current: item, index, scope, wrapper }\n\n // Item accessor: item(selector) and item.field\n const itemFn = <R>(selector: (t: T) => R): (() => R) => {\n const accessor = (): R => selector(entry.current)\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n return accessor\n }\n\n let itemProxy: ItemAccessor<T> | null = null\n const getItemProxy = (): ItemAccessor<T> => {\n if (itemProxy) return itemProxy\n const fieldCache = new Map<string, () => unknown>()\n itemProxy = new Proxy(itemFn as object, {\n get(target, prop) {\n if (typeof prop === 'symbol' || prop === 'then' || prop === 'prototype') {\n return Reflect.get(target, prop)\n }\n const k = prop as string\n const cached = fieldCache.get(k)\n if (cached) return cached\n const accessor = (): unknown => (entry.current as Record<string, unknown>)[k]\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n fieldCache.set(k, accessor)\n return accessor\n },\n }) as ItemAccessor<T>\n return itemProxy\n }\n\n const indexAccessor = (): number => entry.index\n\n buildCtx.rootScope = scope\n buildCtx.state = state\n buildCtx.allBindings = ctx.allBindings\n buildCtx.structuralBlocks = ctx.structuralBlocks\n const prevFlat = getFlatBindings()\n setFlatBindings(ctx.allBindings)\n setRenderContext(buildCtx)\n\n const nodes = opts.render({\n send,\n item: getItemProxy(),\n acc: itemFn,\n index: indexAccessor,\n })\n\n clearRenderContext()\n setFlatBindings(prevFlat)\n setRenderContext(ctx)\n\n for (const node of nodes) wrapper.appendChild(node)\n return entry\n }\n\n const reconcile = (state: S): void => {\n const items = opts.items(state)\n lastItems = items\n\n // Update spacer total height\n spacer.style.height = `${items.length * opts.itemHeight}px`\n\n if (items.length === 0) {\n // Dispose all entries\n for (const entry of entries.values()) {\n disposeScope(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n }\n entries.clear()\n return\n }\n\n const [start, end] = computeRange(items.length)\n\n // Build a map of key → {item, index} for the visible range\n const visibleKeys = new Map<string | number, { item: T; index: number }>()\n for (let i = start; i < end; i++) {\n const item = items[i]!\n visibleKeys.set(opts.key(item), { item, index: i })\n }\n\n // Dispose entries no longer visible\n for (const [key, entry] of entries) {\n if (!visibleKeys.has(key)) {\n disposeScope(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n entries.delete(key)\n }\n }\n\n // Create new entries + update existing\n for (const [key, { item, index }] of visibleKeys) {\n const existing = entries.get(key)\n if (existing) {\n existing.current = item\n if (existing.index !== index) {\n existing.index = index\n existing.wrapper.style.top = `${index * opts.itemHeight}px`\n }\n } else {\n const entry = buildEntry(item, index, state)\n entries.set(key, entry)\n spacer.appendChild(entry.wrapper)\n }\n }\n }\n\n // Scroll handler — reconcile visible range without touching component state\n const onScroll = (): void => {\n scrollTop = scroll.scrollTop\n reconcile(ctx.state as S)\n }\n scroll.addEventListener('scroll', onScroll, { passive: true })\n\n // Register as a structural block BEFORE initial render so this block\n // precedes any nested blocks its rows register. See branch.ts for the\n // full rationale (Phase 1 iteration safety).\n const block: StructuralBlock = {\n mask: FULL_MASK,\n reconcile(state: unknown) {\n const newItems = opts.items(state as S)\n if (newItems === lastItems) return\n reconcile(state as S)\n },\n }\n blocks.push(block)\n\n // Initial render\n reconcile(ctx.state as S)\n\n // Cleanup on parent disposal\n addDisposer(parentScope, () => {\n scroll.removeEventListener('scroll', onScroll)\n for (const entry of entries.values()) {\n disposeScope(entry.scope)\n }\n entries.clear()\n })\n\n return [scroll]\n}\n"]}
1
+ {"version":3,"file":"virtual-each.js","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ItemAccessor, Scope, Send } from '../types.js'\nimport {\n getRenderContext,\n setRenderContext,\n clearRenderContext,\n type RenderContext,\n} from '../render-context.js'\nimport { createScope, disposeScope, addDisposer } from '../scope.js'\nimport { getFlatBindings, setFlatBindings } from '../binding.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport interface VirtualEachOptions<S, T, M = unknown> {\n items: (s: S) => T[]\n key: (item: T) => string | number\n /** Fixed pixel height per item. Required — dynamic heights are not supported yet. */\n itemHeight: number\n /** Scrollable container height in pixels. */\n containerHeight: number\n /** Extra rows to render above/below the viewport for smooth scrolling. Default: 3. */\n overscan?: number\n /** Optional class for the scroll container. */\n class?: string\n render: (opts: {\n send: Send<M>\n item: ItemAccessor<T>\n acc: <R>(selector: (t: T) => R) => () => R\n index: () => number\n }) => Node[]\n}\n\ninterface VirtualEntry<T> {\n key: string | number\n current: T\n index: number\n scope: Scope\n wrapper: HTMLElement\n}\n\nconst buildCtx: RenderContext = {\n rootScope: null as unknown as Scope,\n state: null,\n allBindings: [],\n structuralBlocks: [],\n}\n\n/**\n * Virtualized list — renders only the rows visible in the scroll viewport.\n * Use for lists with 1k+ items where a regular `each()` would be too slow.\n *\n * Current limitations:\n * - Fixed row height (`itemHeight`) — dynamic heights not supported\n * - No transitions / animations\n * - No cross-container reuse (items outside view are fully disposed)\n *\n * ```ts\n * view: ({ text }) => [\n * ...virtualEach({\n * items: (s) => s.rows,\n * key: (r) => r.id,\n * itemHeight: 40,\n * containerHeight: 600,\n * render: ({ item }) => [\n * div({ class: 'row' }, [text(item.label)]),\n * ],\n * }),\n * ]\n * ```\n */\nexport function virtualEach<S, T, M = unknown>(opts: VirtualEachOptions<S, T, M>): Node[] {\n const ctx = getRenderContext('virtualEach')\n const parentScope = ctx.rootScope\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const overscan = opts.overscan ?? 3\n\n // Scroll container\n const scroll = document.createElement('div')\n scroll.style.overflow = 'auto'\n scroll.style.position = 'relative'\n scroll.style.height = `${opts.containerHeight}px`\n scroll.dataset.virtualContainer = ''\n if (opts.class) scroll.className = opts.class\n\n // Inner spacer sized to full content height\n const spacer = document.createElement('div')\n spacer.style.position = 'relative'\n spacer.style.width = '100%'\n spacer.dataset.virtualSpacer = ''\n scroll.appendChild(spacer)\n\n // Map of key → entry\n const entries = new Map<string | number, VirtualEntry<T>>()\n let lastItems: T[] = []\n let scrollTop = 0\n\n const computeRange = (length: number): [number, number] => {\n if (length === 0) return [0, 0]\n const start = Math.max(0, Math.floor(scrollTop / opts.itemHeight) - overscan)\n const end = Math.min(\n length,\n Math.ceil((scrollTop + opts.containerHeight) / opts.itemHeight) + overscan,\n )\n return [start, end]\n }\n\n const buildEntry = (item: T, index: number, state: S): VirtualEntry<T> => {\n const key = opts.key(item)\n const scope = createScope(parentScope)\n\n const wrapper = document.createElement('div')\n wrapper.style.position = 'absolute'\n wrapper.style.top = `${index * opts.itemHeight}px`\n wrapper.style.left = '0'\n wrapper.style.right = '0'\n wrapper.style.height = `${opts.itemHeight}px`\n wrapper.dataset.virtualItem = ''\n wrapper.dataset.virtualKey = String(key)\n\n const entry: VirtualEntry<T> = { key, current: item, index, scope, wrapper }\n\n // Item accessor: item(selector) and item.field\n const itemFn = <R>(selector: (t: T) => R): (() => R) => {\n const accessor = (): R => selector(entry.current)\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n return accessor\n }\n\n let itemProxy: ItemAccessor<T> | null = null\n const getItemProxy = (): ItemAccessor<T> => {\n if (itemProxy) return itemProxy\n const fieldCache = new Map<string, () => unknown>()\n itemProxy = new Proxy(itemFn as object, {\n get(target, prop) {\n if (typeof prop === 'symbol' || prop === 'then' || prop === 'prototype') {\n return Reflect.get(target, prop)\n }\n const k = prop as string\n const cached = fieldCache.get(k)\n if (cached) return cached\n const accessor = (): unknown => (entry.current as Record<string, unknown>)[k]\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n fieldCache.set(k, accessor)\n return accessor\n },\n }) as ItemAccessor<T>\n return itemProxy\n }\n\n const indexAccessor = (): number => entry.index\n\n buildCtx.rootScope = scope\n buildCtx.state = state\n buildCtx.allBindings = ctx.allBindings\n buildCtx.structuralBlocks = ctx.structuralBlocks\n buildCtx.instance = ctx.instance\n const prevFlat = getFlatBindings()\n setFlatBindings(ctx.allBindings)\n setRenderContext(buildCtx)\n\n const nodes = opts.render({\n send,\n item: getItemProxy(),\n acc: itemFn,\n index: indexAccessor,\n })\n\n clearRenderContext()\n setFlatBindings(prevFlat)\n setRenderContext(ctx)\n\n for (const node of nodes) wrapper.appendChild(node)\n return entry\n }\n\n const reconcile = (state: S): void => {\n const items = opts.items(state)\n lastItems = items\n\n // Update spacer total height\n spacer.style.height = `${items.length * opts.itemHeight}px`\n\n if (items.length === 0) {\n // Dispose all entries\n for (const entry of entries.values()) {\n entry.scope.disposalCause = 'each-remove'\n disposeScope(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n }\n entries.clear()\n return\n }\n\n const [start, end] = computeRange(items.length)\n\n // Build a map of key → {item, index} for the visible range\n const visibleKeys = new Map<string | number, { item: T; index: number }>()\n for (let i = start; i < end; i++) {\n const item = items[i]!\n visibleKeys.set(opts.key(item), { item, index: i })\n }\n\n // Dispose entries no longer visible\n for (const [key, entry] of entries) {\n if (!visibleKeys.has(key)) {\n entry.scope.disposalCause = 'each-remove'\n disposeScope(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n entries.delete(key)\n }\n }\n\n // Create new entries + update existing\n for (const [key, { item, index }] of visibleKeys) {\n const existing = entries.get(key)\n if (existing) {\n existing.current = item\n if (existing.index !== index) {\n existing.index = index\n existing.wrapper.style.top = `${index * opts.itemHeight}px`\n }\n } else {\n const entry = buildEntry(item, index, state)\n entries.set(key, entry)\n spacer.appendChild(entry.wrapper)\n }\n }\n }\n\n // Scroll handler — reconcile visible range without touching component state\n const onScroll = (): void => {\n scrollTop = scroll.scrollTop\n reconcile(ctx.state as S)\n }\n scroll.addEventListener('scroll', onScroll, { passive: true })\n\n // Register as a structural block BEFORE initial render so this block\n // precedes any nested blocks its rows register. See branch.ts for the\n // full rationale (Phase 1 iteration safety).\n const block: StructuralBlock = {\n mask: FULL_MASK,\n reconcile(state: unknown) {\n const newItems = opts.items(state as S)\n if (newItems === lastItems) return\n reconcile(state as S)\n },\n }\n blocks.push(block)\n\n // Initial render\n reconcile(ctx.state as S)\n\n // Cleanup on parent disposal\n addDisposer(parentScope, () => {\n scroll.removeEventListener('scroll', onScroll)\n for (const entry of entries.values()) {\n disposeScope(entry.scope)\n }\n entries.clear()\n })\n\n return [scroll]\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,KAAK,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,OAAO,EAAE,CAAA;IACtB,gBAAgB,EAAE,eAAe,EAAE,CAAA;IACnC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAC9B;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAEzD;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,wBAAgB,gBAAgB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CAgBtE"}
1
+ {"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGtD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,KAAK,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,OAAO,EAAE,CAAA;IACtB,gBAAgB,EAAE,eAAe,EAAE,CAAA;IACnC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAM9B;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAEzD;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,wBAAgB,gBAAgB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CAgBtE"}
@@ -1 +1 @@
1
- {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAYA,IAAI,cAAc,GAAyB,IAAI,CAAA;AAE/C,MAAM,UAAU,gBAAgB,CAAC,GAAkB;IACjD,cAAc,GAAG,GAAG,CAAA;AACtB,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,cAAc,GAAG,IAAI,CAAA;AACvB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,aAAsB;IACrD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,aAAa,IAAI,CAAC,CAAC,CAAC,YAAY,CAAA;QAChE,MAAM,IAAI,KAAK,CACb,UAAU,IAAI,4DAA4D;YACxE,0DAA0D;YAC1D,sEAAsE;YACtE,yEAAyE;YACzE,0EAA0E;YAC1E,0EAA0E;YAC1E,gCAAgC;YAChC,yEAAyE;YACzE,sEAAsE,CACzE,CAAA;IACH,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC","sourcesContent":["import type { Scope, Binding } from './types.js'\nimport type { StructuralBlock } from './structural.js'\n\nexport interface RenderContext {\n rootScope: Scope\n state: unknown\n allBindings: Binding[]\n structuralBlocks: StructuralBlock[]\n container?: Element\n send?: (msg: unknown) => void\n}\n\nlet currentContext: RenderContext | null = null\n\nexport function setRenderContext(ctx: RenderContext): void {\n currentContext = ctx\n}\n\nexport function clearRenderContext(): void {\n currentContext = null\n}\n\nexport function getRenderContext(primitiveName?: string): RenderContext {\n if (!currentContext) {\n const name = primitiveName ? `${primitiveName}()` : 'primitives'\n throw new Error(\n `[LLui] ${name} can only be called inside a component's view() function. ` +\n `It was called outside a render context. Common causes:\\n` +\n ` 1. Calling a primitive at module scope instead of inside view().\\n` +\n ` 2. Calling an overlay helper (dialog.overlay, popover.overlay, …) at ` +\n `module scope — these internally use show()/branch() and must be invoked ` +\n `from inside the component's view callback so their result can be spread ` +\n `into the returned node tree.\\n` +\n ` 3. Calling a primitive from a setTimeout / Promise / event handler — ` +\n `the render context only persists during the synchronous view() call.`,\n )\n }\n return currentContext\n}\n"]}
1
+ {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAkBA,IAAI,cAAc,GAAyB,IAAI,CAAA;AAE/C,MAAM,UAAU,gBAAgB,CAAC,GAAkB;IACjD,cAAc,GAAG,GAAG,CAAA;AACtB,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,cAAc,GAAG,IAAI,CAAA;AACvB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,aAAsB;IACrD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,aAAa,IAAI,CAAC,CAAC,CAAC,YAAY,CAAA;QAChE,MAAM,IAAI,KAAK,CACb,UAAU,IAAI,4DAA4D;YACxE,0DAA0D;YAC1D,sEAAsE;YACtE,yEAAyE;YACzE,0EAA0E;YAC1E,0EAA0E;YAC1E,gCAAgC;YAChC,yEAAyE;YACzE,sEAAsE,CACzE,CAAA;IACH,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC","sourcesContent":["import type { Scope, Binding } from './types.js'\nimport type { StructuralBlock } from './structural.js'\nimport type { ComponentInstance } from './update-loop.js'\n\nexport interface RenderContext {\n rootScope: Scope\n state: unknown\n allBindings: Binding[]\n structuralBlocks: StructuralBlock[]\n container?: Element\n send?: (msg: unknown) => void\n /** @internal dev-only — the owning ComponentInstance. Set by mount /\n * hydrate / child to let primitives (currently `each`) emit tracker\n * data to `inst._eachDiffLog`. Nested contexts pass through via\n * spread (e.g. `{ ...ctx, rootScope }`). Undefined outside dev. */\n instance?: ComponentInstance\n}\n\nlet currentContext: RenderContext | null = null\n\nexport function setRenderContext(ctx: RenderContext): void {\n currentContext = ctx\n}\n\nexport function clearRenderContext(): void {\n currentContext = null\n}\n\nexport function getRenderContext(primitiveName?: string): RenderContext {\n if (!currentContext) {\n const name = primitiveName ? `${primitiveName}()` : 'primitives'\n throw new Error(\n `[LLui] ${name} can only be called inside a component's view() function. ` +\n `It was called outside a render context. Common causes:\\n` +\n ` 1. Calling a primitive at module scope instead of inside view().\\n` +\n ` 2. Calling an overlay helper (dialog.overlay, popover.overlay, …) at ` +\n `module scope — these internally use show()/branch() and must be invoked ` +\n `from inside the component's view callback so their result can be spread ` +\n `into the returned node tree.\\n` +\n ` 3. Calling a primitive from a setTimeout / Promise / event handler — ` +\n `the render context only persists during the synchronous view() call.`,\n )\n }\n return currentContext\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAoBhD,wBAAgB,WAAW,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI,GAAG,KAAK,CAwBvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,iBAAiB,UAAQ,GAAG,IAAI,CAyC1E;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI,CAO1D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CA4BvD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAI/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAGtE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,CAAC,CASjG;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAGpE"}
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AA8ChD,wBAAgB,WAAW,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI,GAAG,KAAK,CA8BvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,iBAAiB,UAAQ,GAAG,IAAI,CAqE1E;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI,CAO1D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAuCvD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAI/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAGtE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,CAAC,CASjG;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAGpE"}
package/dist/scope.js CHANGED
@@ -1,4 +1,19 @@
1
1
  let nextId = 1;
2
+ /**
3
+ * Walk up the scope chain to find the owning ComponentInstance. The
4
+ * instance is stamped onto the rootScope by `installDevTools`, so this
5
+ * returns null in production (no devtools) or for scopes that haven't
6
+ * yet been parented to a tracked root (e.g., during initial creation).
7
+ */
8
+ function findInstance(scope) {
9
+ let s = scope;
10
+ while (s) {
11
+ if (s.instance)
12
+ return s.instance;
13
+ s = s.parent;
14
+ }
15
+ return null;
16
+ }
2
17
  // Shared empty arrays — avoid allocating per scope when unused
3
18
  const EMPTY_SCOPES = [];
4
19
  const EMPTY_DISPOSERS = [];
@@ -8,6 +23,14 @@ const EMPTY_UPDATERS = [];
8
23
  // Capped to avoid memory leaks in apps that create/destroy thousands of rows.
9
24
  const SCOPE_POOL = [];
10
25
  const SCOPE_POOL_MAX = 2048;
26
+ // Dev-mode flag. Flipped to true by installDevTools() once any component
27
+ // instance has a disposer log. In production this stays false forever,
28
+ // and disposeScope skips findInstance entirely — zero cost.
29
+ let anyDisposerLogInstalled = false;
30
+ /** @internal — called by devtools.ts::installDevTools */
31
+ export function _markDisposerLogInstalled() {
32
+ anyDisposerLogInstalled = true;
33
+ }
11
34
  /** @internal Drain the scope pool — for testing only */
12
35
  export function _drainScopePool() {
13
36
  SCOPE_POOL.length = 0;
@@ -18,7 +41,13 @@ export function createScope(parent) {
18
41
  scope = SCOPE_POOL.pop();
19
42
  scope.id = nextId++;
20
43
  scope.parent = parent;
21
- // Arrays already reset to empties by dispose
44
+ // Arrays already reset to empties by dispose. Reset dev-only hints
45
+ // so recycled scopes don't carry stale tagging/back-refs. Cheap
46
+ // (two undefined writes) and keeps production-identical behavior
47
+ // when these fields are never set.
48
+ scope.disposalCause = undefined;
49
+ scope.instance = undefined;
50
+ scope._kind = undefined;
22
51
  }
23
52
  else {
24
53
  scope = {
@@ -50,6 +79,20 @@ export function createScope(parent) {
50
79
  */
51
80
  export function disposeScope(scope, skipParentRemoval = false) {
52
81
  if (scope.disposers.length === 0 && scope.children.length === 0 && scope.bindings.length === 0) {
82
+ // Dev-only: still emit a DisposerEvent for empty scopes — the log
83
+ // is meant to capture every scope the app destroys, not only ones
84
+ // that had attached work. Outer flag check keeps production (no
85
+ // devtools ever installed) at true zero cost — no parent-chain walk.
86
+ if (anyDisposerLogInstalled) {
87
+ const inst = findInstance(scope);
88
+ if (inst?._disposerLog !== undefined) {
89
+ inst._disposerLog.push({
90
+ scopeId: String(scope.id),
91
+ cause: scope.disposalCause ?? 'component-unmount',
92
+ timestamp: Date.now(),
93
+ });
94
+ }
95
+ }
53
96
  if (!skipParentRemoval)
54
97
  removeFromParent(scope);
55
98
  scope.parent = null;
@@ -67,6 +110,19 @@ export function disposeScope(scope, skipParentRemoval = false) {
67
110
  for (const disposer of scope.disposers) {
68
111
  disposer();
69
112
  }
113
+ // Dev-only: emit disposer events into the owning instance's log.
114
+ // Outer flag check keeps production (no devtools ever installed) at
115
+ // true zero cost — skips the O(depth) parent-chain walk entirely.
116
+ if (anyDisposerLogInstalled) {
117
+ const inst = findInstance(scope);
118
+ if (inst?._disposerLog !== undefined) {
119
+ inst._disposerLog.push({
120
+ scopeId: String(scope.id),
121
+ cause: scope.disposalCause ?? 'component-unmount',
122
+ timestamp: Date.now(),
123
+ });
124
+ }
125
+ }
70
126
  // Mark bindings as dead and break closure/DOM retention
71
127
  for (const binding of scope.bindings) {
72
128
  binding.dead = true;
@@ -118,6 +174,17 @@ export function disposeScopesBulk(scopes) {
118
174
  const disposers = scope.disposers;
119
175
  for (let d = 0; d < disposers.length; d++)
120
176
  disposers[d]();
177
+ // Dev-only: emit disposer events — same guard as disposeScope.
178
+ if (anyDisposerLogInstalled) {
179
+ const inst = findInstance(scope);
180
+ if (inst?._disposerLog !== undefined) {
181
+ inst._disposerLog.push({
182
+ scopeId: String(scope.id),
183
+ cause: scope.disposalCause ?? 'component-unmount',
184
+ timestamp: Date.now(),
185
+ });
186
+ }
187
+ }
121
188
  // Mark bindings dead
122
189
  const bindings = scope.bindings;
123
190
  for (let b = 0; b < bindings.length; b++) {
package/dist/scope.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Scope, Binding } from './types.js'\n\nlet nextId = 1\n\n// Shared empty arrays — avoid allocating per scope when unused\nconst EMPTY_SCOPES: Scope[] = []\nconst EMPTY_DISPOSERS: Array<() => void> = []\nconst EMPTY_BINDINGS: Binding[] = []\nconst EMPTY_UPDATERS: Array<() => void> = []\n\n// Scope pool — reuse disposed scope objects to reduce GC pressure.\n// Capped to avoid memory leaks in apps that create/destroy thousands of rows.\nconst SCOPE_POOL: Scope[] = []\nconst SCOPE_POOL_MAX = 2048\n\n/** @internal Drain the scope pool — for testing only */\nexport function _drainScopePool(): void {\n SCOPE_POOL.length = 0\n}\n\nexport function createScope(parent: Scope | null): Scope {\n let scope: Scope\n if (SCOPE_POOL.length > 0) {\n scope = SCOPE_POOL.pop()!\n scope.id = nextId++\n scope.parent = parent\n // Arrays already reset to empties by dispose\n } else {\n scope = {\n id: nextId++,\n parent,\n children: EMPTY_SCOPES,\n disposers: EMPTY_DISPOSERS,\n bindings: EMPTY_BINDINGS,\n itemUpdaters: EMPTY_UPDATERS,\n }\n }\n\n if (parent) {\n if (parent.children === EMPTY_SCOPES) parent.children = []\n parent.children.push(scope)\n }\n\n return scope\n}\n\n/**\n * Dispose a scope and all its children. By default, detaches the scope\n * from its parent's `children` array via `indexOf + splice` — O(N) per\n * call, which becomes O(N²) when disposing many sibling scopes in bulk\n * (e.g. `each` clearing 1000 rows).\n *\n * Pass `skipParentRemoval = true` when the caller will batch-remove\n * children afterwards (see `removeOrphanedFromParent`). The scope's\n * `parent` pointer is still set to `null` so the caller can identify\n * orphaned entries.\n */\nexport function disposeScope(scope: Scope, skipParentRemoval = false): void {\n if (scope.disposers.length === 0 && scope.children.length === 0 && scope.bindings.length === 0) {\n if (!skipParentRemoval) removeFromParent(scope)\n scope.parent = null\n // Don't pool empty scopes from the early-return path — they may be\n // disposed idempotently (twice), which would create pool duplicates\n return\n }\n\n // When skipParentRemoval is true, children don't mutate during disposal —\n // iterate directly without allocating a copy. Otherwise, clone to avoid\n // mutation during iteration.\n const children = skipParentRemoval ? scope.children : scope.children.slice()\n for (const child of children) {\n disposeScope(child, skipParentRemoval)\n }\n\n for (const disposer of scope.disposers) {\n disposer()\n }\n\n // Mark bindings as dead and break closure/DOM retention\n for (const binding of scope.bindings) {\n binding.dead = true\n binding.accessor = null!\n binding.node = null!\n binding.lastValue = undefined\n }\n\n // Reset to shared empties — don't just truncate, so pooled scopes\n // don't hold allocated-but-empty arrays\n scope.disposers = EMPTY_DISPOSERS\n scope.bindings = EMPTY_BINDINGS\n scope.children = EMPTY_SCOPES\n scope.itemUpdaters = EMPTY_UPDATERS\n\n if (!skipParentRemoval) removeFromParent(scope)\n scope.parent = null\n\n // Return to pool for reuse\n if (SCOPE_POOL.length < SCOPE_POOL_MAX) SCOPE_POOL.push(scope)\n}\n\n/**\n * Batch-remove children with `parent === null` from `parent.children`.\n * Called after a bulk `disposeScope(child, true)` pass to collapse the\n * individual O(N) splice operations into one O(N) scan.\n */\nexport function removeOrphanedChildren(parent: Scope): void {\n const children = parent.children\n let w = 0\n for (let r = 0; r < children.length; r++) {\n if (children[r]!.parent !== null) children[w++] = children[r]!\n }\n children.length = w\n}\n\n/**\n * Bulk dispose an array of sibling scopes — avoids per-scope function call\n * overhead. Used by each() clear path where 1000+ scopes are disposed at once.\n * Caller must call removeOrphanedChildren(parent) afterwards.\n */\nexport function disposeScopesBulk(scopes: Scope[]): void {\n for (let i = 0; i < scopes.length; i++) {\n const scope = scopes[i]!\n // Recursively dispose children\n const children = scope.children\n if (children.length > 0) {\n disposeScopesBulk(children)\n }\n // Run disposers\n const disposers = scope.disposers\n for (let d = 0; d < disposers.length; d++) disposers[d]!()\n // Mark bindings dead\n const bindings = scope.bindings\n for (let b = 0; b < bindings.length; b++) {\n const binding = bindings[b]!\n binding.dead = true\n binding.accessor = null!\n binding.node = null!\n binding.lastValue = undefined\n }\n // Reset to shared empties + detach from parent + return to pool\n scope.disposers = EMPTY_DISPOSERS\n scope.bindings = EMPTY_BINDINGS\n scope.children = EMPTY_SCOPES\n scope.itemUpdaters = EMPTY_UPDATERS\n scope.parent = null\n if (SCOPE_POOL.length < SCOPE_POOL_MAX) SCOPE_POOL.push(scope)\n }\n}\n\nexport function addBinding(scope: Scope, binding: Binding): void {\n binding.ownerScope = scope\n if (scope.bindings === EMPTY_BINDINGS) scope.bindings = []\n scope.bindings.push(binding)\n}\n\nexport function addItemUpdater(scope: Scope, updater: () => void): void {\n if (scope.itemUpdaters === EMPTY_UPDATERS) scope.itemUpdaters = []\n scope.itemUpdaters.push(updater)\n}\n\n/**\n * Register a per-item updater that compares the new value against the last\n * value before applying. Shared by `text()`, `elSplit()`, and `elTemplate()`\n * so the equality-check logic lives in one place.\n *\n * @param apply - DOM write: receives the new value when it differs\n * @returns the initial value (caller should apply it to the DOM)\n */\nexport function addCheckedItemUpdater<V>(scope: Scope, get: () => V, apply: (value: V) => void): V {\n let last: V = get()\n addItemUpdater(scope, () => {\n const v = get()\n if (v === last || (v !== v && last !== last)) return\n last = v\n apply(v)\n })\n return last\n}\n\nexport function addDisposer(scope: Scope, disposer: () => void): void {\n if (scope.disposers === EMPTY_DISPOSERS) scope.disposers = []\n scope.disposers.push(disposer)\n}\n\nfunction removeFromParent(scope: Scope): void {\n if (scope.parent) {\n const idx = scope.parent.children.indexOf(scope)\n if (idx !== -1) {\n scope.parent.children.splice(idx, 1)\n }\n }\n}\n"]}
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAGA,IAAI,MAAM,GAAG,CAAC,CAAA;AAEd;;;;;GAKG;AACH,SAAS,YAAY,CAAC,KAAY;IAChC,IAAI,CAAC,GAAiB,KAAK,CAAA;IAC3B,OAAO,CAAC,EAAE,CAAC;QACT,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAO,CAAC,CAAC,QAAQ,CAAA;QACjC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;IACd,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,+DAA+D;AAC/D,MAAM,YAAY,GAAY,EAAE,CAAA;AAChC,MAAM,eAAe,GAAsB,EAAE,CAAA;AAC7C,MAAM,cAAc,GAAc,EAAE,CAAA;AACpC,MAAM,cAAc,GAAsB,EAAE,CAAA;AAE5C,mEAAmE;AACnE,8EAA8E;AAC9E,MAAM,UAAU,GAAY,EAAE,CAAA;AAC9B,MAAM,cAAc,GAAG,IAAI,CAAA;AAE3B,yEAAyE;AACzE,uEAAuE;AACvE,4DAA4D;AAC5D,IAAI,uBAAuB,GAAG,KAAK,CAAA;AAEnC,yDAAyD;AACzD,MAAM,UAAU,yBAAyB;IACvC,uBAAuB,GAAG,IAAI,CAAA;AAChC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,eAAe;IAC7B,UAAU,CAAC,MAAM,GAAG,CAAC,CAAA;AACvB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAoB;IAC9C,IAAI,KAAY,CAAA;IAChB,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAG,CAAA;QACzB,KAAK,CAAC,EAAE,GAAG,MAAM,EAAE,CAAA;QACnB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAA;QACrB,mEAAmE;QACnE,gEAAgE;QAChE,iEAAiE;QACjE,mCAAmC;QACnC,KAAK,CAAC,aAAa,GAAG,SAAS,CAAA;QAC/B,KAAK,CAAC,QAAQ,GAAG,SAAS,CAAA;QAC1B,KAAK,CAAC,KAAK,GAAG,SAAS,CAAA;IACzB,CAAC;SAAM,CAAC;QACN,KAAK,GAAG;YACN,EAAE,EAAE,MAAM,EAAE;YACZ,MAAM;YACN,QAAQ,EAAE,YAAY;YACtB,SAAS,EAAE,eAAe;YAC1B,QAAQ,EAAE,cAAc;YACxB,YAAY,EAAE,cAAc;SAC7B,CAAA;IACH,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,MAAM,CAAC,QAAQ,KAAK,YAAY;YAAE,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAA;QAC1D,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,KAAY,EAAE,iBAAiB,GAAG,KAAK;IAClE,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/F,kEAAkE;QAClE,kEAAkE;QAClE,gEAAgE;QAChE,qEAAqE;QACrE,IAAI,uBAAuB,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;YAChC,IAAI,IAAI,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;gBACrC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;oBACrB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,KAAK,EAAE,KAAK,CAAC,aAAa,IAAI,mBAAmB;oBACjD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;iBACtB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QACD,IAAI,CAAC,iBAAiB;YAAE,gBAAgB,CAAC,KAAK,CAAC,CAAA;QAC/C,KAAK,CAAC,MAAM,GAAG,IAAI,CAAA;QACnB,mEAAmE;QACnE,oEAAoE;QACpE,OAAM;IACR,CAAC;IAED,0EAA0E;IAC1E,wEAAwE;IACxE,6BAA6B;IAC7B,MAAM,QAAQ,GAAG,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;IAC5E,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,YAAY,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAA;IACxC,CAAC;IAED,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACvC,QAAQ,EAAE,CAAA;IACZ,CAAC;IAED,iEAAiE;IACjE,oEAAoE;IACpE,kEAAkE;IAClE,IAAI,uBAAuB,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,IAAI,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;YACrC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;gBACrB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,KAAK,EAAE,KAAK,CAAC,aAAa,IAAI,mBAAmB;gBACjD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACtB,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,wDAAwD;IACxD,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;QACnB,OAAO,CAAC,QAAQ,GAAG,IAAK,CAAA;QACxB,OAAO,CAAC,IAAI,GAAG,IAAK,CAAA;QACpB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAA;IAC/B,CAAC;IAED,kEAAkE;IAClE,wCAAwC;IACxC,KAAK,CAAC,SAAS,GAAG,eAAe,CAAA;IACjC,KAAK,CAAC,QAAQ,GAAG,cAAc,CAAA;IAC/B,KAAK,CAAC,QAAQ,GAAG,YAAY,CAAA;IAC7B,KAAK,CAAC,YAAY,GAAG,cAAc,CAAA;IAEnC,IAAI,CAAC,iBAAiB;QAAE,gBAAgB,CAAC,KAAK,CAAC,CAAA;IAC/C,KAAK,CAAC,MAAM,GAAG,IAAI,CAAA;IAEnB,2BAA2B;IAC3B,IAAI,UAAU,CAAC,MAAM,GAAG,cAAc;QAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAChE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAa;IAClD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAA;IAChC,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,IAAI,QAAQ,CAAC,CAAC,CAAE,CAAC,MAAM,KAAK,IAAI;YAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAA;IAChE,CAAC;IACD,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;AACrB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAe;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,CAAA;QACxB,+BAA+B;QAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;QAC/B,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,iBAAiB,CAAC,QAAQ,CAAC,CAAA;QAC7B,CAAC;QACD,gBAAgB;QAChB,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;QACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE;YAAE,SAAS,CAAC,CAAC,CAAE,EAAE,CAAA;QAC1D,+DAA+D;QAC/D,IAAI,uBAAuB,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;YAChC,IAAI,IAAI,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;gBACrC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;oBACrB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,KAAK,EAAE,KAAK,CAAC,aAAa,IAAI,mBAAmB;oBACjD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;iBACtB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QACD,qBAAqB;QACrB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;QAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAA;YAC5B,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;YACnB,OAAO,CAAC,QAAQ,GAAG,IAAK,CAAA;YACxB,OAAO,CAAC,IAAI,GAAG,IAAK,CAAA;YACpB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAA;QAC/B,CAAC;QACD,gEAAgE;QAChE,KAAK,CAAC,SAAS,GAAG,eAAe,CAAA;QACjC,KAAK,CAAC,QAAQ,GAAG,cAAc,CAAA;QAC/B,KAAK,CAAC,QAAQ,GAAG,YAAY,CAAA;QAC7B,KAAK,CAAC,YAAY,GAAG,cAAc,CAAA;QACnC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAA;QACnB,IAAI,UAAU,CAAC,MAAM,GAAG,cAAc;YAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAChE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAY,EAAE,OAAgB;IACvD,OAAO,CAAC,UAAU,GAAG,KAAK,CAAA;IAC1B,IAAI,KAAK,CAAC,QAAQ,KAAK,cAAc;QAAE,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAA;IAC1D,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;AAC9B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAY,EAAE,OAAmB;IAC9D,IAAI,KAAK,CAAC,YAAY,KAAK,cAAc;QAAE,KAAK,CAAC,YAAY,GAAG,EAAE,CAAA;IAClE,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;AAClC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAI,KAAY,EAAE,GAAY,EAAE,KAAyB;IAC5F,IAAI,IAAI,GAAM,GAAG,EAAE,CAAA;IACnB,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,GAAG,GAAG,EAAE,CAAA;QACf,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI,CAAC;YAAE,OAAM;QACpD,IAAI,GAAG,CAAC,CAAA;QACR,KAAK,CAAC,CAAC,CAAC,CAAA;IACV,CAAC,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,QAAoB;IAC5D,IAAI,KAAK,CAAC,SAAS,KAAK,eAAe;QAAE,KAAK,CAAC,SAAS,GAAG,EAAE,CAAA;IAC7D,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAChC,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAY;IACpC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAChD,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YACf,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACtC,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import type { Scope, Binding } from './types.js'\nimport type { ComponentInstance } from './update-loop.js'\n\nlet nextId = 1\n\n/**\n * Walk up the scope chain to find the owning ComponentInstance. The\n * instance is stamped onto the rootScope by `installDevTools`, so this\n * returns null in production (no devtools) or for scopes that haven't\n * yet been parented to a tracked root (e.g., during initial creation).\n */\nfunction findInstance(scope: Scope): ComponentInstance | null {\n let s: Scope | null = scope\n while (s) {\n if (s.instance) return s.instance\n s = s.parent\n }\n return null\n}\n\n// Shared empty arrays — avoid allocating per scope when unused\nconst EMPTY_SCOPES: Scope[] = []\nconst EMPTY_DISPOSERS: Array<() => void> = []\nconst EMPTY_BINDINGS: Binding[] = []\nconst EMPTY_UPDATERS: Array<() => void> = []\n\n// Scope pool — reuse disposed scope objects to reduce GC pressure.\n// Capped to avoid memory leaks in apps that create/destroy thousands of rows.\nconst SCOPE_POOL: Scope[] = []\nconst SCOPE_POOL_MAX = 2048\n\n// Dev-mode flag. Flipped to true by installDevTools() once any component\n// instance has a disposer log. In production this stays false forever,\n// and disposeScope skips findInstance entirely — zero cost.\nlet anyDisposerLogInstalled = false\n\n/** @internal — called by devtools.ts::installDevTools */\nexport function _markDisposerLogInstalled(): void {\n anyDisposerLogInstalled = true\n}\n\n/** @internal Drain the scope pool — for testing only */\nexport function _drainScopePool(): void {\n SCOPE_POOL.length = 0\n}\n\nexport function createScope(parent: Scope | null): Scope {\n let scope: Scope\n if (SCOPE_POOL.length > 0) {\n scope = SCOPE_POOL.pop()!\n scope.id = nextId++\n scope.parent = parent\n // Arrays already reset to empties by dispose. Reset dev-only hints\n // so recycled scopes don't carry stale tagging/back-refs. Cheap\n // (two undefined writes) and keeps production-identical behavior\n // when these fields are never set.\n scope.disposalCause = undefined\n scope.instance = undefined\n scope._kind = undefined\n } else {\n scope = {\n id: nextId++,\n parent,\n children: EMPTY_SCOPES,\n disposers: EMPTY_DISPOSERS,\n bindings: EMPTY_BINDINGS,\n itemUpdaters: EMPTY_UPDATERS,\n }\n }\n\n if (parent) {\n if (parent.children === EMPTY_SCOPES) parent.children = []\n parent.children.push(scope)\n }\n\n return scope\n}\n\n/**\n * Dispose a scope and all its children. By default, detaches the scope\n * from its parent's `children` array via `indexOf + splice` — O(N) per\n * call, which becomes O(N²) when disposing many sibling scopes in bulk\n * (e.g. `each` clearing 1000 rows).\n *\n * Pass `skipParentRemoval = true` when the caller will batch-remove\n * children afterwards (see `removeOrphanedFromParent`). The scope's\n * `parent` pointer is still set to `null` so the caller can identify\n * orphaned entries.\n */\nexport function disposeScope(scope: Scope, skipParentRemoval = false): void {\n if (scope.disposers.length === 0 && scope.children.length === 0 && scope.bindings.length === 0) {\n // Dev-only: still emit a DisposerEvent for empty scopes — the log\n // is meant to capture every scope the app destroys, not only ones\n // that had attached work. Outer flag check keeps production (no\n // devtools ever installed) at true zero cost — no parent-chain walk.\n if (anyDisposerLogInstalled) {\n const inst = findInstance(scope)\n if (inst?._disposerLog !== undefined) {\n inst._disposerLog.push({\n scopeId: String(scope.id),\n cause: scope.disposalCause ?? 'component-unmount',\n timestamp: Date.now(),\n })\n }\n }\n if (!skipParentRemoval) removeFromParent(scope)\n scope.parent = null\n // Don't pool empty scopes from the early-return path — they may be\n // disposed idempotently (twice), which would create pool duplicates\n return\n }\n\n // When skipParentRemoval is true, children don't mutate during disposal —\n // iterate directly without allocating a copy. Otherwise, clone to avoid\n // mutation during iteration.\n const children = skipParentRemoval ? scope.children : scope.children.slice()\n for (const child of children) {\n disposeScope(child, skipParentRemoval)\n }\n\n for (const disposer of scope.disposers) {\n disposer()\n }\n\n // Dev-only: emit disposer events into the owning instance's log.\n // Outer flag check keeps production (no devtools ever installed) at\n // true zero cost — skips the O(depth) parent-chain walk entirely.\n if (anyDisposerLogInstalled) {\n const inst = findInstance(scope)\n if (inst?._disposerLog !== undefined) {\n inst._disposerLog.push({\n scopeId: String(scope.id),\n cause: scope.disposalCause ?? 'component-unmount',\n timestamp: Date.now(),\n })\n }\n }\n\n // Mark bindings as dead and break closure/DOM retention\n for (const binding of scope.bindings) {\n binding.dead = true\n binding.accessor = null!\n binding.node = null!\n binding.lastValue = undefined\n }\n\n // Reset to shared empties — don't just truncate, so pooled scopes\n // don't hold allocated-but-empty arrays\n scope.disposers = EMPTY_DISPOSERS\n scope.bindings = EMPTY_BINDINGS\n scope.children = EMPTY_SCOPES\n scope.itemUpdaters = EMPTY_UPDATERS\n\n if (!skipParentRemoval) removeFromParent(scope)\n scope.parent = null\n\n // Return to pool for reuse\n if (SCOPE_POOL.length < SCOPE_POOL_MAX) SCOPE_POOL.push(scope)\n}\n\n/**\n * Batch-remove children with `parent === null` from `parent.children`.\n * Called after a bulk `disposeScope(child, true)` pass to collapse the\n * individual O(N) splice operations into one O(N) scan.\n */\nexport function removeOrphanedChildren(parent: Scope): void {\n const children = parent.children\n let w = 0\n for (let r = 0; r < children.length; r++) {\n if (children[r]!.parent !== null) children[w++] = children[r]!\n }\n children.length = w\n}\n\n/**\n * Bulk dispose an array of sibling scopes — avoids per-scope function call\n * overhead. Used by each() clear path where 1000+ scopes are disposed at once.\n * Caller must call removeOrphanedChildren(parent) afterwards.\n */\nexport function disposeScopesBulk(scopes: Scope[]): void {\n for (let i = 0; i < scopes.length; i++) {\n const scope = scopes[i]!\n // Recursively dispose children\n const children = scope.children\n if (children.length > 0) {\n disposeScopesBulk(children)\n }\n // Run disposers\n const disposers = scope.disposers\n for (let d = 0; d < disposers.length; d++) disposers[d]!()\n // Dev-only: emit disposer events — same guard as disposeScope.\n if (anyDisposerLogInstalled) {\n const inst = findInstance(scope)\n if (inst?._disposerLog !== undefined) {\n inst._disposerLog.push({\n scopeId: String(scope.id),\n cause: scope.disposalCause ?? 'component-unmount',\n timestamp: Date.now(),\n })\n }\n }\n // Mark bindings dead\n const bindings = scope.bindings\n for (let b = 0; b < bindings.length; b++) {\n const binding = bindings[b]!\n binding.dead = true\n binding.accessor = null!\n binding.node = null!\n binding.lastValue = undefined\n }\n // Reset to shared empties + detach from parent + return to pool\n scope.disposers = EMPTY_DISPOSERS\n scope.bindings = EMPTY_BINDINGS\n scope.children = EMPTY_SCOPES\n scope.itemUpdaters = EMPTY_UPDATERS\n scope.parent = null\n if (SCOPE_POOL.length < SCOPE_POOL_MAX) SCOPE_POOL.push(scope)\n }\n}\n\nexport function addBinding(scope: Scope, binding: Binding): void {\n binding.ownerScope = scope\n if (scope.bindings === EMPTY_BINDINGS) scope.bindings = []\n scope.bindings.push(binding)\n}\n\nexport function addItemUpdater(scope: Scope, updater: () => void): void {\n if (scope.itemUpdaters === EMPTY_UPDATERS) scope.itemUpdaters = []\n scope.itemUpdaters.push(updater)\n}\n\n/**\n * Register a per-item updater that compares the new value against the last\n * value before applying. Shared by `text()`, `elSplit()`, and `elTemplate()`\n * so the equality-check logic lives in one place.\n *\n * @param apply - DOM write: receives the new value when it differs\n * @returns the initial value (caller should apply it to the DOM)\n */\nexport function addCheckedItemUpdater<V>(scope: Scope, get: () => V, apply: (value: V) => void): V {\n let last: V = get()\n addItemUpdater(scope, () => {\n const v = get()\n if (v === last || (v !== v && last !== last)) return\n last = v\n apply(v)\n })\n return last\n}\n\nexport function addDisposer(scope: Scope, disposer: () => void): void {\n if (scope.disposers === EMPTY_DISPOSERS) scope.disposers = []\n scope.disposers.push(disposer)\n}\n\nfunction removeFromParent(scope: Scope): void {\n if (scope.parent) {\n const idx = scope.parent.children.indexOf(scope)\n if (idx !== -1) {\n scope.parent.children.splice(idx, 1)\n }\n }\n}\n"]}
package/dist/ssr.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAC9D,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAKlF;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EACjC,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC1B,YAAY,CAAC,EAAE,CAAC,EAChB,WAAW,CAAC,EAAE,KAAK,GAClB;IAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAAE,CAarD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAezE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,MAAM,CAG5F"}
1
+ {"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAC9D,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAKlF;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EACjC,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC1B,YAAY,CAAC,EAAE,CAAC,EAChB,WAAW,CAAC,EAAE,KAAK,GAClB;IAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAAE,CAiBrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAezE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,MAAM,CAG5F"}
package/dist/ssr.js CHANGED
@@ -21,7 +21,11 @@ export function renderNodes(def, initialState, parentScope) {
21
21
  inst.state = initialState;
22
22
  }
23
23
  setFlatBindings(inst.allBindings);
24
- setRenderContext({ ...inst, send: inst.send });
24
+ setRenderContext({
25
+ ...inst,
26
+ send: inst.send,
27
+ instance: inst,
28
+ });
25
29
  const nodes = def.view(createView(inst.send));
26
30
  clearRenderContext();
27
31
  setFlatBindings(null);
package/dist/ssr.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.js","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentDef, Scope, Binding } from './types.js'\nimport { createComponentInstance, type ComponentInstance } from './update-loop.js'\nimport { setRenderContext, clearRenderContext } from './render-context.js'\nimport { setFlatBindings } from './binding.js'\nimport { createView } from './view-helpers.js'\n\n/**\n * Render a component to DOM nodes for SSR, returning both the produced\n * nodes and the component instance (so callers can compose trees before\n * serializing — e.g. `@llui/vike` stitches layout + page nodes at the\n * `pageSlot()` marker position before one final serialization pass).\n *\n * Accepts an optional `parentScope` so the rendered instance's rootScope\n * becomes a child of an existing scope tree — used by persistent layouts\n * so contexts provided by an outer layout are reachable from an inner\n * page via `useContext`.\n *\n * Call `initSsrDom()` once before using this on the server.\n */\nexport function renderNodes<S, M, E>(\n def: ComponentDef<S, M, E>,\n initialState?: S,\n parentScope?: Scope,\n): { nodes: Node[]; inst: ComponentInstance<S, M, E> } {\n const inst = createComponentInstance(def, undefined, parentScope ?? null)\n if (initialState !== undefined) {\n inst.state = initialState\n }\n\n setFlatBindings(inst.allBindings)\n setRenderContext({ ...inst, send: inst.send as (msg: unknown) => void })\n const nodes = def.view(createView<S, M>(inst.send))\n clearRenderContext()\n setFlatBindings(null)\n\n return { nodes, inst }\n}\n\n/**\n * Serialize an array of DOM nodes to an HTML string, adding\n * `data-llui-hydrate` markers on elements that own reactive bindings.\n *\n * Accepts a flat binding list so compositions of multiple instances\n * (layout + page, for persistent-layout SSR) produce correct markers\n * across the whole tree. Pass the union of every composed instance's\n * `allBindings`.\n */\nexport function serializeNodes(nodes: Node[], bindings: Binding[]): string {\n const hydrateElements = new Set<Node>()\n for (const binding of bindings) {\n const node = binding.node\n if (node.nodeType === 1) {\n hydrateElements.add(node)\n } else if (node.parentNode && node.parentNode.nodeType === 1) {\n hydrateElements.add(node.parentNode)\n }\n }\n let html = ''\n for (const node of nodes) {\n html += nodeToStringWithMarkers(node, hydrateElements)\n }\n return html\n}\n\n/**\n * Render a component to an HTML string for SSR.\n * Evaluates view() against the initial state (or provided data),\n * serializes the DOM to HTML, and adds data-llui-hydrate markers\n * on nodes with reactive bindings.\n *\n * Call initSsrDom() once before using this on the server.\n *\n * For persistent layouts, use `renderNodes` + `serializeNodes` directly\n * so layout and page nodes can be composed before serialization.\n */\nexport function renderToString<S, M, E>(def: ComponentDef<S, M, E>, initialState?: S): string {\n const { nodes, inst } = renderNodes(def, initialState)\n return serializeNodes(nodes, inst.allBindings)\n}\n\nfunction nodeToStringWithMarkers(node: Node, bindingNodes: Set<Node>): string {\n if (node.nodeType === 3) {\n return escapeHtml(node.textContent ?? '')\n }\n if (node.nodeType === 8) {\n return `<!--${node.textContent ?? ''}-->`\n }\n if (node.nodeType === 1) {\n const el = node as Element\n return elementToString(el, bindingNodes)\n }\n return ''\n}\n\nfunction elementToString(el: Element, bindingNodes: Set<Node>): string {\n const tag = el.tagName.toLowerCase()\n let attrs = ''\n\n for (let i = 0; i < el.attributes.length; i++) {\n const attr = el.attributes[i]!\n // Skip event handler attributes\n if (attr.name.startsWith('on')) continue\n attrs += ` ${attr.name}=\"${escapeAttr(attr.value)}\"`\n }\n\n // Add hydrate marker if this element or any of its text children have bindings\n if (bindingNodes.has(el)) {\n attrs += ' data-llui-hydrate'\n }\n\n // Void elements\n if (isVoidElement(tag)) {\n return `<${tag}${attrs} />`\n }\n\n let children = ''\n for (let i = 0; i < el.childNodes.length; i++) {\n children += nodeToStringWithMarkers(el.childNodes[i]!, bindingNodes)\n }\n\n return `<${tag}${attrs}>${children}</${tag}>`\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/\"/g, '&quot;')\n}\n\nconst VOID_ELEMENTS = new Set([\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n])\n\nfunction isVoidElement(tag: string): boolean {\n return VOID_ELEMENTS.has(tag)\n}\n"]}
1
+ {"version":3,"file":"ssr.js","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentDef, Scope, Binding } from './types.js'\nimport { createComponentInstance, type ComponentInstance } from './update-loop.js'\nimport { setRenderContext, clearRenderContext } from './render-context.js'\nimport { setFlatBindings } from './binding.js'\nimport { createView } from './view-helpers.js'\n\n/**\n * Render a component to DOM nodes for SSR, returning both the produced\n * nodes and the component instance (so callers can compose trees before\n * serializing — e.g. `@llui/vike` stitches layout + page nodes at the\n * `pageSlot()` marker position before one final serialization pass).\n *\n * Accepts an optional `parentScope` so the rendered instance's rootScope\n * becomes a child of an existing scope tree — used by persistent layouts\n * so contexts provided by an outer layout are reachable from an inner\n * page via `useContext`.\n *\n * Call `initSsrDom()` once before using this on the server.\n */\nexport function renderNodes<S, M, E>(\n def: ComponentDef<S, M, E>,\n initialState?: S,\n parentScope?: Scope,\n): { nodes: Node[]; inst: ComponentInstance<S, M, E> } {\n const inst = createComponentInstance(def, undefined, parentScope ?? null)\n if (initialState !== undefined) {\n inst.state = initialState\n }\n\n setFlatBindings(inst.allBindings)\n setRenderContext({\n ...inst,\n send: inst.send as (msg: unknown) => void,\n instance: inst as ComponentInstance,\n })\n const nodes = def.view(createView<S, M>(inst.send))\n clearRenderContext()\n setFlatBindings(null)\n\n return { nodes, inst }\n}\n\n/**\n * Serialize an array of DOM nodes to an HTML string, adding\n * `data-llui-hydrate` markers on elements that own reactive bindings.\n *\n * Accepts a flat binding list so compositions of multiple instances\n * (layout + page, for persistent-layout SSR) produce correct markers\n * across the whole tree. Pass the union of every composed instance's\n * `allBindings`.\n */\nexport function serializeNodes(nodes: Node[], bindings: Binding[]): string {\n const hydrateElements = new Set<Node>()\n for (const binding of bindings) {\n const node = binding.node\n if (node.nodeType === 1) {\n hydrateElements.add(node)\n } else if (node.parentNode && node.parentNode.nodeType === 1) {\n hydrateElements.add(node.parentNode)\n }\n }\n let html = ''\n for (const node of nodes) {\n html += nodeToStringWithMarkers(node, hydrateElements)\n }\n return html\n}\n\n/**\n * Render a component to an HTML string for SSR.\n * Evaluates view() against the initial state (or provided data),\n * serializes the DOM to HTML, and adds data-llui-hydrate markers\n * on nodes with reactive bindings.\n *\n * Call initSsrDom() once before using this on the server.\n *\n * For persistent layouts, use `renderNodes` + `serializeNodes` directly\n * so layout and page nodes can be composed before serialization.\n */\nexport function renderToString<S, M, E>(def: ComponentDef<S, M, E>, initialState?: S): string {\n const { nodes, inst } = renderNodes(def, initialState)\n return serializeNodes(nodes, inst.allBindings)\n}\n\nfunction nodeToStringWithMarkers(node: Node, bindingNodes: Set<Node>): string {\n if (node.nodeType === 3) {\n return escapeHtml(node.textContent ?? '')\n }\n if (node.nodeType === 8) {\n return `<!--${node.textContent ?? ''}-->`\n }\n if (node.nodeType === 1) {\n const el = node as Element\n return elementToString(el, bindingNodes)\n }\n return ''\n}\n\nfunction elementToString(el: Element, bindingNodes: Set<Node>): string {\n const tag = el.tagName.toLowerCase()\n let attrs = ''\n\n for (let i = 0; i < el.attributes.length; i++) {\n const attr = el.attributes[i]!\n // Skip event handler attributes\n if (attr.name.startsWith('on')) continue\n attrs += ` ${attr.name}=\"${escapeAttr(attr.value)}\"`\n }\n\n // Add hydrate marker if this element or any of its text children have bindings\n if (bindingNodes.has(el)) {\n attrs += ' data-llui-hydrate'\n }\n\n // Void elements\n if (isVoidElement(tag)) {\n return `<${tag}${attrs} />`\n }\n\n let children = ''\n for (let i = 0; i < el.childNodes.length; i++) {\n children += nodeToStringWithMarkers(el.childNodes[i]!, bindingNodes)\n }\n\n return `<${tag}${attrs}>${children}</${tag}>`\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/\"/g, '&quot;')\n}\n\nconst VOID_ELEMENTS = new Set([\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n])\n\nfunction isVoidElement(tag: string): boolean {\n return VOID_ELEMENTS.has(tag)\n}\n"]}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Per-variant Msg coverage tracker — dev-only.
3
+ *
4
+ * Records each dispatched message's discriminant (or `<non-discriminant>`
5
+ * for objects missing a `type` field) along with the message index it
6
+ * fired at. Consumed by the `llui_coverage` MCP tool to surface untested
7
+ * Msg variants: any variant declared in the compiled `__msgSchema` that
8
+ * never fired in the current session shows up in `neverFired`.
9
+ *
10
+ * Zero cost in production: `installDevTools` is the only caller, and it
11
+ * never runs in prod builds. Hot path is one optional-chain read per
12
+ * dispatched message (`ci._coverage?.record(...)`).
13
+ */
14
+ export interface CoverageSnapshot {
15
+ fired: Record<string, {
16
+ count: number;
17
+ lastIndex: number;
18
+ }>;
19
+ neverFired: string[];
20
+ }
21
+ export interface CoverageTracker {
22
+ record(variant: string, messageIndex: number): void;
23
+ snapshot(knownVariants?: string[]): CoverageSnapshot;
24
+ clear(): void;
25
+ }
26
+ export declare function createCoverageTracker(): CoverageTracker;
27
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/tracking/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3D,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IACnD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAA;IACpD,KAAK,IAAI,IAAI,CAAA;CACd;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAsBvD"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Per-variant Msg coverage tracker — dev-only.
3
+ *
4
+ * Records each dispatched message's discriminant (or `<non-discriminant>`
5
+ * for objects missing a `type` field) along with the message index it
6
+ * fired at. Consumed by the `llui_coverage` MCP tool to surface untested
7
+ * Msg variants: any variant declared in the compiled `__msgSchema` that
8
+ * never fired in the current session shows up in `neverFired`.
9
+ *
10
+ * Zero cost in production: `installDevTools` is the only caller, and it
11
+ * never runs in prod builds. Hot path is one optional-chain read per
12
+ * dispatched message (`ci._coverage?.record(...)`).
13
+ */
14
+ export function createCoverageTracker() {
15
+ const fired = new Map();
16
+ return {
17
+ record(variant, messageIndex) {
18
+ const existing = fired.get(variant);
19
+ if (existing) {
20
+ existing.count++;
21
+ existing.lastIndex = messageIndex;
22
+ }
23
+ else {
24
+ fired.set(variant, { count: 1, lastIndex: messageIndex });
25
+ }
26
+ },
27
+ snapshot(knownVariants) {
28
+ const firedObj = {};
29
+ for (const [k, v] of fired)
30
+ firedObj[k] = { ...v };
31
+ const neverFired = knownVariants ? knownVariants.filter((v) => !fired.has(v)) : [];
32
+ return { fired: firedObj, neverFired };
33
+ },
34
+ clear() {
35
+ fired.clear();
36
+ },
37
+ };
38
+ }
39
+ //# sourceMappingURL=coverage.js.map