sibujs 3.3.2 → 3.4.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 (77) hide show
  1. package/dist/browser.cjs +21 -4
  2. package/dist/browser.js +4 -4
  3. package/dist/build.cjs +3343 -3009
  4. package/dist/build.js +12 -11
  5. package/dist/cdn.global.js +7 -7
  6. package/dist/{chunk-AEOLHSSQ.js → chunk-3DVGCW7O.js} +72 -25
  7. package/dist/{chunk-6TCOWMGY.js → chunk-3Z2NGWQI.js} +1 -1
  8. package/dist/chunk-7ZHH77QA.js +9 -0
  9. package/dist/{chunk-2ARSB7NG.js → chunk-APFRCIRI.js} +2906 -2569
  10. package/dist/{chunk-62V653X2.js → chunk-AXFWGY32.js} +79 -44
  11. package/dist/{chunk-N5TQVEKE.js → chunk-BUKYCWHX.js} +1 -1
  12. package/dist/{chunk-655B7MMR.js → chunk-CEBWDD63.js} +15 -8
  13. package/dist/{chunk-YUBEOWII.js → chunk-ELXMRSVN.js} +16 -5
  14. package/dist/{chunk-FJIRS3FM.js → chunk-F7HW2NI5.js} +1 -3
  15. package/dist/{chunk-P7C7SEJV.js → chunk-FKQG7FTY.js} +5 -5
  16. package/dist/{chunk-RLTFJYDN.js → chunk-H5FI5WDP.js} +1 -1
  17. package/dist/{chunk-G6N3LMO2.js → chunk-ITWCTZYH.js} +1 -1
  18. package/dist/{chunk-YUR5SX7F.js → chunk-JCO7AHNU.js} +70 -59
  19. package/dist/{chunk-ZEUP4TUD.js → chunk-JNGGM2XE.js} +6 -6
  20. package/dist/{chunk-QCFBIVIQ.js → chunk-K35BAR4P.js} +1 -1
  21. package/dist/{chunk-JCQG2I2G.js → chunk-KZBEVFXZ.js} +1 -1
  22. package/dist/{chunk-FJO2ZL4Q.js → chunk-OAVXAFAY.js} +73 -16
  23. package/dist/{chunk-A7CZH3GN.js → chunk-ODXVQ3CS.js} +41 -20
  24. package/dist/{chunk-4NJEAPLI.js → chunk-OOUQUCNN.js} +1 -1
  25. package/dist/{chunk-5VH3GIDX.js → chunk-RT3IYFN2.js} +1 -1
  26. package/dist/{chunk-Q46YIQYW.js → chunk-S3V6ZXPI.js} +22 -5
  27. package/dist/{chunk-IQJ36UTJ.js → chunk-WSGLJYFI.js} +31 -24
  28. package/dist/{chunk-3JHCYHWN.js → chunk-WWV3SJ3L.js} +19 -13
  29. package/dist/data.cjs +16 -2
  30. package/dist/data.js +7 -6
  31. package/dist/devtools.cjs +71 -21
  32. package/dist/devtools.d.cts +12 -5
  33. package/dist/devtools.d.ts +12 -5
  34. package/dist/devtools.js +6 -5
  35. package/dist/ecosystem.cjs +11 -2
  36. package/dist/ecosystem.d.cts +1 -1
  37. package/dist/ecosystem.d.ts +1 -1
  38. package/dist/ecosystem.js +10 -9
  39. package/dist/extras.cjs +454 -250
  40. package/dist/extras.d.cts +2 -2
  41. package/dist/extras.d.ts +2 -2
  42. package/dist/extras.js +26 -22
  43. package/dist/index.cjs +3385 -3045
  44. package/dist/index.d.cts +1179 -1057
  45. package/dist/index.d.ts +1179 -1057
  46. package/dist/index.js +32 -19
  47. package/dist/motion.cjs +223 -204
  48. package/dist/motion.d.cts +8 -1
  49. package/dist/motion.d.ts +8 -1
  50. package/dist/motion.js +6 -3
  51. package/dist/patterns.cjs +13 -6
  52. package/dist/patterns.d.cts +4 -1
  53. package/dist/patterns.d.ts +4 -1
  54. package/dist/patterns.js +5 -5
  55. package/dist/performance.cjs +70 -56
  56. package/dist/performance.js +6 -5
  57. package/dist/plugins.cjs +98 -42
  58. package/dist/plugins.d.cts +2 -1
  59. package/dist/plugins.d.ts +2 -1
  60. package/dist/plugins.js +89 -36
  61. package/dist/ssr.cjs +112 -91
  62. package/dist/ssr.d.cts +8 -1
  63. package/dist/ssr.d.ts +8 -1
  64. package/dist/ssr.js +9 -8
  65. package/dist/{tagFactory-S17H2qxu.d.ts → tagFactory-Bzupt4Pj.d.cts} +1 -1
  66. package/dist/{tagFactory-S17H2qxu.d.cts → tagFactory-Bzupt4Pj.d.ts} +1 -1
  67. package/dist/testing.cjs +18 -4
  68. package/dist/testing.js +18 -7
  69. package/dist/ui.cjs +322 -281
  70. package/dist/ui.d.cts +0 -20
  71. package/dist/ui.d.ts +0 -20
  72. package/dist/ui.js +27 -21
  73. package/dist/widgets.cjs +75 -13
  74. package/dist/widgets.d.cts +6 -1
  75. package/dist/widgets.d.ts +6 -1
  76. package/dist/widgets.js +7 -6
  77. package/package.json +4 -1
package/dist/ui.d.cts CHANGED
@@ -244,26 +244,6 @@ declare function hover(target: HTMLElement): {
244
244
  dispose: () => void;
245
245
  };
246
246
 
247
- /**
248
- * scrollLock stacks body scroll locks — useful when a modal / drawer / sheet
249
- * stack is open and background scroll must be suppressed.
250
- *
251
- * Each `lock()` call increments an internal counter and applies
252
- * `overflow: hidden` + preserves the scrollbar-width padding to prevent
253
- * layout shift. Calling `unlock()` decrements; when the counter hits zero
254
- * the previous body style is restored.
255
- *
256
- * Safe to call from multiple concurrent overlays — the last one to unlock
257
- * releases the lock.
258
- *
259
- * @example
260
- * ```ts
261
- * const lock = scrollLock();
262
- * lock.lock();
263
- * // ... modal open
264
- * lock.unlock();
265
- * ```
266
- */
267
247
  interface ScrollLockHandle {
268
248
  /** Activate a lock. Idempotent per-handle if called twice. */
269
249
  lock: () => void;
package/dist/ui.d.ts CHANGED
@@ -244,26 +244,6 @@ declare function hover(target: HTMLElement): {
244
244
  dispose: () => void;
245
245
  };
246
246
 
247
- /**
248
- * scrollLock stacks body scroll locks — useful when a modal / drawer / sheet
249
- * stack is open and background scroll must be suppressed.
250
- *
251
- * Each `lock()` call increments an internal counter and applies
252
- * `overflow: hidden` + preserves the scrollbar-width padding to prevent
253
- * layout shift. Calling `unlock()` decrements; when the counter hits zero
254
- * the previous body style is restored.
255
- *
256
- * Safe to call from multiple concurrent overlays — the last one to unlock
257
- * releases the lock.
258
- *
259
- * @example
260
- * ```ts
261
- * const lock = scrollLock();
262
- * lock.lock();
263
- * // ... modal open
264
- * lock.unlock();
265
- * ```
266
- */
267
247
  interface ScrollLockHandle {
268
248
  /** Activate a lock. Idempotent per-handle if called twice. */
269
249
  lock: () => void;
package/dist/ui.js CHANGED
@@ -38,7 +38,7 @@ import {
38
38
  toast,
39
39
  withScopedStyle,
40
40
  zipMask
41
- } from "./chunk-62V653X2.js";
41
+ } from "./chunk-AXFWGY32.js";
42
42
  import {
43
43
  RenderProp,
44
44
  assertType,
@@ -58,20 +58,23 @@ import {
58
58
  import {
59
59
  createId
60
60
  } from "./chunk-CCSJMTRN.js";
61
- import "./chunk-QCFBIVIQ.js";
62
- import "./chunk-G6N3LMO2.js";
61
+ import "./chunk-K35BAR4P.js";
62
+ import "./chunk-ITWCTZYH.js";
63
63
  import {
64
- registerDisposer
65
- } from "./chunk-5VGSK6D2.js";
64
+ globalSingleton
65
+ } from "./chunk-7ZHH77QA.js";
66
66
  import "./chunk-L3GAGWCC.js";
67
67
  import {
68
68
  effect
69
- } from "./chunk-N5TQVEKE.js";
69
+ } from "./chunk-BUKYCWHX.js";
70
70
  import "./chunk-S3NFJO6L.js";
71
+ import {
72
+ registerDisposer
73
+ } from "./chunk-5VGSK6D2.js";
71
74
  import {
72
75
  signal
73
- } from "./chunk-JCQG2I2G.js";
74
- import "./chunk-4NJEAPLI.js";
76
+ } from "./chunk-KZBEVFXZ.js";
77
+ import "./chunk-OOUQUCNN.js";
75
78
  import "./chunk-COY6PUD2.js";
76
79
 
77
80
  // src/ui/formAction.ts
@@ -375,24 +378,27 @@ function hover(target) {
375
378
  target.removeEventListener("pointerenter", onEnter);
376
379
  target.removeEventListener("pointerleave", onLeave);
377
380
  }
381
+ registerDisposer(target, dispose);
378
382
  return { hovered, dispose };
379
383
  }
380
384
 
381
385
  // src/ui/scrollLock.ts
382
- var lockCount = 0;
383
- var savedOverflow = null;
384
- var savedPaddingRight = null;
386
+ var _lock = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.scrollLock.v1"), () => ({
387
+ count: 0,
388
+ savedOverflow: null,
389
+ savedPaddingRight: null
390
+ }));
385
391
  function scrollLock() {
386
392
  let owned = false;
387
393
  function lock() {
388
394
  if (owned) return;
389
395
  owned = true;
390
- lockCount++;
391
- if (lockCount !== 1 || typeof document === "undefined") return;
396
+ _lock.count++;
397
+ if (_lock.count !== 1 || typeof document === "undefined") return;
392
398
  const body = document.body;
393
399
  const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
394
- savedOverflow = body.style.overflow;
395
- savedPaddingRight = body.style.paddingRight;
400
+ _lock.savedOverflow = body.style.overflow;
401
+ _lock.savedPaddingRight = body.style.paddingRight;
396
402
  body.style.overflow = "hidden";
397
403
  if (scrollBarWidth > 0) {
398
404
  body.style.paddingRight = `${scrollBarWidth}px`;
@@ -401,13 +407,13 @@ function scrollLock() {
401
407
  function unlock() {
402
408
  if (!owned) return;
403
409
  owned = false;
404
- lockCount = Math.max(0, lockCount - 1);
405
- if (lockCount !== 0 || typeof document === "undefined") return;
410
+ _lock.count = Math.max(0, _lock.count - 1);
411
+ if (_lock.count !== 0 || typeof document === "undefined") return;
406
412
  const body = document.body;
407
- body.style.overflow = savedOverflow ?? "";
408
- body.style.paddingRight = savedPaddingRight ?? "";
409
- savedOverflow = null;
410
- savedPaddingRight = null;
413
+ body.style.overflow = _lock.savedOverflow ?? "";
414
+ body.style.paddingRight = _lock.savedPaddingRight ?? "";
415
+ _lock.savedOverflow = null;
416
+ _lock.savedPaddingRight = null;
411
417
  }
412
418
  return { lock, unlock };
413
419
  }
package/dist/widgets.cjs CHANGED
@@ -32,6 +32,14 @@ __export(widgets_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(widgets_exports);
34
34
 
35
+ // src/core/rendering/createId.ts
36
+ var COUNTER_KEY = /* @__PURE__ */ Symbol.for("sibujs.createId.v1");
37
+ var _counter = globalThis[COUNTER_KEY] ?? (globalThis[COUNTER_KEY] = { n: 0 });
38
+ function createId(prefix = "sibu") {
39
+ _counter.n++;
40
+ return `${prefix}-${_counter.n}`;
41
+ }
42
+
35
43
  // src/core/dev.ts
36
44
  function isDev() {
37
45
  return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
@@ -470,7 +478,7 @@ function forEachSubscriber(signal2, visit) {
470
478
 
471
479
  // src/reactivity/track.ts
472
480
  var _isDev3 = isDev();
473
- var _runtimeVersion = true ? "3.3.2" : "dev";
481
+ var _runtimeVersion = true ? "3.4.0" : "dev";
474
482
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
475
483
  function resolveReactiveApi() {
476
484
  const g = globalThis;
@@ -912,7 +920,6 @@ function watch(getter, callback) {
912
920
  }
913
921
 
914
922
  // src/widgets/Combobox.ts
915
- var comboboxIdCounter = 0;
916
923
  var boundComboboxes = /* @__PURE__ */ new WeakMap();
917
924
  function combobox(options) {
918
925
  const { items, filterFn, itemToString } = options;
@@ -973,7 +980,13 @@ function combobox(options) {
973
980
  function bind(els) {
974
981
  const existing = boundComboboxes.get(els.input);
975
982
  if (existing) return existing;
976
- const listboxId = `sibu-combobox-listbox-${++comboboxIdCounter}`;
983
+ const prevListboxId = els.listbox.id;
984
+ const prevListboxRole = els.listbox.getAttribute("role");
985
+ const prevListboxHidden = els.listbox.hidden;
986
+ const prevInputRole = els.input.getAttribute("role");
987
+ const prevInputAutocomplete = els.input.getAttribute("aria-autocomplete");
988
+ const prevInputControls = els.input.getAttribute("aria-controls");
989
+ const listboxId = createId("sibu-combobox-listbox");
977
990
  els.listbox.id = listboxId;
978
991
  els.listbox.setAttribute("role", "listbox");
979
992
  els.input.setAttribute("role", "combobox");
@@ -1037,10 +1050,14 @@ function combobox(options) {
1037
1050
  if (document.activeElement !== els.input) close();
1038
1051
  }, 100);
1039
1052
  };
1053
+ const onListboxPointerDown = (e) => {
1054
+ e.preventDefault();
1055
+ };
1040
1056
  els.input.addEventListener("input", onInput);
1041
1057
  els.input.addEventListener("keydown", onKey);
1042
1058
  els.input.addEventListener("focus", onFocus);
1043
1059
  els.input.addEventListener("blur", onBlur);
1060
+ els.listbox.addEventListener("mousedown", onListboxPointerDown);
1044
1061
  const teardown = () => {
1045
1062
  boundComboboxes.delete(els.input);
1046
1063
  fxTeardown();
@@ -1048,10 +1065,24 @@ function combobox(options) {
1048
1065
  els.input.removeEventListener("keydown", onKey);
1049
1066
  els.input.removeEventListener("focus", onFocus);
1050
1067
  els.input.removeEventListener("blur", onBlur);
1068
+ els.listbox.removeEventListener("mousedown", onListboxPointerDown);
1051
1069
  if (blurTimer !== null) {
1052
1070
  clearTimeout(blurTimer);
1053
1071
  blurTimer = null;
1054
1072
  }
1073
+ if (prevListboxId === "") els.listbox.removeAttribute("id");
1074
+ else els.listbox.id = prevListboxId;
1075
+ if (prevListboxRole === null) els.listbox.removeAttribute("role");
1076
+ else els.listbox.setAttribute("role", prevListboxRole);
1077
+ els.listbox.hidden = prevListboxHidden;
1078
+ if (prevInputRole === null) els.input.removeAttribute("role");
1079
+ else els.input.setAttribute("role", prevInputRole);
1080
+ if (prevInputAutocomplete === null) els.input.removeAttribute("aria-autocomplete");
1081
+ else els.input.setAttribute("aria-autocomplete", prevInputAutocomplete);
1082
+ if (prevInputControls === null) els.input.removeAttribute("aria-controls");
1083
+ else els.input.setAttribute("aria-controls", prevInputControls);
1084
+ els.input.removeAttribute("aria-expanded");
1085
+ els.input.removeAttribute("aria-activedescendant");
1055
1086
  };
1056
1087
  boundComboboxes.set(els.input, teardown);
1057
1088
  return teardown;
@@ -1387,7 +1418,6 @@ function accordion(options) {
1387
1418
  }
1388
1419
 
1389
1420
  // src/widgets/Popover.ts
1390
- var popoverIdCounter = 0;
1391
1421
  var boundPopovers = /* @__PURE__ */ new WeakMap();
1392
1422
  function popover() {
1393
1423
  const [isOpen, setIsOpen] = signal(false);
@@ -1403,7 +1433,7 @@ function popover() {
1403
1433
  function bind(els) {
1404
1434
  const existing = boundPopovers.get(els.trigger);
1405
1435
  if (existing) return existing;
1406
- const id = `sibu-popover-${++popoverIdCounter}`;
1436
+ const id = createId("sibu-popover");
1407
1437
  const prevPopoverRole = els.popover.getAttribute("role");
1408
1438
  const prevPopoverId = els.popover.id;
1409
1439
  const prevLabelledBy = els.popover.getAttribute("aria-labelledby");
@@ -1477,7 +1507,6 @@ function popover() {
1477
1507
  }
1478
1508
 
1479
1509
  // src/widgets/Select.ts
1480
- var selectIdCounter = 0;
1481
1510
  var boundSelects = /* @__PURE__ */ new WeakMap();
1482
1511
  function select(options) {
1483
1512
  const { items, multiple = false, itemToString, isDisabled } = options;
@@ -1558,7 +1587,11 @@ function select(options) {
1558
1587
  function bind(els) {
1559
1588
  const existing = boundSelects.get(els.listbox);
1560
1589
  if (existing) return existing;
1561
- const listboxId = `sibu-select-${++selectIdCounter}`;
1590
+ const prevId = els.listbox.id;
1591
+ const prevRole = els.listbox.getAttribute("role");
1592
+ const prevMultiselectable = els.listbox.getAttribute("aria-multiselectable");
1593
+ const prevTabIndex = els.listbox.getAttribute("tabindex");
1594
+ const listboxId = createId("sibu-select");
1562
1595
  els.listbox.id = listboxId;
1563
1596
  els.listbox.setAttribute("role", "listbox");
1564
1597
  els.listbox.setAttribute("aria-multiselectable", multiple ? "true" : "false");
@@ -1618,6 +1651,15 @@ function select(options) {
1618
1651
  fxTeardown();
1619
1652
  els.listbox.removeEventListener("keydown", onKey);
1620
1653
  if (typeTimer !== null) clearTimeout(typeTimer);
1654
+ if (prevId === "") els.listbox.removeAttribute("id");
1655
+ else els.listbox.id = prevId;
1656
+ if (prevRole === null) els.listbox.removeAttribute("role");
1657
+ else els.listbox.setAttribute("role", prevRole);
1658
+ if (prevMultiselectable === null) els.listbox.removeAttribute("aria-multiselectable");
1659
+ else els.listbox.setAttribute("aria-multiselectable", prevMultiselectable);
1660
+ if (prevTabIndex === null) els.listbox.removeAttribute("tabindex");
1661
+ else els.listbox.setAttribute("tabindex", prevTabIndex);
1662
+ els.listbox.removeAttribute("aria-activedescendant");
1621
1663
  };
1622
1664
  boundSelects.set(els.listbox, teardown);
1623
1665
  return teardown;
@@ -1642,7 +1684,6 @@ function select(options) {
1642
1684
  }
1643
1685
 
1644
1686
  // src/widgets/Tooltip.ts
1645
- var tooltipIdCounter = 0;
1646
1687
  var boundTriggers = /* @__PURE__ */ new WeakMap();
1647
1688
  function tooltip(options) {
1648
1689
  const delay = options?.delay ?? 0;
@@ -1687,7 +1728,7 @@ function tooltip(options) {
1687
1728
  function bind(els) {
1688
1729
  const existing = boundTriggers.get(els.trigger);
1689
1730
  if (existing) return existing;
1690
- const id = `sibu-tooltip-${++tooltipIdCounter}`;
1731
+ const id = createId("sibu-tooltip");
1691
1732
  els.tooltip.setAttribute("role", "tooltip");
1692
1733
  els.tooltip.id = id;
1693
1734
  const prevDescribedBy = els.trigger.getAttribute("aria-describedby");
@@ -1747,7 +1788,6 @@ function tooltip(options) {
1747
1788
  }
1748
1789
 
1749
1790
  // src/widgets/FileUpload.ts
1750
- var fileUploadIdCounter = 0;
1751
1791
  var boundFileUploads = /* @__PURE__ */ new WeakMap();
1752
1792
  function fileUpload(options) {
1753
1793
  const accept = options?.accept;
@@ -1819,7 +1859,7 @@ function fileUpload(options) {
1819
1859
  function bind(els) {
1820
1860
  const existing = boundFileUploads.get(els.input);
1821
1861
  if (existing) return existing;
1822
- const id = `sibu-fileupload-${++fileUploadIdCounter}`;
1862
+ const id = createId("sibu-fileupload");
1823
1863
  const restore = [];
1824
1864
  if (accept) els.input.accept = accept;
1825
1865
  els.input.multiple = multiple;
@@ -1948,7 +1988,7 @@ function stripHtml(html) {
1948
1988
  }
1949
1989
 
1950
1990
  // src/widgets/contentEditable.ts
1951
- function contentEditable() {
1991
+ function contentEditable(element) {
1952
1992
  const [content, setContentInternal] = signal("");
1953
1993
  const [isFocused, setFocused] = signal(false);
1954
1994
  function setContent(input) {
@@ -1972,6 +2012,7 @@ function contentEditable() {
1972
2012
  const selection = window.getSelection();
1973
2013
  if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return;
1974
2014
  const range = selection.getRangeAt(0);
2015
+ if (element && !element.contains(range.commonAncestorContainer)) return;
1975
2016
  const ancestor = range.commonAncestorContainer;
1976
2017
  const existingWrap = findAncestorByTag(
1977
2018
  ancestor instanceof HTMLElement ? ancestor : ancestor.parentElement,
@@ -2151,12 +2192,18 @@ function datePicker(options) {
2151
2192
  function bind(els) {
2152
2193
  const existing = boundDatePickers.get(els.grid);
2153
2194
  if (existing) return existing;
2195
+ const prevGridRole = els.grid.getAttribute("role");
2196
+ const prevGridTabIndex = els.grid.getAttribute("tabindex");
2197
+ const prevGridLabel = els.grid.getAttribute("aria-label");
2154
2198
  els.grid.setAttribute("role", "grid");
2155
2199
  if (els.grid.tabIndex < 0) els.grid.tabIndex = 0;
2200
+ let pendingFocus = false;
2156
2201
  const fxTeardown = effect(() => {
2157
2202
  const sel = selectedDate();
2158
2203
  const view = viewDate();
2159
2204
  const days = daysInMonth();
2205
+ els.grid.setAttribute("aria-label", view.toLocaleDateString(void 0, { month: "long", year: "numeric" }));
2206
+ let viewCell = null;
2160
2207
  for (const d of days) {
2161
2208
  const cell = els.cell(d.date);
2162
2209
  if (!cell) continue;
@@ -2164,7 +2211,13 @@ function datePicker(options) {
2164
2211
  cell.setAttribute("aria-selected", sel && isSameCalendarDay(sel, d.date) ? "true" : "false");
2165
2212
  if (d.isDisabled) cell.setAttribute("aria-disabled", "true");
2166
2213
  else cell.removeAttribute("aria-disabled");
2167
- cell.tabIndex = isSameCalendarDay(view, d.date) ? 0 : -1;
2214
+ const isView = isSameCalendarDay(view, d.date);
2215
+ cell.tabIndex = isView ? 0 : -1;
2216
+ if (isView) viewCell = cell;
2217
+ }
2218
+ if (pendingFocus && viewCell && typeof viewCell.focus === "function") {
2219
+ pendingFocus = false;
2220
+ viewCell.focus();
2168
2221
  }
2169
2222
  });
2170
2223
  function isSameCalendarDay(a, b) {
@@ -2174,6 +2227,9 @@ function datePicker(options) {
2174
2227
  setViewDate((prev) => new Date(prev.getFullYear(), prev.getMonth(), prev.getDate() + delta));
2175
2228
  }
2176
2229
  const onKey = (e) => {
2230
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Home" || e.key === "End" || e.key === "PageUp" || e.key === "PageDown") {
2231
+ pendingFocus = true;
2232
+ }
2177
2233
  switch (e.key) {
2178
2234
  case "ArrowLeft":
2179
2235
  e.preventDefault();
@@ -2222,6 +2278,12 @@ function datePicker(options) {
2222
2278
  boundDatePickers.delete(els.grid);
2223
2279
  fxTeardown();
2224
2280
  els.grid.removeEventListener("keydown", onKey);
2281
+ if (prevGridRole === null) els.grid.removeAttribute("role");
2282
+ else els.grid.setAttribute("role", prevGridRole);
2283
+ if (prevGridTabIndex === null) els.grid.removeAttribute("tabindex");
2284
+ else els.grid.setAttribute("tabindex", prevGridTabIndex);
2285
+ if (prevGridLabel === null) els.grid.removeAttribute("aria-label");
2286
+ else els.grid.setAttribute("aria-label", prevGridLabel);
2225
2287
  };
2226
2288
  boundDatePickers.set(els.grid, teardown);
2227
2289
  return teardown;
@@ -216,8 +216,13 @@ interface SetContentOptions {
216
216
  * Uses the modern Selection/Range API instead of the deprecated
217
217
  * document.execCommand. Formatting is applied by wrapping the current
218
218
  * selection in the appropriate inline element.
219
+ *
220
+ * @param element Optional editor element to scope formatting to. When provided,
221
+ * `bold()`/`italic()`/`underline()` ignore any selection that lives outside it
222
+ * — otherwise a selection elsewhere on the page could be mutated, since the
223
+ * Selection API is global.
219
224
  */
220
- declare function contentEditable(): {
225
+ declare function contentEditable(element?: HTMLElement): {
221
226
  content: () => string;
222
227
  /**
223
228
  * Update the reactive content value.
package/dist/widgets.d.ts CHANGED
@@ -216,8 +216,13 @@ interface SetContentOptions {
216
216
  * Uses the modern Selection/Range API instead of the deprecated
217
217
  * document.execCommand. Formatting is applied by wrapping the current
218
218
  * selection in the appropriate inline element.
219
+ *
220
+ * @param element Optional editor element to scope formatting to. When provided,
221
+ * `bold()`/`italic()`/`underline()` ignore any selection that lives outside it
222
+ * — otherwise a selection elsewhere on the page could be mutated, since the
223
+ * Selection API is global.
219
224
  */
220
- declare function contentEditable(): {
225
+ declare function contentEditable(element?: HTMLElement): {
221
226
  content: () => string;
222
227
  /**
223
228
  * Update the reactive content value.
package/dist/widgets.js CHANGED
@@ -8,14 +8,15 @@ import {
8
8
  select,
9
9
  tabs,
10
10
  tooltip
11
- } from "./chunk-FJO2ZL4Q.js";
12
- import "./chunk-RLTFJYDN.js";
13
- import "./chunk-G6N3LMO2.js";
11
+ } from "./chunk-OAVXAFAY.js";
12
+ import "./chunk-H5FI5WDP.js";
13
+ import "./chunk-CCSJMTRN.js";
14
+ import "./chunk-ITWCTZYH.js";
14
15
  import "./chunk-L3GAGWCC.js";
15
- import "./chunk-N5TQVEKE.js";
16
+ import "./chunk-BUKYCWHX.js";
16
17
  import "./chunk-S3NFJO6L.js";
17
- import "./chunk-JCQG2I2G.js";
18
- import "./chunk-4NJEAPLI.js";
18
+ import "./chunk-KZBEVFXZ.js";
19
+ import "./chunk-OOUQUCNN.js";
19
20
  import "./chunk-COY6PUD2.js";
20
21
  export {
21
22
  accordion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sibujs",
3
- "version": "3.3.2",
3
+ "version": "3.4.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",
@@ -33,6 +33,8 @@
33
33
  "format": "biome format --write src/ tests/",
34
34
  "test": "vitest",
35
35
  "test:ui": "vitest --ui",
36
+ "test:browser": "npm run build && playwright test",
37
+ "test:browser:install": "playwright install chromium firefox webkit",
36
38
  "build": "tsup index.ts data.ts browser.ts patterns.ts motion.ts ui.ts widgets.ts ssr.ts devtools.ts performance.ts ecosystem.ts plugins.ts build.ts testing.ts extras.ts --dts --format esm,cjs --out-dir dist --clean && tsup cdn.ts --format iife --globalName Sibu --out-dir dist --no-dts --minify",
37
39
  "bench": "node bench.mjs",
38
40
  "bench:save": "node bench.mjs --save",
@@ -135,6 +137,7 @@
135
137
  },
136
138
  "devDependencies": {
137
139
  "@biomejs/biome": "2.4.7",
140
+ "@playwright/test": "^1.61.1",
138
141
  "@types/node": "^25.5.0",
139
142
  "@vitest/coverage-v8": "^3.1.3",
140
143
  "@vitest/ui": "^3.1.3",