sibujs 4.4.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +11 -0
  2. package/dist/browser.cjs +339 -130
  3. package/dist/browser.d.cts +61 -16
  4. package/dist/browser.d.ts +61 -16
  5. package/dist/browser.js +8 -6
  6. package/dist/build.cjs +273 -82
  7. package/dist/build.js +15 -16
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +11 -11
  10. package/dist/cdn.full.global.js +10 -10
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  13. package/dist/chunk-3ISI6ACU.js +44 -0
  14. package/dist/{chunk-VCTAEPSB.js → chunk-47M47FOM.js} +11 -3
  15. package/dist/chunk-5HZXGZ6T.js +24 -0
  16. package/dist/{chunk-KKLW7YWL.js → chunk-5MT6SJ3P.js} +275 -144
  17. package/dist/{chunk-3BTTCZ5J.js → chunk-5Q4R7HCL.js} +42 -4
  18. package/dist/{chunk-ADM46X22.js → chunk-7GQCWFOE.js} +157 -61
  19. package/dist/{chunk-4Z3SPQ67.js → chunk-7NCARGJW.js} +16 -8
  20. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  21. package/dist/{chunk-LQFGQNMV.js → chunk-AIF3Z2T7.js} +302 -39
  22. package/dist/{chunk-UCALUKB5.js → chunk-BAAG6ZTI.js} +98 -29
  23. package/dist/{chunk-NVNJH22U.js → chunk-D33YTSX5.js} +2 -2
  24. package/dist/{chunk-M2F7TZHH.js → chunk-DKGBBKOF.js} +136 -80
  25. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  26. package/dist/{chunk-HFCOH2GN.js → chunk-HYXDKS4N.js} +153 -110
  27. package/dist/chunk-J6FW5TV6.js +233 -0
  28. package/dist/{chunk-36S2YPP4.js → chunk-JSXPZCET.js} +1 -1
  29. package/dist/chunk-NYNYSPK7.js +318 -0
  30. package/dist/{chunk-HQSEH5F6.js → chunk-ORMZXBKQ.js} +108 -45
  31. package/dist/{chunk-LYVUX7NT.js → chunk-OXUY2A6L.js} +287 -130
  32. package/dist/{chunk-6GSIXTWX.js → chunk-PK6FK2G2.js} +187 -66
  33. package/dist/{chunk-W2EQ7X2L.js → chunk-QE4TTDU3.js} +47 -9
  34. package/dist/{chunk-XLO7SLIX.js → chunk-SXXVZMKZ.js} +262 -70
  35. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  36. package/dist/{chunk-RR7M3FHM.js → chunk-UZQ6ALFS.js} +3 -3
  37. package/dist/{chunk-PNIRUQ4C.js → chunk-VVWPJ543.js} +6 -8
  38. package/dist/{chunk-7GIHSAWB.js → chunk-XC4MEKGA.js} +3 -3
  39. package/dist/{chunk-SKPTERHP.js → chunk-XRRZKZYX.js} +55 -31
  40. package/dist/{contracts-DBdg9J_a.d.cts → contracts-DRIuclVT.d.cts} +10 -2
  41. package/dist/{contracts-DBdg9J_a.d.ts → contracts-DRIuclVT.d.ts} +10 -2
  42. package/dist/{customElement-CNZxEB9G.d.ts → customElement-MmInOW1U.d.cts} +59 -10
  43. package/dist/{customElement-CNZxEB9G.d.cts → customElement-MmInOW1U.d.ts} +59 -10
  44. package/dist/data.cjs +293 -125
  45. package/dist/data.d.cts +47 -9
  46. package/dist/data.d.ts +47 -9
  47. package/dist/data.js +12 -9
  48. package/dist/devtools.cjs +121 -58
  49. package/dist/devtools.js +7 -8
  50. package/dist/dispose-GEIG2KOF.js +28 -0
  51. package/dist/ecosystem.cjs +391 -112
  52. package/dist/ecosystem.d.cts +31 -7
  53. package/dist/ecosystem.d.ts +31 -7
  54. package/dist/ecosystem.js +12 -12
  55. package/dist/extras.cjs +2250 -879
  56. package/dist/extras.d.cts +10 -9
  57. package/dist/extras.d.ts +10 -9
  58. package/dist/extras.js +39 -28
  59. package/dist/index.cjs +273 -82
  60. package/dist/index.d.cts +44 -154
  61. package/dist/index.d.ts +44 -154
  62. package/dist/index.js +24 -27
  63. package/dist/motion.cjs +137 -42
  64. package/dist/motion.js +5 -5
  65. package/dist/patterns.cjs +397 -48
  66. package/dist/patterns.d.cts +32 -9
  67. package/dist/patterns.d.ts +32 -9
  68. package/dist/patterns.js +8 -8
  69. package/dist/performance.cjs +338 -217
  70. package/dist/performance.d.cts +2 -2
  71. package/dist/performance.d.ts +2 -2
  72. package/dist/performance.js +8 -9
  73. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  74. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  75. package/dist/plugins.cjs +587 -203
  76. package/dist/plugins.d.cts +3 -3
  77. package/dist/plugins.d.ts +3 -3
  78. package/dist/plugins.js +35 -23
  79. package/dist/signal-EotCj4hS.d.cts +110 -0
  80. package/dist/signal-EotCj4hS.d.ts +110 -0
  81. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  82. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  83. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  84. package/dist/ssr.cjs +214 -73
  85. package/dist/ssr.d.cts +9 -3
  86. package/dist/ssr.d.ts +9 -3
  87. package/dist/ssr.js +11 -12
  88. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  89. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  90. package/dist/tagFactory-DFstCLQV.d.cts +117 -0
  91. package/dist/tagFactory-DkaNVUNV.d.ts +117 -0
  92. package/dist/testing.cjs +2481 -2153
  93. package/dist/testing.d.cts +56 -5
  94. package/dist/testing.d.ts +56 -5
  95. package/dist/testing.js +580 -307
  96. package/dist/ui.cjs +798 -328
  97. package/dist/ui.d.cts +40 -7
  98. package/dist/ui.d.ts +40 -7
  99. package/dist/ui.js +151 -56
  100. package/dist/widgets.cjs +319 -285
  101. package/dist/widgets.js +9 -10
  102. package/package.json +2 -2
  103. package/dist/chunk-2WLZ6757.js +0 -149
  104. package/dist/chunk-7NBDXVHS.js +0 -60
  105. package/dist/chunk-CCSJMTRN.js +0 -15
  106. package/dist/chunk-QKRPLZ2V.js +0 -108
  107. package/dist/chunk-WWV3SJ3L.js +0 -131
  108. package/dist/dispose-46BOMMQJ.js +0 -19
  109. package/dist/plugin-D30wlGW5.d.cts +0 -71
  110. package/dist/plugin-D30wlGW5.d.ts +0 -71
  111. package/dist/tagFactory-Bzupt4Pj.d.cts +0 -55
  112. package/dist/tagFactory-Bzupt4Pj.d.ts +0 -55
@@ -1,22 +1,21 @@
1
1
  import {
2
2
  watch
3
- } from "./chunk-RR7M3FHM.js";
3
+ } from "./chunk-UZQ6ALFS.js";
4
4
  import {
5
- createId
6
- } from "./chunk-CCSJMTRN.js";
5
+ createId,
6
+ domBinding,
7
+ idSegment
8
+ } from "./chunk-3ISI6ACU.js";
7
9
  import {
8
10
  derived
9
- } from "./chunk-W2EQ7X2L.js";
11
+ } from "./chunk-QE4TTDU3.js";
10
12
  import {
11
13
  stripHtml
12
- } from "./chunk-IXKSNWV5.js";
13
- import {
14
- effect
15
- } from "./chunk-7GIHSAWB.js";
14
+ } from "./chunk-2INLLLMZ.js";
16
15
  import {
17
16
  batch,
18
17
  signal
19
- } from "./chunk-NVNJH22U.js";
18
+ } from "./chunk-D33YTSX5.js";
20
19
 
21
20
  // src/widgets/Combobox.ts
22
21
  var boundComboboxes = /* @__PURE__ */ new WeakMap();
@@ -91,7 +90,7 @@ function combobox(options) {
91
90
  els.input.setAttribute("role", "combobox");
92
91
  els.input.setAttribute("aria-autocomplete", "list");
93
92
  els.input.setAttribute("aria-controls", listboxId);
94
- const fxTeardown = effect(() => {
93
+ const fxTeardown = domBinding(() => {
95
94
  const open2 = isOpen();
96
95
  els.input.setAttribute("aria-expanded", open2 ? "true" : "false");
97
96
  els.listbox.hidden = !open2;
@@ -109,7 +108,7 @@ function combobox(options) {
109
108
  }
110
109
  if (activeId) els.input.setAttribute("aria-activedescendant", activeId);
111
110
  else els.input.removeAttribute("aria-activedescendant");
112
- });
111
+ }, els.input);
113
112
  const onInput = () => {
114
113
  setQuery(els.input.value);
115
114
  open();
@@ -157,8 +156,11 @@ function combobox(options) {
157
156
  els.input.addEventListener("focus", onFocus);
158
157
  els.input.addEventListener("blur", onBlur);
159
158
  els.listbox.addEventListener("mousedown", onListboxPointerDown);
159
+ let tornDown = false;
160
160
  const teardown = () => {
161
- boundComboboxes.delete(els.input);
161
+ if (tornDown) return;
162
+ tornDown = true;
163
+ if (boundComboboxes.get(els.input) === teardown) boundComboboxes.delete(els.input);
162
164
  fxTeardown();
163
165
  els.input.removeEventListener("input", onInput);
164
166
  els.input.removeEventListener("keydown", onKey);
@@ -203,11 +205,23 @@ function combobox(options) {
203
205
  };
204
206
  }
205
207
 
208
+ // src/widgets/attributeSnapshot.ts
209
+ function snapshotAttributes(el, names) {
210
+ const previous = names.map((name) => [name, el.getAttribute(name)]);
211
+ return () => {
212
+ for (const [name, value] of previous) {
213
+ if (value === null) el.removeAttribute(name);
214
+ else el.setAttribute(name, value);
215
+ }
216
+ };
217
+ }
218
+
206
219
  // src/widgets/Tabs.ts
207
220
  var boundTablists = /* @__PURE__ */ new WeakMap();
208
221
  function tabs(options) {
209
222
  const { tabs: tabDefs, defaultTab } = options;
210
- const initialTab = defaultTab ?? tabDefs.find((t) => !t.disabled)?.id ?? tabDefs[0]?.id ?? "";
223
+ const firstEnabled = tabDefs.find((t) => !t.disabled)?.id ?? "";
224
+ const initialTab = defaultTab !== void 0 && tabDefs.some((t) => t.id === defaultTab && !t.disabled) ? defaultTab : firstEnabled;
211
225
  const [activeTab, setActiveTabState] = signal(initialTab);
212
226
  function setActiveTab(id) {
213
227
  const tab = tabDefs.find((t) => t.id === id);
@@ -254,58 +268,29 @@ function tabs(options) {
254
268
  function bind(els) {
255
269
  const existing = boundTablists.get(els.tablist);
256
270
  if (existing) return existing;
257
- const restore = [];
258
- const prevTablistRole = els.tablist.getAttribute("role");
271
+ const restore = [snapshotAttributes(els.tablist, ["role"])];
259
272
  els.tablist.setAttribute("role", "tablist");
260
- restore.push(() => {
261
- if (prevTablistRole === null) els.tablist.removeAttribute("role");
262
- else els.tablist.setAttribute("role", prevTablistRole);
263
- });
273
+ const idPrefix = createId("sibu-tabs");
264
274
  for (const def of tabDefs) {
265
275
  const tabEl = els.tabs[def.id];
266
276
  if (!tabEl) continue;
267
- const prevRole = tabEl.getAttribute("role");
268
- const prevId = tabEl.id;
269
- const prevDisabled = tabEl.getAttribute("aria-disabled");
270
- const prevControls = tabEl.getAttribute("aria-controls");
277
+ restore.push(
278
+ snapshotAttributes(tabEl, ["role", "id", "aria-disabled", "aria-controls", "aria-selected", "tabindex"])
279
+ );
271
280
  tabEl.setAttribute("role", "tab");
272
- tabEl.setAttribute("id", `sibu-tab-${def.id}`);
281
+ if (!tabEl.id) tabEl.id = `${idPrefix}-tab-${idSegment(def.id)}`;
273
282
  if (def.disabled) tabEl.setAttribute("aria-disabled", "true");
283
+ else tabEl.removeAttribute("aria-disabled");
274
284
  const panelEl = els.panels?.[def.id];
275
- let prevPanelRole = null;
276
- let prevPanelId = "";
277
- let prevPanelLabelledBy = null;
278
285
  if (panelEl) {
279
- prevPanelRole = panelEl.getAttribute("role");
280
- prevPanelId = panelEl.id;
281
- prevPanelLabelledBy = panelEl.getAttribute("aria-labelledby");
286
+ restore.push(snapshotAttributes(panelEl, ["role", "id", "aria-labelledby", "hidden"]));
282
287
  panelEl.setAttribute("role", "tabpanel");
283
- panelEl.setAttribute("id", `sibu-tabpanel-${def.id}`);
284
- panelEl.setAttribute("aria-labelledby", `sibu-tab-${def.id}`);
285
- tabEl.setAttribute("aria-controls", `sibu-tabpanel-${def.id}`);
288
+ if (!panelEl.id) panelEl.id = `${idPrefix}-panel-${idSegment(def.id)}`;
289
+ panelEl.setAttribute("aria-labelledby", tabEl.id);
290
+ tabEl.setAttribute("aria-controls", panelEl.id);
286
291
  }
287
- restore.push(() => {
288
- if (prevRole === null) tabEl.removeAttribute("role");
289
- else tabEl.setAttribute("role", prevRole);
290
- if (prevId === "") tabEl.removeAttribute("id");
291
- else tabEl.id = prevId;
292
- if (prevDisabled === null) tabEl.removeAttribute("aria-disabled");
293
- else tabEl.setAttribute("aria-disabled", prevDisabled);
294
- if (prevControls === null) tabEl.removeAttribute("aria-controls");
295
- else tabEl.setAttribute("aria-controls", prevControls);
296
- tabEl.removeAttribute("aria-selected");
297
- tabEl.removeAttribute("tabindex");
298
- if (panelEl) {
299
- if (prevPanelRole === null) panelEl.removeAttribute("role");
300
- else panelEl.setAttribute("role", prevPanelRole);
301
- if (prevPanelId === "") panelEl.removeAttribute("id");
302
- else panelEl.id = prevPanelId;
303
- if (prevPanelLabelledBy === null) panelEl.removeAttribute("aria-labelledby");
304
- else panelEl.setAttribute("aria-labelledby", prevPanelLabelledBy);
305
- }
306
- });
307
292
  }
308
- const fxTeardown = effect(() => {
293
+ const fxTeardown = domBinding(() => {
309
294
  const active = activeTab();
310
295
  for (const def of tabDefs) {
311
296
  const tabEl = els.tabs[def.id];
@@ -316,7 +301,7 @@ function tabs(options) {
316
301
  const panelEl = els.panels?.[def.id];
317
302
  if (panelEl) panelEl.hidden = !isAct;
318
303
  }
319
- });
304
+ }, els.tablist);
320
305
  const onKey = (e) => {
321
306
  if (e.key === "ArrowRight" || e.key === "ArrowDown") {
322
307
  e.preventDefault();
@@ -353,7 +338,10 @@ function tabs(options) {
353
338
  tabEl.addEventListener("click", fn);
354
339
  clickHandlers.push({ el: tabEl, fn });
355
340
  }
341
+ let tornDown = false;
356
342
  const teardown = () => {
343
+ if (tornDown) return;
344
+ tornDown = true;
357
345
  boundTablists.delete(els.tablist);
358
346
  fxTeardown();
359
347
  els.tablist.removeEventListener("keydown", onKey);
@@ -379,7 +367,10 @@ function tabs(options) {
379
367
  var boundAccordions = /* @__PURE__ */ new WeakMap();
380
368
  function accordion(options) {
381
369
  const { items: itemDefs, multiple = false, defaultExpanded = [] } = options;
382
- const [expandedIds, setExpandedIds] = signal(new Set(defaultExpanded));
370
+ const knownIds = new Set(itemDefs.map((item) => item.id));
371
+ const validDefaults = defaultExpanded.filter((id) => knownIds.has(id));
372
+ const initialExpanded = multiple ? validDefaults : validDefaults.slice(0, 1);
373
+ const [expandedIds, setExpandedIds] = signal(new Set(initialExpanded));
383
374
  const items = derived(
384
375
  () => itemDefs.map((item) => ({
385
376
  ...item,
@@ -430,42 +421,22 @@ function accordion(options) {
430
421
  if (existing) return existing;
431
422
  }
432
423
  const restore = [];
424
+ const idPrefix = createId("sibu-accordion");
433
425
  for (const item of itemDefs) {
434
426
  const trig = els.triggers[item.id];
435
427
  const panel = els.panels[item.id];
436
428
  if (!trig) continue;
437
- const prevTrigId = trig.id;
438
- const prevTrigControls = trig.getAttribute("aria-controls");
439
- trig.id = `sibu-accordion-trigger-${item.id}`;
440
- let prevPanelRole = null;
441
- let prevPanelId = "";
442
- let prevPanelLabelledBy = null;
429
+ restore.push(snapshotAttributes(trig, ["id", "aria-controls", "aria-expanded"]));
430
+ if (!trig.id) trig.id = `${idPrefix}-trigger-${idSegment(item.id)}`;
443
431
  if (panel) {
444
- prevPanelRole = panel.getAttribute("role");
445
- prevPanelId = panel.id;
446
- prevPanelLabelledBy = panel.getAttribute("aria-labelledby");
432
+ restore.push(snapshotAttributes(panel, ["role", "id", "aria-labelledby", "hidden"]));
447
433
  panel.setAttribute("role", "region");
448
- panel.id = `sibu-accordion-panel-${item.id}`;
434
+ if (!panel.id) panel.id = `${idPrefix}-panel-${idSegment(item.id)}`;
449
435
  panel.setAttribute("aria-labelledby", trig.id);
450
436
  trig.setAttribute("aria-controls", panel.id);
451
437
  }
452
- restore.push(() => {
453
- if (prevTrigId === "") trig.removeAttribute("id");
454
- else trig.id = prevTrigId;
455
- if (prevTrigControls === null) trig.removeAttribute("aria-controls");
456
- else trig.setAttribute("aria-controls", prevTrigControls);
457
- trig.removeAttribute("aria-expanded");
458
- if (panel) {
459
- if (prevPanelRole === null) panel.removeAttribute("role");
460
- else panel.setAttribute("role", prevPanelRole);
461
- if (prevPanelId === "") panel.removeAttribute("id");
462
- else panel.id = prevPanelId;
463
- if (prevPanelLabelledBy === null) panel.removeAttribute("aria-labelledby");
464
- else panel.setAttribute("aria-labelledby", prevPanelLabelledBy);
465
- }
466
- });
467
438
  }
468
- const fxTeardown = effect(() => {
439
+ const fxTeardown = domBinding(() => {
469
440
  const ids = expandedIds();
470
441
  for (const item of itemDefs) {
471
442
  const trig = els.triggers[item.id];
@@ -475,7 +446,7 @@ function accordion(options) {
475
446
  trig.setAttribute("aria-expanded", expanded ? "true" : "false");
476
447
  if (panel) panel.hidden = !expanded;
477
448
  }
478
- });
449
+ }, idempotencyKey);
479
450
  const handlers = [];
480
451
  for (const item of itemDefs) {
481
452
  const trig = els.triggers[item.id];
@@ -491,7 +462,10 @@ function accordion(options) {
491
462
  trig.addEventListener("keydown", key);
492
463
  handlers.push({ el: trig, click, key });
493
464
  }
465
+ let tornDown = false;
494
466
  const teardown = () => {
467
+ if (tornDown) return;
468
+ tornDown = true;
495
469
  if (idempotencyKey) boundAccordions.delete(idempotencyKey);
496
470
  fxTeardown();
497
471
  for (const { el, click, key } of handlers) {
@@ -550,11 +524,11 @@ function popover() {
550
524
  }
551
525
  els.popover.setAttribute("aria-labelledby", els.labelledBy.id);
552
526
  }
553
- const fxTeardown = effect(() => {
527
+ const fxTeardown = domBinding(() => {
554
528
  const open2 = isOpen();
555
529
  els.trigger.setAttribute("aria-expanded", open2 ? "true" : "false");
556
530
  els.popover.hidden = !open2;
557
- });
531
+ }, els.trigger);
558
532
  const onTriggerClick = (e) => {
559
533
  e.preventDefault();
560
534
  toggle();
@@ -577,8 +551,11 @@ function popover() {
577
551
  els.popover.addEventListener("keydown", onKey);
578
552
  els.trigger.addEventListener("keydown", onKey);
579
553
  document.addEventListener("pointerdown", onDocPointer);
554
+ let tornDown = false;
580
555
  const teardown = () => {
581
- boundPopovers.delete(els.trigger);
556
+ if (tornDown) return;
557
+ tornDown = true;
558
+ if (boundPopovers.get(els.trigger) === teardown) boundPopovers.delete(els.trigger);
582
559
  fxTeardown();
583
560
  els.trigger.removeEventListener("click", onTriggerClick);
584
561
  els.popover.removeEventListener("keydown", onKey);
@@ -696,7 +673,7 @@ function select(options) {
696
673
  els.listbox.setAttribute("aria-multiselectable", multiple ? "true" : "false");
697
674
  if (els.listbox.tabIndex < 0) els.listbox.tabIndex = 0;
698
675
  const toStr = els.itemToString ?? itemToString ?? ((it) => String(it));
699
- const fxTeardown = effect(() => {
676
+ const fxTeardown = domBinding(() => {
700
677
  const idx = highlightedIndex();
701
678
  const sel = selectedItems();
702
679
  let activeId = "";
@@ -706,13 +683,14 @@ function select(options) {
706
683
  if (!optEl.id) optEl.id = `${listboxId}-opt-${i}`;
707
684
  optEl.setAttribute("role", "option");
708
685
  optEl.setAttribute("aria-selected", sel.includes(items[i]) ? "true" : "false");
709
- if (isItemDisabled(items[i])) optEl.setAttribute("aria-disabled", "true");
686
+ const disabled = isItemDisabled(items[i]);
687
+ if (disabled) optEl.setAttribute("aria-disabled", "true");
710
688
  else optEl.removeAttribute("aria-disabled");
711
- if (i === idx) activeId = optEl.id;
689
+ if (i === idx && !disabled) activeId = optEl.id;
712
690
  }
713
691
  if (activeId) els.listbox.setAttribute("aria-activedescendant", activeId);
714
692
  else els.listbox.removeAttribute("aria-activedescendant");
715
- });
693
+ }, els.listbox);
716
694
  let typeBuffer = "";
717
695
  let typeTimer = null;
718
696
  const onKey = (e) => {
@@ -724,10 +702,12 @@ function select(options) {
724
702
  highlightPrev();
725
703
  } else if (e.key === "Home") {
726
704
  e.preventDefault();
727
- if (items.length > 0) setHighlightedIndex(0);
705
+ const first = nextEnabled(-1, 1);
706
+ if (first !== -1) setHighlightedIndex(first);
728
707
  } else if (e.key === "End") {
729
708
  e.preventDefault();
730
- if (items.length > 0) setHighlightedIndex(items.length - 1);
709
+ const last = nextEnabled(items.length, -1);
710
+ if (last !== -1) setHighlightedIndex(last);
731
711
  } else if (e.key === "Enter" || e.key === " ") {
732
712
  if (highlightedIndex() >= 0) {
733
713
  e.preventDefault();
@@ -745,8 +725,11 @@ function select(options) {
745
725
  }
746
726
  };
747
727
  els.listbox.addEventListener("keydown", onKey);
728
+ let tornDown = false;
748
729
  const teardown = () => {
749
- boundSelects.delete(els.listbox);
730
+ if (tornDown) return;
731
+ tornDown = true;
732
+ if (boundSelects.get(els.listbox) === teardown) boundSelects.delete(els.listbox);
750
733
  fxTeardown();
751
734
  els.listbox.removeEventListener("keydown", onKey);
752
735
  if (typeTimer !== null) clearTimeout(typeTimer);
@@ -832,9 +815,9 @@ function tooltip(options) {
832
815
  els.tooltip.id = id;
833
816
  const prevDescribedBy = els.trigger.getAttribute("aria-describedby");
834
817
  els.trigger.setAttribute("aria-describedby", prevDescribedBy ? `${prevDescribedBy} ${id}` : id);
835
- const fxTeardown = effect(() => {
818
+ const fxTeardown = domBinding(() => {
836
819
  els.tooltip.hidden = !isVisible();
837
- });
820
+ }, els.trigger);
838
821
  const onTriggerEnter = () => show();
839
822
  const onTriggerLeave = () => scheduleHide();
840
823
  const onTooltipEnter = () => {
@@ -859,8 +842,11 @@ function tooltip(options) {
859
842
  els.trigger.addEventListener("keydown", onKey);
860
843
  els.tooltip.addEventListener("pointerenter", onTooltipEnter);
861
844
  els.tooltip.addEventListener("pointerleave", onTooltipLeave);
845
+ let tornDown = false;
862
846
  const teardown = () => {
863
- boundTriggers.delete(els.trigger);
847
+ if (tornDown) return;
848
+ tornDown = true;
849
+ if (boundTriggers.get(els.trigger) === teardown) boundTriggers.delete(els.trigger);
864
850
  fxTeardown();
865
851
  els.trigger.removeEventListener("pointerenter", onTriggerEnter);
866
852
  els.trigger.removeEventListener("pointerleave", onTriggerLeave);
@@ -898,7 +884,8 @@ function fileUpload(options) {
898
884
  const [isDragOver, setDragOver] = signal(false);
899
885
  function isAccepted(file) {
900
886
  if (!accept) return true;
901
- const acceptedTypes = accept.split(",").map((t) => t.trim().toLowerCase());
887
+ const acceptedTypes = accept.split(",").map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0);
888
+ if (acceptedTypes.length === 0) return true;
902
889
  const fileName = file.name.toLowerCase();
903
890
  const fileType = file.type.toLowerCase();
904
891
  return acceptedTypes.some((pattern) => {
@@ -927,16 +914,17 @@ function fileUpload(options) {
927
914
  }
928
915
  validFiles.push(file);
929
916
  }
917
+ const committed = multiple ? validFiles : validFiles.slice(-1);
930
918
  batch(() => {
931
919
  setErrors(newErrors);
932
- if (validFiles.length > 0) {
920
+ if (committed.length > 0) {
933
921
  if (multiple) {
934
- setFiles((prev) => [...prev, ...validFiles]);
922
+ setFiles((prev) => [...prev, ...committed]);
935
923
  } else {
936
- setFiles([validFiles[validFiles.length - 1]]);
924
+ setFiles(committed);
937
925
  }
938
926
  if (onFiles) {
939
- onFiles(validFiles);
927
+ onFiles(committed);
940
928
  }
941
929
  }
942
930
  });
@@ -960,8 +948,15 @@ function fileUpload(options) {
960
948
  if (existing) return existing;
961
949
  const id = createId("sibu-fileupload");
962
950
  const restore = [];
951
+ const prevAccept = els.input.getAttribute("accept");
952
+ const prevMultiple = els.input.hasAttribute("multiple");
963
953
  if (accept) els.input.accept = accept;
964
954
  els.input.multiple = multiple;
955
+ restore.push(() => {
956
+ if (prevAccept === null) els.input.removeAttribute("accept");
957
+ else els.input.setAttribute("accept", prevAccept);
958
+ els.input.multiple = prevMultiple;
959
+ });
965
960
  let hintId = null;
966
961
  if (els.hint) {
967
962
  const assignedHintId = !els.hint.id;
@@ -982,6 +977,7 @@ function fileUpload(options) {
982
977
  if (els.errorRegion) {
983
978
  const prevRole = els.errorRegion.getAttribute("role");
984
979
  const prevLive = els.errorRegion.getAttribute("aria-live");
980
+ const prevText = els.errorRegion.textContent;
985
981
  els.errorRegion.setAttribute("role", "status");
986
982
  els.errorRegion.setAttribute("aria-live", "polite");
987
983
  restore.push(() => {
@@ -989,12 +985,14 @@ function fileUpload(options) {
989
985
  else els.errorRegion.setAttribute("role", prevRole);
990
986
  if (prevLive === null) els.errorRegion.removeAttribute("aria-live");
991
987
  else els.errorRegion.setAttribute("aria-live", prevLive);
988
+ els.errorRegion.textContent = prevText;
992
989
  });
993
990
  }
994
991
  if (els.dropZone) {
995
992
  const prevDzRole = els.dropZone.getAttribute("role");
996
993
  const prevDzLabel = els.dropZone.getAttribute("aria-label");
997
994
  const prevDzTabindex = els.dropZone.hasAttribute("tabindex") ? els.dropZone.getAttribute("tabindex") : null;
995
+ const prevDzDragOver = els.dropZone.getAttribute("data-drag-over");
998
996
  els.dropZone.setAttribute("role", "button");
999
997
  els.dropZone.setAttribute("aria-label", "File drop zone \u2014 click or press Enter to browse");
1000
998
  if (els.dropZone.tabIndex < 0) els.dropZone.tabIndex = 0;
@@ -1005,13 +1003,15 @@ function fileUpload(options) {
1005
1003
  else els.dropZone.setAttribute("aria-label", prevDzLabel);
1006
1004
  if (prevDzTabindex === null) els.dropZone.removeAttribute("tabindex");
1007
1005
  else els.dropZone.setAttribute("tabindex", prevDzTabindex);
1006
+ if (prevDzDragOver === null) els.dropZone.removeAttribute("data-drag-over");
1007
+ else els.dropZone.setAttribute("data-drag-over", prevDzDragOver);
1008
1008
  });
1009
1009
  }
1010
- const fxTeardown = effect(() => {
1010
+ const fxTeardown = domBinding(() => {
1011
1011
  const errs = errors();
1012
1012
  if (els.errorRegion) els.errorRegion.textContent = errs.join(". ");
1013
1013
  if (els.dropZone) els.dropZone.setAttribute("data-drag-over", isDragOver() ? "true" : "false");
1014
- });
1014
+ }, els.input);
1015
1015
  const onChange = () => {
1016
1016
  if (els.input.files) addFiles(els.input.files);
1017
1017
  };
@@ -1040,7 +1040,10 @@ function fileUpload(options) {
1040
1040
  els.dropZone.addEventListener("dragleave", onDragLeave);
1041
1041
  els.dropZone.addEventListener("drop", onDrop);
1042
1042
  }
1043
+ let tornDown = false;
1043
1044
  const teardown = () => {
1045
+ if (tornDown) return;
1046
+ tornDown = true;
1044
1047
  boundFileUploads.delete(els.input);
1045
1048
  fxTeardown();
1046
1049
  els.input.removeEventListener("change", onChange);
@@ -1069,12 +1072,15 @@ function fileUpload(options) {
1069
1072
  }
1070
1073
 
1071
1074
  // src/widgets/contentEditable.ts
1075
+ function toInertText(html) {
1076
+ return stripHtml(String(html)).replace(/<(?=[a-z!/?])/gi, "&lt;");
1077
+ }
1072
1078
  function contentEditable(element) {
1073
1079
  const [content, setContentInternal] = signal("");
1074
1080
  const [isFocused, setFocused] = signal(false);
1075
1081
  function setContent(input) {
1076
1082
  if (typeof input === "string") {
1077
- setContentInternal(input);
1083
+ setContentInternal(toInertText(input));
1078
1084
  return;
1079
1085
  }
1080
1086
  if (typeof input.text === "string") {
@@ -1083,7 +1089,7 @@ function contentEditable(element) {
1083
1089
  }
1084
1090
  if (typeof input.html === "string") {
1085
1091
  const shouldSanitize = input.sanitize !== false;
1086
- setContentInternal(shouldSanitize ? stripHtml(input.html) : input.html);
1092
+ setContentInternal(shouldSanitize ? toInertText(input.html) : input.html);
1087
1093
  return;
1088
1094
  }
1089
1095
  setContentInternal("");
@@ -1095,7 +1101,7 @@ function contentEditable(element) {
1095
1101
  const range = selection.getRangeAt(0);
1096
1102
  if (element && !element.contains(range.commonAncestorContainer)) return;
1097
1103
  const ancestor = range.commonAncestorContainer;
1098
- const existingWrap = findAncestorByTag(
1104
+ const existingWrap = findWrapperWithinEditor(
1099
1105
  ancestor instanceof HTMLElement ? ancestor : ancestor.parentElement,
1100
1106
  tagName
1101
1107
  );
@@ -1136,14 +1142,19 @@ function contentEditable(element) {
1136
1142
  selection.addRange(newRange);
1137
1143
  }
1138
1144
  }
1139
- function findAncestorByTag(el, tagName) {
1145
+ function findWrapperWithinEditor(el, tagName) {
1140
1146
  const upper = tagName.toUpperCase();
1141
1147
  while (el) {
1148
+ if (el === element || isEditingHost(el)) return null;
1142
1149
  if (el.tagName === upper) return el;
1143
1150
  el = el.parentElement;
1144
1151
  }
1145
1152
  return null;
1146
1153
  }
1154
+ function isEditingHost(el) {
1155
+ const attr = el.getAttribute("contenteditable");
1156
+ return attr !== null && attr.toLowerCase() !== "false";
1157
+ }
1147
1158
  function bold() {
1148
1159
  wrapSelection("strong");
1149
1160
  }
@@ -1279,15 +1290,36 @@ function datePicker(options) {
1279
1290
  els.grid.setAttribute("role", "grid");
1280
1291
  if (els.grid.tabIndex < 0) els.grid.tabIndex = 0;
1281
1292
  let pendingFocus = false;
1282
- const fxTeardown = effect(() => {
1293
+ const touchedCells = /* @__PURE__ */ new Map();
1294
+ const restoreAttr = (el, name, value) => {
1295
+ if (value === null) el.removeAttribute(name);
1296
+ else el.setAttribute(name, value);
1297
+ };
1298
+ const restoreCell = (cell, snap) => {
1299
+ restoreAttr(cell, "role", snap.role);
1300
+ restoreAttr(cell, "aria-selected", snap.selected);
1301
+ restoreAttr(cell, "aria-disabled", snap.disabled);
1302
+ restoreAttr(cell, "tabindex", snap.tabindex);
1303
+ };
1304
+ const fxTeardown = domBinding(() => {
1283
1305
  const sel = selectedDate();
1284
1306
  const view = viewDate();
1285
1307
  const days = daysInMonth();
1286
1308
  els.grid.setAttribute("aria-label", view.toLocaleDateString(void 0, { month: "long", year: "numeric" }));
1287
1309
  let viewCell = null;
1310
+ const displayed = /* @__PURE__ */ new Set();
1288
1311
  for (const d of days) {
1289
1312
  const cell = els.cell(d.date);
1290
1313
  if (!cell) continue;
1314
+ displayed.add(cell);
1315
+ if (!touchedCells.has(cell)) {
1316
+ touchedCells.set(cell, {
1317
+ role: cell.getAttribute("role"),
1318
+ selected: cell.getAttribute("aria-selected"),
1319
+ disabled: cell.getAttribute("aria-disabled"),
1320
+ tabindex: cell.getAttribute("tabindex")
1321
+ });
1322
+ }
1291
1323
  cell.setAttribute("role", "gridcell");
1292
1324
  cell.setAttribute("aria-selected", sel && isSameCalendarDay(sel, d.date) ? "true" : "false");
1293
1325
  if (d.isDisabled) cell.setAttribute("aria-disabled", "true");
@@ -1296,11 +1328,17 @@ function datePicker(options) {
1296
1328
  cell.tabIndex = isView ? 0 : -1;
1297
1329
  if (isView) viewCell = cell;
1298
1330
  }
1331
+ for (const [cell, snap] of touchedCells) {
1332
+ if (!displayed.has(cell)) {
1333
+ restoreCell(cell, snap);
1334
+ touchedCells.delete(cell);
1335
+ }
1336
+ }
1299
1337
  if (pendingFocus && viewCell && typeof viewCell.focus === "function") {
1300
1338
  pendingFocus = false;
1301
1339
  viewCell.focus();
1302
1340
  }
1303
- });
1341
+ }, els.grid);
1304
1342
  function isSameCalendarDay(a, b) {
1305
1343
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1306
1344
  }
@@ -1355,8 +1393,11 @@ function datePicker(options) {
1355
1393
  }
1356
1394
  };
1357
1395
  els.grid.addEventListener("keydown", onKey);
1396
+ let tornDown = false;
1358
1397
  const teardown = () => {
1359
- boundDatePickers.delete(els.grid);
1398
+ if (tornDown) return;
1399
+ tornDown = true;
1400
+ if (boundDatePickers.get(els.grid) === teardown) boundDatePickers.delete(els.grid);
1360
1401
  fxTeardown();
1361
1402
  els.grid.removeEventListener("keydown", onKey);
1362
1403
  if (prevGridRole === null) els.grid.removeAttribute("role");
@@ -1365,6 +1406,8 @@ function datePicker(options) {
1365
1406
  else els.grid.setAttribute("tabindex", prevGridTabIndex);
1366
1407
  if (prevGridLabel === null) els.grid.removeAttribute("aria-label");
1367
1408
  else els.grid.setAttribute("aria-label", prevGridLabel);
1409
+ for (const [cell, snap] of touchedCells) restoreCell(cell, snap);
1410
+ touchedCells.clear();
1368
1411
  };
1369
1412
  boundDatePickers.set(els.grid, teardown);
1370
1413
  return teardown;