@llui/dom 0.0.22 → 0.0.24

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 (97) hide show
  1. package/README.md +26 -21
  2. package/dist/dom-env.d.ts +67 -0
  3. package/dist/dom-env.d.ts.map +1 -0
  4. package/dist/dom-env.js +61 -0
  5. package/dist/dom-env.js.map +1 -0
  6. package/dist/el-split.d.ts.map +1 -1
  7. package/dist/el-split.js +4 -4
  8. package/dist/el-split.js.map +1 -1
  9. package/dist/el-template.d.ts +14 -1
  10. package/dist/el-template.d.ts.map +1 -1
  11. package/dist/el-template.js +41 -7
  12. package/dist/el-template.js.map +1 -1
  13. package/dist/elements.js +2 -2
  14. package/dist/elements.js.map +1 -1
  15. package/dist/hmr.d.ts.map +1 -1
  16. package/dist/hmr.js +1 -0
  17. package/dist/hmr.js.map +1 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/mathml-elements.js +2 -2
  23. package/dist/mathml-elements.js.map +1 -1
  24. package/dist/mount.d.ts +8 -0
  25. package/dist/mount.d.ts.map +1 -1
  26. package/dist/mount.js +14 -9
  27. package/dist/mount.js.map +1 -1
  28. package/dist/primitives/branch.js +1 -1
  29. package/dist/primitives/branch.js.map +1 -1
  30. package/dist/primitives/child.d.ts.map +1 -1
  31. package/dist/primitives/child.js +5 -2
  32. package/dist/primitives/child.js.map +1 -1
  33. package/dist/primitives/client-only.d.ts +108 -0
  34. package/dist/primitives/client-only.d.ts.map +1 -0
  35. package/dist/primitives/client-only.js +110 -0
  36. package/dist/primitives/client-only.js.map +1 -0
  37. package/dist/primitives/each.d.ts.map +1 -1
  38. package/dist/primitives/each.js +19 -9
  39. package/dist/primitives/each.js.map +1 -1
  40. package/dist/primitives/foreign.d.ts.map +1 -1
  41. package/dist/primitives/foreign.js +83 -44
  42. package/dist/primitives/foreign.js.map +1 -1
  43. package/dist/primitives/lazy.js +2 -2
  44. package/dist/primitives/lazy.js.map +1 -1
  45. package/dist/primitives/selector.js +1 -1
  46. package/dist/primitives/selector.js.map +1 -1
  47. package/dist/primitives/slice.d.ts.map +1 -1
  48. package/dist/primitives/slice.js +14 -0
  49. package/dist/primitives/slice.js.map +1 -1
  50. package/dist/primitives/text.js +3 -3
  51. package/dist/primitives/text.js.map +1 -1
  52. package/dist/primitives/unsafe-html.js +11 -13
  53. package/dist/primitives/unsafe-html.js.map +1 -1
  54. package/dist/primitives/virtual-each.d.ts.map +1 -1
  55. package/dist/primitives/virtual-each.js +5 -3
  56. package/dist/primitives/virtual-each.js.map +1 -1
  57. package/dist/render-context.d.ts +9 -0
  58. package/dist/render-context.d.ts.map +1 -1
  59. package/dist/render-context.js.map +1 -1
  60. package/dist/ssr/jsdom.d.ts +18 -0
  61. package/dist/ssr/jsdom.d.ts.map +1 -0
  62. package/dist/ssr/jsdom.js +38 -0
  63. package/dist/ssr/jsdom.js.map +1 -0
  64. package/dist/ssr/legacy.d.ts +25 -0
  65. package/dist/ssr/legacy.d.ts.map +1 -0
  66. package/dist/ssr/legacy.js +61 -0
  67. package/dist/ssr/legacy.js.map +1 -0
  68. package/dist/ssr/linkedom.d.ts +17 -0
  69. package/dist/ssr/linkedom.d.ts.map +1 -0
  70. package/dist/ssr/linkedom.js +36 -0
  71. package/dist/ssr/linkedom.js.map +1 -0
  72. package/dist/ssr-dom.d.ts +11 -7
  73. package/dist/ssr-dom.d.ts.map +1 -1
  74. package/dist/ssr-dom.js +10 -31
  75. package/dist/ssr-dom.js.map +1 -1
  76. package/dist/ssr.d.ts +10 -5
  77. package/dist/ssr.d.ts.map +1 -1
  78. package/dist/ssr.js +11 -7
  79. package/dist/ssr.js.map +1 -1
  80. package/dist/svg-elements.js +2 -2
  81. package/dist/svg-elements.js.map +1 -1
  82. package/dist/types.d.ts +27 -5
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/dist/update-loop.d.ts +3 -1
  86. package/dist/update-loop.d.ts.map +1 -1
  87. package/dist/update-loop.js +15 -1
  88. package/dist/update-loop.js.map +1 -1
  89. package/dist/view-helpers.d.ts +7 -0
  90. package/dist/view-helpers.d.ts.map +1 -1
  91. package/dist/view-helpers.js +2 -0
  92. package/dist/view-helpers.js.map +1 -1
  93. package/package.json +25 -1
  94. package/dist/scope.d.ts +0 -39
  95. package/dist/scope.d.ts.map +0 -1
  96. package/dist/scope.js +0 -250
  97. package/dist/scope.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"branch.js","sourceRoot":"","sources":["../../src/primitives/branch.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BranchOptions, Lifetime } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { setFlatBindings } from '../binding.js'\nimport { createView } from '../view-helpers.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport { pushMountQueue, popMountQueue, flushMountQueue } from './on-mount.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport function branch<S, M = unknown, K extends string = string>(\n opts: BranchOptions<S, M, K>,\n): Node[] {\n const ctx = getRenderContext('branch')\n const parentLifetime = ctx.rootLifetime\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const anchor = document.createComment('branch')\n\n let currentKey = opts.on(ctx.state as S)\n let currentLifetime: Lifetime | null = null\n let currentNodes: Node[] = []\n\n const block: StructuralBlock = {\n mask: (opts as { __mask?: number }).__mask ?? FULL_MASK,\n reconcile(state: unknown) {\n const newKey = opts.on(state as S)\n if (Object.is(newKey, currentKey)) return\n\n const parent = anchor.parentNode\n if (!parent) return\n\n const leavingNodes = currentNodes\n const leavingLifetime = currentLifetime\n\n // Build new arm first (before removing old — for FLIP animations)\n currentNodes = []\n currentLifetime = null\n currentKey = newKey\n\n const newCaseKey = String(newKey) as K\n const newBuilder = opts.cases?.[newCaseKey] ?? opts.default\n // Collect onMount callbacks from the new case into a local queue,\n // then flush them SYNCHRONOUSLY after the new nodes are inserted.\n // Without this, onMount inside a branch case would see stale DOM\n // (nodes not yet attached) OR fall back to queueMicrotask and\n // race with synchronous event dispatches after the reconcile.\n let onMountQueue: Array<() => void> | null = null\n if (newBuilder) {\n const mq = pushMountQueue()\n onMountQueue = mq.queue\n currentLifetime = createLifetime(parentLifetime)\n currentLifetime._kind =\n opts.__disposalCause === 'show-hide'\n ? 'show'\n : opts.__disposalCause === 'scope-rebuild'\n ? 'scope'\n : 'branch'\n setFlatBindings(ctx.allBindings)\n setRenderContext({ ...ctx, rootLifetime: currentLifetime, state })\n currentNodes = newBuilder(createView<S, M>(send))\n clearRenderContext()\n setFlatBindings(null)\n popMountQueue(mq.prev)\n\n const ref = anchor.nextSibling\n for (const node of currentNodes) {\n parent.insertBefore(node, ref)\n }\n }\n if (onMountQueue) flushMountQueue(onMountQueue)\n\n // Fire enter for new nodes\n if (opts.enter && currentNodes.length > 0) {\n opts.enter(currentNodes)\n }\n\n // Handle leave — may be deferred via Promise\n const removeOld = () => {\n for (const node of leavingNodes) {\n if (node.parentNode) node.parentNode.removeChild(node)\n }\n if (leavingLifetime) {\n // Tag BEFORE dispose so the disposer log records the cause.\n // `show()` passes `__disposalCause: 'show-hide'`; raw branch()\n // defaults to `'branch-swap'`. Tag wins over any pre-existing\n // value set by an inner primitive so the outermost cause is\n // reported (matches how humans describe the event).\n leavingLifetime.disposalCause = opts.__disposalCause ?? 'branch-swap'\n disposeLifetime(leavingLifetime)\n }\n }\n\n if (leavingNodes.length > 0 && opts.leave) {\n const result = opts.leave(leavingNodes)\n if (result && typeof (result as Promise<void>).then === 'function') {\n ;(result as Promise<void>).then(removeOld)\n } else {\n removeOld()\n }\n } else {\n removeOld()\n }\n },\n }\n\n // Register the block BEFORE running the initial builder so that parent\n // blocks always precede their nested children in the flat blocks array.\n // This guarantees correct Phase 1 iteration order: parents reconcile\n // first, so a parent that unmounts its old arm can dispose nested child\n // blocks (splicing them out of this array) without corrupting the loop\n // index — the splice only affects entries to the RIGHT of the parent.\n blocks.push(block)\n\n const caseKey = String(currentKey) as K\n const builder = opts.cases?.[caseKey] ?? opts.default\n // Initial-mount onMount callbacks are handled by the outer mountApp\n // queue — we're still inside the first view() call. branch doesn't\n // insert into the DOM at this point (the anchor + initial children\n // are returned to the parent), so we don't need to flush here.\n if (builder) {\n currentLifetime = createLifetime(parentLifetime)\n currentLifetime._kind =\n opts.__disposalCause === 'show-hide'\n ? 'show'\n : opts.__disposalCause === 'scope-rebuild'\n ? 'scope'\n : 'branch'\n setRenderContext({ ...ctx, rootLifetime: currentLifetime })\n currentNodes = builder(createView<S, M>(send))\n clearRenderContext()\n setRenderContext(ctx)\n\n // Fire enter on initial mount\n if (opts.enter && currentNodes.length > 0) {\n opts.enter(currentNodes)\n }\n }\n\n addDisposer(parentLifetime, () => {\n const idx = blocks.indexOf(block)\n if (idx !== -1) blocks.splice(idx, 1)\n if (currentLifetime) {\n disposeLifetime(currentLifetime)\n currentLifetime = null\n }\n })\n\n return [anchor, ...currentNodes]\n}\n"]}
1
+ {"version":3,"file":"branch.js","sourceRoot":"","sources":["../../src/primitives/branch.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BranchOptions, Lifetime } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { setFlatBindings } from '../binding.js'\nimport { createView } from '../view-helpers.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport { pushMountQueue, popMountQueue, flushMountQueue } from './on-mount.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport function branch<S, M = unknown, K extends string = string>(\n opts: BranchOptions<S, M, K>,\n): Node[] {\n const ctx = getRenderContext('branch')\n const parentLifetime = ctx.rootLifetime\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const anchor = ctx.dom.createComment('branch')\n\n let currentKey = opts.on(ctx.state as S)\n let currentLifetime: Lifetime | null = null\n let currentNodes: Node[] = []\n\n const block: StructuralBlock = {\n mask: (opts as { __mask?: number }).__mask ?? FULL_MASK,\n reconcile(state: unknown) {\n const newKey = opts.on(state as S)\n if (Object.is(newKey, currentKey)) return\n\n const parent = anchor.parentNode\n if (!parent) return\n\n const leavingNodes = currentNodes\n const leavingLifetime = currentLifetime\n\n // Build new arm first (before removing old — for FLIP animations)\n currentNodes = []\n currentLifetime = null\n currentKey = newKey\n\n const newCaseKey = String(newKey) as K\n const newBuilder = opts.cases?.[newCaseKey] ?? opts.default\n // Collect onMount callbacks from the new case into a local queue,\n // then flush them SYNCHRONOUSLY after the new nodes are inserted.\n // Without this, onMount inside a branch case would see stale DOM\n // (nodes not yet attached) OR fall back to queueMicrotask and\n // race with synchronous event dispatches after the reconcile.\n let onMountQueue: Array<() => void> | null = null\n if (newBuilder) {\n const mq = pushMountQueue()\n onMountQueue = mq.queue\n currentLifetime = createLifetime(parentLifetime)\n currentLifetime._kind =\n opts.__disposalCause === 'show-hide'\n ? 'show'\n : opts.__disposalCause === 'scope-rebuild'\n ? 'scope'\n : 'branch'\n setFlatBindings(ctx.allBindings)\n setRenderContext({ ...ctx, rootLifetime: currentLifetime, state })\n currentNodes = newBuilder(createView<S, M>(send))\n clearRenderContext()\n setFlatBindings(null)\n popMountQueue(mq.prev)\n\n const ref = anchor.nextSibling\n for (const node of currentNodes) {\n parent.insertBefore(node, ref)\n }\n }\n if (onMountQueue) flushMountQueue(onMountQueue)\n\n // Fire enter for new nodes\n if (opts.enter && currentNodes.length > 0) {\n opts.enter(currentNodes)\n }\n\n // Handle leave — may be deferred via Promise\n const removeOld = () => {\n for (const node of leavingNodes) {\n if (node.parentNode) node.parentNode.removeChild(node)\n }\n if (leavingLifetime) {\n // Tag BEFORE dispose so the disposer log records the cause.\n // `show()` passes `__disposalCause: 'show-hide'`; raw branch()\n // defaults to `'branch-swap'`. Tag wins over any pre-existing\n // value set by an inner primitive so the outermost cause is\n // reported (matches how humans describe the event).\n leavingLifetime.disposalCause = opts.__disposalCause ?? 'branch-swap'\n disposeLifetime(leavingLifetime)\n }\n }\n\n if (leavingNodes.length > 0 && opts.leave) {\n const result = opts.leave(leavingNodes)\n if (result && typeof (result as Promise<void>).then === 'function') {\n ;(result as Promise<void>).then(removeOld)\n } else {\n removeOld()\n }\n } else {\n removeOld()\n }\n },\n }\n\n // Register the block BEFORE running the initial builder so that parent\n // blocks always precede their nested children in the flat blocks array.\n // This guarantees correct Phase 1 iteration order: parents reconcile\n // first, so a parent that unmounts its old arm can dispose nested child\n // blocks (splicing them out of this array) without corrupting the loop\n // index — the splice only affects entries to the RIGHT of the parent.\n blocks.push(block)\n\n const caseKey = String(currentKey) as K\n const builder = opts.cases?.[caseKey] ?? opts.default\n // Initial-mount onMount callbacks are handled by the outer mountApp\n // queue — we're still inside the first view() call. branch doesn't\n // insert into the DOM at this point (the anchor + initial children\n // are returned to the parent), so we don't need to flush here.\n if (builder) {\n currentLifetime = createLifetime(parentLifetime)\n currentLifetime._kind =\n opts.__disposalCause === 'show-hide'\n ? 'show'\n : opts.__disposalCause === 'scope-rebuild'\n ? 'scope'\n : 'branch'\n setRenderContext({ ...ctx, rootLifetime: currentLifetime })\n currentNodes = builder(createView<S, M>(send))\n clearRenderContext()\n setRenderContext(ctx)\n\n // Fire enter on initial mount\n if (opts.enter && currentNodes.length > 0) {\n opts.enter(currentNodes)\n }\n }\n\n addDisposer(parentLifetime, () => {\n const idx = blocks.indexOf(block)\n if (idx !== -1) blocks.splice(idx, 1)\n if (currentLifetime) {\n disposeLifetime(currentLifetime)\n currentLifetime = null\n }\n })\n\n return [anchor, ...currentNodes]\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"child.d.ts","sourceRoot":"","sources":["../../src/primitives/child.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,aAAa,CAAA;AAU7D,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,IAAI,EAAE,CAsGtE"}
1
+ {"version":3,"file":"child.d.ts","sourceRoot":"","sources":["../../src/primitives/child.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,aAAa,CAAA;AAU7D,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,IAAI,EAAE,CAyGtE"}
@@ -25,7 +25,10 @@ export function child(opts) {
25
25
  const parentSend = parentCtx.send;
26
26
  const childDef = opts.def;
27
27
  const initialProps = opts.props(parentCtx.state);
28
- const childInst = createComponentInstance(childDef, initialProps);
28
+ // Child component inherits the parent's DOM env — render-context
29
+ // threading means the same env flows from mountApp to child() to any
30
+ // nested primitives inside the child's view.
31
+ const childInst = createComponentInstance(childDef, initialProps, null, parentCtx.dom);
29
32
  // Wrap child's send to intercept messages for onMsg → parent
30
33
  const originalSend = childInst.send;
31
34
  childInst.send = (msg) => {
@@ -71,7 +74,7 @@ export function child(opts) {
71
74
  prevProps = { ...newProps };
72
75
  }),
73
76
  kind: 'effect',
74
- node: document.createComment('child:' + opts.key),
77
+ node: parentCtx.dom.createComment('child:' + opts.key),
75
78
  perItem: false,
76
79
  });
77
80
  // Run the child's view within the child's render context
@@ -1 +1 @@
1
- {"version":3,"file":"child.js","sourceRoot":"","sources":["../../src/primitives/child.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChildOptions, ComponentDef } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { createComponentInstance, flushInstance, type ComponentInstance } from '../update-loop.js'\nimport { createBinding, setFlatBindings } from '../binding.js'\nimport { registerChild, unregisterChild } from '../addressed.js'\nimport { createView } from '../view-helpers.js'\n\nconst FULL_MASK = 0xffffffff\n\nexport function child<S, ChildM>(opts: ChildOptions<S, ChildM>): Node[] {\n // Dev-mode guard: props must be a function, not a static object\n if (typeof opts.props !== 'function') {\n throw new Error(\n `child(\"${String(opts.key)}\"): props must be a reactive accessor function ` +\n `(s => ({ ... })), not a static object. Static props are captured once at mount ` +\n `and never update.`,\n )\n }\n\n const parentCtx = getRenderContext('child')\n const parentLifetime = parentCtx.rootLifetime\n const childLifetime = createLifetime(parentLifetime)\n childLifetime._kind = 'child'\n // Tag eagerly: childLifetime lives as long as the child component is mounted,\n // so disposing it IS the child-unmount event. Setting the cause up front\n // (instead of inside the disposer closure below) ensures the parent's\n // _disposerLog sees it when `disposeLifetime` walks up the parent chain —\n // `childInst.rootLifetime` is an orphan (parent = null) and cannot emit.\n childLifetime.disposalCause = 'child-unmount'\n const parentSend = parentCtx.send\n\n const childDef = opts.def as ComponentDef<unknown, ChildM, unknown, Record<string, unknown>>\n const initialProps = opts.props(parentCtx.state as S)\n const childInst = createComponentInstance(childDef, initialProps)\n\n // Wrap child's send to intercept messages for onMsg → parent\n const originalSend = childInst.send\n childInst.send = (msg: ChildM) => {\n originalSend(msg)\n if (opts.onMsg && parentSend) {\n // Defer to after the child processes — use microtask\n queueMicrotask(() => {\n const parentMsg = opts.onMsg!(msg)\n if (parentMsg != null) {\n parentSend(parentMsg)\n }\n })\n }\n }\n\n // Track props for shallow-diff\n let prevProps: Record<string, unknown> = { ...initialProps }\n\n // Register a binding on the child scope that watches parent props changes.\n // This is a side-effect-only (`kind: 'effect'`) binding: Phase 2 runs the\n // accessor purely to fire the diff + propsMsg dispatch below. There is no\n // DOM output — the comment node is a detached anchor kept only so the\n // Binding shape stays uniform with other kinds.\n createBinding(childLifetime, {\n mask: FULL_MASK,\n accessor: ((parentState: S) => {\n const newProps = opts.props(parentState)\n\n let changed = false\n for (const key of Object.keys(newProps)) {\n if (!Object.is(newProps[key], prevProps[key])) {\n changed = true\n break\n }\n }\n\n if (changed && childDef.propsMsg) {\n const msg = childDef.propsMsg(newProps)\n // Dispatch via `originalSend` so framework-synthesized propsMsg\n // traffic bypasses the `onMsg` wrapper below. Otherwise a naive\n // `onMsg: m => forward(m)` echoes props/set back to the parent,\n // which mutates parent state, re-fires this accessor, and loops\n // forever.\n originalSend(msg)\n flushInstance(childInst)\n }\n prevProps = { ...newProps }\n }) as (state: never) => unknown,\n kind: 'effect',\n node: document.createComment('child:' + opts.key),\n perItem: false,\n })\n\n // Run the child's view within the child's 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 = childDef.view(createView(childInst.send))\n clearRenderContext()\n setFlatBindings(parentCtx.allBindings)\n setRenderContext(parentCtx)\n\n // Register in component registry for addressed effects\n registerChild(opts.key, { send: childInst.send as (msg: unknown) => void })\n\n // Cleanup: dispose child instance when parent scope disposes\n addDisposer(childLifetime, () => {\n unregisterChild(opts.key)\n childInst.rootLifetime.disposalCause = 'child-unmount'\n disposeLifetime(childInst.rootLifetime)\n })\n\n return nodes\n}\n"]}
1
+ {"version":3,"file":"child.js","sourceRoot":"","sources":["../../src/primitives/child.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChildOptions, ComponentDef } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { createComponentInstance, flushInstance, type ComponentInstance } from '../update-loop.js'\nimport { createBinding, setFlatBindings } from '../binding.js'\nimport { registerChild, unregisterChild } from '../addressed.js'\nimport { createView } from '../view-helpers.js'\n\nconst FULL_MASK = 0xffffffff\n\nexport function child<S, ChildM>(opts: ChildOptions<S, ChildM>): Node[] {\n // Dev-mode guard: props must be a function, not a static object\n if (typeof opts.props !== 'function') {\n throw new Error(\n `child(\"${String(opts.key)}\"): props must be a reactive accessor function ` +\n `(s => ({ ... })), not a static object. Static props are captured once at mount ` +\n `and never update.`,\n )\n }\n\n const parentCtx = getRenderContext('child')\n const parentLifetime = parentCtx.rootLifetime\n const childLifetime = createLifetime(parentLifetime)\n childLifetime._kind = 'child'\n // Tag eagerly: childLifetime lives as long as the child component is mounted,\n // so disposing it IS the child-unmount event. Setting the cause up front\n // (instead of inside the disposer closure below) ensures the parent's\n // _disposerLog sees it when `disposeLifetime` walks up the parent chain —\n // `childInst.rootLifetime` is an orphan (parent = null) and cannot emit.\n childLifetime.disposalCause = 'child-unmount'\n const parentSend = parentCtx.send\n\n const childDef = opts.def as ComponentDef<unknown, ChildM, unknown, Record<string, unknown>>\n const initialProps = opts.props(parentCtx.state as S)\n // Child component inherits the parent's DOM env — render-context\n // threading means the same env flows from mountApp to child() to any\n // nested primitives inside the child's view.\n const childInst = createComponentInstance(childDef, initialProps, null, parentCtx.dom)\n\n // Wrap child's send to intercept messages for onMsg → parent\n const originalSend = childInst.send\n childInst.send = (msg: ChildM) => {\n originalSend(msg)\n if (opts.onMsg && parentSend) {\n // Defer to after the child processes — use microtask\n queueMicrotask(() => {\n const parentMsg = opts.onMsg!(msg)\n if (parentMsg != null) {\n parentSend(parentMsg)\n }\n })\n }\n }\n\n // Track props for shallow-diff\n let prevProps: Record<string, unknown> = { ...initialProps }\n\n // Register a binding on the child scope that watches parent props changes.\n // This is a side-effect-only (`kind: 'effect'`) binding: Phase 2 runs the\n // accessor purely to fire the diff + propsMsg dispatch below. There is no\n // DOM output — the comment node is a detached anchor kept only so the\n // Binding shape stays uniform with other kinds.\n createBinding(childLifetime, {\n mask: FULL_MASK,\n accessor: ((parentState: S) => {\n const newProps = opts.props(parentState)\n\n let changed = false\n for (const key of Object.keys(newProps)) {\n if (!Object.is(newProps[key], prevProps[key])) {\n changed = true\n break\n }\n }\n\n if (changed && childDef.propsMsg) {\n const msg = childDef.propsMsg(newProps)\n // Dispatch via `originalSend` so framework-synthesized propsMsg\n // traffic bypasses the `onMsg` wrapper below. Otherwise a naive\n // `onMsg: m => forward(m)` echoes props/set back to the parent,\n // which mutates parent state, re-fires this accessor, and loops\n // forever.\n originalSend(msg)\n flushInstance(childInst)\n }\n prevProps = { ...newProps }\n }) as (state: never) => unknown,\n kind: 'effect',\n node: parentCtx.dom.createComment('child:' + opts.key),\n perItem: false,\n })\n\n // Run the child's view within the child's 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 = childDef.view(createView(childInst.send))\n clearRenderContext()\n setFlatBindings(parentCtx.allBindings)\n setRenderContext(parentCtx)\n\n // Register in component registry for addressed effects\n registerChild(opts.key, { send: childInst.send as (msg: unknown) => void })\n\n // Cleanup: dispose child instance when parent scope disposes\n addDisposer(childLifetime, () => {\n unregisterChild(opts.key)\n childInst.rootLifetime.disposalCause = 'child-unmount'\n disposeLifetime(childInst.rootLifetime)\n })\n\n return nodes\n}\n"]}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * `clientOnly()` — a view primitive that marks a subtree as browser-only.
3
+ *
4
+ * Problem it solves: some widgets can't render on the server. Leaflet,
5
+ * Chart.js, Monaco, and most imperative browser libraries touch `window`
6
+ * or `document` at construction time. Wrapping them in `clientOnly`
7
+ * means SSR never invokes the render callback — the server emits a
8
+ * placeholder (optionally backed by a user-supplied fallback subtree)
9
+ * and the real render happens at client mount / hydrate time.
10
+ *
11
+ * How it works:
12
+ *
13
+ * - SSR (env without `isBrowser`): emits
14
+ * `<!--llui-client-only-start-->` + fallback nodes (if any) +
15
+ * `<!--llui-client-only-end-->`. The `render` callback is never
16
+ * invoked.
17
+ * - Client mount / hydrate (env with `isBrowser: true`): runs `render`
18
+ * inline. Because LLui's hydration is an atomic swap — the client
19
+ * builds fresh DOM and `container.replaceChildren` wipes the server
20
+ * HTML — no anchor walking or fallback disposal is needed on the
21
+ * client side. The client simply produces its DOM; the SSR output
22
+ * is discarded by `replaceChildren`.
23
+ *
24
+ * State threading: `render` and `fallback` both receive a `View<S, M>`
25
+ * bag keyed to the host component's state, so inner `text`, `branch`,
26
+ * `each`, etc. behave the same as if the primitive weren't there.
27
+ *
28
+ * Gating heavy imports: if the library itself can't be imported on the
29
+ * server, put the import INSIDE `render` via dynamic `import()`:
30
+ *
31
+ * ...clientOnly({
32
+ * fallback: () => [div({ class: 'skeleton' })],
33
+ * render: () => [foreign({
34
+ * create: async (el) => {
35
+ * const L = await import('leaflet')
36
+ * return L.map(el).setView([0, 0], 13)
37
+ * },
38
+ * // ...
39
+ * })],
40
+ * })
41
+ *
42
+ * The bundler sees `import('leaflet')` only from the client-reachable
43
+ * code path; the SSR bundle elides it.
44
+ */
45
+ import type { ComponentDef } from '../types.js';
46
+ import { type View } from '../view-helpers.js';
47
+ export interface ClientOnlyOptions<S, M> {
48
+ /**
49
+ * Browser-only render callback. Invoked on client mount and hydrate.
50
+ * NEVER invoked during SSR (`renderToString` / `renderNodes`). Free
51
+ * to touch `window`, `document`, or import browser-only modules.
52
+ */
53
+ render: (bag: View<S, M>) => Node[];
54
+ /**
55
+ * Server-rendered stand-in. When present, SSR runs `fallback` and
56
+ * serializes its output into the HTML between the anchor comments.
57
+ * Useful for skeleton/shimmer loaders and to preserve layout width
58
+ * before the client widget mounts.
59
+ *
60
+ * Omit to emit only the anchor pair — zero layout impact until the
61
+ * client mounts, which may cause content shift.
62
+ *
63
+ * Both `render` and `fallback` receive the same `View<S, M>` bag;
64
+ * state-dependent fallback bindings are evaluated once at SSR time.
65
+ */
66
+ fallback?: (bag: View<S, M>) => Node[];
67
+ }
68
+ /**
69
+ * Mark a view subtree as browser-only. See module doc comment for the
70
+ * full semantics.
71
+ *
72
+ * Returns `Node[]` — spread into a parent element's children array.
73
+ *
74
+ * ```ts
75
+ * view: () => [
76
+ * div({ class: 'dashboard' }, [
77
+ * ...clientOnly({
78
+ * fallback: () => [div({ class: 'chart-skeleton' })],
79
+ * render: () => [foreign({ create: (el) => new Chart(el, cfg) })],
80
+ * }),
81
+ * ]),
82
+ * ]
83
+ * ```
84
+ */
85
+ export declare function clientOnly<S, M = unknown>(opts: ClientOnlyOptions<S, M>): Node[];
86
+ /**
87
+ * Generated component stub used by `@llui/vite-plugin`'s `'use client'`
88
+ * directive. For SSR builds, every `export const X = component({...})`
89
+ * in a `'use client'` module is rewritten to
90
+ * `export const X = __clientOnlyStub('X')` — the module's real imports
91
+ * and top-level side effects never run under SSR.
92
+ *
93
+ * The stub is a minimal valid `ComponentDef` whose `view()` emits a
94
+ * `clientOnly` placeholder. The client build imports the ORIGINAL
95
+ * module (directive is a no-op on client), so the real component mounts
96
+ * on hydrate; atomic-swap wipes the SSR stub's empty placeholder.
97
+ *
98
+ * State shape is `{}` and message type is `never` — the stub doesn't
99
+ * participate in any real update cycle. Callers that tried to `send()`
100
+ * messages against the stub during SSR would be dispatching into the
101
+ * void, which is fine: SSR doesn't process messages.
102
+ *
103
+ * App authors should not call this directly — reach for `clientOnly`
104
+ * or the `'use client'` directive depending on the granularity you
105
+ * want.
106
+ */
107
+ export declare function __clientOnlyStub(name: string): ComponentDef<object, never, never>;
108
+ //# sourceMappingURL=client-only.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-only.d.ts","sourceRoot":"","sources":["../../src/primitives/client-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAE1D,MAAM,WAAW,iBAAiB,CAAC,CAAC,EAAE,CAAC;IACrC;;;;OAIG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAA;IAEnC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAA;CACvC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAkBhF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAOjF"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * `clientOnly()` — a view primitive that marks a subtree as browser-only.
3
+ *
4
+ * Problem it solves: some widgets can't render on the server. Leaflet,
5
+ * Chart.js, Monaco, and most imperative browser libraries touch `window`
6
+ * or `document` at construction time. Wrapping them in `clientOnly`
7
+ * means SSR never invokes the render callback — the server emits a
8
+ * placeholder (optionally backed by a user-supplied fallback subtree)
9
+ * and the real render happens at client mount / hydrate time.
10
+ *
11
+ * How it works:
12
+ *
13
+ * - SSR (env without `isBrowser`): emits
14
+ * `<!--llui-client-only-start-->` + fallback nodes (if any) +
15
+ * `<!--llui-client-only-end-->`. The `render` callback is never
16
+ * invoked.
17
+ * - Client mount / hydrate (env with `isBrowser: true`): runs `render`
18
+ * inline. Because LLui's hydration is an atomic swap — the client
19
+ * builds fresh DOM and `container.replaceChildren` wipes the server
20
+ * HTML — no anchor walking or fallback disposal is needed on the
21
+ * client side. The client simply produces its DOM; the SSR output
22
+ * is discarded by `replaceChildren`.
23
+ *
24
+ * State threading: `render` and `fallback` both receive a `View<S, M>`
25
+ * bag keyed to the host component's state, so inner `text`, `branch`,
26
+ * `each`, etc. behave the same as if the primitive weren't there.
27
+ *
28
+ * Gating heavy imports: if the library itself can't be imported on the
29
+ * server, put the import INSIDE `render` via dynamic `import()`:
30
+ *
31
+ * ...clientOnly({
32
+ * fallback: () => [div({ class: 'skeleton' })],
33
+ * render: () => [foreign({
34
+ * create: async (el) => {
35
+ * const L = await import('leaflet')
36
+ * return L.map(el).setView([0, 0], 13)
37
+ * },
38
+ * // ...
39
+ * })],
40
+ * })
41
+ *
42
+ * The bundler sees `import('leaflet')` only from the client-reachable
43
+ * code path; the SSR bundle elides it.
44
+ */
45
+ import { getRenderContext } from '../render-context.js';
46
+ import { createView } from '../view-helpers.js';
47
+ /**
48
+ * Mark a view subtree as browser-only. See module doc comment for the
49
+ * full semantics.
50
+ *
51
+ * Returns `Node[]` — spread into a parent element's children array.
52
+ *
53
+ * ```ts
54
+ * view: () => [
55
+ * div({ class: 'dashboard' }, [
56
+ * ...clientOnly({
57
+ * fallback: () => [div({ class: 'chart-skeleton' })],
58
+ * render: () => [foreign({ create: (el) => new Chart(el, cfg) })],
59
+ * }),
60
+ * ]),
61
+ * ]
62
+ * ```
63
+ */
64
+ export function clientOnly(opts) {
65
+ const ctx = getRenderContext('clientOnly');
66
+ const send = ctx.send;
67
+ const bag = createView(send);
68
+ // `ctx.dom.isBrowser` is the discriminator — it's true only when the
69
+ // env wraps the live browser globals (see `browserEnv()`). SSR envs
70
+ // (`jsdomEnv`, `linkedomEnv`, custom adapters) don't set it. This
71
+ // check lets `clientOnly` choose behavior without the primitive
72
+ // needing to know about `renderToString` vs `mountApp` call sites.
73
+ if (ctx.dom.isBrowser) {
74
+ return opts.render(bag);
75
+ }
76
+ const start = ctx.dom.createComment('llui-client-only-start');
77
+ const end = ctx.dom.createComment('llui-client-only-end');
78
+ const fallbackNodes = opts.fallback ? opts.fallback(bag) : [];
79
+ return [start, ...fallbackNodes, end];
80
+ }
81
+ /**
82
+ * Generated component stub used by `@llui/vite-plugin`'s `'use client'`
83
+ * directive. For SSR builds, every `export const X = component({...})`
84
+ * in a `'use client'` module is rewritten to
85
+ * `export const X = __clientOnlyStub('X')` — the module's real imports
86
+ * and top-level side effects never run under SSR.
87
+ *
88
+ * The stub is a minimal valid `ComponentDef` whose `view()` emits a
89
+ * `clientOnly` placeholder. The client build imports the ORIGINAL
90
+ * module (directive is a no-op on client), so the real component mounts
91
+ * on hydrate; atomic-swap wipes the SSR stub's empty placeholder.
92
+ *
93
+ * State shape is `{}` and message type is `never` — the stub doesn't
94
+ * participate in any real update cycle. Callers that tried to `send()`
95
+ * messages against the stub during SSR would be dispatching into the
96
+ * void, which is fine: SSR doesn't process messages.
97
+ *
98
+ * App authors should not call this directly — reach for `clientOnly`
99
+ * or the `'use client'` directive depending on the granularity you
100
+ * want.
101
+ */
102
+ export function __clientOnlyStub(name) {
103
+ return {
104
+ name,
105
+ init: () => [{}, []],
106
+ update: (s) => [s, []],
107
+ view: () => clientOnly({ render: () => [] }),
108
+ };
109
+ }
110
+ //# sourceMappingURL=client-only.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-only.js","sourceRoot":"","sources":["../../src/primitives/client-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAa,MAAM,oBAAoB,CAAA;AAyB1D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,UAAU,CAAiB,IAA6B;IACtE,MAAM,GAAG,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAA;IAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAwB,CAAA;IACzC,MAAM,GAAG,GAAG,UAAU,CAAO,IAAI,CAAC,CAAA;IAElC,qEAAqE;IACrE,oEAAoE;IACpE,kEAAkE;IAClE,gEAAgE;IAChE,mEAAmE;IACnE,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACzB,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,wBAAwB,CAAC,CAAA;IAC7D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAA;IACzD,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7D,OAAO,CAAC,KAAK,EAAE,GAAG,aAAa,EAAE,GAAG,CAAC,CAAA;AACvC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO;QACL,IAAI;QACJ,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;QACpB,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACtB,IAAI,EAAE,GAAG,EAAE,CAAC,UAAU,CAAgB,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;KAC5D,CAAA;AACH,CAAC","sourcesContent":["/**\n * `clientOnly()` — a view primitive that marks a subtree as browser-only.\n *\n * Problem it solves: some widgets can't render on the server. Leaflet,\n * Chart.js, Monaco, and most imperative browser libraries touch `window`\n * or `document` at construction time. Wrapping them in `clientOnly`\n * means SSR never invokes the render callback — the server emits a\n * placeholder (optionally backed by a user-supplied fallback subtree)\n * and the real render happens at client mount / hydrate time.\n *\n * How it works:\n *\n * - SSR (env without `isBrowser`): emits\n * `<!--llui-client-only-start-->` + fallback nodes (if any) +\n * `<!--llui-client-only-end-->`. The `render` callback is never\n * invoked.\n * - Client mount / hydrate (env with `isBrowser: true`): runs `render`\n * inline. Because LLui's hydration is an atomic swap — the client\n * builds fresh DOM and `container.replaceChildren` wipes the server\n * HTML — no anchor walking or fallback disposal is needed on the\n * client side. The client simply produces its DOM; the SSR output\n * is discarded by `replaceChildren`.\n *\n * State threading: `render` and `fallback` both receive a `View<S, M>`\n * bag keyed to the host component's state, so inner `text`, `branch`,\n * `each`, etc. behave the same as if the primitive weren't there.\n *\n * Gating heavy imports: if the library itself can't be imported on the\n * server, put the import INSIDE `render` via dynamic `import()`:\n *\n * ...clientOnly({\n * fallback: () => [div({ class: 'skeleton' })],\n * render: () => [foreign({\n * create: async (el) => {\n * const L = await import('leaflet')\n * return L.map(el).setView([0, 0], 13)\n * },\n * // ...\n * })],\n * })\n *\n * The bundler sees `import('leaflet')` only from the client-reachable\n * code path; the SSR bundle elides it.\n */\n\nimport type { ComponentDef } from '../types.js'\nimport { getRenderContext } from '../render-context.js'\nimport { createView, type View } from '../view-helpers.js'\n\nexport interface ClientOnlyOptions<S, M> {\n /**\n * Browser-only render callback. Invoked on client mount and hydrate.\n * NEVER invoked during SSR (`renderToString` / `renderNodes`). Free\n * to touch `window`, `document`, or import browser-only modules.\n */\n render: (bag: View<S, M>) => Node[]\n\n /**\n * Server-rendered stand-in. When present, SSR runs `fallback` and\n * serializes its output into the HTML between the anchor comments.\n * Useful for skeleton/shimmer loaders and to preserve layout width\n * before the client widget mounts.\n *\n * Omit to emit only the anchor pair — zero layout impact until the\n * client mounts, which may cause content shift.\n *\n * Both `render` and `fallback` receive the same `View<S, M>` bag;\n * state-dependent fallback bindings are evaluated once at SSR time.\n */\n fallback?: (bag: View<S, M>) => Node[]\n}\n\n/**\n * Mark a view subtree as browser-only. See module doc comment for the\n * full semantics.\n *\n * Returns `Node[]` — spread into a parent element's children array.\n *\n * ```ts\n * view: () => [\n * div({ class: 'dashboard' }, [\n * ...clientOnly({\n * fallback: () => [div({ class: 'chart-skeleton' })],\n * render: () => [foreign({ create: (el) => new Chart(el, cfg) })],\n * }),\n * ]),\n * ]\n * ```\n */\nexport function clientOnly<S, M = unknown>(opts: ClientOnlyOptions<S, M>): Node[] {\n const ctx = getRenderContext('clientOnly')\n const send = ctx.send as (msg: M) => void\n const bag = createView<S, M>(send)\n\n // `ctx.dom.isBrowser` is the discriminator — it's true only when the\n // env wraps the live browser globals (see `browserEnv()`). SSR envs\n // (`jsdomEnv`, `linkedomEnv`, custom adapters) don't set it. This\n // check lets `clientOnly` choose behavior without the primitive\n // needing to know about `renderToString` vs `mountApp` call sites.\n if (ctx.dom.isBrowser) {\n return opts.render(bag)\n }\n\n const start = ctx.dom.createComment('llui-client-only-start')\n const end = ctx.dom.createComment('llui-client-only-end')\n const fallbackNodes = opts.fallback ? opts.fallback(bag) : []\n return [start, ...fallbackNodes, end]\n}\n\n/**\n * Generated component stub used by `@llui/vite-plugin`'s `'use client'`\n * directive. For SSR builds, every `export const X = component({...})`\n * in a `'use client'` module is rewritten to\n * `export const X = __clientOnlyStub('X')` — the module's real imports\n * and top-level side effects never run under SSR.\n *\n * The stub is a minimal valid `ComponentDef` whose `view()` emits a\n * `clientOnly` placeholder. The client build imports the ORIGINAL\n * module (directive is a no-op on client), so the real component mounts\n * on hydrate; atomic-swap wipes the SSR stub's empty placeholder.\n *\n * State shape is `{}` and message type is `never` — the stub doesn't\n * participate in any real update cycle. Callers that tried to `send()`\n * messages against the stub during SSR would be dispatching into the\n * void, which is fine: SSR doesn't process messages.\n *\n * App authors should not call this directly — reach for `clientOnly`\n * or the `'use client'` directive depending on the granularity you\n * want.\n */\nexport function __clientOnlyStub(name: string): ComponentDef<object, never, never> {\n return {\n name,\n init: () => [{}, []],\n update: (s) => [s, []],\n view: () => clientOnly<object, never>({ render: () => [] }),\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/primitives/each.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA0B,MAAM,aAAa,CAAA;AAuBtE,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAEpD;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI,CAEzE;AAgCD,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAmR1E"}
1
+ {"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/primitives/each.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA0B,MAAM,aAAa,CAAA;AAwBtE,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAEpD;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI,CAEzE;AAoCD,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAmR1E"}
@@ -2,6 +2,7 @@ import { getRenderContext, setRenderContext, clearRenderContext, } from '../rend
2
2
  import { createLifetime, disposeLifetime, disposeLifetimesBulk, addDisposer, removeOrphanedChildren, } from '../lifetime.js';
3
3
  import { getFlatBindings, setFlatBindings } from '../binding.js';
4
4
  import { FULL_MASK } from '../update-loop.js';
5
+ import { createView } from '../view-helpers.js';
5
6
  // Clear callbacks — registered by selector.bind() during render, called by reconcileClear().
6
7
  // Eliminates per-row disposers (1000 Set.delete calls → 1 registry.clear() call).
7
8
  let activeClearCallbacks = null;
@@ -16,12 +17,16 @@ export function registerOnRemove(cb) {
16
17
  if (activeRemoveCallbacks)
17
18
  activeRemoveCallbacks.push(cb);
18
19
  }
19
- // Reusable render context for buildEntry — avoids object allocation per entry
20
+ // Reusable render context for buildEntry — avoids object allocation per entry.
21
+ // All fields except `rootLifetime`, `state`, `allBindings`, `structuralBlocks`, `dom`
22
+ // are copied from the surrounding render context per reconcile call, so the
23
+ // initial shape's null/empty values are never observed in practice.
20
24
  const buildCtx = {
21
25
  rootLifetime: null,
22
26
  state: null,
23
27
  allBindings: [],
24
28
  structuralBlocks: [],
29
+ dom: null,
25
30
  };
26
31
  // Reusable render bag — mutated per entry instead of allocating new objects
27
32
  const buildBag = {
@@ -37,7 +42,7 @@ export function each(opts) {
37
42
  const ctx = getRenderContext('each');
38
43
  const parentLifetime = ctx.rootLifetime;
39
44
  const blocks = ctx.structuralBlocks;
40
- const anchor = document.createComment('each');
45
+ const anchor = ctx.dom.createComment('each');
41
46
  const entries = [];
42
47
  const clearCallbacks = [];
43
48
  const removeCallbacks = [];
@@ -145,7 +150,7 @@ export function each(opts) {
145
150
  for (let i = 0; i < clearCallbacks.length; i++)
146
151
  clearCallbacks[i]();
147
152
  // Bulk DOM removal
148
- const range = document.createRange();
153
+ const range = ctx.dom.createRange();
149
154
  range.setStartAfter(anchor);
150
155
  const lastEntry = entries[entries.length - 1];
151
156
  const lastNode = lastEntry.nodes[lastEntry.nodes.length - 1];
@@ -377,6 +382,7 @@ function buildEntry(item, index, opts, parentLifetime, ctx, state) {
377
382
  buildCtx.state = currentState;
378
383
  buildCtx.allBindings = ctx.allBindings;
379
384
  buildCtx.structuralBlocks = ctx.structuralBlocks;
385
+ buildCtx.dom = ctx.dom;
380
386
  buildCtx.instance = ctx.instance;
381
387
  const prevFlatBindings = getFlatBindings();
382
388
  setFlatBindings(ctx.allBindings);
@@ -388,6 +394,10 @@ function buildEntry(item, index, opts, parentLifetime, ctx, state) {
388
394
  buildBag.index = indexAccessor;
389
395
  buildBag._getItemProxy = getItemProxy;
390
396
  buildBag.entry = entry;
397
+ // The View bag — lets each.render use `h.text`, `h.scope`, `h.sample`,
398
+ // etc. without reaching for the top-level imports. Each entry gets a
399
+ // fresh View so its `send` is bound to this row's dispatch path.
400
+ buildBag.h = createView(send);
391
401
  // Row factory: pass compiler-injected template + update function through to render
392
402
  const rfOpts = opts;
393
403
  if (rfOpts.__tpl)
@@ -431,7 +441,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
431
441
  }
432
442
  // Remove all DOM nodes in one operation using Range
433
443
  if (entries.length > 0) {
434
- const range = document.createRange();
444
+ const range = ctx.dom.createRange();
435
445
  range.setStartAfter(anchor);
436
446
  const lastEntry = entries[entries.length - 1];
437
447
  const lastNode = lastEntry.nodes[lastEntry.nodes.length - 1];
@@ -460,7 +470,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
460
470
  const ref = lastEntry
461
471
  ? lastEntry.nodes[lastEntry.nodes.length - 1].nextSibling
462
472
  : anchor.nextSibling;
463
- const frag = document.createDocumentFragment();
473
+ const frag = ctx.dom.createDocumentFragment();
464
474
  const newlyAdded = [];
465
475
  for (let i = oldLen; i < newLen; i++) {
466
476
  const entry = buildEntry(newItems[i], i, opts, parentLifetime, ctx, state);
@@ -546,7 +556,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
546
556
  collectNodes(report.leaving, entry.nodes);
547
557
  }
548
558
  // Bulk DOM removal using Range
549
- const range = document.createRange();
559
+ const range = ctx.dom.createRange();
550
560
  range.setStartAfter(anchor);
551
561
  const lastEntry = entries[entries.length - 1];
552
562
  range.setEndAfter(lastEntry.nodes[lastEntry.nodes.length - 1]);
@@ -562,7 +572,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
562
572
  removeOrphanedChildren(parentLifetime);
563
573
  entries.length = 0;
564
574
  // Build all new entries into a fragment
565
- const frag = document.createDocumentFragment();
575
+ const frag = ctx.dom.createDocumentFragment();
566
576
  const newlyAdded = [];
567
577
  for (let i = 0; i < newLen; i++) {
568
578
  const entry = buildEntry(newItems[i], i, opts, parentLifetime, ctx, state);
@@ -630,7 +640,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
630
640
  const hasSurvivors = newEntries.some((e) => oldByKey.has(e.key));
631
641
  if (!hasSurvivors || !survivorsInOrder(entries, newEntries, usedKeys)) {
632
642
  // Full fragment rebuild — one reflow
633
- const frag = document.createDocumentFragment();
643
+ const frag = ctx.dom.createDocumentFragment();
634
644
  for (const entry of newEntries) {
635
645
  for (const node of entry.nodes)
636
646
  frag.appendChild(node);
@@ -655,7 +665,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
655
665
  else {
656
666
  // Batch new entries into a fragment
657
667
  if (!frag)
658
- frag = document.createDocumentFragment();
668
+ frag = ctx.dom.createDocumentFragment();
659
669
  for (const node of entry.nodes)
660
670
  frag.appendChild(node);
661
671
  }