sibujs 4.5.0 → 4.7.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 (116) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +326 -133
  3. package/dist/browser.d.cts +46 -14
  4. package/dist/browser.d.ts +46 -14
  5. package/dist/browser.js +8 -6
  6. package/dist/build.cjs +2094 -1033
  7. package/dist/build.d.cts +133 -44
  8. package/dist/build.d.ts +133 -44
  9. package/dist/build.js +1372 -574
  10. package/dist/cdn.dev.global.js +10 -10
  11. package/dist/cdn.full.dev.global.js +11 -11
  12. package/dist/cdn.full.global.js +10 -10
  13. package/dist/cdn.global.js +10 -10
  14. package/dist/{chunk-2DCGACUU.js → chunk-2BPG2XDA.js} +251 -66
  15. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  16. package/dist/{chunk-4PMLNECI.js → chunk-3QWBSL5R.js} +98 -29
  17. package/dist/{chunk-KKLW7YWL.js → chunk-4AWA2PVD.js} +275 -144
  18. package/dist/chunk-5HZXGZ6T.js +24 -0
  19. package/dist/{chunk-PCT43HW3.js → chunk-7LUJQAOJ.js} +1 -1
  20. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  21. package/dist/chunk-BC2SECJD.js +44 -0
  22. package/dist/{chunk-TBYTO6BS.js → chunk-BSY63EM6.js} +136 -80
  23. package/dist/{chunk-FTIR4QW2.js → chunk-DKXRACVN.js} +6 -8
  24. package/dist/{chunk-ONOHFDLG.js → chunk-FNJXNGYZ.js} +3 -3
  25. package/dist/{chunk-XZZOBQAY.js → chunk-FQRUXCEE.js} +6 -5
  26. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  27. package/dist/chunk-J6FW5TV6.js +233 -0
  28. package/dist/{chunk-UGRX3S57.js → chunk-JWKYU5GV.js} +22 -4
  29. package/dist/chunk-NYNYSPK7.js +318 -0
  30. package/dist/{chunk-RBTPLM32.js → chunk-RUSSKG6G.js} +13 -12
  31. package/dist/{chunk-7LN645I6.js → chunk-SLM3IA34.js} +3 -3
  32. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  33. package/dist/{chunk-B3WHI2QA.js → chunk-UNWRJRKC.js} +55 -31
  34. package/dist/{chunk-KEISJXBU.js → chunk-UOL2ECCS.js} +44 -17
  35. package/dist/{chunk-RJE2BNI4.js → chunk-VZSG24LS.js} +108 -45
  36. package/dist/{chunk-RIXRAYIU.js → chunk-WEQ3DMVL.js} +10 -4
  37. package/dist/{chunk-3JZ4L5TJ.js → chunk-WOLJZUFQ.js} +354 -90
  38. package/dist/{chunk-S373NSMK.js → chunk-XYV3EDB7.js} +283 -123
  39. package/dist/{chunk-VZKNK2V7.js → chunk-XZR4PXRE.js} +296 -38
  40. package/dist/{chunk-GW3SCCZG.js → chunk-Z3OHK6QT.js} +153 -110
  41. package/dist/{chunk-OMJJM3KM.js → chunk-ZVL7TY4K.js} +287 -130
  42. package/dist/{contracts-DBdg9J_a.d.ts → contracts-CLqzJnOV.d.ts} +36 -17
  43. package/dist/{contracts-DBdg9J_a.d.cts → contracts-CTOJXu-x.d.cts} +36 -17
  44. package/dist/{customElement-OB9CIsc5.d.cts → customElement-MmInOW1U.d.cts} +21 -0
  45. package/dist/{customElement-OB9CIsc5.d.ts → customElement-MmInOW1U.d.ts} +21 -0
  46. package/dist/data.cjs +410 -155
  47. package/dist/data.d.cts +166 -12
  48. package/dist/data.d.ts +166 -12
  49. package/dist/data.js +12 -9
  50. package/dist/devtools.cjs +98 -61
  51. package/dist/devtools.js +7 -8
  52. package/dist/dispose-GEIG2KOF.js +28 -0
  53. package/dist/ecosystem.cjs +372 -126
  54. package/dist/ecosystem.d.cts +20 -3
  55. package/dist/ecosystem.d.ts +20 -3
  56. package/dist/ecosystem.js +12 -12
  57. package/dist/extras.cjs +2366 -909
  58. package/dist/extras.d.cts +11 -9
  59. package/dist/extras.d.ts +11 -9
  60. package/dist/extras.js +39 -28
  61. package/dist/index.cjs +421 -158
  62. package/dist/index.d.cts +213 -171
  63. package/dist/index.d.ts +213 -171
  64. package/dist/index.js +24 -27
  65. package/dist/motion.cjs +118 -44
  66. package/dist/motion.js +5 -5
  67. package/dist/patterns.cjs +344 -53
  68. package/dist/patterns.d.cts +28 -9
  69. package/dist/patterns.d.ts +28 -9
  70. package/dist/patterns.js +8 -8
  71. package/dist/performance.cjs +325 -220
  72. package/dist/performance.d.cts +2 -2
  73. package/dist/performance.d.ts +2 -2
  74. package/dist/performance.js +8 -9
  75. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  76. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  77. package/dist/plugins.cjs +664 -233
  78. package/dist/plugins.d.cts +127 -14
  79. package/dist/plugins.d.ts +127 -14
  80. package/dist/plugins.js +96 -42
  81. package/dist/signal-EotCj4hS.d.cts +110 -0
  82. package/dist/signal-EotCj4hS.d.ts +110 -0
  83. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  84. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  85. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  86. package/dist/ssr.cjs +229 -84
  87. package/dist/ssr.d.cts +9 -3
  88. package/dist/ssr.d.ts +9 -3
  89. package/dist/ssr.js +11 -12
  90. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  91. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  92. package/dist/tagFactory-8qL9LCIx.d.cts +156 -0
  93. package/dist/tagFactory-BL2fymez.d.ts +156 -0
  94. package/dist/testing.cjs +2503 -2191
  95. package/dist/testing.d.cts +56 -5
  96. package/dist/testing.d.ts +56 -5
  97. package/dist/testing.js +580 -307
  98. package/dist/types-CJFViL6Q.d.cts +26 -0
  99. package/dist/types-CJFViL6Q.d.ts +26 -0
  100. package/dist/ui.cjs +732 -329
  101. package/dist/ui.d.cts +41 -7
  102. package/dist/ui.d.ts +41 -7
  103. package/dist/ui.js +151 -56
  104. package/dist/widgets.cjs +267 -291
  105. package/dist/widgets.js +9 -10
  106. package/package.json +4 -2
  107. package/dist/chunk-2WLZ6757.js +0 -149
  108. package/dist/chunk-CCSJMTRN.js +0 -15
  109. package/dist/chunk-QKRPLZ2V.js +0 -108
  110. package/dist/chunk-VUF4ALSW.js +0 -60
  111. package/dist/chunk-WWV3SJ3L.js +0 -131
  112. package/dist/dispose-46BOMMQJ.js +0 -19
  113. package/dist/plugin-D30wlGW5.d.cts +0 -71
  114. package/dist/plugin-D30wlGW5.d.ts +0 -71
  115. package/dist/tagFactory-DVoDpHye.d.cts +0 -215
  116. package/dist/tagFactory-DVoDpHye.d.ts +0 -215
package/dist/browser.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // browser.ts
21
21
  var browser_exports = {};
22
22
  __export(browser_exports, {
23
+ __resetSpeechCoordinator: () => __resetSpeechCoordinator,
23
24
  animationFrame: () => animationFrame,
24
25
  battery: () => battery,
25
26
  bounds: () => bounds,
@@ -163,6 +164,25 @@ function reportError(error, context) {
163
164
  });
164
165
  }
165
166
 
167
+ // src/core/rendering/dispose.ts
168
+ function resolveCapture(capture) {
169
+ let frame = capture;
170
+ while (frame?.closed) frame = frame.forwardTo;
171
+ return frame ?? null;
172
+ }
173
+ var registrationCaptures = [];
174
+ function currentDisposerCapture() {
175
+ return resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
176
+ }
177
+ function beginDisposerCapture(capture) {
178
+ if (capture === null && registrationCaptures.length === 0) return false;
179
+ registrationCaptures.push(resolveCapture(capture));
180
+ return true;
181
+ }
182
+ function endDisposerCapture() {
183
+ registrationCaptures.pop();
184
+ }
185
+
166
186
  // src/reactivity/track-core.ts
167
187
  var POOL_MAX = 4096;
168
188
  var nodePool = [];
@@ -321,6 +341,7 @@ function retrack(effectFn, subscriber) {
321
341
  }
322
342
  currentSubscriber = subscriber;
323
343
  const sub = subscriber;
344
+ if (sub._cap === void 0) sub._cap = currentDisposerCapture();
324
345
  const epoch = ++subscriberEpochCounter;
325
346
  sub._epoch = epoch;
326
347
  sub._structDirty = false;
@@ -512,7 +533,12 @@ function drainQueue() {
512
533
  quarantined.add(sub);
513
534
  continue;
514
535
  }
515
- safeInvoke(sub);
536
+ const pushed = beginDisposerCapture(sub._cap ?? null);
537
+ try {
538
+ safeInvoke(sub);
539
+ } finally {
540
+ if (pushed) endDisposerCapture();
541
+ }
516
542
  }
517
543
  }
518
544
  function drainNotificationQueue() {
@@ -648,7 +674,7 @@ function forEachSubscriber(signal2, visit) {
648
674
  }
649
675
 
650
676
  // src/reactivity/track.ts
651
- var _runtimeVersion = true ? "4.5.0" : "dev";
677
+ var _runtimeVersion = true ? "4.7.0" : "dev";
652
678
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
653
679
  function resolveReactiveApi() {
654
680
  const g = globalThis;
@@ -754,6 +780,14 @@ var batch = API2.batch;
754
780
  var enqueueBatchedSignal = API2.enqueueBatchedSignal;
755
781
  var isBatching = API2.isBatching;
756
782
 
783
+ // src/core/devtoolsHook.ts
784
+ function emitDevtools(hook, event, payload) {
785
+ try {
786
+ hook.emit(event, payload);
787
+ } catch {
788
+ }
789
+ }
790
+
757
791
  // src/core/signals/signal.ts
758
792
  var _g = globalThis;
759
793
  function signal(initial, options) {
@@ -787,7 +821,7 @@ function signal(initial, options) {
787
821
  state2.__v++;
788
822
  if (DEV) {
789
823
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
790
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
824
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
791
825
  }
792
826
  if (!enqueueBatchedSignal(state2)) {
793
827
  notifySubscribers2(state2);
@@ -801,7 +835,7 @@ function signal(initial, options) {
801
835
  state2.value = newValue;
802
836
  state2.__v++;
803
837
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
804
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
838
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
805
839
  if (!enqueueBatchedSignal(state2)) {
806
840
  notifySubscribers2(state2);
807
841
  }
@@ -820,7 +854,7 @@ function signal(initial, options) {
820
854
  }
821
855
  if (DEV) {
822
856
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
823
- if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
857
+ if (hook) emitDevtools(hook, "signal:create", { signal: state2, name: debugName, getter: get, initial });
824
858
  }
825
859
  return [get, set];
826
860
  }
@@ -998,12 +1032,17 @@ function effect(effectFn, options) {
998
1032
  try {
999
1033
  retrack2(ctx.bodyFn, ctx.subscriber);
1000
1034
  if (ctx.rerunPending) drainReruns(ctx);
1035
+ } catch (err) {
1036
+ ctx.disposed = true;
1037
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1038
+ cleanup2(ctx.subscriber);
1039
+ throw err;
1001
1040
  } finally {
1002
1041
  ctx.running = false;
1003
1042
  ctx.rerunPending = false;
1004
1043
  }
1005
1044
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1006
- if (hook) hook.emit("effect:create", { effectFn });
1045
+ if (hook) emitDevtools(hook, "effect:create", { effectFn });
1007
1046
  return () => disposeEffect(ctx);
1008
1047
  }
1009
1048
 
@@ -1279,13 +1318,15 @@ function clipboard() {
1279
1318
  const [copied, setCopied] = signal(false);
1280
1319
  let copiedTimer = null;
1281
1320
  let disposed = false;
1321
+ let generation = 0;
1282
1322
  async function copy(value) {
1283
1323
  if (disposed) return;
1284
1324
  if (typeof navigator === "undefined" || !navigator.clipboard) {
1285
1325
  return;
1286
1326
  }
1327
+ const mine = ++generation;
1287
1328
  await navigator.clipboard.writeText(value);
1288
- if (disposed) return;
1329
+ if (disposed || mine !== generation) return;
1289
1330
  setText(value);
1290
1331
  setCopied(true);
1291
1332
  if (copiedTimer !== null) clearTimeout(copiedTimer);
@@ -1297,6 +1338,7 @@ function clipboard() {
1297
1338
  }
1298
1339
  function dispose() {
1299
1340
  disposed = true;
1341
+ generation++;
1300
1342
  if (copiedTimer !== null) {
1301
1343
  clearTimeout(copiedTimer);
1302
1344
  copiedTimer = null;
@@ -1324,15 +1366,26 @@ function draggable(element, data) {
1324
1366
  let currentEl = null;
1325
1367
  let onDragStart = null;
1326
1368
  let onDragEnd = null;
1369
+ let prevDraggableAttr = null;
1370
+ function detach() {
1371
+ if (!currentEl) return;
1372
+ if (onDragStart) currentEl.removeEventListener("dragstart", onDragStart);
1373
+ if (onDragEnd) currentEl.removeEventListener("dragend", onDragEnd);
1374
+ if (prevDraggableAttr === null) currentEl.removeAttribute("draggable");
1375
+ else currentEl.setAttribute("draggable", prevDraggableAttr);
1376
+ currentEl = null;
1377
+ onDragStart = null;
1378
+ onDragEnd = null;
1379
+ setIsDragging(false);
1380
+ }
1327
1381
  const getter = resolveTarget2(element);
1328
1382
  const cleanup3 = effect(() => {
1329
- if (currentEl && onDragStart && onDragEnd) {
1330
- currentEl.removeEventListener("dragstart", onDragStart);
1331
- currentEl.removeEventListener("dragend", onDragEnd);
1332
- }
1333
1383
  const el = getter();
1334
- currentEl = el;
1384
+ if (el === currentEl) return;
1385
+ detach();
1335
1386
  if (!el) return;
1387
+ currentEl = el;
1388
+ prevDraggableAttr = el.getAttribute("draggable");
1336
1389
  el.draggable = true;
1337
1390
  onDragStart = (e) => {
1338
1391
  setIsDragging(true);
@@ -1346,16 +1399,16 @@ function draggable(element, data) {
1346
1399
  el.addEventListener("dragstart", onDragStart);
1347
1400
  el.addEventListener("dragend", onDragEnd);
1348
1401
  });
1402
+ let disposed = false;
1349
1403
  function dispose() {
1404
+ if (disposed) return;
1405
+ disposed = true;
1350
1406
  cleanup3();
1351
- if (currentEl && onDragStart && onDragEnd) {
1352
- currentEl.removeEventListener("dragstart", onDragStart);
1353
- currentEl.removeEventListener("dragend", onDragEnd);
1354
- currentEl = null;
1355
- }
1407
+ detach();
1356
1408
  }
1357
1409
  return { isDragging, dispose };
1358
1410
  }
1411
+ var DROP_ZONE_EXIT_GRACE_MS = 600;
1359
1412
  function dropZone(element, options) {
1360
1413
  const [isOver, setIsOver] = signal(false);
1361
1414
  if (typeof window === "undefined") {
@@ -1367,30 +1420,66 @@ function dropZone(element, options) {
1367
1420
  let onDragEnter = null;
1368
1421
  let onDragLeave = null;
1369
1422
  let onDrop = null;
1370
- const getter = resolveTarget2(element);
1371
- const cleanup3 = effect(() => {
1423
+ let depth = 0;
1424
+ let exitTimer = null;
1425
+ function cancelPendingExit() {
1426
+ if (exitTimer !== null) {
1427
+ clearTimeout(exitTimer);
1428
+ exitTimer = null;
1429
+ }
1430
+ }
1431
+ function resetOver() {
1432
+ cancelPendingExit();
1433
+ depth = 0;
1434
+ setIsOver(false);
1435
+ }
1436
+ function detach() {
1372
1437
  if (currentEl && onDragOver && onDragEnter && onDragLeave && onDrop) {
1373
1438
  currentEl.removeEventListener("dragover", onDragOver);
1374
1439
  currentEl.removeEventListener("dragenter", onDragEnter);
1375
1440
  currentEl.removeEventListener("dragleave", onDragLeave);
1376
1441
  currentEl.removeEventListener("drop", onDrop);
1377
1442
  }
1443
+ currentEl = null;
1444
+ resetOver();
1445
+ }
1446
+ const getter = resolveTarget2(element);
1447
+ const cleanup3 = effect(() => {
1378
1448
  const el = getter();
1379
- currentEl = el;
1449
+ if (el === currentEl) return;
1450
+ detach();
1380
1451
  if (!el) return;
1452
+ currentEl = el;
1381
1453
  onDragOver = (e) => {
1382
1454
  e.preventDefault();
1455
+ cancelPendingExit();
1383
1456
  };
1384
1457
  onDragEnter = (e) => {
1385
1458
  e.preventDefault();
1459
+ cancelPendingExit();
1460
+ depth++;
1386
1461
  setIsOver(true);
1387
1462
  };
1388
- onDragLeave = () => {
1389
- setIsOver(false);
1463
+ onDragLeave = (e) => {
1464
+ depth = Math.max(0, depth - 1);
1465
+ if (depth === 0) {
1466
+ resetOver();
1467
+ return;
1468
+ }
1469
+ const to = e.relatedTarget;
1470
+ if (to != null && typeof to.nodeType === "number") {
1471
+ if (!el.contains(to)) resetOver();
1472
+ return;
1473
+ }
1474
+ cancelPendingExit();
1475
+ exitTimer = setTimeout(() => {
1476
+ exitTimer = null;
1477
+ resetOver();
1478
+ }, DROP_ZONE_EXIT_GRACE_MS);
1390
1479
  };
1391
1480
  onDrop = (e) => {
1392
1481
  e.preventDefault();
1393
- setIsOver(false);
1482
+ resetOver();
1394
1483
  let transferData = null;
1395
1484
  if (e.dataTransfer) {
1396
1485
  const raw = e.dataTransfer.getData("application/json");
@@ -1411,13 +1500,7 @@ function dropZone(element, options) {
1411
1500
  });
1412
1501
  function dispose() {
1413
1502
  cleanup3();
1414
- if (currentEl && onDragOver && onDragEnter && onDragLeave && onDrop) {
1415
- currentEl.removeEventListener("dragover", onDragOver);
1416
- currentEl.removeEventListener("dragenter", onDragEnter);
1417
- currentEl.removeEventListener("dragleave", onDragLeave);
1418
- currentEl.removeEventListener("drop", onDrop);
1419
- currentEl = null;
1420
- }
1503
+ detach();
1421
1504
  }
1422
1505
  return { isOver, dispose };
1423
1506
  }
@@ -1582,9 +1665,9 @@ function formatNumber(value, options) {
1582
1665
  function formatCurrency(value, currency, options) {
1583
1666
  const { locale, ...formatOptions } = options ?? {};
1584
1667
  return new Intl.NumberFormat(locale, {
1668
+ ...formatOptions,
1585
1669
  style: "currency",
1586
- currency,
1587
- ...formatOptions
1670
+ currency
1588
1671
  }).format(value);
1589
1672
  }
1590
1673
 
@@ -1670,18 +1753,36 @@ function swipe(target, options = {}) {
1670
1753
  }
1671
1754
  let startX = 0;
1672
1755
  let startY = 0;
1673
- let tracking = false;
1756
+ let trackedId = null;
1757
+ const idOf = (t) => t.identifier ?? 0;
1758
+ const findTracked = (list) => {
1759
+ if (!list) return null;
1760
+ for (let i = 0; i < list.length; i++) {
1761
+ const t = list[i];
1762
+ if (idOf(t) === trackedId) return t;
1763
+ }
1764
+ return null;
1765
+ };
1674
1766
  const onStart = (e) => {
1675
- if (e.touches.length === 0) return;
1676
- startX = e.touches[0].clientX;
1677
- startY = e.touches[0].clientY;
1678
- tracking = true;
1767
+ const onTarget = e.targetTouches ?? e.touches;
1768
+ if (onTarget.length > 1) {
1769
+ trackedId = null;
1770
+ return;
1771
+ }
1772
+ const touch = e.changedTouches?.[0] ?? e.touches[0];
1773
+ if (!touch) return;
1774
+ trackedId = idOf(touch);
1775
+ startX = touch.clientX;
1776
+ startY = touch.clientY;
1777
+ };
1778
+ const onCancel = (e) => {
1779
+ if (trackedId !== null && findTracked(e.changedTouches)) trackedId = null;
1679
1780
  };
1680
1781
  const onEnd = (e) => {
1681
- if (!tracking) return;
1682
- tracking = false;
1683
- const touch = e.changedTouches[0];
1782
+ if (trackedId === null) return;
1783
+ const touch = findTracked(e.changedTouches);
1684
1784
  if (!touch) return;
1785
+ trackedId = null;
1685
1786
  const dx = touch.clientX - startX;
1686
1787
  const dy = touch.clientY - startY;
1687
1788
  const absX = Math.abs(dx);
@@ -1698,9 +1799,12 @@ function swipe(target, options = {}) {
1698
1799
  };
1699
1800
  target.addEventListener("touchstart", onStart, { passive: true });
1700
1801
  target.addEventListener("touchend", onEnd, { passive: true });
1802
+ target.addEventListener("touchcancel", onCancel, { passive: true });
1701
1803
  function dispose() {
1702
1804
  target.removeEventListener("touchstart", onStart);
1703
1805
  target.removeEventListener("touchend", onEnd);
1806
+ target.removeEventListener("touchcancel", onCancel);
1807
+ trackedId = null;
1704
1808
  }
1705
1809
  return { direction, dispose };
1706
1810
  }
@@ -1727,6 +1831,18 @@ function windowSize() {
1727
1831
  }
1728
1832
 
1729
1833
  // src/browser/urlState.ts
1834
+ var _url = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.urlState.v1"), () => ({
1835
+ instances: /* @__PURE__ */ new Set()
1836
+ }));
1837
+ function notifyUrlWrite() {
1838
+ for (const sync of Array.from(_url.instances)) sync();
1839
+ }
1840
+ var SCROLL_ENTRY_KEY = "__sibuScrollKey";
1841
+ function withoutEntryIdentity(state2) {
1842
+ if (state2 === null || typeof state2 !== "object" || !Object.hasOwn(state2, SCROLL_ENTRY_KEY)) return state2;
1843
+ const { [SCROLL_ENTRY_KEY]: _identity, ...rest } = state2;
1844
+ return rest;
1845
+ }
1730
1846
  function urlState() {
1731
1847
  if (typeof window === "undefined") {
1732
1848
  const [params2] = signal(new URLSearchParams());
@@ -1760,26 +1876,36 @@ function urlState() {
1760
1876
  }
1761
1877
  window.addEventListener("popstate", syncFromLocation);
1762
1878
  window.addEventListener("hashchange", syncFromLocation);
1879
+ _url.instances.add(syncFromLocation);
1880
+ function writeHistory(newUrl, opts) {
1881
+ const state2 = "state" in opts ? opts.state : window.history.state;
1882
+ if (opts.replace) window.history.replaceState(state2, "", newUrl);
1883
+ else window.history.pushState("state" in opts ? state2 : withoutEntryIdentity(state2), "", newUrl);
1884
+ }
1763
1885
  function setParams(next, opts = {}) {
1764
1886
  const p = next instanceof URLSearchParams ? next : new URLSearchParams(next);
1765
1887
  const query = p.toString();
1766
1888
  const newUrl = `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`;
1767
- if (opts.replace) window.history.replaceState(null, "", newUrl);
1768
- else window.history.pushState(null, "", newUrl);
1889
+ writeHistory(newUrl, opts);
1769
1890
  lastSearch = window.location.search;
1770
1891
  setParamsSignal(new URLSearchParams(p));
1892
+ notifyUrlWrite();
1771
1893
  }
1772
1894
  function setHash(next, opts = {}) {
1773
1895
  const normalized = next && next !== "#" ? next.startsWith("#") ? next : `#${next}` : "";
1774
1896
  const newUrl = `${window.location.pathname}${window.location.search}${normalized}`;
1775
- if (opts.replace) window.history.replaceState(null, "", newUrl);
1776
- else window.history.pushState(null, "", newUrl);
1897
+ writeHistory(newUrl, opts);
1777
1898
  lastHash = normalized;
1778
1899
  setHashSignal(normalized);
1900
+ notifyUrlWrite();
1779
1901
  }
1902
+ let disposed = false;
1780
1903
  function dispose() {
1904
+ if (disposed) return;
1905
+ disposed = true;
1781
1906
  window.removeEventListener("popstate", syncFromLocation);
1782
1907
  window.removeEventListener("hashchange", syncFromLocation);
1908
+ _url.instances.delete(syncFromLocation);
1783
1909
  }
1784
1910
  return { params, hash, setParams, setHash, dispose };
1785
1911
  }
@@ -1796,10 +1922,14 @@ function broadcast(channelName) {
1796
1922
  const channel = new BroadcastChannel(channelName);
1797
1923
  const handler = (ev) => setLast(ev.data);
1798
1924
  channel.addEventListener("message", handler);
1925
+ let disposed = false;
1799
1926
  function post(message) {
1927
+ if (disposed) return;
1800
1928
  channel.postMessage(message);
1801
1929
  }
1802
1930
  function dispose() {
1931
+ if (disposed) return;
1932
+ disposed = true;
1803
1933
  channel.removeEventListener("message", handler);
1804
1934
  channel.close();
1805
1935
  }
@@ -1998,31 +2128,42 @@ function animationFrame(options = {}) {
1998
2128
  let prev = -1;
1999
2129
  let start = -1;
2000
2130
  let disposed = false;
2131
+ let active = false;
2132
+ let generation = 0;
2001
2133
  const minFrameMs = options.fpsLimit ? 1e3 / options.fpsLimit : 0;
2002
2134
  const step = (now) => {
2135
+ id = null;
2136
+ if (!active) return;
2137
+ const frameGeneration = generation;
2003
2138
  if (start < 0) start = now;
2004
2139
  const firstTick = prev < 0;
2005
2140
  const dt = firstTick ? 0 : now - prev;
2006
2141
  if (firstTick || dt >= minFrameMs) {
2007
- setDelta(dt);
2008
- setElapsed(now - start);
2009
2142
  prev = now;
2143
+ const elapsedValue = now - start;
2144
+ setDelta(dt);
2145
+ if (!active || generation !== frameGeneration) return;
2146
+ setElapsed(elapsedValue);
2147
+ if (!active || generation !== frameGeneration) return;
2010
2148
  }
2011
- id = requestAnimationFrame(step);
2149
+ if (id === null) id = requestAnimationFrame(step);
2012
2150
  };
2013
2151
  function resume() {
2014
- if (disposed || id !== null) return;
2015
- setRunning(true);
2152
+ if (disposed || active) return;
2153
+ active = true;
2016
2154
  id = requestAnimationFrame(step);
2155
+ setRunning(true);
2017
2156
  }
2018
2157
  function pause() {
2158
+ generation++;
2159
+ active = false;
2019
2160
  if (id !== null) {
2020
2161
  cancelAnimationFrame(id);
2021
2162
  id = null;
2022
2163
  }
2023
- setRunning(false);
2024
2164
  prev = -1;
2025
2165
  start = -1;
2166
+ setRunning(false);
2026
2167
  }
2027
2168
  function dispose() {
2028
2169
  disposed = true;
@@ -2152,6 +2293,35 @@ function keyboard(options = {}) {
2152
2293
  }
2153
2294
 
2154
2295
  // src/browser/speech.ts
2296
+ var _speech = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.speech.v1"), () => ({
2297
+ queue: [],
2298
+ active: null,
2299
+ owners: /* @__PURE__ */ new Set()
2300
+ }));
2301
+ function syncOwners() {
2302
+ for (const owner of Array.from(_speech.owners)) owner.sync();
2303
+ }
2304
+ function pump(synth) {
2305
+ if (_speech.active) return;
2306
+ const index = _speech.queue.findIndex((entry2) => !entry2.owner.paused);
2307
+ if (index === -1) return;
2308
+ const [entry] = _speech.queue.splice(index, 1);
2309
+ _speech.active = entry;
2310
+ const finish = () => {
2311
+ if (_speech.active !== entry) return;
2312
+ _speech.active = null;
2313
+ pump(synth);
2314
+ syncOwners();
2315
+ };
2316
+ entry.utterance.addEventListener("end", finish, { once: true });
2317
+ entry.utterance.addEventListener("error", finish, { once: true });
2318
+ synth.speak(entry.utterance);
2319
+ }
2320
+ function __resetSpeechCoordinator() {
2321
+ _speech.queue.length = 0;
2322
+ _speech.active = null;
2323
+ _speech.owners.clear();
2324
+ }
2155
2325
  function speech() {
2156
2326
  const [speaking, setSpeaking] = signal(false);
2157
2327
  const [paused, setPaused] = signal(false);
@@ -2172,19 +2342,16 @@ function speech() {
2172
2342
  };
2173
2343
  }
2174
2344
  const synth = window.speechSynthesis;
2175
- let interval = null;
2176
- function startPolling() {
2177
- if (interval !== null) return;
2178
- interval = setInterval(() => {
2179
- setSpeaking(synth.speaking);
2180
- setPaused(synth.paused);
2181
- if (!synth.speaking && !synth.paused) {
2182
- clearInterval(interval);
2183
- interval = null;
2184
- }
2185
- }, 200);
2186
- }
2187
2345
  let disposed = false;
2346
+ const owner = {
2347
+ paused: false,
2348
+ sync: () => {
2349
+ const mine = _speech.active?.owner === owner || _speech.queue.some((entry) => entry.owner === owner);
2350
+ setSpeaking(mine);
2351
+ setPaused(owner.paused);
2352
+ }
2353
+ };
2354
+ _speech.owners.add(owner);
2188
2355
  function speak(text, options = {}) {
2189
2356
  if (disposed) return;
2190
2357
  const u = new SpeechSynthesisUtterance(text);
@@ -2197,52 +2364,48 @@ function speech() {
2197
2364
  const match = voices.find((v) => v.name === options.voice);
2198
2365
  if (match) u.voice = match;
2199
2366
  }
2200
- u.addEventListener(
2201
- "start",
2202
- () => {
2203
- if (!disposed) setSpeaking(true);
2204
- },
2205
- { once: true }
2206
- );
2207
- u.addEventListener(
2208
- "end",
2209
- () => {
2210
- if (disposed) return;
2211
- setSpeaking(false);
2212
- setPaused(false);
2213
- },
2214
- { once: true }
2215
- );
2216
- u.addEventListener(
2217
- "error",
2218
- () => {
2219
- if (disposed) return;
2220
- setSpeaking(false);
2221
- setPaused(false);
2222
- },
2223
- { once: true }
2224
- );
2225
- synth.speak(u);
2226
- setSpeaking(true);
2227
- startPolling();
2367
+ _speech.queue.push({ owner, utterance: u });
2368
+ pump(synth);
2369
+ syncOwners();
2370
+ }
2371
+ function pause() {
2372
+ if (disposed || owner.paused) return;
2373
+ owner.paused = true;
2374
+ if (_speech.active?.owner === owner) synth.pause();
2375
+ syncOwners();
2376
+ }
2377
+ function resume() {
2378
+ if (disposed || !owner.paused) return;
2379
+ owner.paused = false;
2380
+ if (_speech.active?.owner === owner) synth.resume();
2381
+ else pump(synth);
2382
+ syncOwners();
2383
+ }
2384
+ function cancel() {
2385
+ if (disposed) return;
2386
+ cancelOwned();
2387
+ syncOwners();
2388
+ }
2389
+ function cancelOwned() {
2390
+ for (let i = _speech.queue.length - 1; i >= 0; i--) {
2391
+ if (_speech.queue[i].owner === owner) _speech.queue.splice(i, 1);
2392
+ }
2393
+ owner.paused = false;
2394
+ if (_speech.active?.owner === owner) {
2395
+ _speech.active = null;
2396
+ if (synth.paused) synth.resume();
2397
+ synth.cancel();
2398
+ pump(synth);
2399
+ }
2228
2400
  }
2229
2401
  function dispose() {
2402
+ if (disposed) return;
2403
+ cancelOwned();
2230
2404
  disposed = true;
2231
- if (interval !== null) {
2232
- clearInterval(interval);
2233
- interval = null;
2234
- }
2235
- synth.cancel();
2405
+ syncOwners();
2406
+ _speech.owners.delete(owner);
2236
2407
  }
2237
- return {
2238
- speaking,
2239
- paused,
2240
- speak,
2241
- pause: () => synth.pause(),
2242
- resume: () => synth.resume(),
2243
- cancel: () => synth.cancel(),
2244
- dispose
2245
- };
2408
+ return { speaking, paused, speak, pause, resume, cancel, dispose };
2246
2409
  }
2247
2410
 
2248
2411
  // src/browser/gamepad.ts
@@ -2253,6 +2416,7 @@ function gamepad() {
2253
2416
  } };
2254
2417
  }
2255
2418
  let rafId = null;
2419
+ let disposed = false;
2256
2420
  function snapshot(pad) {
2257
2421
  return {
2258
2422
  index: pad.index,
@@ -2267,7 +2431,7 @@ function gamepad() {
2267
2431
  for (let i = 0; i < a.length; i++) {
2268
2432
  const pa = a[i];
2269
2433
  const pb = b[i];
2270
- if (pa.index !== pb.index || pa.connected !== pb.connected) return false;
2434
+ if (pa.index !== pb.index || pa.id !== pb.id || pa.connected !== pb.connected) return false;
2271
2435
  if (pa.buttons.length !== pb.buttons.length) return false;
2272
2436
  for (let j = 0; j < pa.buttons.length; j++) {
2273
2437
  if (pa.buttons[j].pressed !== pb.buttons[j].pressed) return false;
@@ -2280,15 +2444,19 @@ function gamepad() {
2280
2444
  }
2281
2445
  return true;
2282
2446
  }
2447
+ function publish() {
2448
+ const snap = Array.from(navigator.getGamepads()).filter((g) => g !== null).map(snapshot);
2449
+ if (!equal(pads(), snap)) setPads(snap);
2450
+ return snap;
2451
+ }
2283
2452
  function poll() {
2284
- const raw = navigator.getGamepads();
2285
- const snap = Array.from(raw).filter((g) => g !== null).map(snapshot);
2286
- const current = pads();
2287
- if (!equal(current, snap)) setPads(snap);
2288
- rafId = requestAnimationFrame(poll);
2453
+ rafId = null;
2454
+ if (disposed) return;
2455
+ publish();
2456
+ if (!disposed && rafId === null) rafId = requestAnimationFrame(poll);
2289
2457
  }
2290
2458
  function startPolling() {
2291
- if (rafId === null) poll();
2459
+ if (!disposed && rafId === null) poll();
2292
2460
  }
2293
2461
  function stopPolling() {
2294
2462
  if (rafId !== null) {
@@ -2298,15 +2466,14 @@ function gamepad() {
2298
2466
  }
2299
2467
  const onConnect = () => startPolling();
2300
2468
  const onDisconnect = () => {
2301
- const raw = navigator.getGamepads();
2302
- const hasAny = Array.from(raw).some((g) => g !== null);
2303
- if (!hasAny) stopPolling();
2469
+ if (publish().length === 0) stopPolling();
2304
2470
  };
2305
2471
  window.addEventListener("gamepadconnected", onConnect);
2306
2472
  window.addEventListener("gamepaddisconnected", onDisconnect);
2307
2473
  const initial = Array.from(navigator.getGamepads()).some((g) => g !== null);
2308
2474
  if (initial) startPolling();
2309
2475
  function dispose() {
2476
+ disposed = true;
2310
2477
  stopPolling();
2311
2478
  window.removeEventListener("gamepadconnected", onConnect);
2312
2479
  window.removeEventListener("gamepaddisconnected", onDisconnect);
@@ -2320,8 +2487,7 @@ function pointerLock() {
2320
2487
  if (typeof document === "undefined") {
2321
2488
  return {
2322
2489
  locked,
2323
- request: () => {
2324
- },
2490
+ request: () => Promise.resolve(),
2325
2491
  exit: () => {
2326
2492
  },
2327
2493
  dispose: () => {
@@ -2333,8 +2499,14 @@ function pointerLock() {
2333
2499
  };
2334
2500
  document.addEventListener("pointerlockchange", handler);
2335
2501
  function request(element) {
2336
- if (typeof element.requestPointerLock === "function") {
2337
- element.requestPointerLock();
2502
+ if (typeof element.requestPointerLock !== "function") {
2503
+ return Promise.resolve();
2504
+ }
2505
+ try {
2506
+ const result = element.requestPointerLock();
2507
+ return Promise.resolve(result).then(() => void 0);
2508
+ } catch (err) {
2509
+ return Promise.reject(err);
2338
2510
  }
2339
2511
  }
2340
2512
  function exit() {
@@ -2437,25 +2609,46 @@ function imageLoader(src) {
2437
2609
  };
2438
2610
  }
2439
2611
  let current = null;
2612
+ let currentSettled = false;
2440
2613
  let disposed = false;
2614
+ let startToken = 0;
2615
+ function resetState() {
2616
+ batch(() => {
2617
+ setStatus("pending");
2618
+ setImage(null);
2619
+ setWidth(0);
2620
+ setHeight(0);
2621
+ });
2622
+ }
2623
+ function abandonCurrent() {
2624
+ if (!current) return;
2625
+ const prev = current;
2626
+ current = null;
2627
+ prev.onload = null;
2628
+ prev.onerror = null;
2629
+ if (!currentSettled) prev.src = "";
2630
+ }
2441
2631
  function start(url) {
2442
- if (current) {
2443
- current.onload = null;
2444
- current.onerror = null;
2445
- }
2446
- setStatus("pending");
2447
- setImage(null);
2632
+ const token = ++startToken;
2633
+ abandonCurrent();
2634
+ resetState();
2635
+ if (disposed || token !== startToken) return;
2448
2636
  const img = new Image();
2449
2637
  current = img;
2638
+ currentSettled = false;
2450
2639
  img.onload = () => {
2451
2640
  if (disposed || current !== img) return;
2452
- setImage(img);
2453
- setWidth(img.naturalWidth);
2454
- setHeight(img.naturalHeight);
2455
- setStatus("loaded");
2641
+ currentSettled = true;
2642
+ batch(() => {
2643
+ setImage(img);
2644
+ setWidth(img.naturalWidth);
2645
+ setHeight(img.naturalHeight);
2646
+ setStatus("loaded");
2647
+ });
2456
2648
  };
2457
2649
  img.onerror = () => {
2458
2650
  if (disposed || current !== img) return;
2651
+ currentSettled = true;
2459
2652
  setStatus("error");
2460
2653
  };
2461
2654
  img.src = url;
@@ -2470,21 +2663,21 @@ function imageLoader(src) {
2470
2663
  start(src);
2471
2664
  }
2472
2665
  function dispose() {
2666
+ if (disposed) return;
2473
2667
  disposed = true;
2668
+ startToken++;
2474
2669
  if (srcEffectTeardown) {
2475
2670
  srcEffectTeardown();
2476
2671
  srcEffectTeardown = null;
2477
2672
  }
2478
- if (current) {
2479
- current.onload = null;
2480
- current.onerror = null;
2481
- current = null;
2482
- }
2673
+ abandonCurrent();
2674
+ resetState();
2483
2675
  }
2484
2676
  return { status, image, width, height, dispose };
2485
2677
  }
2486
2678
  // Annotate the CommonJS export names for ESM import in node:
2487
2679
  0 && (module.exports = {
2680
+ __resetSpeechCoordinator,
2488
2681
  animationFrame,
2489
2682
  battery,
2490
2683
  bounds,