sibujs 4.5.0 → 4.6.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 (109) hide show
  1. package/dist/browser.cjs +309 -129
  2. package/dist/browser.d.cts +46 -14
  3. package/dist/browser.d.ts +46 -14
  4. package/dist/browser.js +8 -6
  5. package/dist/build.cjs +184 -72
  6. package/dist/build.js +15 -16
  7. package/dist/cdn.dev.global.js +12 -12
  8. package/dist/cdn.full.dev.global.js +11 -11
  9. package/dist/cdn.full.global.js +10 -10
  10. package/dist/cdn.global.js +12 -12
  11. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  12. package/dist/chunk-3ISI6ACU.js +44 -0
  13. package/dist/{chunk-ONOHFDLG.js → chunk-47M47FOM.js} +3 -3
  14. package/dist/chunk-5HZXGZ6T.js +24 -0
  15. package/dist/{chunk-KKLW7YWL.js → chunk-5MT6SJ3P.js} +275 -144
  16. package/dist/{chunk-UGRX3S57.js → chunk-5Q4R7HCL.js} +12 -3
  17. package/dist/{chunk-S373NSMK.js → chunk-7GQCWFOE.js} +150 -59
  18. package/dist/{chunk-FTIR4QW2.js → chunk-7NCARGJW.js} +6 -8
  19. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  20. package/dist/{chunk-VZKNK2V7.js → chunk-AIF3Z2T7.js} +296 -38
  21. package/dist/{chunk-4PMLNECI.js → chunk-BAAG6ZTI.js} +98 -29
  22. package/dist/{chunk-XZZOBQAY.js → chunk-D33YTSX5.js} +2 -2
  23. package/dist/{chunk-TBYTO6BS.js → chunk-DKGBBKOF.js} +136 -80
  24. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  25. package/dist/{chunk-GW3SCCZG.js → chunk-HYXDKS4N.js} +153 -110
  26. package/dist/chunk-J6FW5TV6.js +233 -0
  27. package/dist/{chunk-PCT43HW3.js → chunk-JSXPZCET.js} +1 -1
  28. package/dist/chunk-NYNYSPK7.js +318 -0
  29. package/dist/{chunk-RJE2BNI4.js → chunk-ORMZXBKQ.js} +108 -45
  30. package/dist/{chunk-OMJJM3KM.js → chunk-OXUY2A6L.js} +287 -130
  31. package/dist/{chunk-3JZ4L5TJ.js → chunk-PK6FK2G2.js} +181 -66
  32. package/dist/{chunk-RBTPLM32.js → chunk-QE4TTDU3.js} +2 -2
  33. package/dist/{chunk-2DCGACUU.js → chunk-SXXVZMKZ.js} +251 -66
  34. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  35. package/dist/{chunk-7LN645I6.js → chunk-UZQ6ALFS.js} +3 -3
  36. package/dist/{chunk-KEISJXBU.js → chunk-VVWPJ543.js} +6 -8
  37. package/dist/{chunk-RIXRAYIU.js → chunk-XC4MEKGA.js} +3 -3
  38. package/dist/{chunk-B3WHI2QA.js → chunk-XRRZKZYX.js} +55 -31
  39. package/dist/{contracts-DBdg9J_a.d.cts → contracts-DRIuclVT.d.cts} +10 -2
  40. package/dist/{contracts-DBdg9J_a.d.ts → contracts-DRIuclVT.d.ts} +10 -2
  41. package/dist/{customElement-OB9CIsc5.d.cts → customElement-MmInOW1U.d.cts} +21 -0
  42. package/dist/{customElement-OB9CIsc5.d.ts → customElement-MmInOW1U.d.ts} +21 -0
  43. package/dist/data.cjs +213 -117
  44. package/dist/data.d.cts +47 -9
  45. package/dist/data.d.ts +47 -9
  46. package/dist/data.js +12 -9
  47. package/dist/devtools.cjs +81 -57
  48. package/dist/devtools.js +7 -8
  49. package/dist/dispose-GEIG2KOF.js +28 -0
  50. package/dist/ecosystem.cjs +309 -104
  51. package/dist/ecosystem.d.cts +19 -3
  52. package/dist/ecosystem.d.ts +19 -3
  53. package/dist/ecosystem.js +12 -12
  54. package/dist/extras.cjs +2133 -863
  55. package/dist/extras.d.cts +10 -9
  56. package/dist/extras.d.ts +10 -9
  57. package/dist/extras.js +39 -28
  58. package/dist/index.cjs +184 -72
  59. package/dist/index.d.cts +22 -5
  60. package/dist/index.d.ts +22 -5
  61. package/dist/index.js +24 -27
  62. package/dist/motion.cjs +107 -41
  63. package/dist/motion.js +5 -5
  64. package/dist/patterns.cjs +317 -39
  65. package/dist/patterns.d.cts +27 -9
  66. package/dist/patterns.d.ts +27 -9
  67. package/dist/patterns.js +8 -8
  68. package/dist/performance.cjs +308 -216
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +8 -9
  72. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  73. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  74. package/dist/plugins.cjs +549 -202
  75. package/dist/plugins.d.cts +3 -3
  76. package/dist/plugins.d.ts +3 -3
  77. package/dist/plugins.js +35 -23
  78. package/dist/signal-EotCj4hS.d.cts +110 -0
  79. package/dist/signal-EotCj4hS.d.ts +110 -0
  80. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  81. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  82. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  83. package/dist/ssr.cjs +176 -72
  84. package/dist/ssr.d.cts +9 -3
  85. package/dist/ssr.d.ts +9 -3
  86. package/dist/ssr.js +11 -12
  87. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  88. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  89. package/dist/{tagFactory-DVoDpHye.d.ts → tagFactory-DFstCLQV.d.cts} +2 -100
  90. package/dist/{tagFactory-DVoDpHye.d.cts → tagFactory-DkaNVUNV.d.ts} +2 -100
  91. package/dist/testing.cjs +2484 -2185
  92. package/dist/testing.d.cts +56 -5
  93. package/dist/testing.d.ts +56 -5
  94. package/dist/testing.js +580 -307
  95. package/dist/ui.cjs +707 -317
  96. package/dist/ui.d.cts +40 -7
  97. package/dist/ui.d.ts +40 -7
  98. package/dist/ui.js +151 -56
  99. package/dist/widgets.cjs +246 -278
  100. package/dist/widgets.js +9 -10
  101. package/package.json +1 -1
  102. package/dist/chunk-2WLZ6757.js +0 -149
  103. package/dist/chunk-CCSJMTRN.js +0 -15
  104. package/dist/chunk-QKRPLZ2V.js +0 -108
  105. package/dist/chunk-VUF4ALSW.js +0 -60
  106. package/dist/chunk-WWV3SJ3L.js +0 -131
  107. package/dist/dispose-46BOMMQJ.js +0 -19
  108. package/dist/plugin-D30wlGW5.d.cts +0 -71
  109. package/dist/plugin-D30wlGW5.d.ts +0 -71
package/dist/extras.cjs CHANGED
@@ -24,6 +24,8 @@ __export(extras_exports, {
24
24
  Features: () => Features,
25
25
  FocusTrap: () => FocusTrap,
26
26
  Head: () => Head,
27
+ MigrationStorageError: () => MigrationStorageError,
28
+ PluginInstallCancelledError: () => PluginInstallCancelledError,
27
29
  Priority: () => Priority,
28
30
  RenderProp: () => RenderProp,
29
31
  SibuError: () => SibuError,
@@ -32,6 +34,7 @@ __export(extras_exports, {
32
34
  VirtualList: () => VirtualList,
33
35
  __resetDialogStack: () => __resetDialogStack,
34
36
  __resetQueryCache: () => __resetQueryCache,
37
+ __resetSpeechCoordinator: () => __resetSpeechCoordinator,
35
38
  accordion: () => accordion,
36
39
  animate: () => animate,
37
40
  animationFrame: () => animationFrame,
@@ -167,6 +170,7 @@ __export(extras_exports, {
167
170
  isDangerousMetaRefresh: () => isDangerousMetaRefresh,
168
171
  isDebugEnabled: () => isDebugEnabled,
169
172
  isHMRAvailable: () => isHMRAvailable,
173
+ isPluginInstallCancelledError: () => isPluginInstallCancelledError,
170
174
  isWasmCached: () => isWasmCached,
171
175
  island: () => island,
172
176
  keyboard: () => keyboard,
@@ -234,6 +238,7 @@ __export(extras_exports, {
234
238
  renderToStream: () => renderToStream,
235
239
  renderToString: () => renderToString,
236
240
  renderToSuspenseStream: () => renderToSuspenseStream,
241
+ renderWithLoader: () => renderWithLoader,
237
242
  required: () => required,
238
243
  resetIdCounter: () => resetIdCounter,
239
244
  resetPlugins: () => resetPlugins,
@@ -305,6 +310,7 @@ __export(extras_exports, {
305
310
  withBoundary: () => withBoundary,
306
311
  withDefaults: () => withDefaults,
307
312
  withErrorTracking: () => withErrorTracking,
313
+ withLoader: () => withLoader,
308
314
  withProps: () => withProps,
309
315
  withRetry: () => withRetry,
310
316
  withScopedStyle: () => withScopedStyle,
@@ -367,8 +373,8 @@ function safeStringify(value) {
367
373
  return Object.prototype.toString.call(value);
368
374
  }
369
375
  }
370
- function describe(context2) {
371
- return context2.name ? `${context2.phase} "${context2.name}"` : context2.phase;
376
+ function describe(context) {
377
+ return context.name ? `${context.phase} "${context.name}"` : context.phase;
372
378
  }
373
379
  var REACTIVE_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
374
380
  function untrackedRun(fn) {
@@ -376,8 +382,8 @@ function untrackedRun(fn) {
376
382
  return api?.untracked ? api.untracked(fn) : fn();
377
383
  }
378
384
  var PROPAGATE_EVENT = "sibu:error-propagate";
379
- function claimedByBoundary(error, context2) {
380
- const raw = context2.node;
385
+ function claimedByBoundary(error, context) {
386
+ const raw = context.node;
381
387
  if (!raw || typeof CustomEvent === "undefined") return false;
382
388
  const target = resolveEventTarget(raw);
383
389
  if (!target) return false;
@@ -385,7 +391,7 @@ function claimedByBoundary(error, context2) {
385
391
  const event = new CustomEvent(PROPAGATE_EVENT, {
386
392
  bubbles: true,
387
393
  cancelable: true,
388
- detail: { error, context: context2 }
394
+ detail: { error, context }
389
395
  });
390
396
  return target.dispatchEvent(event) === false;
391
397
  } catch {
@@ -404,20 +410,20 @@ function resolveEventTarget(node) {
404
410
  const self2 = node;
405
411
  return typeof self2.dispatchEvent === "function" ? self2 : null;
406
412
  }
407
- function defaultReport(error, context2) {
413
+ function defaultReport(error, context) {
408
414
  if (typeof console === "undefined") return;
409
- console.error(`[SibuJS] Uncaught error during ${describe(context2)}: ${error.message}`, error);
415
+ console.error(`[SibuJS] Uncaught error during ${describe(context)}: ${error.message}`, error);
410
416
  }
411
- function reportError(error, context2) {
417
+ function reportError(error, context) {
412
418
  const err = normalizeError(error);
413
419
  untrackedRun(() => {
414
- if (claimedByBoundary(err, context2)) return;
420
+ if (claimedByBoundary(err, context)) return;
415
421
  const s2 = state();
416
422
  const handler = s2.handler;
417
423
  if (handler && s2.handlerDepth === 0) {
418
424
  s2.handlerDepth++;
419
425
  try {
420
- handler(error instanceof Error ? error : err, context2);
426
+ handler(error instanceof Error ? error : err, context);
421
427
  return;
422
428
  } catch (handlerError) {
423
429
  if (typeof console !== "undefined") {
@@ -428,10 +434,158 @@ function reportError(error, context2) {
428
434
  s2.handlerDepth--;
429
435
  }
430
436
  }
431
- defaultReport(err, context2);
437
+ defaultReport(err, context);
432
438
  });
433
439
  }
434
440
 
441
+ // src/core/rendering/dispose.ts
442
+ var elementDisposers = /* @__PURE__ */ new WeakMap();
443
+ var MAX_DRAIN_TEARDOWNS = 1e4;
444
+ function reportDrainRunaway(label2, executed, remaining) {
445
+ if (typeof console === "undefined") return;
446
+ console.error(
447
+ `[SibuJS ${label2}] runaway cleanup: stopped after running ${executed} teardowns with ${remaining} still queued. Cleanup production did not stabilize before the teardown safety ceiling was reached \u2014 the remaining work was NOT run.`,
448
+ { executed, remaining }
449
+ );
450
+ }
451
+ var activeBindingCount = 0;
452
+ function resolveCapture(capture) {
453
+ let frame = capture;
454
+ while (frame?.closed) frame = frame.forwardTo;
455
+ return frame ?? null;
456
+ }
457
+ var registrationCaptures = [];
458
+ function currentDisposerCapture() {
459
+ return resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
460
+ }
461
+ function beginDisposerCapture(capture) {
462
+ if (capture === null && registrationCaptures.length === 0) return false;
463
+ registrationCaptures.push(resolveCapture(capture));
464
+ return true;
465
+ }
466
+ function endDisposerCapture() {
467
+ registrationCaptures.pop();
468
+ }
469
+ function registerDisposer(node, teardown) {
470
+ let disposers = elementDisposers.get(node);
471
+ if (!disposers) {
472
+ disposers = [];
473
+ elementDisposers.set(node, disposers);
474
+ }
475
+ disposers.push(teardown);
476
+ if (DEV) activeBindingCount++;
477
+ const capture = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
478
+ if (capture) capture.entries.push([node, teardown]);
479
+ }
480
+ function withDisposerRollback(build) {
481
+ const frame = { entries: [], closed: false };
482
+ const captured = frame.entries;
483
+ registrationCaptures.push(frame);
484
+ let result;
485
+ try {
486
+ result = build();
487
+ } catch (err) {
488
+ let executed = 0;
489
+ try {
490
+ while (captured.length > 0) {
491
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
492
+ reportDrainRunaway("rollback", executed, captured.length);
493
+ break;
494
+ }
495
+ const [node, teardown] = captured.pop();
496
+ if (!unregisterDisposer(node, teardown)) continue;
497
+ executed++;
498
+ try {
499
+ teardown();
500
+ } catch (cleanupErr) {
501
+ reportError(cleanupErr, { phase: "cleanup", name: "disposer" });
502
+ }
503
+ }
504
+ } finally {
505
+ frame.closed = true;
506
+ captured.length = 0;
507
+ registrationCaptures.pop();
508
+ }
509
+ throw err;
510
+ }
511
+ frame.closed = true;
512
+ registrationCaptures.pop();
513
+ const parent = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
514
+ if (parent) {
515
+ for (const entry of captured) {
516
+ if (elementDisposers.get(entry[0])?.includes(entry[1])) parent.entries.push(entry);
517
+ }
518
+ frame.forwardTo = parent;
519
+ }
520
+ captured.length = 0;
521
+ return result;
522
+ }
523
+ function unregisterDisposer(node, teardown) {
524
+ const disposers = elementDisposers.get(node);
525
+ if (!disposers) return false;
526
+ const index = disposers.indexOf(teardown);
527
+ if (index === -1) return false;
528
+ disposers.splice(index, 1);
529
+ if (DEV) activeBindingCount--;
530
+ if (disposers.length === 0) elementDisposers.delete(node);
531
+ return true;
532
+ }
533
+ function dispose(node) {
534
+ const stack = [node];
535
+ const order = [];
536
+ while (stack.length > 0) {
537
+ const current = stack.pop();
538
+ order.push(current);
539
+ const children = Array.from(current.childNodes);
540
+ for (let i2 = 0; i2 < children.length; i2++) {
541
+ stack.push(children[i2]);
542
+ }
543
+ }
544
+ for (let i2 = order.length - 1; i2 >= 0; i2--) {
545
+ disposeNodeOwn(order[i2]);
546
+ }
547
+ }
548
+ function disposeNodeOwn(node) {
549
+ if (!elementDisposers.has(node)) return;
550
+ let executed = 0;
551
+ let runaway = false;
552
+ while (!runaway) {
553
+ const pending = elementDisposers.get(node);
554
+ if (!pending || pending.length === 0) break;
555
+ const snapshot = pending.slice();
556
+ elementDisposers.delete(node);
557
+ if (DEV) activeBindingCount -= snapshot.length;
558
+ for (let i2 = 0; i2 < snapshot.length; i2++) {
559
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
560
+ const rest = snapshot.slice(i2);
561
+ const added = elementDisposers.get(node);
562
+ elementDisposers.set(node, added ? rest.concat(added) : rest);
563
+ if (DEV) activeBindingCount += rest.length;
564
+ reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
565
+ runaway = true;
566
+ break;
567
+ }
568
+ executed++;
569
+ try {
570
+ snapshot[i2]();
571
+ } catch (err) {
572
+ reportError(err, { phase: "cleanup", name: "disposer" });
573
+ }
574
+ }
575
+ }
576
+ }
577
+ function replaceChildrenSafely(parent, ...next) {
578
+ for (let i2 = 0; i2 < next.length; i2++) {
579
+ const node = next[i2];
580
+ node.parentNode?.removeChild(node);
581
+ }
582
+ const current = Array.from(parent.childNodes);
583
+ for (let i2 = 0; i2 < current.length; i2++) {
584
+ dispose(current[i2]);
585
+ }
586
+ parent.replaceChildren(...next);
587
+ }
588
+
435
589
  // src/reactivity/track-core.ts
436
590
  var POOL_MAX = 4096;
437
591
  var nodePool = [];
@@ -590,6 +744,7 @@ function retrack(effectFn, subscriber) {
590
744
  }
591
745
  currentSubscriber = subscriber;
592
746
  const sub2 = subscriber;
747
+ if (sub2._cap === void 0) sub2._cap = currentDisposerCapture();
593
748
  const epoch = ++subscriberEpochCounter;
594
749
  sub2._epoch = epoch;
595
750
  sub2._structDirty = false;
@@ -781,7 +936,12 @@ function drainQueue() {
781
936
  quarantined.add(sub2);
782
937
  continue;
783
938
  }
784
- safeInvoke(sub2);
939
+ const pushed = beginDisposerCapture(sub2._cap ?? null);
940
+ try {
941
+ safeInvoke(sub2);
942
+ } finally {
943
+ if (pushed) endDisposerCapture();
944
+ }
785
945
  }
786
946
  }
787
947
  function drainNotificationQueue() {
@@ -917,7 +1077,7 @@ function forEachSubscriber(signal2, visit) {
917
1077
  }
918
1078
 
919
1079
  // src/reactivity/track.ts
920
- var _runtimeVersion = true ? "4.5.0" : "dev";
1080
+ var _runtimeVersion = true ? "4.6.0" : "dev";
921
1081
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
922
1082
  function resolveReactiveApi() {
923
1083
  const g2 = globalThis;
@@ -1118,6 +1278,10 @@ function getSSRStore() {
1118
1278
  }
1119
1279
  return fallbackStore;
1120
1280
  }
1281
+ function getRequestStore() {
1282
+ if (als) return als.getStore() ?? null;
1283
+ return _shared.fallbackDepth > 0 ? fallbackStore : null;
1284
+ }
1121
1285
  function isSSR() {
1122
1286
  return getSSRStore().ssr;
1123
1287
  }
@@ -1839,7 +2003,7 @@ function mutation(mutationFn, options = {}) {
1839
2003
  abortController = new AbortController();
1840
2004
  const signal2 = abortController.signal;
1841
2005
  const myRun = ++runId;
1842
- let context2;
2006
+ let context;
1843
2007
  batch(() => {
1844
2008
  setLoading(true);
1845
2009
  setError(void 0);
@@ -1847,7 +2011,7 @@ function mutation(mutationFn, options = {}) {
1847
2011
  });
1848
2012
  try {
1849
2013
  if (options.onMutate) {
1850
- context2 = await options.onMutate(variables);
2014
+ context = await options.onMutate(variables);
1851
2015
  }
1852
2016
  const result = await withRetry(() => mutationFn(variables, signal2), options.retry, void 0, signal2);
1853
2017
  if (myRun !== runId) return result;
@@ -1858,8 +2022,8 @@ function mutation(mutationFn, options = {}) {
1858
2022
  setLoading(false);
1859
2023
  setStatus("success");
1860
2024
  });
1861
- runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context2));
1862
- runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context2));
2025
+ runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context));
2026
+ runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context));
1863
2027
  return result;
1864
2028
  } catch (err) {
1865
2029
  if (isAbortError(err)) {
@@ -1881,8 +2045,8 @@ function mutation(mutationFn, options = {}) {
1881
2045
  setLoading(false);
1882
2046
  setStatus("error");
1883
2047
  });
1884
- runCallback("mutation onError", () => options.onError?.(errorObj, variables, context2));
1885
- runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context2));
2048
+ runCallback("mutation onError", () => options.onError?.(errorObj, variables, context));
2049
+ runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context));
1886
2050
  throw errorObj;
1887
2051
  }
1888
2052
  }
@@ -2020,18 +2184,21 @@ function infiniteQuery(key, fetcher, options) {
2020
2184
  }
2021
2185
  });
2022
2186
  function fetchNextPage() {
2187
+ if (disposed) return Promise.resolve();
2023
2188
  if (inFlight) return inFlight;
2024
2189
  const param2 = nextPageParam();
2025
2190
  if (param2 === void 0) return Promise.resolve();
2026
2191
  return fetchPage(param2, "next");
2027
2192
  }
2028
2193
  function fetchPreviousPage() {
2194
+ if (disposed) return Promise.resolve();
2029
2195
  if (inFlight) return inFlight;
2030
2196
  const param2 = prevPageParam();
2031
2197
  if (param2 === void 0) return Promise.resolve();
2032
2198
  return fetchPage(param2, "prev");
2033
2199
  }
2034
2200
  async function refetch() {
2201
+ if (disposed) return;
2035
2202
  batch(() => {
2036
2203
  setPages([]);
2037
2204
  setNextPageParam(initialPageParam);
@@ -2115,25 +2282,26 @@ function throttle(getter, interval) {
2115
2282
  let pending = null;
2116
2283
  let lastEmitted = getter();
2117
2284
  let timer = null;
2285
+ function emit(value) {
2286
+ setThrottled(value);
2287
+ lastEmitted = value;
2288
+ cooldown = true;
2289
+ pending = null;
2290
+ timer = setTimeout(endWindow, interval);
2291
+ }
2292
+ function endWindow() {
2293
+ timer = null;
2294
+ if (pending !== null && !Object.is(pending.value, lastEmitted)) {
2295
+ emit(pending.value);
2296
+ return;
2297
+ }
2298
+ pending = null;
2299
+ cooldown = false;
2300
+ }
2118
2301
  const stop2 = effect(() => {
2119
2302
  const value = getter();
2120
2303
  if (!cooldown) {
2121
- if (!Object.is(value, lastEmitted)) {
2122
- setThrottled(value);
2123
- lastEmitted = value;
2124
- cooldown = true;
2125
- pending = null;
2126
- timer = setTimeout(() => {
2127
- timer = null;
2128
- cooldown = false;
2129
- if (pending !== null) {
2130
- const trailingValue = pending.value;
2131
- pending = null;
2132
- setThrottled(trailingValue);
2133
- lastEmitted = trailingValue;
2134
- }
2135
- }, interval);
2136
- }
2304
+ if (!Object.is(value, lastEmitted)) emit(value);
2137
2305
  } else {
2138
2306
  pending = { value };
2139
2307
  }
@@ -2207,7 +2375,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
2207
2375
  });
2208
2376
  runCallback("resource onError", () => options.onError?.(errorObj));
2209
2377
  } finally {
2210
- if (version === fetchVersion) {
2378
+ if (!disposed && version === fetchVersion) {
2211
2379
  runCallback("resource onSettled", () => options.onSettled?.());
2212
2380
  }
2213
2381
  }
@@ -2513,62 +2681,55 @@ function syncAdapter(config) {
2513
2681
  return config;
2514
2682
  }
2515
2683
 
2516
- // src/core/rendering/context.ts
2517
- function context(defaultValue) {
2518
- const [getValue, setValue] = signal(defaultValue);
2519
- const warnIfSSR = (method) => {
2520
- if (!DEV || !isSSR()) return;
2521
- devWarn(
2522
- `context.${method}() called during SSR. SibuJS context() is application-global \u2014 it is NOT isolated per request, so a concurrent request can observe this value. Do not use context() for request-specific SSR data; pass it explicitly or use request-scoped storage instead.`
2523
- );
2524
- };
2525
- const ctx = {
2526
- provide(value) {
2527
- warnIfSSR("provide");
2528
- const previous2 = getValue();
2529
- setValue(value);
2530
- return () => setValue(previous2);
2531
- },
2532
- use() {
2533
- return getValue;
2534
- },
2535
- get() {
2536
- return getValue();
2537
- },
2538
- set(value) {
2539
- warnIfSSR("set");
2540
- setValue(value);
2541
- },
2542
- withContext(value, fn) {
2543
- warnIfSSR("withContext");
2544
- const previous2 = getValue();
2545
- setValue(value);
2546
- try {
2547
- const result = fn();
2548
- if (DEV && result !== null && typeof result?.then === "function") {
2549
- devWarn(
2550
- "context.withContext() received an async callback. Scoping is synchronous only \u2014 the previous value is restored as soon as the callback returns its promise, so anything after an `await` runs outside the scope."
2551
- );
2552
- }
2553
- return result;
2554
- } finally {
2555
- setValue(previous2);
2556
- }
2557
- }
2558
- };
2559
- return ctx;
2560
- }
2561
-
2562
2684
  // src/data/routeLoader.ts
2563
- var LoaderContext = context(null);
2564
- function executeLoader(loader, context2, options) {
2565
- const res = resource(({ signal: signal2 }) => loader(context2, { signal: signal2 }), options);
2566
- LoaderContext.provide(res);
2685
+ var _clientScope = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.routeLoader.v2"), () => ({ stack: [] }));
2686
+ var _disposed = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.routeLoader.disposed.v1"), () => /* @__PURE__ */ new WeakSet());
2687
+ function currentScope() {
2688
+ const cache = getRequestScopedCache("routeLoader");
2689
+ if (!cache) return _clientScope;
2690
+ let scope = cache.get("scope");
2691
+ if (!scope) {
2692
+ scope = { stack: [] };
2693
+ cache.set("scope", scope);
2694
+ }
2695
+ return scope;
2696
+ }
2697
+ function executeLoader(loader, context, options) {
2698
+ const res = resource(({ signal: signal2 }) => loader(context, { signal: signal2 }), options);
2699
+ const disposeResource = res.dispose;
2700
+ res.dispose = () => {
2701
+ _disposed.add(res);
2702
+ disposeResource();
2703
+ };
2567
2704
  return res;
2568
2705
  }
2706
+ function renderWithLoader(loader, render) {
2707
+ if (_disposed.has(loader)) {
2708
+ throw new Error("renderWithLoader: the loader resource has been disposed");
2709
+ }
2710
+ const scope = currentScope();
2711
+ const entry = loader;
2712
+ scope.stack.push(entry);
2713
+ try {
2714
+ return render();
2715
+ } finally {
2716
+ const index = scope.stack.lastIndexOf(entry);
2717
+ if (index !== -1) scope.stack.splice(index, 1);
2718
+ }
2719
+ }
2720
+ function withLoader(loader, context, render, options) {
2721
+ const res = executeLoader(loader, context, options);
2722
+ try {
2723
+ return { view: renderWithLoader(res, render), resource: res };
2724
+ } catch (err) {
2725
+ res.dispose();
2726
+ throw err;
2727
+ }
2728
+ }
2569
2729
  function loaderData() {
2570
- const resource2 = LoaderContext.get();
2571
- if (!resource2) {
2730
+ const scope = currentScope();
2731
+ const resource2 = scope.stack[scope.stack.length - 1];
2732
+ if (!resource2 || _disposed.has(resource2)) {
2572
2733
  throw new Error("loaderData must be used inside a route with a loader");
2573
2734
  }
2574
2735
  return {
@@ -2577,7 +2738,7 @@ function loaderData() {
2577
2738
  error: resource2.error
2578
2739
  };
2579
2740
  }
2580
- async function preloadRoute(route, context2, callerSignal) {
2741
+ async function preloadRoute(route, context, callerSignal) {
2581
2742
  if (!route.loader) return void 0;
2582
2743
  const controller = new AbortController();
2583
2744
  let onAbort = null;
@@ -2590,7 +2751,7 @@ async function preloadRoute(route, context2, callerSignal) {
2590
2751
  }
2591
2752
  }
2592
2753
  try {
2593
- return await route.loader(context2, { signal: controller.signal });
2754
+ return await route.loader(context, { signal: controller.signal });
2594
2755
  } finally {
2595
2756
  if (onAbort) callerSignal?.removeEventListener("abort", onAbort);
2596
2757
  }
@@ -2618,34 +2779,73 @@ function socket(url, options) {
2618
2779
  let heartbeatTimer = null;
2619
2780
  let disposed = false;
2620
2781
  let manuallyClosed = false;
2782
+ let lifecycle = 0;
2621
2783
  function getUrl() {
2622
2784
  return typeof url === "function" ? url() : url;
2623
2785
  }
2624
2786
  function connect() {
2625
2787
  if (disposed) return;
2788
+ const generation = lifecycle;
2789
+ const invalidated = () => disposed || generation !== lifecycle;
2626
2790
  if (typeof WebSocket === "undefined") {
2627
2791
  setStatus("closed");
2628
2792
  return;
2629
2793
  }
2630
- const safeUrl = validateWsUrl(getUrl());
2794
+ let rawUrl;
2795
+ try {
2796
+ rawUrl = getUrl();
2797
+ } catch (error) {
2798
+ setStatus("closed");
2799
+ reportError(error, { phase: "async", name: "socket" });
2800
+ return;
2801
+ }
2802
+ if (invalidated()) {
2803
+ setStatus("closed");
2804
+ return;
2805
+ }
2806
+ const safeUrl = validateWsUrl(rawUrl);
2631
2807
  if (safeUrl === null) {
2632
2808
  setStatus("closed");
2633
2809
  return;
2634
2810
  }
2635
2811
  setStatus("connecting");
2636
- ws = new WebSocket(safeUrl, protocols);
2637
- ws.onopen = () => {
2812
+ if (invalidated()) {
2813
+ setStatus("closed");
2814
+ return;
2815
+ }
2816
+ let instance;
2817
+ try {
2818
+ instance = new WebSocket(safeUrl, protocols);
2819
+ } catch (error) {
2820
+ setStatus("closed");
2821
+ reportError(error, { phase: "async", name: "socket" });
2822
+ return;
2823
+ }
2824
+ if (invalidated()) {
2825
+ instance.close();
2826
+ setStatus("closed");
2827
+ return;
2828
+ }
2829
+ ws = instance;
2830
+ instance.onopen = () => {
2831
+ if (ws !== instance) return;
2832
+ const openGeneration = lifecycle;
2638
2833
  setStatus("open");
2834
+ if (disposed || openGeneration !== lifecycle || ws !== instance) return;
2639
2835
  reconnectCount = 0;
2640
2836
  startHeartbeat();
2641
2837
  };
2642
- ws.onmessage = (event) => {
2838
+ instance.onmessage = (event) => {
2839
+ if (ws !== instance) return;
2643
2840
  setData(event.data);
2644
2841
  };
2645
- ws.onclose = () => {
2842
+ instance.onclose = () => {
2843
+ if (ws !== instance) return;
2844
+ ws = null;
2845
+ const closeGeneration = lifecycle;
2646
2846
  setStatus("closed");
2647
2847
  stopHeartbeat();
2648
- const wasManual = manuallyClosed;
2848
+ const wasManual = manuallyClosed || closeGeneration !== lifecycle;
2649
2849
  manuallyClosed = false;
2650
2850
  if (autoReconnect && !disposed && !wasManual && reconnectCount < maxReconnects) {
2651
2851
  const cap = 3e4;
@@ -2659,7 +2859,7 @@ function socket(url, options) {
2659
2859
  }, jittered);
2660
2860
  }
2661
2861
  };
2662
- ws.onerror = () => {
2862
+ instance.onerror = () => {
2663
2863
  };
2664
2864
  }
2665
2865
  function startHeartbeat() {
@@ -2683,13 +2883,23 @@ function socket(url, options) {
2683
2883
  }
2684
2884
  }
2685
2885
  function close() {
2886
+ lifecycle++;
2686
2887
  manuallyClosed = true;
2687
2888
  if (reconnectTimer !== null) {
2688
2889
  clearTimeout(reconnectTimer);
2689
2890
  reconnectTimer = null;
2690
2891
  }
2691
2892
  stopHeartbeat();
2692
- if (ws) {
2893
+ if (!ws) {
2894
+ setStatus("closed");
2895
+ return;
2896
+ }
2897
+ if (ws.readyState === WebSocket.CLOSED) {
2898
+ ws = null;
2899
+ setStatus("closed");
2900
+ return;
2901
+ }
2902
+ if (ws.readyState !== WebSocket.CLOSING) {
2693
2903
  setStatus("closing");
2694
2904
  ws.close();
2695
2905
  }
@@ -2856,17 +3066,17 @@ function blockedCssReason(value) {
2856
3066
  }
2857
3067
  return -1;
2858
3068
  }
2859
- function sanitizeCSSValue(value, context2) {
3069
+ function sanitizeCSSValue(value, context) {
2860
3070
  const reason = blockedCssReason(value);
2861
3071
  if (reason === -1) return value;
2862
- warnDroppedDeclaration(value, reason, context2);
3072
+ warnDroppedDeclaration(value, reason, context);
2863
3073
  return "";
2864
3074
  }
2865
- function sanitizeStyleAttribute(cssText, context2) {
3075
+ function sanitizeStyleAttribute(cssText, context) {
2866
3076
  const input2 = String(cssText);
2867
3077
  if (input2.trim() === "") return "";
2868
3078
  if (typeof document === "undefined") {
2869
- return sanitizeCSSValue(input2, context2) === "" ? "" : input2;
3079
+ return sanitizeCSSValue(input2, context) === "" ? "" : input2;
2870
3080
  }
2871
3081
  const probe = document.createElement("div");
2872
3082
  try {
@@ -2881,7 +3091,7 @@ function sanitizeStyleAttribute(cssText, context2) {
2881
3091
  let reason = blockedCssReason(value);
2882
3092
  if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
2883
3093
  if (reason !== -1) {
2884
- warnDroppedDeclaration(value, reason, { property, element: context2?.element });
3094
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
2885
3095
  continue;
2886
3096
  }
2887
3097
  const priority = probe.style.getPropertyPriority(property);
@@ -2931,11 +3141,11 @@ function isPolicyAttribute(name) {
2931
3141
  const lower = name.toLowerCase();
2932
3142
  return lower === "style" || URL_ATTRIBUTES.has(lower);
2933
3143
  }
2934
- function sanitizeAttributeString(attr, value, context2) {
3144
+ function sanitizeAttributeString(attr, value, context) {
2935
3145
  const lower = attr.toLowerCase();
2936
3146
  if (lower === "srcset") return sanitizeSrcset(value);
2937
3147
  if (URL_ATTRIBUTES.has(lower)) return sanitizeUrl(value);
2938
- if (lower === "style") return sanitizeStyleAttribute(value, context2);
3148
+ if (lower === "style") return sanitizeStyleAttribute(value, context);
2939
3149
  return value;
2940
3150
  }
2941
3151
 
@@ -2957,6 +3167,7 @@ function stream(url, options) {
2957
3167
  let disposed = false;
2958
3168
  let reconnectTimer = null;
2959
3169
  let attempts = 0;
3170
+ let closeGeneration = 0;
2960
3171
  function connect() {
2961
3172
  if (disposed) return;
2962
3173
  if (typeof EventSource === "undefined") {
@@ -2968,44 +3179,64 @@ function stream(url, options) {
2968
3179
  setStatus("closed");
2969
3180
  return;
2970
3181
  }
3182
+ const generation = closeGeneration;
2971
3183
  setStatus("connecting");
2972
- source2 = new EventSource(safeUrl, {
3184
+ if (disposed || generation !== closeGeneration) return;
3185
+ const instance = new EventSource(safeUrl, {
2973
3186
  withCredentials: options?.withCredentials ?? false
2974
3187
  });
2975
- source2.onopen = () => {
3188
+ if (disposed || generation !== closeGeneration) {
3189
+ instance.close();
3190
+ return;
3191
+ }
3192
+ source2 = instance;
3193
+ const live = () => !disposed && source2 === instance;
3194
+ instance.onopen = () => {
3195
+ if (!live()) return;
2976
3196
  setStatus("open");
2977
3197
  attempts = 0;
2978
3198
  };
2979
- source2.onmessage = (evt) => {
3199
+ instance.onmessage = (evt) => {
3200
+ if (!live()) return;
2980
3201
  setData(evt.data);
2981
3202
  setEvent(evt.type);
2982
3203
  };
2983
- source2.onerror = () => {
2984
- if (source2 && source2.readyState === EventSource.CLOSED) {
2985
- setStatus("closed");
2986
- source2 = null;
2987
- if (autoReconnect && !disposed && attempts < maxReconnects) {
2988
- const delay = Math.min(maxMs, baseMs * 2 ** attempts);
2989
- const jittered = delay * (0.5 + Math.random() * 0.5);
2990
- attempts++;
2991
- reconnectTimer = setTimeout(() => {
2992
- reconnectTimer = null;
2993
- connect();
2994
- }, jittered);
2995
- }
3204
+ instance.onerror = () => {
3205
+ if (!live() || instance.readyState !== EventSource.CLOSED) return;
3206
+ detach(instance);
3207
+ source2 = null;
3208
+ const generation2 = closeGeneration;
3209
+ setStatus("closed");
3210
+ if (disposed || generation2 !== closeGeneration) return;
3211
+ if (autoReconnect && attempts < maxReconnects) {
3212
+ const delay = Math.min(maxMs, baseMs * 2 ** attempts);
3213
+ const jittered = delay * (0.5 + Math.random() * 0.5);
3214
+ attempts++;
3215
+ reconnectTimer = setTimeout(() => {
3216
+ reconnectTimer = null;
3217
+ connect();
3218
+ }, jittered);
2996
3219
  }
2997
3220
  };
2998
3221
  }
3222
+ function detach(instance) {
3223
+ instance.onopen = null;
3224
+ instance.onmessage = null;
3225
+ instance.onerror = null;
3226
+ }
2999
3227
  function close() {
3228
+ closeGeneration++;
3000
3229
  if (reconnectTimer !== null) {
3001
3230
  clearTimeout(reconnectTimer);
3002
3231
  reconnectTimer = null;
3003
3232
  }
3004
- if (source2) {
3005
- source2.close();
3006
- setStatus("closed");
3233
+ const instance = source2;
3234
+ if (instance) {
3007
3235
  source2 = null;
3236
+ detach(instance);
3237
+ instance.close();
3008
3238
  }
3239
+ setStatus("closed");
3009
3240
  }
3010
3241
  function dispose2() {
3011
3242
  disposed = true;
@@ -3306,13 +3537,15 @@ function clipboard() {
3306
3537
  const [copied, setCopied] = signal(false);
3307
3538
  let copiedTimer = null;
3308
3539
  let disposed = false;
3540
+ let generation = 0;
3309
3541
  async function copy(value) {
3310
3542
  if (disposed) return;
3311
3543
  if (typeof navigator === "undefined" || !navigator.clipboard) {
3312
3544
  return;
3313
3545
  }
3546
+ const mine = ++generation;
3314
3547
  await navigator.clipboard.writeText(value);
3315
- if (disposed) return;
3548
+ if (disposed || mine !== generation) return;
3316
3549
  setText(value);
3317
3550
  setCopied(true);
3318
3551
  if (copiedTimer !== null) clearTimeout(copiedTimer);
@@ -3324,6 +3557,7 @@ function clipboard() {
3324
3557
  }
3325
3558
  function dispose2() {
3326
3559
  disposed = true;
3560
+ generation++;
3327
3561
  if (copiedTimer !== null) {
3328
3562
  clearTimeout(copiedTimer);
3329
3563
  copiedTimer = null;
@@ -3358,15 +3592,26 @@ function draggable(element, data2) {
3358
3592
  let currentEl = null;
3359
3593
  let onDragStart = null;
3360
3594
  let onDragEnd = null;
3595
+ let prevDraggableAttr = null;
3596
+ function detach() {
3597
+ if (!currentEl) return;
3598
+ if (onDragStart) currentEl.removeEventListener("dragstart", onDragStart);
3599
+ if (onDragEnd) currentEl.removeEventListener("dragend", onDragEnd);
3600
+ if (prevDraggableAttr === null) currentEl.removeAttribute("draggable");
3601
+ else currentEl.setAttribute("draggable", prevDraggableAttr);
3602
+ currentEl = null;
3603
+ onDragStart = null;
3604
+ onDragEnd = null;
3605
+ setIsDragging(false);
3606
+ }
3361
3607
  const getter = resolveTarget2(element);
3362
3608
  const cleanup3 = effect(() => {
3363
- if (currentEl && onDragStart && onDragEnd) {
3364
- currentEl.removeEventListener("dragstart", onDragStart);
3365
- currentEl.removeEventListener("dragend", onDragEnd);
3366
- }
3367
3609
  const el = getter();
3368
- currentEl = el;
3610
+ if (el === currentEl) return;
3611
+ detach();
3369
3612
  if (!el) return;
3613
+ currentEl = el;
3614
+ prevDraggableAttr = el.getAttribute("draggable");
3370
3615
  el.draggable = true;
3371
3616
  onDragStart = (e) => {
3372
3617
  setIsDragging(true);
@@ -3380,16 +3625,16 @@ function draggable(element, data2) {
3380
3625
  el.addEventListener("dragstart", onDragStart);
3381
3626
  el.addEventListener("dragend", onDragEnd);
3382
3627
  });
3628
+ let disposed = false;
3383
3629
  function dispose2() {
3630
+ if (disposed) return;
3631
+ disposed = true;
3384
3632
  cleanup3();
3385
- if (currentEl && onDragStart && onDragEnd) {
3386
- currentEl.removeEventListener("dragstart", onDragStart);
3387
- currentEl.removeEventListener("dragend", onDragEnd);
3388
- currentEl = null;
3389
- }
3633
+ detach();
3390
3634
  }
3391
3635
  return { isDragging, dispose: dispose2 };
3392
3636
  }
3637
+ var DROP_ZONE_EXIT_GRACE_MS = 600;
3393
3638
  function dropZone(element, options) {
3394
3639
  const [isOver, setIsOver] = signal(false);
3395
3640
  if (typeof window === "undefined") {
@@ -3401,30 +3646,66 @@ function dropZone(element, options) {
3401
3646
  let onDragEnter = null;
3402
3647
  let onDragLeave = null;
3403
3648
  let onDrop = null;
3404
- const getter = resolveTarget2(element);
3405
- const cleanup3 = effect(() => {
3649
+ let depth = 0;
3650
+ let exitTimer = null;
3651
+ function cancelPendingExit() {
3652
+ if (exitTimer !== null) {
3653
+ clearTimeout(exitTimer);
3654
+ exitTimer = null;
3655
+ }
3656
+ }
3657
+ function resetOver() {
3658
+ cancelPendingExit();
3659
+ depth = 0;
3660
+ setIsOver(false);
3661
+ }
3662
+ function detach() {
3406
3663
  if (currentEl && onDragOver && onDragEnter && onDragLeave && onDrop) {
3407
3664
  currentEl.removeEventListener("dragover", onDragOver);
3408
3665
  currentEl.removeEventListener("dragenter", onDragEnter);
3409
3666
  currentEl.removeEventListener("dragleave", onDragLeave);
3410
3667
  currentEl.removeEventListener("drop", onDrop);
3411
3668
  }
3669
+ currentEl = null;
3670
+ resetOver();
3671
+ }
3672
+ const getter = resolveTarget2(element);
3673
+ const cleanup3 = effect(() => {
3412
3674
  const el = getter();
3413
- currentEl = el;
3675
+ if (el === currentEl) return;
3676
+ detach();
3414
3677
  if (!el) return;
3678
+ currentEl = el;
3415
3679
  onDragOver = (e) => {
3416
3680
  e.preventDefault();
3681
+ cancelPendingExit();
3417
3682
  };
3418
3683
  onDragEnter = (e) => {
3419
3684
  e.preventDefault();
3685
+ cancelPendingExit();
3686
+ depth++;
3420
3687
  setIsOver(true);
3421
3688
  };
3422
- onDragLeave = () => {
3423
- setIsOver(false);
3689
+ onDragLeave = (e) => {
3690
+ depth = Math.max(0, depth - 1);
3691
+ if (depth === 0) {
3692
+ resetOver();
3693
+ return;
3694
+ }
3695
+ const to = e.relatedTarget;
3696
+ if (to != null && typeof to.nodeType === "number") {
3697
+ if (!el.contains(to)) resetOver();
3698
+ return;
3699
+ }
3700
+ cancelPendingExit();
3701
+ exitTimer = setTimeout(() => {
3702
+ exitTimer = null;
3703
+ resetOver();
3704
+ }, DROP_ZONE_EXIT_GRACE_MS);
3424
3705
  };
3425
3706
  onDrop = (e) => {
3426
3707
  e.preventDefault();
3427
- setIsOver(false);
3708
+ resetOver();
3428
3709
  let transferData = null;
3429
3710
  if (e.dataTransfer) {
3430
3711
  const raw = e.dataTransfer.getData("application/json");
@@ -3445,13 +3726,7 @@ function dropZone(element, options) {
3445
3726
  });
3446
3727
  function dispose2() {
3447
3728
  cleanup3();
3448
- if (currentEl && onDragOver && onDragEnter && onDragLeave && onDrop) {
3449
- currentEl.removeEventListener("dragover", onDragOver);
3450
- currentEl.removeEventListener("dragenter", onDragEnter);
3451
- currentEl.removeEventListener("dragleave", onDragLeave);
3452
- currentEl.removeEventListener("drop", onDrop);
3453
- currentEl = null;
3454
- }
3729
+ detach();
3455
3730
  }
3456
3731
  return { isOver, dispose: dispose2 };
3457
3732
  }
@@ -3620,9 +3895,9 @@ function formatNumber(value, options) {
3620
3895
  function formatCurrency(value, currency, options) {
3621
3896
  const { locale, ...formatOptions } = options ?? {};
3622
3897
  return new Intl.NumberFormat(locale, {
3898
+ ...formatOptions,
3623
3899
  style: "currency",
3624
- currency,
3625
- ...formatOptions
3900
+ currency
3626
3901
  }).format(value);
3627
3902
  }
3628
3903
 
@@ -3708,18 +3983,36 @@ function swipe(target, options = {}) {
3708
3983
  }
3709
3984
  let startX = 0;
3710
3985
  let startY = 0;
3711
- let tracking = false;
3986
+ let trackedId = null;
3987
+ const idOf = (t) => t.identifier ?? 0;
3988
+ const findTracked = (list) => {
3989
+ if (!list) return null;
3990
+ for (let i2 = 0; i2 < list.length; i2++) {
3991
+ const t = list[i2];
3992
+ if (idOf(t) === trackedId) return t;
3993
+ }
3994
+ return null;
3995
+ };
3712
3996
  const onStart = (e) => {
3713
- if (e.touches.length === 0) return;
3714
- startX = e.touches[0].clientX;
3715
- startY = e.touches[0].clientY;
3716
- tracking = true;
3997
+ const onTarget = e.targetTouches ?? e.touches;
3998
+ if (onTarget.length > 1) {
3999
+ trackedId = null;
4000
+ return;
4001
+ }
4002
+ const touch = e.changedTouches?.[0] ?? e.touches[0];
4003
+ if (!touch) return;
4004
+ trackedId = idOf(touch);
4005
+ startX = touch.clientX;
4006
+ startY = touch.clientY;
4007
+ };
4008
+ const onCancel = (e) => {
4009
+ if (trackedId !== null && findTracked(e.changedTouches)) trackedId = null;
3717
4010
  };
3718
4011
  const onEnd = (e) => {
3719
- if (!tracking) return;
3720
- tracking = false;
3721
- const touch = e.changedTouches[0];
4012
+ if (trackedId === null) return;
4013
+ const touch = findTracked(e.changedTouches);
3722
4014
  if (!touch) return;
4015
+ trackedId = null;
3723
4016
  const dx = touch.clientX - startX;
3724
4017
  const dy = touch.clientY - startY;
3725
4018
  const absX = Math.abs(dx);
@@ -3736,9 +4029,12 @@ function swipe(target, options = {}) {
3736
4029
  };
3737
4030
  target.addEventListener("touchstart", onStart, { passive: true });
3738
4031
  target.addEventListener("touchend", onEnd, { passive: true });
4032
+ target.addEventListener("touchcancel", onCancel, { passive: true });
3739
4033
  function dispose2() {
3740
4034
  target.removeEventListener("touchstart", onStart);
3741
4035
  target.removeEventListener("touchend", onEnd);
4036
+ target.removeEventListener("touchcancel", onCancel);
4037
+ trackedId = null;
3742
4038
  }
3743
4039
  return { direction, dispose: dispose2 };
3744
4040
  }
@@ -3765,6 +4061,18 @@ function windowSize() {
3765
4061
  }
3766
4062
 
3767
4063
  // src/browser/urlState.ts
4064
+ var _url = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.urlState.v1"), () => ({
4065
+ instances: /* @__PURE__ */ new Set()
4066
+ }));
4067
+ function notifyUrlWrite() {
4068
+ for (const sync of Array.from(_url.instances)) sync();
4069
+ }
4070
+ var SCROLL_ENTRY_KEY = "__sibuScrollKey";
4071
+ function withoutEntryIdentity(state2) {
4072
+ if (state2 === null || typeof state2 !== "object" || !Object.hasOwn(state2, SCROLL_ENTRY_KEY)) return state2;
4073
+ const { [SCROLL_ENTRY_KEY]: _identity, ...rest } = state2;
4074
+ return rest;
4075
+ }
3768
4076
  function urlState() {
3769
4077
  if (typeof window === "undefined") {
3770
4078
  const [params2] = signal(new URLSearchParams());
@@ -3798,26 +4106,36 @@ function urlState() {
3798
4106
  }
3799
4107
  window.addEventListener("popstate", syncFromLocation);
3800
4108
  window.addEventListener("hashchange", syncFromLocation);
4109
+ _url.instances.add(syncFromLocation);
4110
+ function writeHistory(newUrl, opts) {
4111
+ const state2 = "state" in opts ? opts.state : window.history.state;
4112
+ if (opts.replace) window.history.replaceState(state2, "", newUrl);
4113
+ else window.history.pushState("state" in opts ? state2 : withoutEntryIdentity(state2), "", newUrl);
4114
+ }
3801
4115
  function setParams(next, opts = {}) {
3802
4116
  const p2 = next instanceof URLSearchParams ? next : new URLSearchParams(next);
3803
4117
  const query2 = p2.toString();
3804
4118
  const newUrl = `${window.location.pathname}${query2 ? `?${query2}` : ""}${window.location.hash}`;
3805
- if (opts.replace) window.history.replaceState(null, "", newUrl);
3806
- else window.history.pushState(null, "", newUrl);
4119
+ writeHistory(newUrl, opts);
3807
4120
  lastSearch = window.location.search;
3808
4121
  setParamsSignal(new URLSearchParams(p2));
4122
+ notifyUrlWrite();
3809
4123
  }
3810
4124
  function setHash(next, opts = {}) {
3811
4125
  const normalized = next && next !== "#" ? next.startsWith("#") ? next : `#${next}` : "";
3812
4126
  const newUrl = `${window.location.pathname}${window.location.search}${normalized}`;
3813
- if (opts.replace) window.history.replaceState(null, "", newUrl);
3814
- else window.history.pushState(null, "", newUrl);
4127
+ writeHistory(newUrl, opts);
3815
4128
  lastHash = normalized;
3816
4129
  setHashSignal(normalized);
4130
+ notifyUrlWrite();
3817
4131
  }
4132
+ let disposed = false;
3818
4133
  function dispose2() {
4134
+ if (disposed) return;
4135
+ disposed = true;
3819
4136
  window.removeEventListener("popstate", syncFromLocation);
3820
4137
  window.removeEventListener("hashchange", syncFromLocation);
4138
+ _url.instances.delete(syncFromLocation);
3821
4139
  }
3822
4140
  return { params, hash, setParams, setHash, dispose: dispose2 };
3823
4141
  }
@@ -3834,10 +4152,14 @@ function broadcast(channelName) {
3834
4152
  const channel = new BroadcastChannel(channelName);
3835
4153
  const handler = (ev) => setLast(ev.data);
3836
4154
  channel.addEventListener("message", handler);
4155
+ let disposed = false;
3837
4156
  function post(message) {
4157
+ if (disposed) return;
3838
4158
  channel.postMessage(message);
3839
4159
  }
3840
4160
  function dispose2() {
4161
+ if (disposed) return;
4162
+ disposed = true;
3841
4163
  channel.removeEventListener("message", handler);
3842
4164
  channel.close();
3843
4165
  }
@@ -4036,31 +4358,42 @@ function animationFrame(options = {}) {
4036
4358
  let prev = -1;
4037
4359
  let start = -1;
4038
4360
  let disposed = false;
4361
+ let active = false;
4362
+ let generation = 0;
4039
4363
  const minFrameMs = options.fpsLimit ? 1e3 / options.fpsLimit : 0;
4040
4364
  const step = (now) => {
4365
+ id = null;
4366
+ if (!active) return;
4367
+ const frameGeneration = generation;
4041
4368
  if (start < 0) start = now;
4042
4369
  const firstTick = prev < 0;
4043
4370
  const dt2 = firstTick ? 0 : now - prev;
4044
4371
  if (firstTick || dt2 >= minFrameMs) {
4045
- setDelta(dt2);
4046
- setElapsed(now - start);
4047
4372
  prev = now;
4373
+ const elapsedValue = now - start;
4374
+ setDelta(dt2);
4375
+ if (!active || generation !== frameGeneration) return;
4376
+ setElapsed(elapsedValue);
4377
+ if (!active || generation !== frameGeneration) return;
4048
4378
  }
4049
- id = requestAnimationFrame(step);
4379
+ if (id === null) id = requestAnimationFrame(step);
4050
4380
  };
4051
4381
  function resume() {
4052
- if (disposed || id !== null) return;
4053
- setRunning(true);
4382
+ if (disposed || active) return;
4383
+ active = true;
4054
4384
  id = requestAnimationFrame(step);
4385
+ setRunning(true);
4055
4386
  }
4056
4387
  function pause() {
4388
+ generation++;
4389
+ active = false;
4057
4390
  if (id !== null) {
4058
4391
  cancelAnimationFrame(id);
4059
4392
  id = null;
4060
4393
  }
4061
- setRunning(false);
4062
4394
  prev = -1;
4063
4395
  start = -1;
4396
+ setRunning(false);
4064
4397
  }
4065
4398
  function dispose2() {
4066
4399
  disposed = true;
@@ -4190,6 +4523,35 @@ function keyboard(options = {}) {
4190
4523
  }
4191
4524
 
4192
4525
  // src/browser/speech.ts
4526
+ var _speech = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.speech.v1"), () => ({
4527
+ queue: [],
4528
+ active: null,
4529
+ owners: /* @__PURE__ */ new Set()
4530
+ }));
4531
+ function syncOwners() {
4532
+ for (const owner of Array.from(_speech.owners)) owner.sync();
4533
+ }
4534
+ function pump(synth) {
4535
+ if (_speech.active) return;
4536
+ const index = _speech.queue.findIndex((entry2) => !entry2.owner.paused);
4537
+ if (index === -1) return;
4538
+ const [entry] = _speech.queue.splice(index, 1);
4539
+ _speech.active = entry;
4540
+ const finish = () => {
4541
+ if (_speech.active !== entry) return;
4542
+ _speech.active = null;
4543
+ pump(synth);
4544
+ syncOwners();
4545
+ };
4546
+ entry.utterance.addEventListener("end", finish, { once: true });
4547
+ entry.utterance.addEventListener("error", finish, { once: true });
4548
+ synth.speak(entry.utterance);
4549
+ }
4550
+ function __resetSpeechCoordinator() {
4551
+ _speech.queue.length = 0;
4552
+ _speech.active = null;
4553
+ _speech.owners.clear();
4554
+ }
4193
4555
  function speech() {
4194
4556
  const [speaking, setSpeaking] = signal(false);
4195
4557
  const [paused, setPaused] = signal(false);
@@ -4210,19 +4572,16 @@ function speech() {
4210
4572
  };
4211
4573
  }
4212
4574
  const synth = window.speechSynthesis;
4213
- let interval = null;
4214
- function startPolling() {
4215
- if (interval !== null) return;
4216
- interval = setInterval(() => {
4217
- setSpeaking(synth.speaking);
4218
- setPaused(synth.paused);
4219
- if (!synth.speaking && !synth.paused) {
4220
- clearInterval(interval);
4221
- interval = null;
4222
- }
4223
- }, 200);
4224
- }
4225
4575
  let disposed = false;
4576
+ const owner = {
4577
+ paused: false,
4578
+ sync: () => {
4579
+ const mine = _speech.active?.owner === owner || _speech.queue.some((entry) => entry.owner === owner);
4580
+ setSpeaking(mine);
4581
+ setPaused(owner.paused);
4582
+ }
4583
+ };
4584
+ _speech.owners.add(owner);
4226
4585
  function speak(text2, options = {}) {
4227
4586
  if (disposed) return;
4228
4587
  const u2 = new SpeechSynthesisUtterance(text2);
@@ -4235,52 +4594,48 @@ function speech() {
4235
4594
  const match = voices.find((v) => v.name === options.voice);
4236
4595
  if (match) u2.voice = match;
4237
4596
  }
4238
- u2.addEventListener(
4239
- "start",
4240
- () => {
4241
- if (!disposed) setSpeaking(true);
4242
- },
4243
- { once: true }
4244
- );
4245
- u2.addEventListener(
4246
- "end",
4247
- () => {
4248
- if (disposed) return;
4249
- setSpeaking(false);
4250
- setPaused(false);
4251
- },
4252
- { once: true }
4253
- );
4254
- u2.addEventListener(
4255
- "error",
4256
- () => {
4257
- if (disposed) return;
4258
- setSpeaking(false);
4259
- setPaused(false);
4260
- },
4261
- { once: true }
4262
- );
4263
- synth.speak(u2);
4264
- setSpeaking(true);
4265
- startPolling();
4597
+ _speech.queue.push({ owner, utterance: u2 });
4598
+ pump(synth);
4599
+ syncOwners();
4600
+ }
4601
+ function pause() {
4602
+ if (disposed || owner.paused) return;
4603
+ owner.paused = true;
4604
+ if (_speech.active?.owner === owner) synth.pause();
4605
+ syncOwners();
4606
+ }
4607
+ function resume() {
4608
+ if (disposed || !owner.paused) return;
4609
+ owner.paused = false;
4610
+ if (_speech.active?.owner === owner) synth.resume();
4611
+ else pump(synth);
4612
+ syncOwners();
4613
+ }
4614
+ function cancel() {
4615
+ if (disposed) return;
4616
+ cancelOwned();
4617
+ syncOwners();
4618
+ }
4619
+ function cancelOwned() {
4620
+ for (let i2 = _speech.queue.length - 1; i2 >= 0; i2--) {
4621
+ if (_speech.queue[i2].owner === owner) _speech.queue.splice(i2, 1);
4622
+ }
4623
+ owner.paused = false;
4624
+ if (_speech.active?.owner === owner) {
4625
+ _speech.active = null;
4626
+ if (synth.paused) synth.resume();
4627
+ synth.cancel();
4628
+ pump(synth);
4629
+ }
4266
4630
  }
4267
4631
  function dispose2() {
4632
+ if (disposed) return;
4633
+ cancelOwned();
4268
4634
  disposed = true;
4269
- if (interval !== null) {
4270
- clearInterval(interval);
4271
- interval = null;
4272
- }
4273
- synth.cancel();
4635
+ syncOwners();
4636
+ _speech.owners.delete(owner);
4274
4637
  }
4275
- return {
4276
- speaking,
4277
- paused,
4278
- speak,
4279
- pause: () => synth.pause(),
4280
- resume: () => synth.resume(),
4281
- cancel: () => synth.cancel(),
4282
- dispose: dispose2
4283
- };
4638
+ return { speaking, paused, speak, pause, resume, cancel, dispose: dispose2 };
4284
4639
  }
4285
4640
 
4286
4641
  // src/browser/gamepad.ts
@@ -4291,6 +4646,7 @@ function gamepad() {
4291
4646
  } };
4292
4647
  }
4293
4648
  let rafId = null;
4649
+ let disposed = false;
4294
4650
  function snapshot(pad) {
4295
4651
  return {
4296
4652
  index: pad.index,
@@ -4305,7 +4661,7 @@ function gamepad() {
4305
4661
  for (let i2 = 0; i2 < a2.length; i2++) {
4306
4662
  const pa = a2[i2];
4307
4663
  const pb = b2[i2];
4308
- if (pa.index !== pb.index || pa.connected !== pb.connected) return false;
4664
+ if (pa.index !== pb.index || pa.id !== pb.id || pa.connected !== pb.connected) return false;
4309
4665
  if (pa.buttons.length !== pb.buttons.length) return false;
4310
4666
  for (let j = 0; j < pa.buttons.length; j++) {
4311
4667
  if (pa.buttons[j].pressed !== pb.buttons[j].pressed) return false;
@@ -4318,15 +4674,19 @@ function gamepad() {
4318
4674
  }
4319
4675
  return true;
4320
4676
  }
4677
+ function publish() {
4678
+ const snap = Array.from(navigator.getGamepads()).filter((g2) => g2 !== null).map(snapshot);
4679
+ if (!equal(pads(), snap)) setPads(snap);
4680
+ return snap;
4681
+ }
4321
4682
  function poll() {
4322
- const raw = navigator.getGamepads();
4323
- const snap = Array.from(raw).filter((g2) => g2 !== null).map(snapshot);
4324
- const current = pads();
4325
- if (!equal(current, snap)) setPads(snap);
4326
- rafId = requestAnimationFrame(poll);
4683
+ rafId = null;
4684
+ if (disposed) return;
4685
+ publish();
4686
+ if (!disposed && rafId === null) rafId = requestAnimationFrame(poll);
4327
4687
  }
4328
4688
  function startPolling() {
4329
- if (rafId === null) poll();
4689
+ if (!disposed && rafId === null) poll();
4330
4690
  }
4331
4691
  function stopPolling() {
4332
4692
  if (rafId !== null) {
@@ -4336,15 +4696,14 @@ function gamepad() {
4336
4696
  }
4337
4697
  const onConnect = () => startPolling();
4338
4698
  const onDisconnect = () => {
4339
- const raw = navigator.getGamepads();
4340
- const hasAny = Array.from(raw).some((g2) => g2 !== null);
4341
- if (!hasAny) stopPolling();
4699
+ if (publish().length === 0) stopPolling();
4342
4700
  };
4343
4701
  window.addEventListener("gamepadconnected", onConnect);
4344
4702
  window.addEventListener("gamepaddisconnected", onDisconnect);
4345
4703
  const initial = Array.from(navigator.getGamepads()).some((g2) => g2 !== null);
4346
4704
  if (initial) startPolling();
4347
4705
  function dispose2() {
4706
+ disposed = true;
4348
4707
  stopPolling();
4349
4708
  window.removeEventListener("gamepadconnected", onConnect);
4350
4709
  window.removeEventListener("gamepaddisconnected", onDisconnect);
@@ -4358,8 +4717,7 @@ function pointerLock() {
4358
4717
  if (typeof document === "undefined") {
4359
4718
  return {
4360
4719
  locked,
4361
- request: () => {
4362
- },
4720
+ request: () => Promise.resolve(),
4363
4721
  exit: () => {
4364
4722
  },
4365
4723
  dispose: () => {
@@ -4371,8 +4729,14 @@ function pointerLock() {
4371
4729
  };
4372
4730
  document.addEventListener("pointerlockchange", handler);
4373
4731
  function request(element) {
4374
- if (typeof element.requestPointerLock === "function") {
4375
- element.requestPointerLock();
4732
+ if (typeof element.requestPointerLock !== "function") {
4733
+ return Promise.resolve();
4734
+ }
4735
+ try {
4736
+ const result = element.requestPointerLock();
4737
+ return Promise.resolve(result).then(() => void 0);
4738
+ } catch (err) {
4739
+ return Promise.reject(err);
4376
4740
  }
4377
4741
  }
4378
4742
  function exit() {
@@ -4475,25 +4839,46 @@ function imageLoader(src) {
4475
4839
  };
4476
4840
  }
4477
4841
  let current = null;
4842
+ let currentSettled = false;
4478
4843
  let disposed = false;
4844
+ let startToken = 0;
4845
+ function resetState() {
4846
+ batch(() => {
4847
+ setStatus("pending");
4848
+ setImage(null);
4849
+ setWidth(0);
4850
+ setHeight(0);
4851
+ });
4852
+ }
4853
+ function abandonCurrent() {
4854
+ if (!current) return;
4855
+ const prev = current;
4856
+ current = null;
4857
+ prev.onload = null;
4858
+ prev.onerror = null;
4859
+ if (!currentSettled) prev.src = "";
4860
+ }
4479
4861
  function start(url) {
4480
- if (current) {
4481
- current.onload = null;
4482
- current.onerror = null;
4483
- }
4484
- setStatus("pending");
4485
- setImage(null);
4862
+ const token = ++startToken;
4863
+ abandonCurrent();
4864
+ resetState();
4865
+ if (disposed || token !== startToken) return;
4486
4866
  const img2 = new Image();
4487
4867
  current = img2;
4868
+ currentSettled = false;
4488
4869
  img2.onload = () => {
4489
4870
  if (disposed || current !== img2) return;
4490
- setImage(img2);
4491
- setWidth(img2.naturalWidth);
4492
- setHeight(img2.naturalHeight);
4493
- setStatus("loaded");
4494
- };
4871
+ currentSettled = true;
4872
+ batch(() => {
4873
+ setImage(img2);
4874
+ setWidth(img2.naturalWidth);
4875
+ setHeight(img2.naturalHeight);
4876
+ setStatus("loaded");
4877
+ });
4878
+ };
4495
4879
  img2.onerror = () => {
4496
4880
  if (disposed || current !== img2) return;
4881
+ currentSettled = true;
4497
4882
  setStatus("error");
4498
4883
  };
4499
4884
  img2.src = url;
@@ -4508,16 +4893,15 @@ function imageLoader(src) {
4508
4893
  start(src);
4509
4894
  }
4510
4895
  function dispose2() {
4896
+ if (disposed) return;
4511
4897
  disposed = true;
4898
+ startToken++;
4512
4899
  if (srcEffectTeardown) {
4513
4900
  srcEffectTeardown();
4514
4901
  srcEffectTeardown = null;
4515
4902
  }
4516
- if (current) {
4517
- current.onload = null;
4518
- current.onerror = null;
4519
- current = null;
4520
- }
4903
+ abandonCurrent();
4904
+ resetState();
4521
4905
  }
4522
4906
  return { status, image, width, height, dispose: dispose2 };
4523
4907
  }
@@ -4525,13 +4909,25 @@ function imageLoader(src) {
4525
4909
  // src/patterns/machine.ts
4526
4910
  function machine(config) {
4527
4911
  const [state2, setState] = signal(config.initial);
4528
- const [context2, setContext] = signal(config.context || {});
4529
- const initialDef = config.states[config.initial];
4530
- if (initialDef?.entry) {
4531
- initialDef.entry(context2());
4912
+ const [context, setContext] = signal(config.context || {});
4913
+ let currentState = config.initial;
4914
+ let currentContext = context();
4915
+ const queue = [];
4916
+ let processing = false;
4917
+ function drain() {
4918
+ processing = true;
4919
+ try {
4920
+ while (queue.length > 0) {
4921
+ step(queue.shift());
4922
+ }
4923
+ } catch (err) {
4924
+ queue.length = 0;
4925
+ throw err;
4926
+ } finally {
4927
+ processing = false;
4928
+ }
4532
4929
  }
4533
- function send(event) {
4534
- const currentState = state2();
4930
+ function step(event) {
4535
4931
  const stateDef = config.states[currentState];
4536
4932
  if (!stateDef?.on) return;
4537
4933
  const transition2 = stateDef.on[event];
@@ -4546,38 +4942,61 @@ function machine(config) {
4546
4942
  guard = transition2.guard;
4547
4943
  action = transition2.action;
4548
4944
  }
4549
- const ctx = context2();
4945
+ const ctx = currentContext;
4550
4946
  if (guard && !guard(ctx)) return;
4551
4947
  if (stateDef.exit) {
4552
4948
  stateDef.exit(ctx);
4553
4949
  }
4950
+ let nextContext = ctx;
4554
4951
  if (action) {
4555
4952
  const rawPatch = action(ctx);
4556
- const next = { ...ctx, ...stripUnsafeKeys(rawPatch) };
4557
- setContext(next);
4953
+ nextContext = { ...ctx, ...stripUnsafeKeys(rawPatch) };
4558
4954
  }
4559
- setState(target);
4955
+ currentState = target;
4956
+ currentContext = nextContext;
4957
+ batch(() => {
4958
+ if (action) setContext(nextContext);
4959
+ setState(target);
4960
+ });
4560
4961
  const targetDef = config.states[target];
4561
4962
  if (targetDef?.entry) {
4562
- targetDef.entry(context2());
4963
+ targetDef.entry(currentContext);
4563
4964
  }
4564
4965
  }
4966
+ function send(event) {
4967
+ queue.push(event);
4968
+ if (processing) return;
4969
+ drain();
4970
+ }
4971
+ const initialDef = config.states[config.initial];
4972
+ if (initialDef?.entry) {
4973
+ processing = true;
4974
+ try {
4975
+ initialDef.entry(currentContext);
4976
+ } catch (err) {
4977
+ queue.length = 0;
4978
+ throw err;
4979
+ } finally {
4980
+ processing = false;
4981
+ }
4982
+ if (queue.length > 0) drain();
4983
+ }
4565
4984
  function matches(s2) {
4566
4985
  return state2() === s2;
4567
4986
  }
4568
4987
  function can(event) {
4569
- const currentState = state2();
4570
- const stateDef = config.states[currentState];
4988
+ const currentState2 = state2();
4989
+ const stateDef = config.states[currentState2];
4571
4990
  if (!stateDef?.on) return false;
4572
4991
  const transition2 = stateDef.on[event];
4573
4992
  if (!transition2) return false;
4574
4993
  if (typeof transition2 === "string") return true;
4575
4994
  if (transition2.guard) {
4576
- return transition2.guard(context2());
4995
+ return transition2.guard(context());
4577
4996
  }
4578
4997
  return true;
4579
4998
  }
4580
- return { state: state2, context: context2, send, matches, can };
4999
+ return { state: state2, context, send, matches, can };
4581
5000
  }
4582
5001
 
4583
5002
  // src/patterns/persist.ts
@@ -4950,6 +5369,9 @@ function optimisticList(initialValue) {
4950
5369
 
4951
5370
  // src/patterns/timeTravel.ts
4952
5371
  function timeline(initial, maxHistory = 100) {
5372
+ if (!Number.isSafeInteger(maxHistory) || maxHistory < 1) {
5373
+ throw new RangeError(`[timeline] maxHistory must be a positive safe integer, got ${maxHistory}`);
5374
+ }
4953
5375
  const [history2, setHistory] = signal([initial]);
4954
5376
  const [index, setIndex] = signal(0);
4955
5377
  const value = derived(() => history2()[index()]);
@@ -5004,6 +5426,120 @@ function timeline(initial, maxHistory = 100) {
5004
5426
  return { value, set, undo, redo, canUndo, canRedo, history: history2, index, reset, jumpTo, dispose: dispose2 };
5005
5427
  }
5006
5428
 
5429
+ // src/utils/adoption.ts
5430
+ var nativeThen = Promise.prototype.then;
5431
+ function probeNative(target, onRejected, onOpen) {
5432
+ let decided = false;
5433
+ const decide = (rejected) => {
5434
+ if (decided) return;
5435
+ decided = true;
5436
+ (rejected ? onRejected : onOpen)();
5437
+ };
5438
+ try {
5439
+ Reflect.apply(nativeThen, target, [void 0, () => decide(true)]);
5440
+ } catch {
5441
+ queueMicrotask(() => decide(true));
5442
+ return;
5443
+ }
5444
+ queueMicrotask(() => decide(false));
5445
+ }
5446
+ function adopt(value) {
5447
+ if (value === null || typeof value !== "object" && typeof value !== "function") return null;
5448
+ let then;
5449
+ try {
5450
+ then = value.then;
5451
+ } catch (error) {
5452
+ return {
5453
+ promise: Promise.reject(error),
5454
+ failedEarly: true,
5455
+ probe: (onRejected) => queueMicrotask(onRejected)
5456
+ };
5457
+ }
5458
+ if (typeof then !== "function") return null;
5459
+ let resolvePromise;
5460
+ let rejectPromise;
5461
+ const promise = new Promise((resolve, reject) => {
5462
+ resolvePromise = resolve;
5463
+ rejectPromise = reject;
5464
+ });
5465
+ let state2 = "invoking";
5466
+ let nativeTarget;
5467
+ const waiters = [];
5468
+ const transition2 = (next) => {
5469
+ state2 = next;
5470
+ if (next === "invoking") return;
5471
+ for (const waiter of waiters.splice(0)) waiter();
5472
+ };
5473
+ const fulfil = (result) => {
5474
+ resolvePromise(result);
5475
+ transition2("fulfilled");
5476
+ };
5477
+ const fail = (error) => {
5478
+ rejectPromise(error);
5479
+ transition2("rejected");
5480
+ };
5481
+ const follow = (result) => {
5482
+ if (result === promise) {
5483
+ fail(new TypeError("adopt: a thenable resolved to its own adoption"));
5484
+ return;
5485
+ }
5486
+ if (result === null || typeof result !== "object" && typeof result !== "function") {
5487
+ fulfil(result);
5488
+ return;
5489
+ }
5490
+ let nextThen;
5491
+ try {
5492
+ nextThen = result.then;
5493
+ } catch (error) {
5494
+ fail(error);
5495
+ return;
5496
+ }
5497
+ if (typeof nextThen !== "function") {
5498
+ fulfil(result);
5499
+ return;
5500
+ }
5501
+ invokeLater(result, nextThen);
5502
+ };
5503
+ const invokeLater = (target, targetThen) => {
5504
+ if (targetThen === nativeThen) {
5505
+ nativeTarget = target;
5506
+ transition2("native");
5507
+ } else {
5508
+ transition2("invoking");
5509
+ }
5510
+ queueMicrotask(() => {
5511
+ let done = false;
5512
+ const onResolve = (result) => {
5513
+ if (done) return;
5514
+ done = true;
5515
+ follow(result);
5516
+ };
5517
+ const onReject = (error) => {
5518
+ if (done) return;
5519
+ done = true;
5520
+ fail(error);
5521
+ };
5522
+ try {
5523
+ Reflect.apply(targetThen, target, [onResolve, onReject]);
5524
+ } catch (error) {
5525
+ onReject(error);
5526
+ }
5527
+ if (!done && state2 === "invoking") transition2("pending");
5528
+ });
5529
+ };
5530
+ invokeLater(value, then);
5531
+ const probe = (onRejected, onOpen) => {
5532
+ const decide = () => {
5533
+ if (state2 === "native") probeNative(nativeTarget, onRejected, onOpen);
5534
+ else (state2 === "rejected" ? onRejected : onOpen)();
5535
+ };
5536
+ if (state2 === "native") probeNative(nativeTarget, onRejected, onOpen);
5537
+ else if (state2 === "invoking") waiters.push(decide);
5538
+ else queueMicrotask(state2 === "rejected" ? onRejected : onOpen);
5539
+ };
5540
+ return { promise, failedEarly: false, probe };
5541
+ }
5542
+
5007
5543
  // src/patterns/globalStore.ts
5008
5544
  function deepClone(value) {
5009
5545
  if (typeof structuredClone === "function") {
@@ -5038,8 +5574,51 @@ function deepClone(value) {
5038
5574
  function globalStore(config) {
5039
5575
  const initialState = deepClone(config.state);
5040
5576
  const [getState, setState] = signal({ ...initialState });
5041
- const listeners = /* @__PURE__ */ new Set();
5577
+ const subscriptions = /* @__PURE__ */ new Map();
5042
5578
  const middlewares = config.middleware || [];
5579
+ function notifyListeners2(state2) {
5580
+ if (subscriptions.size === 0) return;
5581
+ for (const subscription of Array.from(subscriptions.values())) {
5582
+ if (!subscription.active) continue;
5583
+ try {
5584
+ subscription.callback(state2);
5585
+ } catch (err) {
5586
+ reportError(err, { phase: "event", name: "globalStore(subscribe)" });
5587
+ }
5588
+ }
5589
+ }
5590
+ const operations = [];
5591
+ let running = false;
5592
+ function perform(operation, rethrowOwn = true) {
5593
+ operations.push(operation);
5594
+ if (running) return;
5595
+ running = true;
5596
+ let callerError;
5597
+ let callerFailed = false;
5598
+ try {
5599
+ let first = true;
5600
+ let cursor = 0;
5601
+ while (cursor < operations.length) {
5602
+ const next = operations[cursor];
5603
+ operations[cursor++] = void 0;
5604
+ try {
5605
+ next();
5606
+ } catch (err) {
5607
+ if (first && rethrowOwn) {
5608
+ callerFailed = true;
5609
+ callerError = err;
5610
+ } else {
5611
+ reportError(err, { phase: "event", name: "globalStore(dispatch)" });
5612
+ }
5613
+ }
5614
+ first = false;
5615
+ }
5616
+ } finally {
5617
+ operations.length = 0;
5618
+ running = false;
5619
+ }
5620
+ if (callerFailed) throw callerError;
5621
+ }
5043
5622
  function dispatch(action, payload) {
5044
5623
  const actionFn = config.actions[action];
5045
5624
  if (!actionFn) throw new Error(`Unknown action: ${String(action)}`);
@@ -5048,38 +5627,96 @@ function globalStore(config) {
5048
5627
  const rawPatch = actionFn(current, payload);
5049
5628
  const patch = stripUnsafeKeys(rawPatch);
5050
5629
  setState({ ...current, ...patch });
5051
- const newState = getState();
5052
- for (const listener of listeners) {
5053
- listener(newState);
5054
- }
5630
+ notifyListeners2(getState());
5055
5631
  };
5056
- if (middlewares.length === 0) {
5057
- execute();
5058
- return;
5059
- }
5060
- let index = 0;
5061
- const next = () => {
5062
- if (index < middlewares.length) {
5063
- const mw = middlewares[index++];
5064
- mw(getState(), String(action), payload, next);
5065
- } else {
5632
+ perform(() => {
5633
+ if (middlewares.length === 0) {
5066
5634
  execute();
5635
+ return;
5067
5636
  }
5068
- };
5069
- next();
5637
+ const runFrom = (index) => {
5638
+ if (index >= middlewares.length) {
5639
+ execute();
5640
+ return;
5641
+ }
5642
+ let called = false;
5643
+ let failed = false;
5644
+ let synchronous = true;
5645
+ const next = () => {
5646
+ if (failed && !called) {
5647
+ if (DEV)
5648
+ devWarn(
5649
+ `globalStore: middleware ${index} next() called after it failed for "${String(action)}"; ignored.`
5650
+ );
5651
+ called = true;
5652
+ return;
5653
+ }
5654
+ if (called) {
5655
+ if (DEV)
5656
+ devWarn(
5657
+ `globalStore: middleware ${index} next() called more than once for "${String(action)}"; ignored.`
5658
+ );
5659
+ return;
5660
+ }
5661
+ called = true;
5662
+ if (synchronous) {
5663
+ runFrom(index + 1);
5664
+ return;
5665
+ }
5666
+ const proceed = () => perform(() => runFrom(index + 1), false);
5667
+ if (!adoption) {
5668
+ proceed();
5669
+ return;
5670
+ }
5671
+ adoption.probe(() => {
5672
+ failed = true;
5673
+ if (DEV)
5674
+ devWarn(
5675
+ `globalStore: middleware ${index} next() called after it failed for "${String(action)}"; ignored.`
5676
+ );
5677
+ }, proceed);
5678
+ };
5679
+ let adoption = null;
5680
+ try {
5681
+ adoption = adopt(middlewares[index](getState(), String(action), payload, next));
5682
+ } catch (err) {
5683
+ failed = true;
5684
+ throw err;
5685
+ } finally {
5686
+ synchronous = false;
5687
+ }
5688
+ if (adoption) {
5689
+ if (adoption.failedEarly) failed = true;
5690
+ adoption.promise.then(void 0, (err) => {
5691
+ failed = true;
5692
+ reportError(err, { phase: "async", name: "globalStore(middleware)" });
5693
+ });
5694
+ }
5695
+ };
5696
+ runFrom(0);
5697
+ });
5070
5698
  }
5071
5699
  function select3(selector) {
5072
5700
  return () => selector(getState());
5073
5701
  }
5074
5702
  function subscribe(callback) {
5075
- listeners.add(callback);
5076
- return () => listeners.delete(callback);
5703
+ let subscription = subscriptions.get(callback);
5704
+ if (!subscription) {
5705
+ subscription = { callback, active: true };
5706
+ subscriptions.set(callback, subscription);
5707
+ }
5708
+ const own = subscription;
5709
+ return () => {
5710
+ if (subscriptions.get(callback) !== own) return;
5711
+ own.active = false;
5712
+ subscriptions.delete(callback);
5713
+ };
5077
5714
  }
5078
5715
  function reset() {
5079
- setState({ ...initialState });
5080
- for (const listener of listeners) {
5081
- listener(getState());
5082
- }
5716
+ perform(() => {
5717
+ setState({ ...initialState });
5718
+ notifyListeners2(getState());
5719
+ });
5083
5720
  }
5084
5721
  return { getState, select: select3, dispatch, subscribe, reset };
5085
5722
  }
@@ -5203,7 +5840,7 @@ function validateProps(props, schema) {
5203
5840
  const errors = [];
5204
5841
  for (const [key, def] of Object.entries(schema)) {
5205
5842
  const propDef = typeof def === "function" ? { type: def } : def;
5206
- if (result[key] == null && propDef.default !== void 0) {
5843
+ if (result[key] === void 0 && propDef.default !== void 0) {
5207
5844
  result[key] = typeof propDef.default === "function" ? propDef.default() : propDef.default;
5208
5845
  }
5209
5846
  if (propDef.required && result[key] == null) {
@@ -5229,7 +5866,7 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
5229
5866
  for (const [key, def] of Object.entries(schema)) {
5230
5867
  if (typeof def === "function") continue;
5231
5868
  const fallback = def.default;
5232
- if (result[key] == null && fallback !== void 0) {
5869
+ if (result[key] === void 0 && fallback !== void 0) {
5233
5870
  result[key] = typeof fallback === "function" ? fallback() : fallback;
5234
5871
  }
5235
5872
  }
@@ -5255,81 +5892,6 @@ function createGuard(validator) {
5255
5892
  };
5256
5893
  }
5257
5894
 
5258
- // src/core/rendering/dispose.ts
5259
- var elementDisposers = /* @__PURE__ */ new WeakMap();
5260
- var MAX_DRAIN_TEARDOWNS = 1e4;
5261
- function reportDrainRunaway(label2, executed, remaining) {
5262
- if (typeof console === "undefined") return;
5263
- console.error(
5264
- `[SibuJS ${label2}] runaway cleanup: stopped after running ${executed} teardowns with ${remaining} still queued. Cleanup production did not stabilize before the teardown safety ceiling was reached \u2014 the remaining work was NOT run.`,
5265
- { executed, remaining }
5266
- );
5267
- }
5268
- var activeBindingCount = 0;
5269
- function registerDisposer(node, teardown) {
5270
- let disposers = elementDisposers.get(node);
5271
- if (!disposers) {
5272
- disposers = [];
5273
- elementDisposers.set(node, disposers);
5274
- }
5275
- disposers.push(teardown);
5276
- if (DEV) activeBindingCount++;
5277
- }
5278
- function dispose(node) {
5279
- const stack = [node];
5280
- const order = [];
5281
- while (stack.length > 0) {
5282
- const current = stack.pop();
5283
- order.push(current);
5284
- const children = Array.from(current.childNodes);
5285
- for (let i2 = 0; i2 < children.length; i2++) {
5286
- stack.push(children[i2]);
5287
- }
5288
- }
5289
- for (let i2 = order.length - 1; i2 >= 0; i2--) {
5290
- const current = order[i2];
5291
- if (elementDisposers.has(current)) {
5292
- let executed = 0;
5293
- let runaway = false;
5294
- while (!runaway) {
5295
- const pending = elementDisposers.get(current);
5296
- if (!pending || pending.length === 0) break;
5297
- const snapshot = pending.slice();
5298
- elementDisposers.delete(current);
5299
- if (DEV) activeBindingCount -= snapshot.length;
5300
- for (let i3 = 0; i3 < snapshot.length; i3++) {
5301
- if (executed >= MAX_DRAIN_TEARDOWNS) {
5302
- const rest = snapshot.slice(i3);
5303
- const added = elementDisposers.get(current);
5304
- elementDisposers.set(current, added ? rest.concat(added) : rest);
5305
- if (DEV) activeBindingCount += rest.length;
5306
- reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
5307
- runaway = true;
5308
- break;
5309
- }
5310
- executed++;
5311
- try {
5312
- snapshot[i3]();
5313
- } catch (err) {
5314
- reportError(err, { phase: "cleanup", name: "disposer" });
5315
- }
5316
- }
5317
- }
5318
- }
5319
- }
5320
- }
5321
- function replaceChildrenSafely(parent, ...next) {
5322
- for (let i2 = 0; i2 < next.length; i2++) {
5323
- const node = next[i2];
5324
- node.parentNode?.removeChild(node);
5325
- }
5326
- const current = Array.from(parent.childNodes);
5327
- for (let i2 = 0; i2 < current.length; i2++) {
5328
- dispose(current[i2]);
5329
- }
5330
- parent.replaceChildren(...next);
5331
- }
5332
-
5333
5895
  // src/ui/transition.ts
5334
5896
  function transition(element, options = {}) {
5335
5897
  const {
@@ -5357,6 +5919,15 @@ function transition(element, options = {}) {
5357
5919
  resolvePrev();
5358
5920
  }
5359
5921
  }
5922
+ function complete(callback, name, resolve) {
5923
+ try {
5924
+ callback?.();
5925
+ } catch (error) {
5926
+ reportError(error, { phase: "async", name, node: element });
5927
+ } finally {
5928
+ resolve();
5929
+ }
5930
+ }
5360
5931
  function enter() {
5361
5932
  return new Promise((resolve) => {
5362
5933
  cancelPending();
@@ -5369,8 +5940,7 @@ function transition(element, options = {}) {
5369
5940
  activeTimer = null;
5370
5941
  pendingResolve = null;
5371
5942
  if (enterClass) element.classList.remove(enterClass);
5372
- onEnterDone?.();
5373
- resolve();
5943
+ complete(onEnterDone, "transition(onEnterDone)", resolve);
5374
5944
  };
5375
5945
  if (duration > 0) {
5376
5946
  pendingResolve = resolve;
@@ -5391,8 +5961,7 @@ function transition(element, options = {}) {
5391
5961
  activeTimer = null;
5392
5962
  pendingResolve = null;
5393
5963
  if (leaveClass) element.classList.remove(leaveClass);
5394
- onLeaveDone?.();
5395
- resolve();
5964
+ complete(onLeaveDone, "transition(onLeaveDone)", resolve);
5396
5965
  };
5397
5966
  if (duration > 0) {
5398
5967
  pendingResolve = resolve;
@@ -5595,22 +6164,54 @@ async function sequence(steps) {
5595
6164
  }
5596
6165
  }
5597
6166
 
6167
+ // src/utils/adoptThenable.ts
6168
+ function adoptThenable(value) {
6169
+ if (value === null || typeof value !== "object" && typeof value !== "function") return null;
6170
+ let then;
6171
+ try {
6172
+ then = value.then;
6173
+ } catch (error) {
6174
+ return Promise.reject(error);
6175
+ }
6176
+ if (typeof then !== "function") return null;
6177
+ return new Promise((resolve, reject) => {
6178
+ queueMicrotask(() => {
6179
+ try {
6180
+ Reflect.apply(then, value, [resolve, reject]);
6181
+ } catch (error) {
6182
+ reject(error);
6183
+ }
6184
+ });
6185
+ });
6186
+ }
6187
+
5598
6188
  // src/ui/TransitionGroup.ts
5599
6189
  function TransitionGroup(options) {
5600
6190
  const [elements, setElements] = signal([]);
5601
6191
  const positions = /* @__PURE__ */ new Map();
6192
+ function runCallback2(name, callback, el) {
6193
+ const report = (error) => reportError(error, { phase: "async", name: `TransitionGroup.${name}`, node: el });
6194
+ try {
6195
+ adoptThenable(callback(el))?.then(void 0, report);
6196
+ } catch (error) {
6197
+ report(error);
6198
+ }
6199
+ }
5602
6200
  function add(el) {
5603
6201
  setElements((prev) => [...prev, el]);
5604
- if (options.enter) {
5605
- options.enter(el);
5606
- }
6202
+ if (options.enter) runCallback2("enter", options.enter, el);
5607
6203
  }
5608
6204
  async function remove(el) {
5609
- if (options.leave) {
5610
- await options.leave(el);
6205
+ try {
6206
+ if (options.leave) {
6207
+ await adoptThenable(options.leave(el));
6208
+ }
6209
+ } catch (error) {
6210
+ reportError(error, { phase: "async", name: "TransitionGroup.leave", node: el });
6211
+ } finally {
6212
+ positions.delete(el);
6213
+ setElements((prev) => prev.filter((e) => e !== el));
5611
6214
  }
5612
- positions.delete(el);
5613
- setElements((prev) => prev.filter((e) => e !== el));
5614
6215
  }
5615
6216
  function track4(newElements) {
5616
6217
  const oldPositions = /* @__PURE__ */ new Map();
@@ -5622,17 +6223,13 @@ function TransitionGroup(options) {
5622
6223
  const currentSet = new Set(elements());
5623
6224
  for (const el of newElements) {
5624
6225
  if (!currentSet.has(el)) {
5625
- if (options.enter) {
5626
- options.enter(el);
5627
- }
6226
+ if (options.enter) runCallback2("enter", options.enter, el);
5628
6227
  }
5629
6228
  }
5630
6229
  const newSet = new Set(newElements);
5631
6230
  for (const el of elements()) {
5632
6231
  if (!newSet.has(el)) {
5633
- if (options.leave) {
5634
- options.leave(el);
5635
- }
6232
+ if (options.leave) runCallback2("leave", options.leave, el);
5636
6233
  }
5637
6234
  }
5638
6235
  setElements(newElements);
@@ -5642,7 +6239,7 @@ function TransitionGroup(options) {
5642
6239
  if (oldRect && typeof el.getBoundingClientRect === "function") {
5643
6240
  const newRect = el.getBoundingClientRect();
5644
6241
  if (oldRect.left !== newRect.left || oldRect.top !== newRect.top) {
5645
- options.move(el);
6242
+ runCallback2("move", options.move, el);
5646
6243
  }
5647
6244
  }
5648
6245
  }
@@ -5693,9 +6290,13 @@ function springSignal(initial, options) {
5693
6290
  let target = initial;
5694
6291
  let rafId = null;
5695
6292
  let lastTime = 0;
6293
+ let disposed = false;
6294
+ const canAnimate = () => typeof requestAnimationFrame === "function";
5696
6295
  const REF_DT_MS = 1e3 / 60;
5697
6296
  const MAX_STEP_RATIO = 4;
5698
6297
  function tick(now) {
6298
+ rafId = null;
6299
+ if (disposed) return;
5699
6300
  if (lastTime === 0) lastTime = now;
5700
6301
  const rawDt = now - lastTime;
5701
6302
  lastTime = now;
@@ -5708,23 +6309,27 @@ function springSignal(initial, options) {
5708
6309
  if (Math.abs(current - target) < precision && Math.abs(velocity) < precision) {
5709
6310
  current = target;
5710
6311
  velocity = 0;
5711
- rafId = null;
5712
6312
  lastTime = 0;
5713
6313
  setValue(current);
5714
6314
  return;
5715
6315
  }
5716
6316
  setValue(current);
6317
+ if (disposed || rafId !== null) return;
5717
6318
  rafId = requestAnimationFrame(tick);
5718
6319
  }
6320
+ function cancelFrame() {
6321
+ if (rafId !== null) {
6322
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
6323
+ rafId = null;
6324
+ }
6325
+ }
5719
6326
  function set(newTarget) {
6327
+ if (disposed) return;
5720
6328
  target = newTarget;
5721
- if (prefersReducedMotion()) {
6329
+ if (!canAnimate() || prefersReducedMotion()) {
5722
6330
  current = newTarget;
5723
6331
  velocity = 0;
5724
- if (rafId !== null) {
5725
- cancelAnimationFrame(rafId);
5726
- rafId = null;
5727
- }
6332
+ cancelFrame();
5728
6333
  lastTime = 0;
5729
6334
  setValue(current);
5730
6335
  return;
@@ -5735,15 +6340,20 @@ function springSignal(initial, options) {
5735
6340
  }
5736
6341
  }
5737
6342
  function dispose2() {
5738
- if (rafId !== null) {
5739
- cancelAnimationFrame(rafId);
5740
- rafId = null;
5741
- }
6343
+ disposed = true;
6344
+ cancelFrame();
5742
6345
  lastTime = 0;
5743
6346
  }
5744
6347
  return [value, set, dispose2];
5745
6348
  }
5746
6349
 
6350
+ // src/reactivity/domBinding.ts
6351
+ function domBinding(commit, ownerNode) {
6352
+ if (isSSR()) return () => {
6353
+ };
6354
+ return reactiveBinding2(commit, ownerNode);
6355
+ }
6356
+
5747
6357
  // src/ui/form.ts
5748
6358
  function required(message = "This field is required") {
5749
6359
  return (value) => {
@@ -5771,10 +6381,14 @@ function maxLength(max2, message) {
5771
6381
  }
5772
6382
  function matchesPattern(regex, message = "Invalid format") {
5773
6383
  return (value) => {
5774
- if (value && !regex.test(value)) {
5775
- return message;
6384
+ if (!value) return null;
6385
+ const savedLastIndex = regex.lastIndex;
6386
+ regex.lastIndex = 0;
6387
+ try {
6388
+ return regex.test(value) ? null : message;
6389
+ } finally {
6390
+ regex.lastIndex = savedLastIndex;
5776
6391
  }
5777
- return null;
5778
6392
  };
5779
6393
  }
5780
6394
  function email(message = "Invalid email address") {
@@ -5823,13 +6437,13 @@ function bindField(field, extras) {
5823
6437
  const mergedOn = extraOn && typeof extraOn === "object" ? { ...extraOn, ...fieldOn } : fieldOn;
5824
6438
  const onElement = (el) => {
5825
6439
  if (el instanceof HTMLSelectElement && el.multiple) {
5826
- const stop2 = effect(() => {
6440
+ const stop2 = domBinding(() => {
5827
6441
  const v = field.value();
5828
6442
  const selected = Array.isArray(v) ? v.map(String) : [];
5829
6443
  for (const opt of Array.from(el.options)) {
5830
6444
  opt.selected = selected.includes(opt.value);
5831
6445
  }
5832
- });
6446
+ }, el);
5833
6447
  registerDisposer(el, stop2);
5834
6448
  }
5835
6449
  if (typeof extraOnElement === "function") {
@@ -5849,6 +6463,7 @@ function form(config) {
5849
6463
  const fieldEntries = Object.entries(config);
5850
6464
  const fieldMap = {};
5851
6465
  const [manualErrors, setManualErrors] = signal({});
6466
+ const fieldErrors = [];
5852
6467
  for (const [name, cfg] of fieldEntries) {
5853
6468
  const [value, setValue] = signal(cfg.initial);
5854
6469
  const [isTouched, setTouched] = signal(false);
@@ -5863,6 +6478,7 @@ function form(config) {
5863
6478
  }
5864
6479
  return null;
5865
6480
  });
6481
+ fieldErrors.push(error);
5866
6482
  const wrappedSet = (next) => {
5867
6483
  setValue(next);
5868
6484
  setManualErrors((prev) => {
@@ -5939,12 +6555,22 @@ function form(config) {
5939
6555
  field.touch();
5940
6556
  }
5941
6557
  if (isValid()) {
5942
- const result = onSubmit(values());
5943
- if (result && typeof result.then === "function") {
6558
+ const report = (error) => reportError(error, { phase: "async", name: "form.handleSubmit" });
6559
+ let pending;
6560
+ try {
6561
+ pending = adoptThenable(onSubmit(values()));
6562
+ } catch (error) {
6563
+ report(error);
6564
+ return;
6565
+ }
6566
+ if (pending) {
5944
6567
  setSubmitting(true);
5945
- result.then(
6568
+ pending.then(
5946
6569
  () => setSubmitting(false),
5947
- () => setSubmitting(false)
6570
+ (error) => {
6571
+ setSubmitting(false);
6572
+ report(error);
6573
+ }
5948
6574
  );
5949
6575
  }
5950
6576
  }
@@ -5958,6 +6584,17 @@ function form(config) {
5958
6584
  function setError(field, message) {
5959
6585
  setManualErrors((prev) => ({ ...prev, [field]: message }));
5960
6586
  }
6587
+ let disposed = false;
6588
+ function dispose2() {
6589
+ if (disposed) return;
6590
+ disposed = true;
6591
+ errors.dispose();
6592
+ isValid.dispose();
6593
+ isDirty.dispose();
6594
+ touchedState.dispose();
6595
+ values.dispose();
6596
+ for (const error of fieldErrors) error.dispose();
6597
+ }
5961
6598
  return {
5962
6599
  fields: fieldMap,
5963
6600
  errors,
@@ -5968,7 +6605,8 @@ function form(config) {
5968
6605
  values,
5969
6606
  handleSubmit,
5970
6607
  reset,
5971
- setError
6608
+ setError,
6609
+ dispose: dispose2
5972
6610
  };
5973
6611
  }
5974
6612
 
@@ -6012,7 +6650,7 @@ function VirtualList(props) {
6012
6650
  content.appendChild(itemEl);
6013
6651
  }
6014
6652
  };
6015
- registerDisposer(container, effect(update));
6653
+ registerDisposer(container, domBinding(update, container));
6016
6654
  return container;
6017
6655
  }
6018
6656
 
@@ -6022,11 +6660,14 @@ function intersection(options) {
6022
6660
  const [ratio, setRatio] = signal(0);
6023
6661
  let observer = null;
6024
6662
  let currentElement = null;
6663
+ let generation = 0;
6025
6664
  function observe(element) {
6026
6665
  if (typeof IntersectionObserver === "undefined") return;
6027
6666
  unobserve();
6028
6667
  currentElement = element;
6668
+ const observation = ++generation;
6029
6669
  observer = new IntersectionObserver((entries) => {
6670
+ if (observation !== generation) return;
6030
6671
  const entry = entries[0];
6031
6672
  if (entry) {
6032
6673
  setIsIntersecting(entry.isIntersecting);
@@ -6036,8 +6677,10 @@ function intersection(options) {
6036
6677
  observer.observe(element);
6037
6678
  }
6038
6679
  function unobserve() {
6680
+ generation++;
6039
6681
  if (observer) {
6040
6682
  if (currentElement) observer.unobserve(currentElement);
6683
+ observer.takeRecords();
6041
6684
  observer.disconnect();
6042
6685
  observer = null;
6043
6686
  currentElement = null;
@@ -6056,17 +6699,26 @@ function lazyLoad(element, loader, options) {
6056
6699
  return () => {
6057
6700
  };
6058
6701
  }
6702
+ let done = false;
6703
+ const stop2 = () => {
6704
+ done = true;
6705
+ observer.takeRecords();
6706
+ observer.disconnect();
6707
+ };
6059
6708
  const observer = new IntersectionObserver((entries) => {
6709
+ if (done) return;
6060
6710
  for (const entry of entries) {
6061
6711
  if (entry.isIntersecting) {
6712
+ stop2();
6062
6713
  loader();
6063
- observer.disconnect();
6064
6714
  break;
6065
6715
  }
6066
6716
  }
6067
6717
  }, options);
6068
6718
  observer.observe(element);
6069
- return () => observer.disconnect();
6719
+ return () => {
6720
+ if (!done) stop2();
6721
+ };
6070
6722
  }
6071
6723
 
6072
6724
  // src/ui/inputMask.ts
@@ -6215,6 +6867,7 @@ function focus() {
6215
6867
  const dispose2 = () => {
6216
6868
  if (disposed) return;
6217
6869
  disposed = true;
6870
+ unregisterDisposer(element, dispose2);
6218
6871
  element.removeEventListener("focus", onFocus);
6219
6872
  element.removeEventListener("blur", onBlur);
6220
6873
  if (currentElement === element) currentElement = null;
@@ -6283,14 +6936,19 @@ function FocusTrap(nodes, options = {}) {
6283
6936
  }
6284
6937
  };
6285
6938
  container.addEventListener("keydown", onTrapKeydown);
6939
+ let disposed = false;
6286
6940
  if (options.autoFocus !== false) {
6287
6941
  queueMicrotask(() => {
6942
+ if (disposed || !container.isConnected) return;
6288
6943
  const first = getFocusable()[0];
6289
6944
  first?.focus();
6290
6945
  });
6291
6946
  }
6292
6947
  let trapObserver = null;
6293
6948
  function restoreFocusAndCleanup() {
6949
+ if (disposed) return;
6950
+ disposed = true;
6951
+ unregisterDisposer(container, restoreFocusAndCleanup);
6294
6952
  if (options.restoreFocus !== false) previouslyFocused?.focus();
6295
6953
  container.removeEventListener("keydown", onTrapKeydown);
6296
6954
  if (trapObserver) {
@@ -6305,29 +6963,45 @@ function FocusTrap(nodes, options = {}) {
6305
6963
  }
6306
6964
  });
6307
6965
  queueMicrotask(() => {
6308
- if (container.isConnected) {
6309
- trapObserver.observe(container, { childList: true, subtree: true });
6310
- }
6966
+ const observer = trapObserver;
6967
+ if (disposed || !container.isConnected || !observer) return;
6968
+ observer.observe(container, { childList: true, subtree: true });
6311
6969
  });
6312
6970
  }
6313
6971
  registerDisposer(container, restoreFocusAndCleanup);
6314
6972
  return container;
6315
6973
  }
6974
+ function isApplePlatform() {
6975
+ if (typeof navigator === "undefined") return false;
6976
+ const nav2 = navigator;
6977
+ return /mac|iphone|ipad|ipod/i.test(nav2.userAgentData?.platform || nav2.platform || "");
6978
+ }
6316
6979
  function hotkey(combo, handler, options = {}) {
6317
6980
  let key = combo;
6318
6981
  let needCtrl = options.ctrl ?? false;
6319
6982
  let needShift = options.shift ?? false;
6320
6983
  let needAlt = options.alt ?? false;
6321
6984
  let needMeta = options.meta ?? false;
6322
- if (combo.includes("+")) {
6323
- const parts = combo.toLowerCase().split("+");
6985
+ if (combo === "+") {
6986
+ key = "+";
6987
+ } else if (combo.includes("+")) {
6988
+ const literalPlus = combo.endsWith("++");
6989
+ const parts = (literalPlus ? combo.slice(0, -2) : combo).toLowerCase().split("+");
6990
+ if (literalPlus) parts.push("+");
6324
6991
  key = parts[parts.length - 1];
6992
+ if (key === "") throw new Error(`hotkey("${combo}"): missing key`);
6325
6993
  for (let i2 = 0; i2 < parts.length - 1; i2++) {
6326
6994
  const mod = parts[i2];
6327
6995
  if (mod === "ctrl" || mod === "control") needCtrl = true;
6328
6996
  else if (mod === "shift") needShift = true;
6329
- else if (mod === "alt") needAlt = true;
6997
+ else if (mod === "alt" || mod === "option") needAlt = true;
6330
6998
  else if (mod === "meta" || mod === "cmd" || mod === "command") needMeta = true;
6999
+ else if (mod === "mod") {
7000
+ if (isApplePlatform()) needMeta = true;
7001
+ else needCtrl = true;
7002
+ } else {
7003
+ throw new Error(`hotkey("${combo}"): unknown modifier "${mod}"`);
7004
+ }
6331
7005
  }
6332
7006
  }
6333
7007
  const listener = (e) => {
@@ -6785,6 +7459,8 @@ function removeGlobalListenerIfIdle() {
6785
7459
  function dialog() {
6786
7460
  const [isOpen, setIsOpen] = signal(false);
6787
7461
  const entry = { close: () => close() };
7462
+ let opened = false;
7463
+ let disposed = false;
6788
7464
  function pushOnStack() {
6789
7465
  if (dialogStack.indexOf(entry) !== -1) return;
6790
7466
  dialogStack.push(entry);
@@ -6796,23 +7472,27 @@ function dialog() {
6796
7472
  removeGlobalListenerIfIdle();
6797
7473
  }
6798
7474
  function open() {
6799
- if (isOpen()) return;
6800
- setIsOpen(true);
7475
+ if (disposed || opened) return;
7476
+ opened = true;
6801
7477
  pushOnStack();
7478
+ setIsOpen(true);
6802
7479
  }
6803
7480
  function close() {
6804
- if (!isOpen()) {
7481
+ if (!opened) {
6805
7482
  removeFromStack();
6806
7483
  return;
6807
7484
  }
6808
- setIsOpen(false);
7485
+ opened = false;
6809
7486
  removeFromStack();
7487
+ setIsOpen(false);
6810
7488
  }
6811
7489
  function toggle() {
6812
- if (isOpen()) close();
7490
+ if (opened) close();
6813
7491
  else open();
6814
7492
  }
6815
7493
  function dispose2() {
7494
+ disposed = true;
7495
+ opened = false;
6816
7496
  removeFromStack();
6817
7497
  setIsOpen(false);
6818
7498
  }
@@ -7017,32 +7697,49 @@ function pagination(options) {
7017
7697
  function eventBus() {
7018
7698
  const listeners = /* @__PURE__ */ new Map();
7019
7699
  function on(event, handler) {
7020
- let set = listeners.get(event);
7021
- if (!set) {
7022
- set = /* @__PURE__ */ new Set();
7023
- listeners.set(event, set);
7700
+ let subs = listeners.get(event);
7701
+ if (!subs) {
7702
+ subs = /* @__PURE__ */ new Map();
7703
+ listeners.set(event, subs);
7704
+ }
7705
+ let subscription = subs.get(handler);
7706
+ if (!subscription) {
7707
+ subscription = { handler, active: true };
7708
+ subs.set(handler, subscription);
7024
7709
  }
7025
- set.add(handler);
7026
- return () => off(event, handler);
7710
+ const own = subscription;
7711
+ return () => {
7712
+ if (listeners.get(event)?.get(handler) !== own) return;
7713
+ off(event, handler);
7714
+ };
7027
7715
  }
7028
7716
  function emit(event, data2) {
7029
- const set = listeners.get(event);
7030
- if (set) {
7031
- for (const handler of set) {
7032
- handler(data2);
7717
+ const subs = listeners.get(event);
7718
+ if (!subs || subs.size === 0) return;
7719
+ for (const subscription of Array.from(subs.values())) {
7720
+ if (!subscription.active) continue;
7721
+ try {
7722
+ subscription.handler(data2);
7723
+ } catch (err) {
7724
+ reportError(err, { phase: "event", name: `eventBus(${String(event)})` });
7033
7725
  }
7034
7726
  }
7035
7727
  }
7036
7728
  function off(event, handler) {
7037
- const set = listeners.get(event);
7038
- if (set) {
7039
- set.delete(handler);
7040
- if (set.size === 0) {
7041
- listeners.delete(event);
7042
- }
7729
+ const subs = listeners.get(event);
7730
+ if (!subs) return;
7731
+ const subscription = subs.get(handler);
7732
+ if (!subscription) return;
7733
+ subscription.active = false;
7734
+ subs.delete(handler);
7735
+ if (subs.size === 0) {
7736
+ listeners.delete(event);
7043
7737
  }
7044
7738
  }
7045
7739
  function clear() {
7740
+ for (const subs of listeners.values()) {
7741
+ for (const subscription of subs.values()) subscription.active = false;
7742
+ }
7046
7743
  listeners.clear();
7047
7744
  }
7048
7745
  return { on, emit, off, clear };
@@ -7072,28 +7769,34 @@ function insertTask(task) {
7072
7769
  }
7073
7770
  taskQueue.splice(i2, 0, task);
7074
7771
  }
7772
+ function runTask(callback) {
7773
+ try {
7774
+ callback();
7775
+ } catch (err) {
7776
+ reportError(err, { phase: "scheduler", name: "scheduleUpdate" });
7777
+ }
7778
+ }
7075
7779
  function processQueue() {
7076
7780
  if (_sched.isProcessing || taskQueue.length === 0) return;
7077
7781
  _sched.isProcessing = true;
7078
7782
  const startTime = performance.now();
7079
7783
  const timeSlice = 5;
7080
- while (taskQueue.length > 0) {
7081
- const task = taskQueue[0];
7082
- if (task.cancelled) {
7784
+ try {
7785
+ while (taskQueue.length > 0) {
7786
+ const task = taskQueue[0];
7787
+ if (task.cancelled) {
7788
+ taskQueue.shift();
7789
+ continue;
7790
+ }
7791
+ if (task.priority > Priority.IMMEDIATE && performance.now() - startTime > timeSlice) {
7792
+ break;
7793
+ }
7083
7794
  taskQueue.shift();
7084
- continue;
7085
- }
7086
- if (task.priority > Priority.IMMEDIATE && performance.now() - startTime > timeSlice) {
7087
- break;
7088
- }
7089
- taskQueue.shift();
7090
- try {
7091
- task.callback();
7092
- } catch (e) {
7093
- console.error("[Scheduler] Task error:", e);
7795
+ runTask(task.callback);
7094
7796
  }
7797
+ } finally {
7798
+ _sched.isProcessing = false;
7095
7799
  }
7096
- _sched.isProcessing = false;
7097
7800
  if (taskQueue.length > 0) {
7098
7801
  scheduleFrame();
7099
7802
  }
@@ -7161,11 +7864,7 @@ function scheduleUpdate(priority, callback) {
7161
7864
  cancelled: false
7162
7865
  };
7163
7866
  if (priority === Priority.IMMEDIATE) {
7164
- try {
7165
- callback();
7166
- } catch (e) {
7167
- console.error("[Scheduler] Immediate task error:", e);
7168
- }
7867
+ runTask(callback);
7169
7868
  return () => {
7170
7869
  };
7171
7870
  }
@@ -7185,14 +7884,15 @@ function flushScheduler() {
7185
7884
  _sched.scheduledHandle = null;
7186
7885
  _sched.scheduledKind = null;
7187
7886
  _sched.microtaskScheduled = false;
7188
- while (taskQueue.length > 0) {
7189
- const task = taskQueue.shift();
7190
- if (!task) break;
7191
- if (!task.cancelled) {
7192
- task.callback();
7887
+ try {
7888
+ while (taskQueue.length > 0) {
7889
+ const task = taskQueue.shift();
7890
+ if (!task) break;
7891
+ if (!task.cancelled) runTask(task.callback);
7193
7892
  }
7893
+ } finally {
7894
+ _sched.isProcessing = false;
7194
7895
  }
7195
- _sched.isProcessing = false;
7196
7896
  }
7197
7897
  function pendingTasks() {
7198
7898
  return taskQueue.filter((t) => !t.cancelled).length;
@@ -7328,11 +8028,16 @@ function getDOMPool() {
7328
8028
  return _defaultPool;
7329
8029
  }
7330
8030
  var preloadedResources = /* @__PURE__ */ new Set();
8031
+ function hintKey(rel, as, crossorigin, href) {
8032
+ return `${rel}\0${as}\0${crossorigin}\0${href}`;
8033
+ }
7331
8034
  function preloadResource(url, type = "fetch") {
7332
8035
  const safe = sanitizeUrl(url);
7333
8036
  if (!safe) return;
7334
- if (preloadedResources.has(safe)) return;
7335
- preloadedResources.add(safe);
8037
+ const as = type === "script" || type === "style" || type === "image" ? type : "fetch";
8038
+ const key = hintKey("preload", as, as === "fetch" ? "anonymous" : "", safe);
8039
+ if (preloadedResources.has(key)) return;
8040
+ preloadedResources.add(key);
7336
8041
  const link2 = document.createElement("link");
7337
8042
  link2.rel = "preload";
7338
8043
  link2.href = safe;
@@ -7355,8 +8060,9 @@ function preloadResource(url, type = "fetch") {
7355
8060
  function prefetch(url) {
7356
8061
  const safe = sanitizeUrl(url);
7357
8062
  if (!safe) return;
7358
- if (preloadedResources.has(safe)) return;
7359
- preloadedResources.add(safe);
8063
+ const key = hintKey("prefetch", "", "", safe);
8064
+ if (preloadedResources.has(key)) return;
8065
+ preloadedResources.add(key);
7360
8066
  const link2 = document.createElement("link");
7361
8067
  link2.rel = "prefetch";
7362
8068
  link2.href = safe;
@@ -7821,7 +8527,7 @@ function renderToDocument(component, options = {}) {
7821
8527
  <head>
7822
8528
  <meta charset="UTF-8" />
7823
8529
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7824
- ${options.title ? `<title>${escapeHtml(options.title)}</title>` : ""}
8530
+ ${options.title != null ? `<title>${escapeHtml(options.title)}</title>` : ""}
7825
8531
  ${metaTags}
7826
8532
  ${linkTags}
7827
8533
  ${options.headExtra || ""}
@@ -7996,16 +8702,32 @@ function ssrSuspense(props) {
7996
8702
  const timeoutPromise = new Promise((_, reject) => {
7997
8703
  timer = setTimeout(() => reject(new Error(`[SibuJS SSR] ssrSuspense timed out after ${timeoutMs}ms`)), timeoutMs);
7998
8704
  });
7999
- const raced = Promise.race([props.content(), timeoutPromise]);
8705
+ let contentPromise;
8706
+ try {
8707
+ contentPromise = Promise.resolve(props.content());
8708
+ } catch (err) {
8709
+ contentPromise = Promise.reject(err);
8710
+ }
8711
+ const toFallback = (err) => {
8712
+ if (DEV) console.warn("[SibuJS SSR] ssrSuspense rejected:", err);
8713
+ return { id, html: fallbackHtml };
8714
+ };
8715
+ const clearTimer = () => {
8716
+ if (timer !== void 0) clearTimeout(timer);
8717
+ };
8718
+ const raced = Promise.race([contentPromise, timeoutPromise]);
8000
8719
  const promise = raced.then(
8001
8720
  (resolvedEl) => {
8002
- if (timer) clearTimeout(timer);
8003
- return { id, html: renderToString(resolvedEl) };
8721
+ clearTimer();
8722
+ try {
8723
+ return { id, html: renderToString(resolvedEl) };
8724
+ } catch (err) {
8725
+ return toFallback(err);
8726
+ }
8004
8727
  },
8005
8728
  (err) => {
8006
- if (timer) clearTimeout(timer);
8007
- if (DEV) console.warn("[SibuJS SSR] ssrSuspense rejected:", err);
8008
- return { id, html: fallbackHtml };
8729
+ clearTimer();
8730
+ return toFallback(err);
8009
8731
  }
8010
8732
  );
8011
8733
  promise.catch(noop);
@@ -8094,65 +8816,92 @@ function block(factory) {
8094
8816
  }
8095
8817
 
8096
8818
  // src/performance/normalize.ts
8819
+ function dict(source2) {
8820
+ const out = /* @__PURE__ */ Object.create(null);
8821
+ if (source2) {
8822
+ for (const key of Object.keys(source2)) defineOwn(out, key, source2[key]);
8823
+ }
8824
+ return out;
8825
+ }
8826
+ function ownGet(obj, key) {
8827
+ return obj !== void 0 && Object.hasOwn(obj, key) ? obj[key] : void 0;
8828
+ }
8829
+ function defineOwn(obj, key, value) {
8830
+ Object.defineProperty(obj, key, { value, writable: true, enumerable: true, configurable: true });
8831
+ }
8832
+ function readEntityId(entity, idKey, typeName) {
8833
+ const raw = entity !== null && typeof entity === "object" && Object.hasOwn(entity, idKey) ? entity[idKey] : void 0;
8834
+ if (typeof raw === "string") return raw;
8835
+ if (typeof raw === "number" && Number.isFinite(raw)) return String(raw);
8836
+ throw new TypeError(
8837
+ `[normalize] "${typeName}" entity has no valid "${idKey}" (expected a string or finite number, got ${raw === null ? "null" : typeof raw})`
8838
+ );
8839
+ }
8097
8840
  function normalizedStore(schema) {
8098
8841
  const idKey = schema.idKey || "id";
8099
8842
  const [getState, setState] = signal({
8100
8843
  ids: [],
8101
- entities: {}
8844
+ entities: dict()
8102
8845
  });
8103
- const idOf = (entity) => String(entity[idKey]);
8846
+ const idOf = (entity) => readEntityId(entity, idKey, schema.name);
8104
8847
  function add(entity) {
8105
8848
  const id = idOf(entity);
8106
8849
  setState((prev) => {
8107
- const ids = prev.ids.includes(id) ? prev.ids : [...prev.ids, id];
8108
- return {
8109
- ids,
8110
- entities: { ...prev.entities, [id]: entity }
8111
- };
8850
+ const ids = Object.hasOwn(prev.entities, id) ? prev.ids : [...prev.ids, id];
8851
+ const entities = dict(prev.entities);
8852
+ defineOwn(entities, id, entity);
8853
+ return { ids, entities };
8112
8854
  });
8113
8855
  }
8114
8856
  function addMany(entities) {
8857
+ const ids = entities.map(idOf);
8115
8858
  setState((prev) => {
8116
8859
  const nextIds = [...prev.ids];
8117
- const nextEntities = { ...prev.entities };
8118
- for (const entity of entities) {
8119
- const id = idOf(entity);
8120
- if (!nextIds.includes(id)) {
8860
+ const nextEntities = dict(prev.entities);
8861
+ entities.forEach((entity, i2) => {
8862
+ const id = ids[i2];
8863
+ if (!Object.hasOwn(nextEntities, id)) {
8121
8864
  nextIds.push(id);
8122
8865
  }
8123
- nextEntities[id] = entity;
8124
- }
8866
+ defineOwn(nextEntities, id, entity);
8867
+ });
8125
8868
  return { ids: nextIds, entities: nextEntities };
8126
8869
  });
8127
8870
  }
8128
8871
  function get(id) {
8129
- const state2 = getState();
8130
- return state2.entities[id];
8872
+ return ownGet(getState().entities, id);
8131
8873
  }
8132
8874
  function getAll() {
8133
8875
  const state2 = getState();
8134
8876
  return state2.ids.map((id) => state2.entities[id]);
8135
8877
  }
8136
8878
  function update(id, partial) {
8137
- setState((prev) => {
8138
- const existing = prev.entities[id];
8139
- if (!existing) return prev;
8140
- return {
8141
- ids: prev.ids,
8142
- entities: {
8143
- ...prev.entities,
8144
- [id]: { ...existing, ...partial }
8145
- }
8146
- };
8879
+ const prev = getState();
8880
+ if (!Object.hasOwn(prev.entities, id)) return;
8881
+ if (partial !== null && typeof partial === "object" && Object.hasOwn(partial, idKey)) {
8882
+ const nextId = readEntityId(partial, idKey, schema.name);
8883
+ if (nextId !== id) {
8884
+ throw new Error(
8885
+ `[normalizedStore] update("${id}") cannot change "${idKey}" to "${nextId}"; remove and re-add the entity instead.`
8886
+ );
8887
+ }
8888
+ }
8889
+ setState((current) => {
8890
+ const existing = ownGet(current.entities, id);
8891
+ if (!existing) return current;
8892
+ const entities = dict(current.entities);
8893
+ defineOwn(entities, id, { ...existing, ...partial });
8894
+ return { ids: current.ids, entities };
8147
8895
  });
8148
8896
  }
8149
8897
  function remove(id) {
8150
8898
  setState((prev) => {
8151
- if (!prev.entities[id]) return prev;
8152
- const { [id]: _, ...remainingEntities } = prev.entities;
8899
+ if (!Object.hasOwn(prev.entities, id)) return prev;
8900
+ const entities = dict(prev.entities);
8901
+ delete entities[id];
8153
8902
  return {
8154
8903
  ids: prev.ids.filter((existingId) => existingId !== id),
8155
- entities: remainingEntities
8904
+ entities
8156
8905
  };
8157
8906
  });
8158
8907
  }
@@ -8163,31 +8912,37 @@ function normalizedStore(schema) {
8163
8912
  return { add, addMany, get, getAll, update, remove, select: select3, getState };
8164
8913
  }
8165
8914
  function normalize(data2, schema) {
8166
- const entities = {};
8915
+ const entities = dict();
8167
8916
  function ensureTable(name) {
8168
- if (!entities[name]) {
8169
- entities[name] = {};
8917
+ let table2 = ownGet(entities, name);
8918
+ if (!table2) {
8919
+ table2 = dict();
8920
+ defineOwn(entities, name, table2);
8170
8921
  }
8171
- return entities[name];
8922
+ return table2;
8172
8923
  }
8173
8924
  function normalizeEntity(entity, entitySchema) {
8174
8925
  const entityIdKey = entitySchema.idKey || "id";
8175
- const id = String(entity[entityIdKey]);
8926
+ const id = readEntityId(entity, entityIdKey, entitySchema.name);
8176
8927
  const table2 = ensureTable(entitySchema.name);
8177
8928
  const flat = { ...entity };
8178
8929
  if (entitySchema.relations) {
8179
8930
  for (const [field, relationType] of Object.entries(entitySchema.relations)) {
8180
- const value = entity[field];
8931
+ const value = Object.hasOwn(entity, field) ? entity[field] : void 0;
8181
8932
  if (value == null) continue;
8182
8933
  const relSchema = { name: relationType };
8183
8934
  if (Array.isArray(value)) {
8184
- flat[field] = value.map((item) => normalizeEntity(item, relSchema));
8935
+ defineOwn(
8936
+ flat,
8937
+ field,
8938
+ value.map((item) => normalizeEntity(item, relSchema))
8939
+ );
8185
8940
  } else if (typeof value === "object") {
8186
- flat[field] = normalizeEntity(value, relSchema);
8941
+ defineOwn(flat, field, normalizeEntity(value, relSchema));
8187
8942
  }
8188
8943
  }
8189
8944
  }
8190
- table2[id] = flat;
8945
+ defineOwn(table2, id, flat);
8191
8946
  return id;
8192
8947
  }
8193
8948
  if (Array.isArray(data2)) {
@@ -8198,20 +8953,24 @@ function normalize(data2, schema) {
8198
8953
  return { result, entities };
8199
8954
  }
8200
8955
  function denormalize(id, entities, schema) {
8201
- const table2 = entities[schema.name];
8956
+ const table2 = ownGet(entities, schema.name);
8202
8957
  if (!table2) return void 0;
8203
- const entity = table2[id];
8958
+ const entity = ownGet(table2, id);
8204
8959
  if (!entity) return void 0;
8205
8960
  const result = { ...entity };
8206
8961
  if (schema.relations) {
8207
8962
  for (const [field, relationType] of Object.entries(schema.relations)) {
8208
- const value = entity[field];
8963
+ const value = ownGet(entity, field);
8209
8964
  if (value == null) continue;
8210
8965
  const relSchema = { name: relationType };
8211
8966
  if (Array.isArray(value)) {
8212
- result[field] = value.map((relId) => denormalize(relId, entities, relSchema));
8967
+ defineOwn(
8968
+ result,
8969
+ field,
8970
+ value.map((relId) => denormalize(relId, entities, relSchema))
8971
+ );
8213
8972
  } else if (typeof value === "string") {
8214
- result[field] = denormalize(value, entities, relSchema);
8973
+ defineOwn(result, field, denormalize(value, entities, relSchema));
8215
8974
  }
8216
8975
  }
8217
8976
  }
@@ -8421,13 +9180,24 @@ function lazyChunk(id, loader, registry, fallback) {
8421
9180
  if (fallback) {
8422
9181
  container.appendChild(fallback());
8423
9182
  }
9183
+ let disposed = false;
9184
+ registerDisposer(container, () => {
9185
+ disposed = true;
9186
+ });
8424
9187
  registry.load(id, async () => {
8425
9188
  const mod = await loader();
8426
9189
  return typeof mod === "function" ? mod : mod.default;
8427
9190
  }).then((component) => {
9191
+ if (disposed) return;
9192
+ const node = component();
9193
+ if (disposed) {
9194
+ dispose(node);
9195
+ return;
9196
+ }
8428
9197
  clearChildren(container);
8429
- container.appendChild(component());
9198
+ container.appendChild(node);
8430
9199
  }).catch((err) => {
9200
+ if (disposed) return;
8431
9201
  clearChildren(container);
8432
9202
  const errorEl = document.createElement("div");
8433
9203
  errorEl.textContent = `Failed to load chunk '${id}': ${err.message}`;
@@ -8518,7 +9288,7 @@ function Head(props) {
8518
9288
  };
8519
9289
  const apply = () => {
8520
9290
  cleanup3();
8521
- if (props.title) {
9291
+ if (props.title != null) {
8522
9292
  if (typeof props.title === "function") {
8523
9293
  const getter = props.title;
8524
9294
  let lease = null;
@@ -8573,13 +9343,14 @@ function Head(props) {
8573
9343
  return anchor;
8574
9344
  }
8575
9345
  function setStructuredData(data2) {
8576
- const existing = document.head.querySelector('script[type="application/ld+json"][data-sibu]');
8577
- if (existing) existing.remove();
9346
+ const json = escapeScriptJsonLocal(JSON.stringify(data2));
8578
9347
  const script2 = document.createElement("script");
8579
9348
  script2.type = "application/ld+json";
8580
9349
  script2.setAttribute("data-sibu", "true");
8581
- script2.textContent = escapeScriptJsonLocal(JSON.stringify(data2));
8582
- document.head.appendChild(script2);
9350
+ script2.textContent = json;
9351
+ const existing = document.head.querySelector('script[type="application/ld+json"][data-sibu]');
9352
+ if (existing) existing.replaceWith(script2);
9353
+ else document.head.appendChild(script2);
8583
9354
  }
8584
9355
  function setCanonical(url) {
8585
9356
  let link2 = document.head.querySelector('link[rel="canonical"]');
@@ -8595,13 +9366,33 @@ function setCanonical(url) {
8595
9366
 
8596
9367
  // src/platform/customElement.ts
8597
9368
  var SVG_NS2 = "http://www.w3.org/2000/svg";
9369
+ var MAX_RENDER_PASSES = 10;
9370
+ var ABANDONED_RENDER = /* @__PURE__ */ Symbol("sibujs.defineElement.abandonedRender");
8598
9371
  function defineElement(name, component, options = {}) {
8599
9372
  if (customElements.get(name)) return;
8600
9373
  const observed = options.observedAttributes || [];
8601
9374
  class SibuElement extends HTMLElement {
8602
9375
  constructor() {
8603
9376
  super();
8604
- this._rendered = null;
9377
+ // Re-rendering is keyed on connection, not on a previous render: a first render
9378
+ // that throws leaves nothing rendered, and the element must still re-render
9379
+ // when a later attribute change fixes the input.
9380
+ this._connected = false;
9381
+ // A render in progress, and whether an attribute changed during it. The
9382
+ // component may write its host's observed attributes while rendering; with
9383
+ // the old subtree kept during the build, rendering again from inside the
9384
+ // callback recursed until the stack overflowed.
9385
+ this._rendering = false;
9386
+ this._dirty = false;
9387
+ // How many teardowns are draining. A host teardown may reconnect the element
9388
+ // (and a nested disconnect may run inside it), and rendering the next
9389
+ // generation from inside that drain would let the drain tear down what the
9390
+ // new render registers. A counter, not a flag: a nested teardown returning
9391
+ // must not declare the outer one finished.
9392
+ this._teardownDepth = 0;
9393
+ // Disconnects so far. A render compares it across its old-subtree teardown
9394
+ // to detect a disconnect that happened inside it.
9395
+ this._disconnects = 0;
8605
9396
  if (options.shadow !== false) {
8606
9397
  this._root = this.attachShadow({ mode: options.mode || "open" });
8607
9398
  } else {
@@ -8611,32 +9402,106 @@ function defineElement(name, component, options = {}) {
8611
9402
  static get observedAttributes() {
8612
9403
  return observed;
8613
9404
  }
9405
+ /** Whether rendering must wait for work already in progress. */
9406
+ get _busy() {
9407
+ return this._rendering || this._teardownDepth > 0;
9408
+ }
8614
9409
  connectedCallback() {
9410
+ this._connected = true;
9411
+ if (this._busy) {
9412
+ this._dirty = true;
9413
+ return;
9414
+ }
8615
9415
  this._render();
8616
9416
  }
8617
9417
  disconnectedCallback() {
8618
- this._teardown();
9418
+ this._connected = false;
9419
+ this._disconnects++;
9420
+ this._teardownDepth++;
9421
+ try {
9422
+ this._teardown();
9423
+ } finally {
9424
+ this._teardownDepth--;
9425
+ if (this._teardownDepth === 0 && this._connected && this._dirty) {
9426
+ this._dirty = false;
9427
+ if (!this._rendering) this._render();
9428
+ }
9429
+ }
8619
9430
  }
8620
- attributeChangedCallback() {
8621
- if (this._rendered) {
8622
- this._render();
9431
+ attributeChangedCallback(_name, oldValue, newValue) {
9432
+ if (oldValue === newValue || !this._connected) return;
9433
+ if (this._busy) {
9434
+ this._dirty = true;
9435
+ return;
8623
9436
  }
9437
+ this._render();
8624
9438
  }
8625
9439
  _teardown() {
8626
- this._rendered = null;
8627
9440
  replaceChildrenSafely(this._root);
9441
+ disposeNodeOwn(this);
8628
9442
  }
9443
+ /**
9444
+ * Render as a transaction: build the replacement first, commit only if that
9445
+ * succeeds. Tearing the current subtree down before calling the factory
9446
+ * meant a throwing rerender (an invalid attribute, say) left the element
9447
+ * blank with its live state already disposed. Now a failure keeps the
9448
+ * working subtree, releases whatever the failed attempt registered, and is
9449
+ * reported with this element as its node so an enclosing ErrorBoundary can
9450
+ * claim it.
9451
+ */
8629
9452
  _render() {
8630
- this._teardown();
9453
+ this._rendering = true;
9454
+ try {
9455
+ let passes = 0;
9456
+ do {
9457
+ this._dirty = false;
9458
+ if (++passes > MAX_RENDER_PASSES) {
9459
+ reportError(
9460
+ new Error(
9461
+ `[SibuJS] defineElement(${name}): the component changed its own observed attributes on ${MAX_RENDER_PASSES} consecutive renders; stopped re-rendering.`
9462
+ ),
9463
+ { phase: "render", name: `defineElement(${name})`, node: this }
9464
+ );
9465
+ break;
9466
+ }
9467
+ this._renderOnce();
9468
+ } while (this._dirty && this._connected);
9469
+ } finally {
9470
+ this._rendering = false;
9471
+ this._dirty = false;
9472
+ }
9473
+ }
9474
+ _renderOnce() {
8631
9475
  const props = this._getProps();
9476
+ let el;
9477
+ try {
9478
+ el = withDisposerRollback(() => {
9479
+ const built = component(props, this);
9480
+ if (!this._connected) throw ABANDONED_RENDER;
9481
+ return built;
9482
+ });
9483
+ } catch (err) {
9484
+ if (err !== ABANDONED_RENDER) {
9485
+ reportError(err, { phase: "render", name: `defineElement(${name})`, node: this });
9486
+ }
9487
+ return;
9488
+ }
9489
+ const next = [];
8632
9490
  if (options.styles && this._root instanceof ShadowRoot) {
8633
9491
  const styleEl = document.createElement("style");
8634
9492
  styleEl.textContent = options.styles;
8635
- this._root.appendChild(styleEl);
9493
+ next.push(styleEl);
9494
+ }
9495
+ next.push(el);
9496
+ const disconnects = this._disconnects;
9497
+ for (const node of next) node.parentNode?.removeChild(node);
9498
+ replaceChildrenSafely(this._root);
9499
+ if (this._disconnects !== disconnects) {
9500
+ for (const node of next) dispose(node);
9501
+ if (this._connected) this._dirty = true;
9502
+ return;
8636
9503
  }
8637
- const el = component(props, this);
8638
- this._root.appendChild(el);
8639
- this._rendered = el;
9504
+ this._root.replaceChildren(...next);
8640
9505
  }
8641
9506
  _getProps() {
8642
9507
  const props = {};
@@ -8669,6 +9534,11 @@ function svgElement(tag, props = {}, ...nodes) {
8669
9534
  }
8670
9535
 
8671
9536
  // src/platform/worker.ts
9537
+ function toError(err) {
9538
+ if (err instanceof Error) return err;
9539
+ if (err !== null && typeof err === "object" && "name" in err && "message" in err) return err;
9540
+ return new Error(String(err));
9541
+ }
8672
9542
  function worker(workerFn2) {
8673
9543
  const [result, setResult] = signal(null);
8674
9544
  const [error, setError] = signal(null);
@@ -8691,8 +9561,11 @@ function worker(workerFn2) {
8691
9561
  worker2 = new Worker(blobUrl);
8692
9562
  worker2.addEventListener("message", (e) => {
8693
9563
  revokeBlobUrl();
8694
- setResult(e.data);
8695
- setLoading(false);
9564
+ batch(() => {
9565
+ setResult(e.data);
9566
+ setError(null);
9567
+ setLoading(false);
9568
+ });
8696
9569
  });
8697
9570
  worker2.addEventListener("error", (e) => {
8698
9571
  revokeBlobUrl();
@@ -8709,10 +9582,17 @@ function worker(workerFn2) {
8709
9582
  }
8710
9583
  function post(data2) {
8711
9584
  if (!worker2) return;
8712
- setLoading(true);
8713
- setError(null);
8714
- setResult(null);
8715
- worker2.postMessage(data2);
9585
+ try {
9586
+ worker2.postMessage(data2);
9587
+ } catch (err) {
9588
+ setError(toError(err));
9589
+ return;
9590
+ }
9591
+ batch(() => {
9592
+ setLoading(true);
9593
+ setError(null);
9594
+ setResult(null);
9595
+ });
8716
9596
  }
8717
9597
  function terminate() {
8718
9598
  if (!worker2) return;
@@ -8776,9 +9656,14 @@ function workerFn(fn) {
8776
9656
  reject(new Error("Worker is not available"));
8777
9657
  return;
8778
9658
  }
8779
- setLoading(true);
9659
+ try {
9660
+ worker2.postMessage(args);
9661
+ } catch (err) {
9662
+ reject(toError(err));
9663
+ return;
9664
+ }
8780
9665
  queue.push({ resolve, reject });
8781
- worker2.postMessage(args);
9666
+ setLoading(true);
8782
9667
  });
8783
9668
  }
8784
9669
  function terminate() {
@@ -8808,7 +9693,11 @@ function createWorkerPool(workerFn2, poolSize) {
8808
9693
  }
8809
9694
  };
8810
9695
  function dispatchNext(idx) {
8811
- if (!alive || inflight[idx] || queues[idx].length === 0) return;
9696
+ while (alive && !inflight[idx] && queues[idx].length > 0) {
9697
+ dispatchOne(idx);
9698
+ }
9699
+ }
9700
+ function dispatchOne(idx) {
8812
9701
  const w = workers[idx];
8813
9702
  const slot2 = queues[idx].shift();
8814
9703
  const onMsg = (e) => {
@@ -8833,10 +9722,15 @@ function createWorkerPool(workerFn2, poolSize) {
8833
9722
  slot2.reject(new Error(e.message || "Worker error"));
8834
9723
  dispatchNext(idx);
8835
9724
  };
9725
+ try {
9726
+ w.postMessage(slot2.data);
9727
+ } catch (err) {
9728
+ slot2.reject(toError(err));
9729
+ return;
9730
+ }
8836
9731
  inflight[idx] = { ...slot2, onMsg, onErr };
8837
9732
  w.addEventListener("message", onMsg);
8838
9733
  w.addEventListener("error", onErr);
8839
- w.postMessage(slot2.data);
8840
9734
  }
8841
9735
  try {
8842
9736
  if (typeof Worker === "undefined") {
@@ -9723,13 +10617,13 @@ function createSharedScope(initialState) {
9723
10617
  for (const key of Object.keys(initialState)) {
9724
10618
  const [get2, set2] = signal(initialState[key]);
9725
10619
  signals.set(key, { get: get2, set: set2 });
9726
- subscribers.set(key, /* @__PURE__ */ new Set());
10620
+ subscribers.set(key, /* @__PURE__ */ new Map());
9727
10621
  }
9728
10622
  function ensureSignal(key) {
9729
10623
  if (!signals.has(key)) {
9730
10624
  const [get2, set2] = signal(void 0);
9731
10625
  signals.set(key, { get: get2, set: set2 });
9732
- subscribers.set(key, /* @__PURE__ */ new Set());
10626
+ subscribers.set(key, /* @__PURE__ */ new Map());
9733
10627
  }
9734
10628
  }
9735
10629
  function get(key) {
@@ -9740,9 +10634,14 @@ function createSharedScope(initialState) {
9740
10634
  ensureSignal(key);
9741
10635
  signals.get(key)?.set(value);
9742
10636
  const subs = subscribers.get(key);
9743
- if (subs) {
9744
- for (const cb of subs) {
9745
- cb(value);
10637
+ if (subs && subs.size > 0) {
10638
+ for (const subscription of Array.from(subs.values())) {
10639
+ if (!subscription.active) continue;
10640
+ try {
10641
+ subscription.callback(value);
10642
+ } catch (err) {
10643
+ reportError(err, { phase: "event", name: `sharedScope(${String(key)})` });
10644
+ }
9746
10645
  }
9747
10646
  }
9748
10647
  }
@@ -9753,9 +10652,17 @@ function createSharedScope(initialState) {
9753
10652
  return () => {
9754
10653
  };
9755
10654
  }
9756
- subs.add(callback);
10655
+ const cb = callback;
10656
+ let subscription = subs.get(cb);
10657
+ if (!subscription) {
10658
+ subscription = { callback: cb, active: true };
10659
+ subs.set(cb, subscription);
10660
+ }
10661
+ const own = subscription;
9757
10662
  return () => {
9758
- subs.delete(callback);
10663
+ if (subs.get(cb) !== own) return;
10664
+ own.active = false;
10665
+ subs.delete(cb);
9759
10666
  };
9760
10667
  }
9761
10668
  return { get, set, subscribe };
@@ -9822,7 +10729,7 @@ function serviceWorker(scriptUrl, options) {
9822
10729
  arrivedRegistration = null;
9823
10730
  }
9824
10731
  }
9825
- function adopt(reg) {
10732
+ function adopt2(reg) {
9826
10733
  if (unregisterRequested) {
9827
10734
  arrivedRegistration = reg;
9828
10735
  return null;
@@ -9831,7 +10738,7 @@ function serviceWorker(scriptUrl, options) {
9831
10738
  return reg;
9832
10739
  }
9833
10740
  if (supported) {
9834
- registrationPromise = navigator.serviceWorker.register(scriptUrl, options).then(adopt).catch((err) => {
10741
+ registrationPromise = navigator.serviceWorker.register(scriptUrl, options).then(adopt2).catch((err) => {
9835
10742
  if (!unregistered) {
9836
10743
  setError(err instanceof Error ? err : new Error(String(err)));
9837
10744
  }
@@ -9907,15 +10814,31 @@ async function generateStaticSite(options) {
9907
10814
  // src/platform/incrementalRegeneration.ts
9908
10815
  function createISR(options) {
9909
10816
  const { revalidateAfter, fetcher, initialData } = options;
10817
+ if (!Number.isFinite(revalidateAfter) || revalidateAfter <= 0) {
10818
+ throw new RangeError(`[SibuJS ISR] revalidateAfter must be a positive finite number, got ${revalidateAfter}`);
10819
+ }
9910
10820
  const [data2, setData] = signal(initialData);
9911
- const [timestamp, setTimestamp] = signal(initialData !== void 0 ? Date.now() : 0);
10821
+ const [stale, setStale] = signal(initialData === void 0);
9912
10822
  const controller = new AbortController();
9913
10823
  let inFlight = false;
9914
10824
  let disposed = false;
9915
- const isStale = () => {
9916
- const ts = timestamp();
9917
- if (ts === 0) return true;
9918
- return Date.now() - ts >= revalidateAfter;
10825
+ let deadline;
10826
+ const clearDeadline = () => {
10827
+ if (deadline !== void 0) {
10828
+ clearTimeout(deadline);
10829
+ deadline = void 0;
10830
+ }
10831
+ };
10832
+ const armDeadline = () => {
10833
+ clearDeadline();
10834
+ deadline = setTimeout(() => {
10835
+ deadline = void 0;
10836
+ if (disposed) return;
10837
+ setStale(true);
10838
+ revalidate().catch((err) => {
10839
+ if (typeof console !== "undefined") console.warn("[SibuJS ISR] revalidate failed", err);
10840
+ });
10841
+ }, revalidateAfter);
9919
10842
  };
9920
10843
  const revalidate = async () => {
9921
10844
  if (disposed || inFlight) return;
@@ -9924,8 +10847,14 @@ function createISR(options) {
9924
10847
  try {
9925
10848
  const result = await fetcher({ signal: controller.signal });
9926
10849
  if (disposed || controller.signal.aborted) return;
9927
- setData(result);
9928
- setTimestamp(Date.now());
10850
+ batch(() => {
10851
+ setData(result);
10852
+ setStale(false);
10853
+ });
10854
+ armDeadline();
10855
+ } catch (err) {
10856
+ if (!disposed && !controller.signal.aborted) armDeadline();
10857
+ throw err;
9929
10858
  } finally {
9930
10859
  inFlight = false;
9931
10860
  }
@@ -9934,19 +10863,16 @@ function createISR(options) {
9934
10863
  revalidate().catch((err) => {
9935
10864
  if (typeof console !== "undefined") console.warn("[SibuJS ISR] initial fetch failed", err);
9936
10865
  });
10866
+ } else {
10867
+ armDeadline();
9937
10868
  }
9938
- const intervalId = setInterval(() => {
9939
- revalidate().catch((err) => {
9940
- if (typeof console !== "undefined") console.warn("[SibuJS ISR] revalidate failed", err);
9941
- });
9942
- }, revalidateAfter);
9943
10869
  const dispose2 = () => {
9944
10870
  if (disposed) return;
9945
10871
  disposed = true;
9946
- clearInterval(intervalId);
10872
+ clearDeadline();
9947
10873
  controller.abort();
9948
10874
  };
9949
- return { data: data2, isStale, revalidate, dispose: dispose2 };
10875
+ return { data: data2, isStale: stale, revalidate, dispose: dispose2 };
9950
10876
  }
9951
10877
 
9952
10878
  // src/platform/routeActions.ts
@@ -10071,7 +10997,7 @@ function scrollRestoration(options) {
10071
10997
 
10072
10998
  // src/platform/routeMiddleware.ts
10073
10999
  function composeMiddleware(...fns) {
10074
- return async (context2, next) => {
11000
+ return async (context, next) => {
10075
11001
  let index = -1;
10076
11002
  const dispatch = async (i2) => {
10077
11003
  if (i2 <= index) {
@@ -10079,7 +11005,7 @@ function composeMiddleware(...fns) {
10079
11005
  }
10080
11006
  index = i2;
10081
11007
  if (i2 < fns.length) {
10082
- await fns[i2](context2, () => dispatch(i2 + 1));
11008
+ await fns[i2](context, () => dispatch(i2 + 1));
10083
11009
  } else {
10084
11010
  await next();
10085
11011
  }
@@ -10092,46 +11018,136 @@ function createMiddlewareChain() {
10092
11018
  const use2 = (fn) => {
10093
11019
  middlewares.push(fn);
10094
11020
  };
10095
- const run = async (context2) => {
11021
+ const run = async (context) => {
10096
11022
  const composed = composeMiddleware(...middlewares);
10097
- await composed(context2, () => {
11023
+ await composed(context, () => {
10098
11024
  });
10099
11025
  };
10100
11026
  return { use: use2, run };
10101
11027
  }
10102
11028
 
10103
11029
  // src/plugins/plugin.ts
11030
+ var CANCELLED_BRAND = /* @__PURE__ */ Symbol.for("sibujs.plugins.installCancelled.v1");
11031
+ var _a;
11032
+ var PluginInstallCancelledError = class extends Error {
11033
+ constructor(pluginName) {
11034
+ super(`[Plugin] Installation of "${pluginName}" was cancelled before it was committed (registry reset).`);
11035
+ /**
11036
+ * Brand, not identity: the default registry is shared across duplicate copies
11037
+ * of this module, so the error a caller receives may come from another copy's
11038
+ * constructor. `instanceof` is taught to recognise the brand instead.
11039
+ */
11040
+ this[_a] = true;
11041
+ this.name = "PluginInstallCancelledError";
11042
+ this.pluginName = pluginName;
11043
+ }
11044
+ static [(_a = CANCELLED_BRAND, Symbol.hasInstance)](value) {
11045
+ return isPluginInstallCancelledError(value);
11046
+ }
11047
+ };
11048
+ function isPluginInstallCancelledError(value) {
11049
+ return typeof value === "object" && value !== null && value[CANCELLED_BRAND] === true;
11050
+ }
10104
11051
  function createPluginRegistry() {
10105
11052
  const installedPlugins = /* @__PURE__ */ new Set();
10106
11053
  const hooks = { init: [], mount: [], unmount: [], error: [] };
10107
11054
  const provided = /* @__PURE__ */ new Map();
11055
+ const installStack = /* @__PURE__ */ new Set();
11056
+ const pendingInstalls = /* @__PURE__ */ new Map();
11057
+ let generation = 0;
10108
11058
  const registry = {
10109
11059
  installedPlugins,
10110
11060
  hooks,
10111
11061
  provided,
11062
+ /**
11063
+ * Install a plugin as a transaction.
11064
+ *
11065
+ * Hooks and providers registered through `ctx` are staged and committed
11066
+ * only if `install()` returns; a throwing install leaves the registry
11067
+ * exactly as it found it (and can simply be retried). The plugin is marked
11068
+ * installed at commit, before its init hooks run.
11069
+ *
11070
+ * Each `plugin()` call is its own transaction: a dependency installed by a
11071
+ * nested `plugin()` call commits on its own and stays installed even if the
11072
+ * outer installation later fails.
11073
+ */
10112
11074
  plugin(p2, options) {
10113
11075
  if (installedPlugins.has(p2.name)) {
10114
11076
  console.warn(`[Plugin] "${p2.name}" is already installed.`);
10115
11077
  return;
10116
11078
  }
11079
+ if (installStack.has(p2.name)) {
11080
+ throw new Error(`[Plugin] "${p2.name}" is already being installed (recursive installation).`);
11081
+ }
11082
+ if (pendingInstalls.has(p2.name)) {
11083
+ throw new Error(`[Plugin] "${p2.name}" is already being installed (asynchronous installation in flight).`);
11084
+ }
11085
+ const staged = { init: [], mount: [], unmount: [], error: [] };
11086
+ const stagedProvided = /* @__PURE__ */ new Map();
11087
+ let committed = false;
11088
+ const myGeneration = generation;
11089
+ const stale = () => {
11090
+ if (myGeneration === generation) return false;
11091
+ if (DEV) devWarn(`plugin: "${p2.name}" registered after the registry was reset; the registration was ignored.`);
11092
+ return true;
11093
+ };
11094
+ const target = () => stale() ? null : committed ? hooks : staged;
10117
11095
  const ctx = {
10118
- onInit: (cb) => hooks.init.push(cb),
10119
- onMount: (cb) => hooks.mount.push(cb),
10120
- onUnmount: (cb) => hooks.unmount.push(cb),
10121
- onError: (cb) => hooks.error.push(cb),
10122
- provide: (key, value) => provided.set(key, value)
11096
+ onInit: (cb) => void target()?.init.push(cb),
11097
+ onMount: (cb) => void target()?.mount.push(cb),
11098
+ onUnmount: (cb) => void target()?.unmount.push(cb),
11099
+ onError: (cb) => void target()?.error.push(cb),
11100
+ provide: (key, value) => {
11101
+ if (stale()) return;
11102
+ (committed ? provided : stagedProvided).set(key, value);
11103
+ }
10123
11104
  };
10124
- const initHooksBefore = hooks.init.length;
10125
- p2.install(ctx, options);
10126
- installedPlugins.add(p2.name);
10127
- const justAdded = hooks.init.slice(initHooksBefore);
10128
- for (const cb of justAdded) {
10129
- try {
10130
- cb();
10131
- } catch (e) {
10132
- console.error(`[Plugin] "${p2.name}" init error:`, e);
11105
+ const commit = () => {
11106
+ if (myGeneration !== generation) return false;
11107
+ hooks.init.push(...staged.init);
11108
+ hooks.mount.push(...staged.mount);
11109
+ hooks.unmount.push(...staged.unmount);
11110
+ hooks.error.push(...staged.error);
11111
+ for (const [key, value] of stagedProvided) provided.set(key, value);
11112
+ installedPlugins.add(p2.name);
11113
+ committed = true;
11114
+ if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
11115
+ for (const cb of staged.init.slice()) {
11116
+ try {
11117
+ cb();
11118
+ } catch (e) {
11119
+ console.error(`[Plugin] "${p2.name}" init error:`, e);
11120
+ }
10133
11121
  }
11122
+ return true;
11123
+ };
11124
+ installStack.add(p2.name);
11125
+ let pending;
11126
+ try {
11127
+ pending = adoptThenable(p2.install(ctx, options));
11128
+ } finally {
11129
+ installStack.delete(p2.name);
11130
+ }
11131
+ if (!pending) {
11132
+ if (!commit()) throw new PluginInstallCancelledError(p2.name);
11133
+ return;
10134
11134
  }
11135
+ if (myGeneration === generation) pendingInstalls.set(p2.name, myGeneration);
11136
+ const settled = pending.then(
11137
+ () => {
11138
+ if (commit()) return;
11139
+ if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
11140
+ throw new PluginInstallCancelledError(p2.name);
11141
+ },
11142
+ (err) => {
11143
+ if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
11144
+ reportError(err, { phase: "async", name: `plugin(${p2.name})` });
11145
+ throw err;
11146
+ }
11147
+ );
11148
+ settled.catch(() => {
11149
+ });
11150
+ return settled;
10135
11151
  },
10136
11152
  inject(key, defaultValue) {
10137
11153
  if (provided.has(key)) return provided.get(key);
@@ -10169,6 +11185,8 @@ function createPluginRegistry() {
10169
11185
  }
10170
11186
  },
10171
11187
  reset() {
11188
+ generation++;
11189
+ pendingInstalls.clear();
10172
11190
  installedPlugins.clear();
10173
11191
  hooks.init.length = 0;
10174
11192
  hooks.mount.length = 0;
@@ -10188,7 +11206,7 @@ function createPlugin(name, install) {
10188
11206
  }
10189
11207
  function plugin(plugin2, options) {
10190
11208
  _defaults.touched = true;
10191
- _defaults.registry.plugin(plugin2, options);
11209
+ return _defaults.registry.plugin(plugin2, options);
10192
11210
  }
10193
11211
  function inject(key, defaultValue) {
10194
11212
  return _defaults.registry.inject(key, defaultValue);
@@ -10219,35 +11237,44 @@ function setDefaultPluginRegistry(registry) {
10219
11237
  // src/plugins/modular.ts
10220
11238
  function createModuleRegistry() {
10221
11239
  const modules = /* @__PURE__ */ new Map();
10222
- function resolveInternal(name, stack) {
11240
+ const resolving = [];
11241
+ function resolveInternal(name) {
10223
11242
  const entry = modules.get(name);
10224
11243
  if (!entry) {
10225
11244
  throw new Error(`[ModuleRegistry] Module "${name}" is not registered.`);
10226
11245
  }
10227
- if (entry.loaded) {
11246
+ if (entry.state === "loaded") {
10228
11247
  return entry.value;
10229
11248
  }
10230
- if (stack.has(name)) {
10231
- const cycle = [...stack, name].join(" -> ");
11249
+ if (entry.state === "resolving") {
11250
+ const cycle = [...resolving.slice(resolving.indexOf(name)), name].join(" -> ");
10232
11251
  throw new Error(`[ModuleRegistry] Circular dependency detected: ${cycle}`);
10233
11252
  }
10234
- stack.add(name);
10235
- for (const dep of entry.deps) {
10236
- resolveInternal(dep, stack);
11253
+ entry.state = "resolving";
11254
+ resolving.push(name);
11255
+ try {
11256
+ for (const dep of entry.deps) {
11257
+ resolveInternal(dep);
11258
+ }
11259
+ entry.value = entry.factory();
11260
+ entry.state = "loaded";
11261
+ return entry.value;
11262
+ } catch (err) {
11263
+ entry.state = "unloaded";
11264
+ entry.value = void 0;
11265
+ throw err;
11266
+ } finally {
11267
+ resolving.pop();
10237
11268
  }
10238
- stack.delete(name);
10239
- entry.value = entry.factory();
10240
- entry.loaded = true;
10241
- return entry.value;
10242
11269
  }
10243
11270
  return {
10244
11271
  /** Register a module with its factory function and optional dependencies */
10245
11272
  register(name, factory, deps = []) {
10246
- modules.set(name, { factory, deps, loaded: false });
11273
+ modules.set(name, { factory, deps, state: "unloaded" });
10247
11274
  },
10248
11275
  /** Resolve a module, loading its dependencies first */
10249
11276
  resolve(name) {
10250
- return resolveInternal(name, /* @__PURE__ */ new Set());
11277
+ return resolveInternal(name);
10251
11278
  },
10252
11279
  /** Check if a module is registered */
10253
11280
  has(name) {
@@ -10282,7 +11309,7 @@ function createModuleRegistry() {
10282
11309
  /** Reset all loaded modules back to unloaded state (useful for testing) */
10283
11310
  reset() {
10284
11311
  for (const entry of modules.values()) {
10285
- entry.loaded = false;
11312
+ entry.state = "unloaded";
10286
11313
  entry.value = void 0;
10287
11314
  }
10288
11315
  }
@@ -10310,46 +11337,83 @@ function createBundle(modules) {
10310
11337
  function lazyModule(loader) {
10311
11338
  let cached;
10312
11339
  let loadedFlag = false;
10313
- const handle = {
11340
+ let inFlight = null;
11341
+ return {
10314
11342
  get loaded() {
10315
11343
  return loadedFlag;
10316
11344
  },
10317
- async get() {
10318
- if (loadedFlag) return cached;
10319
- cached = await loader();
10320
- loadedFlag = true;
10321
- return cached;
10322
- }
10323
- };
10324
- return handle;
10325
- }
11345
+ get() {
11346
+ if (loadedFlag) return Promise.resolve(cached);
11347
+ if (inFlight) return inFlight;
11348
+ let started;
11349
+ try {
11350
+ started = Promise.resolve(loader());
11351
+ } catch (err) {
11352
+ started = Promise.reject(err);
11353
+ }
11354
+ const attempt = started.then(
11355
+ (value) => {
11356
+ cached = value;
11357
+ loadedFlag = true;
11358
+ if (inFlight === attempt) inFlight = null;
11359
+ return value;
11360
+ },
11361
+ (err) => {
11362
+ if (inFlight === attempt) inFlight = null;
11363
+ throw err;
11364
+ }
11365
+ );
11366
+ inFlight = attempt;
11367
+ return attempt;
11368
+ }
11369
+ };
11370
+ }
11371
+ var MODULE_ENTRY_POINTS = {
11372
+ main: "./index.ts",
11373
+ data: "./data.ts",
11374
+ browser: "./browser.ts",
11375
+ patterns: "./patterns.ts",
11376
+ motion: "./motion.ts",
11377
+ ui: "./ui.ts",
11378
+ widgets: "./widgets.ts",
11379
+ ssr: "./ssr.ts",
11380
+ devtools: "./devtools.ts",
11381
+ performance: "./performance.ts",
11382
+ ecosystem: "./ecosystem.ts",
11383
+ plugins: "./plugins.ts",
11384
+ build: "./build.ts",
11385
+ testing: "./testing.ts",
11386
+ extras: "./extras.ts"
11387
+ };
11388
+ var CDN_ENTRY_POINTS = {
11389
+ cdn: "./dist/cdn.global.js",
11390
+ "cdn-dev": "./dist/cdn.dev.global.js",
11391
+ "cdn-full": "./dist/cdn.full.global.js",
11392
+ "cdn-full-dev": "./dist/cdn.full.dev.global.js"
11393
+ };
10326
11394
  var packageInfo = {
10327
- name: "sibu",
10328
- version: "1.0.0",
10329
- entryPoints: {
10330
- main: "./index.ts",
10331
- core: "./src/core/html.ts",
10332
- hooks: "./src/core/signals/signal.ts",
10333
- router: "./src/plugins/router.ts",
10334
- i18n: "./src/plugins/i18n.ts",
10335
- testing: "./src/testing/index.ts",
10336
- ssr: "./src/core/ssr.ts"
10337
- },
11395
+ name: "sibujs",
11396
+ version: true ? "4.6.0" : "dev",
11397
+ entryPoints: MODULE_ENTRY_POINTS,
10338
11398
  /**
10339
- * Generate a package.json `exports` map for Node.js subpath exports.
10340
- * Maps each entry point to its import, require, and types paths.
11399
+ * Generate the package.json `exports` map. Module entries resolve to the
11400
+ * `.js` (ESM), `.cjs` and `.d.ts` files tsup emits into `dist/`; CDN entries
11401
+ * resolve to their prebuilt global script.
10341
11402
  */
10342
11403
  generateExportsMap() {
10343
11404
  const exportsMap = {};
10344
- for (const [name, sourcePath] of Object.entries(this.entryPoints)) {
11405
+ for (const [name, sourcePath] of Object.entries(MODULE_ENTRY_POINTS)) {
10345
11406
  const distPath = sourcePath.replace(/^\.\//, "./dist/").replace(/\.ts$/, "");
10346
11407
  const subpath = name === "main" ? "." : `./${name}`;
10347
11408
  exportsMap[subpath] = {
10348
- import: `${distPath}.mjs`,
10349
- require: `${distPath}.cjs`,
10350
- types: `${distPath}.d.ts`
11409
+ types: `${distPath}.d.ts`,
11410
+ import: `${distPath}.js`,
11411
+ require: `${distPath}.cjs`
10351
11412
  };
10352
11413
  }
11414
+ for (const [name, file] of Object.entries(CDN_ENTRY_POINTS)) {
11415
+ exportsMap[`./${name}`] = { default: file };
11416
+ }
10353
11417
  return exportsMap;
10354
11418
  }
10355
11419
  };
@@ -10474,29 +11538,26 @@ var env = {
10474
11538
  };
10475
11539
 
10476
11540
  // src/plugins/versioning.ts
10477
- var VERSION = "1.0.0";
11541
+ var VERSION = true ? "4.6.0" : "dev";
11542
+ var NUM = "(?:0|[1-9]\\d*)";
11543
+ var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
11544
+ var BUILD_ID = "[0-9a-zA-Z-]+";
11545
+ var SEMVER_PATTERN = new RegExp(
11546
+ `^(${NUM})(?:\\.(${NUM})(?:\\.(${NUM})(?:-(${PRE_ID}(?:\\.${PRE_ID})*))?(?:\\+(${BUILD_ID}(?:\\.${BUILD_ID})*))?)?)?$`
11547
+ );
10478
11548
  function parseSemVer(version) {
10479
- const trimmed = version.trim().replace(/^v/i, "");
10480
- const prereleaseIndex = trimmed.indexOf("-");
10481
- let main2;
10482
- let prerelease;
10483
- if (prereleaseIndex !== -1) {
10484
- main2 = trimmed.slice(0, prereleaseIndex);
10485
- prerelease = trimmed.slice(prereleaseIndex + 1);
10486
- } else {
10487
- main2 = trimmed;
10488
- }
10489
- const parts = main2.split(".");
10490
- const major = parseInt(parts[0], 10);
10491
- const minor = parseInt(parts[1] || "0", 10);
10492
- const patch = parseInt(parts[2] || "0", 10);
10493
- if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) {
11549
+ const trimmed = String(version).trim().replace(/^v/i, "");
11550
+ const match = SEMVER_PATTERN.exec(trimmed);
11551
+ if (!match) {
10494
11552
  throw new Error(`[Versioning] Invalid semver string: "${version}"`);
10495
11553
  }
10496
- const result = { major, minor, patch };
10497
- if (prerelease !== void 0) {
10498
- result.prerelease = prerelease;
10499
- }
11554
+ const result = {
11555
+ major: Number(match[1]),
11556
+ minor: match[2] === void 0 ? 0 : Number(match[2]),
11557
+ patch: match[3] === void 0 ? 0 : Number(match[3])
11558
+ };
11559
+ if (match[4] !== void 0) result.prerelease = match[4];
11560
+ if (match[5] !== void 0) result.build = match[5];
10500
11561
  return result;
10501
11562
  }
10502
11563
  function compareSemVer(a2, b2) {
@@ -10579,82 +11640,154 @@ function satisfies(version, range) {
10579
11640
  const target = parseSemVer(exactStr);
10580
11641
  return compareSemVer(v, target) === 0;
10581
11642
  }
11643
+ var MigrationStorageError = class extends Error {
11644
+ constructor(version, cause) {
11645
+ const reason = cause instanceof Error ? cause.message : String(cause);
11646
+ super(`[Versioning] Migration ${version} completed, but recording it in storage failed: ${reason}`);
11647
+ this.name = "MigrationStorageError";
11648
+ this.version = version;
11649
+ this.cause = cause;
11650
+ }
11651
+ };
11652
+ var _migrationQueues = globalSingleton(
11653
+ /* @__PURE__ */ Symbol.for("sibujs.migrationQueues.v1"),
11654
+ () => /* @__PURE__ */ new WeakMap()
11655
+ );
11656
+ var NO_STORAGE = {};
10582
11657
  function createMigrationRunner(config) {
10583
11658
  const storageKey = config.storageKey || "__sibu_migration_version__";
10584
11659
  const sortedMigrations = [...config.migrations].sort((a2, b2) => compareSemVer(a2.version, b2.version));
10585
11660
  function getStorage() {
11661
+ if (config.storage) return config.storage;
10586
11662
  try {
10587
11663
  return typeof localStorage !== "undefined" ? localStorage : null;
10588
11664
  } catch {
10589
11665
  return null;
10590
11666
  }
10591
11667
  }
11668
+ function getAppliedVersion() {
11669
+ const storage = getStorage();
11670
+ if (!storage) return null;
11671
+ return storage.getItem(storageKey);
11672
+ }
11673
+ function getPending() {
11674
+ const appliedVersion = getAppliedVersion();
11675
+ if (!appliedVersion) {
11676
+ return sortedMigrations.filter((m) => compareSemVer(m.version, config.currentVersion) <= 0);
11677
+ }
11678
+ return sortedMigrations.filter(
11679
+ (m) => compareSemVer(m.version, appliedVersion) > 0 && compareSemVer(m.version, config.currentVersion) <= 0
11680
+ );
11681
+ }
11682
+ function writeApplied(version, migrationVersion) {
11683
+ const storage = getStorage();
11684
+ if (!storage) return;
11685
+ try {
11686
+ if (version === null) storage.removeItem(storageKey);
11687
+ else storage.setItem(storageKey, version);
11688
+ } catch (e) {
11689
+ throw new MigrationStorageError(migrationVersion, e);
11690
+ }
11691
+ }
11692
+ function serialize(operation) {
11693
+ const identity = getStorage() ?? NO_STORAGE;
11694
+ let queues = _migrationQueues.get(identity);
11695
+ if (!queues) {
11696
+ queues = /* @__PURE__ */ new Map();
11697
+ _migrationQueues.set(identity, queues);
11698
+ }
11699
+ const run = (queues.get(storageKey) ?? Promise.resolve()).then(operation, operation);
11700
+ const tail = run.then(
11701
+ () => void 0,
11702
+ () => void 0
11703
+ );
11704
+ queues.set(storageKey, tail);
11705
+ const settled = queues;
11706
+ tail.then(() => {
11707
+ if (settled.get(storageKey) === tail) settled.delete(storageKey);
11708
+ });
11709
+ return run;
11710
+ }
10592
11711
  return {
10593
11712
  /** Get the last applied migration version from storage */
10594
- getAppliedVersion() {
10595
- const storage = getStorage();
10596
- if (!storage) return null;
10597
- return storage.getItem(storageKey);
10598
- },
11713
+ getAppliedVersion,
10599
11714
  /** Get pending migrations that haven't been applied */
10600
- getPending() {
10601
- const appliedVersion = this.getAppliedVersion();
10602
- if (!appliedVersion) {
10603
- return sortedMigrations.filter((m) => compareSemVer(m.version, config.currentVersion) <= 0);
10604
- }
10605
- return sortedMigrations.filter(
10606
- (m) => compareSemVer(m.version, appliedVersion) > 0 && compareSemVer(m.version, config.currentVersion) <= 0
10607
- );
10608
- },
10609
- /** Run all pending migrations in order */
10610
- async migrate() {
10611
- const pending = this.getPending();
10612
- const applied = [];
10613
- const errors = [];
10614
- for (const migration of pending) {
11715
+ getPending,
11716
+ /**
11717
+ * Run all pending migrations in order. Concurrent calls (and calls racing
11718
+ * `rollback()`) run one after another, each recomputing what is pending —
11719
+ * on this runner and on every other runner in this realm sharing its
11720
+ * storage and storage key.
11721
+ */
11722
+ migrate() {
11723
+ return serialize(async () => {
11724
+ const applied = [];
11725
+ const errors = [];
11726
+ let pending;
10615
11727
  try {
10616
- await migration.up();
10617
- applied.push(migration.version);
10618
- const storage = getStorage();
10619
- if (storage) {
10620
- storage.setItem(storageKey, migration.version);
10621
- }
11728
+ pending = getPending();
10622
11729
  } catch (e) {
10623
11730
  errors.push({
10624
- version: migration.version,
11731
+ version: getAppliedVersion() ?? "",
10625
11732
  error: e instanceof Error ? e : new Error(String(e))
10626
11733
  });
10627
- break;
11734
+ return { applied, errors };
10628
11735
  }
10629
- }
10630
- return { applied, errors };
11736
+ for (const migration of pending) {
11737
+ try {
11738
+ await migration.up();
11739
+ } catch (e) {
11740
+ errors.push({
11741
+ version: migration.version,
11742
+ error: e instanceof Error ? e : new Error(String(e))
11743
+ });
11744
+ break;
11745
+ }
11746
+ applied.push(migration.version);
11747
+ try {
11748
+ writeApplied(migration.version, migration.version);
11749
+ } catch (e) {
11750
+ errors.push({ version: migration.version, error: e });
11751
+ break;
11752
+ }
11753
+ }
11754
+ return { applied, errors };
11755
+ });
10631
11756
  },
10632
- /** Rollback to a specific version */
10633
- async rollback(targetVersion) {
10634
- const appliedVersion = this.getAppliedVersion();
10635
- const rolledBack = [];
10636
- if (!appliedVersion) {
10637
- return { rolledBack };
10638
- }
10639
- const toRollback = sortedMigrations.filter((m) => compareSemVer(m.version, targetVersion) > 0 && compareSemVer(m.version, appliedVersion) <= 0).reverse();
10640
- for (const migration of toRollback) {
10641
- if (!migration.down) {
10642
- throw new Error(
10643
- `[Versioning] Migration ${migration.version} ("${migration.description}") does not have a down() function and cannot be rolled back.`
10644
- );
11757
+ /**
11758
+ * Rollback to a specific version.
11759
+ *
11760
+ * The applied version is checkpointed after EVERY successful `down()`, so a
11761
+ * rollback that fails part-way leaves storage describing what is actually
11762
+ * still applied, and a retry does not repeat completed `down()` steps.
11763
+ * Throws the failing `down()`'s error, a missing-`down()` error, or a
11764
+ * {@link MigrationStorageError} if a checkpoint cannot be written.
11765
+ */
11766
+ rollback(targetVersion) {
11767
+ return serialize(async () => {
11768
+ const appliedVersion = getAppliedVersion();
11769
+ const rolledBack = [];
11770
+ if (!appliedVersion) {
11771
+ return { rolledBack };
10645
11772
  }
10646
- await migration.down();
10647
- rolledBack.push(migration.version);
10648
- }
10649
- const storage = getStorage();
10650
- if (storage) {
10651
- if (targetVersion === "0.0.0") {
10652
- storage.removeItem(storageKey);
10653
- } else {
10654
- storage.setItem(storageKey, targetVersion);
11773
+ const toRollback = sortedMigrations.filter((m) => compareSemVer(m.version, targetVersion) > 0 && compareSemVer(m.version, appliedVersion) <= 0).reverse();
11774
+ for (const migration of toRollback) {
11775
+ if (!migration.down) {
11776
+ throw new Error(
11777
+ `[Versioning] Migration ${migration.version} ("${migration.description}") does not have a down() function and cannot be rolled back.`
11778
+ );
11779
+ }
10655
11780
  }
10656
- }
10657
- return { rolledBack };
11781
+ for (const migration of toRollback) {
11782
+ await migration.down();
11783
+ rolledBack.push(migration.version);
11784
+ const index = sortedMigrations.indexOf(migration);
11785
+ const previous2 = index > 0 ? sortedMigrations[index - 1].version : null;
11786
+ writeApplied(previous2, migration.version);
11787
+ }
11788
+ writeApplied(targetVersion === "0.0.0" ? null : targetVersion, targetVersion);
11789
+ return { rolledBack };
11790
+ });
10658
11791
  }
10659
11792
  };
10660
11793
  }
@@ -10704,28 +11837,43 @@ function preloadCritical(resources) {
10704
11837
  document.head.appendChild(link2);
10705
11838
  }
10706
11839
  }
10707
- function prerenderRoutes(routes, options) {
10708
- const maxCacheSize = options?.maxCacheSize ?? 50;
10709
- const cacheTTL = options?.cacheTTL ?? 0;
10710
- const cache = /* @__PURE__ */ new Map();
10711
- function isValid(entry) {
10712
- if (cacheTTL === 0) return true;
10713
- return Date.now() - entry.timestamp < cacheTTL;
11840
+ function assertCacheSize(name, value) {
11841
+ if (!Number.isInteger(value) || value < 0) {
11842
+ throw new RangeError(`[Startup] ${name} must be a non-negative integer, got ${value}`);
11843
+ }
11844
+ }
11845
+ function makeRoomFor(cache, key, maxSize, isValid) {
11846
+ if (maxSize === 0) return false;
11847
+ if (cache.has(key)) return true;
11848
+ if (cache.size < maxSize) return true;
11849
+ for (const [k, entry] of cache) {
11850
+ if (!isValid(entry)) cache.delete(k);
10714
11851
  }
10715
- function evictOldest() {
10716
- if (cache.size < maxCacheSize) return;
11852
+ while (cache.size >= maxSize) {
10717
11853
  let oldestKey = null;
10718
11854
  let oldestTime = Infinity;
10719
- for (const [key, entry] of cache) {
11855
+ for (const [k, entry] of cache) {
10720
11856
  if (entry.timestamp < oldestTime) {
10721
11857
  oldestTime = entry.timestamp;
10722
- oldestKey = key;
11858
+ oldestKey = k;
10723
11859
  }
10724
11860
  }
10725
- if (oldestKey) cache.delete(oldestKey);
11861
+ if (oldestKey === null) break;
11862
+ cache.delete(oldestKey);
11863
+ }
11864
+ return true;
11865
+ }
11866
+ function prerenderRoutes(routes, options) {
11867
+ const maxCacheSize = options?.maxCacheSize ?? 50;
11868
+ const cacheTTL = options?.cacheTTL ?? 0;
11869
+ assertCacheSize("maxCacheSize", maxCacheSize);
11870
+ const cache = /* @__PURE__ */ new Map();
11871
+ function isValid(entry) {
11872
+ if (cacheTTL === 0) return true;
11873
+ return Date.now() - entry.timestamp < cacheTTL;
10726
11874
  }
10727
11875
  for (const route of routes) {
10728
- evictOldest();
11876
+ if (!makeRoomFor(cache, route.path, maxCacheSize, isValid)) break;
10729
11877
  const html2 = renderToString(route.component());
10730
11878
  cache.set(route.path, { html: html2, timestamp: Date.now() });
10731
11879
  }
@@ -10777,6 +11925,7 @@ function prerenderRoutes(routes, options) {
10777
11925
  function createSSRCache(config) {
10778
11926
  const maxSize = config?.maxSize ?? 100;
10779
11927
  const defaultTTL = config?.defaultTTL ?? 6e4;
11928
+ assertCacheSize("maxSize", maxSize);
10780
11929
  const cache = /* @__PURE__ */ new Map();
10781
11930
  let hits = 0;
10782
11931
  let misses = 0;
@@ -10784,18 +11933,6 @@ function createSSRCache(config) {
10784
11933
  if (entry.ttl === 0) return true;
10785
11934
  return Date.now() - entry.timestamp < entry.ttl;
10786
11935
  }
10787
- function evictOldest() {
10788
- if (cache.size < maxSize) return;
10789
- let oldestKey = null;
10790
- let oldestTime = Infinity;
10791
- for (const [key, entry] of cache) {
10792
- if (entry.timestamp < oldestTime) {
10793
- oldestTime = entry.timestamp;
10794
- oldestKey = key;
10795
- }
10796
- }
10797
- if (oldestKey) cache.delete(oldestKey);
10798
- }
10799
11936
  return {
10800
11937
  /** Get cached HTML for a key, or undefined if not cached or expired */
10801
11938
  get(key) {
@@ -10814,7 +11951,7 @@ function createSSRCache(config) {
10814
11951
  },
10815
11952
  /** Cache an HTML string with an optional TTL override */
10816
11953
  set(key, html2, ttl) {
10817
- evictOldest();
11954
+ if (!makeRoomFor(cache, key, maxSize, isValid)) return;
10818
11955
  cache.set(key, {
10819
11956
  html: html2,
10820
11957
  timestamp: Date.now(),
@@ -10847,25 +11984,30 @@ function createSSRCache(config) {
10847
11984
  }
10848
11985
  };
10849
11986
  }
11987
+ var DEFER_IDLE_TIMEOUT_MS = 2e3;
10850
11988
  function deferNonCritical(tasks) {
10851
11989
  if (tasks.length === 0) return;
10852
- const schedule = typeof requestIdleCallback !== "undefined" ? requestIdleCallback : (cb) => setTimeout(cb, 1);
11990
+ const schedule = typeof requestIdleCallback !== "undefined" ? (cb) => {
11991
+ requestIdleCallback(cb, { timeout: DEFER_IDLE_TIMEOUT_MS });
11992
+ } : (cb) => {
11993
+ setTimeout(cb, 1);
11994
+ };
10853
11995
  const queue = [...tasks];
10854
11996
  let index = 0;
10855
11997
  function processNext(deadline) {
11998
+ let ranOne = false;
10856
11999
  while (index < queue.length) {
10857
- if (deadline && deadline.timeRemaining() < 1) {
12000
+ if (ranOne && deadline && deadline.timeRemaining() < 1) {
10858
12001
  schedule(processNext);
10859
12002
  return;
10860
12003
  }
10861
12004
  try {
10862
12005
  queue[index]();
10863
12006
  } catch (e) {
10864
- if (typeof console !== "undefined") {
10865
- console.error("[Startup] Deferred task failed:", e);
10866
- }
12007
+ reportError(e, { phase: "scheduler", name: "deferNonCritical" });
10867
12008
  }
10868
12009
  index++;
12010
+ ranOne = true;
10869
12011
  }
10870
12012
  }
10871
12013
  schedule(processNext);
@@ -11886,20 +13028,20 @@ function createErrorReporter(options) {
11886
13028
  const logToConsole = options?.logToConsole ?? true;
11887
13029
  const maxErrors = options?.maxErrors ?? 500;
11888
13030
  const errors = [];
11889
- function report(error, context2) {
13031
+ function report(error, context) {
11890
13032
  let sibuError;
11891
13033
  if (error instanceof SibuError) {
11892
13034
  sibuError = error;
11893
- if (context2?.component && !sibuError.component) {
11894
- sibuError.component = context2.component;
13035
+ if (context?.component && !sibuError.component) {
13036
+ sibuError.component = context.component;
11895
13037
  }
11896
- if (context2?.props && !sibuError.props) {
11897
- sibuError.props = context2.props;
13038
+ if (context?.props && !sibuError.props) {
13039
+ sibuError.props = context.props;
11898
13040
  }
11899
13041
  } else {
11900
13042
  sibuError = new SibuError(error.message, {
11901
- component: context2?.component,
11902
- props: context2?.props,
13043
+ component: context?.component,
13044
+ props: context?.props,
11903
13045
  cause: error
11904
13046
  });
11905
13047
  sibuError.stack = error.stack;
@@ -11955,9 +13097,9 @@ function withErrorTracking(name, component, reporter) {
11955
13097
  }
11956
13098
  };
11957
13099
  }
11958
- function formatError(error, context2, seen = /* @__PURE__ */ new Set([error])) {
13100
+ function formatError(error, context, seen = /* @__PURE__ */ new Set([error])) {
11959
13101
  const lines = [];
11960
- const componentLabel = context2?.component ?? (error instanceof SibuError ? error.component : void 0);
13102
+ const componentLabel = context?.component ?? (error instanceof SibuError ? error.component : void 0);
11961
13103
  if (componentLabel) {
11962
13104
  lines.push(`[SibuJS:${componentLabel}] ${error.name}: ${error.message}`);
11963
13105
  } else {
@@ -11984,7 +13126,7 @@ function formatError(error, context2, seen = /* @__PURE__ */ new Set([error])) {
11984
13126
  seen.add(cause);
11985
13127
  lines.push("");
11986
13128
  lines.push("Caused by:");
11987
- lines.push(formatError(cause, context2, seen));
13129
+ lines.push(formatError(cause, context, seen));
11988
13130
  }
11989
13131
  return lines.join("\n");
11990
13132
  }
@@ -12142,9 +13284,21 @@ function createDevtoolsOverlay(options) {
12142
13284
  var COUNTER_KEY = /* @__PURE__ */ Symbol.for("sibujs.createId.v1");
12143
13285
  var _counter = globalThis[COUNTER_KEY] ?? (globalThis[COUNTER_KEY] = { n: 0 });
12144
13286
  function createId(prefix = "sibu") {
13287
+ const request = getRequestStore();
13288
+ if (request) {
13289
+ request.idCounter = (request.idCounter ?? 0) + 1;
13290
+ return `${prefix}-${request.idCounter}`;
13291
+ }
12145
13292
  _counter.n++;
12146
13293
  return `${prefix}-${_counter.n}`;
12147
13294
  }
13295
+ function idSegment(key) {
13296
+ let out = "";
13297
+ for (const ch of String(key)) {
13298
+ out += /[A-Za-z0-9-]/.test(ch) ? ch : `_${(ch.codePointAt(0) ?? 0).toString(16)}_`;
13299
+ }
13300
+ return out;
13301
+ }
12148
13302
 
12149
13303
  // src/core/signals/watch.ts
12150
13304
  function watch(getter, callback) {
@@ -12249,7 +13403,7 @@ function combobox(options) {
12249
13403
  els.input.setAttribute("role", "combobox");
12250
13404
  els.input.setAttribute("aria-autocomplete", "list");
12251
13405
  els.input.setAttribute("aria-controls", listboxId);
12252
- const fxTeardown = effect(() => {
13406
+ const fxTeardown = domBinding(() => {
12253
13407
  const open2 = isOpen();
12254
13408
  els.input.setAttribute("aria-expanded", open2 ? "true" : "false");
12255
13409
  els.listbox.hidden = !open2;
@@ -12267,7 +13421,7 @@ function combobox(options) {
12267
13421
  }
12268
13422
  if (activeId) els.input.setAttribute("aria-activedescendant", activeId);
12269
13423
  else els.input.removeAttribute("aria-activedescendant");
12270
- });
13424
+ }, els.input);
12271
13425
  const onInput = () => {
12272
13426
  setQuery(els.input.value);
12273
13427
  open();
@@ -12315,8 +13469,11 @@ function combobox(options) {
12315
13469
  els.input.addEventListener("focus", onFocus);
12316
13470
  els.input.addEventListener("blur", onBlur);
12317
13471
  els.listbox.addEventListener("mousedown", onListboxPointerDown);
13472
+ let tornDown = false;
12318
13473
  const teardown = () => {
12319
- boundComboboxes.delete(els.input);
13474
+ if (tornDown) return;
13475
+ tornDown = true;
13476
+ if (boundComboboxes.get(els.input) === teardown) boundComboboxes.delete(els.input);
12320
13477
  fxTeardown();
12321
13478
  els.input.removeEventListener("input", onInput);
12322
13479
  els.input.removeEventListener("keydown", onKey);
@@ -12361,11 +13518,23 @@ function combobox(options) {
12361
13518
  };
12362
13519
  }
12363
13520
 
13521
+ // src/widgets/attributeSnapshot.ts
13522
+ function snapshotAttributes(el, names) {
13523
+ const previous2 = names.map((name) => [name, el.getAttribute(name)]);
13524
+ return () => {
13525
+ for (const [name, value] of previous2) {
13526
+ if (value === null) el.removeAttribute(name);
13527
+ else el.setAttribute(name, value);
13528
+ }
13529
+ };
13530
+ }
13531
+
12364
13532
  // src/widgets/Tabs.ts
12365
13533
  var boundTablists = /* @__PURE__ */ new WeakMap();
12366
13534
  function tabs(options) {
12367
13535
  const { tabs: tabDefs, defaultTab } = options;
12368
- const initialTab = defaultTab ?? tabDefs.find((t) => !t.disabled)?.id ?? tabDefs[0]?.id ?? "";
13536
+ const firstEnabled = tabDefs.find((t) => !t.disabled)?.id ?? "";
13537
+ const initialTab = defaultTab !== void 0 && tabDefs.some((t) => t.id === defaultTab && !t.disabled) ? defaultTab : firstEnabled;
12369
13538
  const [activeTab, setActiveTabState] = signal(initialTab);
12370
13539
  function setActiveTab(id) {
12371
13540
  const tab = tabDefs.find((t) => t.id === id);
@@ -12412,58 +13581,29 @@ function tabs(options) {
12412
13581
  function bind(els) {
12413
13582
  const existing = boundTablists.get(els.tablist);
12414
13583
  if (existing) return existing;
12415
- const restore = [];
12416
- const prevTablistRole = els.tablist.getAttribute("role");
13584
+ const restore = [snapshotAttributes(els.tablist, ["role"])];
12417
13585
  els.tablist.setAttribute("role", "tablist");
12418
- restore.push(() => {
12419
- if (prevTablistRole === null) els.tablist.removeAttribute("role");
12420
- else els.tablist.setAttribute("role", prevTablistRole);
12421
- });
13586
+ const idPrefix = createId("sibu-tabs");
12422
13587
  for (const def of tabDefs) {
12423
13588
  const tabEl = els.tabs[def.id];
12424
13589
  if (!tabEl) continue;
12425
- const prevRole = tabEl.getAttribute("role");
12426
- const prevId = tabEl.id;
12427
- const prevDisabled = tabEl.getAttribute("aria-disabled");
12428
- const prevControls = tabEl.getAttribute("aria-controls");
13590
+ restore.push(
13591
+ snapshotAttributes(tabEl, ["role", "id", "aria-disabled", "aria-controls", "aria-selected", "tabindex"])
13592
+ );
12429
13593
  tabEl.setAttribute("role", "tab");
12430
- tabEl.setAttribute("id", `sibu-tab-${def.id}`);
13594
+ if (!tabEl.id) tabEl.id = `${idPrefix}-tab-${idSegment(def.id)}`;
12431
13595
  if (def.disabled) tabEl.setAttribute("aria-disabled", "true");
13596
+ else tabEl.removeAttribute("aria-disabled");
12432
13597
  const panelEl = els.panels?.[def.id];
12433
- let prevPanelRole = null;
12434
- let prevPanelId = "";
12435
- let prevPanelLabelledBy = null;
12436
13598
  if (panelEl) {
12437
- prevPanelRole = panelEl.getAttribute("role");
12438
- prevPanelId = panelEl.id;
12439
- prevPanelLabelledBy = panelEl.getAttribute("aria-labelledby");
13599
+ restore.push(snapshotAttributes(panelEl, ["role", "id", "aria-labelledby", "hidden"]));
12440
13600
  panelEl.setAttribute("role", "tabpanel");
12441
- panelEl.setAttribute("id", `sibu-tabpanel-${def.id}`);
12442
- panelEl.setAttribute("aria-labelledby", `sibu-tab-${def.id}`);
12443
- tabEl.setAttribute("aria-controls", `sibu-tabpanel-${def.id}`);
13601
+ if (!panelEl.id) panelEl.id = `${idPrefix}-panel-${idSegment(def.id)}`;
13602
+ panelEl.setAttribute("aria-labelledby", tabEl.id);
13603
+ tabEl.setAttribute("aria-controls", panelEl.id);
12444
13604
  }
12445
- restore.push(() => {
12446
- if (prevRole === null) tabEl.removeAttribute("role");
12447
- else tabEl.setAttribute("role", prevRole);
12448
- if (prevId === "") tabEl.removeAttribute("id");
12449
- else tabEl.id = prevId;
12450
- if (prevDisabled === null) tabEl.removeAttribute("aria-disabled");
12451
- else tabEl.setAttribute("aria-disabled", prevDisabled);
12452
- if (prevControls === null) tabEl.removeAttribute("aria-controls");
12453
- else tabEl.setAttribute("aria-controls", prevControls);
12454
- tabEl.removeAttribute("aria-selected");
12455
- tabEl.removeAttribute("tabindex");
12456
- if (panelEl) {
12457
- if (prevPanelRole === null) panelEl.removeAttribute("role");
12458
- else panelEl.setAttribute("role", prevPanelRole);
12459
- if (prevPanelId === "") panelEl.removeAttribute("id");
12460
- else panelEl.id = prevPanelId;
12461
- if (prevPanelLabelledBy === null) panelEl.removeAttribute("aria-labelledby");
12462
- else panelEl.setAttribute("aria-labelledby", prevPanelLabelledBy);
12463
- }
12464
- });
12465
13605
  }
12466
- const fxTeardown = effect(() => {
13606
+ const fxTeardown = domBinding(() => {
12467
13607
  const active = activeTab();
12468
13608
  for (const def of tabDefs) {
12469
13609
  const tabEl = els.tabs[def.id];
@@ -12474,7 +13614,7 @@ function tabs(options) {
12474
13614
  const panelEl = els.panels?.[def.id];
12475
13615
  if (panelEl) panelEl.hidden = !isAct;
12476
13616
  }
12477
- });
13617
+ }, els.tablist);
12478
13618
  const onKey = (e) => {
12479
13619
  if (e.key === "ArrowRight" || e.key === "ArrowDown") {
12480
13620
  e.preventDefault();
@@ -12511,7 +13651,10 @@ function tabs(options) {
12511
13651
  tabEl.addEventListener("click", fn);
12512
13652
  clickHandlers.push({ el: tabEl, fn });
12513
13653
  }
13654
+ let tornDown = false;
12514
13655
  const teardown = () => {
13656
+ if (tornDown) return;
13657
+ tornDown = true;
12515
13658
  boundTablists.delete(els.tablist);
12516
13659
  fxTeardown();
12517
13660
  els.tablist.removeEventListener("keydown", onKey);
@@ -12537,7 +13680,10 @@ function tabs(options) {
12537
13680
  var boundAccordions = /* @__PURE__ */ new WeakMap();
12538
13681
  function accordion(options) {
12539
13682
  const { items: itemDefs, multiple = false, defaultExpanded = [] } = options;
12540
- const [expandedIds, setExpandedIds] = signal(new Set(defaultExpanded));
13683
+ const knownIds = new Set(itemDefs.map((item) => item.id));
13684
+ const validDefaults = defaultExpanded.filter((id) => knownIds.has(id));
13685
+ const initialExpanded = multiple ? validDefaults : validDefaults.slice(0, 1);
13686
+ const [expandedIds, setExpandedIds] = signal(new Set(initialExpanded));
12541
13687
  const items = derived(
12542
13688
  () => itemDefs.map((item) => ({
12543
13689
  ...item,
@@ -12588,42 +13734,22 @@ function accordion(options) {
12588
13734
  if (existing) return existing;
12589
13735
  }
12590
13736
  const restore = [];
13737
+ const idPrefix = createId("sibu-accordion");
12591
13738
  for (const item of itemDefs) {
12592
13739
  const trig = els.triggers[item.id];
12593
13740
  const panel = els.panels[item.id];
12594
13741
  if (!trig) continue;
12595
- const prevTrigId = trig.id;
12596
- const prevTrigControls = trig.getAttribute("aria-controls");
12597
- trig.id = `sibu-accordion-trigger-${item.id}`;
12598
- let prevPanelRole = null;
12599
- let prevPanelId = "";
12600
- let prevPanelLabelledBy = null;
13742
+ restore.push(snapshotAttributes(trig, ["id", "aria-controls", "aria-expanded"]));
13743
+ if (!trig.id) trig.id = `${idPrefix}-trigger-${idSegment(item.id)}`;
12601
13744
  if (panel) {
12602
- prevPanelRole = panel.getAttribute("role");
12603
- prevPanelId = panel.id;
12604
- prevPanelLabelledBy = panel.getAttribute("aria-labelledby");
13745
+ restore.push(snapshotAttributes(panel, ["role", "id", "aria-labelledby", "hidden"]));
12605
13746
  panel.setAttribute("role", "region");
12606
- panel.id = `sibu-accordion-panel-${item.id}`;
13747
+ if (!panel.id) panel.id = `${idPrefix}-panel-${idSegment(item.id)}`;
12607
13748
  panel.setAttribute("aria-labelledby", trig.id);
12608
13749
  trig.setAttribute("aria-controls", panel.id);
12609
13750
  }
12610
- restore.push(() => {
12611
- if (prevTrigId === "") trig.removeAttribute("id");
12612
- else trig.id = prevTrigId;
12613
- if (prevTrigControls === null) trig.removeAttribute("aria-controls");
12614
- else trig.setAttribute("aria-controls", prevTrigControls);
12615
- trig.removeAttribute("aria-expanded");
12616
- if (panel) {
12617
- if (prevPanelRole === null) panel.removeAttribute("role");
12618
- else panel.setAttribute("role", prevPanelRole);
12619
- if (prevPanelId === "") panel.removeAttribute("id");
12620
- else panel.id = prevPanelId;
12621
- if (prevPanelLabelledBy === null) panel.removeAttribute("aria-labelledby");
12622
- else panel.setAttribute("aria-labelledby", prevPanelLabelledBy);
12623
- }
12624
- });
12625
13751
  }
12626
- const fxTeardown = effect(() => {
13752
+ const fxTeardown = domBinding(() => {
12627
13753
  const ids = expandedIds();
12628
13754
  for (const item of itemDefs) {
12629
13755
  const trig = els.triggers[item.id];
@@ -12633,7 +13759,7 @@ function accordion(options) {
12633
13759
  trig.setAttribute("aria-expanded", expanded ? "true" : "false");
12634
13760
  if (panel) panel.hidden = !expanded;
12635
13761
  }
12636
- });
13762
+ }, idempotencyKey);
12637
13763
  const handlers = [];
12638
13764
  for (const item of itemDefs) {
12639
13765
  const trig = els.triggers[item.id];
@@ -12649,7 +13775,10 @@ function accordion(options) {
12649
13775
  trig.addEventListener("keydown", key);
12650
13776
  handlers.push({ el: trig, click, key });
12651
13777
  }
13778
+ let tornDown = false;
12652
13779
  const teardown = () => {
13780
+ if (tornDown) return;
13781
+ tornDown = true;
12653
13782
  if (idempotencyKey) boundAccordions.delete(idempotencyKey);
12654
13783
  fxTeardown();
12655
13784
  for (const { el, click, key } of handlers) {
@@ -12708,11 +13837,11 @@ function popover() {
12708
13837
  }
12709
13838
  els.popover.setAttribute("aria-labelledby", els.labelledBy.id);
12710
13839
  }
12711
- const fxTeardown = effect(() => {
13840
+ const fxTeardown = domBinding(() => {
12712
13841
  const open2 = isOpen();
12713
13842
  els.trigger.setAttribute("aria-expanded", open2 ? "true" : "false");
12714
13843
  els.popover.hidden = !open2;
12715
- });
13844
+ }, els.trigger);
12716
13845
  const onTriggerClick = (e) => {
12717
13846
  e.preventDefault();
12718
13847
  toggle();
@@ -12735,8 +13864,11 @@ function popover() {
12735
13864
  els.popover.addEventListener("keydown", onKey);
12736
13865
  els.trigger.addEventListener("keydown", onKey);
12737
13866
  document.addEventListener("pointerdown", onDocPointer);
13867
+ let tornDown = false;
12738
13868
  const teardown = () => {
12739
- boundPopovers.delete(els.trigger);
13869
+ if (tornDown) return;
13870
+ tornDown = true;
13871
+ if (boundPopovers.get(els.trigger) === teardown) boundPopovers.delete(els.trigger);
12740
13872
  fxTeardown();
12741
13873
  els.trigger.removeEventListener("click", onTriggerClick);
12742
13874
  els.popover.removeEventListener("keydown", onKey);
@@ -12854,7 +13986,7 @@ function select2(options) {
12854
13986
  els.listbox.setAttribute("aria-multiselectable", multiple ? "true" : "false");
12855
13987
  if (els.listbox.tabIndex < 0) els.listbox.tabIndex = 0;
12856
13988
  const toStr = els.itemToString ?? itemToString ?? ((it) => String(it));
12857
- const fxTeardown = effect(() => {
13989
+ const fxTeardown = domBinding(() => {
12858
13990
  const idx = highlightedIndex();
12859
13991
  const sel = selectedItems();
12860
13992
  let activeId = "";
@@ -12864,13 +13996,14 @@ function select2(options) {
12864
13996
  if (!optEl.id) optEl.id = `${listboxId}-opt-${i2}`;
12865
13997
  optEl.setAttribute("role", "option");
12866
13998
  optEl.setAttribute("aria-selected", sel.includes(items[i2]) ? "true" : "false");
12867
- if (isItemDisabled(items[i2])) optEl.setAttribute("aria-disabled", "true");
13999
+ const disabled = isItemDisabled(items[i2]);
14000
+ if (disabled) optEl.setAttribute("aria-disabled", "true");
12868
14001
  else optEl.removeAttribute("aria-disabled");
12869
- if (i2 === idx) activeId = optEl.id;
14002
+ if (i2 === idx && !disabled) activeId = optEl.id;
12870
14003
  }
12871
14004
  if (activeId) els.listbox.setAttribute("aria-activedescendant", activeId);
12872
14005
  else els.listbox.removeAttribute("aria-activedescendant");
12873
- });
14006
+ }, els.listbox);
12874
14007
  let typeBuffer = "";
12875
14008
  let typeTimer = null;
12876
14009
  const onKey = (e) => {
@@ -12882,10 +14015,12 @@ function select2(options) {
12882
14015
  highlightPrev();
12883
14016
  } else if (e.key === "Home") {
12884
14017
  e.preventDefault();
12885
- if (items.length > 0) setHighlightedIndex(0);
14018
+ const first = nextEnabled(-1, 1);
14019
+ if (first !== -1) setHighlightedIndex(first);
12886
14020
  } else if (e.key === "End") {
12887
14021
  e.preventDefault();
12888
- if (items.length > 0) setHighlightedIndex(items.length - 1);
14022
+ const last = nextEnabled(items.length, -1);
14023
+ if (last !== -1) setHighlightedIndex(last);
12889
14024
  } else if (e.key === "Enter" || e.key === " ") {
12890
14025
  if (highlightedIndex() >= 0) {
12891
14026
  e.preventDefault();
@@ -12903,8 +14038,11 @@ function select2(options) {
12903
14038
  }
12904
14039
  };
12905
14040
  els.listbox.addEventListener("keydown", onKey);
14041
+ let tornDown = false;
12906
14042
  const teardown = () => {
12907
- boundSelects.delete(els.listbox);
14043
+ if (tornDown) return;
14044
+ tornDown = true;
14045
+ if (boundSelects.get(els.listbox) === teardown) boundSelects.delete(els.listbox);
12908
14046
  fxTeardown();
12909
14047
  els.listbox.removeEventListener("keydown", onKey);
12910
14048
  if (typeTimer !== null) clearTimeout(typeTimer);
@@ -12990,9 +14128,9 @@ function tooltip(options) {
12990
14128
  els.tooltip.id = id;
12991
14129
  const prevDescribedBy = els.trigger.getAttribute("aria-describedby");
12992
14130
  els.trigger.setAttribute("aria-describedby", prevDescribedBy ? `${prevDescribedBy} ${id}` : id);
12993
- const fxTeardown = effect(() => {
14131
+ const fxTeardown = domBinding(() => {
12994
14132
  els.tooltip.hidden = !isVisible();
12995
- });
14133
+ }, els.trigger);
12996
14134
  const onTriggerEnter = () => show();
12997
14135
  const onTriggerLeave = () => scheduleHide();
12998
14136
  const onTooltipEnter = () => {
@@ -13017,8 +14155,11 @@ function tooltip(options) {
13017
14155
  els.trigger.addEventListener("keydown", onKey);
13018
14156
  els.tooltip.addEventListener("pointerenter", onTooltipEnter);
13019
14157
  els.tooltip.addEventListener("pointerleave", onTooltipLeave);
14158
+ let tornDown = false;
13020
14159
  const teardown = () => {
13021
- boundTriggers.delete(els.trigger);
14160
+ if (tornDown) return;
14161
+ tornDown = true;
14162
+ if (boundTriggers.get(els.trigger) === teardown) boundTriggers.delete(els.trigger);
13022
14163
  fxTeardown();
13023
14164
  els.trigger.removeEventListener("pointerenter", onTriggerEnter);
13024
14165
  els.trigger.removeEventListener("pointerleave", onTriggerLeave);
@@ -13056,7 +14197,8 @@ function fileUpload(options) {
13056
14197
  const [isDragOver, setDragOver] = signal(false);
13057
14198
  function isAccepted(file) {
13058
14199
  if (!accept) return true;
13059
- const acceptedTypes = accept.split(",").map((t) => t.trim().toLowerCase());
14200
+ const acceptedTypes = accept.split(",").map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0);
14201
+ if (acceptedTypes.length === 0) return true;
13060
14202
  const fileName = file.name.toLowerCase();
13061
14203
  const fileType = file.type.toLowerCase();
13062
14204
  return acceptedTypes.some((pattern2) => {
@@ -13085,16 +14227,17 @@ function fileUpload(options) {
13085
14227
  }
13086
14228
  validFiles.push(file);
13087
14229
  }
14230
+ const committed = multiple ? validFiles : validFiles.slice(-1);
13088
14231
  batch(() => {
13089
14232
  setErrors(newErrors);
13090
- if (validFiles.length > 0) {
14233
+ if (committed.length > 0) {
13091
14234
  if (multiple) {
13092
- setFiles((prev) => [...prev, ...validFiles]);
14235
+ setFiles((prev) => [...prev, ...committed]);
13093
14236
  } else {
13094
- setFiles([validFiles[validFiles.length - 1]]);
14237
+ setFiles(committed);
13095
14238
  }
13096
14239
  if (onFiles) {
13097
- onFiles(validFiles);
14240
+ onFiles(committed);
13098
14241
  }
13099
14242
  }
13100
14243
  });
@@ -13118,8 +14261,15 @@ function fileUpload(options) {
13118
14261
  if (existing) return existing;
13119
14262
  const id = createId("sibu-fileupload");
13120
14263
  const restore = [];
14264
+ const prevAccept = els.input.getAttribute("accept");
14265
+ const prevMultiple = els.input.hasAttribute("multiple");
13121
14266
  if (accept) els.input.accept = accept;
13122
14267
  els.input.multiple = multiple;
14268
+ restore.push(() => {
14269
+ if (prevAccept === null) els.input.removeAttribute("accept");
14270
+ else els.input.setAttribute("accept", prevAccept);
14271
+ els.input.multiple = prevMultiple;
14272
+ });
13123
14273
  let hintId = null;
13124
14274
  if (els.hint) {
13125
14275
  const assignedHintId = !els.hint.id;
@@ -13140,6 +14290,7 @@ function fileUpload(options) {
13140
14290
  if (els.errorRegion) {
13141
14291
  const prevRole = els.errorRegion.getAttribute("role");
13142
14292
  const prevLive = els.errorRegion.getAttribute("aria-live");
14293
+ const prevText = els.errorRegion.textContent;
13143
14294
  els.errorRegion.setAttribute("role", "status");
13144
14295
  els.errorRegion.setAttribute("aria-live", "polite");
13145
14296
  restore.push(() => {
@@ -13147,12 +14298,14 @@ function fileUpload(options) {
13147
14298
  else els.errorRegion.setAttribute("role", prevRole);
13148
14299
  if (prevLive === null) els.errorRegion.removeAttribute("aria-live");
13149
14300
  else els.errorRegion.setAttribute("aria-live", prevLive);
14301
+ els.errorRegion.textContent = prevText;
13150
14302
  });
13151
14303
  }
13152
14304
  if (els.dropZone) {
13153
14305
  const prevDzRole = els.dropZone.getAttribute("role");
13154
14306
  const prevDzLabel = els.dropZone.getAttribute("aria-label");
13155
14307
  const prevDzTabindex = els.dropZone.hasAttribute("tabindex") ? els.dropZone.getAttribute("tabindex") : null;
14308
+ const prevDzDragOver = els.dropZone.getAttribute("data-drag-over");
13156
14309
  els.dropZone.setAttribute("role", "button");
13157
14310
  els.dropZone.setAttribute("aria-label", "File drop zone \u2014 click or press Enter to browse");
13158
14311
  if (els.dropZone.tabIndex < 0) els.dropZone.tabIndex = 0;
@@ -13163,13 +14316,15 @@ function fileUpload(options) {
13163
14316
  else els.dropZone.setAttribute("aria-label", prevDzLabel);
13164
14317
  if (prevDzTabindex === null) els.dropZone.removeAttribute("tabindex");
13165
14318
  else els.dropZone.setAttribute("tabindex", prevDzTabindex);
14319
+ if (prevDzDragOver === null) els.dropZone.removeAttribute("data-drag-over");
14320
+ else els.dropZone.setAttribute("data-drag-over", prevDzDragOver);
13166
14321
  });
13167
14322
  }
13168
- const fxTeardown = effect(() => {
14323
+ const fxTeardown = domBinding(() => {
13169
14324
  const errs = errors();
13170
14325
  if (els.errorRegion) els.errorRegion.textContent = errs.join(". ");
13171
14326
  if (els.dropZone) els.dropZone.setAttribute("data-drag-over", isDragOver() ? "true" : "false");
13172
- });
14327
+ }, els.input);
13173
14328
  const onChange = () => {
13174
14329
  if (els.input.files) addFiles(els.input.files);
13175
14330
  };
@@ -13198,7 +14353,10 @@ function fileUpload(options) {
13198
14353
  els.dropZone.addEventListener("dragleave", onDragLeave);
13199
14354
  els.dropZone.addEventListener("drop", onDrop);
13200
14355
  }
14356
+ let tornDown = false;
13201
14357
  const teardown = () => {
14358
+ if (tornDown) return;
14359
+ tornDown = true;
13202
14360
  boundFileUploads.delete(els.input);
13203
14361
  fxTeardown();
13204
14362
  els.input.removeEventListener("change", onChange);
@@ -13227,12 +14385,15 @@ function fileUpload(options) {
13227
14385
  }
13228
14386
 
13229
14387
  // src/widgets/contentEditable.ts
14388
+ function toInertText(html2) {
14389
+ return stripHtml(String(html2)).replace(/<(?=[a-z!/?])/gi, "&lt;");
14390
+ }
13230
14391
  function contentEditable(element) {
13231
14392
  const [content, setContentInternal] = signal("");
13232
14393
  const [isFocused, setFocused] = signal(false);
13233
14394
  function setContent(input2) {
13234
14395
  if (typeof input2 === "string") {
13235
- setContentInternal(input2);
14396
+ setContentInternal(toInertText(input2));
13236
14397
  return;
13237
14398
  }
13238
14399
  if (typeof input2.text === "string") {
@@ -13241,7 +14402,7 @@ function contentEditable(element) {
13241
14402
  }
13242
14403
  if (typeof input2.html === "string") {
13243
14404
  const shouldSanitize = input2.sanitize !== false;
13244
- setContentInternal(shouldSanitize ? stripHtml(input2.html) : input2.html);
14405
+ setContentInternal(shouldSanitize ? toInertText(input2.html) : input2.html);
13245
14406
  return;
13246
14407
  }
13247
14408
  setContentInternal("");
@@ -13253,7 +14414,7 @@ function contentEditable(element) {
13253
14414
  const range = selection.getRangeAt(0);
13254
14415
  if (element && !element.contains(range.commonAncestorContainer)) return;
13255
14416
  const ancestor = range.commonAncestorContainer;
13256
- const existingWrap = findAncestorByTag(
14417
+ const existingWrap = findWrapperWithinEditor(
13257
14418
  ancestor instanceof HTMLElement ? ancestor : ancestor.parentElement,
13258
14419
  tagName
13259
14420
  );
@@ -13294,14 +14455,19 @@ function contentEditable(element) {
13294
14455
  selection.addRange(newRange);
13295
14456
  }
13296
14457
  }
13297
- function findAncestorByTag(el, tagName) {
14458
+ function findWrapperWithinEditor(el, tagName) {
13298
14459
  const upper = tagName.toUpperCase();
13299
14460
  while (el) {
14461
+ if (el === element || isEditingHost(el)) return null;
13300
14462
  if (el.tagName === upper) return el;
13301
14463
  el = el.parentElement;
13302
14464
  }
13303
14465
  return null;
13304
14466
  }
14467
+ function isEditingHost(el) {
14468
+ const attr = el.getAttribute("contenteditable");
14469
+ return attr !== null && attr.toLowerCase() !== "false";
14470
+ }
13305
14471
  function bold() {
13306
14472
  wrapSelection("strong");
13307
14473
  }
@@ -13437,15 +14603,36 @@ function datePicker(options) {
13437
14603
  els.grid.setAttribute("role", "grid");
13438
14604
  if (els.grid.tabIndex < 0) els.grid.tabIndex = 0;
13439
14605
  let pendingFocus = false;
13440
- const fxTeardown = effect(() => {
14606
+ const touchedCells = /* @__PURE__ */ new Map();
14607
+ const restoreAttr = (el, name, value) => {
14608
+ if (value === null) el.removeAttribute(name);
14609
+ else el.setAttribute(name, value);
14610
+ };
14611
+ const restoreCell = (cell, snap) => {
14612
+ restoreAttr(cell, "role", snap.role);
14613
+ restoreAttr(cell, "aria-selected", snap.selected);
14614
+ restoreAttr(cell, "aria-disabled", snap.disabled);
14615
+ restoreAttr(cell, "tabindex", snap.tabindex);
14616
+ };
14617
+ const fxTeardown = domBinding(() => {
13441
14618
  const sel = selectedDate();
13442
14619
  const view = viewDate();
13443
14620
  const days = daysInMonth();
13444
14621
  els.grid.setAttribute("aria-label", view.toLocaleDateString(void 0, { month: "long", year: "numeric" }));
13445
14622
  let viewCell = null;
14623
+ const displayed = /* @__PURE__ */ new Set();
13446
14624
  for (const d of days) {
13447
14625
  const cell = els.cell(d.date);
13448
14626
  if (!cell) continue;
14627
+ displayed.add(cell);
14628
+ if (!touchedCells.has(cell)) {
14629
+ touchedCells.set(cell, {
14630
+ role: cell.getAttribute("role"),
14631
+ selected: cell.getAttribute("aria-selected"),
14632
+ disabled: cell.getAttribute("aria-disabled"),
14633
+ tabindex: cell.getAttribute("tabindex")
14634
+ });
14635
+ }
13449
14636
  cell.setAttribute("role", "gridcell");
13450
14637
  cell.setAttribute("aria-selected", sel && isSameCalendarDay(sel, d.date) ? "true" : "false");
13451
14638
  if (d.isDisabled) cell.setAttribute("aria-disabled", "true");
@@ -13454,11 +14641,17 @@ function datePicker(options) {
13454
14641
  cell.tabIndex = isView ? 0 : -1;
13455
14642
  if (isView) viewCell = cell;
13456
14643
  }
14644
+ for (const [cell, snap] of touchedCells) {
14645
+ if (!displayed.has(cell)) {
14646
+ restoreCell(cell, snap);
14647
+ touchedCells.delete(cell);
14648
+ }
14649
+ }
13457
14650
  if (pendingFocus && viewCell && typeof viewCell.focus === "function") {
13458
14651
  pendingFocus = false;
13459
14652
  viewCell.focus();
13460
14653
  }
13461
- });
14654
+ }, els.grid);
13462
14655
  function isSameCalendarDay(a2, b2) {
13463
14656
  return a2.getFullYear() === b2.getFullYear() && a2.getMonth() === b2.getMonth() && a2.getDate() === b2.getDate();
13464
14657
  }
@@ -13513,8 +14706,11 @@ function datePicker(options) {
13513
14706
  }
13514
14707
  };
13515
14708
  els.grid.addEventListener("keydown", onKey);
14709
+ let tornDown = false;
13516
14710
  const teardown = () => {
13517
- boundDatePickers.delete(els.grid);
14711
+ if (tornDown) return;
14712
+ tornDown = true;
14713
+ if (boundDatePickers.get(els.grid) === teardown) boundDatePickers.delete(els.grid);
13518
14714
  fxTeardown();
13519
14715
  els.grid.removeEventListener("keydown", onKey);
13520
14716
  if (prevGridRole === null) els.grid.removeAttribute("role");
@@ -13523,6 +14719,8 @@ function datePicker(options) {
13523
14719
  else els.grid.setAttribute("tabindex", prevGridTabIndex);
13524
14720
  if (prevGridLabel === null) els.grid.removeAttribute("aria-label");
13525
14721
  else els.grid.setAttribute("aria-label", prevGridLabel);
14722
+ for (const [cell, snap] of touchedCells) restoreCell(cell, snap);
14723
+ touchedCells.clear();
13526
14724
  };
13527
14725
  boundDatePickers.set(els.grid, teardown);
13528
14726
  return teardown;
@@ -13625,53 +14823,119 @@ function zustandAdapter(options) {
13625
14823
  }
13626
14824
 
13627
14825
  // src/ecosystem/ui/componentAdapter.ts
14826
+ var _themeLayers = globalSingleton(
14827
+ /* @__PURE__ */ Symbol.for("sibujs.themeLayers.v1"),
14828
+ () => /* @__PURE__ */ new WeakMap()
14829
+ );
14830
+ function commitThemeProperty(root, name, record) {
14831
+ const top = record.layers[record.layers.length - 1];
14832
+ if (top) {
14833
+ root.style.setProperty(name, top.value);
14834
+ return;
14835
+ }
14836
+ _themeLayers.get(root)?.delete(name);
14837
+ if (record.original.value === "") root.style.removeProperty(name);
14838
+ else root.style.setProperty(name, record.original.value, record.original.priority);
14839
+ }
14840
+ function setThemeLayer(root, name, handle, value) {
14841
+ let properties = _themeLayers.get(root);
14842
+ if (!properties) _themeLayers.set(root, properties = /* @__PURE__ */ new Map());
14843
+ let record = properties.get(name);
14844
+ if (!record) {
14845
+ record = {
14846
+ original: { value: root.style.getPropertyValue(name), priority: root.style.getPropertyPriority(name) },
14847
+ layers: []
14848
+ };
14849
+ properties.set(name, record);
14850
+ }
14851
+ const layer = record.layers.find((l) => l.handle === handle);
14852
+ if (layer) layer.value = value;
14853
+ else record.layers.push({ handle, value });
14854
+ commitThemeProperty(root, name, record);
14855
+ }
14856
+ function removeThemeLayer(root, name, handle) {
14857
+ const record = _themeLayers.get(root)?.get(name);
14858
+ if (!record) return;
14859
+ const index = record.layers.findIndex((l) => l.handle === handle);
14860
+ if (index === -1) return;
14861
+ record.layers.splice(index, 1);
14862
+ commitThemeProperty(root, name, record);
14863
+ }
14864
+ function ownString(map2, key) {
14865
+ if (!map2 || key === void 0 || !Object.hasOwn(map2, key)) return void 0;
14866
+ const value = map2[key];
14867
+ return typeof value === "string" ? value : void 0;
14868
+ }
13628
14869
  function createTheme(initial) {
13629
14870
  const [getConfig, setConfig] = signal(initial);
13630
14871
  function resolveClass(component, variant) {
13631
14872
  const config = getConfig();
13632
14873
  const overrideKey = variant ? `${component}-${variant}` : component;
13633
- if (config.classOverrides?.[overrideKey]) {
13634
- return config.classOverrides[overrideKey];
13635
- }
14874
+ const override = ownString(config.classOverrides, overrideKey);
14875
+ if (override) return override;
13636
14876
  const base2 = `${config.prefix}-${component}`;
13637
14877
  return variant ? `${base2} ${config.prefix}-${component}--${variant}` : base2;
13638
14878
  }
13639
14879
  function setTheme(partial) {
13640
14880
  setConfig((prev) => ({ ...prev, ...partial }));
13641
14881
  }
13642
- return { config: getConfig, setTheme, resolveClass };
14882
+ function applyTo(root) {
14883
+ const handle = {};
14884
+ let applied = /* @__PURE__ */ new Set();
14885
+ const stop2 = effect(() => {
14886
+ const variables = getConfig().variables ?? {};
14887
+ const next = new Set(Object.keys(variables));
14888
+ for (const name of applied) {
14889
+ if (!next.has(name)) removeThemeLayer(root, name, handle);
14890
+ }
14891
+ for (const [name, value] of Object.entries(variables)) setThemeLayer(root, name, handle, value);
14892
+ applied = next;
14893
+ });
14894
+ let released = false;
14895
+ return () => {
14896
+ if (released) return;
14897
+ released = true;
14898
+ stop2();
14899
+ for (const name of applied) removeThemeLayer(root, name, handle);
14900
+ applied.clear();
14901
+ };
14902
+ }
14903
+ return { config: getConfig, setTheme, resolveClass, applyTo };
13643
14904
  }
13644
14905
  function componentAdapter(config) {
13645
14906
  const theme = createTheme({ prefix: config.prefix });
14907
+ const reprefix = (cls, prefix) => prefix !== config.prefix && cls.startsWith(`${config.prefix}-`) ? `${prefix}-${cls.slice(config.prefix.length + 1)}` : cls;
13646
14908
  const components = {};
13647
14909
  for (const [name, mapping] of Object.entries(config.components)) {
13648
14910
  const factory = tagFactory(mapping.tag || "div");
13649
- components[name] = (props = {}) => {
14911
+ components[name] = (props = {}, children) => {
13650
14912
  const { variant, size, class: userClass, ...rest } = props;
13651
- const classes = [mapping.baseClass];
13652
- if (variant && mapping.variants?.[variant]) {
13653
- classes.push(mapping.variants[variant]);
13654
- }
13655
- if (size && mapping.sizes?.[size]) {
13656
- classes.push(mapping.sizes[size]);
13657
- }
13658
- let finalClass;
13659
- if (typeof userClass === "function") {
13660
- finalClass = () => {
13661
- const extra = userClass();
13662
- return extra ? `${classes.join(" ")} ${extra}` : classes.join(" ");
13663
- };
13664
- } else if (typeof userClass === "string" && userClass) {
13665
- finalClass = `${classes.join(" ")} ${userClass}`;
13666
- } else {
13667
- finalClass = classes.join(" ");
13668
- }
14913
+ const themeClasses = () => {
14914
+ const current = theme.config();
14915
+ const overrides = current.classOverrides ?? {};
14916
+ const pick = (key, fallback) => Object.hasOwn(overrides, key) ? overrides[key] : reprefix(fallback, current.prefix);
14917
+ const classes = [pick(name, mapping.baseClass)];
14918
+ const variantClass = ownString(mapping.variants, variant);
14919
+ if (variant && variantClass) {
14920
+ classes.push(pick(`${name}-${variant}`, variantClass));
14921
+ }
14922
+ const sizeClass = ownString(mapping.sizes, size);
14923
+ if (size && sizeClass) {
14924
+ classes.push(pick(`${name}-${size}`, sizeClass));
14925
+ }
14926
+ return classes.filter(Boolean).join(" ");
14927
+ };
14928
+ const finalClass = () => {
14929
+ const base2 = themeClasses();
14930
+ const extra = typeof userClass === "function" ? userClass() : userClass;
14931
+ return typeof extra === "string" && extra ? `${base2} ${extra}` : base2;
14932
+ };
13669
14933
  const mergedProps = {
13670
14934
  ...mapping.defaultProps,
13671
14935
  ...rest,
13672
14936
  class: finalClass
13673
14937
  };
13674
- return factory(mergedProps);
14938
+ return children === void 0 ? factory(mergedProps) : factory(mergedProps, children);
13675
14939
  };
13676
14940
  }
13677
14941
  return { name: config.name, components, theme };
@@ -13886,6 +15150,8 @@ var materialAdapter = componentAdapter(materialConfig);
13886
15150
  Features,
13887
15151
  FocusTrap,
13888
15152
  Head,
15153
+ MigrationStorageError,
15154
+ PluginInstallCancelledError,
13889
15155
  Priority,
13890
15156
  RenderProp,
13891
15157
  SibuError,
@@ -13894,6 +15160,7 @@ var materialAdapter = componentAdapter(materialConfig);
13894
15160
  VirtualList,
13895
15161
  __resetDialogStack,
13896
15162
  __resetQueryCache,
15163
+ __resetSpeechCoordinator,
13897
15164
  accordion,
13898
15165
  animate,
13899
15166
  animationFrame,
@@ -14029,6 +15296,7 @@ var materialAdapter = componentAdapter(materialConfig);
14029
15296
  isDangerousMetaRefresh,
14030
15297
  isDebugEnabled,
14031
15298
  isHMRAvailable,
15299
+ isPluginInstallCancelledError,
14032
15300
  isWasmCached,
14033
15301
  island,
14034
15302
  keyboard,
@@ -14096,6 +15364,7 @@ var materialAdapter = componentAdapter(materialConfig);
14096
15364
  renderToStream,
14097
15365
  renderToString,
14098
15366
  renderToSuspenseStream,
15367
+ renderWithLoader,
14099
15368
  required,
14100
15369
  resetIdCounter,
14101
15370
  resetPlugins,
@@ -14167,6 +15436,7 @@ var materialAdapter = componentAdapter(materialConfig);
14167
15436
  withBoundary,
14168
15437
  withDefaults,
14169
15438
  withErrorTracking,
15439
+ withLoader,
14170
15440
  withProps,
14171
15441
  withRetry,
14172
15442
  withScopedStyle,