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