sibujs 4.5.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 (109) hide show
  1. package/dist/browser.cjs +309 -129
  2. package/dist/browser.d.cts +46 -14
  3. package/dist/browser.d.ts +46 -14
  4. package/dist/browser.js +8 -6
  5. package/dist/build.cjs +184 -72
  6. package/dist/build.js +15 -16
  7. package/dist/cdn.dev.global.js +12 -12
  8. package/dist/cdn.full.dev.global.js +11 -11
  9. package/dist/cdn.full.global.js +10 -10
  10. package/dist/cdn.global.js +12 -12
  11. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  12. package/dist/chunk-3ISI6ACU.js +44 -0
  13. package/dist/{chunk-ONOHFDLG.js → chunk-47M47FOM.js} +3 -3
  14. package/dist/chunk-5HZXGZ6T.js +24 -0
  15. package/dist/{chunk-KKLW7YWL.js → chunk-5MT6SJ3P.js} +275 -144
  16. package/dist/{chunk-UGRX3S57.js → chunk-5Q4R7HCL.js} +12 -3
  17. package/dist/{chunk-S373NSMK.js → chunk-7GQCWFOE.js} +150 -59
  18. package/dist/{chunk-FTIR4QW2.js → chunk-7NCARGJW.js} +6 -8
  19. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  20. package/dist/{chunk-VZKNK2V7.js → chunk-AIF3Z2T7.js} +296 -38
  21. package/dist/{chunk-4PMLNECI.js → chunk-BAAG6ZTI.js} +98 -29
  22. package/dist/{chunk-XZZOBQAY.js → chunk-D33YTSX5.js} +2 -2
  23. package/dist/{chunk-TBYTO6BS.js → chunk-DKGBBKOF.js} +136 -80
  24. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  25. package/dist/{chunk-GW3SCCZG.js → chunk-HYXDKS4N.js} +153 -110
  26. package/dist/chunk-J6FW5TV6.js +233 -0
  27. package/dist/{chunk-PCT43HW3.js → chunk-JSXPZCET.js} +1 -1
  28. package/dist/chunk-NYNYSPK7.js +318 -0
  29. package/dist/{chunk-RJE2BNI4.js → chunk-ORMZXBKQ.js} +108 -45
  30. package/dist/{chunk-OMJJM3KM.js → chunk-OXUY2A6L.js} +287 -130
  31. package/dist/{chunk-3JZ4L5TJ.js → chunk-PK6FK2G2.js} +181 -66
  32. package/dist/{chunk-RBTPLM32.js → chunk-QE4TTDU3.js} +2 -2
  33. package/dist/{chunk-2DCGACUU.js → chunk-SXXVZMKZ.js} +251 -66
  34. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  35. package/dist/{chunk-7LN645I6.js → chunk-UZQ6ALFS.js} +3 -3
  36. package/dist/{chunk-KEISJXBU.js → chunk-VVWPJ543.js} +6 -8
  37. package/dist/{chunk-RIXRAYIU.js → chunk-XC4MEKGA.js} +3 -3
  38. package/dist/{chunk-B3WHI2QA.js → chunk-XRRZKZYX.js} +55 -31
  39. package/dist/{contracts-DBdg9J_a.d.cts → contracts-DRIuclVT.d.cts} +10 -2
  40. package/dist/{contracts-DBdg9J_a.d.ts → contracts-DRIuclVT.d.ts} +10 -2
  41. package/dist/{customElement-OB9CIsc5.d.cts → customElement-MmInOW1U.d.cts} +21 -0
  42. package/dist/{customElement-OB9CIsc5.d.ts → customElement-MmInOW1U.d.ts} +21 -0
  43. package/dist/data.cjs +213 -117
  44. package/dist/data.d.cts +47 -9
  45. package/dist/data.d.ts +47 -9
  46. package/dist/data.js +12 -9
  47. package/dist/devtools.cjs +81 -57
  48. package/dist/devtools.js +7 -8
  49. package/dist/dispose-GEIG2KOF.js +28 -0
  50. package/dist/ecosystem.cjs +309 -104
  51. package/dist/ecosystem.d.cts +19 -3
  52. package/dist/ecosystem.d.ts +19 -3
  53. package/dist/ecosystem.js +12 -12
  54. package/dist/extras.cjs +2133 -863
  55. package/dist/extras.d.cts +10 -9
  56. package/dist/extras.d.ts +10 -9
  57. package/dist/extras.js +39 -28
  58. package/dist/index.cjs +184 -72
  59. package/dist/index.d.cts +22 -5
  60. package/dist/index.d.ts +22 -5
  61. package/dist/index.js +24 -27
  62. package/dist/motion.cjs +107 -41
  63. package/dist/motion.js +5 -5
  64. package/dist/patterns.cjs +317 -39
  65. package/dist/patterns.d.cts +27 -9
  66. package/dist/patterns.d.ts +27 -9
  67. package/dist/patterns.js +8 -8
  68. package/dist/performance.cjs +308 -216
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +8 -9
  72. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  73. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  74. package/dist/plugins.cjs +549 -202
  75. package/dist/plugins.d.cts +3 -3
  76. package/dist/plugins.d.ts +3 -3
  77. package/dist/plugins.js +35 -23
  78. package/dist/signal-EotCj4hS.d.cts +110 -0
  79. package/dist/signal-EotCj4hS.d.ts +110 -0
  80. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  81. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  82. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  83. package/dist/ssr.cjs +176 -72
  84. package/dist/ssr.d.cts +9 -3
  85. package/dist/ssr.d.ts +9 -3
  86. package/dist/ssr.js +11 -12
  87. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  88. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  89. package/dist/{tagFactory-DVoDpHye.d.ts → tagFactory-DFstCLQV.d.cts} +2 -100
  90. package/dist/{tagFactory-DVoDpHye.d.cts → tagFactory-DkaNVUNV.d.ts} +2 -100
  91. package/dist/testing.cjs +2484 -2185
  92. package/dist/testing.d.cts +56 -5
  93. package/dist/testing.d.ts +56 -5
  94. package/dist/testing.js +580 -307
  95. package/dist/ui.cjs +707 -317
  96. package/dist/ui.d.cts +40 -7
  97. package/dist/ui.d.ts +40 -7
  98. package/dist/ui.js +151 -56
  99. package/dist/widgets.cjs +246 -278
  100. package/dist/widgets.js +9 -10
  101. package/package.json +1 -1
  102. package/dist/chunk-2WLZ6757.js +0 -149
  103. package/dist/chunk-CCSJMTRN.js +0 -15
  104. package/dist/chunk-QKRPLZ2V.js +0 -108
  105. package/dist/chunk-VUF4ALSW.js +0 -60
  106. package/dist/chunk-WWV3SJ3L.js +0 -131
  107. package/dist/dispose-46BOMMQJ.js +0 -19
  108. package/dist/plugin-D30wlGW5.d.cts +0 -71
  109. package/dist/plugin-D30wlGW5.d.ts +0 -71
package/dist/widgets.cjs CHANGED
@@ -32,14 +32,6 @@ __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
-
43
35
  // src/core/dev.ts
44
36
  var DEV = typeof __SIBU_DEV__ !== "undefined" ? !!__SIBU_DEV__ : typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
45
37
  // safe default: off in browser, on in test/dev Node
@@ -56,6 +48,69 @@ function devWarn(message) {
56
48
  }
57
49
  }
58
50
 
51
+ // src/core/ssr-context.ts
52
+ var SSR_KEY = /* @__PURE__ */ Symbol.for("sibujs.ssr.v1");
53
+ function detectSSRShared() {
54
+ let detected = null;
55
+ try {
56
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
57
+ let mod = null;
58
+ const getBuiltin = process.getBuiltinModule;
59
+ if (typeof getBuiltin === "function") {
60
+ mod = getBuiltin("node:async_hooks");
61
+ } else if (typeof require === "function") {
62
+ mod = require("async_hooks");
63
+ }
64
+ if (mod) detected = new mod.AsyncLocalStorage();
65
+ }
66
+ } catch {
67
+ detected = null;
68
+ }
69
+ return {
70
+ als: detected,
71
+ fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
72
+ fallbackDepth: 0
73
+ };
74
+ }
75
+ var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
76
+ var als = _shared.als;
77
+ var fallbackStore = _shared.fallbackStore;
78
+ _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
79
+ function getSSRStore() {
80
+ if (als) {
81
+ const s = als.getStore();
82
+ if (s) return s;
83
+ }
84
+ return fallbackStore;
85
+ }
86
+ function getRequestStore() {
87
+ if (als) return als.getStore() ?? null;
88
+ return _shared.fallbackDepth > 0 ? fallbackStore : null;
89
+ }
90
+ function isSSR() {
91
+ return getSSRStore().ssr;
92
+ }
93
+
94
+ // src/core/rendering/createId.ts
95
+ var COUNTER_KEY = /* @__PURE__ */ Symbol.for("sibujs.createId.v1");
96
+ var _counter = globalThis[COUNTER_KEY] ?? (globalThis[COUNTER_KEY] = { n: 0 });
97
+ function createId(prefix = "sibu") {
98
+ const request = getRequestStore();
99
+ if (request) {
100
+ request.idCounter = (request.idCounter ?? 0) + 1;
101
+ return `${prefix}-${request.idCounter}`;
102
+ }
103
+ _counter.n++;
104
+ return `${prefix}-${_counter.n}`;
105
+ }
106
+ function idSegment(key) {
107
+ let out = "";
108
+ for (const ch of String(key)) {
109
+ out += /[A-Za-z0-9-]/.test(ch) ? ch : `_${(ch.codePointAt(0) ?? 0).toString(16)}_`;
110
+ }
111
+ return out;
112
+ }
113
+
59
114
  // src/core/errors.ts
60
115
  var ERROR_STATE_KEY = /* @__PURE__ */ Symbol.for("sibujs.runtime-errors.v1");
61
116
  function state() {
@@ -144,6 +199,25 @@ function reportError(error, context) {
144
199
  });
145
200
  }
146
201
 
202
+ // src/core/rendering/dispose.ts
203
+ function resolveCapture(capture) {
204
+ let frame = capture;
205
+ while (frame?.closed) frame = frame.forwardTo;
206
+ return frame ?? null;
207
+ }
208
+ var registrationCaptures = [];
209
+ function currentDisposerCapture() {
210
+ return resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
211
+ }
212
+ function beginDisposerCapture(capture) {
213
+ if (capture === null && registrationCaptures.length === 0) return false;
214
+ registrationCaptures.push(resolveCapture(capture));
215
+ return true;
216
+ }
217
+ function endDisposerCapture() {
218
+ registrationCaptures.pop();
219
+ }
220
+
147
221
  // src/reactivity/track-core.ts
148
222
  var POOL_MAX = 4096;
149
223
  var nodePool = [];
@@ -302,6 +376,7 @@ function retrack(effectFn, subscriber) {
302
376
  }
303
377
  currentSubscriber = subscriber;
304
378
  const sub = subscriber;
379
+ if (sub._cap === void 0) sub._cap = currentDisposerCapture();
305
380
  const epoch = ++subscriberEpochCounter;
306
381
  sub._epoch = epoch;
307
382
  sub._structDirty = false;
@@ -493,7 +568,12 @@ function drainQueue() {
493
568
  quarantined.add(sub);
494
569
  continue;
495
570
  }
496
- safeInvoke(sub);
571
+ const pushed = beginDisposerCapture(sub._cap ?? null);
572
+ try {
573
+ safeInvoke(sub);
574
+ } finally {
575
+ if (pushed) endDisposerCapture();
576
+ }
497
577
  }
498
578
  }
499
579
  function drainNotificationQueue() {
@@ -629,7 +709,7 @@ function forEachSubscriber(signal2, visit) {
629
709
  }
630
710
 
631
711
  // src/reactivity/track.ts
632
- var _runtimeVersion = true ? "4.5.0" : "dev";
712
+ var _runtimeVersion = true ? "4.6.0" : "dev";
633
713
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
634
714
  function resolveReactiveApi() {
635
715
  const g = globalThis;
@@ -795,169 +875,6 @@ function derived(getter, options) {
795
875
  return computedGetter;
796
876
  }
797
877
 
798
- // src/core/ssr-context.ts
799
- var SSR_KEY = /* @__PURE__ */ Symbol.for("sibujs.ssr.v1");
800
- function detectSSRShared() {
801
- let detected = null;
802
- try {
803
- if (typeof process !== "undefined" && process.versions && process.versions.node) {
804
- let mod = null;
805
- const getBuiltin = process.getBuiltinModule;
806
- if (typeof getBuiltin === "function") {
807
- mod = getBuiltin("node:async_hooks");
808
- } else if (typeof require === "function") {
809
- mod = require("async_hooks");
810
- }
811
- if (mod) detected = new mod.AsyncLocalStorage();
812
- }
813
- } catch {
814
- detected = null;
815
- }
816
- return {
817
- als: detected,
818
- fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
819
- fallbackDepth: 0
820
- };
821
- }
822
- var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
823
- var als = _shared.als;
824
- var fallbackStore = _shared.fallbackStore;
825
- _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
826
- function getSSRStore() {
827
- if (als) {
828
- const s = als.getStore();
829
- if (s) return s;
830
- }
831
- return fallbackStore;
832
- }
833
- function isSSR() {
834
- return getSSRStore().ssr;
835
- }
836
-
837
- // src/core/signals/effect.ts
838
- var _g = globalThis;
839
- var MAX_RERUNS = 100;
840
- function flushUserCleanups(ctx) {
841
- const list = ctx.userCleanups;
842
- if (list.length === 0) return;
843
- ctx.userCleanups = [];
844
- for (let i = list.length - 1; i >= 0; i--) {
845
- try {
846
- list[i]();
847
- } catch (err) {
848
- reportError(err, { phase: "cleanup" });
849
- }
850
- }
851
- }
852
- function drainReruns(ctx) {
853
- let reruns = 1;
854
- do {
855
- ctx.rerunPending = false;
856
- if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
857
- retrack2(ctx.bodyFn, ctx.subscriber);
858
- } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
859
- if (ctx.rerunPending) {
860
- ctx.rerunPending = false;
861
- reportError(
862
- new Error(
863
- `effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
864
- ),
865
- { phase: "effect" }
866
- );
867
- }
868
- }
869
- function disposeEffect(ctx) {
870
- if (ctx.disposed) return;
871
- ctx.disposed = true;
872
- const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
873
- if (h) {
874
- try {
875
- h.emit("effect:destroy", { effectFn: ctx.fn });
876
- } catch {
877
- }
878
- }
879
- try {
880
- if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
881
- } catch (err) {
882
- if (typeof console !== "undefined") {
883
- console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
884
- }
885
- }
886
- try {
887
- cleanup2(ctx.subscriber);
888
- } catch (err) {
889
- if (typeof console !== "undefined") {
890
- console.warn("[SibuJS effect] dispose threw:", err);
891
- }
892
- }
893
- }
894
- function effect(effectFn, options) {
895
- devAssert(typeof effectFn === "function", "effect: argument must be a function.");
896
- if (isSSR()) return () => {
897
- };
898
- const ctx = {
899
- fn: effectFn,
900
- onError: options?.onError,
901
- userCleanups: [],
902
- running: false,
903
- rerunPending: false,
904
- disposed: false,
905
- onCleanup: null,
906
- subscriber: null,
907
- bodyFn: null
908
- };
909
- ctx.onCleanup = (fn) => {
910
- ctx.userCleanups.push(fn);
911
- };
912
- const onErrorCaptured = ctx.onError;
913
- ctx.bodyFn = onErrorCaptured ? () => {
914
- try {
915
- ctx.fn(ctx.onCleanup);
916
- } catch (err) {
917
- onErrorCaptured(err);
918
- }
919
- } : () => {
920
- ctx.fn(ctx.onCleanup);
921
- };
922
- const sub = (() => {
923
- if (ctx.disposed) return;
924
- if (ctx.running) {
925
- ctx.rerunPending = true;
926
- return;
927
- }
928
- ctx.running = true;
929
- try {
930
- ctx.rerunPending = false;
931
- if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
932
- retrack2(ctx.bodyFn, sub);
933
- if (ctx.rerunPending) drainReruns(ctx);
934
- } finally {
935
- ctx.running = false;
936
- ctx.rerunPending = false;
937
- }
938
- });
939
- sub.depsHead = null;
940
- sub.depsTail = null;
941
- sub._epoch = 0;
942
- sub._structDirty = false;
943
- sub._hasComputedDep = false;
944
- sub._errorPhase = "effect";
945
- sub._runEpoch = 0;
946
- sub._runs = 0;
947
- ctx.subscriber = sub;
948
- ctx.running = true;
949
- try {
950
- retrack2(ctx.bodyFn, ctx.subscriber);
951
- if (ctx.rerunPending) drainReruns(ctx);
952
- } finally {
953
- ctx.running = false;
954
- ctx.rerunPending = false;
955
- }
956
- const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
957
- if (hook) hook.emit("effect:create", { effectFn });
958
- return () => disposeEffect(ctx);
959
- }
960
-
961
878
  // src/reactivity/batch.ts
962
879
  var batchDepth = 0;
963
880
  var pendingSignals = /* @__PURE__ */ new Set();
@@ -1009,7 +926,7 @@ var enqueueBatchedSignal = API2.enqueueBatchedSignal;
1009
926
  var isBatching = API2.isBatching;
1010
927
 
1011
928
  // src/core/signals/signal.ts
1012
- var _g2 = globalThis;
929
+ var _g = globalThis;
1013
930
  function signal(initial, options) {
1014
931
  const state2 = {
1015
932
  value: initial,
@@ -1040,7 +957,7 @@ function signal(initial, options) {
1040
957
  state2.value = newValue;
1041
958
  state2.__v++;
1042
959
  if (DEV) {
1043
- const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
960
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1044
961
  if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1045
962
  }
1046
963
  if (!enqueueBatchedSignal(state2)) {
@@ -1054,7 +971,7 @@ function signal(initial, options) {
1054
971
  if (Object.is(newValue, prev)) return;
1055
972
  state2.value = newValue;
1056
973
  state2.__v++;
1057
- const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
974
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1058
975
  if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1059
976
  if (!enqueueBatchedSignal(state2)) {
1060
977
  notifySubscribers2(state2);
@@ -1073,7 +990,7 @@ function signal(initial, options) {
1073
990
  };
1074
991
  }
1075
992
  if (DEV) {
1076
- const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
993
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1077
994
  if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1078
995
  }
1079
996
  return [get, set];
@@ -1109,6 +1026,13 @@ function watch(getter, callback) {
1109
1026
  return teardown;
1110
1027
  }
1111
1028
 
1029
+ // src/reactivity/domBinding.ts
1030
+ function domBinding(commit, ownerNode) {
1031
+ if (isSSR()) return () => {
1032
+ };
1033
+ return reactiveBinding2(commit, ownerNode);
1034
+ }
1035
+
1112
1036
  // src/widgets/Combobox.ts
1113
1037
  var boundComboboxes = /* @__PURE__ */ new WeakMap();
1114
1038
  function combobox(options) {
@@ -1182,7 +1106,7 @@ function combobox(options) {
1182
1106
  els.input.setAttribute("role", "combobox");
1183
1107
  els.input.setAttribute("aria-autocomplete", "list");
1184
1108
  els.input.setAttribute("aria-controls", listboxId);
1185
- const fxTeardown = effect(() => {
1109
+ const fxTeardown = domBinding(() => {
1186
1110
  const open2 = isOpen();
1187
1111
  els.input.setAttribute("aria-expanded", open2 ? "true" : "false");
1188
1112
  els.listbox.hidden = !open2;
@@ -1200,7 +1124,7 @@ function combobox(options) {
1200
1124
  }
1201
1125
  if (activeId) els.input.setAttribute("aria-activedescendant", activeId);
1202
1126
  else els.input.removeAttribute("aria-activedescendant");
1203
- });
1127
+ }, els.input);
1204
1128
  const onInput = () => {
1205
1129
  setQuery(els.input.value);
1206
1130
  open();
@@ -1248,8 +1172,11 @@ function combobox(options) {
1248
1172
  els.input.addEventListener("focus", onFocus);
1249
1173
  els.input.addEventListener("blur", onBlur);
1250
1174
  els.listbox.addEventListener("mousedown", onListboxPointerDown);
1175
+ let tornDown = false;
1251
1176
  const teardown = () => {
1252
- boundComboboxes.delete(els.input);
1177
+ if (tornDown) return;
1178
+ tornDown = true;
1179
+ if (boundComboboxes.get(els.input) === teardown) boundComboboxes.delete(els.input);
1253
1180
  fxTeardown();
1254
1181
  els.input.removeEventListener("input", onInput);
1255
1182
  els.input.removeEventListener("keydown", onKey);
@@ -1294,11 +1221,23 @@ function combobox(options) {
1294
1221
  };
1295
1222
  }
1296
1223
 
1224
+ // src/widgets/attributeSnapshot.ts
1225
+ function snapshotAttributes(el, names) {
1226
+ const previous = names.map((name) => [name, el.getAttribute(name)]);
1227
+ return () => {
1228
+ for (const [name, value] of previous) {
1229
+ if (value === null) el.removeAttribute(name);
1230
+ else el.setAttribute(name, value);
1231
+ }
1232
+ };
1233
+ }
1234
+
1297
1235
  // src/widgets/Tabs.ts
1298
1236
  var boundTablists = /* @__PURE__ */ new WeakMap();
1299
1237
  function tabs(options) {
1300
1238
  const { tabs: tabDefs, defaultTab } = options;
1301
- const initialTab = defaultTab ?? tabDefs.find((t) => !t.disabled)?.id ?? tabDefs[0]?.id ?? "";
1239
+ const firstEnabled = tabDefs.find((t) => !t.disabled)?.id ?? "";
1240
+ const initialTab = defaultTab !== void 0 && tabDefs.some((t) => t.id === defaultTab && !t.disabled) ? defaultTab : firstEnabled;
1302
1241
  const [activeTab, setActiveTabState] = signal(initialTab);
1303
1242
  function setActiveTab(id) {
1304
1243
  const tab = tabDefs.find((t) => t.id === id);
@@ -1345,58 +1284,29 @@ function tabs(options) {
1345
1284
  function bind(els) {
1346
1285
  const existing = boundTablists.get(els.tablist);
1347
1286
  if (existing) return existing;
1348
- const restore = [];
1349
- const prevTablistRole = els.tablist.getAttribute("role");
1287
+ const restore = [snapshotAttributes(els.tablist, ["role"])];
1350
1288
  els.tablist.setAttribute("role", "tablist");
1351
- restore.push(() => {
1352
- if (prevTablistRole === null) els.tablist.removeAttribute("role");
1353
- else els.tablist.setAttribute("role", prevTablistRole);
1354
- });
1289
+ const idPrefix = createId("sibu-tabs");
1355
1290
  for (const def of tabDefs) {
1356
1291
  const tabEl = els.tabs[def.id];
1357
1292
  if (!tabEl) continue;
1358
- const prevRole = tabEl.getAttribute("role");
1359
- const prevId = tabEl.id;
1360
- const prevDisabled = tabEl.getAttribute("aria-disabled");
1361
- const prevControls = tabEl.getAttribute("aria-controls");
1293
+ restore.push(
1294
+ snapshotAttributes(tabEl, ["role", "id", "aria-disabled", "aria-controls", "aria-selected", "tabindex"])
1295
+ );
1362
1296
  tabEl.setAttribute("role", "tab");
1363
- tabEl.setAttribute("id", `sibu-tab-${def.id}`);
1297
+ if (!tabEl.id) tabEl.id = `${idPrefix}-tab-${idSegment(def.id)}`;
1364
1298
  if (def.disabled) tabEl.setAttribute("aria-disabled", "true");
1299
+ else tabEl.removeAttribute("aria-disabled");
1365
1300
  const panelEl = els.panels?.[def.id];
1366
- let prevPanelRole = null;
1367
- let prevPanelId = "";
1368
- let prevPanelLabelledBy = null;
1369
1301
  if (panelEl) {
1370
- prevPanelRole = panelEl.getAttribute("role");
1371
- prevPanelId = panelEl.id;
1372
- prevPanelLabelledBy = panelEl.getAttribute("aria-labelledby");
1302
+ restore.push(snapshotAttributes(panelEl, ["role", "id", "aria-labelledby", "hidden"]));
1373
1303
  panelEl.setAttribute("role", "tabpanel");
1374
- panelEl.setAttribute("id", `sibu-tabpanel-${def.id}`);
1375
- panelEl.setAttribute("aria-labelledby", `sibu-tab-${def.id}`);
1376
- tabEl.setAttribute("aria-controls", `sibu-tabpanel-${def.id}`);
1304
+ if (!panelEl.id) panelEl.id = `${idPrefix}-panel-${idSegment(def.id)}`;
1305
+ panelEl.setAttribute("aria-labelledby", tabEl.id);
1306
+ tabEl.setAttribute("aria-controls", panelEl.id);
1377
1307
  }
1378
- restore.push(() => {
1379
- if (prevRole === null) tabEl.removeAttribute("role");
1380
- else tabEl.setAttribute("role", prevRole);
1381
- if (prevId === "") tabEl.removeAttribute("id");
1382
- else tabEl.id = prevId;
1383
- if (prevDisabled === null) tabEl.removeAttribute("aria-disabled");
1384
- else tabEl.setAttribute("aria-disabled", prevDisabled);
1385
- if (prevControls === null) tabEl.removeAttribute("aria-controls");
1386
- else tabEl.setAttribute("aria-controls", prevControls);
1387
- tabEl.removeAttribute("aria-selected");
1388
- tabEl.removeAttribute("tabindex");
1389
- if (panelEl) {
1390
- if (prevPanelRole === null) panelEl.removeAttribute("role");
1391
- else panelEl.setAttribute("role", prevPanelRole);
1392
- if (prevPanelId === "") panelEl.removeAttribute("id");
1393
- else panelEl.id = prevPanelId;
1394
- if (prevPanelLabelledBy === null) panelEl.removeAttribute("aria-labelledby");
1395
- else panelEl.setAttribute("aria-labelledby", prevPanelLabelledBy);
1396
- }
1397
- });
1398
1308
  }
1399
- const fxTeardown = effect(() => {
1309
+ const fxTeardown = domBinding(() => {
1400
1310
  const active = activeTab();
1401
1311
  for (const def of tabDefs) {
1402
1312
  const tabEl = els.tabs[def.id];
@@ -1407,7 +1317,7 @@ function tabs(options) {
1407
1317
  const panelEl = els.panels?.[def.id];
1408
1318
  if (panelEl) panelEl.hidden = !isAct;
1409
1319
  }
1410
- });
1320
+ }, els.tablist);
1411
1321
  const onKey = (e) => {
1412
1322
  if (e.key === "ArrowRight" || e.key === "ArrowDown") {
1413
1323
  e.preventDefault();
@@ -1444,7 +1354,10 @@ function tabs(options) {
1444
1354
  tabEl.addEventListener("click", fn);
1445
1355
  clickHandlers.push({ el: tabEl, fn });
1446
1356
  }
1357
+ let tornDown = false;
1447
1358
  const teardown = () => {
1359
+ if (tornDown) return;
1360
+ tornDown = true;
1448
1361
  boundTablists.delete(els.tablist);
1449
1362
  fxTeardown();
1450
1363
  els.tablist.removeEventListener("keydown", onKey);
@@ -1470,7 +1383,10 @@ function tabs(options) {
1470
1383
  var boundAccordions = /* @__PURE__ */ new WeakMap();
1471
1384
  function accordion(options) {
1472
1385
  const { items: itemDefs, multiple = false, defaultExpanded = [] } = options;
1473
- const [expandedIds, setExpandedIds] = signal(new Set(defaultExpanded));
1386
+ const knownIds = new Set(itemDefs.map((item) => item.id));
1387
+ const validDefaults = defaultExpanded.filter((id) => knownIds.has(id));
1388
+ const initialExpanded = multiple ? validDefaults : validDefaults.slice(0, 1);
1389
+ const [expandedIds, setExpandedIds] = signal(new Set(initialExpanded));
1474
1390
  const items = derived(
1475
1391
  () => itemDefs.map((item) => ({
1476
1392
  ...item,
@@ -1521,42 +1437,22 @@ function accordion(options) {
1521
1437
  if (existing) return existing;
1522
1438
  }
1523
1439
  const restore = [];
1440
+ const idPrefix = createId("sibu-accordion");
1524
1441
  for (const item of itemDefs) {
1525
1442
  const trig = els.triggers[item.id];
1526
1443
  const panel = els.panels[item.id];
1527
1444
  if (!trig) continue;
1528
- const prevTrigId = trig.id;
1529
- const prevTrigControls = trig.getAttribute("aria-controls");
1530
- trig.id = `sibu-accordion-trigger-${item.id}`;
1531
- let prevPanelRole = null;
1532
- let prevPanelId = "";
1533
- let prevPanelLabelledBy = null;
1445
+ restore.push(snapshotAttributes(trig, ["id", "aria-controls", "aria-expanded"]));
1446
+ if (!trig.id) trig.id = `${idPrefix}-trigger-${idSegment(item.id)}`;
1534
1447
  if (panel) {
1535
- prevPanelRole = panel.getAttribute("role");
1536
- prevPanelId = panel.id;
1537
- prevPanelLabelledBy = panel.getAttribute("aria-labelledby");
1448
+ restore.push(snapshotAttributes(panel, ["role", "id", "aria-labelledby", "hidden"]));
1538
1449
  panel.setAttribute("role", "region");
1539
- panel.id = `sibu-accordion-panel-${item.id}`;
1450
+ if (!panel.id) panel.id = `${idPrefix}-panel-${idSegment(item.id)}`;
1540
1451
  panel.setAttribute("aria-labelledby", trig.id);
1541
1452
  trig.setAttribute("aria-controls", panel.id);
1542
1453
  }
1543
- restore.push(() => {
1544
- if (prevTrigId === "") trig.removeAttribute("id");
1545
- else trig.id = prevTrigId;
1546
- if (prevTrigControls === null) trig.removeAttribute("aria-controls");
1547
- else trig.setAttribute("aria-controls", prevTrigControls);
1548
- trig.removeAttribute("aria-expanded");
1549
- if (panel) {
1550
- if (prevPanelRole === null) panel.removeAttribute("role");
1551
- else panel.setAttribute("role", prevPanelRole);
1552
- if (prevPanelId === "") panel.removeAttribute("id");
1553
- else panel.id = prevPanelId;
1554
- if (prevPanelLabelledBy === null) panel.removeAttribute("aria-labelledby");
1555
- else panel.setAttribute("aria-labelledby", prevPanelLabelledBy);
1556
- }
1557
- });
1558
1454
  }
1559
- const fxTeardown = effect(() => {
1455
+ const fxTeardown = domBinding(() => {
1560
1456
  const ids = expandedIds();
1561
1457
  for (const item of itemDefs) {
1562
1458
  const trig = els.triggers[item.id];
@@ -1566,7 +1462,7 @@ function accordion(options) {
1566
1462
  trig.setAttribute("aria-expanded", expanded ? "true" : "false");
1567
1463
  if (panel) panel.hidden = !expanded;
1568
1464
  }
1569
- });
1465
+ }, idempotencyKey);
1570
1466
  const handlers = [];
1571
1467
  for (const item of itemDefs) {
1572
1468
  const trig = els.triggers[item.id];
@@ -1582,7 +1478,10 @@ function accordion(options) {
1582
1478
  trig.addEventListener("keydown", key);
1583
1479
  handlers.push({ el: trig, click, key });
1584
1480
  }
1481
+ let tornDown = false;
1585
1482
  const teardown = () => {
1483
+ if (tornDown) return;
1484
+ tornDown = true;
1586
1485
  if (idempotencyKey) boundAccordions.delete(idempotencyKey);
1587
1486
  fxTeardown();
1588
1487
  for (const { el, click, key } of handlers) {
@@ -1641,11 +1540,11 @@ function popover() {
1641
1540
  }
1642
1541
  els.popover.setAttribute("aria-labelledby", els.labelledBy.id);
1643
1542
  }
1644
- const fxTeardown = effect(() => {
1543
+ const fxTeardown = domBinding(() => {
1645
1544
  const open2 = isOpen();
1646
1545
  els.trigger.setAttribute("aria-expanded", open2 ? "true" : "false");
1647
1546
  els.popover.hidden = !open2;
1648
- });
1547
+ }, els.trigger);
1649
1548
  const onTriggerClick = (e) => {
1650
1549
  e.preventDefault();
1651
1550
  toggle();
@@ -1668,8 +1567,11 @@ function popover() {
1668
1567
  els.popover.addEventListener("keydown", onKey);
1669
1568
  els.trigger.addEventListener("keydown", onKey);
1670
1569
  document.addEventListener("pointerdown", onDocPointer);
1570
+ let tornDown = false;
1671
1571
  const teardown = () => {
1672
- boundPopovers.delete(els.trigger);
1572
+ if (tornDown) return;
1573
+ tornDown = true;
1574
+ if (boundPopovers.get(els.trigger) === teardown) boundPopovers.delete(els.trigger);
1673
1575
  fxTeardown();
1674
1576
  els.trigger.removeEventListener("click", onTriggerClick);
1675
1577
  els.popover.removeEventListener("keydown", onKey);
@@ -1787,7 +1689,7 @@ function select(options) {
1787
1689
  els.listbox.setAttribute("aria-multiselectable", multiple ? "true" : "false");
1788
1690
  if (els.listbox.tabIndex < 0) els.listbox.tabIndex = 0;
1789
1691
  const toStr = els.itemToString ?? itemToString ?? ((it) => String(it));
1790
- const fxTeardown = effect(() => {
1692
+ const fxTeardown = domBinding(() => {
1791
1693
  const idx = highlightedIndex();
1792
1694
  const sel = selectedItems();
1793
1695
  let activeId = "";
@@ -1797,13 +1699,14 @@ function select(options) {
1797
1699
  if (!optEl.id) optEl.id = `${listboxId}-opt-${i}`;
1798
1700
  optEl.setAttribute("role", "option");
1799
1701
  optEl.setAttribute("aria-selected", sel.includes(items[i]) ? "true" : "false");
1800
- if (isItemDisabled(items[i])) optEl.setAttribute("aria-disabled", "true");
1702
+ const disabled = isItemDisabled(items[i]);
1703
+ if (disabled) optEl.setAttribute("aria-disabled", "true");
1801
1704
  else optEl.removeAttribute("aria-disabled");
1802
- if (i === idx) activeId = optEl.id;
1705
+ if (i === idx && !disabled) activeId = optEl.id;
1803
1706
  }
1804
1707
  if (activeId) els.listbox.setAttribute("aria-activedescendant", activeId);
1805
1708
  else els.listbox.removeAttribute("aria-activedescendant");
1806
- });
1709
+ }, els.listbox);
1807
1710
  let typeBuffer = "";
1808
1711
  let typeTimer = null;
1809
1712
  const onKey = (e) => {
@@ -1815,10 +1718,12 @@ function select(options) {
1815
1718
  highlightPrev();
1816
1719
  } else if (e.key === "Home") {
1817
1720
  e.preventDefault();
1818
- if (items.length > 0) setHighlightedIndex(0);
1721
+ const first = nextEnabled(-1, 1);
1722
+ if (first !== -1) setHighlightedIndex(first);
1819
1723
  } else if (e.key === "End") {
1820
1724
  e.preventDefault();
1821
- if (items.length > 0) setHighlightedIndex(items.length - 1);
1725
+ const last = nextEnabled(items.length, -1);
1726
+ if (last !== -1) setHighlightedIndex(last);
1822
1727
  } else if (e.key === "Enter" || e.key === " ") {
1823
1728
  if (highlightedIndex() >= 0) {
1824
1729
  e.preventDefault();
@@ -1836,8 +1741,11 @@ function select(options) {
1836
1741
  }
1837
1742
  };
1838
1743
  els.listbox.addEventListener("keydown", onKey);
1744
+ let tornDown = false;
1839
1745
  const teardown = () => {
1840
- boundSelects.delete(els.listbox);
1746
+ if (tornDown) return;
1747
+ tornDown = true;
1748
+ if (boundSelects.get(els.listbox) === teardown) boundSelects.delete(els.listbox);
1841
1749
  fxTeardown();
1842
1750
  els.listbox.removeEventListener("keydown", onKey);
1843
1751
  if (typeTimer !== null) clearTimeout(typeTimer);
@@ -1923,9 +1831,9 @@ function tooltip(options) {
1923
1831
  els.tooltip.id = id;
1924
1832
  const prevDescribedBy = els.trigger.getAttribute("aria-describedby");
1925
1833
  els.trigger.setAttribute("aria-describedby", prevDescribedBy ? `${prevDescribedBy} ${id}` : id);
1926
- const fxTeardown = effect(() => {
1834
+ const fxTeardown = domBinding(() => {
1927
1835
  els.tooltip.hidden = !isVisible();
1928
- });
1836
+ }, els.trigger);
1929
1837
  const onTriggerEnter = () => show();
1930
1838
  const onTriggerLeave = () => scheduleHide();
1931
1839
  const onTooltipEnter = () => {
@@ -1950,8 +1858,11 @@ function tooltip(options) {
1950
1858
  els.trigger.addEventListener("keydown", onKey);
1951
1859
  els.tooltip.addEventListener("pointerenter", onTooltipEnter);
1952
1860
  els.tooltip.addEventListener("pointerleave", onTooltipLeave);
1861
+ let tornDown = false;
1953
1862
  const teardown = () => {
1954
- boundTriggers.delete(els.trigger);
1863
+ if (tornDown) return;
1864
+ tornDown = true;
1865
+ if (boundTriggers.get(els.trigger) === teardown) boundTriggers.delete(els.trigger);
1955
1866
  fxTeardown();
1956
1867
  els.trigger.removeEventListener("pointerenter", onTriggerEnter);
1957
1868
  els.trigger.removeEventListener("pointerleave", onTriggerLeave);
@@ -1989,7 +1900,8 @@ function fileUpload(options) {
1989
1900
  const [isDragOver, setDragOver] = signal(false);
1990
1901
  function isAccepted(file) {
1991
1902
  if (!accept) return true;
1992
- const acceptedTypes = accept.split(",").map((t) => t.trim().toLowerCase());
1903
+ const acceptedTypes = accept.split(",").map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0);
1904
+ if (acceptedTypes.length === 0) return true;
1993
1905
  const fileName = file.name.toLowerCase();
1994
1906
  const fileType = file.type.toLowerCase();
1995
1907
  return acceptedTypes.some((pattern) => {
@@ -2018,16 +1930,17 @@ function fileUpload(options) {
2018
1930
  }
2019
1931
  validFiles.push(file);
2020
1932
  }
1933
+ const committed = multiple ? validFiles : validFiles.slice(-1);
2021
1934
  batch(() => {
2022
1935
  setErrors(newErrors);
2023
- if (validFiles.length > 0) {
1936
+ if (committed.length > 0) {
2024
1937
  if (multiple) {
2025
- setFiles((prev) => [...prev, ...validFiles]);
1938
+ setFiles((prev) => [...prev, ...committed]);
2026
1939
  } else {
2027
- setFiles([validFiles[validFiles.length - 1]]);
1940
+ setFiles(committed);
2028
1941
  }
2029
1942
  if (onFiles) {
2030
- onFiles(validFiles);
1943
+ onFiles(committed);
2031
1944
  }
2032
1945
  }
2033
1946
  });
@@ -2051,8 +1964,15 @@ function fileUpload(options) {
2051
1964
  if (existing) return existing;
2052
1965
  const id = createId("sibu-fileupload");
2053
1966
  const restore = [];
1967
+ const prevAccept = els.input.getAttribute("accept");
1968
+ const prevMultiple = els.input.hasAttribute("multiple");
2054
1969
  if (accept) els.input.accept = accept;
2055
1970
  els.input.multiple = multiple;
1971
+ restore.push(() => {
1972
+ if (prevAccept === null) els.input.removeAttribute("accept");
1973
+ else els.input.setAttribute("accept", prevAccept);
1974
+ els.input.multiple = prevMultiple;
1975
+ });
2056
1976
  let hintId = null;
2057
1977
  if (els.hint) {
2058
1978
  const assignedHintId = !els.hint.id;
@@ -2073,6 +1993,7 @@ function fileUpload(options) {
2073
1993
  if (els.errorRegion) {
2074
1994
  const prevRole = els.errorRegion.getAttribute("role");
2075
1995
  const prevLive = els.errorRegion.getAttribute("aria-live");
1996
+ const prevText = els.errorRegion.textContent;
2076
1997
  els.errorRegion.setAttribute("role", "status");
2077
1998
  els.errorRegion.setAttribute("aria-live", "polite");
2078
1999
  restore.push(() => {
@@ -2080,12 +2001,14 @@ function fileUpload(options) {
2080
2001
  else els.errorRegion.setAttribute("role", prevRole);
2081
2002
  if (prevLive === null) els.errorRegion.removeAttribute("aria-live");
2082
2003
  else els.errorRegion.setAttribute("aria-live", prevLive);
2004
+ els.errorRegion.textContent = prevText;
2083
2005
  });
2084
2006
  }
2085
2007
  if (els.dropZone) {
2086
2008
  const prevDzRole = els.dropZone.getAttribute("role");
2087
2009
  const prevDzLabel = els.dropZone.getAttribute("aria-label");
2088
2010
  const prevDzTabindex = els.dropZone.hasAttribute("tabindex") ? els.dropZone.getAttribute("tabindex") : null;
2011
+ const prevDzDragOver = els.dropZone.getAttribute("data-drag-over");
2089
2012
  els.dropZone.setAttribute("role", "button");
2090
2013
  els.dropZone.setAttribute("aria-label", "File drop zone \u2014 click or press Enter to browse");
2091
2014
  if (els.dropZone.tabIndex < 0) els.dropZone.tabIndex = 0;
@@ -2096,13 +2019,15 @@ function fileUpload(options) {
2096
2019
  else els.dropZone.setAttribute("aria-label", prevDzLabel);
2097
2020
  if (prevDzTabindex === null) els.dropZone.removeAttribute("tabindex");
2098
2021
  else els.dropZone.setAttribute("tabindex", prevDzTabindex);
2022
+ if (prevDzDragOver === null) els.dropZone.removeAttribute("data-drag-over");
2023
+ else els.dropZone.setAttribute("data-drag-over", prevDzDragOver);
2099
2024
  });
2100
2025
  }
2101
- const fxTeardown = effect(() => {
2026
+ const fxTeardown = domBinding(() => {
2102
2027
  const errs = errors();
2103
2028
  if (els.errorRegion) els.errorRegion.textContent = errs.join(". ");
2104
2029
  if (els.dropZone) els.dropZone.setAttribute("data-drag-over", isDragOver() ? "true" : "false");
2105
- });
2030
+ }, els.input);
2106
2031
  const onChange = () => {
2107
2032
  if (els.input.files) addFiles(els.input.files);
2108
2033
  };
@@ -2131,7 +2056,10 @@ function fileUpload(options) {
2131
2056
  els.dropZone.addEventListener("dragleave", onDragLeave);
2132
2057
  els.dropZone.addEventListener("drop", onDrop);
2133
2058
  }
2059
+ let tornDown = false;
2134
2060
  const teardown = () => {
2061
+ if (tornDown) return;
2062
+ tornDown = true;
2135
2063
  boundFileUploads.delete(els.input);
2136
2064
  fxTeardown();
2137
2065
  els.input.removeEventListener("change", onChange);
@@ -2178,12 +2106,15 @@ function stripHtml(html) {
2178
2106
  }
2179
2107
 
2180
2108
  // src/widgets/contentEditable.ts
2109
+ function toInertText(html) {
2110
+ return stripHtml(String(html)).replace(/<(?=[a-z!/?])/gi, "&lt;");
2111
+ }
2181
2112
  function contentEditable(element) {
2182
2113
  const [content, setContentInternal] = signal("");
2183
2114
  const [isFocused, setFocused] = signal(false);
2184
2115
  function setContent(input) {
2185
2116
  if (typeof input === "string") {
2186
- setContentInternal(input);
2117
+ setContentInternal(toInertText(input));
2187
2118
  return;
2188
2119
  }
2189
2120
  if (typeof input.text === "string") {
@@ -2192,7 +2123,7 @@ function contentEditable(element) {
2192
2123
  }
2193
2124
  if (typeof input.html === "string") {
2194
2125
  const shouldSanitize = input.sanitize !== false;
2195
- setContentInternal(shouldSanitize ? stripHtml(input.html) : input.html);
2126
+ setContentInternal(shouldSanitize ? toInertText(input.html) : input.html);
2196
2127
  return;
2197
2128
  }
2198
2129
  setContentInternal("");
@@ -2204,7 +2135,7 @@ function contentEditable(element) {
2204
2135
  const range = selection.getRangeAt(0);
2205
2136
  if (element && !element.contains(range.commonAncestorContainer)) return;
2206
2137
  const ancestor = range.commonAncestorContainer;
2207
- const existingWrap = findAncestorByTag(
2138
+ const existingWrap = findWrapperWithinEditor(
2208
2139
  ancestor instanceof HTMLElement ? ancestor : ancestor.parentElement,
2209
2140
  tagName
2210
2141
  );
@@ -2245,14 +2176,19 @@ function contentEditable(element) {
2245
2176
  selection.addRange(newRange);
2246
2177
  }
2247
2178
  }
2248
- function findAncestorByTag(el, tagName) {
2179
+ function findWrapperWithinEditor(el, tagName) {
2249
2180
  const upper = tagName.toUpperCase();
2250
2181
  while (el) {
2182
+ if (el === element || isEditingHost(el)) return null;
2251
2183
  if (el.tagName === upper) return el;
2252
2184
  el = el.parentElement;
2253
2185
  }
2254
2186
  return null;
2255
2187
  }
2188
+ function isEditingHost(el) {
2189
+ const attr = el.getAttribute("contenteditable");
2190
+ return attr !== null && attr.toLowerCase() !== "false";
2191
+ }
2256
2192
  function bold() {
2257
2193
  wrapSelection("strong");
2258
2194
  }
@@ -2388,15 +2324,36 @@ function datePicker(options) {
2388
2324
  els.grid.setAttribute("role", "grid");
2389
2325
  if (els.grid.tabIndex < 0) els.grid.tabIndex = 0;
2390
2326
  let pendingFocus = false;
2391
- const fxTeardown = effect(() => {
2327
+ const touchedCells = /* @__PURE__ */ new Map();
2328
+ const restoreAttr = (el, name, value) => {
2329
+ if (value === null) el.removeAttribute(name);
2330
+ else el.setAttribute(name, value);
2331
+ };
2332
+ const restoreCell = (cell, snap) => {
2333
+ restoreAttr(cell, "role", snap.role);
2334
+ restoreAttr(cell, "aria-selected", snap.selected);
2335
+ restoreAttr(cell, "aria-disabled", snap.disabled);
2336
+ restoreAttr(cell, "tabindex", snap.tabindex);
2337
+ };
2338
+ const fxTeardown = domBinding(() => {
2392
2339
  const sel = selectedDate();
2393
2340
  const view = viewDate();
2394
2341
  const days = daysInMonth();
2395
2342
  els.grid.setAttribute("aria-label", view.toLocaleDateString(void 0, { month: "long", year: "numeric" }));
2396
2343
  let viewCell = null;
2344
+ const displayed = /* @__PURE__ */ new Set();
2397
2345
  for (const d of days) {
2398
2346
  const cell = els.cell(d.date);
2399
2347
  if (!cell) continue;
2348
+ displayed.add(cell);
2349
+ if (!touchedCells.has(cell)) {
2350
+ touchedCells.set(cell, {
2351
+ role: cell.getAttribute("role"),
2352
+ selected: cell.getAttribute("aria-selected"),
2353
+ disabled: cell.getAttribute("aria-disabled"),
2354
+ tabindex: cell.getAttribute("tabindex")
2355
+ });
2356
+ }
2400
2357
  cell.setAttribute("role", "gridcell");
2401
2358
  cell.setAttribute("aria-selected", sel && isSameCalendarDay(sel, d.date) ? "true" : "false");
2402
2359
  if (d.isDisabled) cell.setAttribute("aria-disabled", "true");
@@ -2405,11 +2362,17 @@ function datePicker(options) {
2405
2362
  cell.tabIndex = isView ? 0 : -1;
2406
2363
  if (isView) viewCell = cell;
2407
2364
  }
2365
+ for (const [cell, snap] of touchedCells) {
2366
+ if (!displayed.has(cell)) {
2367
+ restoreCell(cell, snap);
2368
+ touchedCells.delete(cell);
2369
+ }
2370
+ }
2408
2371
  if (pendingFocus && viewCell && typeof viewCell.focus === "function") {
2409
2372
  pendingFocus = false;
2410
2373
  viewCell.focus();
2411
2374
  }
2412
- });
2375
+ }, els.grid);
2413
2376
  function isSameCalendarDay(a, b) {
2414
2377
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2415
2378
  }
@@ -2464,8 +2427,11 @@ function datePicker(options) {
2464
2427
  }
2465
2428
  };
2466
2429
  els.grid.addEventListener("keydown", onKey);
2430
+ let tornDown = false;
2467
2431
  const teardown = () => {
2468
- boundDatePickers.delete(els.grid);
2432
+ if (tornDown) return;
2433
+ tornDown = true;
2434
+ if (boundDatePickers.get(els.grid) === teardown) boundDatePickers.delete(els.grid);
2469
2435
  fxTeardown();
2470
2436
  els.grid.removeEventListener("keydown", onKey);
2471
2437
  if (prevGridRole === null) els.grid.removeAttribute("role");
@@ -2474,6 +2440,8 @@ function datePicker(options) {
2474
2440
  else els.grid.setAttribute("tabindex", prevGridTabIndex);
2475
2441
  if (prevGridLabel === null) els.grid.removeAttribute("aria-label");
2476
2442
  else els.grid.setAttribute("aria-label", prevGridLabel);
2443
+ for (const [cell, snap] of touchedCells) restoreCell(cell, snap);
2444
+ touchedCells.clear();
2477
2445
  };
2478
2446
  boundDatePickers.set(els.grid, teardown);
2479
2447
  return teardown;