@llui/dom 0.0.23 → 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 +14 -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 +5 -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 +15 -1
  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;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;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"}
@@ -17,12 +17,16 @@ export function registerOnRemove(cb) {
17
17
  if (activeRemoveCallbacks)
18
18
  activeRemoveCallbacks.push(cb);
19
19
  }
20
- // 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.
21
24
  const buildCtx = {
22
25
  rootLifetime: null,
23
26
  state: null,
24
27
  allBindings: [],
25
28
  structuralBlocks: [],
29
+ dom: null,
26
30
  };
27
31
  // Reusable render bag — mutated per entry instead of allocating new objects
28
32
  const buildBag = {
@@ -38,7 +42,7 @@ export function each(opts) {
38
42
  const ctx = getRenderContext('each');
39
43
  const parentLifetime = ctx.rootLifetime;
40
44
  const blocks = ctx.structuralBlocks;
41
- const anchor = document.createComment('each');
45
+ const anchor = ctx.dom.createComment('each');
42
46
  const entries = [];
43
47
  const clearCallbacks = [];
44
48
  const removeCallbacks = [];
@@ -146,7 +150,7 @@ export function each(opts) {
146
150
  for (let i = 0; i < clearCallbacks.length; i++)
147
151
  clearCallbacks[i]();
148
152
  // Bulk DOM removal
149
- const range = document.createRange();
153
+ const range = ctx.dom.createRange();
150
154
  range.setStartAfter(anchor);
151
155
  const lastEntry = entries[entries.length - 1];
152
156
  const lastNode = lastEntry.nodes[lastEntry.nodes.length - 1];
@@ -378,6 +382,7 @@ function buildEntry(item, index, opts, parentLifetime, ctx, state) {
378
382
  buildCtx.state = currentState;
379
383
  buildCtx.allBindings = ctx.allBindings;
380
384
  buildCtx.structuralBlocks = ctx.structuralBlocks;
385
+ buildCtx.dom = ctx.dom;
381
386
  buildCtx.instance = ctx.instance;
382
387
  const prevFlatBindings = getFlatBindings();
383
388
  setFlatBindings(ctx.allBindings);
@@ -436,7 +441,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
436
441
  }
437
442
  // Remove all DOM nodes in one operation using Range
438
443
  if (entries.length > 0) {
439
- const range = document.createRange();
444
+ const range = ctx.dom.createRange();
440
445
  range.setStartAfter(anchor);
441
446
  const lastEntry = entries[entries.length - 1];
442
447
  const lastNode = lastEntry.nodes[lastEntry.nodes.length - 1];
@@ -465,7 +470,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
465
470
  const ref = lastEntry
466
471
  ? lastEntry.nodes[lastEntry.nodes.length - 1].nextSibling
467
472
  : anchor.nextSibling;
468
- const frag = document.createDocumentFragment();
473
+ const frag = ctx.dom.createDocumentFragment();
469
474
  const newlyAdded = [];
470
475
  for (let i = oldLen; i < newLen; i++) {
471
476
  const entry = buildEntry(newItems[i], i, opts, parentLifetime, ctx, state);
@@ -551,7 +556,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
551
556
  collectNodes(report.leaving, entry.nodes);
552
557
  }
553
558
  // Bulk DOM removal using Range
554
- const range = document.createRange();
559
+ const range = ctx.dom.createRange();
555
560
  range.setStartAfter(anchor);
556
561
  const lastEntry = entries[entries.length - 1];
557
562
  range.setEndAfter(lastEntry.nodes[lastEntry.nodes.length - 1]);
@@ -567,7 +572,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
567
572
  removeOrphanedChildren(parentLifetime);
568
573
  entries.length = 0;
569
574
  // Build all new entries into a fragment
570
- const frag = document.createDocumentFragment();
575
+ const frag = ctx.dom.createDocumentFragment();
571
576
  const newlyAdded = [];
572
577
  for (let i = 0; i < newLen; i++) {
573
578
  const entry = buildEntry(newItems[i], i, opts, parentLifetime, ctx, state);
@@ -635,7 +640,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
635
640
  const hasSurvivors = newEntries.some((e) => oldByKey.has(e.key));
636
641
  if (!hasSurvivors || !survivorsInOrder(entries, newEntries, usedKeys)) {
637
642
  // Full fragment rebuild — one reflow
638
- const frag = document.createDocumentFragment();
643
+ const frag = ctx.dom.createDocumentFragment();
639
644
  for (const entry of newEntries) {
640
645
  for (const node of entry.nodes)
641
646
  frag.appendChild(node);
@@ -660,7 +665,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
660
665
  else {
661
666
  // Batch new entries into a fragment
662
667
  if (!frag)
663
- frag = document.createDocumentFragment();
668
+ frag = ctx.dom.createDocumentFragment();
664
669
  for (const node of entry.nodes)
665
670
  frag.appendChild(node);
666
671
  }