sibujs 3.3.1 → 3.3.3

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 (78) hide show
  1. package/dist/browser.cjs +41 -18
  2. package/dist/browser.js +5 -5
  3. package/dist/build.cjs +57 -22
  4. package/dist/build.js +14 -14
  5. package/dist/cdn.global.js +7 -7
  6. package/dist/{chunk-MHBCEJQO.js → chunk-23VT3KZB.js} +2 -2
  7. package/dist/{chunk-WL7BIR6O.js → chunk-25LY5SRH.js} +31 -24
  8. package/dist/{chunk-37BUKSLH.js → chunk-5K6RVA2S.js} +1 -1
  9. package/dist/{chunk-AMIKDMLP.js → chunk-5Y34FWED.js} +17 -6
  10. package/dist/chunk-7ZHH77QA.js +9 -0
  11. package/dist/chunk-CCSJMTRN.js +15 -0
  12. package/dist/{chunk-R3QEDXFS.js → chunk-EX77FXTT.js} +1 -1
  13. package/dist/{chunk-WW6DAGGR.js → chunk-F3HO2MP7.js} +1 -1
  14. package/dist/{chunk-ZUVLC7TM.js → chunk-F7HW2NI5.js} +2 -4
  15. package/dist/{chunk-ULLTNDRA.js → chunk-IEYFHN5V.js} +72 -25
  16. package/dist/{chunk-Z37APKBV.js → chunk-IQN5W7GE.js} +73 -16
  17. package/dist/{chunk-WZG2SZOT.js → chunk-IWFE4AHO.js} +1 -1
  18. package/dist/{chunk-IKLYI3RF.js → chunk-MYXEBADX.js} +53 -23
  19. package/dist/{chunk-6LTFHJQG.js → chunk-QCQQ2N5H.js} +41 -20
  20. package/dist/{chunk-VOVVTOEA.js → chunk-QTOOBABV.js} +22 -5
  21. package/dist/{chunk-LU2MQXQQ.js → chunk-RZKGMHH4.js} +6 -6
  22. package/dist/{chunk-GOJMFRBL.js → chunk-S3NFJO6L.js} +20 -14
  23. package/dist/{chunk-NHKQKKZU.js → chunk-SBPUXWCE.js} +1 -1
  24. package/dist/{chunk-SLMFA3ZZ.js → chunk-TBDTU7UO.js} +2 -2
  25. package/dist/{chunk-4UUMSLSL.js → chunk-TVCCXPU2.js} +5 -5
  26. package/dist/{chunk-TEFZT5PJ.js → chunk-UOMYIAG6.js} +1 -1
  27. package/dist/{chunk-XH2RTYEQ.js → chunk-WFUUT6TG.js} +15 -8
  28. package/dist/{chunk-M5KBNOSJ.js → chunk-WTFMV2RU.js} +70 -59
  29. package/dist/{chunk-3JHCYHWN.js → chunk-WWV3SJ3L.js} +19 -13
  30. package/dist/{chunk-RYMOSG5B.js → chunk-Y35NQNLG.js} +90 -60
  31. package/dist/{chunk-F7FXQ3QS.js → chunk-ZXCZWMZT.js} +1 -1
  32. package/dist/data.cjs +36 -16
  33. package/dist/data.js +8 -7
  34. package/dist/devtools.cjs +91 -35
  35. package/dist/devtools.d.cts +12 -5
  36. package/dist/devtools.d.ts +12 -5
  37. package/dist/devtools.js +7 -6
  38. package/dist/ecosystem.cjs +31 -16
  39. package/dist/ecosystem.js +11 -10
  40. package/dist/extras.cjs +485 -280
  41. package/dist/extras.d.cts +1 -1
  42. package/dist/extras.d.ts +1 -1
  43. package/dist/extras.js +28 -24
  44. package/dist/index.cjs +59 -22
  45. package/dist/index.d.cts +24 -8
  46. package/dist/index.d.ts +24 -8
  47. package/dist/index.js +25 -21
  48. package/dist/motion.cjs +223 -204
  49. package/dist/motion.d.cts +8 -1
  50. package/dist/motion.d.ts +8 -1
  51. package/dist/motion.js +6 -3
  52. package/dist/patterns.cjs +33 -20
  53. package/dist/patterns.d.cts +4 -1
  54. package/dist/patterns.d.ts +4 -1
  55. package/dist/patterns.js +6 -6
  56. package/dist/performance.cjs +90 -70
  57. package/dist/performance.js +8 -7
  58. package/dist/plugins.cjs +207 -144
  59. package/dist/plugins.d.cts +2 -1
  60. package/dist/plugins.d.ts +2 -1
  61. package/dist/plugins.js +178 -124
  62. package/dist/{ssr-6D67RAVB.js → ssr-2PPULEK2.js} +2 -2
  63. package/dist/ssr.cjs +132 -105
  64. package/dist/ssr.d.cts +8 -1
  65. package/dist/ssr.d.ts +8 -1
  66. package/dist/ssr.js +11 -10
  67. package/dist/testing.cjs +38 -18
  68. package/dist/testing.js +19 -8
  69. package/dist/ui.cjs +337 -294
  70. package/dist/ui.d.cts +0 -20
  71. package/dist/ui.d.ts +0 -20
  72. package/dist/ui.js +30 -24
  73. package/dist/widgets.cjs +95 -27
  74. package/dist/widgets.d.cts +6 -1
  75. package/dist/widgets.d.ts +6 -1
  76. package/dist/widgets.js +8 -7
  77. package/package.json +1 -1
  78. package/dist/chunk-YT6HQ6AM.js +0 -14
@@ -1,20 +1,23 @@
1
1
  import {
2
- dispose
3
- } from "./chunk-5VGSK6D2.js";
2
+ globalSingleton
3
+ } from "./chunk-7ZHH77QA.js";
4
4
  import {
5
5
  stripHtml
6
6
  } from "./chunk-L3GAGWCC.js";
7
7
  import {
8
8
  effect
9
- } from "./chunk-MHBCEJQO.js";
9
+ } from "./chunk-23VT3KZB.js";
10
+ import {
11
+ dispose
12
+ } from "./chunk-5VGSK6D2.js";
10
13
  import {
11
14
  signal
12
- } from "./chunk-WZG2SZOT.js";
15
+ } from "./chunk-IWFE4AHO.js";
13
16
  import {
14
17
  forEachSubscriber,
15
18
  getSubscriberCount,
16
19
  getSubscriberDeps
17
- } from "./chunk-TEFZT5PJ.js";
20
+ } from "./chunk-UOMYIAG6.js";
18
21
  import {
19
22
  isDev
20
23
  } from "./chunk-COY6PUD2.js";
@@ -147,10 +150,50 @@ function createGlobalHook() {
147
150
  listeners.set(event, set);
148
151
  }
149
152
  set.add(fn);
153
+ return () => {
154
+ set?.delete(fn);
155
+ };
150
156
  },
151
157
  off(event, fn) {
152
158
  listeners.get(event)?.delete(fn);
153
159
  },
160
+ getSignalNodes() {
161
+ const out = [];
162
+ for (const [, node] of nodes) {
163
+ let value = "";
164
+ try {
165
+ const ref = node.ref;
166
+ let raw;
167
+ if (node.type === "effect") {
168
+ raw = void 0;
169
+ } else if (node.type === "computed" && ref) {
170
+ raw = ref._v;
171
+ } else if (ref && "value" in ref) {
172
+ raw = ref.value;
173
+ }
174
+ if (raw === void 0) value = "undefined";
175
+ else if (raw === null) value = "null";
176
+ else if (typeof raw === "object") value = JSON.stringify(raw);
177
+ else value = String(raw);
178
+ } catch {
179
+ value = "?";
180
+ }
181
+ if (value.length > 200) value = `${value.slice(0, 200)}...`;
182
+ out.push({
183
+ id: String(node.id),
184
+ name: node.name || null,
185
+ kind: node.type === "computed" ? "derived" : node.type,
186
+ value,
187
+ // Edge identities aren't tracked by this registry (only a subscriber
188
+ // count, via node.ref.__sc) — left empty until the core exposes the
189
+ // dependency graph. The node inventory itself is real.
190
+ subscribers: [],
191
+ dependencies: [],
192
+ evalCount: 0
193
+ });
194
+ }
195
+ return out;
196
+ },
154
197
  emit(event, payload) {
155
198
  events.push({ event, payload, ts: Date.now() });
156
199
  if (events.length > 2e3) events.splice(0, events.length - 2e3);
@@ -176,34 +219,38 @@ function getOrCreateHook() {
176
219
  }
177
220
  return g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
178
221
  }
179
- var activeDevTools = null;
180
- var nextNodeId = 0;
181
- var inferNameArmed = false;
222
+ var _dt = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.devtools.state.v1"), () => ({
223
+ active: null,
224
+ nextNodeId: 0,
225
+ // Gate for inferName(): only `true` while devtools are initialized AND in dev
226
+ // mode — otherwise skip the Error-stack walk so prod hot paths don't allocate.
227
+ inferNameArmed: false
228
+ }));
182
229
  function getActiveDevTools() {
183
- return activeDevTools;
230
+ return _dt.active;
184
231
  }
185
232
  function initDevTools(config) {
186
233
  const maxEvents = config?.maxEvents ?? 1e3;
187
234
  const enabled = config?.enabled ?? isDev();
188
235
  const expose = config?.expose ?? isDev();
189
236
  if (!enabled) return createNoopApi();
190
- inferNameArmed = true;
237
+ _dt.inferNameArmed = true;
191
238
  const hook = getOrCreateHook();
192
- nextNodeId = 0;
239
+ _dt.nextNodeId = 0;
193
240
  const eventLog = [];
194
241
  hook.on("signal:create", (payload) => {
195
242
  const p = payload;
196
- nextNodeId++;
243
+ _dt.nextNodeId++;
197
244
  const name = inferName();
198
245
  const node = {
199
- id: nextNodeId,
246
+ id: _dt.nextNodeId,
200
247
  type: "signal",
201
248
  name,
202
249
  ref: p.signal,
203
250
  getter: p.getter,
204
251
  createdAt: Date.now()
205
252
  };
206
- hook.nodes.set(nextNodeId, node);
253
+ hook.nodes.set(_dt.nextNodeId, node);
207
254
  emit();
208
255
  });
209
256
  hook.on("signal:update", (payload) => {
@@ -223,17 +270,17 @@ function initDevTools(config) {
223
270
  });
224
271
  hook.on("computed:create", (payload) => {
225
272
  const p = payload;
226
- nextNodeId++;
273
+ _dt.nextNodeId++;
227
274
  const name = inferName();
228
275
  const node = {
229
- id: nextNodeId,
276
+ id: _dt.nextNodeId,
230
277
  type: "computed",
231
278
  name,
232
279
  ref: p.signal,
233
280
  getter: p.getter,
234
281
  createdAt: Date.now()
235
282
  };
236
- hook.nodes.set(nextNodeId, node);
283
+ hook.nodes.set(_dt.nextNodeId, node);
237
284
  emit();
238
285
  });
239
286
  hook.on("computed:update", (payload) => {
@@ -250,11 +297,11 @@ function initDevTools(config) {
250
297
  emit();
251
298
  });
252
299
  hook.on("effect:create", (payload) => {
253
- nextNodeId++;
300
+ _dt.nextNodeId++;
254
301
  const name = inferName();
255
302
  const p = payload;
256
- const node = { id: nextNodeId, type: "effect", name, ref: p.effectFn, createdAt: Date.now() };
257
- hook.nodes.set(nextNodeId, node);
303
+ const node = { id: _dt.nextNodeId, type: "effect", name, ref: p.effectFn, createdAt: Date.now() };
304
+ hook.nodes.set(_dt.nextNodeId, node);
258
305
  emit();
259
306
  });
260
307
  hook.on("effect:run", (payload) => {
@@ -411,8 +458,8 @@ function initDevTools(config) {
411
458
  hook.components.clear();
412
459
  hook.events.length = 0;
413
460
  isActive = false;
414
- inferNameArmed = false;
415
- activeDevTools = null;
461
+ _dt.inferNameArmed = false;
462
+ _dt.active = null;
416
463
  const g = globalThis;
417
464
  if (g.__SIBU__) {
418
465
  delete g.__SIBU__.devtools;
@@ -580,7 +627,7 @@ function initDevTools(config) {
580
627
  w.__SIBU_DEVTOOLS_VERSION__ = () => changeVersion;
581
628
  w.__SIBU_DEVTOOLS_DATA__ = buildData;
582
629
  }
583
- activeDevTools = api;
630
+ _dt.active = api;
584
631
  if (typeof document !== "undefined") {
585
632
  domObserver = new MutationObserver((mutations) => {
586
633
  let changed = false;
@@ -656,7 +703,7 @@ function initDevTools(config) {
656
703
  return api;
657
704
  }
658
705
  function inferName() {
659
- if (!inferNameArmed || !isDev()) return "anonymous";
706
+ if (!_dt.inferNameArmed || !isDev()) return "anonymous";
660
707
  try {
661
708
  const stack = new Error().stack || "";
662
709
  for (const line of stack.split("\n")) {
@@ -737,7 +784,7 @@ function devState(name, initial) {
737
784
  const oldValue = get();
738
785
  set(next);
739
786
  const newValue = get();
740
- const dt = activeDevTools;
787
+ const dt = _dt.active;
741
788
  if (dt?.isEnabled() && !Object.is(oldValue, newValue)) {
742
789
  dt.record({ type: "state-change", component, key, oldValue, newValue, timestamp: Date.now() });
743
790
  }
@@ -1,22 +1,24 @@
1
1
  import {
2
2
  watch
3
- } from "./chunk-SLMFA3ZZ.js";
3
+ } from "./chunk-TBDTU7UO.js";
4
+ import {
5
+ createId
6
+ } from "./chunk-CCSJMTRN.js";
4
7
  import {
5
8
  derived
6
- } from "./chunk-WW6DAGGR.js";
9
+ } from "./chunk-F3HO2MP7.js";
7
10
  import {
8
11
  stripHtml
9
12
  } from "./chunk-L3GAGWCC.js";
10
13
  import {
11
14
  effect
12
- } from "./chunk-MHBCEJQO.js";
15
+ } from "./chunk-23VT3KZB.js";
13
16
  import {
14
17
  batch,
15
18
  signal
16
- } from "./chunk-WZG2SZOT.js";
19
+ } from "./chunk-IWFE4AHO.js";
17
20
 
18
21
  // src/widgets/Combobox.ts
19
- var comboboxIdCounter = 0;
20
22
  var boundComboboxes = /* @__PURE__ */ new WeakMap();
21
23
  function combobox(options) {
22
24
  const { items, filterFn, itemToString } = options;
@@ -77,7 +79,13 @@ function combobox(options) {
77
79
  function bind(els) {
78
80
  const existing = boundComboboxes.get(els.input);
79
81
  if (existing) return existing;
80
- const listboxId = `sibu-combobox-listbox-${++comboboxIdCounter}`;
82
+ const prevListboxId = els.listbox.id;
83
+ const prevListboxRole = els.listbox.getAttribute("role");
84
+ const prevListboxHidden = els.listbox.hidden;
85
+ const prevInputRole = els.input.getAttribute("role");
86
+ const prevInputAutocomplete = els.input.getAttribute("aria-autocomplete");
87
+ const prevInputControls = els.input.getAttribute("aria-controls");
88
+ const listboxId = createId("sibu-combobox-listbox");
81
89
  els.listbox.id = listboxId;
82
90
  els.listbox.setAttribute("role", "listbox");
83
91
  els.input.setAttribute("role", "combobox");
@@ -141,10 +149,14 @@ function combobox(options) {
141
149
  if (document.activeElement !== els.input) close();
142
150
  }, 100);
143
151
  };
152
+ const onListboxPointerDown = (e) => {
153
+ e.preventDefault();
154
+ };
144
155
  els.input.addEventListener("input", onInput);
145
156
  els.input.addEventListener("keydown", onKey);
146
157
  els.input.addEventListener("focus", onFocus);
147
158
  els.input.addEventListener("blur", onBlur);
159
+ els.listbox.addEventListener("mousedown", onListboxPointerDown);
148
160
  const teardown = () => {
149
161
  boundComboboxes.delete(els.input);
150
162
  fxTeardown();
@@ -152,10 +164,24 @@ function combobox(options) {
152
164
  els.input.removeEventListener("keydown", onKey);
153
165
  els.input.removeEventListener("focus", onFocus);
154
166
  els.input.removeEventListener("blur", onBlur);
167
+ els.listbox.removeEventListener("mousedown", onListboxPointerDown);
155
168
  if (blurTimer !== null) {
156
169
  clearTimeout(blurTimer);
157
170
  blurTimer = null;
158
171
  }
172
+ if (prevListboxId === "") els.listbox.removeAttribute("id");
173
+ else els.listbox.id = prevListboxId;
174
+ if (prevListboxRole === null) els.listbox.removeAttribute("role");
175
+ else els.listbox.setAttribute("role", prevListboxRole);
176
+ els.listbox.hidden = prevListboxHidden;
177
+ if (prevInputRole === null) els.input.removeAttribute("role");
178
+ else els.input.setAttribute("role", prevInputRole);
179
+ if (prevInputAutocomplete === null) els.input.removeAttribute("aria-autocomplete");
180
+ else els.input.setAttribute("aria-autocomplete", prevInputAutocomplete);
181
+ if (prevInputControls === null) els.input.removeAttribute("aria-controls");
182
+ else els.input.setAttribute("aria-controls", prevInputControls);
183
+ els.input.removeAttribute("aria-expanded");
184
+ els.input.removeAttribute("aria-activedescendant");
159
185
  };
160
186
  boundComboboxes.set(els.input, teardown);
161
187
  return teardown;
@@ -491,7 +517,6 @@ function accordion(options) {
491
517
  }
492
518
 
493
519
  // src/widgets/Popover.ts
494
- var popoverIdCounter = 0;
495
520
  var boundPopovers = /* @__PURE__ */ new WeakMap();
496
521
  function popover() {
497
522
  const [isOpen, setIsOpen] = signal(false);
@@ -507,7 +532,7 @@ function popover() {
507
532
  function bind(els) {
508
533
  const existing = boundPopovers.get(els.trigger);
509
534
  if (existing) return existing;
510
- const id = `sibu-popover-${++popoverIdCounter}`;
535
+ const id = createId("sibu-popover");
511
536
  const prevPopoverRole = els.popover.getAttribute("role");
512
537
  const prevPopoverId = els.popover.id;
513
538
  const prevLabelledBy = els.popover.getAttribute("aria-labelledby");
@@ -581,7 +606,6 @@ function popover() {
581
606
  }
582
607
 
583
608
  // src/widgets/Select.ts
584
- var selectIdCounter = 0;
585
609
  var boundSelects = /* @__PURE__ */ new WeakMap();
586
610
  function select(options) {
587
611
  const { items, multiple = false, itemToString, isDisabled } = options;
@@ -662,7 +686,11 @@ function select(options) {
662
686
  function bind(els) {
663
687
  const existing = boundSelects.get(els.listbox);
664
688
  if (existing) return existing;
665
- const listboxId = `sibu-select-${++selectIdCounter}`;
689
+ const prevId = els.listbox.id;
690
+ const prevRole = els.listbox.getAttribute("role");
691
+ const prevMultiselectable = els.listbox.getAttribute("aria-multiselectable");
692
+ const prevTabIndex = els.listbox.getAttribute("tabindex");
693
+ const listboxId = createId("sibu-select");
666
694
  els.listbox.id = listboxId;
667
695
  els.listbox.setAttribute("role", "listbox");
668
696
  els.listbox.setAttribute("aria-multiselectable", multiple ? "true" : "false");
@@ -722,6 +750,15 @@ function select(options) {
722
750
  fxTeardown();
723
751
  els.listbox.removeEventListener("keydown", onKey);
724
752
  if (typeTimer !== null) clearTimeout(typeTimer);
753
+ if (prevId === "") els.listbox.removeAttribute("id");
754
+ else els.listbox.id = prevId;
755
+ if (prevRole === null) els.listbox.removeAttribute("role");
756
+ else els.listbox.setAttribute("role", prevRole);
757
+ if (prevMultiselectable === null) els.listbox.removeAttribute("aria-multiselectable");
758
+ else els.listbox.setAttribute("aria-multiselectable", prevMultiselectable);
759
+ if (prevTabIndex === null) els.listbox.removeAttribute("tabindex");
760
+ else els.listbox.setAttribute("tabindex", prevTabIndex);
761
+ els.listbox.removeAttribute("aria-activedescendant");
725
762
  };
726
763
  boundSelects.set(els.listbox, teardown);
727
764
  return teardown;
@@ -746,7 +783,6 @@ function select(options) {
746
783
  }
747
784
 
748
785
  // src/widgets/Tooltip.ts
749
- var tooltipIdCounter = 0;
750
786
  var boundTriggers = /* @__PURE__ */ new WeakMap();
751
787
  function tooltip(options) {
752
788
  const delay = options?.delay ?? 0;
@@ -791,7 +827,7 @@ function tooltip(options) {
791
827
  function bind(els) {
792
828
  const existing = boundTriggers.get(els.trigger);
793
829
  if (existing) return existing;
794
- const id = `sibu-tooltip-${++tooltipIdCounter}`;
830
+ const id = createId("sibu-tooltip");
795
831
  els.tooltip.setAttribute("role", "tooltip");
796
832
  els.tooltip.id = id;
797
833
  const prevDescribedBy = els.trigger.getAttribute("aria-describedby");
@@ -851,7 +887,6 @@ function tooltip(options) {
851
887
  }
852
888
 
853
889
  // src/widgets/FileUpload.ts
854
- var fileUploadIdCounter = 0;
855
890
  var boundFileUploads = /* @__PURE__ */ new WeakMap();
856
891
  function fileUpload(options) {
857
892
  const accept = options?.accept;
@@ -923,7 +958,7 @@ function fileUpload(options) {
923
958
  function bind(els) {
924
959
  const existing = boundFileUploads.get(els.input);
925
960
  if (existing) return existing;
926
- const id = `sibu-fileupload-${++fileUploadIdCounter}`;
961
+ const id = createId("sibu-fileupload");
927
962
  const restore = [];
928
963
  if (accept) els.input.accept = accept;
929
964
  els.input.multiple = multiple;
@@ -1034,7 +1069,7 @@ function fileUpload(options) {
1034
1069
  }
1035
1070
 
1036
1071
  // src/widgets/contentEditable.ts
1037
- function contentEditable() {
1072
+ function contentEditable(element) {
1038
1073
  const [content, setContentInternal] = signal("");
1039
1074
  const [isFocused, setFocused] = signal(false);
1040
1075
  function setContent(input) {
@@ -1058,6 +1093,7 @@ function contentEditable() {
1058
1093
  const selection = window.getSelection();
1059
1094
  if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return;
1060
1095
  const range = selection.getRangeAt(0);
1096
+ if (element && !element.contains(range.commonAncestorContainer)) return;
1061
1097
  const ancestor = range.commonAncestorContainer;
1062
1098
  const existingWrap = findAncestorByTag(
1063
1099
  ancestor instanceof HTMLElement ? ancestor : ancestor.parentElement,
@@ -1237,12 +1273,18 @@ function datePicker(options) {
1237
1273
  function bind(els) {
1238
1274
  const existing = boundDatePickers.get(els.grid);
1239
1275
  if (existing) return existing;
1276
+ const prevGridRole = els.grid.getAttribute("role");
1277
+ const prevGridTabIndex = els.grid.getAttribute("tabindex");
1278
+ const prevGridLabel = els.grid.getAttribute("aria-label");
1240
1279
  els.grid.setAttribute("role", "grid");
1241
1280
  if (els.grid.tabIndex < 0) els.grid.tabIndex = 0;
1281
+ let pendingFocus = false;
1242
1282
  const fxTeardown = effect(() => {
1243
1283
  const sel = selectedDate();
1244
1284
  const view = viewDate();
1245
1285
  const days = daysInMonth();
1286
+ els.grid.setAttribute("aria-label", view.toLocaleDateString(void 0, { month: "long", year: "numeric" }));
1287
+ let viewCell = null;
1246
1288
  for (const d of days) {
1247
1289
  const cell = els.cell(d.date);
1248
1290
  if (!cell) continue;
@@ -1250,7 +1292,13 @@ function datePicker(options) {
1250
1292
  cell.setAttribute("aria-selected", sel && isSameCalendarDay(sel, d.date) ? "true" : "false");
1251
1293
  if (d.isDisabled) cell.setAttribute("aria-disabled", "true");
1252
1294
  else cell.removeAttribute("aria-disabled");
1253
- cell.tabIndex = isSameCalendarDay(view, d.date) ? 0 : -1;
1295
+ const isView = isSameCalendarDay(view, d.date);
1296
+ cell.tabIndex = isView ? 0 : -1;
1297
+ if (isView) viewCell = cell;
1298
+ }
1299
+ if (pendingFocus && viewCell && typeof viewCell.focus === "function") {
1300
+ pendingFocus = false;
1301
+ viewCell.focus();
1254
1302
  }
1255
1303
  });
1256
1304
  function isSameCalendarDay(a, b) {
@@ -1260,6 +1308,9 @@ function datePicker(options) {
1260
1308
  setViewDate((prev) => new Date(prev.getFullYear(), prev.getMonth(), prev.getDate() + delta));
1261
1309
  }
1262
1310
  const onKey = (e) => {
1311
+ 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") {
1312
+ pendingFocus = true;
1313
+ }
1263
1314
  switch (e.key) {
1264
1315
  case "ArrowLeft":
1265
1316
  e.preventDefault();
@@ -1308,6 +1359,12 @@ function datePicker(options) {
1308
1359
  boundDatePickers.delete(els.grid);
1309
1360
  fxTeardown();
1310
1361
  els.grid.removeEventListener("keydown", onKey);
1362
+ if (prevGridRole === null) els.grid.removeAttribute("role");
1363
+ else els.grid.setAttribute("role", prevGridRole);
1364
+ if (prevGridTabIndex === null) els.grid.removeAttribute("tabindex");
1365
+ else els.grid.setAttribute("tabindex", prevGridTabIndex);
1366
+ if (prevGridLabel === null) els.grid.removeAttribute("aria-label");
1367
+ else els.grid.setAttribute("aria-label", prevGridLabel);
1311
1368
  };
1312
1369
  boundDatePickers.set(els.grid, teardown);
1313
1370
  return teardown;
@@ -3,7 +3,7 @@ import {
3
3
  notifySubscribers,
4
4
  queueSignalNotification,
5
5
  recordDependency
6
- } from "./chunk-TEFZT5PJ.js";
6
+ } from "./chunk-UOMYIAG6.js";
7
7
  import {
8
8
  isDev
9
9
  } from "./chunk-COY6PUD2.js";
@@ -1,7 +1,3 @@
1
- import {
2
- __resetIdCounter,
3
- createId
4
- } from "./chunk-YT6HQ6AM.js";
5
1
  import {
6
2
  a,
7
3
  abbr,
@@ -139,33 +135,32 @@ import {
139
135
  use,
140
136
  var_,
141
137
  video
142
- } from "./chunk-37BUKSLH.js";
138
+ } from "./chunk-5K6RVA2S.js";
143
139
  import {
144
140
  watch
145
- } from "./chunk-SLMFA3ZZ.js";
141
+ } from "./chunk-TBDTU7UO.js";
142
+ import {
143
+ __resetIdCounter,
144
+ createId
145
+ } from "./chunk-CCSJMTRN.js";
146
146
  import {
147
147
  trustHTML
148
- } from "./chunk-R3QEDXFS.js";
148
+ } from "./chunk-EX77FXTT.js";
149
149
  import {
150
150
  context
151
- } from "./chunk-F7FXQ3QS.js";
151
+ } from "./chunk-ZXCZWMZT.js";
152
152
  import {
153
153
  SVG_NS,
154
154
  bindChildNode,
155
155
  tagFactory
156
- } from "./chunk-LU2MQXQQ.js";
156
+ } from "./chunk-RZKGMHH4.js";
157
157
  import {
158
158
  bindAttribute,
159
159
  bindDynamic
160
- } from "./chunk-NHKQKKZU.js";
160
+ } from "./chunk-SBPUXWCE.js";
161
161
  import {
162
162
  derived
163
- } from "./chunk-WW6DAGGR.js";
164
- import {
165
- checkLeaks,
166
- dispose,
167
- registerDisposer
168
- } from "./chunk-5VGSK6D2.js";
163
+ } from "./chunk-F3HO2MP7.js";
169
164
  import {
170
165
  isEventHandlerAttr,
171
166
  isUrlAttribute,
@@ -175,7 +170,7 @@ import {
175
170
  import {
176
171
  effect,
177
172
  on
178
- } from "./chunk-MHBCEJQO.js";
173
+ } from "./chunk-23VT3KZB.js";
179
174
  import {
180
175
  disableSSR,
181
176
  enableSSR,
@@ -184,13 +179,18 @@ import {
184
179
  isSSR,
185
180
  runInSSRContext,
186
181
  withSSR
187
- } from "./chunk-GOJMFRBL.js";
182
+ } from "./chunk-S3NFJO6L.js";
183
+ import {
184
+ checkLeaks,
185
+ dispose,
186
+ registerDisposer
187
+ } from "./chunk-5VGSK6D2.js";
188
188
  import {
189
189
  batch,
190
190
  enqueueBatchedSignal,
191
191
  isBatching,
192
192
  signal
193
- } from "./chunk-WZG2SZOT.js";
193
+ } from "./chunk-IWFE4AHO.js";
194
194
  import {
195
195
  notifySubscribers,
196
196
  recordDependency,
@@ -200,7 +200,7 @@ import {
200
200
  suspendTracking,
201
201
  track,
202
202
  untracked
203
- } from "./chunk-TEFZT5PJ.js";
203
+ } from "./chunk-UOMYIAG6.js";
204
204
  import {
205
205
  __export,
206
206
  devAssert,
@@ -290,6 +290,7 @@ __export(index_exports, {
290
290
  footer: () => footer,
291
291
  form: () => form,
292
292
  g: () => g,
293
+ getAction: () => getAction,
293
294
  getRequestScopedCache: () => getRequestScopedCache,
294
295
  getSSRStore: () => getSSRStore,
295
296
  getSlot: () => getSlot,
@@ -358,6 +359,7 @@ __export(index_exports, {
358
359
  reactiveArray: () => reactiveArray,
359
360
  rect: () => rect,
360
361
  ref: () => ref,
362
+ registerAction: () => registerAction,
361
363
  registerComponent: () => registerComponent,
362
364
  registerDisposer: () => registerDisposer,
363
365
  resolveComponent: () => resolveComponent,
@@ -1320,7 +1322,21 @@ function KeepAlive(activeKey, cases, options) {
1320
1322
  }
1321
1323
 
1322
1324
  // src/core/rendering/action.ts
1323
- function action(element, actionFn, param2) {
1325
+ var ACTIONS_KEY = /* @__PURE__ */ Symbol.for("sibujs.actions.v1");
1326
+ var _actions = globalThis[ACTIONS_KEY] ?? (globalThis[ACTIONS_KEY] = /* @__PURE__ */ new Map());
1327
+ function registerAction(name, fn) {
1328
+ _actions.set(name, fn);
1329
+ }
1330
+ function getAction(name) {
1331
+ return _actions.get(name);
1332
+ }
1333
+ function action(element, action2, param2) {
1334
+ const actionFn = typeof action2 === "string" ? getAction(action2) : action2;
1335
+ if (!actionFn) {
1336
+ throw new Error(
1337
+ `[SibuJS] No action registered under the name "${action2}". Register it with registerAction() before applying it by name.`
1338
+ );
1339
+ }
1324
1340
  const cleanup = actionFn(element, param2);
1325
1341
  if (typeof cleanup === "function") {
1326
1342
  registerDisposer(element, cleanup);
@@ -1397,6 +1413,11 @@ var trapFocus = (element) => {
1397
1413
  element.addEventListener("keydown", handler);
1398
1414
  return () => element.removeEventListener("keydown", handler);
1399
1415
  };
1416
+ registerAction("clickOutside", clickOutside);
1417
+ registerAction("longPress", longPress);
1418
+ registerAction("copyOnClick", copyOnClick);
1419
+ registerAction("autoResize", autoResize);
1420
+ registerAction("trapFocus", trapFocus);
1400
1421
 
1401
1422
  // src/core/rendering/catch.ts
1402
1423
  var globalErrorHandler = null;
@@ -2329,7 +2350,6 @@ function Suspense({ nodes, fallback }) {
2329
2350
  }
2330
2351
 
2331
2352
  // src/components/ErrorDisplay.ts
2332
- var _isDev3 = isDev();
2333
2353
  var STYLES = `
2334
2354
  .sibu-error-display {
2335
2355
  border: 1px solid var(--sibu-err-border, #e5484d);
@@ -2640,7 +2660,7 @@ function ErrorDisplay(props) {
2640
2660
  injectStyles();
2641
2661
  const severity = props.severity ?? "error";
2642
2662
  const normalized = normalizeError(props.error);
2643
- const showDetails = props.alwaysShowDetails ?? _isDev3;
2663
+ const showDetails = props.alwaysShowDetails ?? isDev();
2644
2664
  const headline = props.title ?? normalized.message;
2645
2665
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
2646
2666
  const [copyLabel, setCopyLabel] = signal("Copy");
@@ -3127,6 +3147,11 @@ function Loading(props = {}) {
3127
3147
  if (variant === "dots") {
3128
3148
  return div({
3129
3149
  class: `sibu-loading${sizeClass}`,
3150
+ role: "status",
3151
+ "aria-live": "polite",
3152
+ // When there's no visible text, give the live region an accessible name
3153
+ // so it isn't announced as an empty status.
3154
+ "aria-label": text2 ? void 0 : "Loading",
3130
3155
  nodes: [
3131
3156
  div({
3132
3157
  class: "sibu-loading-dots",
@@ -3142,6 +3167,9 @@ function Loading(props = {}) {
3142
3167
  }
3143
3168
  return div({
3144
3169
  class: `sibu-loading${sizeClass}`,
3170
+ role: "status",
3171
+ "aria-live": "polite",
3172
+ "aria-label": text2 ? void 0 : "Loading",
3145
3173
  nodes: [
3146
3174
  div({ class: "sibu-loading-spinner" }),
3147
3175
  text2 ? span({ class: "sibu-loading-text", nodes: text2 }) : null
@@ -3164,6 +3192,8 @@ export {
3164
3192
  when,
3165
3193
  match,
3166
3194
  KeepAlive,
3195
+ registerAction,
3196
+ getAction,
3167
3197
  action,
3168
3198
  clickOutside,
3169
3199
  longPress,