@fluixi/reactive 1.0.0-alpha.104 → 1.0.0-alpha.106

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 (82) hide show
  1. package/dist/cdn/reactive.cjs +74 -34
  2. package/dist/cdn/reactive.global.js +2 -2
  3. package/dist/cdn/reactive.mjs +74 -34
  4. package/dist/cdn/signal.cjs +23 -3
  5. package/dist/cdn/signal.global.js +2 -2
  6. package/dist/cdn/signal.mjs +23 -3
  7. package/dist/index.cjs +74 -34
  8. package/dist/index.mjs +74 -34
  9. package/dist/lib/index.cjs +75 -35
  10. package/dist/lib/index.mjs +75 -35
  11. package/dist/lib/observable/index.cjs +1 -1
  12. package/dist/lib/observable/index.mjs +1 -1
  13. package/dist/lib/signal/api.cjs +40 -14
  14. package/dist/lib/signal/api.mjs +40 -14
  15. package/dist/lib/signal/cached-resource.cjs +36 -13
  16. package/dist/lib/signal/cached-resource.mjs +36 -13
  17. package/dist/lib/signal/graph/children.cjs +25 -6
  18. package/dist/lib/signal/graph/children.mjs +25 -6
  19. package/dist/lib/signal/graph/compat.cjs +41 -15
  20. package/dist/lib/signal/graph/compat.d.ts.map +1 -1
  21. package/dist/lib/signal/graph/compat.js +20 -11
  22. package/dist/lib/signal/graph/compat.mjs +41 -15
  23. package/dist/lib/signal/graph/context-children.cjs +1 -1
  24. package/dist/lib/signal/graph/context-children.mjs +1 -1
  25. package/dist/lib/signal/graph/core.cjs +24 -4
  26. package/dist/lib/signal/graph/core.d.ts +10 -0
  27. package/dist/lib/signal/graph/core.d.ts.map +1 -1
  28. package/dist/lib/signal/graph/core.js +27 -2
  29. package/dist/lib/signal/graph/core.mjs +24 -4
  30. package/dist/lib/signal/graph/debounced.cjs +35 -12
  31. package/dist/lib/signal/graph/debounced.mjs +35 -12
  32. package/dist/lib/signal/graph/derived.cjs +35 -12
  33. package/dist/lib/signal/graph/derived.mjs +35 -12
  34. package/dist/lib/signal/graph/detect.cjs +1 -1
  35. package/dist/lib/signal/graph/detect.mjs +1 -1
  36. package/dist/lib/signal/graph/error-boundary.cjs +10 -8
  37. package/dist/lib/signal/graph/error-boundary.mjs +10 -8
  38. package/dist/lib/signal/graph/index.cjs +24 -4
  39. package/dist/lib/signal/graph/index.mjs +24 -4
  40. package/dist/lib/signal/graph/observe.cjs +1 -1
  41. package/dist/lib/signal/graph/observe.mjs +1 -1
  42. package/dist/lib/signal/graph/resource.cjs +35 -12
  43. package/dist/lib/signal/graph/resource.mjs +35 -12
  44. package/dist/lib/signal/graph/runtime.cjs +29 -5
  45. package/dist/lib/signal/graph/runtime.d.ts.map +1 -1
  46. package/dist/lib/signal/graph/runtime.js +5 -3
  47. package/dist/lib/signal/graph/runtime.mjs +29 -5
  48. package/dist/lib/signal/graph/signal-next.cjs +67 -29
  49. package/dist/lib/signal/graph/signal-next.mjs +67 -29
  50. package/dist/lib/signal/graph/state.cjs +1 -1
  51. package/dist/lib/signal/graph/state.mjs +1 -1
  52. package/dist/lib/signal/graph/suspense.cjs +10 -8
  53. package/dist/lib/signal/graph/suspense.mjs +10 -8
  54. package/dist/lib/signal/index-legacy.cjs +67 -29
  55. package/dist/lib/signal/index-legacy.mjs +67 -29
  56. package/dist/lib/signal/index.cjs +67 -29
  57. package/dist/lib/signal/index.mjs +67 -29
  58. package/dist/lib/signal/list.cjs +58 -25
  59. package/dist/lib/signal/list.d.ts +3 -8
  60. package/dist/lib/signal/list.d.ts.map +1 -1
  61. package/dist/lib/signal/list.js +40 -18
  62. package/dist/lib/signal/list.mjs +58 -25
  63. package/dist/lib/signal/resource-api.cjs +35 -12
  64. package/dist/lib/signal/resource-api.mjs +35 -12
  65. package/dist/lib/signal/signal.cjs +41 -15
  66. package/dist/lib/signal/signal.mjs +41 -15
  67. package/dist/lib/signal/utilities.cjs +41 -15
  68. package/dist/lib/signal/utilities.mjs +41 -15
  69. package/dist/lib/store/api.cjs +9 -7
  70. package/dist/lib/store/api.mjs +9 -7
  71. package/dist/lib/store/index.cjs +10 -8
  72. package/dist/lib/store/index.mjs +10 -8
  73. package/dist/lib/store/store.cjs +9 -7
  74. package/dist/lib/store/store.d.ts.map +1 -1
  75. package/dist/lib/store/store.js +8 -6
  76. package/dist/lib/store/store.mjs +9 -7
  77. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  78. package/dist/version.generated.cjs +2 -2
  79. package/dist/version.generated.d.ts +1 -1
  80. package/dist/version.generated.js +1 -1
  81. package/dist/version.generated.mjs +2 -2
  82. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
- /*! @fluixi/reactive v1.0.0-alpha.104 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/reactive v1.0.0-alpha.106 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
3
  // src/version.generated.ts
4
- var VERSION = "1.0.0-alpha.104";
4
+ var VERSION = "1.0.0-alpha.106";
5
5
 
6
6
  // src/lib/signal/graph/state.ts
7
7
  var MAJOR = VERSION.split(".")[0];
@@ -197,6 +197,7 @@ var Computed = class extends Source {
197
197
  super();
198
198
  this._threw = false;
199
199
  this._initialized = false;
200
+ this._disposed = false;
200
201
  this.state = DIRTY;
201
202
  /** Nodes this computation read on its last run. Rebuilt on every run. */
202
203
  this.sources = null;
@@ -212,8 +213,23 @@ var Computed = class extends Source {
212
213
  if (this._threw) throw this._error;
213
214
  return this._value;
214
215
  }
216
+ /**
217
+ * Unlink from every source, for good. Without this, a source outlives the
218
+ * computation and keeps it (and everything its function captured) reachable
219
+ * through its observers set: a disposed list row stayed in memory that way.
220
+ * The last value stays readable; the node simply never updates again.
221
+ */
222
+ dispose() {
223
+ if (this._disposed) return;
224
+ this._disposed = true;
225
+ if (this.sources) {
226
+ for (const s of this.sources) s.observers?.delete(this);
227
+ this.sources = null;
228
+ }
229
+ }
215
230
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
216
231
  updateIfNecessary() {
232
+ if (this._disposed) return;
217
233
  if (this.state === CHECK) {
218
234
  if (this.sources) {
219
235
  for (const s of this.sources) {
@@ -257,7 +273,7 @@ var Computed = class extends Source {
257
273
  reportReactiveRun(this);
258
274
  if (changed) {
259
275
  this.version++;
260
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
276
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
261
277
  notifyWatchers(this);
262
278
  }
263
279
  }
@@ -271,7 +287,7 @@ var Computed = class extends Source {
271
287
  const wasClean = this.state === CLEAN;
272
288
  this.state = level;
273
289
  if (wasClean) {
274
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
290
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
275
291
  notifyWatchers(this);
276
292
  }
277
293
  }
@@ -310,6 +326,10 @@ var Watcher = class {
310
326
  this._pending.clear();
311
327
  return out;
312
328
  }
329
+ /** Drain pending without the array, for callers that only need it empty. */
330
+ clearPending() {
331
+ this._pending.clear();
332
+ }
313
333
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
314
334
  _notify(node) {
315
335
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -527,14 +547,14 @@ var EffectNode = class {
527
547
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
528
548
  });
529
549
  });
530
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
550
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
531
551
  this.watcher = new Watcher(() => enqueue(this));
532
552
  this.run();
533
553
  this.watcher.watch(this.computed);
534
554
  }
535
555
  run() {
536
556
  if (this.disposed) return;
537
- this.watcher.getPending();
557
+ this.watcher.clearPending();
538
558
  try {
539
559
  this.computed.get();
540
560
  } catch (e) {
@@ -551,6 +571,7 @@ var EffectNode = class {
551
571
  this.disposed = true;
552
572
  reportReactiveDispose(this.computed);
553
573
  this.watcher.unwatch(this.computed);
574
+ this.computed.dispose();
554
575
  disposeOwner(this.owner);
555
576
  }
556
577
  };
@@ -589,13 +610,15 @@ function createSignal(value, options) {
589
610
  },
590
611
  enumerable: false
591
612
  });
592
- reportReactiveNode({
593
- kind: "signal",
594
- node: state,
595
- handle: read,
596
- name: options?.name,
597
- set: write
598
- });
613
+ if (isObserved()) {
614
+ reportReactiveNode({
615
+ kind: "signal",
616
+ node: state,
617
+ handle: read,
618
+ name: options?.name,
619
+ set: write
620
+ });
621
+ }
599
622
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
600
623
  return [read, write];
601
624
  }
@@ -619,8 +642,11 @@ function createMemo(fn, value, options) {
619
642
  const read = (() => c.get());
620
643
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
621
644
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
622
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
623
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
645
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
646
+ onCleanup(() => {
647
+ c.dispose();
648
+ if (isObserved()) reportReactiveDispose(c);
649
+ });
624
650
  return read;
625
651
  }
626
652
  function createEffect(fn, value, options) {
@@ -677,6 +703,20 @@ function on(deps, fn, options) {
677
703
  }
678
704
 
679
705
  // src/lib/signal/list.ts
706
+ function lazyIndex(initial) {
707
+ let current = initial;
708
+ let state = null;
709
+ return {
710
+ index: () => {
711
+ if (!state) state = createSignal(current, { name: "row index" });
712
+ return state[0]();
713
+ },
714
+ set: (n) => {
715
+ current = n;
716
+ if (state) state[1](n);
717
+ }
718
+ };
719
+ }
680
720
  function mapArray(list, mapFn, options) {
681
721
  let items = [];
682
722
  onCleanup(() => {
@@ -706,9 +746,9 @@ function mapArray(list, mapFn, options) {
706
746
  let rendered, setIndex, dispose;
707
747
  createRoot((d) => {
708
748
  dispose = d;
709
- const [index, setIdx] = createSignal(i, { name: "row index" });
710
- setIndex = setIdx;
711
- rendered = mapFn(value, index);
749
+ const idx = lazyIndex(i);
750
+ setIndex = idx.set;
751
+ rendered = mapFn(value, idx.index);
712
752
  });
713
753
  const entry = { item: value, rendered, dispose, setIndex };
714
754
  result.push(entry);
@@ -748,21 +788,19 @@ function keyArray(list, keyFn, mapFn, options) {
748
788
  const reuse = prevByKey.get(key);
749
789
  if (reuse && !usedKeys.has(key)) {
750
790
  reuse.setIndex(i);
791
+ reuse.setItem(next[i]);
751
792
  resultEntries.push(reuse);
752
793
  } else {
753
- let rendered, setIndex, dispose;
794
+ let rendered, setIndex, setItem, dispose;
754
795
  createRoot((d) => {
755
796
  dispose = d;
756
- const [index, setIdx] = createSignal(i, { name: "row index" });
757
- setIndex = setIdx;
758
- const item = createMemo((prev) => {
759
- const cur = list() || [];
760
- const v = cur[index()];
761
- return v !== void 0 && v !== null ? v : prev;
762
- }, next[i]);
763
- rendered = mapFn(item, index);
797
+ const idx = lazyIndex(i);
798
+ setIndex = idx.set;
799
+ const [item, writeItem] = createSignal(next[i]);
800
+ setItem = (v) => writeItem(() => v);
801
+ rendered = mapFn(item, idx.index);
764
802
  });
765
- resultEntries.push({ rendered, dispose, setIndex });
803
+ resultEntries.push({ rendered, dispose, setIndex, setItem });
766
804
  }
767
805
  resultKeys.push(key);
768
806
  usedKeys.add(key);
@@ -812,7 +850,7 @@ function indexArray(list, mapFn, options) {
812
850
 
813
851
  // src/lib/signal/graph/detect.ts
814
852
  var __BROWSER_PROD__ = typeof window === "undefined" ? false : window.__FLUIXI_PROD__ === true;
815
- var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : false;
853
+ var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : process.env.NODE_ENV !== "production";
816
854
  var $SIGNAL2 = /* @__PURE__ */ Symbol.for("signal");
817
855
  var $MEMO2 = /* @__PURE__ */ Symbol.for("memo");
818
856
  var $RESOURCE = /* @__PURE__ */ Symbol.for("resource");
@@ -27,7 +27,7 @@ __export(list_exports, {
27
27
  module.exports = __toCommonJS(list_exports);
28
28
 
29
29
  // src/version.generated.ts
30
- var VERSION = "1.0.0-alpha.104";
30
+ var VERSION = "1.0.0-alpha.106";
31
31
 
32
32
  // src/lib/signal/graph/state.ts
33
33
  var MAJOR = VERSION.split(".")[0];
@@ -169,6 +169,7 @@ var Computed = class extends Source {
169
169
  super();
170
170
  this._threw = false;
171
171
  this._initialized = false;
172
+ this._disposed = false;
172
173
  this.state = DIRTY;
173
174
  /** Nodes this computation read on its last run. Rebuilt on every run. */
174
175
  this.sources = null;
@@ -184,8 +185,23 @@ var Computed = class extends Source {
184
185
  if (this._threw) throw this._error;
185
186
  return this._value;
186
187
  }
188
+ /**
189
+ * Unlink from every source, for good. Without this, a source outlives the
190
+ * computation and keeps it (and everything its function captured) reachable
191
+ * through its observers set: a disposed list row stayed in memory that way.
192
+ * The last value stays readable; the node simply never updates again.
193
+ */
194
+ dispose() {
195
+ if (this._disposed) return;
196
+ this._disposed = true;
197
+ if (this.sources) {
198
+ for (const s of this.sources) s.observers?.delete(this);
199
+ this.sources = null;
200
+ }
201
+ }
187
202
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
188
203
  updateIfNecessary() {
204
+ if (this._disposed) return;
189
205
  if (this.state === CHECK) {
190
206
  if (this.sources) {
191
207
  for (const s of this.sources) {
@@ -229,7 +245,7 @@ var Computed = class extends Source {
229
245
  reportReactiveRun(this);
230
246
  if (changed) {
231
247
  this.version++;
232
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
248
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
233
249
  notifyWatchers(this);
234
250
  }
235
251
  }
@@ -243,7 +259,7 @@ var Computed = class extends Source {
243
259
  const wasClean = this.state === CLEAN;
244
260
  this.state = level;
245
261
  if (wasClean) {
246
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
262
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
247
263
  notifyWatchers(this);
248
264
  }
249
265
  }
@@ -388,13 +404,15 @@ function createSignal(value, options) {
388
404
  },
389
405
  enumerable: false
390
406
  });
391
- reportReactiveNode({
392
- kind: "signal",
393
- node: state,
394
- handle: read,
395
- name: options?.name,
396
- set: write
397
- });
407
+ if (isObserved()) {
408
+ reportReactiveNode({
409
+ kind: "signal",
410
+ node: state,
411
+ handle: read,
412
+ name: options?.name,
413
+ set: write
414
+ });
415
+ }
398
416
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
399
417
  return [read, write];
400
418
  }
@@ -418,8 +436,11 @@ function createMemo(fn, value, options) {
418
436
  const read = (() => c.get());
419
437
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
420
438
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
421
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
422
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
439
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
440
+ onCleanup(() => {
441
+ c.dispose();
442
+ if (isObserved()) reportReactiveDispose(c);
443
+ });
423
444
  return read;
424
445
  }
425
446
  function untrack2(fn) {
@@ -427,6 +448,20 @@ function untrack2(fn) {
427
448
  }
428
449
 
429
450
  // src/lib/signal/list.ts
451
+ function lazyIndex(initial) {
452
+ let current = initial;
453
+ let state = null;
454
+ return {
455
+ index: () => {
456
+ if (!state) state = createSignal(current, { name: "row index" });
457
+ return state[0]();
458
+ },
459
+ set: (n) => {
460
+ current = n;
461
+ if (state) state[1](n);
462
+ }
463
+ };
464
+ }
430
465
  function mapArray(list, mapFn, options) {
431
466
  let items = [];
432
467
  onCleanup(() => {
@@ -456,9 +491,9 @@ function mapArray(list, mapFn, options) {
456
491
  let rendered, setIndex, dispose;
457
492
  createRoot((d) => {
458
493
  dispose = d;
459
- const [index, setIdx] = createSignal(i, { name: "row index" });
460
- setIndex = setIdx;
461
- rendered = mapFn(value, index);
494
+ const idx = lazyIndex(i);
495
+ setIndex = idx.set;
496
+ rendered = mapFn(value, idx.index);
462
497
  });
463
498
  const entry = { item: value, rendered, dispose, setIndex };
464
499
  result.push(entry);
@@ -498,21 +533,19 @@ function keyArray(list, keyFn, mapFn, options) {
498
533
  const reuse = prevByKey.get(key);
499
534
  if (reuse && !usedKeys.has(key)) {
500
535
  reuse.setIndex(i);
536
+ reuse.setItem(next[i]);
501
537
  resultEntries.push(reuse);
502
538
  } else {
503
- let rendered, setIndex, dispose;
539
+ let rendered, setIndex, setItem, dispose;
504
540
  createRoot((d) => {
505
541
  dispose = d;
506
- const [index, setIdx] = createSignal(i, { name: "row index" });
507
- setIndex = setIdx;
508
- const item = createMemo((prev) => {
509
- const cur = list() || [];
510
- const v = cur[index()];
511
- return v !== void 0 && v !== null ? v : prev;
512
- }, next[i]);
513
- rendered = mapFn(item, index);
542
+ const idx = lazyIndex(i);
543
+ setIndex = idx.set;
544
+ const [item, writeItem] = createSignal(next[i]);
545
+ setItem = (v) => writeItem(() => v);
546
+ rendered = mapFn(item, idx.index);
514
547
  });
515
- resultEntries.push({ rendered, dispose, setIndex });
548
+ resultEntries.push({ rendered, dispose, setIndex, setItem });
516
549
  }
517
550
  resultKeys.push(key);
518
551
  usedKeys.add(key);
@@ -14,18 +14,13 @@ interface ListOptions {
14
14
  /** Returned (as-is, not wrapped) when the list is empty/nullish. */
15
15
  fallback?: () => any;
16
16
  }
17
- /**
18
- * Reference-keyed map. Reuses an item's mapped value while the same value (by
19
- * `===`) stays in the list; new values get a fresh root + index signal; removed
20
- * values are disposed. The map fn receives the plain value (a snapshot) and a
21
- * reactive index accessor. Backs `<For>`.
22
- */
23
17
  export declare function mapArray<T, U>(list: ListSource<T>, mapFn: (item: T, index: Accessor<number>) => U, options?: ListOptions): Accessor<U[]>;
24
18
  /**
25
19
  * Key-function map. Like `mapArray` but reuse is keyed by `keyFn(item)` (so the
26
20
  * same key survives object recreation), and the map fn receives a *live* item
27
- * accessor (reads through to the current list value, so sub-property updates
28
- * propagate). Backs `<For by={...}>`.
21
+ * accessor: a per-entry signal the reconciliation sets, so a new object under
22
+ * the same key propagates without the whole list being re-read per row.
23
+ * Backs `<For by={...}>`.
29
24
  */
30
25
  export declare function keyArray<T, U>(list: ListSource<T>, keyFn: (item: T) => any, mapFn: (item: Accessor<T>, index: Accessor<number>) => U, options?: ListOptions): Accessor<U[]>;
31
26
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/lib/signal/list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,KAAK,QAAQ,EAA4D,MAAM,mBAAmB,CAAC;AAE5G,KAAK,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,CAAC,CAAC;AAEvE,UAAU,WAAW;IACnB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAC9C,OAAO,CAAC,EAAE,WAAW,GACpB,QAAQ,CAAC,CAAC,EAAE,CAAC,CA6Cf;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,EACvB,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EACxD,OAAO,CAAC,EAAE,WAAW,GACpB,QAAQ,CAAC,CAAC,EAAE,CAAC,CAuDf;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAC7B,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,EAC9C,OAAO,CAAC,EAAE,WAAW,GACpB,QAAQ,CAAC,CAAC,EAAE,CAAC,CAuCf"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/lib/signal/list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAyE,MAAM,mBAAmB,CAAC;AAEzH,KAAK,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,CAAC,CAAC;AAEvE,UAAU,WAAW;IACnB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC;CACtB;AA4BD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAC9C,OAAO,CAAC,EAAE,WAAW,GACpB,QAAQ,CAAC,CAAC,EAAE,CAAC,CA6Cf;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,EACvB,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EACxD,OAAO,CAAC,EAAE,WAAW,GACpB,QAAQ,CAAC,CAAC,EAAE,CAAC,CAuDf;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAC7B,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,EAC9C,OAAO,CAAC,EAAE,WAAW,GACpB,QAAQ,CAAC,CAAC,EAAE,CAAC,CAuCf"}
@@ -15,6 +15,27 @@ import { createMemo, createRoot, createSignal, onCleanup, untrack } from './grap
15
15
  * values are disposed. The map fn receives the plain value (a snapshot) and a
16
16
  * reactive index accessor. Backs `<For>`.
17
17
  */
18
+ /**
19
+ * A row index that builds its signal on first read. Most rows never read
20
+ * their index, and an unread index signal is a State plus its closure per row.
21
+ * `set` keeps the plain number current until (and unless) that read happens.
22
+ */
23
+ function lazyIndex(initial) {
24
+ let current = initial;
25
+ let state = null;
26
+ return {
27
+ index: () => {
28
+ if (!state)
29
+ state = createSignal(current, { name: 'row index' });
30
+ return state[0]();
31
+ },
32
+ set: (n) => {
33
+ current = n;
34
+ if (state)
35
+ state[1](n);
36
+ },
37
+ };
38
+ }
18
39
  export function mapArray(list, mapFn, options) {
19
40
  let items = [];
20
41
  onCleanup(() => { for (const e of items)
@@ -46,9 +67,9 @@ export function mapArray(list, mapFn, options) {
46
67
  let rendered, setIndex, dispose;
47
68
  createRoot((d) => {
48
69
  dispose = d;
49
- const [index, setIdx] = createSignal(i, { name: 'row index' });
50
- setIndex = setIdx;
51
- rendered = mapFn(value, index);
70
+ const idx = lazyIndex(i);
71
+ setIndex = idx.set;
72
+ rendered = mapFn(value, idx.index);
52
73
  });
53
74
  const entry = { item: value, rendered, dispose, setIndex };
54
75
  result.push(entry);
@@ -66,8 +87,9 @@ export function mapArray(list, mapFn, options) {
66
87
  /**
67
88
  * Key-function map. Like `mapArray` but reuse is keyed by `keyFn(item)` (so the
68
89
  * same key survives object recreation), and the map fn receives a *live* item
69
- * accessor (reads through to the current list value, so sub-property updates
70
- * propagate). Backs `<For by={...}>`.
90
+ * accessor: a per-entry signal the reconciliation sets, so a new object under
91
+ * the same key propagates without the whole list being re-read per row.
92
+ * Backs `<For by={...}>`.
71
93
  */
72
94
  export function keyArray(list, keyFn, mapFn, options) {
73
95
  let keys = [];
@@ -95,25 +117,25 @@ export function keyArray(list, keyFn, mapFn, options) {
95
117
  const reuse = prevByKey.get(key);
96
118
  if (reuse && !usedKeys.has(key)) {
97
119
  reuse.setIndex(i);
120
+ reuse.setItem(next[i]);
98
121
  resultEntries.push(reuse);
99
122
  }
100
123
  else {
101
- let rendered, setIndex, dispose;
124
+ let rendered, setIndex, setItem, dispose;
102
125
  createRoot((d) => {
103
126
  dispose = d;
104
- const [index, setIdx] = createSignal(i, { name: 'row index' });
105
- setIndex = setIdx;
106
- // Live accessor: reads the current list at this index. Falls back to the
107
- // last known value while the array is mid-shrink (idx out of bounds)
108
- // so render fns don't read undefined before disposal completes.
109
- const item = createMemo((prev) => {
110
- const cur = (list() || []);
111
- const v = cur[index()];
112
- return v !== undefined && v !== null ? v : prev;
113
- }, next[i]);
114
- rendered = mapFn(item, index);
127
+ const idx = lazyIndex(i);
128
+ setIndex = idx.set;
129
+ // The entry's own signal, set during reconciliation. A memo that
130
+ // read the whole list made every list write recompute every row;
131
+ // this way only a row whose item actually changed is notified.
132
+ // The signal also holds the last value while the array is
133
+ // mid-shrink, so render fns don't read undefined before disposal.
134
+ const [item, writeItem] = createSignal(next[i]);
135
+ setItem = (v) => writeItem(() => v);
136
+ rendered = mapFn(item, idx.index);
115
137
  });
116
- resultEntries.push({ rendered, dispose, setIndex });
138
+ resultEntries.push({ rendered, dispose, setIndex, setItem });
117
139
  }
118
140
  resultKeys.push(key);
119
141
  usedKeys.add(key);
@@ -1,5 +1,5 @@
1
1
  // src/version.generated.ts
2
- var VERSION = "1.0.0-alpha.104";
2
+ var VERSION = "1.0.0-alpha.106";
3
3
 
4
4
  // src/lib/signal/graph/state.ts
5
5
  var MAJOR = VERSION.split(".")[0];
@@ -141,6 +141,7 @@ var Computed = class extends Source {
141
141
  super();
142
142
  this._threw = false;
143
143
  this._initialized = false;
144
+ this._disposed = false;
144
145
  this.state = DIRTY;
145
146
  /** Nodes this computation read on its last run. Rebuilt on every run. */
146
147
  this.sources = null;
@@ -156,8 +157,23 @@ var Computed = class extends Source {
156
157
  if (this._threw) throw this._error;
157
158
  return this._value;
158
159
  }
160
+ /**
161
+ * Unlink from every source, for good. Without this, a source outlives the
162
+ * computation and keeps it (and everything its function captured) reachable
163
+ * through its observers set: a disposed list row stayed in memory that way.
164
+ * The last value stays readable; the node simply never updates again.
165
+ */
166
+ dispose() {
167
+ if (this._disposed) return;
168
+ this._disposed = true;
169
+ if (this.sources) {
170
+ for (const s of this.sources) s.observers?.delete(this);
171
+ this.sources = null;
172
+ }
173
+ }
159
174
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
160
175
  updateIfNecessary() {
176
+ if (this._disposed) return;
161
177
  if (this.state === CHECK) {
162
178
  if (this.sources) {
163
179
  for (const s of this.sources) {
@@ -201,7 +217,7 @@ var Computed = class extends Source {
201
217
  reportReactiveRun(this);
202
218
  if (changed) {
203
219
  this.version++;
204
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
220
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
205
221
  notifyWatchers(this);
206
222
  }
207
223
  }
@@ -215,7 +231,7 @@ var Computed = class extends Source {
215
231
  const wasClean = this.state === CLEAN;
216
232
  this.state = level;
217
233
  if (wasClean) {
218
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
234
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
219
235
  notifyWatchers(this);
220
236
  }
221
237
  }
@@ -360,13 +376,15 @@ function createSignal(value, options) {
360
376
  },
361
377
  enumerable: false
362
378
  });
363
- reportReactiveNode({
364
- kind: "signal",
365
- node: state,
366
- handle: read,
367
- name: options?.name,
368
- set: write
369
- });
379
+ if (isObserved()) {
380
+ reportReactiveNode({
381
+ kind: "signal",
382
+ node: state,
383
+ handle: read,
384
+ name: options?.name,
385
+ set: write
386
+ });
387
+ }
370
388
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
371
389
  return [read, write];
372
390
  }
@@ -390,8 +408,11 @@ function createMemo(fn, value, options) {
390
408
  const read = (() => c.get());
391
409
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
392
410
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
393
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
394
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
411
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
412
+ onCleanup(() => {
413
+ c.dispose();
414
+ if (isObserved()) reportReactiveDispose(c);
415
+ });
395
416
  return read;
396
417
  }
397
418
  function untrack2(fn) {
@@ -399,6 +420,20 @@ function untrack2(fn) {
399
420
  }
400
421
 
401
422
  // src/lib/signal/list.ts
423
+ function lazyIndex(initial) {
424
+ let current = initial;
425
+ let state = null;
426
+ return {
427
+ index: () => {
428
+ if (!state) state = createSignal(current, { name: "row index" });
429
+ return state[0]();
430
+ },
431
+ set: (n) => {
432
+ current = n;
433
+ if (state) state[1](n);
434
+ }
435
+ };
436
+ }
402
437
  function mapArray(list, mapFn, options) {
403
438
  let items = [];
404
439
  onCleanup(() => {
@@ -428,9 +463,9 @@ function mapArray(list, mapFn, options) {
428
463
  let rendered, setIndex, dispose;
429
464
  createRoot((d) => {
430
465
  dispose = d;
431
- const [index, setIdx] = createSignal(i, { name: "row index" });
432
- setIndex = setIdx;
433
- rendered = mapFn(value, index);
466
+ const idx = lazyIndex(i);
467
+ setIndex = idx.set;
468
+ rendered = mapFn(value, idx.index);
434
469
  });
435
470
  const entry = { item: value, rendered, dispose, setIndex };
436
471
  result.push(entry);
@@ -470,21 +505,19 @@ function keyArray(list, keyFn, mapFn, options) {
470
505
  const reuse = prevByKey.get(key);
471
506
  if (reuse && !usedKeys.has(key)) {
472
507
  reuse.setIndex(i);
508
+ reuse.setItem(next[i]);
473
509
  resultEntries.push(reuse);
474
510
  } else {
475
- let rendered, setIndex, dispose;
511
+ let rendered, setIndex, setItem, dispose;
476
512
  createRoot((d) => {
477
513
  dispose = d;
478
- const [index, setIdx] = createSignal(i, { name: "row index" });
479
- setIndex = setIdx;
480
- const item = createMemo((prev) => {
481
- const cur = list() || [];
482
- const v = cur[index()];
483
- return v !== void 0 && v !== null ? v : prev;
484
- }, next[i]);
485
- rendered = mapFn(item, index);
514
+ const idx = lazyIndex(i);
515
+ setIndex = idx.set;
516
+ const [item, writeItem] = createSignal(next[i]);
517
+ setItem = (v) => writeItem(() => v);
518
+ rendered = mapFn(item, idx.index);
486
519
  });
487
- resultEntries.push({ rendered, dispose, setIndex });
520
+ resultEntries.push({ rendered, dispose, setIndex, setItem });
488
521
  }
489
522
  resultKeys.push(key);
490
523
  usedKeys.add(key);