sibujs 1.4.0 → 1.5.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 (79) hide show
  1. package/README.md +105 -119
  2. package/dist/browser.cjs +53 -14
  3. package/dist/browser.d.cts +14 -9
  4. package/dist/browser.d.ts +14 -9
  5. package/dist/browser.js +4 -4
  6. package/dist/build.cjs +124 -42
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +10 -10
  10. package/dist/cdn.global.js +7 -7
  11. package/dist/chunk-5ZYQ6KDD.js +154 -0
  12. package/dist/chunk-6BMPXPUW.js +26 -0
  13. package/dist/chunk-7GRNSCFT.js +1097 -0
  14. package/dist/chunk-BGTHZHJ5.js +1016 -0
  15. package/dist/chunk-BMPL52BF.js +654 -0
  16. package/dist/chunk-GJPXRJ45.js +37 -0
  17. package/dist/chunk-JCDUJN2F.js +2779 -0
  18. package/dist/chunk-K4G4ZQNR.js +286 -0
  19. package/dist/chunk-MB6QFH3I.js +2776 -0
  20. package/dist/chunk-MYRV7VDM.js +742 -0
  21. package/dist/chunk-NZIIMDWI.js +84 -0
  22. package/dist/chunk-P3XWXJZU.js +282 -0
  23. package/dist/chunk-PDZQY43A.js +616 -0
  24. package/dist/chunk-RJ46C3CS.js +1293 -0
  25. package/dist/chunk-SFKNRVCU.js +292 -0
  26. package/dist/chunk-TDGZL5CU.js +365 -0
  27. package/dist/chunk-VAPYJN4X.js +368 -0
  28. package/dist/chunk-VQDZK23A.js +1023 -0
  29. package/dist/chunk-VQNQZCWJ.js +61 -0
  30. package/dist/chunk-XHK6BDAJ.js +76 -0
  31. package/dist/chunk-XUEEGU5O.js +409 -0
  32. package/dist/contracts-ey_Qh8ef.d.cts +239 -0
  33. package/dist/contracts-ey_Qh8ef.d.ts +239 -0
  34. package/dist/customElement-BL3Uo8dL.d.cts +318 -0
  35. package/dist/customElement-BL3Uo8dL.d.ts +318 -0
  36. package/dist/data.cjs +52 -11
  37. package/dist/data.js +6 -6
  38. package/dist/devtools.cjs +22 -24
  39. package/dist/devtools.js +26 -28
  40. package/dist/ecosystem.cjs +31 -6
  41. package/dist/ecosystem.d.cts +4 -4
  42. package/dist/ecosystem.d.ts +4 -4
  43. package/dist/ecosystem.js +7 -7
  44. package/dist/extras.cjs +304 -108
  45. package/dist/extras.d.cts +3 -3
  46. package/dist/extras.d.ts +3 -3
  47. package/dist/extras.js +19 -19
  48. package/dist/index.cjs +124 -42
  49. package/dist/index.d.cts +58 -48
  50. package/dist/index.d.ts +58 -48
  51. package/dist/index.js +10 -10
  52. package/dist/motion.cjs +13 -2
  53. package/dist/motion.d.cts +1 -1
  54. package/dist/motion.d.ts +1 -1
  55. package/dist/motion.js +3 -3
  56. package/dist/patterns.cjs +91 -24
  57. package/dist/patterns.d.cts +46 -12
  58. package/dist/patterns.d.ts +46 -12
  59. package/dist/patterns.js +5 -5
  60. package/dist/performance.cjs +97 -12
  61. package/dist/performance.d.cts +6 -1
  62. package/dist/performance.d.ts +6 -1
  63. package/dist/performance.js +5 -3
  64. package/dist/plugins.cjs +19 -13
  65. package/dist/plugins.d.cts +3 -3
  66. package/dist/plugins.d.ts +3 -3
  67. package/dist/plugins.js +16 -18
  68. package/dist/ssr.cjs +9 -0
  69. package/dist/ssr.d.cts +1 -1
  70. package/dist/ssr.d.ts +1 -1
  71. package/dist/ssr.js +7 -7
  72. package/dist/testing.js +2 -2
  73. package/dist/ui.cjs +130 -48
  74. package/dist/ui.d.cts +13 -16
  75. package/dist/ui.d.ts +13 -16
  76. package/dist/ui.js +6 -6
  77. package/dist/widgets.cjs +31 -6
  78. package/dist/widgets.js +5 -5
  79. package/package.json +1 -1
package/dist/plugins.cjs CHANGED
@@ -1014,7 +1014,15 @@ function dispose(node) {
1014
1014
  const disposers = elementDisposers.get(current);
1015
1015
  if (disposers) {
1016
1016
  if (_isDev5) activeBindingCount -= disposers.length;
1017
- for (const d of disposers) d();
1017
+ for (const d of disposers) {
1018
+ try {
1019
+ d();
1020
+ } catch (err) {
1021
+ if (_isDev5 && typeof console !== "undefined") {
1022
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
1023
+ }
1024
+ }
1025
+ }
1018
1026
  elementDisposers.delete(current);
1019
1027
  }
1020
1028
  }
@@ -1425,9 +1433,7 @@ function t(key, params) {
1425
1433
  return params ? message.replace(/\{(\w+)\}/g, (_, p2) => String(params[p2] ?? "")) : message;
1426
1434
  }
1427
1435
  function Trans(key, params) {
1428
- return span({
1429
- nodes: () => t(key, params)
1430
- });
1436
+ return span(() => t(key, params));
1431
1437
  }
1432
1438
  function hasTranslation(key) {
1433
1439
  const locale = currentLocale();
@@ -1598,9 +1604,12 @@ var RouteMatcher = class {
1598
1604
  return this.namedRoutes.get(name) || null;
1599
1605
  }
1600
1606
  matchPattern(path2, routePath) {
1601
- if (routePath === "*" || routePath.endsWith("/*")) {
1607
+ if (routePath === "*") {
1608
+ return { params: { pathMatch: path2 } };
1609
+ }
1610
+ if (routePath.endsWith("/*")) {
1602
1611
  const basePath = routePath.slice(0, -2);
1603
- if (path2.startsWith(basePath)) {
1612
+ if (path2 === basePath || path2.startsWith(`${basePath}/`)) {
1604
1613
  return { params: { pathMatch: path2.slice(basePath.length) } };
1605
1614
  }
1606
1615
  return null;
@@ -1719,6 +1728,8 @@ var GuardManager = class {
1719
1728
  const next = (result) => {
1720
1729
  if (resolved || signal2.aborted) return;
1721
1730
  resolved = true;
1731
+ clearTimeout(timeoutId);
1732
+ signal2.removeEventListener("abort", abortHandler);
1722
1733
  if (result instanceof Error) {
1723
1734
  reject(result);
1724
1735
  } else if (result === false) {
@@ -1732,10 +1743,10 @@ var GuardManager = class {
1732
1743
  const abortHandler = () => {
1733
1744
  if (!resolved) {
1734
1745
  resolved = true;
1746
+ clearTimeout(timeoutId);
1735
1747
  reject(new Error("Navigation aborted"));
1736
1748
  }
1737
1749
  };
1738
- signal2.addEventListener("abort", abortHandler);
1739
1750
  const timeoutId = setTimeout(() => {
1740
1751
  if (!resolved) {
1741
1752
  resolved = true;
@@ -1743,6 +1754,7 @@ var GuardManager = class {
1743
1754
  reject(new Error("Guard timeout"));
1744
1755
  }
1745
1756
  }, this.timeout);
1757
+ signal2.addEventListener("abort", abortHandler);
1746
1758
  try {
1747
1759
  guard(to, from, next);
1748
1760
  } catch (error) {
@@ -1753,12 +1765,6 @@ var GuardManager = class {
1753
1765
  reject(error instanceof Error ? error : new Error(String(error)));
1754
1766
  }
1755
1767
  }
1756
- Promise.resolve().then(() => {
1757
- if (resolved) {
1758
- clearTimeout(timeoutId);
1759
- signal2.removeEventListener("abort", abortHandler);
1760
- }
1761
- });
1762
1768
  });
1763
1769
  }
1764
1770
  addBeforeEach(guard) {
@@ -24,7 +24,7 @@ declare function t(key: string, params?: Params$1): string;
24
24
  * registerTranslations("en", { greeting: "Hello, {name}!" });
25
25
  * registerTranslations("es", { greeting: "Hola, {name}!" });
26
26
  *
27
- * div({ nodes: [Trans("greeting", { name: "World" })] });
27
+ * div([Trans("greeting", { name: "World" })]);
28
28
  * // When locale changes, the text updates automatically
29
29
  * ```
30
30
  */
@@ -235,11 +235,11 @@ declare function Route(): Node;
235
235
  * ```ts
236
236
  * // Cache all routes (max 10)
237
237
  * createRouter(routes, { keepAlive: 10 });
238
- * mount(() => div({ nodes: [nav, KeepAliveRoute()] }), root);
238
+ * mount(() => div([nav, KeepAliveRoute()]), root);
239
239
  *
240
240
  * // Or cache specific routes by name
241
241
  * createRouter(routes, { keepAlive: ["dashboard", "settings"] });
242
- * mount(() => div({ nodes: [nav, KeepAliveRoute()] }), root);
242
+ * mount(() => div([nav, KeepAliveRoute()]), root);
243
243
  *
244
244
  * // Or override per-outlet
245
245
  * KeepAliveRoute({ max: 5, include: ["dashboard"] })
package/dist/plugins.d.ts CHANGED
@@ -24,7 +24,7 @@ declare function t(key: string, params?: Params$1): string;
24
24
  * registerTranslations("en", { greeting: "Hello, {name}!" });
25
25
  * registerTranslations("es", { greeting: "Hola, {name}!" });
26
26
  *
27
- * div({ nodes: [Trans("greeting", { name: "World" })] });
27
+ * div([Trans("greeting", { name: "World" })]);
28
28
  * // When locale changes, the text updates automatically
29
29
  * ```
30
30
  */
@@ -235,11 +235,11 @@ declare function Route(): Node;
235
235
  * ```ts
236
236
  * // Cache all routes (max 10)
237
237
  * createRouter(routes, { keepAlive: 10 });
238
- * mount(() => div({ nodes: [nav, KeepAliveRoute()] }), root);
238
+ * mount(() => div([nav, KeepAliveRoute()]), root);
239
239
  *
240
240
  * // Or cache specific routes by name
241
241
  * createRouter(routes, { keepAlive: ["dashboard", "settings"] });
242
- * mount(() => div({ nodes: [nav, KeepAliveRoute()] }), root);
242
+ * mount(() => div([nav, KeepAliveRoute()]), root);
243
243
  *
244
244
  * // Or override per-outlet
245
245
  * KeepAliveRoute({ max: 5, include: ["dashboard"] })
package/dist/plugins.js CHANGED
@@ -34,25 +34,25 @@ import {
34
34
  } from "./chunk-K5ZUMYVS.js";
35
35
  import {
36
36
  span
37
- } from "./chunk-32DY64NT.js";
38
- import "./chunk-F3FA4F32.js";
37
+ } from "./chunk-P3XWXJZU.js";
38
+ import "./chunk-SFKNRVCU.js";
39
39
  import {
40
40
  dispose,
41
41
  registerDisposer
42
- } from "./chunk-PTQJDMRT.js";
42
+ } from "./chunk-5ZYQ6KDD.js";
43
43
  import {
44
44
  sanitizeUrl
45
45
  } from "./chunk-CMBFNA7L.js";
46
46
  import {
47
47
  effect
48
- } from "./chunk-CHF5OHIA.js";
48
+ } from "./chunk-VQNQZCWJ.js";
49
49
  import "./chunk-EUZND3CB.js";
50
50
  import {
51
51
  signal
52
- } from "./chunk-WZSPOOER.js";
52
+ } from "./chunk-NZIIMDWI.js";
53
53
  import {
54
54
  track
55
- } from "./chunk-ZD6OAMTH.js";
55
+ } from "./chunk-K4G4ZQNR.js";
56
56
  import "./chunk-5X6PP2UK.js";
57
57
 
58
58
  // src/plugins/i18n.ts
@@ -73,9 +73,7 @@ function t(key, params) {
73
73
  return params ? message.replace(/\{(\w+)\}/g, (_, p) => String(params[p] ?? "")) : message;
74
74
  }
75
75
  function Trans(key, params) {
76
- return span({
77
- nodes: () => t(key, params)
78
- });
76
+ return span(() => t(key, params));
79
77
  }
80
78
  function hasTranslation(key) {
81
79
  const locale = currentLocale();
@@ -206,9 +204,12 @@ var RouteMatcher = class {
206
204
  return this.namedRoutes.get(name) || null;
207
205
  }
208
206
  matchPattern(path, routePath) {
209
- if (routePath === "*" || routePath.endsWith("/*")) {
207
+ if (routePath === "*") {
208
+ return { params: { pathMatch: path } };
209
+ }
210
+ if (routePath.endsWith("/*")) {
210
211
  const basePath = routePath.slice(0, -2);
211
- if (path.startsWith(basePath)) {
212
+ if (path === basePath || path.startsWith(`${basePath}/`)) {
212
213
  return { params: { pathMatch: path.slice(basePath.length) } };
213
214
  }
214
215
  return null;
@@ -327,6 +328,8 @@ var GuardManager = class {
327
328
  const next = (result) => {
328
329
  if (resolved || signal2.aborted) return;
329
330
  resolved = true;
331
+ clearTimeout(timeoutId);
332
+ signal2.removeEventListener("abort", abortHandler);
330
333
  if (result instanceof Error) {
331
334
  reject(result);
332
335
  } else if (result === false) {
@@ -340,10 +343,10 @@ var GuardManager = class {
340
343
  const abortHandler = () => {
341
344
  if (!resolved) {
342
345
  resolved = true;
346
+ clearTimeout(timeoutId);
343
347
  reject(new Error("Navigation aborted"));
344
348
  }
345
349
  };
346
- signal2.addEventListener("abort", abortHandler);
347
350
  const timeoutId = setTimeout(() => {
348
351
  if (!resolved) {
349
352
  resolved = true;
@@ -351,6 +354,7 @@ var GuardManager = class {
351
354
  reject(new Error("Guard timeout"));
352
355
  }
353
356
  }, this.timeout);
357
+ signal2.addEventListener("abort", abortHandler);
354
358
  try {
355
359
  guard(to, from, next);
356
360
  } catch (error) {
@@ -361,12 +365,6 @@ var GuardManager = class {
361
365
  reject(error instanceof Error ? error : new Error(String(error)));
362
366
  }
363
367
  }
364
- Promise.resolve().then(() => {
365
- if (resolved) {
366
- clearTimeout(timeoutId);
367
- signal2.removeEventListener("abort", abortHandler);
368
- }
369
- });
370
368
  });
371
369
  }
372
370
  addBeforeEach(guard) {
package/dist/ssr.cjs CHANGED
@@ -658,12 +658,21 @@ function queueSignalNotification(signal2) {
658
658
  }
659
659
  }
660
660
  }
661
+ var MAX_DRAIN_ITERATIONS = 1e3;
661
662
  function drainNotificationQueue() {
662
663
  if (notifyDepth > 0) return;
663
664
  notifyDepth++;
664
665
  try {
665
666
  let i2 = 0;
666
667
  while (i2 < pendingQueue.length) {
668
+ if (i2 >= MAX_DRAIN_ITERATIONS) {
669
+ if (typeof console !== "undefined") {
670
+ console.error(
671
+ `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
672
+ );
673
+ }
674
+ break;
675
+ }
667
676
  safeInvoke(pendingQueue[i2]);
668
677
  i2++;
669
678
  }
package/dist/ssr.d.cts CHANGED
@@ -217,7 +217,7 @@ interface SharedScope<T extends Record<string, unknown>> {
217
217
  * const app = createMicroApp({ name: "widget", shadow: true });
218
218
  * document.body.appendChild(app.element);
219
219
  *
220
- * app.mount(() => div({ nodes: "Hello from micro-app!" }));
220
+ * app.mount(() => div("Hello from micro-app!"));
221
221
  *
222
222
  * // Later, tear it down:
223
223
  * app.unmount();
package/dist/ssr.d.ts CHANGED
@@ -217,7 +217,7 @@ interface SharedScope<T extends Record<string, unknown>> {
217
217
  * const app = createMicroApp({ name: "widget", shadow: true });
218
218
  * document.body.appendChild(app.element);
219
219
  *
220
- * app.mount(() => div({ nodes: "Hello from micro-app!" }));
220
+ * app.mount(() => div("Hello from micro-app!"));
221
221
  *
222
222
  * // Later, tear it down:
223
223
  * app.unmount();
package/dist/ssr.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  wasm,
23
23
  worker,
24
24
  workerFn
25
- } from "./chunk-2BYQDGN3.js";
25
+ } from "./chunk-MYRV7VDM.js";
26
26
  import {
27
27
  collectStream,
28
28
  deserializeState,
@@ -42,14 +42,14 @@ import {
42
42
  suspenseSwapScript,
43
43
  trustHTML
44
44
  } from "./chunk-3X2YG6YM.js";
45
- import "./chunk-32DY64NT.js";
46
- import "./chunk-F3FA4F32.js";
47
- import "./chunk-PTQJDMRT.js";
45
+ import "./chunk-P3XWXJZU.js";
46
+ import "./chunk-SFKNRVCU.js";
47
+ import "./chunk-5ZYQ6KDD.js";
48
48
  import "./chunk-CMBFNA7L.js";
49
- import "./chunk-CHF5OHIA.js";
49
+ import "./chunk-VQNQZCWJ.js";
50
50
  import "./chunk-EUZND3CB.js";
51
- import "./chunk-WZSPOOER.js";
52
- import "./chunk-ZD6OAMTH.js";
51
+ import "./chunk-NZIIMDWI.js";
52
+ import "./chunk-K4G4ZQNR.js";
53
53
  import "./chunk-5X6PP2UK.js";
54
54
  export {
55
55
  Head,
package/dist/testing.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  effect
3
- } from "./chunk-CHF5OHIA.js";
3
+ } from "./chunk-VQNQZCWJ.js";
4
4
  import "./chunk-EUZND3CB.js";
5
- import "./chunk-ZD6OAMTH.js";
5
+ import "./chunk-K4G4ZQNR.js";
6
6
  import "./chunk-5X6PP2UK.js";
7
7
 
8
8
  // src/testing/a11y.ts
package/dist/ui.cjs CHANGED
@@ -324,21 +324,37 @@ function derived(getter, options) {
324
324
  cs._v = getter();
325
325
  }, markDirty);
326
326
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
327
+ let evaluating = false;
327
328
  function computedGetter() {
329
+ if (evaluating) {
330
+ throw new Error(
331
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
332
+ );
333
+ }
328
334
  if (trackingSuspended) {
329
335
  if (cs._d) {
330
- cs._d = false;
331
- cs._v = getter();
336
+ evaluating = true;
337
+ try {
338
+ cs._d = false;
339
+ cs._v = getter();
340
+ } finally {
341
+ evaluating = false;
342
+ }
332
343
  }
333
344
  return cs._v;
334
345
  }
335
346
  recordDependency(cs);
336
347
  if (cs._d) {
337
348
  const oldValue = cs._v;
338
- track(() => {
339
- cs._d = false;
340
- cs._v = getter();
341
- }, markDirty);
349
+ evaluating = true;
350
+ try {
351
+ track(() => {
352
+ cs._d = false;
353
+ cs._v = getter();
354
+ }, markDirty);
355
+ } finally {
356
+ evaluating = false;
357
+ }
342
358
  if (hook && oldValue !== cs._v) {
343
359
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
344
360
  }
@@ -477,7 +493,7 @@ function bindField(field, extras) {
477
493
  blur: () => field.touch()
478
494
  };
479
495
  const { on: extraOn, value: _ignoreValue, ...restExtras } = extras ?? {};
480
- const mergedOn = extraOn && typeof extraOn === "object" ? { ...fieldOn, ...extraOn } : fieldOn;
496
+ const mergedOn = extraOn && typeof extraOn === "object" ? { ...extraOn, ...fieldOn } : fieldOn;
481
497
  return {
482
498
  value: field.value,
483
499
  on: mergedOn,
@@ -548,14 +564,23 @@ function form(config) {
548
564
  }
549
565
  return result;
550
566
  });
567
+ const [submitting, setSubmitting] = signal(false);
551
568
  function handleSubmit(onSubmit) {
552
569
  return (e) => {
553
570
  if (e) e.preventDefault();
571
+ if (submitting()) return;
554
572
  for (const field of Object.values(fieldMap)) {
555
573
  field.touch();
556
574
  }
557
575
  if (isValid()) {
558
- onSubmit(values());
576
+ const result = onSubmit(values());
577
+ if (result && typeof result.then === "function") {
578
+ setSubmitting(true);
579
+ result.then(
580
+ () => setSubmitting(false),
581
+ () => setSubmitting(false)
582
+ );
583
+ }
559
584
  }
560
585
  };
561
586
  }
@@ -572,6 +597,7 @@ function form(config) {
572
597
  errors,
573
598
  isValid,
574
599
  isDirty,
600
+ submitting,
575
601
  touched: touchedState,
576
602
  values,
577
603
  handleSubmit,
@@ -615,6 +641,20 @@ function formAction(fn) {
615
641
  return { run, pending, error, result, reset, onSubmit };
616
642
  }
617
643
 
644
+ // src/core/rendering/dispose.ts
645
+ var elementDisposers = /* @__PURE__ */ new WeakMap();
646
+ var _isDev4 = isDev();
647
+ var activeBindingCount = 0;
648
+ function registerDisposer(node, teardown) {
649
+ let disposers = elementDisposers.get(node);
650
+ if (!disposers) {
651
+ disposers = [];
652
+ elementDisposers.set(node, disposers);
653
+ }
654
+ disposers.push(teardown);
655
+ if (_isDev4) activeBindingCount++;
656
+ }
657
+
618
658
  // src/core/ssr-context.ts
619
659
  var ssrMode = false;
620
660
  function isSSR() {
@@ -668,9 +708,9 @@ function VirtualList(props) {
668
708
  content.style.right = "0";
669
709
  spacer.appendChild(content);
670
710
  container.appendChild(spacer);
671
- container.addEventListener("scroll", () => {
672
- setScrollTop(container.scrollTop);
673
- });
711
+ const onScroll = () => setScrollTop(container.scrollTop);
712
+ container.addEventListener("scroll", onScroll);
713
+ registerDisposer(container, () => container.removeEventListener("scroll", onScroll));
674
714
  const update = () => {
675
715
  const items = props.items();
676
716
  const totalHeight = items.length * props.itemHeight;
@@ -778,13 +818,52 @@ function inputMask(options) {
778
818
  }
779
819
  return raw;
780
820
  }
821
+ function isSlot(c) {
822
+ return c === "9" || c === "A" || c === "*";
823
+ }
824
+ function buildStripRegex() {
825
+ const hasDigit = options.pattern.includes("9");
826
+ const hasLetter = options.pattern.includes("A");
827
+ const hasAny = options.pattern.includes("*");
828
+ if (hasAny) {
829
+ const literals = /* @__PURE__ */ new Set();
830
+ for (const c of options.pattern) {
831
+ if (!isSlot(c)) literals.add(c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
832
+ }
833
+ return literals.size > 0 ? new RegExp(`[${Array.from(literals).join("")}]`, "g") : /(?!)/g;
834
+ }
835
+ if (hasDigit && hasLetter) return /[^a-zA-Z0-9]/g;
836
+ if (hasDigit) return /[^0-9]/g;
837
+ if (hasLetter) return /[^a-zA-Z]/g;
838
+ return /[^a-zA-Z0-9]/g;
839
+ }
840
+ const stripRegex = buildStripRegex();
841
+ const rawCharTest = options.pattern.includes("*") ? () => true : (c) => /[a-zA-Z0-9]/.test(c);
781
842
  function bind(input) {
782
843
  input.addEventListener("input", () => {
783
- const raw = input.value.replace(/[^a-zA-Z0-9]/g, "");
844
+ const cursorBefore = input.selectionStart ?? input.value.length;
845
+ const oldValue = input.value;
846
+ const raw = oldValue.replace(stripRegex, "");
784
847
  const masked = applyMask(raw);
785
848
  setValue(masked);
786
849
  setRawValue(extractRaw(masked));
787
850
  input.value = masked;
851
+ let rawBefore = 0;
852
+ for (let i = 0; i < cursorBefore && i < oldValue.length; i++) {
853
+ if (rawCharTest(oldValue[i])) rawBefore++;
854
+ }
855
+ let newCursor = 0;
856
+ let counted = 0;
857
+ for (; newCursor < masked.length; newCursor++) {
858
+ if (newCursor < options.pattern.length && isSlot(options.pattern[newCursor])) {
859
+ counted++;
860
+ if (counted >= rawBefore) {
861
+ newCursor++;
862
+ break;
863
+ }
864
+ }
865
+ }
866
+ input.setSelectionRange(newCursor, newCursor);
788
867
  });
789
868
  input.addEventListener("focus", () => {
790
869
  if (!input.value) {
@@ -854,7 +933,10 @@ function FocusTrap(nodes, options = {}) {
854
933
  const focusable = container.querySelectorAll(
855
934
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
856
935
  );
857
- if (focusable.length === 0) return;
936
+ if (focusable.length === 0) {
937
+ e.preventDefault();
938
+ return;
939
+ }
858
940
  const first = focusable[0];
859
941
  const last = focusable[focusable.length - 1];
860
942
  if (e.shiftKey) {
@@ -877,24 +959,27 @@ function FocusTrap(nodes, options = {}) {
877
959
  first?.focus();
878
960
  });
879
961
  }
962
+ let trapObserver = null;
963
+ function restoreFocusAndCleanup() {
964
+ if (options.restoreFocus !== false) previouslyFocused?.focus();
965
+ if (trapObserver) {
966
+ trapObserver.disconnect();
967
+ trapObserver = null;
968
+ }
969
+ }
880
970
  if (options.restoreFocus !== false) {
881
- const observer = new MutationObserver((mutations) => {
882
- for (const mutation of mutations) {
883
- for (const removed of Array.from(mutation.removedNodes)) {
884
- if (removed === container || removed.contains(container)) {
885
- previouslyFocused?.focus();
886
- observer.disconnect();
887
- return;
888
- }
889
- }
971
+ trapObserver = new MutationObserver(() => {
972
+ if (!container.isConnected) {
973
+ restoreFocusAndCleanup();
890
974
  }
891
975
  });
892
976
  queueMicrotask(() => {
893
- if (container.parentNode) {
894
- observer.observe(container.parentNode, { childList: true });
977
+ if (container.isConnected) {
978
+ trapObserver.observe(document.body, { childList: true, subtree: true });
895
979
  }
896
980
  });
897
981
  }
982
+ registerDisposer(container, restoreFocusAndCleanup);
898
983
  return container;
899
984
  }
900
985
  function hotkey(combo, handler, options = {}) {
@@ -951,20 +1036,6 @@ function createId(prefix = "sibu") {
951
1036
  return `${prefix}-${idCounter}`;
952
1037
  }
953
1038
 
954
- // src/core/rendering/dispose.ts
955
- var elementDisposers = /* @__PURE__ */ new WeakMap();
956
- var _isDev4 = isDev();
957
- var activeBindingCount = 0;
958
- function registerDisposer(node, teardown) {
959
- let disposers = elementDisposers.get(node);
960
- if (!disposers) {
961
- disposers = [];
962
- elementDisposers.set(node, disposers);
963
- }
964
- disposers.push(teardown);
965
- if (_isDev4) activeBindingCount++;
966
- }
967
-
968
1039
  // src/ui/a11yPrimitives.ts
969
1040
  var DEFAULT_FOCUS_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
970
1041
  function createFocusManager(container, options = {}) {
@@ -1177,6 +1248,10 @@ function scopedStyle(css) {
1177
1248
  if (trimmed.startsWith("@") || trimmed.startsWith("from") || trimmed.startsWith("to") || /^\d+%$/.test(trimmed)) {
1178
1249
  return match;
1179
1250
  }
1251
+ const pseudoIdx = trimmed.indexOf("::");
1252
+ if (pseudoIdx >= 0) {
1253
+ return `${trimmed.slice(0, pseudoIdx)}[${attr}]${trimmed.slice(pseudoIdx)}${delimiter}`;
1254
+ }
1180
1255
  return `${trimmed}[${attr}]${delimiter}`;
1181
1256
  });
1182
1257
  if (typeof document !== "undefined") {
@@ -1341,28 +1416,35 @@ function dialog() {
1341
1416
  close();
1342
1417
  }
1343
1418
  }
1344
- function open() {
1345
- setIsOpen(true);
1419
+ function attachListener() {
1346
1420
  if (typeof window !== "undefined" && !listenerAttached) {
1347
1421
  window.addEventListener("keydown", handleKeydown);
1348
1422
  listenerAttached = true;
1349
1423
  }
1350
1424
  }
1351
- function close() {
1352
- setIsOpen(false);
1425
+ function detachListener() {
1353
1426
  if (typeof window !== "undefined" && listenerAttached) {
1354
1427
  window.removeEventListener("keydown", handleKeydown);
1355
1428
  listenerAttached = false;
1356
1429
  }
1357
1430
  }
1431
+ function open() {
1432
+ setIsOpen(true);
1433
+ attachListener();
1434
+ }
1435
+ function close() {
1436
+ setIsOpen(false);
1437
+ detachListener();
1438
+ }
1358
1439
  function toggle() {
1359
- if (isOpen()) {
1360
- close();
1361
- } else {
1362
- open();
1363
- }
1440
+ if (isOpen()) close();
1441
+ else open();
1442
+ }
1443
+ function dispose() {
1444
+ detachListener();
1445
+ setIsOpen(false);
1364
1446
  }
1365
- return { open, close, isOpen, toggle };
1447
+ return { open, close, isOpen, toggle, dispose };
1366
1448
  }
1367
1449
 
1368
1450
  // src/ui/toast.ts
package/dist/ui.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- export { B as BoundFieldProps, C as CustomElementOptions, F as FieldConfig, a as FocusTrap, b as FormConfig, c as FormField, d as FormReturn, I as IntersectionResult, M as MaskOptions, T as Toast, e as ToastInstance, V as ValidatorFn, f as VirtualList, g as VirtualListProps, h as announce, i as aria, j as bindAttrs, k as bindBoolAttr, l as bindData, m as bindField, n as creditCardMask, o as custom, p as dateMask, q as defineElement, r as dialog, s as email, t as eventBus, u as focus, v as form, w as hotkey, x as infiniteScroll, y as inputMask, z as intersection, A as lazyLoad, D as matchesPattern, E as max, G as maxLength, H as min, J as minLength, K as pagination, L as phoneMask, N as removeScopedStyle, O as required, P as scopedStyle, Q as ssnMask, R as svgElement, S as timeMask, U as toast, W as withScopedStyle, X as zipMask } from './customElement-D2DJp_xn.cjs';
2
- export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as compose, d as createGuard, e as createSlots, f as defineComponent, g as defineSlottedComponent, h as defineStrictComponent, v as validateProps, i as validators, w as withBoundary, j as withDefaults, k as withProps, l as withWrapper } from './contracts-xo5ckdRP.cjs';
1
+ export { B as BoundFieldProps, C as CustomElementOptions, F as FieldConfig, a as FocusTrap, b as FormConfig, c as FormField, d as FormReturn, I as IntersectionResult, M as MaskOptions, T as Toast, e as ToastInstance, V as ValidatorFn, f as VirtualList, g as VirtualListProps, h as announce, i as aria, j as bindAttrs, k as bindBoolAttr, l as bindData, m as bindField, n as creditCardMask, o as custom, p as dateMask, q as defineElement, r as dialog, s as email, t as eventBus, u as focus, v as form, w as hotkey, x as infiniteScroll, y as inputMask, z as intersection, A as lazyLoad, D as matchesPattern, E as max, G as maxLength, H as min, J as minLength, K as pagination, L as phoneMask, N as removeScopedStyle, O as required, P as scopedStyle, Q as ssnMask, R as svgElement, S as timeMask, U as toast, W as withScopedStyle, X as zipMask } from './customElement-BL3Uo8dL.cjs';
2
+ export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as compose, d as createGuard, e as createSlots, f as defineComponent, g as defineSlottedComponent, h as defineStrictComponent, v as validateProps, i as validators, w as withBoundary, j as withDefaults, k as withProps, l as withWrapper } from './contracts-ey_Qh8ef.cjs';
3
3
 
4
4
  interface FormActionHandle<TArgs extends unknown[], TResult> {
5
5
  /** Invoke the action. Rejections become `error()`, resolutions `result()`. */
@@ -30,14 +30,11 @@ interface FormActionHandle<TArgs extends unknown[], TResult> {
30
30
  * return res.json();
31
31
  * });
32
32
  *
33
- * form({
34
- * on: { submit: save.onSubmit },
35
- * nodes: [
36
- * input({ name: "title" }),
37
- * button({ disabled: save.pending, nodes: () => (save.pending() ? "Saving..." : "Save") }),
38
- * when(() => save.error() != null, () => div({ class: "error", nodes: () => String(save.error()) })),
39
- * ],
40
- * });
33
+ * form({ on: { submit: save.onSubmit } }, [
34
+ * input({ name: "title" }),
35
+ * button({ disabled: save.pending }, () => (save.pending() ? "Saving..." : "Save")),
36
+ * when(() => save.error() != null, () => div("error", () => String(save.error()))),
37
+ * ]);
41
38
  * ```
42
39
  */
43
40
  declare function formAction<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult>): FormActionHandle<TArgs, TResult>;
@@ -102,10 +99,10 @@ interface ListboxHandle {
102
99
  *
103
100
  * @example
104
101
  * ```ts
105
- * const container = ul({ nodes: [
106
- * li({ role: "option", "data-value": "a", nodes: "Apple" }),
107
- * li({ role: "option", "data-value": "b", nodes: "Banana" }),
108
- * ]}) as HTMLElement;
102
+ * const container = ul([
103
+ * li({ role: "option", "data-value": "a" }, "Apple"),
104
+ * li({ role: "option", "data-value": "b" }, "Banana"),
105
+ * ]) as HTMLElement;
109
106
  *
110
107
  * const lb = createListbox(container, { onSelect: v => console.log(v) });
111
108
  * ```
@@ -142,8 +139,8 @@ interface DialogAriaHandle {
142
139
  * const dlg = document.createElement("div");
143
140
  * const aria = createDialogAria(dlg, { alert: false });
144
141
  * dlg.append(
145
- * h2({ id: aria.titleId, nodes: "Delete?" }),
146
- * p({ id: aria.descriptionId, nodes: "This cannot be undone." }),
142
+ * h2({ id: aria.titleId }, "Delete?"),
143
+ * p({ id: aria.descriptionId }, "This cannot be undone."),
147
144
  * );
148
145
  * ```
149
146
  */