sibujs 1.3.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 (87) 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 +125 -135
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +11 -91
  10. package/dist/cdn.global.js +6 -6
  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-CNZ35WI2.js +178 -0
  17. package/dist/chunk-GJPXRJ45.js +37 -0
  18. package/dist/chunk-JCDUJN2F.js +2779 -0
  19. package/dist/chunk-K4G4ZQNR.js +286 -0
  20. package/dist/chunk-M4NLBH4I.js +725 -0
  21. package/dist/chunk-MB6QFH3I.js +2776 -0
  22. package/dist/chunk-MYRV7VDM.js +742 -0
  23. package/dist/chunk-NZIIMDWI.js +84 -0
  24. package/dist/chunk-P3XWXJZU.js +282 -0
  25. package/dist/chunk-PDZQY43A.js +616 -0
  26. package/dist/chunk-RJ46C3CS.js +1293 -0
  27. package/dist/chunk-SFKNRVCU.js +292 -0
  28. package/dist/chunk-TDGZL5CU.js +365 -0
  29. package/dist/chunk-UHNL42EF.js +2730 -0
  30. package/dist/chunk-VAPYJN4X.js +368 -0
  31. package/dist/chunk-VQDZK23A.js +1023 -0
  32. package/dist/chunk-VQNQZCWJ.js +61 -0
  33. package/dist/chunk-XHK6BDAJ.js +76 -0
  34. package/dist/chunk-XUEEGU5O.js +409 -0
  35. package/dist/chunk-ZWKZCBO6.js +317 -0
  36. package/dist/contracts-ey_Qh8ef.d.cts +239 -0
  37. package/dist/contracts-ey_Qh8ef.d.ts +239 -0
  38. package/dist/contracts-xo5ckdRP.d.cts +240 -0
  39. package/dist/contracts-xo5ckdRP.d.ts +240 -0
  40. package/dist/customElement-BL3Uo8dL.d.cts +318 -0
  41. package/dist/customElement-BL3Uo8dL.d.ts +318 -0
  42. package/dist/data.cjs +52 -11
  43. package/dist/data.js +6 -6
  44. package/dist/devtools.cjs +22 -24
  45. package/dist/devtools.d.cts +1 -1
  46. package/dist/devtools.d.ts +1 -1
  47. package/dist/devtools.js +26 -28
  48. package/dist/ecosystem.cjs +31 -6
  49. package/dist/ecosystem.d.cts +4 -4
  50. package/dist/ecosystem.d.ts +4 -4
  51. package/dist/ecosystem.js +7 -7
  52. package/dist/extras.cjs +305 -131
  53. package/dist/extras.d.cts +3 -3
  54. package/dist/extras.d.ts +3 -3
  55. package/dist/extras.js +21 -29
  56. package/dist/index.cjs +124 -56
  57. package/dist/index.d.cts +60 -72
  58. package/dist/index.d.ts +60 -72
  59. package/dist/index.js +10 -14
  60. package/dist/motion.cjs +13 -2
  61. package/dist/motion.d.cts +1 -1
  62. package/dist/motion.d.ts +1 -1
  63. package/dist/motion.js +3 -3
  64. package/dist/patterns.cjs +91 -46
  65. package/dist/patterns.d.cts +46 -60
  66. package/dist/patterns.d.ts +46 -60
  67. package/dist/patterns.js +6 -14
  68. package/dist/performance.cjs +97 -12
  69. package/dist/performance.d.cts +6 -1
  70. package/dist/performance.d.ts +6 -1
  71. package/dist/performance.js +5 -3
  72. package/dist/plugins.cjs +20 -14
  73. package/dist/plugins.d.cts +3 -3
  74. package/dist/plugins.d.ts +3 -3
  75. package/dist/plugins.js +17 -19
  76. package/dist/ssr.cjs +9 -0
  77. package/dist/ssr.d.cts +1 -1
  78. package/dist/ssr.d.ts +1 -1
  79. package/dist/ssr.js +7 -7
  80. package/dist/testing.js +2 -2
  81. package/dist/ui.cjs +130 -53
  82. package/dist/ui.d.cts +13 -16
  83. package/dist/ui.d.ts +13 -16
  84. package/dist/ui.js +7 -9
  85. package/dist/widgets.cjs +31 -6
  86. package/dist/widgets.js +5 -5
  87. package/package.json +1 -1
package/dist/ui.cjs CHANGED
@@ -30,7 +30,6 @@ __export(ui_exports, {
30
30
  bindBoolAttr: () => bindBoolAttr,
31
31
  bindData: () => bindData,
32
32
  bindField: () => bindField,
33
- composable: () => composable,
34
33
  compose: () => compose,
35
34
  createDialogAria: () => createDialogAria,
36
35
  createFocusManager: () => createFocusManager,
@@ -325,21 +324,37 @@ function derived(getter, options) {
325
324
  cs._v = getter();
326
325
  }, markDirty);
327
326
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
327
+ let evaluating = false;
328
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
+ }
329
334
  if (trackingSuspended) {
330
335
  if (cs._d) {
331
- cs._d = false;
332
- cs._v = getter();
336
+ evaluating = true;
337
+ try {
338
+ cs._d = false;
339
+ cs._v = getter();
340
+ } finally {
341
+ evaluating = false;
342
+ }
333
343
  }
334
344
  return cs._v;
335
345
  }
336
346
  recordDependency(cs);
337
347
  if (cs._d) {
338
348
  const oldValue = cs._v;
339
- track(() => {
340
- cs._d = false;
341
- cs._v = getter();
342
- }, markDirty);
349
+ evaluating = true;
350
+ try {
351
+ track(() => {
352
+ cs._d = false;
353
+ cs._v = getter();
354
+ }, markDirty);
355
+ } finally {
356
+ evaluating = false;
357
+ }
343
358
  if (hook && oldValue !== cs._v) {
344
359
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
345
360
  }
@@ -478,7 +493,7 @@ function bindField(field, extras) {
478
493
  blur: () => field.touch()
479
494
  };
480
495
  const { on: extraOn, value: _ignoreValue, ...restExtras } = extras ?? {};
481
- const mergedOn = extraOn && typeof extraOn === "object" ? { ...fieldOn, ...extraOn } : fieldOn;
496
+ const mergedOn = extraOn && typeof extraOn === "object" ? { ...extraOn, ...fieldOn } : fieldOn;
482
497
  return {
483
498
  value: field.value,
484
499
  on: mergedOn,
@@ -549,14 +564,23 @@ function form(config) {
549
564
  }
550
565
  return result;
551
566
  });
567
+ const [submitting, setSubmitting] = signal(false);
552
568
  function handleSubmit(onSubmit) {
553
569
  return (e) => {
554
570
  if (e) e.preventDefault();
571
+ if (submitting()) return;
555
572
  for (const field of Object.values(fieldMap)) {
556
573
  field.touch();
557
574
  }
558
575
  if (isValid()) {
559
- 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
+ }
560
584
  }
561
585
  };
562
586
  }
@@ -573,6 +597,7 @@ function form(config) {
573
597
  errors,
574
598
  isValid,
575
599
  isDirty,
600
+ submitting,
576
601
  touched: touchedState,
577
602
  values,
578
603
  handleSubmit,
@@ -616,6 +641,20 @@ function formAction(fn) {
616
641
  return { run, pending, error, result, reset, onSubmit };
617
642
  }
618
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
+
619
658
  // src/core/ssr-context.ts
620
659
  var ssrMode = false;
621
660
  function isSSR() {
@@ -669,9 +708,9 @@ function VirtualList(props) {
669
708
  content.style.right = "0";
670
709
  spacer.appendChild(content);
671
710
  container.appendChild(spacer);
672
- container.addEventListener("scroll", () => {
673
- setScrollTop(container.scrollTop);
674
- });
711
+ const onScroll = () => setScrollTop(container.scrollTop);
712
+ container.addEventListener("scroll", onScroll);
713
+ registerDisposer(container, () => container.removeEventListener("scroll", onScroll));
675
714
  const update = () => {
676
715
  const items = props.items();
677
716
  const totalHeight = items.length * props.itemHeight;
@@ -779,13 +818,52 @@ function inputMask(options) {
779
818
  }
780
819
  return raw;
781
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);
782
842
  function bind(input) {
783
843
  input.addEventListener("input", () => {
784
- 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, "");
785
847
  const masked = applyMask(raw);
786
848
  setValue(masked);
787
849
  setRawValue(extractRaw(masked));
788
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);
789
867
  });
790
868
  input.addEventListener("focus", () => {
791
869
  if (!input.value) {
@@ -855,7 +933,10 @@ function FocusTrap(nodes, options = {}) {
855
933
  const focusable = container.querySelectorAll(
856
934
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
857
935
  );
858
- if (focusable.length === 0) return;
936
+ if (focusable.length === 0) {
937
+ e.preventDefault();
938
+ return;
939
+ }
859
940
  const first = focusable[0];
860
941
  const last = focusable[focusable.length - 1];
861
942
  if (e.shiftKey) {
@@ -878,24 +959,27 @@ function FocusTrap(nodes, options = {}) {
878
959
  first?.focus();
879
960
  });
880
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
+ }
881
970
  if (options.restoreFocus !== false) {
882
- const observer = new MutationObserver((mutations) => {
883
- for (const mutation of mutations) {
884
- for (const removed of Array.from(mutation.removedNodes)) {
885
- if (removed === container || removed.contains(container)) {
886
- previouslyFocused?.focus();
887
- observer.disconnect();
888
- return;
889
- }
890
- }
971
+ trapObserver = new MutationObserver(() => {
972
+ if (!container.isConnected) {
973
+ restoreFocusAndCleanup();
891
974
  }
892
975
  });
893
976
  queueMicrotask(() => {
894
- if (container.parentNode) {
895
- observer.observe(container.parentNode, { childList: true });
977
+ if (container.isConnected) {
978
+ trapObserver.observe(document.body, { childList: true, subtree: true });
896
979
  }
897
980
  });
898
981
  }
982
+ registerDisposer(container, restoreFocusAndCleanup);
899
983
  return container;
900
984
  }
901
985
  function hotkey(combo, handler, options = {}) {
@@ -952,20 +1036,6 @@ function createId(prefix = "sibu") {
952
1036
  return `${prefix}-${idCounter}`;
953
1037
  }
954
1038
 
955
- // src/core/rendering/dispose.ts
956
- var elementDisposers = /* @__PURE__ */ new WeakMap();
957
- var _isDev4 = isDev();
958
- var activeBindingCount = 0;
959
- function registerDisposer(node, teardown) {
960
- let disposers = elementDisposers.get(node);
961
- if (!disposers) {
962
- disposers = [];
963
- elementDisposers.set(node, disposers);
964
- }
965
- disposers.push(teardown);
966
- if (_isDev4) activeBindingCount++;
967
- }
968
-
969
1039
  // src/ui/a11yPrimitives.ts
970
1040
  var DEFAULT_FOCUS_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
971
1041
  function createFocusManager(container, options = {}) {
@@ -1178,6 +1248,10 @@ function scopedStyle(css) {
1178
1248
  if (trimmed.startsWith("@") || trimmed.startsWith("from") || trimmed.startsWith("to") || /^\d+%$/.test(trimmed)) {
1179
1249
  return match;
1180
1250
  }
1251
+ const pseudoIdx = trimmed.indexOf("::");
1252
+ if (pseudoIdx >= 0) {
1253
+ return `${trimmed.slice(0, pseudoIdx)}[${attr}]${trimmed.slice(pseudoIdx)}${delimiter}`;
1254
+ }
1181
1255
  return `${trimmed}[${attr}]${delimiter}`;
1182
1256
  });
1183
1257
  if (typeof document !== "undefined") {
@@ -1342,28 +1416,35 @@ function dialog() {
1342
1416
  close();
1343
1417
  }
1344
1418
  }
1345
- function open() {
1346
- setIsOpen(true);
1419
+ function attachListener() {
1347
1420
  if (typeof window !== "undefined" && !listenerAttached) {
1348
1421
  window.addEventListener("keydown", handleKeydown);
1349
1422
  listenerAttached = true;
1350
1423
  }
1351
1424
  }
1352
- function close() {
1353
- setIsOpen(false);
1425
+ function detachListener() {
1354
1426
  if (typeof window !== "undefined" && listenerAttached) {
1355
1427
  window.removeEventListener("keydown", handleKeydown);
1356
1428
  listenerAttached = false;
1357
1429
  }
1358
1430
  }
1431
+ function open() {
1432
+ setIsOpen(true);
1433
+ attachListener();
1434
+ }
1435
+ function close() {
1436
+ setIsOpen(false);
1437
+ detachListener();
1438
+ }
1359
1439
  function toggle() {
1360
- if (isOpen()) {
1361
- close();
1362
- } else {
1363
- open();
1364
- }
1440
+ if (isOpen()) close();
1441
+ else open();
1365
1442
  }
1366
- return { open, close, isOpen, toggle };
1443
+ function dispose() {
1444
+ detachListener();
1445
+ setIsOpen(false);
1446
+ }
1447
+ return { open, close, isOpen, toggle, dispose };
1367
1448
  }
1368
1449
 
1369
1450
  // src/ui/toast.ts
@@ -1758,9 +1839,6 @@ function svgElement(tag, props = {}, ...nodes) {
1758
1839
  }
1759
1840
 
1760
1841
  // src/patterns/composable.ts
1761
- function composable(setup) {
1762
- return setup;
1763
- }
1764
1842
  function RenderProp(props) {
1765
1843
  return props.render(props.data());
1766
1844
  }
@@ -1933,7 +2011,6 @@ function createGuard(validator) {
1933
2011
  bindBoolAttr,
1934
2012
  bindData,
1935
2013
  bindField,
1936
- composable,
1937
2014
  compose,
1938
2015
  createDialogAria,
1939
2016
  createFocusManager,
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 composable, d as compose, e as createGuard, f as createSlots, g as defineComponent, h as defineSlottedComponent, i as defineStrictComponent, v as validateProps, j as validators, w as withBoundary, k as withDefaults, l as withProps, m as withWrapper } from './contracts-DDrwxvJ-.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
  */
package/dist/ui.d.ts 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.js';
2
- export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as composable, d as compose, e as createGuard, f as createSlots, g as defineComponent, h as defineSlottedComponent, i as defineStrictComponent, v as validateProps, j as validators, w as withBoundary, k as withDefaults, l as withProps, m as withWrapper } from './contracts-DDrwxvJ-.js';
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.js';
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.js';
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
  */
package/dist/ui.js CHANGED
@@ -37,11 +37,10 @@ import {
37
37
  toast,
38
38
  withScopedStyle,
39
39
  zipMask
40
- } from "./chunk-JCI5M6U6.js";
40
+ } from "./chunk-VQDZK23A.js";
41
41
  import {
42
42
  RenderProp,
43
43
  assertType,
44
- composable,
45
44
  compose,
46
45
  createGuard,
47
46
  createSlots,
@@ -54,23 +53,23 @@ import {
54
53
  withDefaults,
55
54
  withProps,
56
55
  withWrapper
57
- } from "./chunk-XYU6TZOW.js";
56
+ } from "./chunk-CNZ35WI2.js";
58
57
  import {
59
58
  createId
60
59
  } from "./chunk-YT6HQ6AM.js";
61
60
  import {
62
61
  registerDisposer
63
- } from "./chunk-PTQJDMRT.js";
64
- import "./chunk-NEKUBFPT.js";
62
+ } from "./chunk-5ZYQ6KDD.js";
63
+ import "./chunk-XHK6BDAJ.js";
65
64
  import "./chunk-CMBFNA7L.js";
66
65
  import {
67
66
  effect
68
- } from "./chunk-CHF5OHIA.js";
67
+ } from "./chunk-VQNQZCWJ.js";
69
68
  import "./chunk-EUZND3CB.js";
70
69
  import {
71
70
  signal
72
- } from "./chunk-WZSPOOER.js";
73
- import "./chunk-ZD6OAMTH.js";
71
+ } from "./chunk-NZIIMDWI.js";
72
+ import "./chunk-K4G4ZQNR.js";
74
73
  import "./chunk-5X6PP2UK.js";
75
74
 
76
75
  // src/ui/formAction.ts
@@ -420,7 +419,6 @@ export {
420
419
  bindBoolAttr,
421
420
  bindData,
422
421
  bindField,
423
- composable,
424
422
  compose,
425
423
  createDialogAria,
426
424
  createFocusManager,
package/dist/widgets.cjs CHANGED
@@ -127,12 +127,21 @@ function queueSignalNotification(signal2) {
127
127
  }
128
128
  }
129
129
  }
130
+ var MAX_DRAIN_ITERATIONS = 1e3;
130
131
  function drainNotificationQueue() {
131
132
  if (notifyDepth > 0) return;
132
133
  notifyDepth++;
133
134
  try {
134
135
  let i = 0;
135
136
  while (i < pendingQueue.length) {
137
+ if (i >= MAX_DRAIN_ITERATIONS) {
138
+ if (typeof console !== "undefined") {
139
+ console.error(
140
+ `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
141
+ );
142
+ }
143
+ break;
144
+ }
136
145
  safeInvoke(pendingQueue[i]);
137
146
  i++;
138
147
  }
@@ -299,21 +308,37 @@ function derived(getter, options) {
299
308
  cs._v = getter();
300
309
  }, markDirty);
301
310
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
311
+ let evaluating = false;
302
312
  function computedGetter() {
313
+ if (evaluating) {
314
+ throw new Error(
315
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
316
+ );
317
+ }
303
318
  if (trackingSuspended) {
304
319
  if (cs._d) {
305
- cs._d = false;
306
- cs._v = getter();
320
+ evaluating = true;
321
+ try {
322
+ cs._d = false;
323
+ cs._v = getter();
324
+ } finally {
325
+ evaluating = false;
326
+ }
307
327
  }
308
328
  return cs._v;
309
329
  }
310
330
  recordDependency(cs);
311
331
  if (cs._d) {
312
332
  const oldValue = cs._v;
313
- track(() => {
314
- cs._d = false;
315
- cs._v = getter();
316
- }, markDirty);
333
+ evaluating = true;
334
+ try {
335
+ track(() => {
336
+ cs._d = false;
337
+ cs._v = getter();
338
+ }, markDirty);
339
+ } finally {
340
+ evaluating = false;
341
+ }
317
342
  if (hook && oldValue !== cs._v) {
318
343
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
319
344
  }
package/dist/widgets.js CHANGED
@@ -8,12 +8,12 @@ import {
8
8
  select,
9
9
  tabs,
10
10
  tooltip
11
- } from "./chunk-EBGIRKQY.js";
12
- import "./chunk-NYVAC6P5.js";
13
- import "./chunk-NEKUBFPT.js";
11
+ } from "./chunk-PDZQY43A.js";
12
+ import "./chunk-GJPXRJ45.js";
13
+ import "./chunk-XHK6BDAJ.js";
14
14
  import "./chunk-EUZND3CB.js";
15
- import "./chunk-WZSPOOER.js";
16
- import "./chunk-ZD6OAMTH.js";
15
+ import "./chunk-NZIIMDWI.js";
16
+ import "./chunk-K4G4ZQNR.js";
17
17
  import "./chunk-5X6PP2UK.js";
18
18
  export {
19
19
  accordion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sibujs",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "A lightweight, function-based frontend framework that combines the best of React, Svelte, and Vue — with zero VDOM and maximum simplicity. Designed for developers who want fine-grained reactivity and full control without compilation or magic.",
5
5
  "keywords": [
6
6
  "frontend",