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