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/widgets.cjs CHANGED
@@ -50,11 +50,88 @@ function devWarn(message) {
50
50
 
51
51
  // src/reactivity/track.ts
52
52
  var _isDev2 = isDev();
53
- var subscriberStack = new Array(32);
54
- var stackCapacity = 32;
55
- var stackTop = -1;
53
+ var POOL_MAX = 4096;
54
+ var nodePool = [];
55
+ function createNode() {
56
+ return {
57
+ sig: null,
58
+ sub: null,
59
+ epoch: 0,
60
+ sigPrev: null,
61
+ sigNext: null,
62
+ subPrev: null,
63
+ subNext: null,
64
+ prevActive: null
65
+ };
66
+ }
67
+ function allocNode(sig, sub, epoch) {
68
+ const n = nodePool.pop();
69
+ if (n) {
70
+ n.sig = sig;
71
+ n.sub = sub;
72
+ n.epoch = epoch;
73
+ return n;
74
+ }
75
+ const fresh = createNode();
76
+ fresh.sig = sig;
77
+ fresh.sub = sub;
78
+ fresh.epoch = epoch;
79
+ return fresh;
80
+ }
81
+ function freeNode(node) {
82
+ node.sig = null;
83
+ node.sub = null;
84
+ node.sigPrev = null;
85
+ node.sigNext = null;
86
+ node.subPrev = null;
87
+ node.subNext = null;
88
+ node.prevActive = null;
89
+ if (nodePool.length < POOL_MAX) nodePool.push(node);
90
+ }
91
+ function linkSignal(sig, node) {
92
+ const oldHead = sig.subsHead ?? null;
93
+ node.sigPrev = null;
94
+ node.sigNext = oldHead;
95
+ if (oldHead) oldHead.sigPrev = node;
96
+ else sig.subsTail = node;
97
+ sig.subsHead = node;
98
+ sig.__sc = (sig.__sc ?? 0) + 1;
99
+ }
100
+ function unlinkSignal(node) {
101
+ const sig = node.sig;
102
+ if (!sig) return;
103
+ const prev = node.sigPrev;
104
+ const next = node.sigNext;
105
+ if (prev) prev.sigNext = next;
106
+ else sig.subsHead = next;
107
+ if (next) next.sigPrev = prev;
108
+ else sig.subsTail = prev;
109
+ sig.__sc = (sig.__sc ?? 1) - 1;
110
+ if (sig.__activeNode === node) sig.__activeNode = node.prevActive;
111
+ if (sig.__sc === 0) {
112
+ sig.subsHead = null;
113
+ sig.subsTail = null;
114
+ }
115
+ }
116
+ function linkSub(sub, node) {
117
+ const oldTail = sub.depsTail ?? null;
118
+ node.subPrev = oldTail;
119
+ node.subNext = null;
120
+ if (oldTail) oldTail.subNext = node;
121
+ else sub.depsHead = node;
122
+ sub.depsTail = node;
123
+ }
124
+ function unlinkSub(node) {
125
+ const sub = node.sub;
126
+ if (!sub) return;
127
+ const prev = node.subPrev;
128
+ const next = node.subNext;
129
+ if (prev) prev.subNext = next;
130
+ else sub.depsHead = next;
131
+ if (next) next.subPrev = prev;
132
+ else sub.depsTail = prev;
133
+ }
56
134
  var currentSubscriber = null;
57
- var SUBS = "__s";
58
135
  var notifyDepth = 0;
59
136
  var pendingQueue = [];
60
137
  var pendingSet = /* @__PURE__ */ new Set();
@@ -67,92 +144,136 @@ function safeInvoke(sub) {
67
144
  }
68
145
  }
69
146
  var trackingSuspended = false;
147
+ var subscriberEpochCounter = 0;
70
148
  function retrack(effectFn, subscriber) {
71
149
  const prev = currentSubscriber;
72
150
  currentSubscriber = subscriber;
151
+ const sub = subscriber;
152
+ const epoch = ++subscriberEpochCounter;
153
+ sub._epoch = epoch;
154
+ sub._structDirty = false;
155
+ for (let n = sub.depsHead ?? null; n !== null; n = n.subNext) {
156
+ const sig = n.sig;
157
+ n.prevActive = sig.__activeNode ?? null;
158
+ sig.__activeNode = n;
159
+ }
73
160
  try {
74
161
  effectFn();
75
162
  } finally {
76
163
  currentSubscriber = prev;
164
+ let node = sub.depsHead ?? null;
165
+ while (node !== null) {
166
+ const next = node.subNext;
167
+ const sig = node.sig;
168
+ sig.__activeNode = node.prevActive;
169
+ node.prevActive = null;
170
+ if (node.epoch !== epoch) {
171
+ unlinkSub(node);
172
+ unlinkSignal(node);
173
+ freeNode(node);
174
+ }
175
+ node = next;
176
+ }
77
177
  }
78
178
  }
79
179
  function track(effectFn, subscriber) {
80
180
  if (!subscriber) subscriber = effectFn;
81
181
  cleanup(subscriber);
82
- ++stackTop;
83
- if (stackTop >= stackCapacity) {
84
- stackCapacity *= 2;
85
- subscriberStack.length = stackCapacity;
86
- }
87
- subscriberStack[stackTop] = subscriber;
182
+ const prev = currentSubscriber;
88
183
  currentSubscriber = subscriber;
89
184
  try {
90
185
  effectFn();
91
186
  } finally {
92
- stackTop--;
93
- currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
187
+ currentSubscriber = prev;
188
+ const sub2 = subscriber;
189
+ for (let n = sub2.depsHead ?? null; n !== null; n = n.subNext) {
190
+ const sig = n.sig;
191
+ sig.__activeNode = n.prevActive;
192
+ n.prevActive = null;
193
+ }
94
194
  }
95
- return () => cleanup(subscriber);
195
+ const sub = subscriber;
196
+ return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
96
197
  }
97
198
  function recordDependency(signal2) {
98
199
  if (!currentSubscriber) return;
99
200
  const sub = currentSubscriber;
100
- if (sub._dep === signal2) return;
101
- const deps = sub._deps;
102
- if (deps) {
103
- if (deps.has(signal2)) return;
104
- deps.add(signal2);
105
- } else if (sub._dep !== void 0) {
106
- const set = /* @__PURE__ */ new Set();
107
- set.add(sub._dep);
108
- set.add(signal2);
109
- sub._deps = set;
110
- sub._dep = void 0;
111
- } else {
112
- sub._dep = signal2;
201
+ const sig = signal2;
202
+ const epoch = sub._epoch ?? 0;
203
+ const active = sig.__activeNode ?? null;
204
+ if (active !== null && active.sub === sub) {
205
+ active.epoch = epoch;
206
+ return;
207
+ }
208
+ const node = allocNode(signal2, sub, epoch);
209
+ node.prevActive = active;
210
+ sig.__activeNode = node;
211
+ linkSub(sub, node);
212
+ linkSignal(sig, node);
213
+ sub._structDirty = true;
214
+ }
215
+ function cleanup(subscriber) {
216
+ const sub = subscriber;
217
+ let node = sub.depsHead ?? null;
218
+ sub.depsHead = null;
219
+ sub.depsTail = null;
220
+ while (node) {
221
+ const next = node.subNext;
222
+ unlinkSignal(node);
223
+ freeNode(node);
224
+ node = next;
113
225
  }
114
- let subs = signal2[SUBS];
115
- if (!subs) {
116
- subs = /* @__PURE__ */ new Set();
117
- signal2[SUBS] = subs;
226
+ }
227
+ var maxSubscriberRepeats = 50;
228
+ var maxDrainIterations = 1e6;
229
+ var drainEpoch = 0;
230
+ function tickRepeat(sub) {
231
+ const s = sub;
232
+ if (s._runEpoch !== drainEpoch) {
233
+ s._runEpoch = drainEpoch;
234
+ s._runs = 1;
235
+ return false;
236
+ }
237
+ s._runs = (s._runs ?? 0) + 1;
238
+ return s._runs > maxSubscriberRepeats;
239
+ }
240
+ function cycleError(sub) {
241
+ if (typeof console !== "undefined") {
242
+ const name = sub.__name ?? "<unnamed>";
243
+ console.error(
244
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
245
+ );
118
246
  }
119
- subs.add(currentSubscriber);
120
- if (subs.size === 1) {
121
- signal2.__f = currentSubscriber;
122
- } else if (signal2.__f !== void 0) {
123
- signal2.__f = void 0;
247
+ }
248
+ function absoluteDrainError() {
249
+ if (typeof console !== "undefined") {
250
+ console.error(
251
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
252
+ );
124
253
  }
125
254
  }
126
- function queueSignalNotification(signal2) {
127
- const subs = signal2[SUBS];
128
- if (!subs) return;
129
- for (const sub of subs) {
130
- if (sub._c) {
131
- propagateDirty(sub);
132
- } else if (!pendingSet.has(sub)) {
133
- pendingSet.add(sub);
134
- pendingQueue.push(sub);
255
+ function drainQueue() {
256
+ let i = 0;
257
+ while (i < pendingQueue.length) {
258
+ if (i >= maxDrainIterations) {
259
+ absoluteDrainError();
260
+ break;
135
261
  }
262
+ const sub = pendingQueue[i++];
263
+ if (tickRepeat(sub)) {
264
+ cycleError(sub);
265
+ break;
266
+ }
267
+ pendingSet.delete(sub);
268
+ safeInvoke(sub);
136
269
  }
137
270
  }
138
- var maxDrainIterations = 1e5;
139
271
  function drainNotificationQueue() {
140
272
  if (notifyDepth > 0) return;
141
273
  notifyDepth++;
274
+ drainEpoch++;
142
275
  try {
143
- let i = 0;
144
- while (i < pendingQueue.length) {
145
- if (i >= maxDrainIterations) {
146
- if (typeof console !== "undefined") {
147
- console.error(
148
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
149
- );
150
- }
151
- break;
152
- }
153
- safeInvoke(pendingQueue[i]);
154
- i++;
155
- }
276
+ drainQueue();
156
277
  } finally {
157
278
  notifyDepth--;
158
279
  if (notifyDepth === 0) {
@@ -170,131 +291,82 @@ function propagateDirty(sub) {
170
291
  stack.push(rootSig);
171
292
  while (stack.length > baseLen) {
172
293
  const sig = stack.pop();
173
- const first = sig.__f;
174
- if (first) {
175
- if (first._c) {
176
- const nSig = first._sig;
177
- if (!nSig._d) {
178
- nSig._d = true;
179
- stack.push(nSig);
294
+ let node = sig.subsHead ?? null;
295
+ while (node) {
296
+ const s = node.sub;
297
+ if (s) {
298
+ if (s._c) {
299
+ const nSig = s._sig;
300
+ if (nSig) {
301
+ if (!nSig._d) {
302
+ nSig._d = true;
303
+ stack.push(nSig);
304
+ }
305
+ } else {
306
+ s();
307
+ }
308
+ } else if (!pendingSet.has(s)) {
309
+ pendingSet.add(s);
310
+ pendingQueue.push(s);
180
311
  }
181
- } else if (!pendingSet.has(first)) {
182
- pendingSet.add(first);
183
- pendingQueue.push(first);
184
312
  }
185
- continue;
313
+ node = node.sigNext;
186
314
  }
187
- const subs = sig[SUBS];
188
- if (!subs) continue;
189
- for (const s of subs) {
315
+ }
316
+ }
317
+ function queueSignalNotification(signal2) {
318
+ const sig = signal2;
319
+ let node = sig.subsHead ?? null;
320
+ while (node) {
321
+ const s = node.sub;
322
+ if (s) {
190
323
  if (s._c) {
191
- const nSig = s._sig;
192
- if (nSig && !nSig._d) {
193
- nSig._d = true;
194
- stack.push(nSig);
195
- } else if (!nSig) {
196
- s();
197
- }
324
+ propagateDirty(s);
198
325
  } else if (!pendingSet.has(s)) {
199
326
  pendingSet.add(s);
200
327
  pendingQueue.push(s);
201
328
  }
202
329
  }
330
+ node = node.sigNext;
203
331
  }
204
332
  }
205
333
  function notifySubscribers(signal2) {
206
- const first = signal2.__f;
207
- if (first) {
208
- if (notifyDepth > 0) {
209
- if (first._c) {
210
- propagateDirty(first);
211
- } else if (!pendingSet.has(first)) {
212
- pendingSet.add(first);
213
- pendingQueue.push(first);
214
- }
215
- return;
216
- }
217
- notifyDepth++;
218
- try {
219
- if (first._c) {
220
- propagateDirty(first);
221
- } else {
222
- safeInvoke(first);
223
- }
224
- let i = 0;
225
- while (i < pendingQueue.length) {
226
- if (i >= maxDrainIterations) {
227
- if (typeof console !== "undefined") {
228
- console.error(
229
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
230
- );
231
- }
232
- break;
233
- }
234
- safeInvoke(pendingQueue[i]);
235
- i++;
236
- }
237
- } finally {
238
- notifyDepth--;
239
- if (notifyDepth === 0) {
240
- pendingQueue.length = 0;
241
- pendingSet.clear();
242
- }
243
- }
244
- return;
245
- }
246
- const subs = signal2[SUBS];
247
- if (!subs || subs.size === 0) return;
334
+ const sig = signal2;
335
+ const head = sig.subsHead;
336
+ if (!head) return;
248
337
  if (notifyDepth > 0) {
249
- for (const sub of subs) {
250
- if (sub._c) {
251
- propagateDirty(sub);
252
- } else if (!pendingSet.has(sub)) {
253
- pendingSet.add(sub);
254
- pendingQueue.push(sub);
338
+ let node = head;
339
+ while (node) {
340
+ const s = node.sub;
341
+ if (s) {
342
+ if (s._c) {
343
+ propagateDirty(s);
344
+ } else if (!pendingSet.has(s)) {
345
+ pendingSet.add(s);
346
+ pendingQueue.push(s);
347
+ }
255
348
  }
349
+ node = node.sigNext;
256
350
  }
257
351
  return;
258
352
  }
259
353
  notifyDepth++;
354
+ drainEpoch++;
260
355
  try {
261
- let directCount = 0;
262
- let hasComputedSub = false;
263
- for (const sub of subs) {
264
- if (sub._c) hasComputedSub = true;
265
- pendingQueue[directCount++] = sub;
266
- }
267
- if (!hasComputedSub) {
268
- for (let i2 = 0; i2 < directCount; i2++) {
269
- safeInvoke(pendingQueue[i2]);
270
- }
271
- } else {
272
- for (let i2 = 0; i2 < directCount; i2++) {
273
- if (pendingQueue[i2]._c) {
274
- propagateDirty(pendingQueue[i2]);
275
- }
276
- }
277
- for (let i2 = 0; i2 < directCount; i2++) {
278
- const sub = pendingQueue[i2];
279
- if (!sub._c && !pendingSet.has(sub)) {
280
- pendingSet.add(sub);
281
- safeInvoke(sub);
356
+ let node = head;
357
+ while (node) {
358
+ const s = node.sub;
359
+ if (s) {
360
+ if (s._c) {
361
+ propagateDirty(s);
362
+ } else if (!pendingSet.has(s)) {
363
+ pendingSet.add(s);
364
+ pendingQueue.push(s);
282
365
  }
283
366
  }
367
+ node = node.sigNext;
284
368
  }
285
- let i = directCount;
286
- while (i < pendingQueue.length) {
287
- if (i - directCount >= maxDrainIterations) {
288
- if (typeof console !== "undefined") {
289
- console.error(
290
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
291
- );
292
- }
293
- break;
294
- }
295
- safeInvoke(pendingQueue[i]);
296
- i++;
297
- }
369
+ drainQueue();
298
370
  } finally {
299
371
  notifyDepth--;
300
372
  if (notifyDepth === 0) {
@@ -303,37 +375,6 @@ function notifySubscribers(signal2) {
303
375
  }
304
376
  }
305
377
  }
306
- function cleanup(subscriber) {
307
- const sub = subscriber;
308
- const singleDep = sub._dep;
309
- if (singleDep !== void 0) {
310
- const subs = singleDep[SUBS];
311
- if (subs) {
312
- subs.delete(subscriber);
313
- if (singleDep.__f === subscriber) {
314
- singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
315
- } else if (subs.size === 1 && singleDep.__f === void 0) {
316
- singleDep.__f = subs.values().next().value;
317
- }
318
- }
319
- sub._dep = void 0;
320
- return;
321
- }
322
- const deps = sub._deps;
323
- if (!deps || deps.size === 0) return;
324
- for (const signal2 of deps) {
325
- const subs = signal2[SUBS];
326
- if (subs) {
327
- subs.delete(subscriber);
328
- if (signal2.__f === subscriber) {
329
- signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
330
- } else if (subs.size === 1 && signal2.__f === void 0) {
331
- signal2.__f = subs.values().next().value;
332
- }
333
- }
334
- }
335
- deps.clear();
336
- }
337
378
 
338
379
  // src/core/signals/derived.ts
339
380
  function derived(getter, options) {
@@ -343,6 +384,7 @@ function derived(getter, options) {
343
384
  const cs = {};
344
385
  cs._d = false;
345
386
  cs._g = getter;
387
+ cs.__v = 0;
346
388
  const markDirty = () => {
347
389
  if (cs._d) return;
348
390
  cs._d = true;
@@ -372,11 +414,14 @@ function derived(getter, options) {
372
414
  evaluating = true;
373
415
  let threw = true;
374
416
  try {
417
+ const prev = cs._v;
375
418
  retrack(() => {
376
- cs._v = getter();
419
+ const next = getter();
420
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
377
421
  cs._d = false;
378
422
  threw = false;
379
423
  }, markDirty);
424
+ if (!Object.is(prev, cs._v)) cs.__v++;
380
425
  } finally {
381
426
  evaluating = false;
382
427
  if (threw) cs._d = true;
@@ -396,6 +441,7 @@ function derived(getter, options) {
396
441
  cs._d = false;
397
442
  threw = false;
398
443
  }, markDirty);
444
+ if (!Object.is(oldValue, cs._v)) cs.__v++;
399
445
  } finally {
400
446
  evaluating = false;
401
447
  if (threw) cs._d = true;
@@ -442,92 +488,122 @@ function isSSR() {
442
488
 
443
489
  // src/core/signals/effect.ts
444
490
  var _g = globalThis;
491
+ var MAX_RERUNS = 100;
492
+ function flushUserCleanups(ctx) {
493
+ const list = ctx.userCleanups;
494
+ if (list.length === 0) return;
495
+ ctx.userCleanups = [];
496
+ for (let i = list.length - 1; i >= 0; i--) {
497
+ try {
498
+ list[i]();
499
+ } catch (err) {
500
+ if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
501
+ }
502
+ }
503
+ }
504
+ function drainReruns(ctx) {
505
+ let reruns = 1;
506
+ do {
507
+ ctx.rerunPending = false;
508
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
509
+ retrack(ctx.bodyFn, ctx.subscriber);
510
+ } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
511
+ if (ctx.rerunPending) {
512
+ ctx.rerunPending = false;
513
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
514
+ console.error(
515
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
516
+ );
517
+ }
518
+ }
519
+ }
520
+ function disposeEffect(ctx) {
521
+ if (ctx.disposed) return;
522
+ ctx.disposed = true;
523
+ const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
524
+ if (h) {
525
+ try {
526
+ h.emit("effect:destroy", { effectFn: ctx.fn });
527
+ } catch {
528
+ }
529
+ }
530
+ try {
531
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
532
+ } catch (err) {
533
+ if (typeof console !== "undefined") {
534
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
535
+ }
536
+ }
537
+ try {
538
+ cleanup(ctx.subscriber);
539
+ } catch (err) {
540
+ if (typeof console !== "undefined") {
541
+ console.warn("[SibuJS effect] dispose threw:", err);
542
+ }
543
+ }
544
+ }
445
545
  function effect(effectFn, options) {
446
546
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
447
547
  if (isSSR()) return () => {
448
548
  };
449
- const onError = options?.onError;
450
- let userCleanups = [];
451
- const onCleanup = (fn) => {
452
- userCleanups.push(fn);
549
+ const ctx = {
550
+ fn: effectFn,
551
+ onError: options?.onError,
552
+ userCleanups: [],
553
+ running: false,
554
+ rerunPending: false,
555
+ disposed: false,
556
+ onCleanup: null,
557
+ subscriber: null,
558
+ bodyFn: null
453
559
  };
454
- const runUserCleanups = () => {
455
- if (userCleanups.length === 0) return;
456
- const list = userCleanups;
457
- userCleanups = [];
458
- for (let i = list.length - 1; i >= 0; i--) {
459
- try {
460
- list[i]();
461
- } catch (err) {
462
- if (typeof console !== "undefined") {
463
- console.warn("[SibuJS effect] onCleanup threw:", err);
464
- }
465
- }
466
- }
560
+ ctx.onCleanup = (fn) => {
561
+ ctx.userCleanups.push(fn);
467
562
  };
468
- const invokeBody = () => effectFn(onCleanup);
469
- const wrappedFn = onError ? () => {
563
+ const onErrorCaptured = ctx.onError;
564
+ ctx.bodyFn = onErrorCaptured ? () => {
470
565
  try {
471
- invokeBody();
566
+ ctx.fn(ctx.onCleanup);
472
567
  } catch (err) {
473
- onError(err);
568
+ onErrorCaptured(err);
474
569
  }
475
- } : invokeBody;
476
- let cleanupHandle = () => {
570
+ } : () => {
571
+ ctx.fn(ctx.onCleanup);
477
572
  };
478
- let running = false;
479
- const subscriber = () => {
480
- if (running) {
481
- if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
482
- console.warn(
483
- "[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."
484
- );
485
- }
573
+ const sub = (() => {
574
+ if (ctx.running) {
575
+ ctx.rerunPending = true;
486
576
  return;
487
577
  }
488
- running = true;
578
+ ctx.running = true;
489
579
  try {
490
- runUserCleanups();
491
- cleanupHandle();
492
- cleanupHandle = track(wrappedFn, subscriber);
580
+ ctx.rerunPending = false;
581
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
582
+ retrack(ctx.bodyFn, sub);
583
+ if (ctx.rerunPending) drainReruns(ctx);
493
584
  } finally {
494
- running = false;
585
+ ctx.running = false;
586
+ ctx.rerunPending = false;
495
587
  }
496
- };
497
- running = true;
588
+ });
589
+ sub.depsHead = null;
590
+ sub.depsTail = null;
591
+ sub._epoch = 0;
592
+ sub._structDirty = false;
593
+ sub._runEpoch = 0;
594
+ sub._runs = 0;
595
+ ctx.subscriber = sub;
596
+ ctx.running = true;
498
597
  try {
499
- cleanupHandle = track(wrappedFn, subscriber);
598
+ retrack(ctx.bodyFn, ctx.subscriber);
599
+ if (ctx.rerunPending) drainReruns(ctx);
500
600
  } finally {
501
- running = false;
601
+ ctx.running = false;
602
+ ctx.rerunPending = false;
502
603
  }
503
604
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
504
605
  if (hook) hook.emit("effect:create", { effectFn });
505
- let disposed = false;
506
- return () => {
507
- if (disposed) return;
508
- disposed = true;
509
- const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
510
- if (h) {
511
- try {
512
- h.emit("effect:destroy", { effectFn });
513
- } catch {
514
- }
515
- }
516
- try {
517
- runUserCleanups();
518
- } catch (err) {
519
- if (typeof console !== "undefined") {
520
- console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
521
- }
522
- }
523
- try {
524
- cleanupHandle();
525
- } catch (err) {
526
- if (typeof console !== "undefined") {
527
- console.warn("[SibuJS effect] dispose threw:", err);
528
- }
529
- }
530
- };
606
+ return () => disposeEffect(ctx);
531
607
  }
532
608
 
533
609
  // src/reactivity/batch.ts
@@ -564,32 +640,64 @@ function flushBatch() {
564
640
  var _g2 = globalThis;
565
641
  var _isDev3 = isDev();
566
642
  function signal(initial, options) {
567
- const state = { value: initial };
643
+ const state = {
644
+ value: initial,
645
+ __v: 0,
646
+ __sc: 0,
647
+ subsHead: null,
648
+ subsTail: null,
649
+ __activeNode: null,
650
+ __name: void 0
651
+ };
568
652
  const debugName = _isDev3 ? options?.name : void 0;
569
653
  const equalsFn = options?.equals;
570
- if (debugName) {
571
- state.__name = debugName;
572
- }
654
+ if (debugName) state.__name = debugName;
573
655
  function get() {
574
656
  recordDependency(state);
575
657
  return state.value;
576
658
  }
577
659
  get.__signal = state;
578
660
  if (debugName) get.__name = debugName;
579
- function set(next) {
580
- const newValue = typeof next === "function" ? next(state.value) : next;
581
- if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
582
- if (_isDev3) {
583
- const oldValue = state.value;
661
+ let set;
662
+ if (equalsFn) {
663
+ set = (next) => {
664
+ const prev = state.value;
665
+ const newValue = typeof next === "function" ? next(prev) : next;
666
+ if (equalsFn(prev, newValue)) return;
667
+ state.value = newValue;
668
+ state.__v++;
669
+ if (_isDev3) {
670
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
671
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
672
+ }
673
+ if (!enqueueBatchedSignal(state)) {
674
+ notifySubscribers(state);
675
+ }
676
+ };
677
+ } else if (_isDev3) {
678
+ set = (next) => {
679
+ const prev = state.value;
680
+ const newValue = typeof next === "function" ? next(prev) : next;
681
+ if (Object.is(newValue, prev)) return;
584
682
  state.value = newValue;
683
+ state.__v++;
585
684
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
586
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue, newValue });
587
- } else {
685
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
686
+ if (!enqueueBatchedSignal(state)) {
687
+ notifySubscribers(state);
688
+ }
689
+ };
690
+ } else {
691
+ set = (next) => {
692
+ const prev = state.value;
693
+ const newValue = typeof next === "function" ? next(prev) : next;
694
+ if (Object.is(newValue, prev)) return;
588
695
  state.value = newValue;
589
- }
590
- if (!enqueueBatchedSignal(state)) {
591
- notifySubscribers(state);
592
- }
696
+ state.__v++;
697
+ if (!enqueueBatchedSignal(state)) {
698
+ notifySubscribers(state);
699
+ }
700
+ };
593
701
  }
594
702
  if (_isDev3) {
595
703
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;