sibujs 3.4.1 → 4.0.1

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 (98) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +118 -15
  3. package/dist/browser.cjs +410 -101
  4. package/dist/browser.d.cts +50 -5
  5. package/dist/browser.d.ts +50 -5
  6. package/dist/browser.js +8 -6
  7. package/dist/build.cjs +784 -392
  8. package/dist/build.js +15 -15
  9. package/dist/cdn.global.js +11 -7
  10. package/dist/{chunk-PBZWJQNY.js → chunk-52ERIVFD.js} +8 -8
  11. package/dist/chunk-52XFPGSN.js +123 -0
  12. package/dist/{chunk-Y22QAKJX.js → chunk-5BVKQX2F.js} +4 -4
  13. package/dist/chunk-5INI7D2L.js +194 -0
  14. package/dist/{chunk-4I3M326N.js → chunk-AD5YSZAN.js} +19 -25
  15. package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
  16. package/dist/chunk-DHDZ7IVN.js +109 -0
  17. package/dist/{chunk-VQZDHAEK.js → chunk-E72G5JSK.js} +1 -1
  18. package/dist/{chunk-MPE75KOA.js → chunk-HPXTUPJD.js} +259 -124
  19. package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
  20. package/dist/{chunk-FWFEBYQ6.js → chunk-IKVUUE7D.js} +115 -41
  21. package/dist/{chunk-5MFF5EPT.js → chunk-J4WZMCIW.js} +5 -5
  22. package/dist/{chunk-BQLVTAUZ.js → chunk-JYXQCELN.js} +57 -22
  23. package/dist/{chunk-WEZ5SP7L.js → chunk-PHB3MCK4.js} +73 -34
  24. package/dist/chunk-PHRH66Y7.js +147 -0
  25. package/dist/{chunk-S2VW4NBA.js → chunk-PJDSI2K7.js} +295 -198
  26. package/dist/{chunk-QHPUFNHZ.js → chunk-PTLJ7NQX.js} +3 -3
  27. package/dist/{chunk-NXR2YFZB.js → chunk-QD4I7ZGZ.js} +24 -34
  28. package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
  29. package/dist/{chunk-7PNKW3FJ.js → chunk-TNEHT3NG.js} +232 -67
  30. package/dist/{chunk-335KJEN2.js → chunk-TVURNWDK.js} +5 -3
  31. package/dist/{chunk-CV7FAQ2R.js → chunk-UNAYBND2.js} +9 -3
  32. package/dist/chunk-VPP2FONR.js +142 -0
  33. package/dist/{chunk-X4JS367G.js → chunk-XDOMTQHM.js} +14 -10
  34. package/dist/chunk-XET2RUJF.js +61 -0
  35. package/dist/{chunk-3FFCUIGV.js → chunk-XZMCVZW6.js} +309 -188
  36. package/dist/{chunk-YDWP3ZOU.js → chunk-YLV26GAJ.js} +228 -86
  37. package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.cts} +6 -6
  38. package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.ts} +6 -6
  39. package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.cts} +73 -11
  40. package/dist/{customElement-CPfIrbvg.d.ts → customElement-CKLoO63w.d.ts} +73 -11
  41. package/dist/data.cjs +483 -205
  42. package/dist/data.d.cts +139 -7
  43. package/dist/data.d.ts +139 -7
  44. package/dist/data.js +9 -9
  45. package/dist/devtools.cjs +222 -87
  46. package/dist/devtools.js +8 -8
  47. package/dist/dispose-4FZ6OFXR.js +19 -0
  48. package/dist/ecosystem.cjs +426 -182
  49. package/dist/ecosystem.js +11 -11
  50. package/dist/extras.cjs +1988 -772
  51. package/dist/extras.d.cts +7 -7
  52. package/dist/extras.d.ts +7 -7
  53. package/dist/extras.js +29 -26
  54. package/dist/index.cjs +791 -392
  55. package/dist/index.d.cts +299 -28
  56. package/dist/index.d.ts +299 -28
  57. package/dist/index.js +33 -18
  58. package/dist/motion.cjs +180 -50
  59. package/dist/motion.d.cts +26 -0
  60. package/dist/motion.d.ts +26 -0
  61. package/dist/motion.js +5 -5
  62. package/dist/patterns.cjs +437 -170
  63. package/dist/patterns.d.cts +60 -13
  64. package/dist/patterns.d.ts +60 -13
  65. package/dist/patterns.js +7 -7
  66. package/dist/performance.cjs +333 -148
  67. package/dist/performance.d.cts +55 -7
  68. package/dist/performance.d.ts +55 -7
  69. package/dist/performance.js +9 -9
  70. package/dist/plugins.cjs +1378 -583
  71. package/dist/plugins.d.cts +227 -9
  72. package/dist/plugins.d.ts +227 -9
  73. package/dist/plugins.js +610 -321
  74. package/dist/{ssr-D62yFwuw.d.cts → ssr-BiPRdZ6n.d.cts} +44 -15
  75. package/dist/{ssr-D62yFwuw.d.ts → ssr-BiPRdZ6n.d.ts} +44 -15
  76. package/dist/{ssr-2PPULEK2.js → ssr-JFP5535P.js} +5 -4
  77. package/dist/ssr.cjs +1111 -450
  78. package/dist/ssr.d.cts +230 -20
  79. package/dist/ssr.d.ts +230 -20
  80. package/dist/ssr.js +15 -12
  81. package/dist/{startup-0Qv6aosO.d.cts → startup-BMpaiMhP.d.cts} +1 -1
  82. package/dist/{startup-0Qv6aosO.d.ts → startup-BMpaiMhP.d.ts} +1 -1
  83. package/dist/testing.cjs +205 -63
  84. package/dist/testing.d.cts +1 -1
  85. package/dist/testing.d.ts +1 -1
  86. package/dist/testing.js +8 -10
  87. package/dist/ui.cjs +640 -199
  88. package/dist/ui.d.cts +2 -2
  89. package/dist/ui.d.ts +2 -2
  90. package/dist/ui.js +10 -10
  91. package/dist/widgets.cjs +207 -82
  92. package/dist/widgets.js +9 -9
  93. package/package.json +12 -7
  94. package/dist/chunk-5VGSK6D2.js +0 -80
  95. package/dist/chunk-6CAJU5TP.js +0 -38
  96. package/dist/chunk-COY6PUD2.js +0 -34
  97. package/dist/chunk-DLBFEAIS.js +0 -96
  98. package/dist/chunk-L3GAGWCC.js +0 -111
package/dist/browser.cjs CHANGED
@@ -81,8 +81,95 @@ function devWarn(message) {
81
81
  }
82
82
  }
83
83
 
84
+ // src/core/errors.ts
85
+ var ERROR_STATE_KEY = /* @__PURE__ */ Symbol.for("sibujs.runtime-errors.v1");
86
+ function state() {
87
+ const g = globalThis;
88
+ const existing = g[ERROR_STATE_KEY];
89
+ if (existing) return existing;
90
+ const created = { handler: null, handlerDepth: 0 };
91
+ g[ERROR_STATE_KEY] = created;
92
+ return created;
93
+ }
94
+ function normalizeError(value) {
95
+ if (value instanceof Error) return value;
96
+ const message = typeof value === "string" ? value : `Non-Error value thrown: ${safeStringify(value)}`;
97
+ return new Error(message, { cause: value });
98
+ }
99
+ function safeStringify(value) {
100
+ if (typeof value === "symbol") return value.toString();
101
+ try {
102
+ return typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
103
+ } catch {
104
+ return Object.prototype.toString.call(value);
105
+ }
106
+ }
107
+ function describe(context) {
108
+ return context.name ? `${context.phase} "${context.name}"` : context.phase;
109
+ }
110
+ var REACTIVE_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
111
+ function untrackedRun(fn) {
112
+ const api = globalThis[REACTIVE_REGISTRY_KEY];
113
+ return api?.untracked ? api.untracked(fn) : fn();
114
+ }
115
+ var PROPAGATE_EVENT = "sibu:error-propagate";
116
+ function claimedByBoundary(error, context) {
117
+ const raw = context.node;
118
+ if (!raw || typeof CustomEvent === "undefined") return false;
119
+ const target = resolveEventTarget(raw);
120
+ if (!target) return false;
121
+ try {
122
+ const event = new CustomEvent(PROPAGATE_EVENT, {
123
+ bubbles: true,
124
+ cancelable: true,
125
+ detail: { error, context }
126
+ });
127
+ return target.dispatchEvent(event) === false;
128
+ } catch {
129
+ return false;
130
+ }
131
+ }
132
+ function resolveEventTarget(node) {
133
+ const ELEMENT_NODE = 1;
134
+ let current = node;
135
+ while (current) {
136
+ if (current.nodeType === ELEMENT_NODE && typeof current.dispatchEvent === "function") {
137
+ return current;
138
+ }
139
+ current = current.parentNode;
140
+ }
141
+ const self = node;
142
+ return typeof self.dispatchEvent === "function" ? self : null;
143
+ }
144
+ function defaultReport(error, context) {
145
+ if (typeof console === "undefined") return;
146
+ console.error(`[SibuJS] Uncaught error during ${describe(context)}: ${error.message}`, error);
147
+ }
148
+ function reportError(error, context) {
149
+ const err = normalizeError(error);
150
+ untrackedRun(() => {
151
+ if (claimedByBoundary(err, context)) return;
152
+ const s = state();
153
+ const handler = s.handler;
154
+ if (handler && s.handlerDepth === 0) {
155
+ s.handlerDepth++;
156
+ try {
157
+ handler(error instanceof Error ? error : err, context);
158
+ return;
159
+ } catch (handlerError) {
160
+ if (typeof console !== "undefined") {
161
+ const detail = handlerError instanceof Error ? handlerError.message : String(handlerError);
162
+ console.error(`[SibuJS] runtime error handler itself threw: ${detail}`, handlerError);
163
+ }
164
+ } finally {
165
+ s.handlerDepth--;
166
+ }
167
+ }
168
+ defaultReport(err, context);
169
+ });
170
+ }
171
+
84
172
  // src/reactivity/track-core.ts
85
- var _isDev2 = isDev();
86
173
  var POOL_MAX = 4096;
87
174
  var nodePool = [];
88
175
  function createNode() {
@@ -90,6 +177,7 @@ function createNode() {
90
177
  sig: null,
91
178
  sub: null,
92
179
  epoch: 0,
180
+ depVersion: 0,
93
181
  sigPrev: null,
94
182
  sigNext: null,
95
183
  subPrev: null,
@@ -174,8 +262,27 @@ function safeInvoke(sub) {
174
262
  try {
175
263
  sub();
176
264
  } catch (err) {
177
- if (_isDev2) devWarn(`Subscriber threw during notification: ${err instanceof Error ? err.message : String(err)}`);
265
+ const s = sub;
266
+ reportError(err, { phase: s._errorPhase ?? "effect", name: s.__name, node: s._errorNode });
267
+ }
268
+ }
269
+ function depsChanged(sub) {
270
+ let node = sub.depsHead ?? null;
271
+ if (node === null) return true;
272
+ while (node !== null) {
273
+ const sig = node.sig;
274
+ if (sig === null) return true;
275
+ if (sig._d === true && sig._validate !== void 0) {
276
+ try {
277
+ sig._validate();
278
+ } catch {
279
+ return true;
280
+ }
281
+ }
282
+ if (node.depVersion !== sig.__v) return true;
283
+ node = node.subNext;
178
284
  }
285
+ return false;
179
286
  }
180
287
  var suspendDepth = 0;
181
288
  var trackingSuspended = false;
@@ -257,7 +364,7 @@ function track(effectFn, subscriber) {
257
364
  const sub = subscriber;
258
365
  return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
259
366
  }
260
- function reactiveBinding(commit) {
367
+ function reactiveBinding(commit, ownerNode) {
261
368
  const run = () => {
262
369
  const s = subscriber;
263
370
  if (s._disposed || s._reentrant) return;
@@ -273,13 +380,17 @@ function reactiveBinding(commit) {
273
380
  subscriber.depsTail = null;
274
381
  subscriber._epoch = 0;
275
382
  subscriber._structDirty = false;
383
+ subscriber._hasComputedDep = false;
276
384
  subscriber._runEpoch = 0;
277
385
  subscriber._runs = 0;
278
386
  subscriber._reentrant = false;
279
387
  subscriber._disposed = false;
388
+ subscriber._errorPhase = "binding";
389
+ subscriber._errorNode = ownerNode;
280
390
  run();
281
391
  return subscriber._dispose ?? (subscriber._dispose = () => {
282
392
  subscriber._disposed = true;
393
+ subscriber._errorNode = void 0;
283
394
  cleanup(subscriber);
284
395
  });
285
396
  }
@@ -291,9 +402,12 @@ function recordDependency(signal2) {
291
402
  const active = sig.__activeNode ?? null;
292
403
  if (active !== null && active.sub === sub) {
293
404
  active.epoch = epoch;
405
+ active.depVersion = sig.__v ?? Number.NaN;
294
406
  return;
295
407
  }
408
+ if (sig._validate !== void 0) sub._hasComputedDep = true;
296
409
  const node = allocNode(signal2, sub, epoch);
410
+ node.depVersion = sig.__v ?? Number.NaN;
297
411
  node.prevActive = active;
298
412
  sig.__activeNode = node;
299
413
  linkSub(sub, node);
@@ -312,9 +426,10 @@ function cleanup(subscriber) {
312
426
  node = next;
313
427
  }
314
428
  }
315
- var maxSubscriberRepeats = 50;
429
+ var maxSubscriberRepeats = 1e3;
316
430
  var maxDrainIterations = 1e6;
317
431
  var drainEpoch = 0;
432
+ var quarantined = /* @__PURE__ */ new Set();
318
433
  function setMaxSubscriberRepeats(n) {
319
434
  const prev = maxSubscriberRepeats;
320
435
  if (Number.isFinite(n) && n > 0) maxSubscriberRepeats = Math.floor(n);
@@ -336,19 +451,21 @@ function tickRepeat(sub) {
336
451
  return s._runs > maxSubscriberRepeats;
337
452
  }
338
453
  function cycleError(sub) {
339
- if (typeof console !== "undefined") {
340
- const name = sub.__name ?? "<unnamed>";
341
- console.error(
342
- `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
343
- );
344
- }
454
+ const name = sub.__name ?? "<unnamed>";
455
+ reportError(
456
+ new Error(
457
+ `subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. This subscriber is quarantined for the rest of this update; other pending work still runs.`
458
+ ),
459
+ { phase: "scheduler", name }
460
+ );
345
461
  }
346
462
  function absoluteDrainError() {
347
- if (typeof console !== "undefined") {
348
- console.error(
349
- `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
350
- );
351
- }
463
+ reportError(
464
+ new Error(
465
+ `Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
466
+ ),
467
+ { phase: "scheduler" }
468
+ );
352
469
  }
353
470
  function drainQueue() {
354
471
  let i = 0;
@@ -358,11 +475,14 @@ function drainQueue() {
358
475
  break;
359
476
  }
360
477
  const sub = pendingQueue[i++];
478
+ pendingSet.delete(sub);
479
+ if (quarantined.size > 0 && quarantined.has(sub)) continue;
480
+ if (sub._hasComputedDep === true && !depsChanged(sub)) continue;
361
481
  if (tickRepeat(sub)) {
362
482
  cycleError(sub);
363
- break;
483
+ quarantined.add(sub);
484
+ continue;
364
485
  }
365
- pendingSet.delete(sub);
366
486
  safeInvoke(sub);
367
487
  }
368
488
  }
@@ -377,6 +497,7 @@ function drainNotificationQueue() {
377
497
  if (notifyDepth === 0) {
378
498
  pendingQueue.length = 0;
379
499
  pendingSet.clear();
500
+ if (quarantined.size > 0) quarantined.clear();
380
501
  }
381
502
  }
382
503
  }
@@ -470,6 +591,7 @@ function notifySubscribers(signal2) {
470
591
  if (notifyDepth === 0) {
471
592
  pendingQueue.length = 0;
472
593
  pendingSet.clear();
594
+ if (quarantined.size > 0) quarantined.clear();
473
595
  }
474
596
  }
475
597
  }
@@ -496,14 +618,14 @@ function forEachSubscriber(signal2, visit) {
496
618
  }
497
619
 
498
620
  // src/reactivity/track.ts
499
- var _isDev3 = isDev();
500
- var _runtimeVersion = true ? "3.4.1" : "dev";
621
+ var _isDev2 = isDev();
622
+ var _runtimeVersion = true ? "4.0.1" : "dev";
501
623
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
502
624
  function resolveReactiveApi() {
503
625
  const g = globalThis;
504
626
  const existing = g[REGISTRY_KEY];
505
627
  if (existing) {
506
- if (_isDev3 && !existing.__dupWarned) {
628
+ if (_isDev2 && !existing.__dupWarned) {
507
629
  existing.__dupWarned = true;
508
630
  devWarn(
509
631
  `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity still works \u2014 all copies share the first one \u2014 but de-duplicate sibujs in your bundler (e.g. Vite optimizeDeps.exclude: ['sibujs'] or resolve.dedupe: ['sibujs']).`
@@ -605,70 +727,72 @@ var isBatching = API2.isBatching;
605
727
 
606
728
  // src/core/signals/signal.ts
607
729
  var _g = globalThis;
608
- var _isDev4 = isDev();
730
+ var _isDev3 = isDev();
609
731
  function signal(initial, options) {
610
- const state = {
732
+ const state2 = {
611
733
  value: initial,
612
734
  __v: 0,
613
735
  __sc: 0,
614
736
  subsHead: null,
615
737
  subsTail: null,
616
738
  __activeNode: null,
617
- __name: void 0
739
+ __name: void 0,
740
+ _d: false,
741
+ _validate: void 0
618
742
  };
619
- const debugName = _isDev4 ? options?.name : void 0;
743
+ const debugName = _isDev3 ? options?.name : void 0;
620
744
  const equalsFn = options?.equals;
621
- if (debugName) state.__name = debugName;
745
+ if (debugName) state2.__name = debugName;
622
746
  function get() {
623
- recordDependency2(state);
624
- return state.value;
747
+ recordDependency2(state2);
748
+ return state2.value;
625
749
  }
626
- get.__signal = state;
750
+ get.__signal = state2;
627
751
  if (debugName) get.__name = debugName;
628
752
  let set;
629
753
  if (equalsFn) {
630
754
  set = (next) => {
631
- const prev = state.value;
755
+ const prev = state2.value;
632
756
  const newValue = typeof next === "function" ? next(prev) : next;
633
757
  if (equalsFn(prev, newValue)) return;
634
- state.value = newValue;
635
- state.__v++;
636
- if (_isDev4) {
758
+ state2.value = newValue;
759
+ state2.__v++;
760
+ if (_isDev3) {
637
761
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
638
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
762
+ if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
639
763
  }
640
- if (!enqueueBatchedSignal(state)) {
641
- notifySubscribers2(state);
764
+ if (!enqueueBatchedSignal(state2)) {
765
+ notifySubscribers2(state2);
642
766
  }
643
767
  };
644
- } else if (_isDev4) {
768
+ } else if (_isDev3) {
645
769
  set = (next) => {
646
- const prev = state.value;
770
+ const prev = state2.value;
647
771
  const newValue = typeof next === "function" ? next(prev) : next;
648
772
  if (Object.is(newValue, prev)) return;
649
- state.value = newValue;
650
- state.__v++;
773
+ state2.value = newValue;
774
+ state2.__v++;
651
775
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
652
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
653
- if (!enqueueBatchedSignal(state)) {
654
- notifySubscribers2(state);
776
+ if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
777
+ if (!enqueueBatchedSignal(state2)) {
778
+ notifySubscribers2(state2);
655
779
  }
656
780
  };
657
781
  } else {
658
782
  set = (next) => {
659
- const prev = state.value;
783
+ const prev = state2.value;
660
784
  const newValue = typeof next === "function" ? next(prev) : next;
661
785
  if (Object.is(newValue, prev)) return;
662
- state.value = newValue;
663
- state.__v++;
664
- if (!enqueueBatchedSignal(state)) {
665
- notifySubscribers2(state);
786
+ state2.value = newValue;
787
+ state2.__v++;
788
+ if (!enqueueBatchedSignal(state2)) {
789
+ notifySubscribers2(state2);
666
790
  }
667
791
  };
668
792
  }
669
- if (_isDev4) {
793
+ if (_isDev3) {
670
794
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
671
- if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
795
+ if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
672
796
  }
673
797
  return [get, set];
674
798
  }
@@ -702,20 +826,24 @@ function detectSSRShared() {
702
826
  const getBuiltin = process.getBuiltinModule;
703
827
  if (typeof getBuiltin === "function") {
704
828
  mod = getBuiltin("node:async_hooks");
705
- } else {
706
- const req = Function("return typeof require==='function'?require:null")();
707
- if (req) mod = req("node:async_hooks");
829
+ } else if (typeof require === "function") {
830
+ mod = require("async_hooks");
708
831
  }
709
832
  if (mod) detected = new mod.AsyncLocalStorage();
710
833
  }
711
834
  } catch {
712
835
  detected = null;
713
836
  }
714
- return { als: detected, fallbackStore: { ssr: false, suspenseIdCounter: 0 } };
837
+ return {
838
+ als: detected,
839
+ fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
840
+ fallbackDepth: 0
841
+ };
715
842
  }
716
843
  var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
717
844
  var als = _shared.als;
718
845
  var fallbackStore = _shared.fallbackStore;
846
+ _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
719
847
  function getSSRStore() {
720
848
  if (als) {
721
849
  const s = als.getStore();
@@ -738,7 +866,7 @@ function flushUserCleanups(ctx) {
738
866
  try {
739
867
  list[i]();
740
868
  } catch (err) {
741
- if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
869
+ reportError(err, { phase: "cleanup" });
742
870
  }
743
871
  }
744
872
  }
@@ -751,11 +879,12 @@ function drainReruns(ctx) {
751
879
  } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
752
880
  if (ctx.rerunPending) {
753
881
  ctx.rerunPending = false;
754
- if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
755
- console.error(
756
- `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
757
- );
758
- }
882
+ reportError(
883
+ new Error(
884
+ `effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
885
+ ),
886
+ { phase: "effect" }
887
+ );
759
888
  }
760
889
  }
761
890
  function disposeEffect(ctx) {
@@ -832,6 +961,8 @@ function effect(effectFn, options) {
832
961
  sub.depsTail = null;
833
962
  sub._epoch = 0;
834
963
  sub._structDirty = false;
964
+ sub._hasComputedDep = false;
965
+ sub._errorPhase = "effect";
835
966
  sub._runEpoch = 0;
836
967
  sub._runs = 0;
837
968
  ctx.subscriber = sub;
@@ -1082,13 +1213,13 @@ function idle(timeout = 6e4) {
1082
1213
 
1083
1214
  // src/browser/permissions.ts
1084
1215
  function permissions(name) {
1085
- const [state, setState] = signal("prompt");
1216
+ const [state2, setState] = signal("prompt");
1086
1217
  let permissionStatus = null;
1087
1218
  let onChange = null;
1088
1219
  let disposed = false;
1089
1220
  if (typeof navigator === "undefined" || !navigator.permissions) {
1090
1221
  setState("unsupported");
1091
- return { state, dispose: () => {
1222
+ return { state: state2, dispose: () => {
1092
1223
  } };
1093
1224
  }
1094
1225
  navigator.permissions.query({ name }).then((status) => {
@@ -1100,6 +1231,7 @@ function permissions(name) {
1100
1231
  };
1101
1232
  status.addEventListener("change", onChange);
1102
1233
  }).catch(() => {
1234
+ if (disposed) return;
1103
1235
  setState("unsupported");
1104
1236
  });
1105
1237
  function dispose() {
@@ -1110,7 +1242,7 @@ function permissions(name) {
1110
1242
  onChange = null;
1111
1243
  }
1112
1244
  }
1113
- return { state, dispose };
1245
+ return { state: state2, dispose };
1114
1246
  }
1115
1247
 
1116
1248
  // src/browser/clipboard.ts
@@ -1118,20 +1250,25 @@ function clipboard() {
1118
1250
  const [text, setText] = signal("");
1119
1251
  const [copied, setCopied] = signal(false);
1120
1252
  let copiedTimer = null;
1253
+ let disposed = false;
1121
1254
  async function copy(value) {
1255
+ if (disposed) return;
1122
1256
  if (typeof navigator === "undefined" || !navigator.clipboard) {
1123
1257
  return;
1124
1258
  }
1125
1259
  await navigator.clipboard.writeText(value);
1260
+ if (disposed) return;
1126
1261
  setText(value);
1127
1262
  setCopied(true);
1128
1263
  if (copiedTimer !== null) clearTimeout(copiedTimer);
1129
1264
  copiedTimer = setTimeout(() => {
1265
+ if (disposed) return;
1130
1266
  setCopied(false);
1131
1267
  copiedTimer = null;
1132
1268
  }, 2e3);
1133
1269
  }
1134
1270
  function dispose() {
1271
+ disposed = true;
1135
1272
  if (copiedTimer !== null) {
1136
1273
  clearTimeout(copiedTimer);
1137
1274
  copiedTimer = null;
@@ -1257,26 +1394,137 @@ function dropZone(element, options) {
1257
1394
  return { isOver, dispose };
1258
1395
  }
1259
1396
 
1397
+ // src/utils/globalSingleton.ts
1398
+ function globalSingleton(key, create) {
1399
+ const g = globalThis;
1400
+ return g[key] ?? (g[key] = create());
1401
+ }
1402
+
1403
+ // src/utils/singletonResource.ts
1404
+ function singletonResource(io) {
1405
+ let original = null;
1406
+ let captured = false;
1407
+ const stack = [];
1408
+ function apply() {
1409
+ if (stack.length > 0) {
1410
+ io.write(stack[stack.length - 1].value);
1411
+ return;
1412
+ }
1413
+ if (captured) {
1414
+ io.write(original);
1415
+ captured = false;
1416
+ original = null;
1417
+ }
1418
+ }
1419
+ return {
1420
+ acquire(value) {
1421
+ if (!captured) {
1422
+ original = io.read();
1423
+ captured = true;
1424
+ }
1425
+ const entry = { value, released: false };
1426
+ stack.push(entry);
1427
+ apply();
1428
+ return {
1429
+ set(next) {
1430
+ if (entry.released) return;
1431
+ entry.value = next;
1432
+ apply();
1433
+ },
1434
+ release() {
1435
+ if (entry.released) return;
1436
+ entry.released = true;
1437
+ const index = stack.indexOf(entry);
1438
+ if (index !== -1) stack.splice(index, 1);
1439
+ apply();
1440
+ }
1441
+ };
1442
+ },
1443
+ ownerCount() {
1444
+ return stack.length;
1445
+ }
1446
+ };
1447
+ }
1448
+
1449
+ // src/utils/documentResources.ts
1450
+ var INERT_LEASE = {
1451
+ set() {
1452
+ },
1453
+ release() {
1454
+ }
1455
+ };
1456
+ function inertLease() {
1457
+ return INERT_LEASE;
1458
+ }
1459
+ var titleResource = globalSingleton(
1460
+ /* @__PURE__ */ Symbol.for("sibujs.documentTitle.v1"),
1461
+ () => singletonResource({
1462
+ read: () => document.title,
1463
+ write: (value) => {
1464
+ document.title = value;
1465
+ }
1466
+ })
1467
+ );
1468
+ var baseResource = globalSingleton(
1469
+ /* @__PURE__ */ Symbol.for("sibujs.documentBase.v1"),
1470
+ () => singletonResource({
1471
+ read: () => {
1472
+ const el = document.head.querySelector("base");
1473
+ if (!el) return null;
1474
+ return {
1475
+ href: el.getAttribute("href") ?? void 0,
1476
+ target: el.getAttribute("target") ?? void 0
1477
+ };
1478
+ },
1479
+ write: (spec) => {
1480
+ const existing = document.head.querySelector("base");
1481
+ if (spec === null) {
1482
+ existing?.remove();
1483
+ return;
1484
+ }
1485
+ const el = existing ?? document.head.appendChild(document.createElement("base"));
1486
+ if (spec.href === void 0) el.removeAttribute("href");
1487
+ else el.setAttribute("href", spec.href);
1488
+ if (spec.target === void 0) el.removeAttribute("target");
1489
+ else el.setAttribute("target", spec.target);
1490
+ }
1491
+ })
1492
+ );
1493
+ var scrollRestorationResource = globalSingleton(
1494
+ /* @__PURE__ */ Symbol.for("sibujs.historyScrollRestoration.v1"),
1495
+ () => singletonResource({
1496
+ read: () => history.scrollRestoration,
1497
+ write: (mode) => {
1498
+ history.scrollRestoration = mode;
1499
+ }
1500
+ })
1501
+ );
1502
+ function acquireTitle(value) {
1503
+ if (typeof document === "undefined") return inertLease();
1504
+ return titleResource.acquire(value);
1505
+ }
1506
+
1260
1507
  // src/browser/title.ts
1261
1508
  function title(value) {
1262
1509
  if (typeof document === "undefined") {
1263
1510
  return () => {
1264
1511
  };
1265
1512
  }
1266
- const previousTitle = document.title;
1267
1513
  if (typeof value === "function") {
1514
+ let lease2 = null;
1268
1515
  const cleanup3 = effect(() => {
1269
- document.title = value();
1516
+ const next = value();
1517
+ if (lease2) lease2.set(next);
1518
+ else lease2 = acquireTitle(next);
1270
1519
  });
1271
1520
  return () => {
1272
1521
  cleanup3();
1273
- document.title = previousTitle;
1522
+ lease2?.release();
1523
+ lease2 = null;
1274
1524
  };
1275
1525
  }
1276
- document.title = value;
1277
- return () => {
1278
- document.title = previousTitle;
1279
- };
1526
+ const lease = acquireTitle(value);
1527
+ return () => lease.release();
1280
1528
  }
1281
1529
 
1282
1530
  // src/browser/colorScheme.ts
@@ -1590,51 +1838,112 @@ function wakeLock() {
1590
1838
  };
1591
1839
  }
1592
1840
  const api = navigator.wakeLock;
1593
- let sentinel = null;
1841
+ let current = null;
1842
+ let desire = false;
1843
+ let generation = 0;
1844
+ let inflight = null;
1594
1845
  let disposed = false;
1846
+ function publishActive(value) {
1847
+ if (disposed) return;
1848
+ setActive(value);
1849
+ }
1850
+ function syncReleasedState() {
1851
+ if (current?.released) {
1852
+ current = null;
1853
+ publishActive(false);
1854
+ }
1855
+ }
1856
+ function discard(sentinel) {
1857
+ if (sentinel.released) return;
1858
+ sentinel.release().catch((err) => {
1859
+ reportError(err, { phase: "async", name: "wakeLock(discard)" });
1860
+ });
1861
+ }
1595
1862
  async function request() {
1596
1863
  if (disposed) return;
1597
- try {
1598
- const s = await api.request("screen");
1599
- if (disposed) {
1600
- s.release().catch(() => {
1601
- });
1602
- return;
1603
- }
1604
- sentinel = s;
1605
- setActive(true);
1606
- sentinel.addEventListener("release", () => {
1607
- setActive(false);
1608
- });
1609
- } catch {
1610
- setActive(false);
1864
+ desire = true;
1865
+ syncReleasedState();
1866
+ if (current && !current.released) {
1867
+ publishActive(true);
1868
+ return;
1611
1869
  }
1870
+ if (inflight) return inflight;
1871
+ const myGeneration = ++generation;
1872
+ const attempt = (async () => {
1873
+ try {
1874
+ const sentinel = await api.request("screen");
1875
+ if (disposed || !desire || generation !== myGeneration) {
1876
+ discard(sentinel);
1877
+ return;
1878
+ }
1879
+ if (sentinel.released) {
1880
+ publishActive(false);
1881
+ return;
1882
+ }
1883
+ try {
1884
+ sentinel.addEventListener("release", () => {
1885
+ if (current !== sentinel) return;
1886
+ current = null;
1887
+ publishActive(false);
1888
+ });
1889
+ } catch (listenerError) {
1890
+ discard(sentinel);
1891
+ publishActive(false);
1892
+ reportError(listenerError, { phase: "async", name: "wakeLock(listener registration)" });
1893
+ return;
1894
+ }
1895
+ if (sentinel.released) {
1896
+ publishActive(false);
1897
+ return;
1898
+ }
1899
+ current = sentinel;
1900
+ publishActive(true);
1901
+ } catch (err) {
1902
+ if (generation === myGeneration) publishActive(false);
1903
+ reportError(err, { phase: "async", name: "wakeLock(request)" });
1904
+ }
1905
+ })();
1906
+ inflight = attempt;
1907
+ void attempt.finally(() => {
1908
+ if (inflight === attempt) inflight = null;
1909
+ }).catch(() => {
1910
+ });
1911
+ return attempt;
1612
1912
  }
1613
1913
  async function release() {
1614
- if (sentinel && !sentinel.released) {
1615
- await sentinel.release();
1914
+ desire = false;
1915
+ generation++;
1916
+ inflight = null;
1917
+ const held = current;
1918
+ current = null;
1919
+ publishActive(false);
1920
+ if (held && !held.released) {
1921
+ await held.release();
1616
1922
  }
1617
- sentinel = null;
1618
- setActive(false);
1619
1923
  }
1620
1924
  const onVisibility = () => {
1621
- if (sentinel?.released && !document.hidden) {
1622
- request().catch((err) => {
1623
- if (typeof console !== "undefined") {
1624
- console.warn("[SibuJS wakeLock] re-acquire failed:", err);
1625
- }
1626
- });
1627
- }
1925
+ syncReleasedState();
1926
+ if (!desire || current || document.hidden) return;
1927
+ request().catch((err) => {
1928
+ reportError(err, { phase: "async", name: "wakeLock(visibility re-acquire)" });
1929
+ });
1628
1930
  };
1629
1931
  document.addEventListener("visibilitychange", onVisibility);
1630
1932
  function dispose() {
1631
- disposed = true;
1933
+ if (disposed) return;
1934
+ desire = false;
1935
+ generation++;
1936
+ inflight = null;
1632
1937
  document.removeEventListener("visibilitychange", onVisibility);
1633
- release().catch((err) => {
1634
- if (typeof console !== "undefined") {
1635
- console.warn("[SibuJS wakeLock] release failed:", err);
1636
- }
1637
- });
1938
+ const held = current;
1939
+ current = null;
1940
+ setActive(false);
1941
+ disposed = true;
1942
+ if (held && !held.released) {
1943
+ held.release().catch((err) => {
1944
+ reportError(err, { phase: "async", name: "wakeLock(dispose release)" });
1945
+ });
1946
+ }
1638
1947
  }
1639
1948
  return { active, request, release, dispose };
1640
1949
  }