sibujs 2.0.0 → 2.2.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 (62) hide show
  1. package/dist/browser.cjs +369 -276
  2. package/dist/browser.js +4 -4
  3. package/dist/build.cjs +411 -300
  4. package/dist/build.js +10 -10
  5. package/dist/cdn.global.js +8 -8
  6. package/dist/{chunk-JA6667UN.js → chunk-2JQUV4Y3.js} +4 -4
  7. package/dist/{chunk-3NSGB5JN.js → chunk-2KM2724A.js} +2 -2
  8. package/dist/{chunk-52YJLLRO.js → chunk-4YTVESDX.js} +1 -1
  9. package/dist/chunk-5WD7BYTZ.js +152 -0
  10. package/dist/{chunk-CC65Y57T.js → chunk-6QZO7MMG.js} +48 -16
  11. package/dist/{chunk-54EDRCEF.js → chunk-DF3GTP4Q.js} +7 -2
  12. package/dist/{chunk-ND2664SF.js → chunk-J63GPPCJ.js} +13 -9
  13. package/dist/{chunk-O2MNQFLP.js → chunk-KH4OE6WY.js} +5 -5
  14. package/dist/{chunk-3LR7GLWQ.js → chunk-KZA7ANXP.js} +3 -3
  15. package/dist/chunk-L4DAT4WU.js +400 -0
  16. package/dist/{chunk-WOMYAHHI.js → chunk-L52H775O.js} +4 -4
  17. package/dist/{chunk-ITX6OO3F.js → chunk-NEWH4O5U.js} +1 -1
  18. package/dist/{chunk-7JDB7I65.js → chunk-RJIRT46U.js} +4 -4
  19. package/dist/{chunk-KLRMB5ZS.js → chunk-STFTTMO2.js} +2 -2
  20. package/dist/{chunk-DFPFITST.js → chunk-UKMXT5T6.js} +1 -1
  21. package/dist/{chunk-SAHNHTFC.js → chunk-V65KTDZW.js} +3 -3
  22. package/dist/{chunk-R73P76YZ.js → chunk-VSNLICTS.js} +1 -1
  23. package/dist/{chunk-MIUAXB7K.js → chunk-XDKP4T7G.js} +2 -2
  24. package/dist/{chunk-JXMMDLBY.js → chunk-XVYB3J6C.js} +27 -29
  25. package/dist/{chunk-GTBNNBJ6.js → chunk-YMOIAHWA.js} +1 -1
  26. package/dist/data.cjs +382 -274
  27. package/dist/data.js +6 -6
  28. package/dist/devtools.cjs +398 -284
  29. package/dist/devtools.d.cts +1 -1
  30. package/dist/devtools.d.ts +1 -1
  31. package/dist/devtools.js +4 -4
  32. package/dist/ecosystem.cjs +382 -274
  33. package/dist/ecosystem.js +7 -7
  34. package/dist/extras.cjs +421 -299
  35. package/dist/extras.d.cts +1 -1
  36. package/dist/extras.d.ts +1 -1
  37. package/dist/extras.js +19 -19
  38. package/dist/index.cjs +413 -300
  39. package/dist/index.d.cts +16 -11
  40. package/dist/index.d.ts +16 -11
  41. package/dist/index.js +14 -10
  42. package/dist/{introspect-cY2pg9pW.d.ts → introspect-BZWKvQUZ.d.ts} +2 -1
  43. package/dist/{introspect-BWNjNw64.d.cts → introspect-DsJlDD2T.d.cts} +2 -1
  44. package/dist/motion.cjs +189 -149
  45. package/dist/motion.js +3 -3
  46. package/dist/patterns.cjs +382 -274
  47. package/dist/patterns.js +5 -5
  48. package/dist/performance.cjs +360 -260
  49. package/dist/performance.js +4 -4
  50. package/dist/plugins.cjs +376 -257
  51. package/dist/plugins.js +6 -6
  52. package/dist/ssr.cjs +383 -271
  53. package/dist/ssr.js +7 -7
  54. package/dist/testing.cjs +168 -109
  55. package/dist/testing.js +2 -2
  56. package/dist/ui.cjs +373 -258
  57. package/dist/ui.js +6 -6
  58. package/dist/widgets.cjs +382 -274
  59. package/dist/widgets.js +6 -6
  60. package/package.json +1 -1
  61. package/dist/chunk-HB24TBAF.js +0 -121
  62. package/dist/chunk-VLPPXTYG.js +0 -332
package/dist/ssr.cjs CHANGED
@@ -682,11 +682,88 @@ function escapeAttr(str) {
682
682
 
683
683
  // src/reactivity/track.ts
684
684
  var _isDev3 = isDev();
685
- var subscriberStack = new Array(32);
686
- var stackCapacity = 32;
687
- var stackTop = -1;
685
+ var POOL_MAX = 4096;
686
+ var nodePool = [];
687
+ function createNode() {
688
+ return {
689
+ sig: null,
690
+ sub: null,
691
+ epoch: 0,
692
+ sigPrev: null,
693
+ sigNext: null,
694
+ subPrev: null,
695
+ subNext: null,
696
+ prevActive: null
697
+ };
698
+ }
699
+ function allocNode(sig, sub2, epoch) {
700
+ const n = nodePool.pop();
701
+ if (n) {
702
+ n.sig = sig;
703
+ n.sub = sub2;
704
+ n.epoch = epoch;
705
+ return n;
706
+ }
707
+ const fresh = createNode();
708
+ fresh.sig = sig;
709
+ fresh.sub = sub2;
710
+ fresh.epoch = epoch;
711
+ return fresh;
712
+ }
713
+ function freeNode(node) {
714
+ node.sig = null;
715
+ node.sub = null;
716
+ node.sigPrev = null;
717
+ node.sigNext = null;
718
+ node.subPrev = null;
719
+ node.subNext = null;
720
+ node.prevActive = null;
721
+ if (nodePool.length < POOL_MAX) nodePool.push(node);
722
+ }
723
+ function linkSignal(sig, node) {
724
+ const oldHead = sig.subsHead ?? null;
725
+ node.sigPrev = null;
726
+ node.sigNext = oldHead;
727
+ if (oldHead) oldHead.sigPrev = node;
728
+ else sig.subsTail = node;
729
+ sig.subsHead = node;
730
+ sig.__sc = (sig.__sc ?? 0) + 1;
731
+ }
732
+ function unlinkSignal(node) {
733
+ const sig = node.sig;
734
+ if (!sig) return;
735
+ const prev = node.sigPrev;
736
+ const next = node.sigNext;
737
+ if (prev) prev.sigNext = next;
738
+ else sig.subsHead = next;
739
+ if (next) next.sigPrev = prev;
740
+ else sig.subsTail = prev;
741
+ sig.__sc = (sig.__sc ?? 1) - 1;
742
+ if (sig.__activeNode === node) sig.__activeNode = node.prevActive;
743
+ if (sig.__sc === 0) {
744
+ sig.subsHead = null;
745
+ sig.subsTail = null;
746
+ }
747
+ }
748
+ function linkSub(sub2, node) {
749
+ const oldTail = sub2.depsTail ?? null;
750
+ node.subPrev = oldTail;
751
+ node.subNext = null;
752
+ if (oldTail) oldTail.subNext = node;
753
+ else sub2.depsHead = node;
754
+ sub2.depsTail = node;
755
+ }
756
+ function unlinkSub(node) {
757
+ const sub2 = node.sub;
758
+ if (!sub2) return;
759
+ const prev = node.subPrev;
760
+ const next = node.subNext;
761
+ if (prev) prev.subNext = next;
762
+ else sub2.depsHead = next;
763
+ if (next) next.subPrev = prev;
764
+ else sub2.depsTail = prev;
765
+ }
688
766
  var currentSubscriber = null;
689
- var SUBS = "__s";
690
767
  var notifyDepth = 0;
691
768
  var pendingQueue = [];
692
769
  var pendingSet = /* @__PURE__ */ new Set();
@@ -698,83 +775,136 @@ function safeInvoke(sub2) {
698
775
  if (_isDev3) devWarn(`Subscriber threw during notification: ${err instanceof Error ? err.message : String(err)}`);
699
776
  }
700
777
  }
778
+ var subscriberEpochCounter = 0;
779
+ function retrack(effectFn, subscriber) {
780
+ const prev = currentSubscriber;
781
+ currentSubscriber = subscriber;
782
+ const sub2 = subscriber;
783
+ const epoch = ++subscriberEpochCounter;
784
+ sub2._epoch = epoch;
785
+ sub2._structDirty = false;
786
+ for (let n = sub2.depsHead ?? null; n !== null; n = n.subNext) {
787
+ const sig = n.sig;
788
+ n.prevActive = sig.__activeNode ?? null;
789
+ sig.__activeNode = n;
790
+ }
791
+ try {
792
+ effectFn();
793
+ } finally {
794
+ currentSubscriber = prev;
795
+ let node = sub2.depsHead ?? null;
796
+ while (node !== null) {
797
+ const next = node.subNext;
798
+ const sig = node.sig;
799
+ sig.__activeNode = node.prevActive;
800
+ node.prevActive = null;
801
+ if (node.epoch !== epoch) {
802
+ unlinkSub(node);
803
+ unlinkSignal(node);
804
+ freeNode(node);
805
+ }
806
+ node = next;
807
+ }
808
+ }
809
+ }
701
810
  function track(effectFn, subscriber) {
702
811
  if (!subscriber) subscriber = effectFn;
703
812
  cleanup(subscriber);
704
- ++stackTop;
705
- if (stackTop >= stackCapacity) {
706
- stackCapacity *= 2;
707
- subscriberStack.length = stackCapacity;
708
- }
709
- subscriberStack[stackTop] = subscriber;
813
+ const prev = currentSubscriber;
710
814
  currentSubscriber = subscriber;
711
815
  try {
712
816
  effectFn();
713
817
  } finally {
714
- stackTop--;
715
- currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
818
+ currentSubscriber = prev;
819
+ const sub3 = subscriber;
820
+ for (let n = sub3.depsHead ?? null; n !== null; n = n.subNext) {
821
+ const sig = n.sig;
822
+ sig.__activeNode = n.prevActive;
823
+ n.prevActive = null;
824
+ }
716
825
  }
717
- return () => cleanup(subscriber);
826
+ const sub2 = subscriber;
827
+ return sub2._dispose ?? (sub2._dispose = () => cleanup(subscriber));
718
828
  }
719
829
  function recordDependency(signal2) {
720
830
  if (!currentSubscriber) return;
721
831
  const sub2 = currentSubscriber;
722
- if (sub2._dep === signal2) return;
723
- const deps = sub2._deps;
724
- if (deps) {
725
- if (deps.has(signal2)) return;
726
- deps.add(signal2);
727
- } else if (sub2._dep !== void 0) {
728
- const set = /* @__PURE__ */ new Set();
729
- set.add(sub2._dep);
730
- set.add(signal2);
731
- sub2._deps = set;
732
- sub2._dep = void 0;
733
- } else {
734
- sub2._dep = signal2;
832
+ const sig = signal2;
833
+ const epoch = sub2._epoch ?? 0;
834
+ const active = sig.__activeNode ?? null;
835
+ if (active !== null && active.sub === sub2) {
836
+ active.epoch = epoch;
837
+ return;
735
838
  }
736
- let subs = signal2[SUBS];
737
- if (!subs) {
738
- subs = /* @__PURE__ */ new Set();
739
- signal2[SUBS] = subs;
839
+ const node = allocNode(signal2, sub2, epoch);
840
+ node.prevActive = active;
841
+ sig.__activeNode = node;
842
+ linkSub(sub2, node);
843
+ linkSignal(sig, node);
844
+ sub2._structDirty = true;
845
+ }
846
+ function cleanup(subscriber) {
847
+ const sub2 = subscriber;
848
+ let node = sub2.depsHead ?? null;
849
+ sub2.depsHead = null;
850
+ sub2.depsTail = null;
851
+ while (node) {
852
+ const next = node.subNext;
853
+ unlinkSignal(node);
854
+ freeNode(node);
855
+ node = next;
856
+ }
857
+ }
858
+ var maxSubscriberRepeats = 50;
859
+ var maxDrainIterations = 1e6;
860
+ var drainEpoch = 0;
861
+ function tickRepeat(sub2) {
862
+ const s2 = sub2;
863
+ if (s2._runEpoch !== drainEpoch) {
864
+ s2._runEpoch = drainEpoch;
865
+ s2._runs = 1;
866
+ return false;
740
867
  }
741
- subs.add(currentSubscriber);
742
- if (subs.size === 1) {
743
- signal2.__f = currentSubscriber;
744
- } else if (signal2.__f !== void 0) {
745
- signal2.__f = void 0;
868
+ s2._runs = (s2._runs ?? 0) + 1;
869
+ return s2._runs > maxSubscriberRepeats;
870
+ }
871
+ function cycleError(sub2) {
872
+ if (typeof console !== "undefined") {
873
+ const name = sub2.__name ?? "<unnamed>";
874
+ console.error(
875
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
876
+ );
746
877
  }
747
878
  }
748
- function queueSignalNotification(signal2) {
749
- const subs = signal2[SUBS];
750
- if (!subs) return;
751
- for (const sub2 of subs) {
752
- if (sub2._c) {
753
- propagateDirty(sub2);
754
- } else if (!pendingSet.has(sub2)) {
755
- pendingSet.add(sub2);
756
- pendingQueue.push(sub2);
879
+ function absoluteDrainError() {
880
+ if (typeof console !== "undefined") {
881
+ console.error(
882
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
883
+ );
884
+ }
885
+ }
886
+ function drainQueue() {
887
+ let i2 = 0;
888
+ while (i2 < pendingQueue.length) {
889
+ if (i2 >= maxDrainIterations) {
890
+ absoluteDrainError();
891
+ break;
757
892
  }
893
+ const sub2 = pendingQueue[i2++];
894
+ if (tickRepeat(sub2)) {
895
+ cycleError(sub2);
896
+ break;
897
+ }
898
+ pendingSet.delete(sub2);
899
+ safeInvoke(sub2);
758
900
  }
759
901
  }
760
- var maxDrainIterations = 1e5;
761
902
  function drainNotificationQueue() {
762
903
  if (notifyDepth > 0) return;
763
904
  notifyDepth++;
905
+ drainEpoch++;
764
906
  try {
765
- let i2 = 0;
766
- while (i2 < pendingQueue.length) {
767
- if (i2 >= maxDrainIterations) {
768
- if (typeof console !== "undefined") {
769
- console.error(
770
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
771
- );
772
- }
773
- break;
774
- }
775
- safeInvoke(pendingQueue[i2]);
776
- i2++;
777
- }
907
+ drainQueue();
778
908
  } finally {
779
909
  notifyDepth--;
780
910
  if (notifyDepth === 0) {
@@ -792,131 +922,82 @@ function propagateDirty(sub2) {
792
922
  stack.push(rootSig);
793
923
  while (stack.length > baseLen) {
794
924
  const sig = stack.pop();
795
- const first = sig.__f;
796
- if (first) {
797
- if (first._c) {
798
- const nSig = first._sig;
799
- if (!nSig._d) {
800
- nSig._d = true;
801
- stack.push(nSig);
925
+ let node = sig.subsHead ?? null;
926
+ while (node) {
927
+ const s2 = node.sub;
928
+ if (s2) {
929
+ if (s2._c) {
930
+ const nSig = s2._sig;
931
+ if (nSig) {
932
+ if (!nSig._d) {
933
+ nSig._d = true;
934
+ stack.push(nSig);
935
+ }
936
+ } else {
937
+ s2();
938
+ }
939
+ } else if (!pendingSet.has(s2)) {
940
+ pendingSet.add(s2);
941
+ pendingQueue.push(s2);
802
942
  }
803
- } else if (!pendingSet.has(first)) {
804
- pendingSet.add(first);
805
- pendingQueue.push(first);
806
943
  }
807
- continue;
944
+ node = node.sigNext;
808
945
  }
809
- const subs = sig[SUBS];
810
- if (!subs) continue;
811
- for (const s2 of subs) {
946
+ }
947
+ }
948
+ function queueSignalNotification(signal2) {
949
+ const sig = signal2;
950
+ let node = sig.subsHead ?? null;
951
+ while (node) {
952
+ const s2 = node.sub;
953
+ if (s2) {
812
954
  if (s2._c) {
813
- const nSig = s2._sig;
814
- if (nSig && !nSig._d) {
815
- nSig._d = true;
816
- stack.push(nSig);
817
- } else if (!nSig) {
818
- s2();
819
- }
955
+ propagateDirty(s2);
820
956
  } else if (!pendingSet.has(s2)) {
821
957
  pendingSet.add(s2);
822
958
  pendingQueue.push(s2);
823
959
  }
824
960
  }
961
+ node = node.sigNext;
825
962
  }
826
963
  }
827
964
  function notifySubscribers(signal2) {
828
- const first = signal2.__f;
829
- if (first) {
830
- if (notifyDepth > 0) {
831
- if (first._c) {
832
- propagateDirty(first);
833
- } else if (!pendingSet.has(first)) {
834
- pendingSet.add(first);
835
- pendingQueue.push(first);
836
- }
837
- return;
838
- }
839
- notifyDepth++;
840
- try {
841
- if (first._c) {
842
- propagateDirty(first);
843
- } else {
844
- safeInvoke(first);
845
- }
846
- let i2 = 0;
847
- while (i2 < pendingQueue.length) {
848
- if (i2 >= maxDrainIterations) {
849
- if (typeof console !== "undefined") {
850
- console.error(
851
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
852
- );
853
- }
854
- break;
855
- }
856
- safeInvoke(pendingQueue[i2]);
857
- i2++;
858
- }
859
- } finally {
860
- notifyDepth--;
861
- if (notifyDepth === 0) {
862
- pendingQueue.length = 0;
863
- pendingSet.clear();
864
- }
865
- }
866
- return;
867
- }
868
- const subs = signal2[SUBS];
869
- if (!subs || subs.size === 0) return;
965
+ const sig = signal2;
966
+ const head2 = sig.subsHead;
967
+ if (!head2) return;
870
968
  if (notifyDepth > 0) {
871
- for (const sub2 of subs) {
872
- if (sub2._c) {
873
- propagateDirty(sub2);
874
- } else if (!pendingSet.has(sub2)) {
875
- pendingSet.add(sub2);
876
- pendingQueue.push(sub2);
969
+ let node = head2;
970
+ while (node) {
971
+ const s2 = node.sub;
972
+ if (s2) {
973
+ if (s2._c) {
974
+ propagateDirty(s2);
975
+ } else if (!pendingSet.has(s2)) {
976
+ pendingSet.add(s2);
977
+ pendingQueue.push(s2);
978
+ }
877
979
  }
980
+ node = node.sigNext;
878
981
  }
879
982
  return;
880
983
  }
881
984
  notifyDepth++;
985
+ drainEpoch++;
882
986
  try {
883
- let directCount = 0;
884
- let hasComputedSub = false;
885
- for (const sub2 of subs) {
886
- if (sub2._c) hasComputedSub = true;
887
- pendingQueue[directCount++] = sub2;
888
- }
889
- if (!hasComputedSub) {
890
- for (let i3 = 0; i3 < directCount; i3++) {
891
- safeInvoke(pendingQueue[i3]);
892
- }
893
- } else {
894
- for (let i3 = 0; i3 < directCount; i3++) {
895
- if (pendingQueue[i3]._c) {
896
- propagateDirty(pendingQueue[i3]);
897
- }
898
- }
899
- for (let i3 = 0; i3 < directCount; i3++) {
900
- const sub2 = pendingQueue[i3];
901
- if (!sub2._c && !pendingSet.has(sub2)) {
902
- pendingSet.add(sub2);
903
- safeInvoke(sub2);
904
- }
905
- }
906
- }
907
- let i2 = directCount;
908
- while (i2 < pendingQueue.length) {
909
- if (i2 - directCount >= maxDrainIterations) {
910
- if (typeof console !== "undefined") {
911
- console.error(
912
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
913
- );
987
+ let node = head2;
988
+ while (node) {
989
+ const s2 = node.sub;
990
+ if (s2) {
991
+ if (s2._c) {
992
+ propagateDirty(s2);
993
+ } else if (!pendingSet.has(s2)) {
994
+ pendingSet.add(s2);
995
+ pendingQueue.push(s2);
914
996
  }
915
- break;
916
997
  }
917
- safeInvoke(pendingQueue[i2]);
918
- i2++;
998
+ node = node.sigNext;
919
999
  }
1000
+ drainQueue();
920
1001
  } finally {
921
1002
  notifyDepth--;
922
1003
  if (notifyDepth === 0) {
@@ -925,126 +1006,125 @@ function notifySubscribers(signal2) {
925
1006
  }
926
1007
  }
927
1008
  }
928
- function cleanup(subscriber) {
929
- const sub2 = subscriber;
930
- const singleDep = sub2._dep;
931
- if (singleDep !== void 0) {
932
- const subs = singleDep[SUBS];
933
- if (subs) {
934
- subs.delete(subscriber);
935
- if (singleDep.__f === subscriber) {
936
- singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
937
- } else if (subs.size === 1 && singleDep.__f === void 0) {
938
- singleDep.__f = subs.values().next().value;
939
- }
1009
+
1010
+ // src/core/signals/effect.ts
1011
+ var _g = globalThis;
1012
+ var MAX_RERUNS = 100;
1013
+ function flushUserCleanups(ctx) {
1014
+ const list = ctx.userCleanups;
1015
+ if (list.length === 0) return;
1016
+ ctx.userCleanups = [];
1017
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
1018
+ try {
1019
+ list[i2]();
1020
+ } catch (err) {
1021
+ if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
1022
+ }
1023
+ }
1024
+ }
1025
+ function drainReruns(ctx) {
1026
+ let reruns = 1;
1027
+ do {
1028
+ ctx.rerunPending = false;
1029
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1030
+ retrack(ctx.bodyFn, ctx.subscriber);
1031
+ } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
1032
+ if (ctx.rerunPending) {
1033
+ ctx.rerunPending = false;
1034
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
1035
+ console.error(
1036
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
1037
+ );
940
1038
  }
941
- sub2._dep = void 0;
942
- return;
943
1039
  }
944
- const deps = sub2._deps;
945
- if (!deps || deps.size === 0) return;
946
- for (const signal2 of deps) {
947
- const subs = signal2[SUBS];
948
- if (subs) {
949
- subs.delete(subscriber);
950
- if (signal2.__f === subscriber) {
951
- signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
952
- } else if (subs.size === 1 && signal2.__f === void 0) {
953
- signal2.__f = subs.values().next().value;
954
- }
1040
+ }
1041
+ function disposeEffect(ctx) {
1042
+ if (ctx.disposed) return;
1043
+ ctx.disposed = true;
1044
+ const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1045
+ if (h) {
1046
+ try {
1047
+ h.emit("effect:destroy", { effectFn: ctx.fn });
1048
+ } catch {
1049
+ }
1050
+ }
1051
+ try {
1052
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1053
+ } catch (err) {
1054
+ if (typeof console !== "undefined") {
1055
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
1056
+ }
1057
+ }
1058
+ try {
1059
+ cleanup(ctx.subscriber);
1060
+ } catch (err) {
1061
+ if (typeof console !== "undefined") {
1062
+ console.warn("[SibuJS effect] dispose threw:", err);
955
1063
  }
956
1064
  }
957
- deps.clear();
958
1065
  }
959
-
960
- // src/core/signals/effect.ts
961
- var _g = globalThis;
962
1066
  function effect(effectFn, options) {
963
1067
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
964
1068
  if (isSSR()) return () => {
965
1069
  };
966
- const onError = options?.onError;
967
- let userCleanups = [];
968
- const onCleanup = (fn) => {
969
- userCleanups.push(fn);
1070
+ const ctx = {
1071
+ fn: effectFn,
1072
+ onError: options?.onError,
1073
+ userCleanups: [],
1074
+ running: false,
1075
+ rerunPending: false,
1076
+ disposed: false,
1077
+ onCleanup: null,
1078
+ subscriber: null,
1079
+ bodyFn: null
970
1080
  };
971
- const runUserCleanups = () => {
972
- if (userCleanups.length === 0) return;
973
- const list = userCleanups;
974
- userCleanups = [];
975
- for (let i2 = list.length - 1; i2 >= 0; i2--) {
976
- try {
977
- list[i2]();
978
- } catch (err) {
979
- if (typeof console !== "undefined") {
980
- console.warn("[SibuJS effect] onCleanup threw:", err);
981
- }
982
- }
983
- }
1081
+ ctx.onCleanup = (fn) => {
1082
+ ctx.userCleanups.push(fn);
984
1083
  };
985
- const invokeBody = () => effectFn(onCleanup);
986
- const wrappedFn = onError ? () => {
1084
+ const onErrorCaptured = ctx.onError;
1085
+ ctx.bodyFn = onErrorCaptured ? () => {
987
1086
  try {
988
- invokeBody();
1087
+ ctx.fn(ctx.onCleanup);
989
1088
  } catch (err) {
990
- onError(err);
1089
+ onErrorCaptured(err);
991
1090
  }
992
- } : invokeBody;
993
- let cleanupHandle = () => {
1091
+ } : () => {
1092
+ ctx.fn(ctx.onCleanup);
994
1093
  };
995
- let running = false;
996
- const subscriber = () => {
997
- if (running) {
998
- if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
999
- console.warn(
1000
- "[SibuJS] effect re-entered itself while running \u2014 the triggering update will be ignored. Wrap mutual writes in `batch()` or split the effect to avoid this."
1001
- );
1002
- }
1094
+ const sub2 = (() => {
1095
+ if (ctx.running) {
1096
+ ctx.rerunPending = true;
1003
1097
  return;
1004
1098
  }
1005
- running = true;
1099
+ ctx.running = true;
1006
1100
  try {
1007
- runUserCleanups();
1008
- cleanupHandle();
1009
- cleanupHandle = track(wrappedFn, subscriber);
1101
+ ctx.rerunPending = false;
1102
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1103
+ retrack(ctx.bodyFn, sub2);
1104
+ if (ctx.rerunPending) drainReruns(ctx);
1010
1105
  } finally {
1011
- running = false;
1106
+ ctx.running = false;
1107
+ ctx.rerunPending = false;
1012
1108
  }
1013
- };
1014
- running = true;
1109
+ });
1110
+ sub2.depsHead = null;
1111
+ sub2.depsTail = null;
1112
+ sub2._epoch = 0;
1113
+ sub2._structDirty = false;
1114
+ sub2._runEpoch = 0;
1115
+ sub2._runs = 0;
1116
+ ctx.subscriber = sub2;
1117
+ ctx.running = true;
1015
1118
  try {
1016
- cleanupHandle = track(wrappedFn, subscriber);
1119
+ retrack(ctx.bodyFn, ctx.subscriber);
1120
+ if (ctx.rerunPending) drainReruns(ctx);
1017
1121
  } finally {
1018
- running = false;
1122
+ ctx.running = false;
1123
+ ctx.rerunPending = false;
1019
1124
  }
1020
1125
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1021
1126
  if (hook) hook.emit("effect:create", { effectFn });
1022
- let disposed = false;
1023
- return () => {
1024
- if (disposed) return;
1025
- disposed = true;
1026
- const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1027
- if (h) {
1028
- try {
1029
- h.emit("effect:destroy", { effectFn });
1030
- } catch {
1031
- }
1032
- }
1033
- try {
1034
- runUserCleanups();
1035
- } catch (err) {
1036
- if (typeof console !== "undefined") {
1037
- console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
1038
- }
1039
- }
1040
- try {
1041
- cleanupHandle();
1042
- } catch (err) {
1043
- if (typeof console !== "undefined") {
1044
- console.warn("[SibuJS effect] dispose threw:", err);
1045
- }
1046
- }
1047
- };
1127
+ return () => disposeEffect(ctx);
1048
1128
  }
1049
1129
 
1050
1130
  // src/platform/head.ts
@@ -1229,32 +1309,64 @@ function flushBatch() {
1229
1309
  var _g2 = globalThis;
1230
1310
  var _isDev4 = isDev();
1231
1311
  function signal(initial, options) {
1232
- const state = { value: initial };
1312
+ const state = {
1313
+ value: initial,
1314
+ __v: 0,
1315
+ __sc: 0,
1316
+ subsHead: null,
1317
+ subsTail: null,
1318
+ __activeNode: null,
1319
+ __name: void 0
1320
+ };
1233
1321
  const debugName = _isDev4 ? options?.name : void 0;
1234
1322
  const equalsFn = options?.equals;
1235
- if (debugName) {
1236
- state.__name = debugName;
1237
- }
1323
+ if (debugName) state.__name = debugName;
1238
1324
  function get() {
1239
1325
  recordDependency(state);
1240
1326
  return state.value;
1241
1327
  }
1242
1328
  get.__signal = state;
1243
1329
  if (debugName) get.__name = debugName;
1244
- function set(next) {
1245
- const newValue = typeof next === "function" ? next(state.value) : next;
1246
- if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
1247
- if (_isDev4) {
1248
- const oldValue = state.value;
1330
+ let set;
1331
+ if (equalsFn) {
1332
+ set = (next) => {
1333
+ const prev = state.value;
1334
+ const newValue = typeof next === "function" ? next(prev) : next;
1335
+ if (equalsFn(prev, newValue)) return;
1336
+ state.value = newValue;
1337
+ state.__v++;
1338
+ if (_isDev4) {
1339
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1340
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
1341
+ }
1342
+ if (!enqueueBatchedSignal(state)) {
1343
+ notifySubscribers(state);
1344
+ }
1345
+ };
1346
+ } else if (_isDev4) {
1347
+ set = (next) => {
1348
+ const prev = state.value;
1349
+ const newValue = typeof next === "function" ? next(prev) : next;
1350
+ if (Object.is(newValue, prev)) return;
1249
1351
  state.value = newValue;
1352
+ state.__v++;
1250
1353
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1251
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue, newValue });
1252
- } else {
1354
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
1355
+ if (!enqueueBatchedSignal(state)) {
1356
+ notifySubscribers(state);
1357
+ }
1358
+ };
1359
+ } else {
1360
+ set = (next) => {
1361
+ const prev = state.value;
1362
+ const newValue = typeof next === "function" ? next(prev) : next;
1363
+ if (Object.is(newValue, prev)) return;
1253
1364
  state.value = newValue;
1254
- }
1255
- if (!enqueueBatchedSignal(state)) {
1256
- notifySubscribers(state);
1257
- }
1365
+ state.__v++;
1366
+ if (!enqueueBatchedSignal(state)) {
1367
+ notifySubscribers(state);
1368
+ }
1369
+ };
1258
1370
  }
1259
1371
  if (_isDev4) {
1260
1372
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;