@fictjs/runtime 0.2.2 → 0.3.0

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 (58) hide show
  1. package/dist/advanced.cjs +10 -8
  2. package/dist/advanced.cjs.map +1 -1
  3. package/dist/advanced.d.cts +10 -16
  4. package/dist/advanced.d.ts +10 -16
  5. package/dist/advanced.js +5 -3
  6. package/dist/advanced.js.map +1 -1
  7. package/dist/{chunk-3U7EBKEU.cjs → chunk-ID3WBWNO.cjs} +559 -319
  8. package/dist/chunk-ID3WBWNO.cjs.map +1 -0
  9. package/dist/{chunk-3A4VW6AK.cjs → chunk-L4DIV3RC.cjs} +7 -7
  10. package/dist/{chunk-3A4VW6AK.cjs.map → chunk-L4DIV3RC.cjs.map} +1 -1
  11. package/dist/{chunk-URDFDRHR.cjs → chunk-M2TSXZ4C.cjs} +16 -16
  12. package/dist/{chunk-URDFDRHR.cjs.map → chunk-M2TSXZ4C.cjs.map} +1 -1
  13. package/dist/{chunk-YVS4WJ2W.js → chunk-SO6X7G5S.js} +558 -318
  14. package/dist/chunk-SO6X7G5S.js.map +1 -0
  15. package/dist/{chunk-LU2LD2WJ.js → chunk-TWELIZRY.js} +2 -2
  16. package/dist/{chunk-TEYUDPTA.js → chunk-XLIZJMMJ.js} +2 -2
  17. package/dist/{context-9gFXOdJl.d.cts → context-B25xyQrJ.d.cts} +36 -2
  18. package/dist/{context-4woHo7-L.d.ts → context-CGdP7_Jb.d.ts} +36 -2
  19. package/dist/{effect-ClARNUCc.d.cts → effect-D6kaLM2-.d.cts} +80 -1
  20. package/dist/{effect-ClARNUCc.d.ts → effect-D6kaLM2-.d.ts} +80 -1
  21. package/dist/index.cjs +40 -38
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +4 -4
  24. package/dist/index.d.ts +4 -4
  25. package/dist/index.dev.js +430 -246
  26. package/dist/index.dev.js.map +1 -1
  27. package/dist/index.js +4 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/internal.cjs +39 -35
  30. package/dist/internal.cjs.map +1 -1
  31. package/dist/internal.d.cts +8 -6
  32. package/dist/internal.d.ts +8 -6
  33. package/dist/internal.js +7 -3
  34. package/dist/internal.js.map +1 -1
  35. package/dist/{props-DAyeRPwH.d.ts → props-BEgIVMRx.d.ts} +8 -15
  36. package/dist/{props-CBwuh35e.d.cts → props-BIfromL0.d.cts} +8 -15
  37. package/dist/scope-Cx_3CjIZ.d.cts +18 -0
  38. package/dist/scope-CzNkn587.d.ts +18 -0
  39. package/package.json +1 -1
  40. package/src/advanced.ts +1 -0
  41. package/src/binding.ts +30 -4
  42. package/src/constants.ts +5 -0
  43. package/src/cycle-guard.ts +164 -103
  44. package/src/devtools.ts +22 -2
  45. package/src/dom.ts +84 -10
  46. package/src/hooks.ts +60 -13
  47. package/src/index.ts +3 -1
  48. package/src/internal.ts +2 -2
  49. package/src/lifecycle.ts +13 -5
  50. package/src/memo.ts +3 -4
  51. package/src/props.ts +16 -0
  52. package/src/signal.ts +204 -36
  53. package/dist/chunk-3U7EBKEU.cjs.map +0 -1
  54. package/dist/chunk-YVS4WJ2W.js.map +0 -1
  55. package/dist/scope-DvgMquEy.d.ts +0 -55
  56. package/dist/scope-xmdo6lVU.d.cts +0 -55
  57. /package/dist/{chunk-LU2LD2WJ.js.map → chunk-TWELIZRY.js.map} +0 -0
  58. /package/dist/{chunk-TEYUDPTA.js.map → chunk-XLIZJMMJ.js.map} +0 -0
package/dist/index.dev.js CHANGED
@@ -21,110 +21,158 @@ var endFlushGuard = () => {
21
21
  var enterRootGuard = () => true;
22
22
  var exitRootGuard = () => {
23
23
  };
24
- if (isDev) {
25
- const defaultOptions = {
26
- maxFlushCyclesPerMicrotask: 1e4,
27
- maxEffectRunsPerFlush: 2e4,
28
- windowSize: 5,
29
- highUsageRatio: 0.8,
30
- maxRootReentrantDepth: 10,
31
- enableWindowWarning: true,
32
- devMode: false
33
- };
34
- let options = {
35
- ...defaultOptions
36
- };
37
- let effectRunsThisFlush = 0;
38
- let windowUsage = [];
39
- let rootDepth = /* @__PURE__ */ new WeakMap();
40
- let flushWarned = false;
41
- let rootWarned = false;
42
- let windowWarned = false;
43
- setCycleProtectionOptions = (opts) => {
44
- options = { ...options, ...opts };
45
- };
46
- resetCycleProtectionStateForTests = () => {
47
- options = { ...defaultOptions };
48
- effectRunsThisFlush = 0;
49
- windowUsage = [];
50
- rootDepth = /* @__PURE__ */ new WeakMap();
51
- flushWarned = false;
52
- rootWarned = false;
53
- windowWarned = false;
54
- };
55
- beginFlushGuard = () => {
56
- effectRunsThisFlush = 0;
57
- flushWarned = false;
58
- windowWarned = false;
59
- };
60
- beforeEffectRunGuard = () => {
61
- const next = ++effectRunsThisFlush;
62
- if (next > options.maxFlushCyclesPerMicrotask || next > options.maxEffectRunsPerFlush) {
63
- const message = `[fict] cycle protection triggered: flush-budget-exceeded`;
64
- if (options.devMode) {
65
- throw new Error(message);
66
- }
67
- if (!flushWarned) {
68
- flushWarned = true;
69
- console.warn(message, { effectRuns: next });
70
- }
71
- return false;
24
+ var defaultOptions = {
25
+ enabled: true,
26
+ maxFlushCyclesPerMicrotask: 1e4,
27
+ maxEffectRunsPerFlush: 2e4,
28
+ windowSize: 5,
29
+ highUsageRatio: 0.8,
30
+ maxRootReentrantDepth: 10,
31
+ enableWindowWarning: true,
32
+ devMode: isDev,
33
+ // Backoff warning options
34
+ enableBackoffWarning: isDev,
35
+ backoffWarningRatio: 0.5
36
+ };
37
+ var enabled = defaultOptions.enabled;
38
+ var options = {
39
+ ...defaultOptions
40
+ };
41
+ var effectRunsThisFlush = 0;
42
+ var windowUsage = [];
43
+ var rootDepth = /* @__PURE__ */ new WeakMap();
44
+ var flushWarned = false;
45
+ var rootWarned = false;
46
+ var windowWarned = false;
47
+ var backoffWarned50 = false;
48
+ var backoffWarned75 = false;
49
+ setCycleProtectionOptions = (opts) => {
50
+ if (typeof opts.enabled === "boolean") {
51
+ enabled = opts.enabled;
52
+ }
53
+ options = { ...options, ...opts };
54
+ };
55
+ resetCycleProtectionStateForTests = () => {
56
+ options = { ...defaultOptions };
57
+ enabled = defaultOptions.enabled;
58
+ effectRunsThisFlush = 0;
59
+ windowUsage = [];
60
+ rootDepth = /* @__PURE__ */ new WeakMap();
61
+ flushWarned = false;
62
+ rootWarned = false;
63
+ windowWarned = false;
64
+ backoffWarned50 = false;
65
+ backoffWarned75 = false;
66
+ };
67
+ beginFlushGuard = () => {
68
+ if (!enabled) return;
69
+ effectRunsThisFlush = 0;
70
+ flushWarned = false;
71
+ windowWarned = false;
72
+ backoffWarned50 = false;
73
+ backoffWarned75 = false;
74
+ };
75
+ beforeEffectRunGuard = () => {
76
+ if (!enabled) return true;
77
+ const next = ++effectRunsThisFlush;
78
+ const limit = Math.min(options.maxFlushCyclesPerMicrotask, options.maxEffectRunsPerFlush);
79
+ if (options.enableBackoffWarning && isDev) {
80
+ const ratio = next / limit;
81
+ const backoffRatio = options.backoffWarningRatio ?? 0.5;
82
+ if (!backoffWarned50 && ratio >= backoffRatio && ratio < backoffRatio + 0.25) {
83
+ backoffWarned50 = true;
84
+ console.warn(
85
+ `[fict] cycle guard: approaching effect limit (${Math.round(ratio * 100)}% of budget used)
86
+ - Current: ${next} effects, Limit: ${limit}
87
+ - Tip: Check for effects that trigger other effects in a loop.
88
+ - Common causes: signal updates inside effects that read and write the same signal.`
89
+ );
90
+ } else if (!backoffWarned75 && ratio >= backoffRatio + 0.25 && ratio < 1) {
91
+ backoffWarned75 = true;
92
+ console.warn(
93
+ `[fict] cycle guard: nearing effect limit (${Math.round(ratio * 100)}% of budget used)
94
+ - Current: ${next} effects, Limit: ${limit}
95
+ - Warning: Consider breaking the reactive dependency cycle.
96
+ - Debug: Use browser devtools to identify the recursive effect chain.`
97
+ );
72
98
  }
73
- return true;
74
- };
75
- endFlushGuard = () => {
76
- recordWindowUsage(effectRunsThisFlush, options.maxFlushCyclesPerMicrotask);
77
- effectRunsThisFlush = 0;
78
- };
79
- enterRootGuard = (root) => {
80
- const depth = (rootDepth.get(root) ?? 0) + 1;
81
- if (depth > options.maxRootReentrantDepth) {
82
- const message = `[fict] cycle protection triggered: root-reentry`;
83
- if (options.devMode) {
84
- throw new Error(message);
85
- }
86
- if (!rootWarned) {
87
- rootWarned = true;
88
- console.warn(message, { depth });
89
- }
90
- return false;
99
+ }
100
+ if (next > limit) {
101
+ const message = `[fict] cycle protection triggered: flush-budget-exceeded`;
102
+ if (options.devMode) {
103
+ throw new Error(
104
+ message + `
105
+ - Effect runs: ${next}, Limit: ${limit}
106
+ - This indicates a reactive cycle where effects keep triggering each other.
107
+ - Check for patterns like: createEffect(() => { signal(); signal(newValue); })`
108
+ );
91
109
  }
92
- rootDepth.set(root, depth);
93
- return true;
94
- };
95
- exitRootGuard = (root) => {
96
- const depth = rootDepth.get(root);
97
- if (depth === void 0) return;
98
- if (depth <= 1) {
99
- rootDepth.delete(root);
100
- } else {
101
- rootDepth.set(root, depth - 1);
110
+ if (!flushWarned) {
111
+ flushWarned = true;
112
+ console.warn(message, { effectRuns: next, limit });
102
113
  }
103
- };
104
- const recordWindowUsage = (used, budget) => {
105
- if (!options.enableWindowWarning) return;
106
- const entry = { used, budget };
107
- windowUsage.push(entry);
108
- if (windowUsage.length > options.windowSize) {
109
- windowUsage.shift();
110
- }
111
- if (windowWarned) return;
112
- if (windowUsage.length >= options.windowSize && windowUsage.every(
113
- (item) => item.budget > 0 && item.used / item.budget >= options.highUsageRatio
114
- )) {
115
- windowWarned = true;
116
- reportCycle("high-usage-window", {
117
- windowSize: options.windowSize,
118
- ratio: options.highUsageRatio
119
- });
114
+ return false;
115
+ }
116
+ return true;
117
+ };
118
+ endFlushGuard = () => {
119
+ if (!enabled) return;
120
+ recordWindowUsage(effectRunsThisFlush, options.maxFlushCyclesPerMicrotask);
121
+ effectRunsThisFlush = 0;
122
+ };
123
+ enterRootGuard = (root) => {
124
+ if (!enabled) return true;
125
+ const depth = (rootDepth.get(root) ?? 0) + 1;
126
+ if (depth > options.maxRootReentrantDepth) {
127
+ const message = `[fict] cycle protection triggered: root-reentry`;
128
+ if (options.devMode) {
129
+ throw new Error(
130
+ message + `
131
+ - Re-entry depth: ${depth}, Max allowed: ${options.maxRootReentrantDepth}
132
+ - This indicates recursive render() or component initialization.
133
+ - Check for components that trigger re-renders during their own render phase.`
134
+ );
120
135
  }
121
- };
122
- const reportCycle = (reason, detail = void 0) => {
123
- const hook = getDevtoolsHook();
124
- hook?.cycleDetected?.(detail ? { reason, detail } : { reason });
125
- console.warn(`[fict] cycle protection triggered: ${reason}`, detail ?? "");
126
- };
127
- }
136
+ if (!rootWarned) {
137
+ rootWarned = true;
138
+ console.warn(message, { depth, maxAllowed: options.maxRootReentrantDepth });
139
+ }
140
+ return false;
141
+ }
142
+ rootDepth.set(root, depth);
143
+ return true;
144
+ };
145
+ exitRootGuard = (root) => {
146
+ if (!enabled) return;
147
+ const depth = rootDepth.get(root);
148
+ if (depth === void 0) return;
149
+ if (depth <= 1) {
150
+ rootDepth.delete(root);
151
+ } else {
152
+ rootDepth.set(root, depth - 1);
153
+ }
154
+ };
155
+ var recordWindowUsage = (used, budget) => {
156
+ if (!options.enableWindowWarning) return;
157
+ const entry = { used, budget };
158
+ windowUsage.push(entry);
159
+ if (windowUsage.length > options.windowSize) {
160
+ windowUsage.shift();
161
+ }
162
+ if (windowWarned) return;
163
+ if (windowUsage.length >= options.windowSize && windowUsage.every((item) => item.budget > 0 && item.used / item.budget >= options.highUsageRatio)) {
164
+ windowWarned = true;
165
+ reportCycle("high-usage-window", {
166
+ windowSize: options.windowSize,
167
+ ratio: options.highUsageRatio
168
+ });
169
+ }
170
+ };
171
+ var reportCycle = (reason, detail = void 0) => {
172
+ const hook = getDevtoolsHook();
173
+ hook?.cycleDetected?.(detail ? { reason, detail } : { reason });
174
+ console.warn(`[fict] cycle protection triggered: ${reason}`, detail ?? "");
175
+ };
128
176
 
129
177
  // src/lifecycle.ts
130
178
  var isDev2 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
@@ -173,13 +221,19 @@ function onCleanup(fn) {
173
221
  function flushOnMount(root) {
174
222
  const cbs = root.onMountCallbacks;
175
223
  if (!cbs || cbs.length === 0) return;
176
- for (let i = 0; i < cbs.length; i++) {
177
- const cleanup = cbs[i]();
178
- if (typeof cleanup === "function") {
179
- root.cleanups.push(cleanup);
224
+ const prevRoot = currentRoot;
225
+ currentRoot = root;
226
+ try {
227
+ for (let i = 0; i < cbs.length; i++) {
228
+ const cleanup = cbs[i]();
229
+ if (typeof cleanup === "function") {
230
+ root.cleanups.push(cleanup);
231
+ }
180
232
  }
233
+ } finally {
234
+ currentRoot = prevRoot;
235
+ cbs.length = 0;
181
236
  }
182
- cbs.length = 0;
183
237
  }
184
238
  function registerRootCleanup(fn) {
185
239
  if (currentRoot) {
@@ -208,8 +262,8 @@ function destroyRoot(root) {
208
262
  globalSuspenseHandlers.delete(root);
209
263
  }
210
264
  }
211
- function createRoot(fn, options) {
212
- const parent = options?.inherit ? currentRoot : void 0;
265
+ function createRoot(fn, options2) {
266
+ const parent = options2?.inherit ? currentRoot : void 0;
213
267
  const root = createRootContext(parent);
214
268
  const prev = pushRoot(root);
215
269
  let value;
@@ -364,8 +418,98 @@ function handleSuspend(token, startRoot) {
364
418
  return false;
365
419
  }
366
420
 
367
- // src/signal.ts
421
+ // src/effect.ts
422
+ function createEffect(fn) {
423
+ let cleanups = [];
424
+ const rootForError = getCurrentRoot();
425
+ const doCleanup = () => {
426
+ runCleanupList(cleanups);
427
+ cleanups = [];
428
+ };
429
+ const run = () => {
430
+ const bucket = [];
431
+ withEffectCleanups(bucket, () => {
432
+ try {
433
+ const maybeCleanup = fn();
434
+ if (typeof maybeCleanup === "function") {
435
+ bucket.push(maybeCleanup);
436
+ }
437
+ } catch (err) {
438
+ if (handleSuspend(err, rootForError)) {
439
+ return;
440
+ }
441
+ if (handleError(err, { source: "effect" }, rootForError)) {
442
+ return;
443
+ }
444
+ throw err;
445
+ }
446
+ });
447
+ cleanups = bucket;
448
+ };
449
+ const disposeEffect = effectWithCleanup(run, doCleanup, rootForError);
450
+ const teardown = () => {
451
+ runCleanupList(cleanups);
452
+ disposeEffect();
453
+ };
454
+ registerRootCleanup(teardown);
455
+ return teardown;
456
+ }
457
+ function createRenderEffect(fn) {
458
+ let cleanup;
459
+ const rootForError = getCurrentRoot();
460
+ const doCleanup = () => {
461
+ if (cleanup) {
462
+ cleanup();
463
+ cleanup = void 0;
464
+ }
465
+ };
466
+ const run = () => {
467
+ try {
468
+ const maybeCleanup = fn();
469
+ if (typeof maybeCleanup === "function") {
470
+ cleanup = maybeCleanup;
471
+ }
472
+ } catch (err) {
473
+ if (handleSuspend(err, rootForError)) {
474
+ return;
475
+ }
476
+ const handled = handleError(err, { source: "effect" }, rootForError);
477
+ if (handled) {
478
+ return;
479
+ }
480
+ throw err;
481
+ }
482
+ };
483
+ const disposeEffect = effectWithCleanup(run, doCleanup, rootForError);
484
+ const teardown = () => {
485
+ if (cleanup) {
486
+ cleanup();
487
+ cleanup = void 0;
488
+ }
489
+ disposeEffect();
490
+ };
491
+ registerRootCleanup(teardown);
492
+ return teardown;
493
+ }
494
+
495
+ // src/hooks.ts
368
496
  var isDev3 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
497
+ var ctxStack = [];
498
+ function __fictPushContext() {
499
+ const ctx = { slots: [], cursor: 0 };
500
+ ctxStack.push(ctx);
501
+ return ctx;
502
+ }
503
+ function __fictGetCurrentComponentId() {
504
+ return ctxStack[ctxStack.length - 1]?.componentId;
505
+ }
506
+ function __fictPopContext() {
507
+ const ctx = ctxStack.pop();
508
+ if (ctx) ctx.rendering = false;
509
+ }
510
+
511
+ // src/signal.ts
512
+ var isDev4 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
369
513
  var Mutable = 1;
370
514
  var Watching = 2;
371
515
  var Running = 4;
@@ -410,6 +554,7 @@ function link(dep, sub, version) {
410
554
  else sub.deps = newLink;
411
555
  if (prevSub !== void 0) prevSub.nextSub = newLink;
412
556
  else dep.subs = newLink;
557
+ if (isDev4) trackDependencyDevtools(dep, sub);
413
558
  }
414
559
  function unlink(lnk, sub = lnk.sub) {
415
560
  const dep = lnk.dep;
@@ -425,6 +570,7 @@ function unlink(lnk, sub = lnk.sub) {
425
570
  else dep.subsTail = prevSub;
426
571
  if (prevSub !== void 0) prevSub.nextSub = nextSub;
427
572
  else if ((dep.subs = nextSub) === void 0) unwatched(dep);
573
+ if (isDev4) untrackDependencyDevtools(dep, sub);
428
574
  return nextDep;
429
575
  }
430
576
  function unwatched(dep) {
@@ -581,6 +727,11 @@ function shallowPropagate(firstLink) {
581
727
  function update(node) {
582
728
  return "getter" in node && node.getter !== void 0 ? updateComputed(node) : updateSignal(node);
583
729
  }
730
+ function valuesDiffer(node, prev, next) {
731
+ if (node.equals === false) return true;
732
+ if (typeof node.equals === "function") return !node.equals(prev, next);
733
+ return prev !== next;
734
+ }
584
735
  function notify(effect) {
585
736
  effect.flags &= ~Watching;
586
737
  const effects = [];
@@ -617,7 +768,7 @@ function updateSignal(s) {
617
768
  s.flags = Mutable;
618
769
  const current = s.currentValue;
619
770
  const pending = s.pendingValue;
620
- if (current !== pending) {
771
+ if (valuesDiffer(s, current, pending)) {
621
772
  s.currentValue = pending;
622
773
  return true;
623
774
  }
@@ -635,8 +786,9 @@ function updateComputed(c) {
635
786
  activeSub = prevSub;
636
787
  c.flags &= ~Running;
637
788
  purgeDeps(c);
638
- if (oldValue !== newValue) {
789
+ if (valuesDiffer(c, oldValue, newValue)) {
639
790
  c.value = newValue;
791
+ if (isDev4) updateComputedDevtools(c, newValue);
640
792
  return true;
641
793
  }
642
794
  return false;
@@ -658,7 +810,7 @@ function runEffect(e) {
658
810
  }
659
811
  }
660
812
  ++cycle;
661
- effectRunDevtools(e);
813
+ if (isDev4) effectRunDevtools(e);
662
814
  e.depsTail = void 0;
663
815
  e.flags = WatchingRunning;
664
816
  const prevSub = activeSub;
@@ -702,7 +854,7 @@ function runEffect(e) {
702
854
  }
703
855
  if (isDirty) {
704
856
  ++cycle;
705
- effectRunDevtools(e);
857
+ if (isDev4) effectRunDevtools(e);
706
858
  e.depsTail = void 0;
707
859
  e.flags = WatchingRunning;
708
860
  const prevSub = activeSub;
@@ -751,10 +903,21 @@ function flush() {
751
903
  while (highIndex < highPriorityQueue.length) {
752
904
  const e = highPriorityQueue[highIndex];
753
905
  if (!beforeEffectRunGuard()) {
754
- if (highIndex > 0) {
755
- highPriorityQueue.copyWithin(0, highIndex);
756
- highPriorityQueue.length -= highIndex;
906
+ for (let i = 0; i < highPriorityQueue.length; i++) {
907
+ const queued = highPriorityQueue[i];
908
+ if (queued && queued.flags !== 0) {
909
+ queued.flags = Watching;
910
+ }
911
+ }
912
+ for (let i = 0; i < lowPriorityQueue.length; i++) {
913
+ const queued = lowPriorityQueue[i];
914
+ if (queued && queued.flags !== 0) {
915
+ queued.flags = Watching;
916
+ }
757
917
  }
918
+ highPriorityQueue.length = 0;
919
+ lowPriorityQueue.length = 0;
920
+ flushScheduled = false;
758
921
  endFlushGuard();
759
922
  return;
760
923
  }
@@ -775,10 +938,21 @@ function flush() {
775
938
  }
776
939
  const e = lowPriorityQueue[lowIndex];
777
940
  if (!beforeEffectRunGuard()) {
778
- if (lowIndex > 0) {
779
- lowPriorityQueue.copyWithin(0, lowIndex);
780
- lowPriorityQueue.length -= lowIndex;
941
+ for (let i = 0; i < highPriorityQueue.length; i++) {
942
+ const queued = highPriorityQueue[i];
943
+ if (queued && queued.flags !== 0) {
944
+ queued.flags = Watching;
945
+ }
781
946
  }
947
+ for (let i = 0; i < lowPriorityQueue.length; i++) {
948
+ const queued = lowPriorityQueue[i];
949
+ if (queued && queued.flags !== 0) {
950
+ queued.flags = Watching;
951
+ }
952
+ }
953
+ highPriorityQueue.length = 0;
954
+ lowPriorityQueue.length = 0;
955
+ flushScheduled = false;
782
956
  endFlushGuard();
783
957
  return;
784
958
  }
@@ -788,26 +962,31 @@ function flush() {
788
962
  lowPriorityQueue.length = 0;
789
963
  endFlushGuard();
790
964
  }
791
- function signal(initialValue) {
965
+ function signal(initialValue, options2) {
792
966
  const s = {
793
967
  currentValue: initialValue,
794
968
  pendingValue: initialValue,
795
969
  subs: void 0,
796
970
  subsTail: void 0,
797
971
  flags: Mutable,
798
- __id: void 0
972
+ __id: void 0,
973
+ ...options2?.equals !== void 0 ? { equals: options2.equals } : {},
974
+ ...options2?.name !== void 0 ? { name: options2.name } : {},
975
+ ...options2?.devToolsSource !== void 0 ? { devToolsSource: options2.devToolsSource } : {}
799
976
  };
800
- registerSignalDevtools(initialValue, s);
977
+ if (isDev4) registerSignalDevtools(s);
801
978
  const accessor = signalOper.bind(s);
802
979
  accessor[SIGNAL_MARKER] = true;
803
980
  return accessor;
804
981
  }
805
982
  function signalOper(value) {
806
983
  if (arguments.length > 0) {
807
- if (this.pendingValue !== value) {
808
- this.pendingValue = value;
984
+ const next = value;
985
+ const prev = this.pendingValue;
986
+ if (valuesDiffer(this, prev, next)) {
987
+ this.pendingValue = next;
809
988
  this.flags = MutableDirty;
810
- updateSignalDevtools(this, value);
989
+ if (isDev4) updateSignalDevtools(this, next);
811
990
  const subs = this.subs;
812
991
  if (subs !== void 0) {
813
992
  propagate(subs);
@@ -834,7 +1013,7 @@ function signalOper(value) {
834
1013
  }
835
1014
  return this.currentValue;
836
1015
  }
837
- function computed(getter) {
1016
+ function computed(getter, options2) {
838
1017
  const c = {
839
1018
  value: void 0,
840
1019
  subs: void 0,
@@ -842,13 +1021,21 @@ function computed(getter) {
842
1021
  deps: void 0,
843
1022
  depsTail: void 0,
844
1023
  flags: 0,
845
- getter
1024
+ getter,
1025
+ __id: void 0,
1026
+ ...options2?.equals !== void 0 ? { equals: options2.equals } : {},
1027
+ ...options2?.name !== void 0 ? { name: options2.name } : {},
1028
+ ...options2?.devToolsSource !== void 0 ? { devToolsSource: options2.devToolsSource } : {}
846
1029
  };
847
- const bound = computedOper.bind(c);
1030
+ if (isDev4) registerComputedDevtools(c);
1031
+ const bound = computedOper.bind(
1032
+ c
1033
+ );
848
1034
  bound[COMPUTED_MARKER] = true;
849
1035
  return bound;
850
1036
  }
851
1037
  function computedOper() {
1038
+ if (inCleanup) return this.value;
852
1039
  const flags = this.flags;
853
1040
  if (flags & Dirty) {
854
1041
  if (updateComputed(this)) {
@@ -869,6 +1056,7 @@ function computedOper() {
869
1056
  const prevSub = setActiveSub(this);
870
1057
  try {
871
1058
  this.value = this.getter(void 0);
1059
+ if (isDev4) updateComputedDevtools(this, this.value);
872
1060
  } finally {
873
1061
  setActiveSub(prevSub);
874
1062
  this.flags &= ~Running;
@@ -892,12 +1080,12 @@ function effectWithCleanup(fn, cleanupRunner, root) {
892
1080
  if (resolvedRoot) {
893
1081
  e.root = resolvedRoot;
894
1082
  }
895
- registerEffectDevtools(e);
1083
+ if (isDev4) registerEffectDevtools(e);
896
1084
  const prevSub = activeSub;
897
1085
  if (prevSub !== void 0) link(e, prevSub, 0);
898
1086
  activeSub = e;
899
1087
  try {
900
- effectRunDevtools(e);
1088
+ if (isDev4) effectRunDevtools(e);
901
1089
  fn();
902
1090
  } finally {
903
1091
  activeSub = prevSub;
@@ -969,17 +1157,28 @@ function setTransitionContext(value) {
969
1157
  var registerSignalDevtools = () => void 0;
970
1158
  var updateSignalDevtools = () => {
971
1159
  };
1160
+ var registerComputedDevtools = () => void 0;
1161
+ var updateComputedDevtools = () => {
1162
+ };
972
1163
  var registerEffectDevtools = () => void 0;
973
1164
  var effectRunDevtools = () => {
974
1165
  };
975
- if (isDev3) {
976
- let devtoolsSignalId = 0;
977
- let devtoolsEffectId = 0;
978
- registerSignalDevtools = (value, node) => {
1166
+ var trackDependencyDevtools = () => {
1167
+ };
1168
+ var untrackDependencyDevtools = () => {
1169
+ };
1170
+ if (isDev4) {
1171
+ let nextDevtoolsId = 0;
1172
+ registerSignalDevtools = (node) => {
979
1173
  const hook = getDevtoolsHook();
980
1174
  if (!hook) return void 0;
981
- const id = ++devtoolsSignalId;
982
- hook.registerSignal(id, value);
1175
+ const id = ++nextDevtoolsId;
1176
+ const options2 = {};
1177
+ if (node.name !== void 0) options2.name = node.name;
1178
+ if (node.devToolsSource !== void 0) options2.source = node.devToolsSource;
1179
+ const ownerId = __fictGetCurrentComponentId();
1180
+ if (ownerId !== void 0) options2.ownerId = ownerId;
1181
+ hook.registerSignal(id, node.currentValue, options2);
983
1182
  node.__id = id;
984
1183
  return id;
985
1184
  };
@@ -989,11 +1188,32 @@ if (isDev3) {
989
1188
  const id = node.__id;
990
1189
  if (id) hook.updateSignal(id, value);
991
1190
  };
1191
+ registerComputedDevtools = (node) => {
1192
+ const hook = getDevtoolsHook();
1193
+ if (!hook) return void 0;
1194
+ const id = ++nextDevtoolsId;
1195
+ const options2 = {};
1196
+ if (node.name !== void 0) options2.name = node.name;
1197
+ if (node.devToolsSource !== void 0) options2.source = node.devToolsSource;
1198
+ const ownerId = __fictGetCurrentComponentId();
1199
+ if (ownerId !== void 0) options2.ownerId = ownerId;
1200
+ options2.hasValue = false;
1201
+ hook.registerComputed(id, node.value, options2);
1202
+ node.__id = id;
1203
+ return id;
1204
+ };
1205
+ updateComputedDevtools = (node, value) => {
1206
+ const hook = getDevtoolsHook();
1207
+ if (!hook) return;
1208
+ const id = node.__id;
1209
+ if (id) hook.updateComputed(id, value);
1210
+ };
992
1211
  registerEffectDevtools = (node) => {
993
1212
  const hook = getDevtoolsHook();
994
1213
  if (!hook) return void 0;
995
- const id = ++devtoolsEffectId;
996
- hook.registerEffect(id);
1214
+ const id = ++nextDevtoolsId;
1215
+ const ownerId = __fictGetCurrentComponentId();
1216
+ hook.registerEffect(id, ownerId !== void 0 ? { ownerId } : void 0);
997
1217
  node.__id = id;
998
1218
  return id;
999
1219
  };
@@ -1003,85 +1223,25 @@ if (isDev3) {
1003
1223
  const id = node.__id;
1004
1224
  if (id) hook.effectRun(id);
1005
1225
  };
1006
- }
1007
-
1008
- // src/memo.ts
1009
- function createMemo(fn) {
1010
- return computed(fn);
1011
- }
1012
-
1013
- // src/effect.ts
1014
- function createEffect(fn) {
1015
- let cleanups = [];
1016
- const rootForError = getCurrentRoot();
1017
- const doCleanup = () => {
1018
- runCleanupList(cleanups);
1019
- cleanups = [];
1020
- };
1021
- const run = () => {
1022
- const bucket = [];
1023
- withEffectCleanups(bucket, () => {
1024
- try {
1025
- const maybeCleanup = fn();
1026
- if (typeof maybeCleanup === "function") {
1027
- bucket.push(maybeCleanup);
1028
- }
1029
- } catch (err) {
1030
- if (handleSuspend(err, rootForError)) {
1031
- return;
1032
- }
1033
- if (handleError(err, { source: "effect" }, rootForError)) {
1034
- return;
1035
- }
1036
- throw err;
1037
- }
1038
- });
1039
- cleanups = bucket;
1226
+ trackDependencyDevtools = (dep, sub) => {
1227
+ const hook = getDevtoolsHook();
1228
+ if (!hook?.trackDependency) return;
1229
+ const depId = dep.__id;
1230
+ const subId = sub.__id;
1231
+ if (depId && subId) hook.trackDependency(subId, depId);
1040
1232
  };
1041
- const disposeEffect = effectWithCleanup(run, doCleanup, rootForError);
1042
- const teardown = () => {
1043
- runCleanupList(cleanups);
1044
- disposeEffect();
1233
+ untrackDependencyDevtools = (dep, sub) => {
1234
+ const hook = getDevtoolsHook();
1235
+ if (!hook?.untrackDependency) return;
1236
+ const depId = dep.__id;
1237
+ const subId = sub.__id;
1238
+ if (depId && subId) hook.untrackDependency(subId, depId);
1045
1239
  };
1046
- registerRootCleanup(teardown);
1047
- return teardown;
1048
1240
  }
1049
- function createRenderEffect(fn) {
1050
- let cleanup;
1051
- const rootForError = getCurrentRoot();
1052
- const doCleanup = () => {
1053
- if (cleanup) {
1054
- cleanup();
1055
- cleanup = void 0;
1056
- }
1057
- };
1058
- const run = () => {
1059
- try {
1060
- const maybeCleanup = fn();
1061
- if (typeof maybeCleanup === "function") {
1062
- cleanup = maybeCleanup;
1063
- }
1064
- } catch (err) {
1065
- if (handleSuspend(err, rootForError)) {
1066
- return;
1067
- }
1068
- const handled = handleError(err, { source: "effect" }, rootForError);
1069
- if (handled) {
1070
- return;
1071
- }
1072
- throw err;
1073
- }
1074
- };
1075
- const disposeEffect = effectWithCleanup(run, doCleanup, rootForError);
1076
- const teardown = () => {
1077
- if (cleanup) {
1078
- cleanup();
1079
- cleanup = void 0;
1080
- }
1081
- disposeEffect();
1082
- };
1083
- registerRootCleanup(teardown);
1084
- return teardown;
1241
+
1242
+ // src/memo.ts
1243
+ function createMemo(fn, options2) {
1244
+ return computed(fn, options2);
1085
1245
  }
1086
1246
 
1087
1247
  // src/ref.ts
@@ -1165,8 +1325,8 @@ var DelegatedEventNames = [
1165
1325
  ];
1166
1326
 
1167
1327
  // src/constants.ts
1168
- var isDev4 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
1169
- var booleans = isDev4 ? [
1328
+ var isDev5 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
1329
+ var booleans = isDev5 ? [
1170
1330
  "allowfullscreen",
1171
1331
  "async",
1172
1332
  "alpha",
@@ -1216,7 +1376,7 @@ var booleans = isDev4 ? [
1216
1376
  "sharedstoragewritable"
1217
1377
  ] : [];
1218
1378
  var BooleanAttributes = new Set(booleans);
1219
- var properties = isDev4 ? [
1379
+ var properties = isDev5 ? [
1220
1380
  // Core properties
1221
1381
  "className",
1222
1382
  "value",
@@ -1252,7 +1412,7 @@ var ChildProperties = /* @__PURE__ */ new Set([
1252
1412
  "innerText",
1253
1413
  "children"
1254
1414
  ]);
1255
- var PropAliases = isDev4 ? {
1415
+ var PropAliases = isDev5 ? {
1256
1416
  // Direct mapping
1257
1417
  class: "className",
1258
1418
  // Element-specific mappings
@@ -1341,7 +1501,7 @@ var PropAliases = isDev4 ? {
1341
1501
  }
1342
1502
  } : {};
1343
1503
  function getPropAlias(prop2, tagName) {
1344
- if (!isDev4) return void 0;
1504
+ if (!isDev5) return void 0;
1345
1505
  const a = PropAliases[prop2];
1346
1506
  if (typeof a === "object") {
1347
1507
  return a[tagName] ? a["$"] : void 0;
@@ -1350,7 +1510,7 @@ function getPropAlias(prop2, tagName) {
1350
1510
  }
1351
1511
  var $$EVENTS = "_$FICT_DELEGATE";
1352
1512
  var DelegatedEvents = new Set(DelegatedEventNames);
1353
- var svgElements = isDev4 ? [
1513
+ var svgElements = isDev5 ? [
1354
1514
  "altGlyph",
1355
1515
  "altGlyphDef",
1356
1516
  "altGlyphItem",
@@ -1434,7 +1594,7 @@ var SVGNamespace = {
1434
1594
  xlink: "http://www.w3.org/1999/xlink",
1435
1595
  xml: "http://www.w3.org/XML/1998/namespace"
1436
1596
  };
1437
- var unitlessList = isDev4 ? [
1597
+ var unitlessList = isDev5 ? [
1438
1598
  "animationIterationCount",
1439
1599
  "animation-iteration-count",
1440
1600
  "borderImageOutset",
@@ -1657,13 +1817,22 @@ function removeNodes(nodes) {
1657
1817
  }
1658
1818
 
1659
1819
  // src/binding.ts
1660
- var isDev5 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
1820
+ var isDev6 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
1661
1821
  function isReactive(value) {
1662
1822
  if (typeof value !== "function") return false;
1663
1823
  if (isSignal(value) || isComputed(value)) return true;
1664
1824
  if (isEffect(value) || isEffectScope(value)) return false;
1665
1825
  return value.length === 0;
1666
1826
  }
1827
+ function isStrictlyReactive(value) {
1828
+ if (typeof value !== "function") return false;
1829
+ return isSignal(value) || isComputed(value) || isPropGetterFn(value);
1830
+ }
1831
+ var PROP_GETTER_MARKER = Symbol.for("fict:prop-getter");
1832
+ function isPropGetterFn(value) {
1833
+ if (typeof value !== "function") return false;
1834
+ return value[PROP_GETTER_MARKER] === true;
1835
+ }
1667
1836
  function callEventHandler(handler, event, node, data) {
1668
1837
  if (!handler) return;
1669
1838
  const context = node ?? event.currentTarget ?? void 0;
@@ -1750,7 +1919,7 @@ function applyStyle(el, value, prev) {
1750
1919
  }
1751
1920
  }
1752
1921
  }
1753
- var isUnitlessStyleProperty = isDev5 ? (prop2) => UnitlessStyles.has(prop2) : (prop2) => prop2 === "opacity" || prop2 === "zIndex";
1922
+ var isUnitlessStyleProperty = isDev6 ? (prop2) => UnitlessStyles.has(prop2) : (prop2) => prop2 === "opacity" || prop2 === "zIndex";
1754
1923
  function createClassBinding(el, value) {
1755
1924
  if (isReactive(value)) {
1756
1925
  let prev = {};
@@ -1953,15 +2122,15 @@ function globalEventHandler(e) {
1953
2122
  }
1954
2123
  retarget(oriTarget);
1955
2124
  }
1956
- function bindEvent(el, eventName, handler, options) {
2125
+ function bindEvent(el, eventName, handler, options2) {
1957
2126
  if (handler == null) return () => {
1958
2127
  };
1959
2128
  const rootRef = getCurrentRoot();
1960
- const shouldDelegate = options == null && DelegatedEvents.has(eventName);
2129
+ const shouldDelegate = options2 == null && DelegatedEvents.has(eventName);
1961
2130
  if (shouldDelegate) {
1962
2131
  const key = `$$${eventName}`;
1963
2132
  delegateEvents([eventName]);
1964
- const resolveHandler = isReactive(handler) ? handler : () => handler;
2133
+ const resolveHandler = isStrictlyReactive(handler) ? handler : () => handler;
1965
2134
  el[key] = function(...args) {
1966
2135
  try {
1967
2136
  const fn = resolveHandler();
@@ -1976,7 +2145,7 @@ function bindEvent(el, eventName, handler, options) {
1976
2145
  el[key] = void 0;
1977
2146
  };
1978
2147
  }
1979
- const getHandler = isReactive(handler) ? handler : () => handler;
2148
+ const getHandler = isStrictlyReactive(handler) ? handler : () => handler;
1980
2149
  const wrapped = (event) => {
1981
2150
  try {
1982
2151
  const resolved = getHandler();
@@ -1988,8 +2157,8 @@ function bindEvent(el, eventName, handler, options) {
1988
2157
  throw err;
1989
2158
  }
1990
2159
  };
1991
- el.addEventListener(eventName, wrapped, options);
1992
- const cleanup = () => el.removeEventListener(eventName, wrapped, options);
2160
+ el.addEventListener(eventName, wrapped, options2);
2161
+ const cleanup = () => el.removeEventListener(eventName, wrapped, options2);
1993
2162
  registerRootCleanup(cleanup);
1994
2163
  return cleanup;
1995
2164
  }
@@ -2064,20 +2233,8 @@ function createPortal(container, render2, createElementFn) {
2064
2233
  };
2065
2234
  }
2066
2235
 
2067
- // src/hooks.ts
2068
- var isDev6 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
2069
- var ctxStack = [];
2070
- function __fictPushContext() {
2071
- const ctx = { slots: [], cursor: 0 };
2072
- ctxStack.push(ctx);
2073
- return ctx;
2074
- }
2075
- function __fictPopContext() {
2076
- ctxStack.pop();
2077
- }
2078
-
2079
2236
  // src/props.ts
2080
- var PROP_GETTER_MARKER = Symbol.for("fict:prop-getter");
2237
+ var PROP_GETTER_MARKER2 = Symbol.for("fict:prop-getter");
2081
2238
  var propGetters = /* @__PURE__ */ new WeakSet();
2082
2239
  var rawToProxy = /* @__PURE__ */ new WeakMap();
2083
2240
  var proxyToRaw = /* @__PURE__ */ new WeakMap();
@@ -2087,7 +2244,7 @@ function __fictProp(getter) {
2087
2244
  if (Object.isExtensible(getter)) {
2088
2245
  try {
2089
2246
  ;
2090
- getter[PROP_GETTER_MARKER] = true;
2247
+ getter[PROP_GETTER_MARKER2] = true;
2091
2248
  } catch {
2092
2249
  }
2093
2250
  }
@@ -2097,7 +2254,7 @@ function __fictProp(getter) {
2097
2254
  function isPropGetter(value) {
2098
2255
  if (typeof value !== "function") return false;
2099
2256
  const fn = value;
2100
- return propGetters.has(fn) || fn[PROP_GETTER_MARKER] === true;
2257
+ return propGetters.has(fn) || fn[PROP_GETTER_MARKER2] === true;
2101
2258
  }
2102
2259
  function createPropsProxy(props) {
2103
2260
  if (!props || typeof props !== "object") {
@@ -2211,12 +2368,19 @@ function mergeProps(...sources) {
2211
2368
  }
2212
2369
  });
2213
2370
  }
2214
- function prop(getter, options) {
2371
+ function keyed(target, key, options2) {
2372
+ return prop(() => {
2373
+ const resolvedTarget = isPropGetter(target) ? target() : target;
2374
+ const resolvedKey = typeof key === "function" ? key() : key;
2375
+ return resolvedTarget[resolvedKey];
2376
+ }, options2);
2377
+ }
2378
+ function prop(getter, options2) {
2215
2379
  if (isPropGetter(getter)) {
2216
2380
  return getter;
2217
2381
  }
2218
2382
  const fn = getter;
2219
- const unwrap = options?.unwrap !== false;
2383
+ const unwrap = options2?.unwrap !== false;
2220
2384
  return __fictProp(
2221
2385
  createMemo(() => {
2222
2386
  const value = fn();
@@ -2232,6 +2396,7 @@ function prop(getter, options) {
2232
2396
  var SVG_NS = "http://www.w3.org/2000/svg";
2233
2397
  var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
2234
2398
  var isDev7 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
2399
+ var nextComponentId = 1;
2235
2400
  function render(view, container) {
2236
2401
  const root = createRootContext();
2237
2402
  const prev = pushRoot(root);
@@ -2324,9 +2489,27 @@ function createElementWithContext(node, namespace) {
2324
2489
  }
2325
2490
  });
2326
2491
  const props = createPropsProxy(baseProps);
2327
- __fictPushContext();
2492
+ const hook = isDev7 ? getDevtoolsHook() : void 0;
2493
+ const parentId = hook ? __fictGetCurrentComponentId() : void 0;
2494
+ const componentId = hook ? nextComponentId++ : void 0;
2495
+ if (hook?.registerComponent && componentId !== void 0) {
2496
+ hook.registerComponent(componentId, vnode.type.name || "Anonymous", parentId);
2497
+ }
2498
+ const ctx = __fictPushContext();
2499
+ if (componentId !== void 0) {
2500
+ ctx.componentId = componentId;
2501
+ if (parentId !== void 0) {
2502
+ ctx.parentId = parentId;
2503
+ }
2504
+ }
2328
2505
  try {
2329
2506
  const rendered = vnode.type(props);
2507
+ if (hook && componentId !== void 0) {
2508
+ onMount(() => {
2509
+ hook.componentMount?.(componentId);
2510
+ });
2511
+ onCleanup(() => hook.componentUnmount?.(componentId));
2512
+ }
2330
2513
  return createElementWithContext(rendered, namespace);
2331
2514
  } catch (err) {
2332
2515
  if (handleSuspend(err)) {
@@ -2948,6 +3131,7 @@ export {
2948
3131
  createRoot,
2949
3132
  createSuspenseToken,
2950
3133
  hasContext,
3134
+ keyed,
2951
3135
  mergeProps,
2952
3136
  onCleanup,
2953
3137
  onDestroy,