@llui/dom 0.0.19 → 0.0.21

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 (109) hide show
  1. package/README.md +25 -21
  2. package/dist/addressed.d.ts +1 -1
  3. package/dist/addressed.d.ts.map +1 -1
  4. package/dist/addressed.js.map +1 -1
  5. package/dist/binding.d.ts +2 -2
  6. package/dist/binding.d.ts.map +1 -1
  7. package/dist/binding.js +2 -2
  8. package/dist/binding.js.map +1 -1
  9. package/dist/devtools.d.ts +3 -3
  10. package/dist/devtools.d.ts.map +1 -1
  11. package/dist/devtools.js +14 -12
  12. package/dist/devtools.js.map +1 -1
  13. package/dist/el-split.js +3 -3
  14. package/dist/el-split.js.map +1 -1
  15. package/dist/el-template.js +3 -3
  16. package/dist/el-template.js.map +1 -1
  17. package/dist/elements.js +1 -1
  18. package/dist/elements.js.map +1 -1
  19. package/dist/hmr.d.ts +2 -1
  20. package/dist/hmr.d.ts.map +1 -1
  21. package/dist/hmr.js +74 -34
  22. package/dist/hmr.js.map +1 -1
  23. package/dist/index.d.ts +7 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/internal.d.ts +1 -1
  28. package/dist/internal.d.ts.map +1 -1
  29. package/dist/internal.js +1 -1
  30. package/dist/internal.js.map +1 -1
  31. package/dist/lifetime.d.ts +39 -0
  32. package/dist/lifetime.d.ts.map +1 -0
  33. package/dist/lifetime.js +250 -0
  34. package/dist/lifetime.js.map +1 -0
  35. package/dist/mathml-elements.js +1 -1
  36. package/dist/mathml-elements.js.map +1 -1
  37. package/dist/mount.d.ts +41 -7
  38. package/dist/mount.d.ts.map +1 -1
  39. package/dist/mount.js +216 -9
  40. package/dist/mount.js.map +1 -1
  41. package/dist/primitives/branch.d.ts.map +1 -1
  42. package/dist/primitives/branch.js +30 -20
  43. package/dist/primitives/branch.js.map +1 -1
  44. package/dist/primitives/child.js +12 -12
  45. package/dist/primitives/child.js.map +1 -1
  46. package/dist/primitives/context.js +6 -6
  47. package/dist/primitives/context.js.map +1 -1
  48. package/dist/primitives/each.d.ts.map +1 -1
  49. package/dist/primitives/each.js +37 -32
  50. package/dist/primitives/each.js.map +1 -1
  51. package/dist/primitives/error-boundary.js +7 -7
  52. package/dist/primitives/error-boundary.js.map +1 -1
  53. package/dist/primitives/foreign.js +3 -3
  54. package/dist/primitives/foreign.js.map +1 -1
  55. package/dist/primitives/lazy.js +11 -11
  56. package/dist/primitives/lazy.js.map +1 -1
  57. package/dist/primitives/on-mount.js +2 -2
  58. package/dist/primitives/on-mount.js.map +1 -1
  59. package/dist/primitives/portal.js +4 -4
  60. package/dist/primitives/portal.js.map +1 -1
  61. package/dist/primitives/sample.d.ts +18 -0
  62. package/dist/primitives/sample.d.ts.map +1 -0
  63. package/dist/primitives/sample.js +22 -0
  64. package/dist/primitives/sample.js.map +1 -0
  65. package/dist/primitives/scope.d.ts +25 -0
  66. package/dist/primitives/scope.d.ts.map +1 -0
  67. package/dist/primitives/scope.js +36 -0
  68. package/dist/primitives/scope.js.map +1 -0
  69. package/dist/primitives/selector.js +4 -4
  70. package/dist/primitives/selector.js.map +1 -1
  71. package/dist/primitives/show.d.ts.map +1 -1
  72. package/dist/primitives/show.js +4 -1
  73. package/dist/primitives/show.js.map +1 -1
  74. package/dist/primitives/slice.d.ts.map +1 -1
  75. package/dist/primitives/slice.js +16 -1
  76. package/dist/primitives/slice.js.map +1 -1
  77. package/dist/primitives/text.js +3 -3
  78. package/dist/primitives/text.js.map +1 -1
  79. package/dist/primitives/unsafe-html.d.ts +28 -0
  80. package/dist/primitives/unsafe-html.d.ts.map +1 -0
  81. package/dist/primitives/unsafe-html.js +82 -0
  82. package/dist/primitives/unsafe-html.js.map +1 -0
  83. package/dist/primitives/virtual-each.d.ts.map +1 -1
  84. package/dist/primitives/virtual-each.js +9 -9
  85. package/dist/primitives/virtual-each.js.map +1 -1
  86. package/dist/render-context.d.ts +2 -2
  87. package/dist/render-context.d.ts.map +1 -1
  88. package/dist/render-context.js.map +1 -1
  89. package/dist/ssr.d.ts +4 -4
  90. package/dist/ssr.d.ts.map +1 -1
  91. package/dist/ssr.js +3 -3
  92. package/dist/ssr.js.map +1 -1
  93. package/dist/svg-elements.js +1 -1
  94. package/dist/svg-elements.js.map +1 -1
  95. package/dist/tracking/disposer-log.d.ts +4 -4
  96. package/dist/tracking/disposer-log.d.ts.map +1 -1
  97. package/dist/tracking/disposer-log.js.map +1 -1
  98. package/dist/types.d.ts +50 -9
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/types.js.map +1 -1
  101. package/dist/update-loop.d.ts +3 -3
  102. package/dist/update-loop.d.ts.map +1 -1
  103. package/dist/update-loop.js +12 -8
  104. package/dist/update-loop.js.map +1 -1
  105. package/dist/view-helpers.d.ts +15 -1
  106. package/dist/view-helpers.d.ts.map +1 -1
  107. package/dist/view-helpers.js +6 -0
  108. package/dist/view-helpers.js.map +1 -1
  109. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ import type { ScopeOptions } from '../types.js';
2
+ /**
3
+ * Rebuild a subtree when `on(state)` changes.
4
+ *
5
+ * The `render` callback runs once at mount and once again every time
6
+ * `on(state)` returns a new value (compared with `Object.is`). Each run
7
+ * gets a fresh `Lifetime`, so bindings, onMount callbacks, and any
8
+ * per-arm state are recreated cleanly. Use `sample()` inside `render`
9
+ * when you need to read a whole-state snapshot at rebuild time without
10
+ * creating a reactive binding.
11
+ *
12
+ * Sugar over `branch({ on, cases: {}, default: render, __disposalCause: 'scope-rebuild' })`.
13
+ *
14
+ * ```ts
15
+ * scope({
16
+ * on: (s) => String(s.chartEpoch),
17
+ * render: (h) => {
18
+ * const stats = sample<State, Stats>((s) => s.stats)
19
+ * return [chartView(h, stats)]
20
+ * },
21
+ * })
22
+ * ```
23
+ */
24
+ export declare function scope<S, M = unknown>(opts: ScopeOptions<S, M>): Node[];
25
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/primitives/scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAWtE"}
@@ -0,0 +1,36 @@
1
+ import { branch } from './branch.js';
2
+ /**
3
+ * Rebuild a subtree when `on(state)` changes.
4
+ *
5
+ * The `render` callback runs once at mount and once again every time
6
+ * `on(state)` returns a new value (compared with `Object.is`). Each run
7
+ * gets a fresh `Lifetime`, so bindings, onMount callbacks, and any
8
+ * per-arm state are recreated cleanly. Use `sample()` inside `render`
9
+ * when you need to read a whole-state snapshot at rebuild time without
10
+ * creating a reactive binding.
11
+ *
12
+ * Sugar over `branch({ on, cases: {}, default: render, __disposalCause: 'scope-rebuild' })`.
13
+ *
14
+ * ```ts
15
+ * scope({
16
+ * on: (s) => String(s.chartEpoch),
17
+ * render: (h) => {
18
+ * const stats = sample<State, Stats>((s) => s.stats)
19
+ * return [chartView(h, stats)]
20
+ * },
21
+ * })
22
+ * ```
23
+ */
24
+ export function scope(opts) {
25
+ return branch({
26
+ on: opts.on,
27
+ cases: {},
28
+ default: opts.render,
29
+ enter: opts.enter,
30
+ leave: opts.leave,
31
+ onTransition: opts.onTransition,
32
+ __disposalCause: 'scope-rebuild',
33
+ __mask: opts.__mask,
34
+ });
35
+ }
36
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/primitives/scope.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,KAAK,CAAiB,IAAwB;IAC5D,OAAO,MAAM,CAAO;QAClB,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,IAAI,CAAC,MAAM;QACpB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,eAAe,EAAE,eAAe;QAChC,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { ScopeOptions } from '../types.js'\nimport { branch } from './branch.js'\n\n/**\n * Rebuild a subtree when `on(state)` changes.\n *\n * The `render` callback runs once at mount and once again every time\n * `on(state)` returns a new value (compared with `Object.is`). Each run\n * gets a fresh `Lifetime`, so bindings, onMount callbacks, and any\n * per-arm state are recreated cleanly. Use `sample()` inside `render`\n * when you need to read a whole-state snapshot at rebuild time without\n * creating a reactive binding.\n *\n * Sugar over `branch({ on, cases: {}, default: render, __disposalCause: 'scope-rebuild' })`.\n *\n * ```ts\n * scope({\n * on: (s) => String(s.chartEpoch),\n * render: (h) => {\n * const stats = sample<State, Stats>((s) => s.stats)\n * return [chartView(h, stats)]\n * },\n * })\n * ```\n */\nexport function scope<S, M = unknown>(opts: ScopeOptions<S, M>): Node[] {\n return branch<S, M>({\n on: opts.on,\n cases: {},\n default: opts.render,\n enter: opts.enter,\n leave: opts.leave,\n onTransition: opts.onTransition,\n __disposalCause: 'scope-rebuild',\n __mask: opts.__mask,\n })\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { getRenderContext } from '../render-context.js';
2
2
  import { createBinding, applyBinding } from '../binding.js';
3
- import { addDisposer } from '../scope.js';
3
+ import { addDisposer } from '../lifetime.js';
4
4
  import { FULL_MASK } from '../update-loop.js';
5
5
  import { registerOnClear } from './each.js';
6
6
  /**
@@ -16,7 +16,7 @@ import { registerOnClear } from './each.js';
16
16
  */
17
17
  export function selector(field) {
18
18
  const ctx = getRenderContext('selector');
19
- const scope = ctx.rootScope;
19
+ const scope = ctx.rootLifetime;
20
20
  const registry = new Map();
21
21
  let lastValue = field(ctx.state);
22
22
  let generation = 0;
@@ -97,8 +97,8 @@ export function selector(field) {
97
97
  // Per-row disposer for generic reconcile paths (scope disposal).
98
98
  // Uses generation check to skip work if already bulk-cleared.
99
99
  const gen = generation;
100
- const itemScope = getRenderContext('selector').rootScope;
101
- addDisposer(itemScope, () => {
100
+ const itemLifetime = getRenderContext('selector').rootLifetime;
101
+ addDisposer(itemLifetime, () => {
102
102
  if (gen !== generation)
103
103
  return; // already bulk-cleared, no-op
104
104
  const idx = bucket.indexOf(entry);
@@ -1 +1 @@
1
- {"version":3,"file":"selector.js","sourceRoot":"","sources":["../../src/primitives/selector.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRenderContext } from '../render-context.js'\nimport { createBinding, applyBinding } from '../binding.js'\nimport { addDisposer } from '../scope.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport { registerOnClear } from './each.js'\nimport type { BindingKind } from '../types.js'\n\ninterface SelectorEntry {\n node: Node\n kind: BindingKind\n key: string | undefined\n lastValue: unknown\n transform: (match: boolean) => unknown\n gen: number\n}\n\n/**\n * Optimized \"one-of-N\" reactive binding — O(1) updates instead of O(n).\n *\n * Uses a generation counter instead of per-row disposers:\n * - bind() tags entries with the current generation\n * - Individual row removal bumps the generation for that entry (set gen = -1)\n * - Bulk clear bumps the generation and clears the registry via registerOnClear\n * - updateSelector() skips stale entries (gen !== current) and compacts lazily\n *\n * Zero per-row closure allocations. Zero Set.delete calls on disposal.\n */\nexport function selector<S, V>(field: (s: S) => V): SelectorInstance<V> {\n const ctx = getRenderContext('selector')\n const scope = ctx.rootScope\n\n const registry = new Map<V, SelectorEntry[]>()\n let lastValue: V = field(ctx.state as S)\n let generation = 0\n let registeredOnClear = false\n\n function updateBucket(bucket: SelectorEntry[], match: boolean, gen: number): void {\n let hasStale = false\n for (let i = 0; i < bucket.length; i++) {\n const entry = bucket[i]!\n if (entry.gen !== gen) {\n hasStale = true\n continue\n }\n const v = entry.transform(match)\n if (!Object.is(v, entry.lastValue)) {\n entry.lastValue = v\n applyBinding({ kind: entry.kind, node: entry.node, key: entry.key }, v)\n }\n }\n // Lazy compaction — remove stale entries when encountered\n if (hasStale) {\n let w = 0\n for (let r = 0; r < bucket.length; r++) {\n if (bucket[r]!.gen === gen) bucket[w++] = bucket[r]!\n }\n bucket.length = w\n }\n }\n\n function updateSelector(state: S): V {\n const newValue = field(state)\n if (Object.is(newValue, lastValue)) return lastValue\n const gen = generation\n\n const oldBucket = registry.get(lastValue)\n if (oldBucket) updateBucket(oldBucket, false, gen)\n\n const newBucket = registry.get(newValue)\n if (newBucket) updateBucket(newBucket, true, gen)\n\n lastValue = newValue\n return newValue\n }\n\n createBinding(scope, {\n mask: FULL_MASK,\n accessor: ((state: S) => updateSelector(state)) as (state: never) => unknown,\n kind: 'text',\n node: document.createComment('selector'),\n perItem: false,\n })\n\n return {\n bind(\n node: Node,\n key: V | (() => V),\n kind: BindingKind,\n propKey: string | undefined,\n transform: (match: boolean) => unknown,\n ): void {\n const currentKey = typeof key === 'function' ? (key as () => V)() : key\n const initialMatch = Object.is(lastValue, currentKey)\n const initialValue = transform(initialMatch)\n\n applyBinding({ kind, node, key: propKey }, initialValue)\n\n const entry: SelectorEntry = {\n node,\n kind,\n key: propKey,\n lastValue: initialValue,\n transform,\n gen: generation,\n }\n\n let bucket = registry.get(currentKey)\n if (!bucket) {\n bucket = []\n registry.set(currentKey, bucket)\n }\n bucket.push(entry)\n\n // Register bulk clear callback (once per selector per each() block)\n if (!registeredOnClear) {\n registerOnClear(() => {\n generation++\n registry.clear()\n })\n registeredOnClear = true\n }\n\n // Per-row disposer for generic reconcile paths (scope disposal).\n // Uses generation check to skip work if already bulk-cleared.\n const gen = generation\n const itemScope = getRenderContext('selector').rootScope\n addDisposer(itemScope, () => {\n if (gen !== generation) return // already bulk-cleared, no-op\n const idx = bucket!.indexOf(entry)\n if (idx !== -1) bucket!.splice(idx, 1)\n if (bucket!.length === 0) registry.delete(currentKey)\n })\n },\n\n __directUpdate(state: unknown): void {\n updateSelector(state as S)\n },\n }\n}\n\nexport interface SelectorInstance<V> {\n bind(\n node: Node,\n key: V | (() => V),\n kind: BindingKind,\n propKey: string | undefined,\n transform: (match: boolean) => unknown,\n ): void\n __directUpdate(state: unknown): void\n}\n"]}
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../../src/primitives/selector.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRenderContext } from '../render-context.js'\nimport { createBinding, applyBinding } from '../binding.js'\nimport { addDisposer } from '../lifetime.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport { registerOnClear } from './each.js'\nimport type { BindingKind } from '../types.js'\n\ninterface SelectorEntry {\n node: Node\n kind: BindingKind\n key: string | undefined\n lastValue: unknown\n transform: (match: boolean) => unknown\n gen: number\n}\n\n/**\n * Optimized \"one-of-N\" reactive binding — O(1) updates instead of O(n).\n *\n * Uses a generation counter instead of per-row disposers:\n * - bind() tags entries with the current generation\n * - Individual row removal bumps the generation for that entry (set gen = -1)\n * - Bulk clear bumps the generation and clears the registry via registerOnClear\n * - updateSelector() skips stale entries (gen !== current) and compacts lazily\n *\n * Zero per-row closure allocations. Zero Set.delete calls on disposal.\n */\nexport function selector<S, V>(field: (s: S) => V): SelectorInstance<V> {\n const ctx = getRenderContext('selector')\n const scope = ctx.rootLifetime\n\n const registry = new Map<V, SelectorEntry[]>()\n let lastValue: V = field(ctx.state as S)\n let generation = 0\n let registeredOnClear = false\n\n function updateBucket(bucket: SelectorEntry[], match: boolean, gen: number): void {\n let hasStale = false\n for (let i = 0; i < bucket.length; i++) {\n const entry = bucket[i]!\n if (entry.gen !== gen) {\n hasStale = true\n continue\n }\n const v = entry.transform(match)\n if (!Object.is(v, entry.lastValue)) {\n entry.lastValue = v\n applyBinding({ kind: entry.kind, node: entry.node, key: entry.key }, v)\n }\n }\n // Lazy compaction — remove stale entries when encountered\n if (hasStale) {\n let w = 0\n for (let r = 0; r < bucket.length; r++) {\n if (bucket[r]!.gen === gen) bucket[w++] = bucket[r]!\n }\n bucket.length = w\n }\n }\n\n function updateSelector(state: S): V {\n const newValue = field(state)\n if (Object.is(newValue, lastValue)) return lastValue\n const gen = generation\n\n const oldBucket = registry.get(lastValue)\n if (oldBucket) updateBucket(oldBucket, false, gen)\n\n const newBucket = registry.get(newValue)\n if (newBucket) updateBucket(newBucket, true, gen)\n\n lastValue = newValue\n return newValue\n }\n\n createBinding(scope, {\n mask: FULL_MASK,\n accessor: ((state: S) => updateSelector(state)) as (state: never) => unknown,\n kind: 'text',\n node: document.createComment('selector'),\n perItem: false,\n })\n\n return {\n bind(\n node: Node,\n key: V | (() => V),\n kind: BindingKind,\n propKey: string | undefined,\n transform: (match: boolean) => unknown,\n ): void {\n const currentKey = typeof key === 'function' ? (key as () => V)() : key\n const initialMatch = Object.is(lastValue, currentKey)\n const initialValue = transform(initialMatch)\n\n applyBinding({ kind, node, key: propKey }, initialValue)\n\n const entry: SelectorEntry = {\n node,\n kind,\n key: propKey,\n lastValue: initialValue,\n transform,\n gen: generation,\n }\n\n let bucket = registry.get(currentKey)\n if (!bucket) {\n bucket = []\n registry.set(currentKey, bucket)\n }\n bucket.push(entry)\n\n // Register bulk clear callback (once per selector per each() block)\n if (!registeredOnClear) {\n registerOnClear(() => {\n generation++\n registry.clear()\n })\n registeredOnClear = true\n }\n\n // Per-row disposer for generic reconcile paths (scope disposal).\n // Uses generation check to skip work if already bulk-cleared.\n const gen = generation\n const itemLifetime = getRenderContext('selector').rootLifetime\n addDisposer(itemLifetime, () => {\n if (gen !== generation) return // already bulk-cleared, no-op\n const idx = bucket!.indexOf(entry)\n if (idx !== -1) bucket!.splice(idx, 1)\n if (bucket!.length === 0) registry.delete(currentKey)\n })\n },\n\n __directUpdate(state: unknown): void {\n updateSelector(state as S)\n },\n }\n}\n\nexport interface SelectorInstance<V> {\n bind(\n node: Node,\n key: V | (() => V),\n kind: BindingKind,\n propKey: string | undefined,\n transform: (match: boolean) => unknown,\n ): void\n __directUpdate(state: unknown): void\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../src/primitives/show.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAK9C,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAYpE"}
1
+ {"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../src/primitives/show.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAK9C,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAepE"}
@@ -5,7 +5,10 @@ export function show(opts) {
5
5
  // arm — it lets the disposer log distinguish show/hide transitions from
6
6
  // multi-case branch swaps.
7
7
  return branch({
8
- on: opts.when,
8
+ // branch.on is string-only; stringify the boolean for the case lookup.
9
+ // JS object literals stringify boolean keys, so `cases.{true, false}`
10
+ // matches `String(true)` / `String(false)`.
11
+ on: (s) => String(opts.when(s)),
9
12
  cases: { true: opts.render, false: opts.fallback ?? EMPTY },
10
13
  enter: opts.enter,
11
14
  leave: opts.leave,
@@ -1 +1 @@
1
- {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/primitives/show.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,EAAY,CAAA;AAEhC,MAAM,UAAU,IAAI,CAAiB,IAAuB;IAC1D,sEAAsE;IACtE,wEAAwE;IACxE,2BAA2B;IAC3B,OAAO,MAAM,CAAO;QAClB,EAAE,EAAE,IAAI,CAAC,IAAI;QACb,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK,EAAE;QAC3D,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,eAAe,EAAE,WAAW;KAC7B,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { ShowOptions } from '../types.js'\nimport { branch } from './branch.js'\n\nconst EMPTY = () => [] as Node[]\n\nexport function show<S, M = unknown>(opts: ShowOptions<S, M>): Node[] {\n // `__disposalCause` is read by branch.ts when it disposes the leaving\n // arm — it lets the disposer log distinguish show/hide transitions from\n // multi-case branch swaps.\n return branch<S, M>({\n on: opts.when,\n cases: { true: opts.render, false: opts.fallback ?? EMPTY },\n enter: opts.enter,\n leave: opts.leave,\n onTransition: opts.onTransition,\n __disposalCause: 'show-hide',\n })\n}\n"]}
1
+ {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/primitives/show.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,EAAY,CAAA;AAEhC,MAAM,UAAU,IAAI,CAAiB,IAAuB;IAC1D,sEAAsE;IACtE,wEAAwE;IACxE,2BAA2B;IAC3B,OAAO,MAAM,CAAO;QAClB,uEAAuE;QACvE,sEAAsE;QACtE,4CAA4C;QAC5C,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK,EAAE;QAC3D,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,eAAe,EAAE,WAAW;KAC7B,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { ShowOptions } from '../types.js'\nimport { branch } from './branch.js'\n\nconst EMPTY = () => [] as Node[]\n\nexport function show<S, M = unknown>(opts: ShowOptions<S, M>): Node[] {\n // `__disposalCause` is read by branch.ts when it disposes the leaving\n // arm — it lets the disposer log distinguish show/hide transitions from\n // multi-case branch swaps.\n return branch<S, M>({\n // branch.on is string-only; stringify the boolean for the case lookup.\n // JS object literals stringify boolean keys, so `cases.{true, false}`\n // matches `String(true)` / `String(false)`.\n on: (s) => String(opts.when(s)),\n cases: { true: opts.render, false: opts.fallback ?? EMPTY },\n enter: opts.enter,\n leave: opts.leave,\n onTransition: opts.onTransition,\n __disposalCause: 'show-hide',\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"slice.d.ts","sourceRoot":"","sources":["../../src/primitives/slice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAA2C,MAAM,aAAa,CAAA;AAChF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAS9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAChC,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;CAAE,EACpC,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,GAAG,GACrB,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAuDd"}
1
+ {"version":3,"file":"slice.d.ts","sourceRoot":"","sources":["../../src/primitives/slice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAyD,MAAM,aAAa,CAAA;AAC9F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAY9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAChC,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;CAAE,EACpC,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,GAAG,GACrB,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAmEd"}
@@ -1,8 +1,11 @@
1
1
  import { show as _show } from './show.js';
2
2
  import { branch as _branch } from './branch.js';
3
+ import { scope as _scope } from './scope.js';
3
4
  import { each as _each } from './each.js';
4
5
  import { text as _text } from './text.js';
6
+ import { unsafeHtml as _unsafeHtml } from './unsafe-html.js';
5
7
  import { memo as _memo } from './memo.js';
8
+ import { sample as _sample } from './sample.js';
6
9
  import { selector as _selector } from './selector.js';
7
10
  import { useContext } from './context.js';
8
11
  /**
@@ -46,7 +49,13 @@ export function slice(h, lift) {
46
49
  branch: (opts) => _branch({
47
50
  ...opts,
48
51
  on: (r) => opts.on(lift(r)),
49
- cases: wrapCases(opts.cases),
52
+ cases: opts.cases ? wrapCases(opts.cases) : undefined,
53
+ default: opts.default ? wrapCase(opts.default) : undefined,
54
+ }),
55
+ scope: (opts) => _scope({
56
+ ...opts,
57
+ on: (r) => opts.on(lift(r)),
58
+ render: wrapCase(opts.render),
50
59
  }),
51
60
  each: (opts) => _each({
52
61
  ...opts,
@@ -57,6 +66,11 @@ export function slice(h, lift) {
57
66
  return _text(accessor);
58
67
  return _text((r) => accessor(lift(r)), mask);
59
68
  },
69
+ unsafeHtml: (accessor, mask) => {
70
+ if (typeof accessor === 'string')
71
+ return _unsafeHtml(accessor);
72
+ return _unsafeHtml((r) => accessor(lift(r)), mask);
73
+ },
60
74
  memo: (accessor) => {
61
75
  const m = _memo((r) => accessor(lift(r)));
62
76
  return (s) => m(s);
@@ -66,6 +80,7 @@ export function slice(h, lift) {
66
80
  const root = useContext(c);
67
81
  return (s) => root(s);
68
82
  },
83
+ sample: (selector) => _sample((r) => selector(lift(r))),
69
84
  };
70
85
  }
71
86
  //# sourceMappingURL=slice.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"slice.js","sourceRoot":"","sources":["../../src/primitives/slice.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Send, EachOptions, ShowOptions, BranchOptions } from '../types.js'\nimport type { View } from '../view-helpers.js'\nimport { show as _show } from './show.js'\nimport { branch as _branch } from './branch.js'\nimport { each as _each } from './each.js'\nimport { text as _text } from './text.js'\nimport { memo as _memo } from './memo.js'\nimport { selector as _selector } from './selector.js'\nimport { useContext, type Context } from './context.js'\n\n/**\n * Build a `View<Sub, M>` that composes a selector into every state-bound\n * accessor. Used to write view-functions over a sub-slice of parent state:\n *\n * ```ts\n * import { slice } from '@llui/dom'\n *\n * view: (h) => {\n * const formView = slice(h, (s) => s.form)\n * return [...formView.show({ when: f => f.valid, render: (h) => [...] })]\n * }\n * ```\n *\n * Kept as a standalone function rather than a method on the View bundle so\n * apps that don't use it don't pay for its bundle cost — tree-shaken when\n * unused.\n */\nexport function slice<Root, Sub, M>(\n h: View<Root, M> | { send: Send<M> },\n lift: (r: Root) => Sub,\n): View<Sub, M> {\n const send = h.send\n\n // Wrap a Sub-typed case callback to work with the Root-typed primitive.\n // The inner callback receives a View<Root, M> from the primitive, and we\n // narrow it to View<Sub, M> via a recursive slice() call.\n const wrapCase =\n (fn: (h: View<Sub, M>) => Node[]) =>\n (rootH: View<Root, M>): Node[] =>\n fn(slice(rootH, lift))\n\n const wrapCases = (\n cases: Record<string | number, (h: View<Sub, M>) => Node[]>,\n ): Record<string | number, (h: View<Root, M>) => Node[]> => {\n const out: Record<string | number, (h: View<Root, M>) => Node[]> = {}\n for (const key of Object.keys(cases)) {\n out[key] = wrapCase(cases[key]!)\n }\n return out\n }\n\n return {\n send,\n show: (opts: ShowOptions<Sub, M>) =>\n _show<Root, M>({\n ...opts,\n when: (r) => opts.when(lift(r)),\n render: wrapCase(opts.render),\n fallback: opts.fallback ? wrapCase(opts.fallback) : undefined,\n }),\n branch: (opts: BranchOptions<Sub, M>) =>\n _branch<Root, M>({\n ...opts,\n on: (r) => opts.on(lift(r)),\n cases: wrapCases(opts.cases),\n }),\n each: <T>(opts: EachOptions<Sub, T, M>) =>\n _each<Root, T, M>({\n ...opts,\n items: (r) => opts.items(lift(r)),\n }),\n text: (accessor, mask) => {\n if (typeof accessor === 'string') return _text(accessor)\n return _text<Root>((r) => accessor(lift(r)), mask)\n },\n memo: <T>(accessor: (s: Sub) => T) => {\n const m = _memo<Root, T>((r) => accessor(lift(r)))\n return (s: Sub) => m(s as unknown as Root)\n },\n selector: <V>(field: (s: Sub) => V) => _selector<Root, V>((r) => field(lift(r))),\n ctx: <T>(c: Context<T>) => {\n const root = useContext<Root, T>(c)\n return (s: Sub) => root(s as unknown as Root)\n },\n }\n}\n"]}
1
+ {"version":3,"file":"slice.js","sourceRoot":"","sources":["../../src/primitives/slice.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Send, EachOptions, ShowOptions, BranchOptions, ScopeOptions } from '../types.js'\nimport type { View } from '../view-helpers.js'\nimport { show as _show } from './show.js'\nimport { branch as _branch } from './branch.js'\nimport { scope as _scope } from './scope.js'\nimport { each as _each } from './each.js'\nimport { text as _text } from './text.js'\nimport { unsafeHtml as _unsafeHtml } from './unsafe-html.js'\nimport { memo as _memo } from './memo.js'\nimport { sample as _sample } from './sample.js'\nimport { selector as _selector } from './selector.js'\nimport { useContext, type Context } from './context.js'\n\n/**\n * Build a `View<Sub, M>` that composes a selector into every state-bound\n * accessor. Used to write view-functions over a sub-slice of parent state:\n *\n * ```ts\n * import { slice } from '@llui/dom'\n *\n * view: (h) => {\n * const formView = slice(h, (s) => s.form)\n * return [...formView.show({ when: f => f.valid, render: (h) => [...] })]\n * }\n * ```\n *\n * Kept as a standalone function rather than a method on the View bundle so\n * apps that don't use it don't pay for its bundle cost — tree-shaken when\n * unused.\n */\nexport function slice<Root, Sub, M>(\n h: View<Root, M> | { send: Send<M> },\n lift: (r: Root) => Sub,\n): View<Sub, M> {\n const send = h.send\n\n // Wrap a Sub-typed case callback to work with the Root-typed primitive.\n // The inner callback receives a View<Root, M> from the primitive, and we\n // narrow it to View<Sub, M> via a recursive slice() call.\n const wrapCase =\n (fn: (h: View<Sub, M>) => Node[]) =>\n (rootH: View<Root, M>): Node[] =>\n fn(slice(rootH, lift))\n\n const wrapCases = (\n cases: Record<string, (h: View<Sub, M>) => Node[]>,\n ): Record<string, (h: View<Root, M>) => Node[]> => {\n const out: Record<string, (h: View<Root, M>) => Node[]> = {}\n for (const key of Object.keys(cases)) {\n out[key] = wrapCase(cases[key]!)\n }\n return out\n }\n\n return {\n send,\n show: (opts: ShowOptions<Sub, M>) =>\n _show<Root, M>({\n ...opts,\n when: (r) => opts.when(lift(r)),\n render: wrapCase(opts.render),\n fallback: opts.fallback ? wrapCase(opts.fallback) : undefined,\n }),\n branch: (opts: BranchOptions<Sub, M>) =>\n _branch<Root, M>({\n ...opts,\n on: (r) => opts.on(lift(r)),\n cases: opts.cases ? wrapCases(opts.cases) : undefined,\n default: opts.default ? wrapCase(opts.default) : undefined,\n }),\n scope: (opts: ScopeOptions<Sub, M>) =>\n _scope<Root, M>({\n ...opts,\n on: (r) => opts.on(lift(r)),\n render: wrapCase(opts.render),\n }),\n each: <T>(opts: EachOptions<Sub, T, M>) =>\n _each<Root, T, M>({\n ...opts,\n items: (r) => opts.items(lift(r)),\n }),\n text: (accessor, mask) => {\n if (typeof accessor === 'string') return _text(accessor)\n return _text<Root>((r) => accessor(lift(r)), mask)\n },\n unsafeHtml: (accessor, mask) => {\n if (typeof accessor === 'string') return _unsafeHtml(accessor)\n return _unsafeHtml<Root>((r) => accessor(lift(r)), mask)\n },\n memo: <T>(accessor: (s: Sub) => T) => {\n const m = _memo<Root, T>((r) => accessor(lift(r)))\n return (s: Sub) => m(s as unknown as Root)\n },\n selector: <V>(field: (s: Sub) => V) => _selector<Root, V>((r) => field(lift(r))),\n ctx: <T>(c: Context<T>) => {\n const root = useContext<Root, T>(c)\n return (s: Sub) => root(s as unknown as Root)\n },\n sample: <R>(selector: (s: Sub) => R) => _sample<Root, R>((r) => selector(lift(r))),\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { getRenderContext } from '../render-context.js';
2
2
  import { createBinding } from '../binding.js';
3
- import { addCheckedItemUpdater } from '../scope.js';
3
+ import { addCheckedItemUpdater } from '../lifetime.js';
4
4
  import { FULL_MASK } from '../update-loop.js';
5
5
  export function text(accessor, mask) {
6
6
  if (typeof accessor === 'string') {
@@ -12,7 +12,7 @@ export function text(accessor, mask) {
12
12
  // Register as direct updater, bypassing Phase 2 binding scan
13
13
  if (accessor.length === 0) {
14
14
  const get = accessor;
15
- const initial = addCheckedItemUpdater(ctx.rootScope, () => String(get()), (v) => {
15
+ const initial = addCheckedItemUpdater(ctx.rootLifetime, () => String(get()), (v) => {
16
16
  node.nodeValue = v;
17
17
  });
18
18
  node.nodeValue = initial;
@@ -20,7 +20,7 @@ export function text(accessor, mask) {
20
20
  }
21
21
  // Component-level state accessor
22
22
  const bindingMask = mask ?? FULL_MASK;
23
- const binding = createBinding(ctx.rootScope, {
23
+ const binding = createBinding(ctx.rootLifetime, {
24
24
  mask: bindingMask,
25
25
  accessor: accessor,
26
26
  kind: 'text',
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/primitives/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE7C,MAAM,UAAU,IAAI,CAClB,QAAsD,EACtD,IAAa;IAEb,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAA;IAC1C,CAAC;IAED,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;IAExC,mEAAmE;IACnE,6DAA6D;IAC7D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,QAAwB,CAAA;QACpC,MAAM,OAAO,GAAG,qBAAqB,CACnC,GAAG,CAAC,SAAS,EACb,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,EACnB,CAAC,CAAC,EAAE,EAAE;YACJ,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QACpB,CAAC,CACF,CAAA;QACD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAA;QACxB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,iCAAiC;IACjC,MAAM,WAAW,GAAG,IAAI,IAAI,SAAS,CAAA;IACrC,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE;QAC3C,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,QAAqC;QAC/C,IAAI,EAAE,MAAM;QACZ,IAAI;QACJ,OAAO,EAAE,KAAK;KACf,CAAC,CAAA;IACF,MAAM,YAAY,GAAI,QAA6B,CAAC,GAAG,CAAC,KAAU,CAAC,CAAA;IACnE,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;IACrC,OAAO,CAAC,SAAS,GAAG,YAAY,CAAA;IAEhC,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { getRenderContext } from '../render-context.js'\nimport { createBinding } from '../binding.js'\nimport { addCheckedItemUpdater } from '../scope.js'\nimport { FULL_MASK } from '../update-loop.js'\n\nexport function text<S>(\n accessor: ((s: S) => string) | (() => string) | string,\n mask?: number,\n): Text {\n if (typeof accessor === 'string') {\n return document.createTextNode(accessor)\n }\n\n const ctx = getRenderContext('text')\n const node = document.createTextNode('')\n\n // Per-item accessor from each() — zero-arg function (length === 0)\n // Register as direct updater, bypassing Phase 2 binding scan\n if (accessor.length === 0) {\n const get = accessor as () => string\n const initial = addCheckedItemUpdater(\n ctx.rootScope,\n () => String(get()),\n (v) => {\n node.nodeValue = v\n },\n )\n node.nodeValue = initial\n return node\n }\n\n // Component-level state accessor\n const bindingMask = mask ?? FULL_MASK\n const binding = createBinding(ctx.rootScope, {\n mask: bindingMask,\n accessor: accessor as (state: never) => unknown,\n kind: 'text',\n node,\n perItem: false,\n })\n const initialValue = (accessor as (s: S) => string)(ctx.state as S)\n node.nodeValue = String(initialValue)\n binding.lastValue = initialValue\n\n return node\n}\n"]}
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/primitives/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE7C,MAAM,UAAU,IAAI,CAClB,QAAsD,EACtD,IAAa;IAEb,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAA;IAC1C,CAAC;IAED,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;IAExC,mEAAmE;IACnE,6DAA6D;IAC7D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,QAAwB,CAAA;QACpC,MAAM,OAAO,GAAG,qBAAqB,CACnC,GAAG,CAAC,YAAY,EAChB,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,EACnB,CAAC,CAAC,EAAE,EAAE;YACJ,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QACpB,CAAC,CACF,CAAA;QACD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAA;QACxB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,iCAAiC;IACjC,MAAM,WAAW,GAAG,IAAI,IAAI,SAAS,CAAA;IACrC,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,YAAY,EAAE;QAC9C,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,QAAqC;QAC/C,IAAI,EAAE,MAAM;QACZ,IAAI;QACJ,OAAO,EAAE,KAAK;KACf,CAAC,CAAA;IACF,MAAM,YAAY,GAAI,QAA6B,CAAC,GAAG,CAAC,KAAU,CAAC,CAAA;IACnE,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;IACrC,OAAO,CAAC,SAAS,GAAG,YAAY,CAAA;IAEhC,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { getRenderContext } from '../render-context.js'\nimport { createBinding } from '../binding.js'\nimport { addCheckedItemUpdater } from '../lifetime.js'\nimport { FULL_MASK } from '../update-loop.js'\n\nexport function text<S>(\n accessor: ((s: S) => string) | (() => string) | string,\n mask?: number,\n): Text {\n if (typeof accessor === 'string') {\n return document.createTextNode(accessor)\n }\n\n const ctx = getRenderContext('text')\n const node = document.createTextNode('')\n\n // Per-item accessor from each() — zero-arg function (length === 0)\n // Register as direct updater, bypassing Phase 2 binding scan\n if (accessor.length === 0) {\n const get = accessor as () => string\n const initial = addCheckedItemUpdater(\n ctx.rootLifetime,\n () => String(get()),\n (v) => {\n node.nodeValue = v\n },\n )\n node.nodeValue = initial\n return node\n }\n\n // Component-level state accessor\n const bindingMask = mask ?? FULL_MASK\n const binding = createBinding(ctx.rootLifetime, {\n mask: bindingMask,\n accessor: accessor as (state: never) => unknown,\n kind: 'text',\n node,\n perItem: false,\n })\n const initialValue = (accessor as (s: S) => string)(ctx.state as S)\n node.nodeValue = String(initialValue)\n binding.lastValue = initialValue\n\n return node\n}\n"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Insert raw HTML into the DOM tree.
3
+ *
4
+ * **Security.** The caller is responsible for sanitizing `html`. Never
5
+ * interpolate unsanitized user input — this is the XSS escape hatch and
6
+ * the name carries the warning deliberately.
7
+ *
8
+ * **Opaque to the framework.** The parsed subtree is treated as a black
9
+ * box by LLui — no bindings, events, `each`, `show`, or `child` inside
10
+ * it will be discovered or driven by the framework. Use element helpers
11
+ * for anything that needs to react to state or dispatch messages.
12
+ *
13
+ * **Static form.** `unsafeHtml('<b>hi</b>')` parses once at view time
14
+ * and returns the parsed nodes. Zero reactive overhead.
15
+ *
16
+ * **Reactive form.** `unsafeHtml((s) => s.markdownHtml)` registers a
17
+ * structural block that re-parses when the returned string differs
18
+ * (strict `===`) from the previous value. Unchanged strings short-
19
+ * circuit — existing DOM identity (focus, selection, listeners
20
+ * attached outside LLui) is preserved.
21
+ *
22
+ * **Mask hint.** The second parameter mirrors `text()`: the compiler
23
+ * passes a precise mask derived from which state fields the accessor
24
+ * reads. Phase 2 / Phase 1 skip the reconcile when no interesting bit
25
+ * is set. Defaults to FULL_MASK for hand-written components.
26
+ */
27
+ export declare function unsafeHtml<S>(accessor: ((s: S) => string) | string, mask?: number): Node[];
28
+ //# sourceMappingURL=unsafe-html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsafe-html.d.ts","sourceRoot":"","sources":["../../src/primitives/unsafe-html.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,CA8C1F"}
@@ -0,0 +1,82 @@
1
+ import { getRenderContext } from '../render-context.js';
2
+ import { FULL_MASK } from '../update-loop.js';
3
+ /**
4
+ * Insert raw HTML into the DOM tree.
5
+ *
6
+ * **Security.** The caller is responsible for sanitizing `html`. Never
7
+ * interpolate unsanitized user input — this is the XSS escape hatch and
8
+ * the name carries the warning deliberately.
9
+ *
10
+ * **Opaque to the framework.** The parsed subtree is treated as a black
11
+ * box by LLui — no bindings, events, `each`, `show`, or `child` inside
12
+ * it will be discovered or driven by the framework. Use element helpers
13
+ * for anything that needs to react to state or dispatch messages.
14
+ *
15
+ * **Static form.** `unsafeHtml('<b>hi</b>')` parses once at view time
16
+ * and returns the parsed nodes. Zero reactive overhead.
17
+ *
18
+ * **Reactive form.** `unsafeHtml((s) => s.markdownHtml)` registers a
19
+ * structural block that re-parses when the returned string differs
20
+ * (strict `===`) from the previous value. Unchanged strings short-
21
+ * circuit — existing DOM identity (focus, selection, listeners
22
+ * attached outside LLui) is preserved.
23
+ *
24
+ * **Mask hint.** The second parameter mirrors `text()`: the compiler
25
+ * passes a precise mask derived from which state fields the accessor
26
+ * reads. Phase 2 / Phase 1 skip the reconcile when no interesting bit
27
+ * is set. Defaults to FULL_MASK for hand-written components.
28
+ */
29
+ export function unsafeHtml(accessor, mask) {
30
+ if (typeof accessor === 'string') {
31
+ return parseHtmlToNodes(accessor);
32
+ }
33
+ const ctx = getRenderContext('unsafeHtml');
34
+ const blocks = ctx.structuralBlocks;
35
+ const blockMask = mask ?? FULL_MASK;
36
+ const anchor = document.createComment('unsafeHtml');
37
+ let currentHtml = accessor(ctx.state);
38
+ let currentNodes = parseHtmlToNodes(currentHtml);
39
+ const block = {
40
+ mask: blockMask,
41
+ reconcile(state) {
42
+ const newHtml = accessor(state);
43
+ // Identity short-circuit — don't rebuild the subtree when the
44
+ // author handed us the same string. Preserves focus, selection,
45
+ // and any listeners outside LLui's view of the world.
46
+ if (newHtml === currentHtml)
47
+ return;
48
+ const parent = anchor.parentNode;
49
+ if (!parent)
50
+ return;
51
+ const oldNodes = currentNodes;
52
+ currentNodes = parseHtmlToNodes(newHtml);
53
+ currentHtml = newHtml;
54
+ // The anchor sits immediately before the current node range, so
55
+ // `anchor.nextSibling` is the first old node (or whatever follows
56
+ // when the old list is empty). Insert new nodes there, then
57
+ // remove the old ones — matches `branch()`'s enter/leave order so
58
+ // transitions can inspect both sets.
59
+ const ref = anchor.nextSibling;
60
+ for (const node of currentNodes) {
61
+ parent.insertBefore(node, ref);
62
+ }
63
+ for (const node of oldNodes) {
64
+ if (node.parentNode)
65
+ node.parentNode.removeChild(node);
66
+ }
67
+ },
68
+ };
69
+ blocks.push(block);
70
+ return [anchor, ...currentNodes];
71
+ }
72
+ function parseHtmlToNodes(html) {
73
+ // `<template>` parses into an inert DocumentFragment without running
74
+ // scripts, resolving images, or firing connection callbacks. The
75
+ // childNodes live reference is stable until we move the nodes out.
76
+ const template = document.createElement('template');
77
+ template.innerHTML = html;
78
+ // Snapshot — moving nodes to a parent drains template.content, but
79
+ // callers hold this array and we iterate it on reconcile.
80
+ return Array.from(template.content.childNodes);
81
+ }
82
+ //# sourceMappingURL=unsafe-html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsafe-html.js","sourceRoot":"","sources":["../../src/primitives/unsafe-html.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,UAAU,CAAI,QAAqC,EAAE,IAAa;IAChF,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IACnC,CAAC;IAED,MAAM,GAAG,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAA;IACnC,MAAM,SAAS,GAAG,IAAI,IAAI,SAAS,CAAA;IAEnC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAC,CAAA;IACnD,IAAI,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAU,CAAC,CAAA;IAC1C,IAAI,YAAY,GAAW,gBAAgB,CAAC,WAAW,CAAC,CAAA;IAExD,MAAM,KAAK,GAAoB;QAC7B,IAAI,EAAE,SAAS;QACf,SAAS,CAAC,KAAc;YACtB,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAU,CAAC,CAAA;YACpC,8DAA8D;YAC9D,gEAAgE;YAChE,sDAAsD;YACtD,IAAI,OAAO,KAAK,WAAW;gBAAE,OAAM;YAEnC,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAA;YAChC,IAAI,CAAC,MAAM;gBAAE,OAAM;YAEnB,MAAM,QAAQ,GAAG,YAAY,CAAA;YAC7B,YAAY,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;YACxC,WAAW,GAAG,OAAO,CAAA;YAErB,gEAAgE;YAChE,kEAAkE;YAClE,4DAA4D;YAC5D,kEAAkE;YAClE,qCAAqC;YACrC,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAA;YAC9B,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;gBAChC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;YAChC,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;gBAC5B,IAAI,IAAI,CAAC,UAAU;oBAAE,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;YACxD,CAAC;QACH,CAAC;KACF,CAAA;IACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAElB,OAAO,CAAC,MAAM,EAAE,GAAG,YAAY,CAAC,CAAA;AAClC,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,qEAAqE;IACrE,iEAAiE;IACjE,mEAAmE;IACnE,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAA;IACnD,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAA;IACzB,mEAAmE;IACnE,0DAA0D;IAC1D,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;AAChD,CAAC","sourcesContent":["import type { StructuralBlock } from '../structural.js'\nimport { getRenderContext } from '../render-context.js'\nimport { FULL_MASK } from '../update-loop.js'\n\n/**\n * Insert raw HTML into the DOM tree.\n *\n * **Security.** The caller is responsible for sanitizing `html`. Never\n * interpolate unsanitized user input — this is the XSS escape hatch and\n * the name carries the warning deliberately.\n *\n * **Opaque to the framework.** The parsed subtree is treated as a black\n * box by LLui — no bindings, events, `each`, `show`, or `child` inside\n * it will be discovered or driven by the framework. Use element helpers\n * for anything that needs to react to state or dispatch messages.\n *\n * **Static form.** `unsafeHtml('<b>hi</b>')` parses once at view time\n * and returns the parsed nodes. Zero reactive overhead.\n *\n * **Reactive form.** `unsafeHtml((s) => s.markdownHtml)` registers a\n * structural block that re-parses when the returned string differs\n * (strict `===`) from the previous value. Unchanged strings short-\n * circuit — existing DOM identity (focus, selection, listeners\n * attached outside LLui) is preserved.\n *\n * **Mask hint.** The second parameter mirrors `text()`: the compiler\n * passes a precise mask derived from which state fields the accessor\n * reads. Phase 2 / Phase 1 skip the reconcile when no interesting bit\n * is set. Defaults to FULL_MASK for hand-written components.\n */\nexport function unsafeHtml<S>(accessor: ((s: S) => string) | string, mask?: number): Node[] {\n if (typeof accessor === 'string') {\n return parseHtmlToNodes(accessor)\n }\n\n const ctx = getRenderContext('unsafeHtml')\n const blocks = ctx.structuralBlocks\n const blockMask = mask ?? FULL_MASK\n\n const anchor = document.createComment('unsafeHtml')\n let currentHtml = accessor(ctx.state as S)\n let currentNodes: Node[] = parseHtmlToNodes(currentHtml)\n\n const block: StructuralBlock = {\n mask: blockMask,\n reconcile(state: unknown) {\n const newHtml = accessor(state as S)\n // Identity short-circuit — don't rebuild the subtree when the\n // author handed us the same string. Preserves focus, selection,\n // and any listeners outside LLui's view of the world.\n if (newHtml === currentHtml) return\n\n const parent = anchor.parentNode\n if (!parent) return\n\n const oldNodes = currentNodes\n currentNodes = parseHtmlToNodes(newHtml)\n currentHtml = newHtml\n\n // The anchor sits immediately before the current node range, so\n // `anchor.nextSibling` is the first old node (or whatever follows\n // when the old list is empty). Insert new nodes there, then\n // remove the old ones — matches `branch()`'s enter/leave order so\n // transitions can inspect both sets.\n const ref = anchor.nextSibling\n for (const node of currentNodes) {\n parent.insertBefore(node, ref)\n }\n for (const node of oldNodes) {\n if (node.parentNode) node.parentNode.removeChild(node)\n }\n },\n }\n blocks.push(block)\n\n return [anchor, ...currentNodes]\n}\n\nfunction parseHtmlToNodes(html: string): Node[] {\n // `<template>` parses into an inert DocumentFragment without running\n // scripts, resolving images, or firing connection callbacks. The\n // childNodes live reference is stable until we move the nodes out.\n const template = document.createElement('template')\n template.innerHTML = html\n // Snapshot — moving nodes to a parent drains template.content, but\n // callers hold this array and we iterate it on reconcile.\n return Array.from(template.content.childNodes)\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-each.d.ts","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAS,IAAI,EAAE,MAAM,aAAa,CAAA;AAY5D,MAAM,WAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO;IACnD,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAA;IACpB,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAA;IACjC,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,eAAe,EAAE,MAAM,CAAA;IACvB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,CAAC,IAAI,EAAE;QACb,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QACb,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;QACrB,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAA;QAC1C,KAAK,EAAE,MAAM,MAAM,CAAA;KACpB,KAAK,IAAI,EAAE,CAAA;CACb;AAiBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAkMxF"}
1
+ {"version":3,"file":"virtual-each.d.ts","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAY,IAAI,EAAE,MAAM,aAAa,CAAA;AAY/D,MAAM,WAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO;IACnD,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAA;IACpB,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAA;IACjC,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,eAAe,EAAE,MAAM,CAAA;IACvB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,CAAC,IAAI,EAAE;QACb,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QACb,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;QACrB,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAA;QAC1C,KAAK,EAAE,MAAM,MAAM,CAAA;KACpB,KAAK,IAAI,EAAE,CAAA;CACb;AAiBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAkMxF"}
@@ -1,9 +1,9 @@
1
1
  import { getRenderContext, setRenderContext, clearRenderContext, } from '../render-context.js';
2
- import { createScope, disposeScope, addDisposer } from '../scope.js';
2
+ import { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js';
3
3
  import { getFlatBindings, setFlatBindings } from '../binding.js';
4
4
  import { FULL_MASK } from '../update-loop.js';
5
5
  const buildCtx = {
6
- rootScope: null,
6
+ rootLifetime: null,
7
7
  state: null,
8
8
  allBindings: [],
9
9
  structuralBlocks: [],
@@ -33,7 +33,7 @@ const buildCtx = {
33
33
  */
34
34
  export function virtualEach(opts) {
35
35
  const ctx = getRenderContext('virtualEach');
36
- const parentScope = ctx.rootScope;
36
+ const parentLifetime = ctx.rootLifetime;
37
37
  const blocks = ctx.structuralBlocks;
38
38
  const send = ctx.send;
39
39
  const overscan = opts.overscan ?? 3;
@@ -64,7 +64,7 @@ export function virtualEach(opts) {
64
64
  };
65
65
  const buildEntry = (item, index, state) => {
66
66
  const key = opts.key(item);
67
- const scope = createScope(parentScope);
67
+ const scope = createLifetime(parentLifetime);
68
68
  const wrapper = document.createElement('div');
69
69
  wrapper.style.position = 'absolute';
70
70
  wrapper.style.top = `${index * opts.itemHeight}px`;
@@ -103,7 +103,7 @@ export function virtualEach(opts) {
103
103
  return itemProxy;
104
104
  };
105
105
  const indexAccessor = () => entry.index;
106
- buildCtx.rootScope = scope;
106
+ buildCtx.rootLifetime = scope;
107
107
  buildCtx.state = state;
108
108
  buildCtx.allBindings = ctx.allBindings;
109
109
  buildCtx.structuralBlocks = ctx.structuralBlocks;
@@ -133,7 +133,7 @@ export function virtualEach(opts) {
133
133
  // Dispose all entries
134
134
  for (const entry of entries.values()) {
135
135
  entry.scope.disposalCause = 'each-remove';
136
- disposeScope(entry.scope);
136
+ disposeLifetime(entry.scope);
137
137
  if (entry.wrapper.parentNode)
138
138
  entry.wrapper.parentNode.removeChild(entry.wrapper);
139
139
  }
@@ -151,7 +151,7 @@ export function virtualEach(opts) {
151
151
  for (const [key, entry] of entries) {
152
152
  if (!visibleKeys.has(key)) {
153
153
  entry.scope.disposalCause = 'each-remove';
154
- disposeScope(entry.scope);
154
+ disposeLifetime(entry.scope);
155
155
  if (entry.wrapper.parentNode)
156
156
  entry.wrapper.parentNode.removeChild(entry.wrapper);
157
157
  entries.delete(key);
@@ -196,10 +196,10 @@ export function virtualEach(opts) {
196
196
  // Initial render
197
197
  reconcile(ctx.state);
198
198
  // Cleanup on parent disposal
199
- addDisposer(parentScope, () => {
199
+ addDisposer(parentLifetime, () => {
200
200
  scroll.removeEventListener('scroll', onScroll);
201
201
  for (const entry of entries.values()) {
202
- disposeScope(entry.scope);
202
+ disposeLifetime(entry.scope);
203
203
  }
204
204
  entries.clear();
205
205
  });
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-each.js","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ItemAccessor, Scope, Send } from '../types.js'\nimport {\n getRenderContext,\n setRenderContext,\n clearRenderContext,\n type RenderContext,\n} from '../render-context.js'\nimport { createScope, disposeScope, addDisposer } from '../scope.js'\nimport { getFlatBindings, setFlatBindings } from '../binding.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport interface VirtualEachOptions<S, T, M = unknown> {\n items: (s: S) => T[]\n key: (item: T) => string | number\n /** Fixed pixel height per item. Required — dynamic heights are not supported yet. */\n itemHeight: number\n /** Scrollable container height in pixels. */\n containerHeight: number\n /** Extra rows to render above/below the viewport for smooth scrolling. Default: 3. */\n overscan?: number\n /** Optional class for the scroll container. */\n class?: string\n render: (opts: {\n send: Send<M>\n item: ItemAccessor<T>\n acc: <R>(selector: (t: T) => R) => () => R\n index: () => number\n }) => Node[]\n}\n\ninterface VirtualEntry<T> {\n key: string | number\n current: T\n index: number\n scope: Scope\n wrapper: HTMLElement\n}\n\nconst buildCtx: RenderContext = {\n rootScope: null as unknown as Scope,\n state: null,\n allBindings: [],\n structuralBlocks: [],\n}\n\n/**\n * Virtualized list — renders only the rows visible in the scroll viewport.\n * Use for lists with 1k+ items where a regular `each()` would be too slow.\n *\n * Current limitations:\n * - Fixed row height (`itemHeight`) — dynamic heights not supported\n * - No transitions / animations\n * - No cross-container reuse (items outside view are fully disposed)\n *\n * ```ts\n * view: ({ text }) => [\n * ...virtualEach({\n * items: (s) => s.rows,\n * key: (r) => r.id,\n * itemHeight: 40,\n * containerHeight: 600,\n * render: ({ item }) => [\n * div({ class: 'row' }, [text(item.label)]),\n * ],\n * }),\n * ]\n * ```\n */\nexport function virtualEach<S, T, M = unknown>(opts: VirtualEachOptions<S, T, M>): Node[] {\n const ctx = getRenderContext('virtualEach')\n const parentScope = ctx.rootScope\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const overscan = opts.overscan ?? 3\n\n // Scroll container\n const scroll = document.createElement('div')\n scroll.style.overflow = 'auto'\n scroll.style.position = 'relative'\n scroll.style.height = `${opts.containerHeight}px`\n scroll.dataset.virtualContainer = ''\n if (opts.class) scroll.className = opts.class\n\n // Inner spacer sized to full content height\n const spacer = document.createElement('div')\n spacer.style.position = 'relative'\n spacer.style.width = '100%'\n spacer.dataset.virtualSpacer = ''\n scroll.appendChild(spacer)\n\n // Map of key → entry\n const entries = new Map<string | number, VirtualEntry<T>>()\n let lastItems: T[] = []\n let scrollTop = 0\n\n const computeRange = (length: number): [number, number] => {\n if (length === 0) return [0, 0]\n const start = Math.max(0, Math.floor(scrollTop / opts.itemHeight) - overscan)\n const end = Math.min(\n length,\n Math.ceil((scrollTop + opts.containerHeight) / opts.itemHeight) + overscan,\n )\n return [start, end]\n }\n\n const buildEntry = (item: T, index: number, state: S): VirtualEntry<T> => {\n const key = opts.key(item)\n const scope = createScope(parentScope)\n\n const wrapper = document.createElement('div')\n wrapper.style.position = 'absolute'\n wrapper.style.top = `${index * opts.itemHeight}px`\n wrapper.style.left = '0'\n wrapper.style.right = '0'\n wrapper.style.height = `${opts.itemHeight}px`\n wrapper.dataset.virtualItem = ''\n wrapper.dataset.virtualKey = String(key)\n\n const entry: VirtualEntry<T> = { key, current: item, index, scope, wrapper }\n\n // Item accessor: item(selector) and item.field\n const itemFn = <R>(selector: (t: T) => R): (() => R) => {\n const accessor = (): R => selector(entry.current)\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n return accessor\n }\n\n let itemProxy: ItemAccessor<T> | null = null\n const getItemProxy = (): ItemAccessor<T> => {\n if (itemProxy) return itemProxy\n const fieldCache = new Map<string, () => unknown>()\n itemProxy = new Proxy(itemFn as object, {\n get(target, prop) {\n if (typeof prop === 'symbol' || prop === 'then' || prop === 'prototype') {\n return Reflect.get(target, prop)\n }\n const k = prop as string\n const cached = fieldCache.get(k)\n if (cached) return cached\n const accessor = (): unknown => (entry.current as Record<string, unknown>)[k]\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n fieldCache.set(k, accessor)\n return accessor\n },\n }) as ItemAccessor<T>\n return itemProxy\n }\n\n const indexAccessor = (): number => entry.index\n\n buildCtx.rootScope = scope\n buildCtx.state = state\n buildCtx.allBindings = ctx.allBindings\n buildCtx.structuralBlocks = ctx.structuralBlocks\n buildCtx.instance = ctx.instance\n const prevFlat = getFlatBindings()\n setFlatBindings(ctx.allBindings)\n setRenderContext(buildCtx)\n\n const nodes = opts.render({\n send,\n item: getItemProxy(),\n acc: itemFn,\n index: indexAccessor,\n })\n\n clearRenderContext()\n setFlatBindings(prevFlat)\n setRenderContext(ctx)\n\n for (const node of nodes) wrapper.appendChild(node)\n return entry\n }\n\n const reconcile = (state: S): void => {\n const items = opts.items(state)\n lastItems = items\n\n // Update spacer total height\n spacer.style.height = `${items.length * opts.itemHeight}px`\n\n if (items.length === 0) {\n // Dispose all entries\n for (const entry of entries.values()) {\n entry.scope.disposalCause = 'each-remove'\n disposeScope(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n }\n entries.clear()\n return\n }\n\n const [start, end] = computeRange(items.length)\n\n // Build a map of key → {item, index} for the visible range\n const visibleKeys = new Map<string | number, { item: T; index: number }>()\n for (let i = start; i < end; i++) {\n const item = items[i]!\n visibleKeys.set(opts.key(item), { item, index: i })\n }\n\n // Dispose entries no longer visible\n for (const [key, entry] of entries) {\n if (!visibleKeys.has(key)) {\n entry.scope.disposalCause = 'each-remove'\n disposeScope(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n entries.delete(key)\n }\n }\n\n // Create new entries + update existing\n for (const [key, { item, index }] of visibleKeys) {\n const existing = entries.get(key)\n if (existing) {\n existing.current = item\n if (existing.index !== index) {\n existing.index = index\n existing.wrapper.style.top = `${index * opts.itemHeight}px`\n }\n } else {\n const entry = buildEntry(item, index, state)\n entries.set(key, entry)\n spacer.appendChild(entry.wrapper)\n }\n }\n }\n\n // Scroll handler — reconcile visible range without touching component state\n const onScroll = (): void => {\n scrollTop = scroll.scrollTop\n reconcile(ctx.state as S)\n }\n scroll.addEventListener('scroll', onScroll, { passive: true })\n\n // Register as a structural block BEFORE initial render so this block\n // precedes any nested blocks its rows register. See branch.ts for the\n // full rationale (Phase 1 iteration safety).\n const block: StructuralBlock = {\n mask: FULL_MASK,\n reconcile(state: unknown) {\n const newItems = opts.items(state as S)\n if (newItems === lastItems) return\n reconcile(state as S)\n },\n }\n blocks.push(block)\n\n // Initial render\n reconcile(ctx.state as S)\n\n // Cleanup on parent disposal\n addDisposer(parentScope, () => {\n scroll.removeEventListener('scroll', onScroll)\n for (const entry of entries.values()) {\n disposeScope(entry.scope)\n }\n entries.clear()\n })\n\n return [scroll]\n}\n"]}
1
+ {"version":3,"file":"virtual-each.js","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ItemAccessor, Lifetime, Send } from '../types.js'\nimport {\n getRenderContext,\n setRenderContext,\n clearRenderContext,\n type RenderContext,\n} from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { getFlatBindings, setFlatBindings } from '../binding.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport interface VirtualEachOptions<S, T, M = unknown> {\n items: (s: S) => T[]\n key: (item: T) => string | number\n /** Fixed pixel height per item. Required — dynamic heights are not supported yet. */\n itemHeight: number\n /** Scrollable container height in pixels. */\n containerHeight: number\n /** Extra rows to render above/below the viewport for smooth scrolling. Default: 3. */\n overscan?: number\n /** Optional class for the scroll container. */\n class?: string\n render: (opts: {\n send: Send<M>\n item: ItemAccessor<T>\n acc: <R>(selector: (t: T) => R) => () => R\n index: () => number\n }) => Node[]\n}\n\ninterface VirtualEntry<T> {\n key: string | number\n current: T\n index: number\n scope: Lifetime\n wrapper: HTMLElement\n}\n\nconst buildCtx: RenderContext = {\n rootLifetime: null as unknown as Lifetime,\n state: null,\n allBindings: [],\n structuralBlocks: [],\n}\n\n/**\n * Virtualized list — renders only the rows visible in the scroll viewport.\n * Use for lists with 1k+ items where a regular `each()` would be too slow.\n *\n * Current limitations:\n * - Fixed row height (`itemHeight`) — dynamic heights not supported\n * - No transitions / animations\n * - No cross-container reuse (items outside view are fully disposed)\n *\n * ```ts\n * view: ({ text }) => [\n * ...virtualEach({\n * items: (s) => s.rows,\n * key: (r) => r.id,\n * itemHeight: 40,\n * containerHeight: 600,\n * render: ({ item }) => [\n * div({ class: 'row' }, [text(item.label)]),\n * ],\n * }),\n * ]\n * ```\n */\nexport function virtualEach<S, T, M = unknown>(opts: VirtualEachOptions<S, T, M>): Node[] {\n const ctx = getRenderContext('virtualEach')\n const parentLifetime = ctx.rootLifetime\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const overscan = opts.overscan ?? 3\n\n // Scroll container\n const scroll = document.createElement('div')\n scroll.style.overflow = 'auto'\n scroll.style.position = 'relative'\n scroll.style.height = `${opts.containerHeight}px`\n scroll.dataset.virtualContainer = ''\n if (opts.class) scroll.className = opts.class\n\n // Inner spacer sized to full content height\n const spacer = document.createElement('div')\n spacer.style.position = 'relative'\n spacer.style.width = '100%'\n spacer.dataset.virtualSpacer = ''\n scroll.appendChild(spacer)\n\n // Map of key → entry\n const entries = new Map<string | number, VirtualEntry<T>>()\n let lastItems: T[] = []\n let scrollTop = 0\n\n const computeRange = (length: number): [number, number] => {\n if (length === 0) return [0, 0]\n const start = Math.max(0, Math.floor(scrollTop / opts.itemHeight) - overscan)\n const end = Math.min(\n length,\n Math.ceil((scrollTop + opts.containerHeight) / opts.itemHeight) + overscan,\n )\n return [start, end]\n }\n\n const buildEntry = (item: T, index: number, state: S): VirtualEntry<T> => {\n const key = opts.key(item)\n const scope = createLifetime(parentLifetime)\n\n const wrapper = document.createElement('div')\n wrapper.style.position = 'absolute'\n wrapper.style.top = `${index * opts.itemHeight}px`\n wrapper.style.left = '0'\n wrapper.style.right = '0'\n wrapper.style.height = `${opts.itemHeight}px`\n wrapper.dataset.virtualItem = ''\n wrapper.dataset.virtualKey = String(key)\n\n const entry: VirtualEntry<T> = { key, current: item, index, scope, wrapper }\n\n // Item accessor: item(selector) and item.field\n const itemFn = <R>(selector: (t: T) => R): (() => R) => {\n const accessor = (): R => selector(entry.current)\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n return accessor\n }\n\n let itemProxy: ItemAccessor<T> | null = null\n const getItemProxy = (): ItemAccessor<T> => {\n if (itemProxy) return itemProxy\n const fieldCache = new Map<string, () => unknown>()\n itemProxy = new Proxy(itemFn as object, {\n get(target, prop) {\n if (typeof prop === 'symbol' || prop === 'then' || prop === 'prototype') {\n return Reflect.get(target, prop)\n }\n const k = prop as string\n const cached = fieldCache.get(k)\n if (cached) return cached\n const accessor = (): unknown => (entry.current as Record<string, unknown>)[k]\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n fieldCache.set(k, accessor)\n return accessor\n },\n }) as ItemAccessor<T>\n return itemProxy\n }\n\n const indexAccessor = (): number => entry.index\n\n buildCtx.rootLifetime = scope\n buildCtx.state = state\n buildCtx.allBindings = ctx.allBindings\n buildCtx.structuralBlocks = ctx.structuralBlocks\n buildCtx.instance = ctx.instance\n const prevFlat = getFlatBindings()\n setFlatBindings(ctx.allBindings)\n setRenderContext(buildCtx)\n\n const nodes = opts.render({\n send,\n item: getItemProxy(),\n acc: itemFn,\n index: indexAccessor,\n })\n\n clearRenderContext()\n setFlatBindings(prevFlat)\n setRenderContext(ctx)\n\n for (const node of nodes) wrapper.appendChild(node)\n return entry\n }\n\n const reconcile = (state: S): void => {\n const items = opts.items(state)\n lastItems = items\n\n // Update spacer total height\n spacer.style.height = `${items.length * opts.itemHeight}px`\n\n if (items.length === 0) {\n // Dispose all entries\n for (const entry of entries.values()) {\n entry.scope.disposalCause = 'each-remove'\n disposeLifetime(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n }\n entries.clear()\n return\n }\n\n const [start, end] = computeRange(items.length)\n\n // Build a map of key → {item, index} for the visible range\n const visibleKeys = new Map<string | number, { item: T; index: number }>()\n for (let i = start; i < end; i++) {\n const item = items[i]!\n visibleKeys.set(opts.key(item), { item, index: i })\n }\n\n // Dispose entries no longer visible\n for (const [key, entry] of entries) {\n if (!visibleKeys.has(key)) {\n entry.scope.disposalCause = 'each-remove'\n disposeLifetime(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n entries.delete(key)\n }\n }\n\n // Create new entries + update existing\n for (const [key, { item, index }] of visibleKeys) {\n const existing = entries.get(key)\n if (existing) {\n existing.current = item\n if (existing.index !== index) {\n existing.index = index\n existing.wrapper.style.top = `${index * opts.itemHeight}px`\n }\n } else {\n const entry = buildEntry(item, index, state)\n entries.set(key, entry)\n spacer.appendChild(entry.wrapper)\n }\n }\n }\n\n // Scroll handler — reconcile visible range without touching component state\n const onScroll = (): void => {\n scrollTop = scroll.scrollTop\n reconcile(ctx.state as S)\n }\n scroll.addEventListener('scroll', onScroll, { passive: true })\n\n // Register as a structural block BEFORE initial render so this block\n // precedes any nested blocks its rows register. See branch.ts for the\n // full rationale (Phase 1 iteration safety).\n const block: StructuralBlock = {\n mask: FULL_MASK,\n reconcile(state: unknown) {\n const newItems = opts.items(state as S)\n if (newItems === lastItems) return\n reconcile(state as S)\n },\n }\n blocks.push(block)\n\n // Initial render\n reconcile(ctx.state as S)\n\n // Cleanup on parent disposal\n addDisposer(parentLifetime, () => {\n scroll.removeEventListener('scroll', onScroll)\n for (const entry of entries.values()) {\n disposeLifetime(entry.scope)\n }\n entries.clear()\n })\n\n return [scroll]\n}\n"]}
@@ -1,7 +1,7 @@
1
- import type { Scope, Binding } from './types.js';
1
+ import type { Lifetime, Binding } from './types.js';
2
2
  import type { StructuralBlock } from './structural.js';
3
3
  export interface RenderContext {
4
- rootScope: Scope;
4
+ rootLifetime: Lifetime;
5
5
  state: unknown;
6
6
  allBindings: Binding[];
7
7
  structuralBlocks: StructuralBlock[];
@@ -1 +1 @@
1
- {"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGtD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,KAAK,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,OAAO,EAAE,CAAA;IACtB,gBAAgB,EAAE,eAAe,EAAE,CAAA;IACnC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAM9B;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAEzD;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,wBAAgB,gBAAgB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CAgBtE"}
1
+ {"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGtD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,QAAQ,CAAA;IACtB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,OAAO,EAAE,CAAA;IACtB,gBAAgB,EAAE,eAAe,EAAE,CAAA;IACnC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAM9B;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAEzD;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,wBAAgB,gBAAgB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CAgBtE"}