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