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/ui.cjs CHANGED
@@ -103,11 +103,88 @@ function devWarn(message) {
103
103
 
104
104
  // src/reactivity/track.ts
105
105
  var _isDev2 = isDev();
106
- var subscriberStack = new Array(32);
107
- var stackCapacity = 32;
108
- var stackTop = -1;
106
+ var POOL_MAX = 4096;
107
+ var nodePool = [];
108
+ function createNode() {
109
+ return {
110
+ sig: null,
111
+ sub: null,
112
+ epoch: 0,
113
+ sigPrev: null,
114
+ sigNext: null,
115
+ subPrev: null,
116
+ subNext: null,
117
+ prevActive: null
118
+ };
119
+ }
120
+ function allocNode(sig, sub, epoch) {
121
+ const n = nodePool.pop();
122
+ if (n) {
123
+ n.sig = sig;
124
+ n.sub = sub;
125
+ n.epoch = epoch;
126
+ return n;
127
+ }
128
+ const fresh = createNode();
129
+ fresh.sig = sig;
130
+ fresh.sub = sub;
131
+ fresh.epoch = epoch;
132
+ return fresh;
133
+ }
134
+ function freeNode(node) {
135
+ node.sig = null;
136
+ node.sub = null;
137
+ node.sigPrev = null;
138
+ node.sigNext = null;
139
+ node.subPrev = null;
140
+ node.subNext = null;
141
+ node.prevActive = null;
142
+ if (nodePool.length < POOL_MAX) nodePool.push(node);
143
+ }
144
+ function linkSignal(sig, node) {
145
+ const oldHead = sig.subsHead ?? null;
146
+ node.sigPrev = null;
147
+ node.sigNext = oldHead;
148
+ if (oldHead) oldHead.sigPrev = node;
149
+ else sig.subsTail = node;
150
+ sig.subsHead = node;
151
+ sig.__sc = (sig.__sc ?? 0) + 1;
152
+ }
153
+ function unlinkSignal(node) {
154
+ const sig = node.sig;
155
+ if (!sig) return;
156
+ const prev = node.sigPrev;
157
+ const next = node.sigNext;
158
+ if (prev) prev.sigNext = next;
159
+ else sig.subsHead = next;
160
+ if (next) next.sigPrev = prev;
161
+ else sig.subsTail = prev;
162
+ sig.__sc = (sig.__sc ?? 1) - 1;
163
+ if (sig.__activeNode === node) sig.__activeNode = node.prevActive;
164
+ if (sig.__sc === 0) {
165
+ sig.subsHead = null;
166
+ sig.subsTail = null;
167
+ }
168
+ }
169
+ function linkSub(sub, node) {
170
+ const oldTail = sub.depsTail ?? null;
171
+ node.subPrev = oldTail;
172
+ node.subNext = null;
173
+ if (oldTail) oldTail.subNext = node;
174
+ else sub.depsHead = node;
175
+ sub.depsTail = node;
176
+ }
177
+ function unlinkSub(node) {
178
+ const sub = node.sub;
179
+ if (!sub) return;
180
+ const prev = node.subPrev;
181
+ const next = node.subNext;
182
+ if (prev) prev.subNext = next;
183
+ else sub.depsHead = next;
184
+ if (next) next.subPrev = prev;
185
+ else sub.depsTail = prev;
186
+ }
109
187
  var currentSubscriber = null;
110
- var SUBS = "__s";
111
188
  var notifyDepth = 0;
112
189
  var pendingQueue = [];
113
190
  var pendingSet = /* @__PURE__ */ new Set();
@@ -120,63 +197,130 @@ function safeInvoke(sub) {
120
197
  }
121
198
  }
122
199
  var trackingSuspended = false;
200
+ var subscriberEpochCounter = 0;
123
201
  function retrack(effectFn, subscriber) {
124
202
  const prev = currentSubscriber;
125
203
  currentSubscriber = subscriber;
204
+ const sub = subscriber;
205
+ const epoch = ++subscriberEpochCounter;
206
+ sub._epoch = epoch;
207
+ sub._structDirty = false;
208
+ for (let n = sub.depsHead ?? null; n !== null; n = n.subNext) {
209
+ const sig = n.sig;
210
+ n.prevActive = sig.__activeNode ?? null;
211
+ sig.__activeNode = n;
212
+ }
126
213
  try {
127
214
  effectFn();
128
215
  } finally {
129
216
  currentSubscriber = prev;
217
+ let node = sub.depsHead ?? null;
218
+ while (node !== null) {
219
+ const next = node.subNext;
220
+ const sig = node.sig;
221
+ sig.__activeNode = node.prevActive;
222
+ node.prevActive = null;
223
+ if (node.epoch !== epoch) {
224
+ unlinkSub(node);
225
+ unlinkSignal(node);
226
+ freeNode(node);
227
+ }
228
+ node = next;
229
+ }
130
230
  }
131
231
  }
132
232
  function track(effectFn, subscriber) {
133
233
  if (!subscriber) subscriber = effectFn;
134
234
  cleanup(subscriber);
135
- ++stackTop;
136
- if (stackTop >= stackCapacity) {
137
- stackCapacity *= 2;
138
- subscriberStack.length = stackCapacity;
139
- }
140
- subscriberStack[stackTop] = subscriber;
235
+ const prev = currentSubscriber;
141
236
  currentSubscriber = subscriber;
142
237
  try {
143
238
  effectFn();
144
239
  } finally {
145
- stackTop--;
146
- currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
240
+ currentSubscriber = prev;
241
+ const sub2 = subscriber;
242
+ for (let n = sub2.depsHead ?? null; n !== null; n = n.subNext) {
243
+ const sig = n.sig;
244
+ sig.__activeNode = n.prevActive;
245
+ n.prevActive = null;
246
+ }
147
247
  }
148
- return () => cleanup(subscriber);
248
+ const sub = subscriber;
249
+ return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
149
250
  }
150
251
  function recordDependency(signal2) {
151
252
  if (!currentSubscriber) return;
152
253
  const sub = currentSubscriber;
153
- if (sub._dep === signal2) return;
154
- const deps = sub._deps;
155
- if (deps) {
156
- if (deps.has(signal2)) return;
157
- deps.add(signal2);
158
- } else if (sub._dep !== void 0) {
159
- const set = /* @__PURE__ */ new Set();
160
- set.add(sub._dep);
161
- set.add(signal2);
162
- sub._deps = set;
163
- sub._dep = void 0;
164
- } else {
165
- sub._dep = signal2;
254
+ const sig = signal2;
255
+ const epoch = sub._epoch ?? 0;
256
+ const active = sig.__activeNode ?? null;
257
+ if (active !== null && active.sub === sub) {
258
+ active.epoch = epoch;
259
+ return;
166
260
  }
167
- let subs = signal2[SUBS];
168
- if (!subs) {
169
- subs = /* @__PURE__ */ new Set();
170
- signal2[SUBS] = subs;
261
+ const node = allocNode(signal2, sub, epoch);
262
+ node.prevActive = active;
263
+ sig.__activeNode = node;
264
+ linkSub(sub, node);
265
+ linkSignal(sig, node);
266
+ sub._structDirty = true;
267
+ }
268
+ function cleanup(subscriber) {
269
+ const sub = subscriber;
270
+ let node = sub.depsHead ?? null;
271
+ sub.depsHead = null;
272
+ sub.depsTail = null;
273
+ while (node) {
274
+ const next = node.subNext;
275
+ unlinkSignal(node);
276
+ freeNode(node);
277
+ node = next;
278
+ }
279
+ }
280
+ var maxSubscriberRepeats = 50;
281
+ var maxDrainIterations = 1e6;
282
+ var drainEpoch = 0;
283
+ function tickRepeat(sub) {
284
+ const s = sub;
285
+ if (s._runEpoch !== drainEpoch) {
286
+ s._runEpoch = drainEpoch;
287
+ s._runs = 1;
288
+ return false;
289
+ }
290
+ s._runs = (s._runs ?? 0) + 1;
291
+ return s._runs > maxSubscriberRepeats;
292
+ }
293
+ function cycleError(sub) {
294
+ if (typeof console !== "undefined") {
295
+ const name = sub.__name ?? "<unnamed>";
296
+ console.error(
297
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
298
+ );
299
+ }
300
+ }
301
+ function absoluteDrainError() {
302
+ if (typeof console !== "undefined") {
303
+ console.error(
304
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
305
+ );
171
306
  }
172
- subs.add(currentSubscriber);
173
- if (subs.size === 1) {
174
- signal2.__f = currentSubscriber;
175
- } else if (signal2.__f !== void 0) {
176
- signal2.__f = void 0;
307
+ }
308
+ function drainQueue() {
309
+ let i = 0;
310
+ while (i < pendingQueue.length) {
311
+ if (i >= maxDrainIterations) {
312
+ absoluteDrainError();
313
+ break;
314
+ }
315
+ const sub = pendingQueue[i++];
316
+ if (tickRepeat(sub)) {
317
+ cycleError(sub);
318
+ break;
319
+ }
320
+ pendingSet.delete(sub);
321
+ safeInvoke(sub);
177
322
  }
178
323
  }
179
- var maxDrainIterations = 1e5;
180
324
  function propagateDirty(sub) {
181
325
  sub();
182
326
  const rootSig = sub._sig;
@@ -186,131 +330,66 @@ function propagateDirty(sub) {
186
330
  stack.push(rootSig);
187
331
  while (stack.length > baseLen) {
188
332
  const sig = stack.pop();
189
- const first = sig.__f;
190
- if (first) {
191
- if (first._c) {
192
- const nSig = first._sig;
193
- if (!nSig._d) {
194
- nSig._d = true;
195
- stack.push(nSig);
196
- }
197
- } else if (!pendingSet.has(first)) {
198
- pendingSet.add(first);
199
- pendingQueue.push(first);
200
- }
201
- continue;
202
- }
203
- const subs = sig[SUBS];
204
- if (!subs) continue;
205
- for (const s of subs) {
206
- if (s._c) {
207
- const nSig = s._sig;
208
- if (nSig && !nSig._d) {
209
- nSig._d = true;
210
- stack.push(nSig);
211
- } else if (!nSig) {
212
- s();
333
+ let node = sig.subsHead ?? null;
334
+ while (node) {
335
+ const s = node.sub;
336
+ if (s) {
337
+ if (s._c) {
338
+ const nSig = s._sig;
339
+ if (nSig) {
340
+ if (!nSig._d) {
341
+ nSig._d = true;
342
+ stack.push(nSig);
343
+ }
344
+ } else {
345
+ s();
346
+ }
347
+ } else if (!pendingSet.has(s)) {
348
+ pendingSet.add(s);
349
+ pendingQueue.push(s);
213
350
  }
214
- } else if (!pendingSet.has(s)) {
215
- pendingSet.add(s);
216
- pendingQueue.push(s);
217
351
  }
352
+ node = node.sigNext;
218
353
  }
219
354
  }
220
355
  }
221
356
  function notifySubscribers(signal2) {
222
- const first = signal2.__f;
223
- if (first) {
224
- if (notifyDepth > 0) {
225
- if (first._c) {
226
- propagateDirty(first);
227
- } else if (!pendingSet.has(first)) {
228
- pendingSet.add(first);
229
- pendingQueue.push(first);
230
- }
231
- return;
232
- }
233
- notifyDepth++;
234
- try {
235
- if (first._c) {
236
- propagateDirty(first);
237
- } else {
238
- safeInvoke(first);
239
- }
240
- let i = 0;
241
- while (i < pendingQueue.length) {
242
- if (i >= maxDrainIterations) {
243
- if (typeof console !== "undefined") {
244
- console.error(
245
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
246
- );
247
- }
248
- break;
249
- }
250
- safeInvoke(pendingQueue[i]);
251
- i++;
252
- }
253
- } finally {
254
- notifyDepth--;
255
- if (notifyDepth === 0) {
256
- pendingQueue.length = 0;
257
- pendingSet.clear();
258
- }
259
- }
260
- return;
261
- }
262
- const subs = signal2[SUBS];
263
- if (!subs || subs.size === 0) return;
357
+ const sig = signal2;
358
+ const head = sig.subsHead;
359
+ if (!head) return;
264
360
  if (notifyDepth > 0) {
265
- for (const sub of subs) {
266
- if (sub._c) {
267
- propagateDirty(sub);
268
- } else if (!pendingSet.has(sub)) {
269
- pendingSet.add(sub);
270
- pendingQueue.push(sub);
361
+ let node = head;
362
+ while (node) {
363
+ const s = node.sub;
364
+ if (s) {
365
+ if (s._c) {
366
+ propagateDirty(s);
367
+ } else if (!pendingSet.has(s)) {
368
+ pendingSet.add(s);
369
+ pendingQueue.push(s);
370
+ }
271
371
  }
372
+ node = node.sigNext;
272
373
  }
273
374
  return;
274
375
  }
275
376
  notifyDepth++;
377
+ drainEpoch++;
276
378
  try {
277
- let directCount = 0;
278
- let hasComputedSub = false;
279
- for (const sub of subs) {
280
- if (sub._c) hasComputedSub = true;
281
- pendingQueue[directCount++] = sub;
282
- }
283
- if (!hasComputedSub) {
284
- for (let i2 = 0; i2 < directCount; i2++) {
285
- safeInvoke(pendingQueue[i2]);
286
- }
287
- } else {
288
- for (let i2 = 0; i2 < directCount; i2++) {
289
- if (pendingQueue[i2]._c) {
290
- propagateDirty(pendingQueue[i2]);
291
- }
292
- }
293
- for (let i2 = 0; i2 < directCount; i2++) {
294
- const sub = pendingQueue[i2];
295
- if (!sub._c && !pendingSet.has(sub)) {
296
- pendingSet.add(sub);
297
- safeInvoke(sub);
298
- }
299
- }
300
- }
301
- let i = directCount;
302
- while (i < pendingQueue.length) {
303
- if (i - directCount >= maxDrainIterations) {
304
- if (typeof console !== "undefined") {
305
- console.error(
306
- `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
307
- );
379
+ let node = head;
380
+ while (node) {
381
+ const s = node.sub;
382
+ if (s) {
383
+ if (s._c) {
384
+ propagateDirty(s);
385
+ } else if (!pendingSet.has(s)) {
386
+ pendingSet.add(s);
387
+ pendingQueue.push(s);
308
388
  }
309
- break;
310
389
  }
311
- safeInvoke(pendingQueue[i]);
312
- i++;
390
+ node = node.sigNext;
313
391
  }
392
+ drainQueue();
314
393
  } finally {
315
394
  notifyDepth--;
316
395
  if (notifyDepth === 0) {
@@ -319,37 +398,6 @@ function notifySubscribers(signal2) {
319
398
  }
320
399
  }
321
400
  }
322
- function cleanup(subscriber) {
323
- const sub = subscriber;
324
- const singleDep = sub._dep;
325
- if (singleDep !== void 0) {
326
- const subs = singleDep[SUBS];
327
- if (subs) {
328
- subs.delete(subscriber);
329
- if (singleDep.__f === subscriber) {
330
- singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
331
- } else if (subs.size === 1 && singleDep.__f === void 0) {
332
- singleDep.__f = subs.values().next().value;
333
- }
334
- }
335
- sub._dep = void 0;
336
- return;
337
- }
338
- const deps = sub._deps;
339
- if (!deps || deps.size === 0) return;
340
- for (const signal2 of deps) {
341
- const subs = signal2[SUBS];
342
- if (subs) {
343
- subs.delete(subscriber);
344
- if (signal2.__f === subscriber) {
345
- signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
346
- } else if (subs.size === 1 && signal2.__f === void 0) {
347
- signal2.__f = subs.values().next().value;
348
- }
349
- }
350
- }
351
- deps.clear();
352
- }
353
401
 
354
402
  // src/core/signals/derived.ts
355
403
  function derived(getter, options) {
@@ -359,6 +407,7 @@ function derived(getter, options) {
359
407
  const cs = {};
360
408
  cs._d = false;
361
409
  cs._g = getter;
410
+ cs.__v = 0;
362
411
  const markDirty = () => {
363
412
  if (cs._d) return;
364
413
  cs._d = true;
@@ -388,11 +437,14 @@ function derived(getter, options) {
388
437
  evaluating = true;
389
438
  let threw = true;
390
439
  try {
440
+ const prev = cs._v;
391
441
  retrack(() => {
392
- cs._v = getter();
442
+ const next = getter();
443
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
393
444
  cs._d = false;
394
445
  threw = false;
395
446
  }, markDirty);
447
+ if (!Object.is(prev, cs._v)) cs.__v++;
396
448
  } finally {
397
449
  evaluating = false;
398
450
  if (threw) cs._d = true;
@@ -412,6 +464,7 @@ function derived(getter, options) {
412
464
  cs._d = false;
413
465
  threw = false;
414
466
  }, markDirty);
467
+ if (!Object.is(oldValue, cs._v)) cs.__v++;
415
468
  } finally {
416
469
  evaluating = false;
417
470
  if (threw) cs._d = true;
@@ -444,32 +497,64 @@ function enqueueBatchedSignal(signal2) {
444
497
  var _g = globalThis;
445
498
  var _isDev3 = isDev();
446
499
  function signal(initial, options) {
447
- const state = { value: initial };
500
+ const state = {
501
+ value: initial,
502
+ __v: 0,
503
+ __sc: 0,
504
+ subsHead: null,
505
+ subsTail: null,
506
+ __activeNode: null,
507
+ __name: void 0
508
+ };
448
509
  const debugName = _isDev3 ? options?.name : void 0;
449
510
  const equalsFn = options?.equals;
450
- if (debugName) {
451
- state.__name = debugName;
452
- }
511
+ if (debugName) state.__name = debugName;
453
512
  function get() {
454
513
  recordDependency(state);
455
514
  return state.value;
456
515
  }
457
516
  get.__signal = state;
458
517
  if (debugName) get.__name = debugName;
459
- function set(next) {
460
- const newValue = typeof next === "function" ? next(state.value) : next;
461
- if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
462
- if (_isDev3) {
463
- const oldValue = state.value;
518
+ let set;
519
+ if (equalsFn) {
520
+ set = (next) => {
521
+ const prev = state.value;
522
+ const newValue = typeof next === "function" ? next(prev) : next;
523
+ if (equalsFn(prev, newValue)) return;
524
+ state.value = newValue;
525
+ state.__v++;
526
+ if (_isDev3) {
527
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
528
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
529
+ }
530
+ if (!enqueueBatchedSignal(state)) {
531
+ notifySubscribers(state);
532
+ }
533
+ };
534
+ } else if (_isDev3) {
535
+ set = (next) => {
536
+ const prev = state.value;
537
+ const newValue = typeof next === "function" ? next(prev) : next;
538
+ if (Object.is(newValue, prev)) return;
464
539
  state.value = newValue;
540
+ state.__v++;
465
541
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
466
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue, newValue });
467
- } else {
542
+ if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
543
+ if (!enqueueBatchedSignal(state)) {
544
+ notifySubscribers(state);
545
+ }
546
+ };
547
+ } else {
548
+ set = (next) => {
549
+ const prev = state.value;
550
+ const newValue = typeof next === "function" ? next(prev) : next;
551
+ if (Object.is(newValue, prev)) return;
468
552
  state.value = newValue;
469
- }
470
- if (!enqueueBatchedSignal(state)) {
471
- notifySubscribers(state);
472
- }
553
+ state.__v++;
554
+ if (!enqueueBatchedSignal(state)) {
555
+ notifySubscribers(state);
556
+ }
557
+ };
473
558
  }
474
559
  if (_isDev3) {
475
560
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -799,92 +884,122 @@ function isSSR() {
799
884
 
800
885
  // src/core/signals/effect.ts
801
886
  var _g2 = globalThis;
887
+ var MAX_RERUNS = 100;
888
+ function flushUserCleanups(ctx) {
889
+ const list = ctx.userCleanups;
890
+ if (list.length === 0) return;
891
+ ctx.userCleanups = [];
892
+ for (let i = list.length - 1; i >= 0; i--) {
893
+ try {
894
+ list[i]();
895
+ } catch (err) {
896
+ if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
897
+ }
898
+ }
899
+ }
900
+ function drainReruns(ctx) {
901
+ let reruns = 1;
902
+ do {
903
+ ctx.rerunPending = false;
904
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
905
+ retrack(ctx.bodyFn, ctx.subscriber);
906
+ } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
907
+ if (ctx.rerunPending) {
908
+ ctx.rerunPending = false;
909
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
910
+ console.error(
911
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
912
+ );
913
+ }
914
+ }
915
+ }
916
+ function disposeEffect(ctx) {
917
+ if (ctx.disposed) return;
918
+ ctx.disposed = true;
919
+ const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
920
+ if (h) {
921
+ try {
922
+ h.emit("effect:destroy", { effectFn: ctx.fn });
923
+ } catch {
924
+ }
925
+ }
926
+ try {
927
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
928
+ } catch (err) {
929
+ if (typeof console !== "undefined") {
930
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
931
+ }
932
+ }
933
+ try {
934
+ cleanup(ctx.subscriber);
935
+ } catch (err) {
936
+ if (typeof console !== "undefined") {
937
+ console.warn("[SibuJS effect] dispose threw:", err);
938
+ }
939
+ }
940
+ }
802
941
  function effect(effectFn, options) {
803
942
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
804
943
  if (isSSR()) return () => {
805
944
  };
806
- const onError = options?.onError;
807
- let userCleanups = [];
808
- const onCleanup = (fn) => {
809
- userCleanups.push(fn);
945
+ const ctx = {
946
+ fn: effectFn,
947
+ onError: options?.onError,
948
+ userCleanups: [],
949
+ running: false,
950
+ rerunPending: false,
951
+ disposed: false,
952
+ onCleanup: null,
953
+ subscriber: null,
954
+ bodyFn: null
810
955
  };
811
- const runUserCleanups = () => {
812
- if (userCleanups.length === 0) return;
813
- const list = userCleanups;
814
- userCleanups = [];
815
- for (let i = list.length - 1; i >= 0; i--) {
816
- try {
817
- list[i]();
818
- } catch (err) {
819
- if (typeof console !== "undefined") {
820
- console.warn("[SibuJS effect] onCleanup threw:", err);
821
- }
822
- }
823
- }
956
+ ctx.onCleanup = (fn) => {
957
+ ctx.userCleanups.push(fn);
824
958
  };
825
- const invokeBody = () => effectFn(onCleanup);
826
- const wrappedFn = onError ? () => {
959
+ const onErrorCaptured = ctx.onError;
960
+ ctx.bodyFn = onErrorCaptured ? () => {
827
961
  try {
828
- invokeBody();
962
+ ctx.fn(ctx.onCleanup);
829
963
  } catch (err) {
830
- onError(err);
964
+ onErrorCaptured(err);
831
965
  }
832
- } : invokeBody;
833
- let cleanupHandle = () => {
966
+ } : () => {
967
+ ctx.fn(ctx.onCleanup);
834
968
  };
835
- let running = false;
836
- const subscriber = () => {
837
- if (running) {
838
- if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
839
- console.warn(
840
- "[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."
841
- );
842
- }
969
+ const sub = (() => {
970
+ if (ctx.running) {
971
+ ctx.rerunPending = true;
843
972
  return;
844
973
  }
845
- running = true;
974
+ ctx.running = true;
846
975
  try {
847
- runUserCleanups();
848
- cleanupHandle();
849
- cleanupHandle = track(wrappedFn, subscriber);
976
+ ctx.rerunPending = false;
977
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
978
+ retrack(ctx.bodyFn, sub);
979
+ if (ctx.rerunPending) drainReruns(ctx);
850
980
  } finally {
851
- running = false;
981
+ ctx.running = false;
982
+ ctx.rerunPending = false;
852
983
  }
853
- };
854
- running = true;
984
+ });
985
+ sub.depsHead = null;
986
+ sub.depsTail = null;
987
+ sub._epoch = 0;
988
+ sub._structDirty = false;
989
+ sub._runEpoch = 0;
990
+ sub._runs = 0;
991
+ ctx.subscriber = sub;
992
+ ctx.running = true;
855
993
  try {
856
- cleanupHandle = track(wrappedFn, subscriber);
994
+ retrack(ctx.bodyFn, ctx.subscriber);
995
+ if (ctx.rerunPending) drainReruns(ctx);
857
996
  } finally {
858
- running = false;
997
+ ctx.running = false;
998
+ ctx.rerunPending = false;
859
999
  }
860
1000
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
861
1001
  if (hook) hook.emit("effect:create", { effectFn });
862
- let disposed = false;
863
- return () => {
864
- if (disposed) return;
865
- disposed = true;
866
- const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
867
- if (h) {
868
- try {
869
- h.emit("effect:destroy", { effectFn });
870
- } catch {
871
- }
872
- }
873
- try {
874
- runUserCleanups();
875
- } catch (err) {
876
- if (typeof console !== "undefined") {
877
- console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
878
- }
879
- }
880
- try {
881
- cleanupHandle();
882
- } catch (err) {
883
- if (typeof console !== "undefined") {
884
- console.warn("[SibuJS effect] dispose threw:", err);
885
- }
886
- }
887
- };
1002
+ return () => disposeEffect(ctx);
888
1003
  }
889
1004
 
890
1005
  // src/ui/virtualList.ts