@fictjs/runtime 0.2.3 → 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 +8 -16
  4. package/dist/advanced.d.ts +8 -16
  5. package/dist/advanced.js +5 -3
  6. package/dist/advanced.js.map +1 -1
  7. package/dist/{chunk-2U6M3LKS.cjs → chunk-ID3WBWNO.cjs} +452 -219
  8. package/dist/chunk-ID3WBWNO.cjs.map +1 -0
  9. package/dist/{chunk-5YTFFAVU.cjs → chunk-L4DIV3RC.cjs} +7 -7
  10. package/dist/{chunk-5YTFFAVU.cjs.map → chunk-L4DIV3RC.cjs.map} +1 -1
  11. package/dist/{chunk-W525IQWC.cjs → chunk-M2TSXZ4C.cjs} +16 -16
  12. package/dist/{chunk-W525IQWC.cjs.map → chunk-M2TSXZ4C.cjs.map} +1 -1
  13. package/dist/{chunk-YVDWXY44.js → chunk-SO6X7G5S.js} +450 -217
  14. package/dist/chunk-SO6X7G5S.js.map +1 -0
  15. package/dist/{chunk-UHXUEGQH.js → chunk-TWELIZRY.js} +2 -2
  16. package/dist/{chunk-3WD7QD5G.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 +322 -145
  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 +59 -7
  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-2U6M3LKS.cjs.map +0 -1
  54. package/dist/chunk-YVDWXY44.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-UHXUEGQH.js.map → chunk-TWELIZRY.js.map} +0 -0
  58. /package/dist/{chunk-3WD7QD5G.js.map → chunk-XLIZJMMJ.js.map} +0 -0
package/dist/index.dev.js CHANGED
@@ -22,14 +22,17 @@ var enterRootGuard = () => true;
22
22
  var exitRootGuard = () => {
23
23
  };
24
24
  var defaultOptions = {
25
- enabled: isDev,
25
+ enabled: true,
26
26
  maxFlushCyclesPerMicrotask: 1e4,
27
27
  maxEffectRunsPerFlush: 2e4,
28
28
  windowSize: 5,
29
29
  highUsageRatio: 0.8,
30
30
  maxRootReentrantDepth: 10,
31
31
  enableWindowWarning: true,
32
- devMode: false
32
+ devMode: isDev,
33
+ // Backoff warning options
34
+ enableBackoffWarning: isDev,
35
+ backoffWarningRatio: 0.5
33
36
  };
34
37
  var enabled = defaultOptions.enabled;
35
38
  var options = {
@@ -41,6 +44,8 @@ var rootDepth = /* @__PURE__ */ new WeakMap();
41
44
  var flushWarned = false;
42
45
  var rootWarned = false;
43
46
  var windowWarned = false;
47
+ var backoffWarned50 = false;
48
+ var backoffWarned75 = false;
44
49
  setCycleProtectionOptions = (opts) => {
45
50
  if (typeof opts.enabled === "boolean") {
46
51
  enabled = opts.enabled;
@@ -56,24 +61,55 @@ resetCycleProtectionStateForTests = () => {
56
61
  flushWarned = false;
57
62
  rootWarned = false;
58
63
  windowWarned = false;
64
+ backoffWarned50 = false;
65
+ backoffWarned75 = false;
59
66
  };
60
67
  beginFlushGuard = () => {
61
68
  if (!enabled) return;
62
69
  effectRunsThisFlush = 0;
63
70
  flushWarned = false;
64
71
  windowWarned = false;
72
+ backoffWarned50 = false;
73
+ backoffWarned75 = false;
65
74
  };
66
75
  beforeEffectRunGuard = () => {
67
76
  if (!enabled) return true;
68
77
  const next = ++effectRunsThisFlush;
69
- if (next > options.maxFlushCyclesPerMicrotask || next > options.maxEffectRunsPerFlush) {
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
+ );
98
+ }
99
+ }
100
+ if (next > limit) {
70
101
  const message = `[fict] cycle protection triggered: flush-budget-exceeded`;
71
102
  if (options.devMode) {
72
- throw new Error(message);
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
+ );
73
109
  }
74
110
  if (!flushWarned) {
75
111
  flushWarned = true;
76
- console.warn(message, { effectRuns: next });
112
+ console.warn(message, { effectRuns: next, limit });
77
113
  }
78
114
  return false;
79
115
  }
@@ -90,11 +126,16 @@ enterRootGuard = (root) => {
90
126
  if (depth > options.maxRootReentrantDepth) {
91
127
  const message = `[fict] cycle protection triggered: root-reentry`;
92
128
  if (options.devMode) {
93
- throw new Error(message);
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
+ );
94
135
  }
95
136
  if (!rootWarned) {
96
137
  rootWarned = true;
97
- console.warn(message, { depth });
138
+ console.warn(message, { depth, maxAllowed: options.maxRootReentrantDepth });
98
139
  }
99
140
  return false;
100
141
  }
@@ -180,13 +221,19 @@ function onCleanup(fn) {
180
221
  function flushOnMount(root) {
181
222
  const cbs = root.onMountCallbacks;
182
223
  if (!cbs || cbs.length === 0) return;
183
- for (let i = 0; i < cbs.length; i++) {
184
- const cleanup = cbs[i]();
185
- if (typeof cleanup === "function") {
186
- 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
+ }
187
232
  }
233
+ } finally {
234
+ currentRoot = prevRoot;
235
+ cbs.length = 0;
188
236
  }
189
- cbs.length = 0;
190
237
  }
191
238
  function registerRootCleanup(fn) {
192
239
  if (currentRoot) {
@@ -371,8 +418,98 @@ function handleSuspend(token, startRoot) {
371
418
  return false;
372
419
  }
373
420
 
374
- // 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
375
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";
376
513
  var Mutable = 1;
377
514
  var Watching = 2;
378
515
  var Running = 4;
@@ -417,6 +554,7 @@ function link(dep, sub, version) {
417
554
  else sub.deps = newLink;
418
555
  if (prevSub !== void 0) prevSub.nextSub = newLink;
419
556
  else dep.subs = newLink;
557
+ if (isDev4) trackDependencyDevtools(dep, sub);
420
558
  }
421
559
  function unlink(lnk, sub = lnk.sub) {
422
560
  const dep = lnk.dep;
@@ -432,6 +570,7 @@ function unlink(lnk, sub = lnk.sub) {
432
570
  else dep.subsTail = prevSub;
433
571
  if (prevSub !== void 0) prevSub.nextSub = nextSub;
434
572
  else if ((dep.subs = nextSub) === void 0) unwatched(dep);
573
+ if (isDev4) untrackDependencyDevtools(dep, sub);
435
574
  return nextDep;
436
575
  }
437
576
  function unwatched(dep) {
@@ -588,6 +727,11 @@ function shallowPropagate(firstLink) {
588
727
  function update(node) {
589
728
  return "getter" in node && node.getter !== void 0 ? updateComputed(node) : updateSignal(node);
590
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
+ }
591
735
  function notify(effect) {
592
736
  effect.flags &= ~Watching;
593
737
  const effects = [];
@@ -624,7 +768,7 @@ function updateSignal(s) {
624
768
  s.flags = Mutable;
625
769
  const current = s.currentValue;
626
770
  const pending = s.pendingValue;
627
- if (current !== pending) {
771
+ if (valuesDiffer(s, current, pending)) {
628
772
  s.currentValue = pending;
629
773
  return true;
630
774
  }
@@ -642,8 +786,9 @@ function updateComputed(c) {
642
786
  activeSub = prevSub;
643
787
  c.flags &= ~Running;
644
788
  purgeDeps(c);
645
- if (oldValue !== newValue) {
789
+ if (valuesDiffer(c, oldValue, newValue)) {
646
790
  c.value = newValue;
791
+ if (isDev4) updateComputedDevtools(c, newValue);
647
792
  return true;
648
793
  }
649
794
  return false;
@@ -665,7 +810,7 @@ function runEffect(e) {
665
810
  }
666
811
  }
667
812
  ++cycle;
668
- effectRunDevtools(e);
813
+ if (isDev4) effectRunDevtools(e);
669
814
  e.depsTail = void 0;
670
815
  e.flags = WatchingRunning;
671
816
  const prevSub = activeSub;
@@ -709,7 +854,7 @@ function runEffect(e) {
709
854
  }
710
855
  if (isDirty) {
711
856
  ++cycle;
712
- effectRunDevtools(e);
857
+ if (isDev4) effectRunDevtools(e);
713
858
  e.depsTail = void 0;
714
859
  e.flags = WatchingRunning;
715
860
  const prevSub = activeSub;
@@ -758,10 +903,21 @@ function flush() {
758
903
  while (highIndex < highPriorityQueue.length) {
759
904
  const e = highPriorityQueue[highIndex];
760
905
  if (!beforeEffectRunGuard()) {
761
- if (highIndex > 0) {
762
- highPriorityQueue.copyWithin(0, highIndex);
763
- 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
+ }
764
917
  }
918
+ highPriorityQueue.length = 0;
919
+ lowPriorityQueue.length = 0;
920
+ flushScheduled = false;
765
921
  endFlushGuard();
766
922
  return;
767
923
  }
@@ -782,10 +938,21 @@ function flush() {
782
938
  }
783
939
  const e = lowPriorityQueue[lowIndex];
784
940
  if (!beforeEffectRunGuard()) {
785
- if (lowIndex > 0) {
786
- lowPriorityQueue.copyWithin(0, lowIndex);
787
- 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
+ }
788
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;
789
956
  endFlushGuard();
790
957
  return;
791
958
  }
@@ -795,26 +962,31 @@ function flush() {
795
962
  lowPriorityQueue.length = 0;
796
963
  endFlushGuard();
797
964
  }
798
- function signal(initialValue) {
965
+ function signal(initialValue, options2) {
799
966
  const s = {
800
967
  currentValue: initialValue,
801
968
  pendingValue: initialValue,
802
969
  subs: void 0,
803
970
  subsTail: void 0,
804
971
  flags: Mutable,
805
- __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 } : {}
806
976
  };
807
- registerSignalDevtools(initialValue, s);
977
+ if (isDev4) registerSignalDevtools(s);
808
978
  const accessor = signalOper.bind(s);
809
979
  accessor[SIGNAL_MARKER] = true;
810
980
  return accessor;
811
981
  }
812
982
  function signalOper(value) {
813
983
  if (arguments.length > 0) {
814
- if (this.pendingValue !== value) {
815
- this.pendingValue = value;
984
+ const next = value;
985
+ const prev = this.pendingValue;
986
+ if (valuesDiffer(this, prev, next)) {
987
+ this.pendingValue = next;
816
988
  this.flags = MutableDirty;
817
- updateSignalDevtools(this, value);
989
+ if (isDev4) updateSignalDevtools(this, next);
818
990
  const subs = this.subs;
819
991
  if (subs !== void 0) {
820
992
  propagate(subs);
@@ -841,7 +1013,7 @@ function signalOper(value) {
841
1013
  }
842
1014
  return this.currentValue;
843
1015
  }
844
- function computed(getter) {
1016
+ function computed(getter, options2) {
845
1017
  const c = {
846
1018
  value: void 0,
847
1019
  subs: void 0,
@@ -849,13 +1021,21 @@ function computed(getter) {
849
1021
  deps: void 0,
850
1022
  depsTail: void 0,
851
1023
  flags: 0,
852
- 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 } : {}
853
1029
  };
854
- const bound = computedOper.bind(c);
1030
+ if (isDev4) registerComputedDevtools(c);
1031
+ const bound = computedOper.bind(
1032
+ c
1033
+ );
855
1034
  bound[COMPUTED_MARKER] = true;
856
1035
  return bound;
857
1036
  }
858
1037
  function computedOper() {
1038
+ if (inCleanup) return this.value;
859
1039
  const flags = this.flags;
860
1040
  if (flags & Dirty) {
861
1041
  if (updateComputed(this)) {
@@ -876,6 +1056,7 @@ function computedOper() {
876
1056
  const prevSub = setActiveSub(this);
877
1057
  try {
878
1058
  this.value = this.getter(void 0);
1059
+ if (isDev4) updateComputedDevtools(this, this.value);
879
1060
  } finally {
880
1061
  setActiveSub(prevSub);
881
1062
  this.flags &= ~Running;
@@ -899,12 +1080,12 @@ function effectWithCleanup(fn, cleanupRunner, root) {
899
1080
  if (resolvedRoot) {
900
1081
  e.root = resolvedRoot;
901
1082
  }
902
- registerEffectDevtools(e);
1083
+ if (isDev4) registerEffectDevtools(e);
903
1084
  const prevSub = activeSub;
904
1085
  if (prevSub !== void 0) link(e, prevSub, 0);
905
1086
  activeSub = e;
906
1087
  try {
907
- effectRunDevtools(e);
1088
+ if (isDev4) effectRunDevtools(e);
908
1089
  fn();
909
1090
  } finally {
910
1091
  activeSub = prevSub;
@@ -976,17 +1157,28 @@ function setTransitionContext(value) {
976
1157
  var registerSignalDevtools = () => void 0;
977
1158
  var updateSignalDevtools = () => {
978
1159
  };
1160
+ var registerComputedDevtools = () => void 0;
1161
+ var updateComputedDevtools = () => {
1162
+ };
979
1163
  var registerEffectDevtools = () => void 0;
980
1164
  var effectRunDevtools = () => {
981
1165
  };
982
- if (isDev3) {
983
- let devtoolsSignalId = 0;
984
- let devtoolsEffectId = 0;
985
- registerSignalDevtools = (value, node) => {
1166
+ var trackDependencyDevtools = () => {
1167
+ };
1168
+ var untrackDependencyDevtools = () => {
1169
+ };
1170
+ if (isDev4) {
1171
+ let nextDevtoolsId = 0;
1172
+ registerSignalDevtools = (node) => {
986
1173
  const hook = getDevtoolsHook();
987
1174
  if (!hook) return void 0;
988
- const id = ++devtoolsSignalId;
989
- 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);
990
1182
  node.__id = id;
991
1183
  return id;
992
1184
  };
@@ -996,11 +1188,32 @@ if (isDev3) {
996
1188
  const id = node.__id;
997
1189
  if (id) hook.updateSignal(id, value);
998
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
+ };
999
1211
  registerEffectDevtools = (node) => {
1000
1212
  const hook = getDevtoolsHook();
1001
1213
  if (!hook) return void 0;
1002
- const id = ++devtoolsEffectId;
1003
- hook.registerEffect(id);
1214
+ const id = ++nextDevtoolsId;
1215
+ const ownerId = __fictGetCurrentComponentId();
1216
+ hook.registerEffect(id, ownerId !== void 0 ? { ownerId } : void 0);
1004
1217
  node.__id = id;
1005
1218
  return id;
1006
1219
  };
@@ -1010,85 +1223,25 @@ if (isDev3) {
1010
1223
  const id = node.__id;
1011
1224
  if (id) hook.effectRun(id);
1012
1225
  };
1013
- }
1014
-
1015
- // src/memo.ts
1016
- function createMemo(fn) {
1017
- return computed(fn);
1018
- }
1019
-
1020
- // src/effect.ts
1021
- function createEffect(fn) {
1022
- let cleanups = [];
1023
- const rootForError = getCurrentRoot();
1024
- const doCleanup = () => {
1025
- runCleanupList(cleanups);
1026
- cleanups = [];
1027
- };
1028
- const run = () => {
1029
- const bucket = [];
1030
- withEffectCleanups(bucket, () => {
1031
- try {
1032
- const maybeCleanup = fn();
1033
- if (typeof maybeCleanup === "function") {
1034
- bucket.push(maybeCleanup);
1035
- }
1036
- } catch (err) {
1037
- if (handleSuspend(err, rootForError)) {
1038
- return;
1039
- }
1040
- if (handleError(err, { source: "effect" }, rootForError)) {
1041
- return;
1042
- }
1043
- throw err;
1044
- }
1045
- });
1046
- 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);
1047
1232
  };
1048
- const disposeEffect = effectWithCleanup(run, doCleanup, rootForError);
1049
- const teardown = () => {
1050
- runCleanupList(cleanups);
1051
- 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);
1052
1239
  };
1053
- registerRootCleanup(teardown);
1054
- return teardown;
1055
1240
  }
1056
- function createRenderEffect(fn) {
1057
- let cleanup;
1058
- const rootForError = getCurrentRoot();
1059
- const doCleanup = () => {
1060
- if (cleanup) {
1061
- cleanup();
1062
- cleanup = void 0;
1063
- }
1064
- };
1065
- const run = () => {
1066
- try {
1067
- const maybeCleanup = fn();
1068
- if (typeof maybeCleanup === "function") {
1069
- cleanup = maybeCleanup;
1070
- }
1071
- } catch (err) {
1072
- if (handleSuspend(err, rootForError)) {
1073
- return;
1074
- }
1075
- const handled = handleError(err, { source: "effect" }, rootForError);
1076
- if (handled) {
1077
- return;
1078
- }
1079
- throw err;
1080
- }
1081
- };
1082
- const disposeEffect = effectWithCleanup(run, doCleanup, rootForError);
1083
- const teardown = () => {
1084
- if (cleanup) {
1085
- cleanup();
1086
- cleanup = void 0;
1087
- }
1088
- disposeEffect();
1089
- };
1090
- registerRootCleanup(teardown);
1091
- return teardown;
1241
+
1242
+ // src/memo.ts
1243
+ function createMemo(fn, options2) {
1244
+ return computed(fn, options2);
1092
1245
  }
1093
1246
 
1094
1247
  // src/ref.ts
@@ -1172,8 +1325,8 @@ var DelegatedEventNames = [
1172
1325
  ];
1173
1326
 
1174
1327
  // src/constants.ts
1175
- var isDev4 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
1176
- var booleans = isDev4 ? [
1328
+ var isDev5 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
1329
+ var booleans = isDev5 ? [
1177
1330
  "allowfullscreen",
1178
1331
  "async",
1179
1332
  "alpha",
@@ -1223,7 +1376,7 @@ var booleans = isDev4 ? [
1223
1376
  "sharedstoragewritable"
1224
1377
  ] : [];
1225
1378
  var BooleanAttributes = new Set(booleans);
1226
- var properties = isDev4 ? [
1379
+ var properties = isDev5 ? [
1227
1380
  // Core properties
1228
1381
  "className",
1229
1382
  "value",
@@ -1259,7 +1412,7 @@ var ChildProperties = /* @__PURE__ */ new Set([
1259
1412
  "innerText",
1260
1413
  "children"
1261
1414
  ]);
1262
- var PropAliases = isDev4 ? {
1415
+ var PropAliases = isDev5 ? {
1263
1416
  // Direct mapping
1264
1417
  class: "className",
1265
1418
  // Element-specific mappings
@@ -1348,7 +1501,7 @@ var PropAliases = isDev4 ? {
1348
1501
  }
1349
1502
  } : {};
1350
1503
  function getPropAlias(prop2, tagName) {
1351
- if (!isDev4) return void 0;
1504
+ if (!isDev5) return void 0;
1352
1505
  const a = PropAliases[prop2];
1353
1506
  if (typeof a === "object") {
1354
1507
  return a[tagName] ? a["$"] : void 0;
@@ -1357,7 +1510,7 @@ function getPropAlias(prop2, tagName) {
1357
1510
  }
1358
1511
  var $$EVENTS = "_$FICT_DELEGATE";
1359
1512
  var DelegatedEvents = new Set(DelegatedEventNames);
1360
- var svgElements = isDev4 ? [
1513
+ var svgElements = isDev5 ? [
1361
1514
  "altGlyph",
1362
1515
  "altGlyphDef",
1363
1516
  "altGlyphItem",
@@ -1441,7 +1594,7 @@ var SVGNamespace = {
1441
1594
  xlink: "http://www.w3.org/1999/xlink",
1442
1595
  xml: "http://www.w3.org/XML/1998/namespace"
1443
1596
  };
1444
- var unitlessList = isDev4 ? [
1597
+ var unitlessList = isDev5 ? [
1445
1598
  "animationIterationCount",
1446
1599
  "animation-iteration-count",
1447
1600
  "borderImageOutset",
@@ -1664,13 +1817,22 @@ function removeNodes(nodes) {
1664
1817
  }
1665
1818
 
1666
1819
  // src/binding.ts
1667
- 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";
1668
1821
  function isReactive(value) {
1669
1822
  if (typeof value !== "function") return false;
1670
1823
  if (isSignal(value) || isComputed(value)) return true;
1671
1824
  if (isEffect(value) || isEffectScope(value)) return false;
1672
1825
  return value.length === 0;
1673
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
+ }
1674
1836
  function callEventHandler(handler, event, node, data) {
1675
1837
  if (!handler) return;
1676
1838
  const context = node ?? event.currentTarget ?? void 0;
@@ -1757,7 +1919,7 @@ function applyStyle(el, value, prev) {
1757
1919
  }
1758
1920
  }
1759
1921
  }
1760
- 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";
1761
1923
  function createClassBinding(el, value) {
1762
1924
  if (isReactive(value)) {
1763
1925
  let prev = {};
@@ -1968,7 +2130,7 @@ function bindEvent(el, eventName, handler, options2) {
1968
2130
  if (shouldDelegate) {
1969
2131
  const key = `$$${eventName}`;
1970
2132
  delegateEvents([eventName]);
1971
- const resolveHandler = isReactive(handler) ? handler : () => handler;
2133
+ const resolveHandler = isStrictlyReactive(handler) ? handler : () => handler;
1972
2134
  el[key] = function(...args) {
1973
2135
  try {
1974
2136
  const fn = resolveHandler();
@@ -1983,7 +2145,7 @@ function bindEvent(el, eventName, handler, options2) {
1983
2145
  el[key] = void 0;
1984
2146
  };
1985
2147
  }
1986
- const getHandler = isReactive(handler) ? handler : () => handler;
2148
+ const getHandler = isStrictlyReactive(handler) ? handler : () => handler;
1987
2149
  const wrapped = (event) => {
1988
2150
  try {
1989
2151
  const resolved = getHandler();
@@ -2071,20 +2233,8 @@ function createPortal(container, render2, createElementFn) {
2071
2233
  };
2072
2234
  }
2073
2235
 
2074
- // src/hooks.ts
2075
- var isDev6 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
2076
- var ctxStack = [];
2077
- function __fictPushContext() {
2078
- const ctx = { slots: [], cursor: 0 };
2079
- ctxStack.push(ctx);
2080
- return ctx;
2081
- }
2082
- function __fictPopContext() {
2083
- ctxStack.pop();
2084
- }
2085
-
2086
2236
  // src/props.ts
2087
- var PROP_GETTER_MARKER = Symbol.for("fict:prop-getter");
2237
+ var PROP_GETTER_MARKER2 = Symbol.for("fict:prop-getter");
2088
2238
  var propGetters = /* @__PURE__ */ new WeakSet();
2089
2239
  var rawToProxy = /* @__PURE__ */ new WeakMap();
2090
2240
  var proxyToRaw = /* @__PURE__ */ new WeakMap();
@@ -2094,7 +2244,7 @@ function __fictProp(getter) {
2094
2244
  if (Object.isExtensible(getter)) {
2095
2245
  try {
2096
2246
  ;
2097
- getter[PROP_GETTER_MARKER] = true;
2247
+ getter[PROP_GETTER_MARKER2] = true;
2098
2248
  } catch {
2099
2249
  }
2100
2250
  }
@@ -2104,7 +2254,7 @@ function __fictProp(getter) {
2104
2254
  function isPropGetter(value) {
2105
2255
  if (typeof value !== "function") return false;
2106
2256
  const fn = value;
2107
- return propGetters.has(fn) || fn[PROP_GETTER_MARKER] === true;
2257
+ return propGetters.has(fn) || fn[PROP_GETTER_MARKER2] === true;
2108
2258
  }
2109
2259
  function createPropsProxy(props) {
2110
2260
  if (!props || typeof props !== "object") {
@@ -2218,6 +2368,13 @@ function mergeProps(...sources) {
2218
2368
  }
2219
2369
  });
2220
2370
  }
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
+ }
2221
2378
  function prop(getter, options2) {
2222
2379
  if (isPropGetter(getter)) {
2223
2380
  return getter;
@@ -2239,6 +2396,7 @@ function prop(getter, options2) {
2239
2396
  var SVG_NS = "http://www.w3.org/2000/svg";
2240
2397
  var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
2241
2398
  var isDev7 = true ? true : typeof process === "undefined" || process.env?.NODE_ENV !== "production";
2399
+ var nextComponentId = 1;
2242
2400
  function render(view, container) {
2243
2401
  const root = createRootContext();
2244
2402
  const prev = pushRoot(root);
@@ -2331,9 +2489,27 @@ function createElementWithContext(node, namespace) {
2331
2489
  }
2332
2490
  });
2333
2491
  const props = createPropsProxy(baseProps);
2334
- __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
+ }
2335
2505
  try {
2336
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
+ }
2337
2513
  return createElementWithContext(rendered, namespace);
2338
2514
  } catch (err) {
2339
2515
  if (handleSuspend(err)) {
@@ -2955,6 +3131,7 @@ export {
2955
3131
  createRoot,
2956
3132
  createSuspenseToken,
2957
3133
  hasContext,
3134
+ keyed,
2958
3135
  mergeProps,
2959
3136
  onCleanup,
2960
3137
  onDestroy,