sibujs 4.4.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 (112) hide show
  1. package/README.md +11 -0
  2. package/dist/browser.cjs +339 -130
  3. package/dist/browser.d.cts +61 -16
  4. package/dist/browser.d.ts +61 -16
  5. package/dist/browser.js +8 -6
  6. package/dist/build.cjs +273 -82
  7. package/dist/build.js +15 -16
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +11 -11
  10. package/dist/cdn.full.global.js +10 -10
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  13. package/dist/chunk-3ISI6ACU.js +44 -0
  14. package/dist/{chunk-VCTAEPSB.js → chunk-47M47FOM.js} +11 -3
  15. package/dist/chunk-5HZXGZ6T.js +24 -0
  16. package/dist/{chunk-KKLW7YWL.js → chunk-5MT6SJ3P.js} +275 -144
  17. package/dist/{chunk-3BTTCZ5J.js → chunk-5Q4R7HCL.js} +42 -4
  18. package/dist/{chunk-ADM46X22.js → chunk-7GQCWFOE.js} +157 -61
  19. package/dist/{chunk-4Z3SPQ67.js → chunk-7NCARGJW.js} +16 -8
  20. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  21. package/dist/{chunk-LQFGQNMV.js → chunk-AIF3Z2T7.js} +302 -39
  22. package/dist/{chunk-UCALUKB5.js → chunk-BAAG6ZTI.js} +98 -29
  23. package/dist/{chunk-NVNJH22U.js → chunk-D33YTSX5.js} +2 -2
  24. package/dist/{chunk-M2F7TZHH.js → chunk-DKGBBKOF.js} +136 -80
  25. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  26. package/dist/{chunk-HFCOH2GN.js → chunk-HYXDKS4N.js} +153 -110
  27. package/dist/chunk-J6FW5TV6.js +233 -0
  28. package/dist/{chunk-36S2YPP4.js → chunk-JSXPZCET.js} +1 -1
  29. package/dist/chunk-NYNYSPK7.js +318 -0
  30. package/dist/{chunk-HQSEH5F6.js → chunk-ORMZXBKQ.js} +108 -45
  31. package/dist/{chunk-LYVUX7NT.js → chunk-OXUY2A6L.js} +287 -130
  32. package/dist/{chunk-6GSIXTWX.js → chunk-PK6FK2G2.js} +187 -66
  33. package/dist/{chunk-W2EQ7X2L.js → chunk-QE4TTDU3.js} +47 -9
  34. package/dist/{chunk-XLO7SLIX.js → chunk-SXXVZMKZ.js} +262 -70
  35. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  36. package/dist/{chunk-RR7M3FHM.js → chunk-UZQ6ALFS.js} +3 -3
  37. package/dist/{chunk-PNIRUQ4C.js → chunk-VVWPJ543.js} +6 -8
  38. package/dist/{chunk-7GIHSAWB.js → chunk-XC4MEKGA.js} +3 -3
  39. package/dist/{chunk-SKPTERHP.js → chunk-XRRZKZYX.js} +55 -31
  40. package/dist/{contracts-DBdg9J_a.d.cts → contracts-DRIuclVT.d.cts} +10 -2
  41. package/dist/{contracts-DBdg9J_a.d.ts → contracts-DRIuclVT.d.ts} +10 -2
  42. package/dist/{customElement-CNZxEB9G.d.ts → customElement-MmInOW1U.d.cts} +59 -10
  43. package/dist/{customElement-CNZxEB9G.d.cts → customElement-MmInOW1U.d.ts} +59 -10
  44. package/dist/data.cjs +293 -125
  45. package/dist/data.d.cts +47 -9
  46. package/dist/data.d.ts +47 -9
  47. package/dist/data.js +12 -9
  48. package/dist/devtools.cjs +121 -58
  49. package/dist/devtools.js +7 -8
  50. package/dist/dispose-GEIG2KOF.js +28 -0
  51. package/dist/ecosystem.cjs +391 -112
  52. package/dist/ecosystem.d.cts +31 -7
  53. package/dist/ecosystem.d.ts +31 -7
  54. package/dist/ecosystem.js +12 -12
  55. package/dist/extras.cjs +2250 -879
  56. package/dist/extras.d.cts +10 -9
  57. package/dist/extras.d.ts +10 -9
  58. package/dist/extras.js +39 -28
  59. package/dist/index.cjs +273 -82
  60. package/dist/index.d.cts +44 -154
  61. package/dist/index.d.ts +44 -154
  62. package/dist/index.js +24 -27
  63. package/dist/motion.cjs +137 -42
  64. package/dist/motion.js +5 -5
  65. package/dist/patterns.cjs +397 -48
  66. package/dist/patterns.d.cts +32 -9
  67. package/dist/patterns.d.ts +32 -9
  68. package/dist/patterns.js +8 -8
  69. package/dist/performance.cjs +338 -217
  70. package/dist/performance.d.cts +2 -2
  71. package/dist/performance.d.ts +2 -2
  72. package/dist/performance.js +8 -9
  73. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  74. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  75. package/dist/plugins.cjs +587 -203
  76. package/dist/plugins.d.cts +3 -3
  77. package/dist/plugins.d.ts +3 -3
  78. package/dist/plugins.js +35 -23
  79. package/dist/signal-EotCj4hS.d.cts +110 -0
  80. package/dist/signal-EotCj4hS.d.ts +110 -0
  81. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  82. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  83. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  84. package/dist/ssr.cjs +214 -73
  85. package/dist/ssr.d.cts +9 -3
  86. package/dist/ssr.d.ts +9 -3
  87. package/dist/ssr.js +11 -12
  88. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  89. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  90. package/dist/tagFactory-DFstCLQV.d.cts +117 -0
  91. package/dist/tagFactory-DkaNVUNV.d.ts +117 -0
  92. package/dist/testing.cjs +2481 -2153
  93. package/dist/testing.d.cts +56 -5
  94. package/dist/testing.d.ts +56 -5
  95. package/dist/testing.js +580 -307
  96. package/dist/ui.cjs +798 -328
  97. package/dist/ui.d.cts +40 -7
  98. package/dist/ui.d.ts +40 -7
  99. package/dist/ui.js +151 -56
  100. package/dist/widgets.cjs +319 -285
  101. package/dist/widgets.js +9 -10
  102. package/package.json +2 -2
  103. package/dist/chunk-2WLZ6757.js +0 -149
  104. package/dist/chunk-7NBDXVHS.js +0 -60
  105. package/dist/chunk-CCSJMTRN.js +0 -15
  106. package/dist/chunk-QKRPLZ2V.js +0 -108
  107. package/dist/chunk-WWV3SJ3L.js +0 -131
  108. package/dist/dispose-46BOMMQJ.js +0 -19
  109. package/dist/plugin-D30wlGW5.d.cts +0 -71
  110. package/dist/plugin-D30wlGW5.d.ts +0 -71
  111. package/dist/tagFactory-Bzupt4Pj.d.cts +0 -55
  112. package/dist/tagFactory-Bzupt4Pj.d.ts +0 -55
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 = [];
@@ -310,8 +330,18 @@ function untracked(fn) {
310
330
  var subscriberEpochCounter = 0;
311
331
  function retrack(effectFn, subscriber) {
312
332
  const prev = currentSubscriber;
333
+ let savedDepth = 0;
334
+ let savedSuspendSub = null;
335
+ if (trackingSuspended) {
336
+ savedDepth = suspendDepth;
337
+ savedSuspendSub = suspendSavedSub;
338
+ suspendDepth = 0;
339
+ suspendSavedSub = null;
340
+ trackingSuspended = false;
341
+ }
313
342
  currentSubscriber = subscriber;
314
343
  const sub = subscriber;
344
+ if (sub._cap === void 0) sub._cap = currentDisposerCapture();
315
345
  const epoch = ++subscriberEpochCounter;
316
346
  sub._epoch = epoch;
317
347
  sub._structDirty = false;
@@ -324,6 +354,11 @@ function retrack(effectFn, subscriber) {
324
354
  effectFn();
325
355
  } finally {
326
356
  currentSubscriber = prev;
357
+ if (savedDepth !== 0) {
358
+ suspendDepth = savedDepth;
359
+ suspendSavedSub = savedSuspendSub;
360
+ trackingSuspended = true;
361
+ }
327
362
  let node = sub.depsHead ?? null;
328
363
  while (node !== null) {
329
364
  const next = node.subNext;
@@ -343,11 +378,25 @@ function track(effectFn, subscriber) {
343
378
  if (!subscriber) return reactiveBinding(effectFn);
344
379
  cleanup(subscriber);
345
380
  const prev = currentSubscriber;
381
+ let savedDepth = 0;
382
+ let savedSuspendSub = null;
383
+ if (trackingSuspended) {
384
+ savedDepth = suspendDepth;
385
+ savedSuspendSub = suspendSavedSub;
386
+ suspendDepth = 0;
387
+ suspendSavedSub = null;
388
+ trackingSuspended = false;
389
+ }
346
390
  currentSubscriber = subscriber;
347
391
  try {
348
392
  effectFn();
349
393
  } finally {
350
394
  currentSubscriber = prev;
395
+ if (savedDepth !== 0) {
396
+ suspendDepth = savedDepth;
397
+ suspendSavedSub = savedSuspendSub;
398
+ trackingSuspended = true;
399
+ }
351
400
  const sub2 = subscriber;
352
401
  for (let n = sub2.depsHead ?? null; n !== null; n = n.subNext) {
353
402
  const sig = n.sig;
@@ -484,7 +533,12 @@ function drainQueue() {
484
533
  quarantined.add(sub);
485
534
  continue;
486
535
  }
487
- safeInvoke(sub);
536
+ const pushed = beginDisposerCapture(sub._cap ?? null);
537
+ try {
538
+ safeInvoke(sub);
539
+ } finally {
540
+ if (pushed) endDisposerCapture();
541
+ }
488
542
  }
489
543
  }
490
544
  function drainNotificationQueue() {
@@ -518,8 +572,9 @@ function propagateDirty(sub) {
518
572
  if (s._c) {
519
573
  const nSig = s._sig;
520
574
  if (nSig) {
521
- if (!nSig._d) {
575
+ if (!nSig._d || nSig._f === true) {
522
576
  nSig._d = true;
577
+ nSig._f = false;
523
578
  stack.push(nSig);
524
579
  }
525
580
  } else {
@@ -619,7 +674,7 @@ function forEachSubscriber(signal2, visit) {
619
674
  }
620
675
 
621
676
  // src/reactivity/track.ts
622
- var _runtimeVersion = true ? "4.4.0" : "dev";
677
+ var _runtimeVersion = true ? "4.6.0" : "dev";
623
678
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
624
679
  function resolveReactiveApi() {
625
680
  const g = globalThis;
@@ -1250,13 +1305,15 @@ function clipboard() {
1250
1305
  const [copied, setCopied] = signal(false);
1251
1306
  let copiedTimer = null;
1252
1307
  let disposed = false;
1308
+ let generation = 0;
1253
1309
  async function copy(value) {
1254
1310
  if (disposed) return;
1255
1311
  if (typeof navigator === "undefined" || !navigator.clipboard) {
1256
1312
  return;
1257
1313
  }
1314
+ const mine = ++generation;
1258
1315
  await navigator.clipboard.writeText(value);
1259
- if (disposed) return;
1316
+ if (disposed || mine !== generation) return;
1260
1317
  setText(value);
1261
1318
  setCopied(true);
1262
1319
  if (copiedTimer !== null) clearTimeout(copiedTimer);
@@ -1268,6 +1325,7 @@ function clipboard() {
1268
1325
  }
1269
1326
  function dispose() {
1270
1327
  disposed = true;
1328
+ generation++;
1271
1329
  if (copiedTimer !== null) {
1272
1330
  clearTimeout(copiedTimer);
1273
1331
  copiedTimer = null;
@@ -1295,15 +1353,26 @@ function draggable(element, data) {
1295
1353
  let currentEl = null;
1296
1354
  let onDragStart = null;
1297
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
+ }
1298
1368
  const getter = resolveTarget2(element);
1299
1369
  const cleanup3 = effect(() => {
1300
- if (currentEl && onDragStart && onDragEnd) {
1301
- currentEl.removeEventListener("dragstart", onDragStart);
1302
- currentEl.removeEventListener("dragend", onDragEnd);
1303
- }
1304
1370
  const el = getter();
1305
- currentEl = el;
1371
+ if (el === currentEl) return;
1372
+ detach();
1306
1373
  if (!el) return;
1374
+ currentEl = el;
1375
+ prevDraggableAttr = el.getAttribute("draggable");
1307
1376
  el.draggable = true;
1308
1377
  onDragStart = (e) => {
1309
1378
  setIsDragging(true);
@@ -1317,16 +1386,16 @@ function draggable(element, data) {
1317
1386
  el.addEventListener("dragstart", onDragStart);
1318
1387
  el.addEventListener("dragend", onDragEnd);
1319
1388
  });
1389
+ let disposed = false;
1320
1390
  function dispose() {
1391
+ if (disposed) return;
1392
+ disposed = true;
1321
1393
  cleanup3();
1322
- if (currentEl && onDragStart && onDragEnd) {
1323
- currentEl.removeEventListener("dragstart", onDragStart);
1324
- currentEl.removeEventListener("dragend", onDragEnd);
1325
- currentEl = null;
1326
- }
1394
+ detach();
1327
1395
  }
1328
1396
  return { isDragging, dispose };
1329
1397
  }
1398
+ var DROP_ZONE_EXIT_GRACE_MS = 600;
1330
1399
  function dropZone(element, options) {
1331
1400
  const [isOver, setIsOver] = signal(false);
1332
1401
  if (typeof window === "undefined") {
@@ -1338,30 +1407,66 @@ function dropZone(element, options) {
1338
1407
  let onDragEnter = null;
1339
1408
  let onDragLeave = null;
1340
1409
  let onDrop = null;
1341
- const getter = resolveTarget2(element);
1342
- 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() {
1343
1424
  if (currentEl && onDragOver && onDragEnter && onDragLeave && onDrop) {
1344
1425
  currentEl.removeEventListener("dragover", onDragOver);
1345
1426
  currentEl.removeEventListener("dragenter", onDragEnter);
1346
1427
  currentEl.removeEventListener("dragleave", onDragLeave);
1347
1428
  currentEl.removeEventListener("drop", onDrop);
1348
1429
  }
1430
+ currentEl = null;
1431
+ resetOver();
1432
+ }
1433
+ const getter = resolveTarget2(element);
1434
+ const cleanup3 = effect(() => {
1349
1435
  const el = getter();
1350
- currentEl = el;
1436
+ if (el === currentEl) return;
1437
+ detach();
1351
1438
  if (!el) return;
1439
+ currentEl = el;
1352
1440
  onDragOver = (e) => {
1353
1441
  e.preventDefault();
1442
+ cancelPendingExit();
1354
1443
  };
1355
1444
  onDragEnter = (e) => {
1356
1445
  e.preventDefault();
1446
+ cancelPendingExit();
1447
+ depth++;
1357
1448
  setIsOver(true);
1358
1449
  };
1359
- onDragLeave = () => {
1360
- 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);
1361
1466
  };
1362
1467
  onDrop = (e) => {
1363
1468
  e.preventDefault();
1364
- setIsOver(false);
1469
+ resetOver();
1365
1470
  let transferData = null;
1366
1471
  if (e.dataTransfer) {
1367
1472
  const raw = e.dataTransfer.getData("application/json");
@@ -1382,13 +1487,7 @@ function dropZone(element, options) {
1382
1487
  });
1383
1488
  function dispose() {
1384
1489
  cleanup3();
1385
- if (currentEl && onDragOver && onDragEnter && onDragLeave && onDrop) {
1386
- currentEl.removeEventListener("dragover", onDragOver);
1387
- currentEl.removeEventListener("dragenter", onDragEnter);
1388
- currentEl.removeEventListener("dragleave", onDragLeave);
1389
- currentEl.removeEventListener("drop", onDrop);
1390
- currentEl = null;
1391
- }
1490
+ detach();
1392
1491
  }
1393
1492
  return { isOver, dispose };
1394
1493
  }
@@ -1553,9 +1652,9 @@ function formatNumber(value, options) {
1553
1652
  function formatCurrency(value, currency, options) {
1554
1653
  const { locale, ...formatOptions } = options ?? {};
1555
1654
  return new Intl.NumberFormat(locale, {
1655
+ ...formatOptions,
1556
1656
  style: "currency",
1557
- currency,
1558
- ...formatOptions
1657
+ currency
1559
1658
  }).format(value);
1560
1659
  }
1561
1660
 
@@ -1641,18 +1740,36 @@ function swipe(target, options = {}) {
1641
1740
  }
1642
1741
  let startX = 0;
1643
1742
  let startY = 0;
1644
- 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
+ };
1645
1753
  const onStart = (e) => {
1646
- if (e.touches.length === 0) return;
1647
- startX = e.touches[0].clientX;
1648
- startY = e.touches[0].clientY;
1649
- 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;
1650
1767
  };
1651
1768
  const onEnd = (e) => {
1652
- if (!tracking) return;
1653
- tracking = false;
1654
- const touch = e.changedTouches[0];
1769
+ if (trackedId === null) return;
1770
+ const touch = findTracked(e.changedTouches);
1655
1771
  if (!touch) return;
1772
+ trackedId = null;
1656
1773
  const dx = touch.clientX - startX;
1657
1774
  const dy = touch.clientY - startY;
1658
1775
  const absX = Math.abs(dx);
@@ -1669,9 +1786,12 @@ function swipe(target, options = {}) {
1669
1786
  };
1670
1787
  target.addEventListener("touchstart", onStart, { passive: true });
1671
1788
  target.addEventListener("touchend", onEnd, { passive: true });
1789
+ target.addEventListener("touchcancel", onCancel, { passive: true });
1672
1790
  function dispose() {
1673
1791
  target.removeEventListener("touchstart", onStart);
1674
1792
  target.removeEventListener("touchend", onEnd);
1793
+ target.removeEventListener("touchcancel", onCancel);
1794
+ trackedId = null;
1675
1795
  }
1676
1796
  return { direction, dispose };
1677
1797
  }
@@ -1698,6 +1818,18 @@ function windowSize() {
1698
1818
  }
1699
1819
 
1700
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
+ }
1701
1833
  function urlState() {
1702
1834
  if (typeof window === "undefined") {
1703
1835
  const [params2] = signal(new URLSearchParams());
@@ -1731,26 +1863,36 @@ function urlState() {
1731
1863
  }
1732
1864
  window.addEventListener("popstate", syncFromLocation);
1733
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
+ }
1734
1872
  function setParams(next, opts = {}) {
1735
1873
  const p = next instanceof URLSearchParams ? next : new URLSearchParams(next);
1736
1874
  const query = p.toString();
1737
1875
  const newUrl = `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`;
1738
- if (opts.replace) window.history.replaceState(null, "", newUrl);
1739
- else window.history.pushState(null, "", newUrl);
1876
+ writeHistory(newUrl, opts);
1740
1877
  lastSearch = window.location.search;
1741
1878
  setParamsSignal(new URLSearchParams(p));
1879
+ notifyUrlWrite();
1742
1880
  }
1743
1881
  function setHash(next, opts = {}) {
1744
1882
  const normalized = next && next !== "#" ? next.startsWith("#") ? next : `#${next}` : "";
1745
1883
  const newUrl = `${window.location.pathname}${window.location.search}${normalized}`;
1746
- if (opts.replace) window.history.replaceState(null, "", newUrl);
1747
- else window.history.pushState(null, "", newUrl);
1884
+ writeHistory(newUrl, opts);
1748
1885
  lastHash = normalized;
1749
1886
  setHashSignal(normalized);
1887
+ notifyUrlWrite();
1750
1888
  }
1889
+ let disposed = false;
1751
1890
  function dispose() {
1891
+ if (disposed) return;
1892
+ disposed = true;
1752
1893
  window.removeEventListener("popstate", syncFromLocation);
1753
1894
  window.removeEventListener("hashchange", syncFromLocation);
1895
+ _url.instances.delete(syncFromLocation);
1754
1896
  }
1755
1897
  return { params, hash, setParams, setHash, dispose };
1756
1898
  }
@@ -1767,10 +1909,14 @@ function broadcast(channelName) {
1767
1909
  const channel = new BroadcastChannel(channelName);
1768
1910
  const handler = (ev) => setLast(ev.data);
1769
1911
  channel.addEventListener("message", handler);
1912
+ let disposed = false;
1770
1913
  function post(message) {
1914
+ if (disposed) return;
1771
1915
  channel.postMessage(message);
1772
1916
  }
1773
1917
  function dispose() {
1918
+ if (disposed) return;
1919
+ disposed = true;
1774
1920
  channel.removeEventListener("message", handler);
1775
1921
  channel.close();
1776
1922
  }
@@ -1969,31 +2115,42 @@ function animationFrame(options = {}) {
1969
2115
  let prev = -1;
1970
2116
  let start = -1;
1971
2117
  let disposed = false;
2118
+ let active = false;
2119
+ let generation = 0;
1972
2120
  const minFrameMs = options.fpsLimit ? 1e3 / options.fpsLimit : 0;
1973
2121
  const step = (now) => {
2122
+ id = null;
2123
+ if (!active) return;
2124
+ const frameGeneration = generation;
1974
2125
  if (start < 0) start = now;
1975
2126
  const firstTick = prev < 0;
1976
2127
  const dt = firstTick ? 0 : now - prev;
1977
2128
  if (firstTick || dt >= minFrameMs) {
1978
- setDelta(dt);
1979
- setElapsed(now - start);
1980
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;
1981
2135
  }
1982
- id = requestAnimationFrame(step);
2136
+ if (id === null) id = requestAnimationFrame(step);
1983
2137
  };
1984
2138
  function resume() {
1985
- if (disposed || id !== null) return;
1986
- setRunning(true);
2139
+ if (disposed || active) return;
2140
+ active = true;
1987
2141
  id = requestAnimationFrame(step);
2142
+ setRunning(true);
1988
2143
  }
1989
2144
  function pause() {
2145
+ generation++;
2146
+ active = false;
1990
2147
  if (id !== null) {
1991
2148
  cancelAnimationFrame(id);
1992
2149
  id = null;
1993
2150
  }
1994
- setRunning(false);
1995
2151
  prev = -1;
1996
2152
  start = -1;
2153
+ setRunning(false);
1997
2154
  }
1998
2155
  function dispose() {
1999
2156
  disposed = true;
@@ -2123,6 +2280,35 @@ function keyboard(options = {}) {
2123
2280
  }
2124
2281
 
2125
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
+ }
2126
2312
  function speech() {
2127
2313
  const [speaking, setSpeaking] = signal(false);
2128
2314
  const [paused, setPaused] = signal(false);
@@ -2143,19 +2329,16 @@ function speech() {
2143
2329
  };
2144
2330
  }
2145
2331
  const synth = window.speechSynthesis;
2146
- let interval = null;
2147
- function startPolling() {
2148
- if (interval !== null) return;
2149
- interval = setInterval(() => {
2150
- setSpeaking(synth.speaking);
2151
- setPaused(synth.paused);
2152
- if (!synth.speaking && !synth.paused) {
2153
- clearInterval(interval);
2154
- interval = null;
2155
- }
2156
- }, 200);
2157
- }
2158
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);
2159
2342
  function speak(text, options = {}) {
2160
2343
  if (disposed) return;
2161
2344
  const u = new SpeechSynthesisUtterance(text);
@@ -2168,52 +2351,48 @@ function speech() {
2168
2351
  const match = voices.find((v) => v.name === options.voice);
2169
2352
  if (match) u.voice = match;
2170
2353
  }
2171
- u.addEventListener(
2172
- "start",
2173
- () => {
2174
- if (!disposed) setSpeaking(true);
2175
- },
2176
- { once: true }
2177
- );
2178
- u.addEventListener(
2179
- "end",
2180
- () => {
2181
- if (disposed) return;
2182
- setSpeaking(false);
2183
- setPaused(false);
2184
- },
2185
- { once: true }
2186
- );
2187
- u.addEventListener(
2188
- "error",
2189
- () => {
2190
- if (disposed) return;
2191
- setSpeaking(false);
2192
- setPaused(false);
2193
- },
2194
- { once: true }
2195
- );
2196
- synth.speak(u);
2197
- setSpeaking(true);
2198
- 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
+ }
2199
2387
  }
2200
2388
  function dispose() {
2389
+ if (disposed) return;
2390
+ cancelOwned();
2201
2391
  disposed = true;
2202
- if (interval !== null) {
2203
- clearInterval(interval);
2204
- interval = null;
2205
- }
2206
- synth.cancel();
2392
+ syncOwners();
2393
+ _speech.owners.delete(owner);
2207
2394
  }
2208
- return {
2209
- speaking,
2210
- paused,
2211
- speak,
2212
- pause: () => synth.pause(),
2213
- resume: () => synth.resume(),
2214
- cancel: () => synth.cancel(),
2215
- dispose
2216
- };
2395
+ return { speaking, paused, speak, pause, resume, cancel, dispose };
2217
2396
  }
2218
2397
 
2219
2398
  // src/browser/gamepad.ts
@@ -2224,6 +2403,7 @@ function gamepad() {
2224
2403
  } };
2225
2404
  }
2226
2405
  let rafId = null;
2406
+ let disposed = false;
2227
2407
  function snapshot(pad) {
2228
2408
  return {
2229
2409
  index: pad.index,
@@ -2238,7 +2418,7 @@ function gamepad() {
2238
2418
  for (let i = 0; i < a.length; i++) {
2239
2419
  const pa = a[i];
2240
2420
  const pb = b[i];
2241
- 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;
2242
2422
  if (pa.buttons.length !== pb.buttons.length) return false;
2243
2423
  for (let j = 0; j < pa.buttons.length; j++) {
2244
2424
  if (pa.buttons[j].pressed !== pb.buttons[j].pressed) return false;
@@ -2251,15 +2431,19 @@ function gamepad() {
2251
2431
  }
2252
2432
  return true;
2253
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
+ }
2254
2439
  function poll() {
2255
- const raw = navigator.getGamepads();
2256
- const snap = Array.from(raw).filter((g) => g !== null).map(snapshot);
2257
- const current = pads();
2258
- if (!equal(current, snap)) setPads(snap);
2259
- rafId = requestAnimationFrame(poll);
2440
+ rafId = null;
2441
+ if (disposed) return;
2442
+ publish();
2443
+ if (!disposed && rafId === null) rafId = requestAnimationFrame(poll);
2260
2444
  }
2261
2445
  function startPolling() {
2262
- if (rafId === null) poll();
2446
+ if (!disposed && rafId === null) poll();
2263
2447
  }
2264
2448
  function stopPolling() {
2265
2449
  if (rafId !== null) {
@@ -2269,15 +2453,14 @@ function gamepad() {
2269
2453
  }
2270
2454
  const onConnect = () => startPolling();
2271
2455
  const onDisconnect = () => {
2272
- const raw = navigator.getGamepads();
2273
- const hasAny = Array.from(raw).some((g) => g !== null);
2274
- if (!hasAny) stopPolling();
2456
+ if (publish().length === 0) stopPolling();
2275
2457
  };
2276
2458
  window.addEventListener("gamepadconnected", onConnect);
2277
2459
  window.addEventListener("gamepaddisconnected", onDisconnect);
2278
2460
  const initial = Array.from(navigator.getGamepads()).some((g) => g !== null);
2279
2461
  if (initial) startPolling();
2280
2462
  function dispose() {
2463
+ disposed = true;
2281
2464
  stopPolling();
2282
2465
  window.removeEventListener("gamepadconnected", onConnect);
2283
2466
  window.removeEventListener("gamepaddisconnected", onDisconnect);
@@ -2291,8 +2474,7 @@ function pointerLock() {
2291
2474
  if (typeof document === "undefined") {
2292
2475
  return {
2293
2476
  locked,
2294
- request: () => {
2295
- },
2477
+ request: () => Promise.resolve(),
2296
2478
  exit: () => {
2297
2479
  },
2298
2480
  dispose: () => {
@@ -2304,8 +2486,14 @@ function pointerLock() {
2304
2486
  };
2305
2487
  document.addEventListener("pointerlockchange", handler);
2306
2488
  function request(element) {
2307
- if (typeof element.requestPointerLock === "function") {
2308
- 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);
2309
2497
  }
2310
2498
  }
2311
2499
  function exit() {
@@ -2408,25 +2596,46 @@ function imageLoader(src) {
2408
2596
  };
2409
2597
  }
2410
2598
  let current = null;
2599
+ let currentSettled = false;
2411
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
+ }
2412
2618
  function start(url) {
2413
- if (current) {
2414
- current.onload = null;
2415
- current.onerror = null;
2416
- }
2417
- setStatus("pending");
2418
- setImage(null);
2619
+ const token = ++startToken;
2620
+ abandonCurrent();
2621
+ resetState();
2622
+ if (disposed || token !== startToken) return;
2419
2623
  const img = new Image();
2420
2624
  current = img;
2625
+ currentSettled = false;
2421
2626
  img.onload = () => {
2422
2627
  if (disposed || current !== img) return;
2423
- setImage(img);
2424
- setWidth(img.naturalWidth);
2425
- setHeight(img.naturalHeight);
2426
- setStatus("loaded");
2628
+ currentSettled = true;
2629
+ batch(() => {
2630
+ setImage(img);
2631
+ setWidth(img.naturalWidth);
2632
+ setHeight(img.naturalHeight);
2633
+ setStatus("loaded");
2634
+ });
2427
2635
  };
2428
2636
  img.onerror = () => {
2429
2637
  if (disposed || current !== img) return;
2638
+ currentSettled = true;
2430
2639
  setStatus("error");
2431
2640
  };
2432
2641
  img.src = url;
@@ -2441,21 +2650,21 @@ function imageLoader(src) {
2441
2650
  start(src);
2442
2651
  }
2443
2652
  function dispose() {
2653
+ if (disposed) return;
2444
2654
  disposed = true;
2655
+ startToken++;
2445
2656
  if (srcEffectTeardown) {
2446
2657
  srcEffectTeardown();
2447
2658
  srcEffectTeardown = null;
2448
2659
  }
2449
- if (current) {
2450
- current.onload = null;
2451
- current.onerror = null;
2452
- current = null;
2453
- }
2660
+ abandonCurrent();
2661
+ resetState();
2454
2662
  }
2455
2663
  return { status, image, width, height, dispose };
2456
2664
  }
2457
2665
  // Annotate the CommonJS export names for ESM import in node:
2458
2666
  0 && (module.exports = {
2667
+ __resetSpeechCoordinator,
2459
2668
  animationFrame,
2460
2669
  battery,
2461
2670
  bounds,