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/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.6.0" : "dev";
652
678
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
653
679
  function resolveReactiveApi() {
654
680
  const g = globalThis;
@@ -1279,13 +1305,15 @@ function clipboard() {
1279
1305
  const [copied, setCopied] = signal(false);
1280
1306
  let copiedTimer = null;
1281
1307
  let disposed = false;
1308
+ let generation = 0;
1282
1309
  async function copy(value) {
1283
1310
  if (disposed) return;
1284
1311
  if (typeof navigator === "undefined" || !navigator.clipboard) {
1285
1312
  return;
1286
1313
  }
1314
+ const mine = ++generation;
1287
1315
  await navigator.clipboard.writeText(value);
1288
- if (disposed) return;
1316
+ if (disposed || mine !== generation) return;
1289
1317
  setText(value);
1290
1318
  setCopied(true);
1291
1319
  if (copiedTimer !== null) clearTimeout(copiedTimer);
@@ -1297,6 +1325,7 @@ function clipboard() {
1297
1325
  }
1298
1326
  function dispose() {
1299
1327
  disposed = true;
1328
+ generation++;
1300
1329
  if (copiedTimer !== null) {
1301
1330
  clearTimeout(copiedTimer);
1302
1331
  copiedTimer = null;
@@ -1324,15 +1353,26 @@ function draggable(element, data) {
1324
1353
  let currentEl = null;
1325
1354
  let onDragStart = null;
1326
1355
  let onDragEnd = null;
1356
+ let prevDraggableAttr = null;
1357
+ function detach() {
1358
+ if (!currentEl) return;
1359
+ if (onDragStart) currentEl.removeEventListener("dragstart", onDragStart);
1360
+ if (onDragEnd) currentEl.removeEventListener("dragend", onDragEnd);
1361
+ if (prevDraggableAttr === null) currentEl.removeAttribute("draggable");
1362
+ else currentEl.setAttribute("draggable", prevDraggableAttr);
1363
+ currentEl = null;
1364
+ onDragStart = null;
1365
+ onDragEnd = null;
1366
+ setIsDragging(false);
1367
+ }
1327
1368
  const getter = resolveTarget2(element);
1328
1369
  const cleanup3 = effect(() => {
1329
- if (currentEl && onDragStart && onDragEnd) {
1330
- currentEl.removeEventListener("dragstart", onDragStart);
1331
- currentEl.removeEventListener("dragend", onDragEnd);
1332
- }
1333
1370
  const el = getter();
1334
- currentEl = el;
1371
+ if (el === currentEl) return;
1372
+ detach();
1335
1373
  if (!el) return;
1374
+ currentEl = el;
1375
+ prevDraggableAttr = el.getAttribute("draggable");
1336
1376
  el.draggable = true;
1337
1377
  onDragStart = (e) => {
1338
1378
  setIsDragging(true);
@@ -1346,16 +1386,16 @@ function draggable(element, data) {
1346
1386
  el.addEventListener("dragstart", onDragStart);
1347
1387
  el.addEventListener("dragend", onDragEnd);
1348
1388
  });
1389
+ let disposed = false;
1349
1390
  function dispose() {
1391
+ if (disposed) return;
1392
+ disposed = true;
1350
1393
  cleanup3();
1351
- if (currentEl && onDragStart && onDragEnd) {
1352
- currentEl.removeEventListener("dragstart", onDragStart);
1353
- currentEl.removeEventListener("dragend", onDragEnd);
1354
- currentEl = null;
1355
- }
1394
+ detach();
1356
1395
  }
1357
1396
  return { isDragging, dispose };
1358
1397
  }
1398
+ var DROP_ZONE_EXIT_GRACE_MS = 600;
1359
1399
  function dropZone(element, options) {
1360
1400
  const [isOver, setIsOver] = signal(false);
1361
1401
  if (typeof window === "undefined") {
@@ -1367,30 +1407,66 @@ function dropZone(element, options) {
1367
1407
  let onDragEnter = null;
1368
1408
  let onDragLeave = null;
1369
1409
  let onDrop = null;
1370
- const getter = resolveTarget2(element);
1371
- const cleanup3 = effect(() => {
1410
+ let depth = 0;
1411
+ let exitTimer = null;
1412
+ function cancelPendingExit() {
1413
+ if (exitTimer !== null) {
1414
+ clearTimeout(exitTimer);
1415
+ exitTimer = null;
1416
+ }
1417
+ }
1418
+ function resetOver() {
1419
+ cancelPendingExit();
1420
+ depth = 0;
1421
+ setIsOver(false);
1422
+ }
1423
+ function detach() {
1372
1424
  if (currentEl && onDragOver && onDragEnter && onDragLeave && onDrop) {
1373
1425
  currentEl.removeEventListener("dragover", onDragOver);
1374
1426
  currentEl.removeEventListener("dragenter", onDragEnter);
1375
1427
  currentEl.removeEventListener("dragleave", onDragLeave);
1376
1428
  currentEl.removeEventListener("drop", onDrop);
1377
1429
  }
1430
+ currentEl = null;
1431
+ resetOver();
1432
+ }
1433
+ const getter = resolveTarget2(element);
1434
+ const cleanup3 = effect(() => {
1378
1435
  const el = getter();
1379
- currentEl = el;
1436
+ if (el === currentEl) return;
1437
+ detach();
1380
1438
  if (!el) return;
1439
+ currentEl = el;
1381
1440
  onDragOver = (e) => {
1382
1441
  e.preventDefault();
1442
+ cancelPendingExit();
1383
1443
  };
1384
1444
  onDragEnter = (e) => {
1385
1445
  e.preventDefault();
1446
+ cancelPendingExit();
1447
+ depth++;
1386
1448
  setIsOver(true);
1387
1449
  };
1388
- onDragLeave = () => {
1389
- setIsOver(false);
1450
+ onDragLeave = (e) => {
1451
+ depth = Math.max(0, depth - 1);
1452
+ if (depth === 0) {
1453
+ resetOver();
1454
+ return;
1455
+ }
1456
+ const to = e.relatedTarget;
1457
+ if (to != null && typeof to.nodeType === "number") {
1458
+ if (!el.contains(to)) resetOver();
1459
+ return;
1460
+ }
1461
+ cancelPendingExit();
1462
+ exitTimer = setTimeout(() => {
1463
+ exitTimer = null;
1464
+ resetOver();
1465
+ }, DROP_ZONE_EXIT_GRACE_MS);
1390
1466
  };
1391
1467
  onDrop = (e) => {
1392
1468
  e.preventDefault();
1393
- setIsOver(false);
1469
+ resetOver();
1394
1470
  let transferData = null;
1395
1471
  if (e.dataTransfer) {
1396
1472
  const raw = e.dataTransfer.getData("application/json");
@@ -1411,13 +1487,7 @@ function dropZone(element, options) {
1411
1487
  });
1412
1488
  function dispose() {
1413
1489
  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
- }
1490
+ detach();
1421
1491
  }
1422
1492
  return { isOver, dispose };
1423
1493
  }
@@ -1582,9 +1652,9 @@ function formatNumber(value, options) {
1582
1652
  function formatCurrency(value, currency, options) {
1583
1653
  const { locale, ...formatOptions } = options ?? {};
1584
1654
  return new Intl.NumberFormat(locale, {
1655
+ ...formatOptions,
1585
1656
  style: "currency",
1586
- currency,
1587
- ...formatOptions
1657
+ currency
1588
1658
  }).format(value);
1589
1659
  }
1590
1660
 
@@ -1670,18 +1740,36 @@ function swipe(target, options = {}) {
1670
1740
  }
1671
1741
  let startX = 0;
1672
1742
  let startY = 0;
1673
- let tracking = false;
1743
+ let trackedId = null;
1744
+ const idOf = (t) => t.identifier ?? 0;
1745
+ const findTracked = (list) => {
1746
+ if (!list) return null;
1747
+ for (let i = 0; i < list.length; i++) {
1748
+ const t = list[i];
1749
+ if (idOf(t) === trackedId) return t;
1750
+ }
1751
+ return null;
1752
+ };
1674
1753
  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;
1754
+ const onTarget = e.targetTouches ?? e.touches;
1755
+ if (onTarget.length > 1) {
1756
+ trackedId = null;
1757
+ return;
1758
+ }
1759
+ const touch = e.changedTouches?.[0] ?? e.touches[0];
1760
+ if (!touch) return;
1761
+ trackedId = idOf(touch);
1762
+ startX = touch.clientX;
1763
+ startY = touch.clientY;
1764
+ };
1765
+ const onCancel = (e) => {
1766
+ if (trackedId !== null && findTracked(e.changedTouches)) trackedId = null;
1679
1767
  };
1680
1768
  const onEnd = (e) => {
1681
- if (!tracking) return;
1682
- tracking = false;
1683
- const touch = e.changedTouches[0];
1769
+ if (trackedId === null) return;
1770
+ const touch = findTracked(e.changedTouches);
1684
1771
  if (!touch) return;
1772
+ trackedId = null;
1685
1773
  const dx = touch.clientX - startX;
1686
1774
  const dy = touch.clientY - startY;
1687
1775
  const absX = Math.abs(dx);
@@ -1698,9 +1786,12 @@ function swipe(target, options = {}) {
1698
1786
  };
1699
1787
  target.addEventListener("touchstart", onStart, { passive: true });
1700
1788
  target.addEventListener("touchend", onEnd, { passive: true });
1789
+ target.addEventListener("touchcancel", onCancel, { passive: true });
1701
1790
  function dispose() {
1702
1791
  target.removeEventListener("touchstart", onStart);
1703
1792
  target.removeEventListener("touchend", onEnd);
1793
+ target.removeEventListener("touchcancel", onCancel);
1794
+ trackedId = null;
1704
1795
  }
1705
1796
  return { direction, dispose };
1706
1797
  }
@@ -1727,6 +1818,18 @@ function windowSize() {
1727
1818
  }
1728
1819
 
1729
1820
  // src/browser/urlState.ts
1821
+ var _url = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.urlState.v1"), () => ({
1822
+ instances: /* @__PURE__ */ new Set()
1823
+ }));
1824
+ function notifyUrlWrite() {
1825
+ for (const sync of Array.from(_url.instances)) sync();
1826
+ }
1827
+ var SCROLL_ENTRY_KEY = "__sibuScrollKey";
1828
+ function withoutEntryIdentity(state2) {
1829
+ if (state2 === null || typeof state2 !== "object" || !Object.hasOwn(state2, SCROLL_ENTRY_KEY)) return state2;
1830
+ const { [SCROLL_ENTRY_KEY]: _identity, ...rest } = state2;
1831
+ return rest;
1832
+ }
1730
1833
  function urlState() {
1731
1834
  if (typeof window === "undefined") {
1732
1835
  const [params2] = signal(new URLSearchParams());
@@ -1760,26 +1863,36 @@ function urlState() {
1760
1863
  }
1761
1864
  window.addEventListener("popstate", syncFromLocation);
1762
1865
  window.addEventListener("hashchange", syncFromLocation);
1866
+ _url.instances.add(syncFromLocation);
1867
+ function writeHistory(newUrl, opts) {
1868
+ const state2 = "state" in opts ? opts.state : window.history.state;
1869
+ if (opts.replace) window.history.replaceState(state2, "", newUrl);
1870
+ else window.history.pushState("state" in opts ? state2 : withoutEntryIdentity(state2), "", newUrl);
1871
+ }
1763
1872
  function setParams(next, opts = {}) {
1764
1873
  const p = next instanceof URLSearchParams ? next : new URLSearchParams(next);
1765
1874
  const query = p.toString();
1766
1875
  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);
1876
+ writeHistory(newUrl, opts);
1769
1877
  lastSearch = window.location.search;
1770
1878
  setParamsSignal(new URLSearchParams(p));
1879
+ notifyUrlWrite();
1771
1880
  }
1772
1881
  function setHash(next, opts = {}) {
1773
1882
  const normalized = next && next !== "#" ? next.startsWith("#") ? next : `#${next}` : "";
1774
1883
  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);
1884
+ writeHistory(newUrl, opts);
1777
1885
  lastHash = normalized;
1778
1886
  setHashSignal(normalized);
1887
+ notifyUrlWrite();
1779
1888
  }
1889
+ let disposed = false;
1780
1890
  function dispose() {
1891
+ if (disposed) return;
1892
+ disposed = true;
1781
1893
  window.removeEventListener("popstate", syncFromLocation);
1782
1894
  window.removeEventListener("hashchange", syncFromLocation);
1895
+ _url.instances.delete(syncFromLocation);
1783
1896
  }
1784
1897
  return { params, hash, setParams, setHash, dispose };
1785
1898
  }
@@ -1796,10 +1909,14 @@ function broadcast(channelName) {
1796
1909
  const channel = new BroadcastChannel(channelName);
1797
1910
  const handler = (ev) => setLast(ev.data);
1798
1911
  channel.addEventListener("message", handler);
1912
+ let disposed = false;
1799
1913
  function post(message) {
1914
+ if (disposed) return;
1800
1915
  channel.postMessage(message);
1801
1916
  }
1802
1917
  function dispose() {
1918
+ if (disposed) return;
1919
+ disposed = true;
1803
1920
  channel.removeEventListener("message", handler);
1804
1921
  channel.close();
1805
1922
  }
@@ -1998,31 +2115,42 @@ function animationFrame(options = {}) {
1998
2115
  let prev = -1;
1999
2116
  let start = -1;
2000
2117
  let disposed = false;
2118
+ let active = false;
2119
+ let generation = 0;
2001
2120
  const minFrameMs = options.fpsLimit ? 1e3 / options.fpsLimit : 0;
2002
2121
  const step = (now) => {
2122
+ id = null;
2123
+ if (!active) return;
2124
+ const frameGeneration = generation;
2003
2125
  if (start < 0) start = now;
2004
2126
  const firstTick = prev < 0;
2005
2127
  const dt = firstTick ? 0 : now - prev;
2006
2128
  if (firstTick || dt >= minFrameMs) {
2007
- setDelta(dt);
2008
- setElapsed(now - start);
2009
2129
  prev = now;
2130
+ const elapsedValue = now - start;
2131
+ setDelta(dt);
2132
+ if (!active || generation !== frameGeneration) return;
2133
+ setElapsed(elapsedValue);
2134
+ if (!active || generation !== frameGeneration) return;
2010
2135
  }
2011
- id = requestAnimationFrame(step);
2136
+ if (id === null) id = requestAnimationFrame(step);
2012
2137
  };
2013
2138
  function resume() {
2014
- if (disposed || id !== null) return;
2015
- setRunning(true);
2139
+ if (disposed || active) return;
2140
+ active = true;
2016
2141
  id = requestAnimationFrame(step);
2142
+ setRunning(true);
2017
2143
  }
2018
2144
  function pause() {
2145
+ generation++;
2146
+ active = false;
2019
2147
  if (id !== null) {
2020
2148
  cancelAnimationFrame(id);
2021
2149
  id = null;
2022
2150
  }
2023
- setRunning(false);
2024
2151
  prev = -1;
2025
2152
  start = -1;
2153
+ setRunning(false);
2026
2154
  }
2027
2155
  function dispose() {
2028
2156
  disposed = true;
@@ -2152,6 +2280,35 @@ function keyboard(options = {}) {
2152
2280
  }
2153
2281
 
2154
2282
  // src/browser/speech.ts
2283
+ var _speech = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.speech.v1"), () => ({
2284
+ queue: [],
2285
+ active: null,
2286
+ owners: /* @__PURE__ */ new Set()
2287
+ }));
2288
+ function syncOwners() {
2289
+ for (const owner of Array.from(_speech.owners)) owner.sync();
2290
+ }
2291
+ function pump(synth) {
2292
+ if (_speech.active) return;
2293
+ const index = _speech.queue.findIndex((entry2) => !entry2.owner.paused);
2294
+ if (index === -1) return;
2295
+ const [entry] = _speech.queue.splice(index, 1);
2296
+ _speech.active = entry;
2297
+ const finish = () => {
2298
+ if (_speech.active !== entry) return;
2299
+ _speech.active = null;
2300
+ pump(synth);
2301
+ syncOwners();
2302
+ };
2303
+ entry.utterance.addEventListener("end", finish, { once: true });
2304
+ entry.utterance.addEventListener("error", finish, { once: true });
2305
+ synth.speak(entry.utterance);
2306
+ }
2307
+ function __resetSpeechCoordinator() {
2308
+ _speech.queue.length = 0;
2309
+ _speech.active = null;
2310
+ _speech.owners.clear();
2311
+ }
2155
2312
  function speech() {
2156
2313
  const [speaking, setSpeaking] = signal(false);
2157
2314
  const [paused, setPaused] = signal(false);
@@ -2172,19 +2329,16 @@ function speech() {
2172
2329
  };
2173
2330
  }
2174
2331
  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
2332
  let disposed = false;
2333
+ const owner = {
2334
+ paused: false,
2335
+ sync: () => {
2336
+ const mine = _speech.active?.owner === owner || _speech.queue.some((entry) => entry.owner === owner);
2337
+ setSpeaking(mine);
2338
+ setPaused(owner.paused);
2339
+ }
2340
+ };
2341
+ _speech.owners.add(owner);
2188
2342
  function speak(text, options = {}) {
2189
2343
  if (disposed) return;
2190
2344
  const u = new SpeechSynthesisUtterance(text);
@@ -2197,52 +2351,48 @@ function speech() {
2197
2351
  const match = voices.find((v) => v.name === options.voice);
2198
2352
  if (match) u.voice = match;
2199
2353
  }
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();
2354
+ _speech.queue.push({ owner, utterance: u });
2355
+ pump(synth);
2356
+ syncOwners();
2357
+ }
2358
+ function pause() {
2359
+ if (disposed || owner.paused) return;
2360
+ owner.paused = true;
2361
+ if (_speech.active?.owner === owner) synth.pause();
2362
+ syncOwners();
2363
+ }
2364
+ function resume() {
2365
+ if (disposed || !owner.paused) return;
2366
+ owner.paused = false;
2367
+ if (_speech.active?.owner === owner) synth.resume();
2368
+ else pump(synth);
2369
+ syncOwners();
2370
+ }
2371
+ function cancel() {
2372
+ if (disposed) return;
2373
+ cancelOwned();
2374
+ syncOwners();
2375
+ }
2376
+ function cancelOwned() {
2377
+ for (let i = _speech.queue.length - 1; i >= 0; i--) {
2378
+ if (_speech.queue[i].owner === owner) _speech.queue.splice(i, 1);
2379
+ }
2380
+ owner.paused = false;
2381
+ if (_speech.active?.owner === owner) {
2382
+ _speech.active = null;
2383
+ if (synth.paused) synth.resume();
2384
+ synth.cancel();
2385
+ pump(synth);
2386
+ }
2228
2387
  }
2229
2388
  function dispose() {
2389
+ if (disposed) return;
2390
+ cancelOwned();
2230
2391
  disposed = true;
2231
- if (interval !== null) {
2232
- clearInterval(interval);
2233
- interval = null;
2234
- }
2235
- synth.cancel();
2392
+ syncOwners();
2393
+ _speech.owners.delete(owner);
2236
2394
  }
2237
- return {
2238
- speaking,
2239
- paused,
2240
- speak,
2241
- pause: () => synth.pause(),
2242
- resume: () => synth.resume(),
2243
- cancel: () => synth.cancel(),
2244
- dispose
2245
- };
2395
+ return { speaking, paused, speak, pause, resume, cancel, dispose };
2246
2396
  }
2247
2397
 
2248
2398
  // src/browser/gamepad.ts
@@ -2253,6 +2403,7 @@ function gamepad() {
2253
2403
  } };
2254
2404
  }
2255
2405
  let rafId = null;
2406
+ let disposed = false;
2256
2407
  function snapshot(pad) {
2257
2408
  return {
2258
2409
  index: pad.index,
@@ -2267,7 +2418,7 @@ function gamepad() {
2267
2418
  for (let i = 0; i < a.length; i++) {
2268
2419
  const pa = a[i];
2269
2420
  const pb = b[i];
2270
- if (pa.index !== pb.index || pa.connected !== pb.connected) return false;
2421
+ if (pa.index !== pb.index || pa.id !== pb.id || pa.connected !== pb.connected) return false;
2271
2422
  if (pa.buttons.length !== pb.buttons.length) return false;
2272
2423
  for (let j = 0; j < pa.buttons.length; j++) {
2273
2424
  if (pa.buttons[j].pressed !== pb.buttons[j].pressed) return false;
@@ -2280,15 +2431,19 @@ function gamepad() {
2280
2431
  }
2281
2432
  return true;
2282
2433
  }
2434
+ function publish() {
2435
+ const snap = Array.from(navigator.getGamepads()).filter((g) => g !== null).map(snapshot);
2436
+ if (!equal(pads(), snap)) setPads(snap);
2437
+ return snap;
2438
+ }
2283
2439
  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);
2440
+ rafId = null;
2441
+ if (disposed) return;
2442
+ publish();
2443
+ if (!disposed && rafId === null) rafId = requestAnimationFrame(poll);
2289
2444
  }
2290
2445
  function startPolling() {
2291
- if (rafId === null) poll();
2446
+ if (!disposed && rafId === null) poll();
2292
2447
  }
2293
2448
  function stopPolling() {
2294
2449
  if (rafId !== null) {
@@ -2298,15 +2453,14 @@ function gamepad() {
2298
2453
  }
2299
2454
  const onConnect = () => startPolling();
2300
2455
  const onDisconnect = () => {
2301
- const raw = navigator.getGamepads();
2302
- const hasAny = Array.from(raw).some((g) => g !== null);
2303
- if (!hasAny) stopPolling();
2456
+ if (publish().length === 0) stopPolling();
2304
2457
  };
2305
2458
  window.addEventListener("gamepadconnected", onConnect);
2306
2459
  window.addEventListener("gamepaddisconnected", onDisconnect);
2307
2460
  const initial = Array.from(navigator.getGamepads()).some((g) => g !== null);
2308
2461
  if (initial) startPolling();
2309
2462
  function dispose() {
2463
+ disposed = true;
2310
2464
  stopPolling();
2311
2465
  window.removeEventListener("gamepadconnected", onConnect);
2312
2466
  window.removeEventListener("gamepaddisconnected", onDisconnect);
@@ -2320,8 +2474,7 @@ function pointerLock() {
2320
2474
  if (typeof document === "undefined") {
2321
2475
  return {
2322
2476
  locked,
2323
- request: () => {
2324
- },
2477
+ request: () => Promise.resolve(),
2325
2478
  exit: () => {
2326
2479
  },
2327
2480
  dispose: () => {
@@ -2333,8 +2486,14 @@ function pointerLock() {
2333
2486
  };
2334
2487
  document.addEventListener("pointerlockchange", handler);
2335
2488
  function request(element) {
2336
- if (typeof element.requestPointerLock === "function") {
2337
- element.requestPointerLock();
2489
+ if (typeof element.requestPointerLock !== "function") {
2490
+ return Promise.resolve();
2491
+ }
2492
+ try {
2493
+ const result = element.requestPointerLock();
2494
+ return Promise.resolve(result).then(() => void 0);
2495
+ } catch (err) {
2496
+ return Promise.reject(err);
2338
2497
  }
2339
2498
  }
2340
2499
  function exit() {
@@ -2437,25 +2596,46 @@ function imageLoader(src) {
2437
2596
  };
2438
2597
  }
2439
2598
  let current = null;
2599
+ let currentSettled = false;
2440
2600
  let disposed = false;
2601
+ let startToken = 0;
2602
+ function resetState() {
2603
+ batch(() => {
2604
+ setStatus("pending");
2605
+ setImage(null);
2606
+ setWidth(0);
2607
+ setHeight(0);
2608
+ });
2609
+ }
2610
+ function abandonCurrent() {
2611
+ if (!current) return;
2612
+ const prev = current;
2613
+ current = null;
2614
+ prev.onload = null;
2615
+ prev.onerror = null;
2616
+ if (!currentSettled) prev.src = "";
2617
+ }
2441
2618
  function start(url) {
2442
- if (current) {
2443
- current.onload = null;
2444
- current.onerror = null;
2445
- }
2446
- setStatus("pending");
2447
- setImage(null);
2619
+ const token = ++startToken;
2620
+ abandonCurrent();
2621
+ resetState();
2622
+ if (disposed || token !== startToken) return;
2448
2623
  const img = new Image();
2449
2624
  current = img;
2625
+ currentSettled = false;
2450
2626
  img.onload = () => {
2451
2627
  if (disposed || current !== img) return;
2452
- setImage(img);
2453
- setWidth(img.naturalWidth);
2454
- setHeight(img.naturalHeight);
2455
- setStatus("loaded");
2628
+ currentSettled = true;
2629
+ batch(() => {
2630
+ setImage(img);
2631
+ setWidth(img.naturalWidth);
2632
+ setHeight(img.naturalHeight);
2633
+ setStatus("loaded");
2634
+ });
2456
2635
  };
2457
2636
  img.onerror = () => {
2458
2637
  if (disposed || current !== img) return;
2638
+ currentSettled = true;
2459
2639
  setStatus("error");
2460
2640
  };
2461
2641
  img.src = url;
@@ -2470,21 +2650,21 @@ function imageLoader(src) {
2470
2650
  start(src);
2471
2651
  }
2472
2652
  function dispose() {
2653
+ if (disposed) return;
2473
2654
  disposed = true;
2655
+ startToken++;
2474
2656
  if (srcEffectTeardown) {
2475
2657
  srcEffectTeardown();
2476
2658
  srcEffectTeardown = null;
2477
2659
  }
2478
- if (current) {
2479
- current.onload = null;
2480
- current.onerror = null;
2481
- current = null;
2482
- }
2660
+ abandonCurrent();
2661
+ resetState();
2483
2662
  }
2484
2663
  return { status, image, width, height, dispose };
2485
2664
  }
2486
2665
  // Annotate the CommonJS export names for ESM import in node:
2487
2666
  0 && (module.exports = {
2667
+ __resetSpeechCoordinator,
2488
2668
  animationFrame,
2489
2669
  battery,
2490
2670
  bounds,