sibujs 1.4.0 → 2.0.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 (190) hide show
  1. package/README.md +105 -119
  2. package/dist/browser.cjs +288 -80
  3. package/dist/browser.d.cts +19 -9
  4. package/dist/browser.d.ts +19 -9
  5. package/dist/browser.js +6 -6
  6. package/dist/build.cjs +1019 -313
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +15 -13
  10. package/dist/cdn.global.js +17 -16
  11. package/dist/chunk-2RA7SHDA.js +65 -0
  12. package/dist/chunk-2UPRY23K.js +80 -0
  13. package/dist/chunk-3JHCYHWN.js +125 -0
  14. package/dist/{chunk-ZWKZCBO6.js → chunk-3LR7GLWQ.js} +154 -33
  15. package/dist/{chunk-3AIRKM3B.js → chunk-3NSGB5JN.js} +115 -34
  16. package/dist/{chunk-3ARAQO7B.js → chunk-52YJLLRO.js} +29 -6
  17. package/dist/chunk-54EDRCEF.js +93 -0
  18. package/dist/chunk-7JDB7I65.js +1327 -0
  19. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  20. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  21. package/dist/{chunk-WR5D4EGH.js → chunk-GTBNNBJ6.js} +14 -2
  22. package/dist/chunk-HB24TBAF.js +121 -0
  23. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  24. package/dist/{chunk-JAKHTMQU.js → chunk-JA6667UN.js} +206 -46
  25. package/dist/{chunk-77L6NL3X.js → chunk-JXMMDLBY.js} +306 -183
  26. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  27. package/dist/{chunk-F3FA4F32.js → chunk-KLRMB5ZS.js} +135 -79
  28. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  29. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  30. package/dist/{chunk-TSOKIX5Z.js → chunk-MIUAXB7K.js} +126 -74
  31. package/dist/{chunk-QWZG56ET.js → chunk-ND2664SF.js} +558 -190
  32. package/dist/{chunk-JCI5M6U6.js → chunk-O2MNQFLP.js} +261 -79
  33. package/dist/{chunk-EWFVA3TJ.js → chunk-R73P76YZ.js} +1 -1
  34. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  35. package/dist/chunk-UCS6AMJ7.js +79 -0
  36. package/dist/{chunk-ZD6OAMTH.js → chunk-VLPPXTYG.js} +90 -35
  37. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  38. package/dist/{contracts-xo5ckdRP.d.cts → contracts-ey_Qh8ef.d.cts} +7 -8
  39. package/dist/{contracts-xo5ckdRP.d.ts → contracts-ey_Qh8ef.d.ts} +7 -8
  40. package/dist/{customElement-D2DJp_xn.d.cts → customElement-CPfIrbvg.d.cts} +18 -9
  41. package/dist/{customElement-D2DJp_xn.d.ts → customElement-CPfIrbvg.d.ts} +18 -9
  42. package/dist/data.cjs +452 -100
  43. package/dist/data.d.cts +20 -2
  44. package/dist/data.d.ts +20 -2
  45. package/dist/data.js +11 -9
  46. package/dist/devtools.cjs +535 -247
  47. package/dist/devtools.d.cts +1 -1
  48. package/dist/devtools.d.ts +1 -1
  49. package/dist/devtools.js +34 -30
  50. package/dist/ecosystem.cjs +499 -143
  51. package/dist/ecosystem.d.cts +13 -11
  52. package/dist/ecosystem.d.ts +13 -11
  53. package/dist/ecosystem.js +12 -11
  54. package/dist/extras.cjs +3639 -1629
  55. package/dist/extras.d.cts +11 -11
  56. package/dist/extras.d.ts +11 -11
  57. package/dist/extras.js +58 -45
  58. package/dist/index.cjs +1023 -313
  59. package/dist/index.d.cts +128 -55
  60. package/dist/index.d.ts +128 -55
  61. package/dist/index.js +28 -16
  62. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  63. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  64. package/dist/motion.cjs +90 -36
  65. package/dist/motion.d.cts +1 -1
  66. package/dist/motion.d.ts +1 -1
  67. package/dist/motion.js +4 -4
  68. package/dist/patterns.cjs +414 -81
  69. package/dist/patterns.d.cts +53 -20
  70. package/dist/patterns.d.ts +53 -20
  71. package/dist/patterns.js +7 -7
  72. package/dist/performance.cjs +364 -108
  73. package/dist/performance.d.cts +29 -17
  74. package/dist/performance.d.ts +29 -17
  75. package/dist/performance.js +13 -6
  76. package/dist/plugin-D30wlGW5.d.cts +71 -0
  77. package/dist/plugin-D30wlGW5.d.ts +71 -0
  78. package/dist/plugins.cjs +652 -271
  79. package/dist/plugins.d.cts +13 -6
  80. package/dist/plugins.d.ts +13 -6
  81. package/dist/plugins.js +116 -50
  82. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  83. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  84. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  85. package/dist/ssr.cjs +648 -219
  86. package/dist/ssr.d.cts +27 -7
  87. package/dist/ssr.d.ts +27 -7
  88. package/dist/ssr.js +12 -11
  89. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.cts} +9 -1
  90. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.ts} +9 -1
  91. package/dist/testing.cjs +252 -63
  92. package/dist/testing.d.cts +17 -4
  93. package/dist/testing.d.ts +17 -4
  94. package/dist/testing.js +100 -44
  95. package/dist/ui.cjs +576 -168
  96. package/dist/ui.d.cts +13 -16
  97. package/dist/ui.d.ts +13 -16
  98. package/dist/ui.js +20 -17
  99. package/dist/widgets.cjs +1001 -93
  100. package/dist/widgets.d.cts +104 -2
  101. package/dist/widgets.d.ts +104 -2
  102. package/dist/widgets.js +9 -7
  103. package/package.json +8 -2
  104. package/dist/chunk-32DY64NT.js +0 -282
  105. package/dist/chunk-3CRQALYP.js +0 -877
  106. package/dist/chunk-4EI4AG32.js +0 -482
  107. package/dist/chunk-4MYMUBRS.js +0 -21
  108. package/dist/chunk-6HLLIF3K.js +0 -398
  109. package/dist/chunk-6LSNVCS2.js +0 -937
  110. package/dist/chunk-6SA3QQES.js +0 -61
  111. package/dist/chunk-7BF6TK55.js +0 -1097
  112. package/dist/chunk-7TQKR4PP.js +0 -294
  113. package/dist/chunk-7V26P53V.js +0 -712
  114. package/dist/chunk-AZ3ISID5.js +0 -298
  115. package/dist/chunk-B7SWRFUT.js +0 -332
  116. package/dist/chunk-BGN5ZMP4.js +0 -26
  117. package/dist/chunk-BTU3TJDS.js +0 -365
  118. package/dist/chunk-BW3WT46K.js +0 -937
  119. package/dist/chunk-C6KFWOFV.js +0 -616
  120. package/dist/chunk-CHF5OHIA.js +0 -61
  121. package/dist/chunk-CHJ27IGK.js +0 -26
  122. package/dist/chunk-CMBFNA7L.js +0 -27
  123. package/dist/chunk-DAHRH4ON.js +0 -331
  124. package/dist/chunk-DKOHBI74.js +0 -924
  125. package/dist/chunk-DTCOOBMX.js +0 -725
  126. package/dist/chunk-EBGIRKQY.js +0 -616
  127. package/dist/chunk-EUZND3CB.js +0 -27
  128. package/dist/chunk-EVCZO745.js +0 -365
  129. package/dist/chunk-FGOEVHY3.js +0 -60
  130. package/dist/chunk-G3BOQPVO.js +0 -365
  131. package/dist/chunk-GCOK2LC3.js +0 -282
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-K5ZUMYVS.js +0 -89
  134. package/dist/chunk-KQPDEVVS.js +0 -398
  135. package/dist/chunk-L6JRBDNS.js +0 -60
  136. package/dist/chunk-LA6KQEDU.js +0 -712
  137. package/dist/chunk-MDVXJWFN.js +0 -304
  138. package/dist/chunk-MEZVEBPN.js +0 -2008
  139. package/dist/chunk-MK4ERFYL.js +0 -2249
  140. package/dist/chunk-MLKGABMK.js +0 -9
  141. package/dist/chunk-MQ5GOYPH.js +0 -2249
  142. package/dist/chunk-N6IZB6KJ.js +0 -567
  143. package/dist/chunk-NEKUBFPT.js +0 -60
  144. package/dist/chunk-NHUC2QWH.js +0 -282
  145. package/dist/chunk-NMRUZALC.js +0 -1097
  146. package/dist/chunk-NYVAC6P5.js +0 -37
  147. package/dist/chunk-OF7UZIVB.js +0 -725
  148. package/dist/chunk-P6W3STU4.js +0 -2249
  149. package/dist/chunk-PBHF5WKN.js +0 -616
  150. package/dist/chunk-PTQJDMRT.js +0 -146
  151. package/dist/chunk-PZEGYCF5.js +0 -61
  152. package/dist/chunk-QBMDLBU2.js +0 -975
  153. package/dist/chunk-RQGQSLQK.js +0 -725
  154. package/dist/chunk-SDLZDHKP.js +0 -107
  155. package/dist/chunk-TNQWPPE6.js +0 -37
  156. package/dist/chunk-UHNL42EF.js +0 -2730
  157. package/dist/chunk-UNXCEF6S.js +0 -21
  158. package/dist/chunk-V2XTI523.js +0 -347
  159. package/dist/chunk-VAU366PN.js +0 -2241
  160. package/dist/chunk-VMVDTCXB.js +0 -712
  161. package/dist/chunk-VRW3FULF.js +0 -725
  162. package/dist/chunk-WADYRCO2.js +0 -304
  163. package/dist/chunk-WILQZRO4.js +0 -282
  164. package/dist/chunk-WUHJISPP.js +0 -298
  165. package/dist/chunk-XYU6TZOW.js +0 -182
  166. package/dist/chunk-Y6GP4QGG.js +0 -276
  167. package/dist/chunk-YECR7UIA.js +0 -347
  168. package/dist/chunk-YUTWTI4B.js +0 -654
  169. package/dist/chunk-Z65KYU7I.js +0 -26
  170. package/dist/chunk-Z6POF5YC.js +0 -975
  171. package/dist/chunk-ZBJP6WFL.js +0 -482
  172. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  173. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  174. package/dist/contracts-DOrhwbke.d.cts +0 -245
  175. package/dist/contracts-DOrhwbke.d.ts +0 -245
  176. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  177. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  178. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  179. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  180. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  181. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  182. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  183. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  184. package/dist/ssr-3RXHP5ES.js +0 -38
  185. package/dist/ssr-6GIMY5MX.js +0 -38
  186. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  187. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  188. package/dist/ssr-WKUPVSSK.js +0 -36
  189. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  190. package/dist/tagFactory-Dl8QCLga.d.ts +0 -23
package/dist/widgets.cjs CHANGED
@@ -39,12 +39,12 @@ function isDev() {
39
39
  var _isDev = isDev();
40
40
  function devAssert(condition, message) {
41
41
  if (_isDev && !condition) {
42
- throw new Error(`[Sibu] ${message}`);
42
+ throw new Error(`[SibuJS] ${message}`);
43
43
  }
44
44
  }
45
45
  function devWarn(message) {
46
46
  if (_isDev) {
47
- console.warn(`[Sibu] ${message}`);
47
+ console.warn(`[SibuJS] ${message}`);
48
48
  }
49
49
  }
50
50
 
@@ -54,11 +54,11 @@ var subscriberStack = new Array(32);
54
54
  var stackCapacity = 32;
55
55
  var stackTop = -1;
56
56
  var currentSubscriber = null;
57
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
58
57
  var SUBS = "__s";
59
58
  var notifyDepth = 0;
60
59
  var pendingQueue = [];
61
60
  var pendingSet = /* @__PURE__ */ new Set();
61
+ var propagateStack = [];
62
62
  function safeInvoke(sub) {
63
63
  try {
64
64
  sub();
@@ -67,6 +67,15 @@ function safeInvoke(sub) {
67
67
  }
68
68
  }
69
69
  var trackingSuspended = false;
70
+ function retrack(effectFn, subscriber) {
71
+ const prev = currentSubscriber;
72
+ currentSubscriber = subscriber;
73
+ try {
74
+ effectFn();
75
+ } finally {
76
+ currentSubscriber = prev;
77
+ }
78
+ }
70
79
  function track(effectFn, subscriber) {
71
80
  if (!subscriber) subscriber = effectFn;
72
81
  cleanup(subscriber);
@@ -105,7 +114,6 @@ function recordDependency(signal2) {
105
114
  let subs = signal2[SUBS];
106
115
  if (!subs) {
107
116
  subs = /* @__PURE__ */ new Set();
108
- signalSubscribers.set(signal2, subs);
109
117
  signal2[SUBS] = subs;
110
118
  }
111
119
  subs.add(currentSubscriber);
@@ -127,57 +135,71 @@ function queueSignalNotification(signal2) {
127
135
  }
128
136
  }
129
137
  }
138
+ var maxDrainIterations = 1e5;
130
139
  function drainNotificationQueue() {
131
140
  if (notifyDepth > 0) return;
132
141
  notifyDepth++;
133
142
  try {
134
143
  let i = 0;
135
144
  while (i < pendingQueue.length) {
145
+ if (i >= maxDrainIterations) {
146
+ if (typeof console !== "undefined") {
147
+ console.error(
148
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
149
+ );
150
+ }
151
+ break;
152
+ }
136
153
  safeInvoke(pendingQueue[i]);
137
154
  i++;
138
155
  }
139
156
  } finally {
140
- pendingQueue.length = 0;
141
- pendingSet.clear();
142
157
  notifyDepth--;
158
+ if (notifyDepth === 0) {
159
+ pendingQueue.length = 0;
160
+ pendingSet.clear();
161
+ }
143
162
  }
144
163
  }
145
164
  function propagateDirty(sub) {
146
165
  sub();
147
- let sig = sub._sig;
148
- while (sig) {
166
+ const rootSig = sub._sig;
167
+ if (!rootSig) return;
168
+ const stack = propagateStack;
169
+ const baseLen = stack.length;
170
+ stack.push(rootSig);
171
+ while (stack.length > baseLen) {
172
+ const sig = stack.pop();
149
173
  const first = sig.__f;
150
174
  if (first) {
151
175
  if (first._c) {
152
176
  const nSig = first._sig;
153
- nSig._d = true;
154
- sig = nSig;
155
- continue;
156
- }
157
- if (!pendingSet.has(first)) {
177
+ if (!nSig._d) {
178
+ nSig._d = true;
179
+ stack.push(nSig);
180
+ }
181
+ } else if (!pendingSet.has(first)) {
158
182
  pendingSet.add(first);
159
183
  pendingQueue.push(first);
160
184
  }
161
- break;
185
+ continue;
162
186
  }
163
187
  const subs = sig[SUBS];
164
- if (!subs) break;
165
- let nextSig;
188
+ if (!subs) continue;
166
189
  for (const s of subs) {
167
190
  if (s._c) {
168
- s();
169
191
  const nSig = s._sig;
170
- if (nSig && !nextSig) {
171
- nextSig = nSig;
172
- } else if (nSig) {
173
- propagateDirty(s);
192
+ if (nSig && !nSig._d) {
193
+ nSig._d = true;
194
+ stack.push(nSig);
195
+ } else if (!nSig) {
196
+ s();
174
197
  }
175
198
  } else if (!pendingSet.has(s)) {
176
199
  pendingSet.add(s);
177
200
  pendingQueue.push(s);
178
201
  }
179
202
  }
180
- sig = nextSig;
181
203
  }
182
204
  }
183
205
  function notifySubscribers(signal2) {
@@ -201,13 +223,23 @@ function notifySubscribers(signal2) {
201
223
  }
202
224
  let i = 0;
203
225
  while (i < pendingQueue.length) {
226
+ if (i >= maxDrainIterations) {
227
+ if (typeof console !== "undefined") {
228
+ console.error(
229
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
230
+ );
231
+ }
232
+ break;
233
+ }
204
234
  safeInvoke(pendingQueue[i]);
205
235
  i++;
206
236
  }
207
237
  } finally {
208
- pendingQueue.length = 0;
209
- pendingSet.clear();
210
238
  notifyDepth--;
239
+ if (notifyDepth === 0) {
240
+ pendingQueue.length = 0;
241
+ pendingSet.clear();
242
+ }
211
243
  }
212
244
  return;
213
245
  }
@@ -227,30 +259,48 @@ function notifySubscribers(signal2) {
227
259
  notifyDepth++;
228
260
  try {
229
261
  let directCount = 0;
262
+ let hasComputedSub = false;
230
263
  for (const sub of subs) {
264
+ if (sub._c) hasComputedSub = true;
231
265
  pendingQueue[directCount++] = sub;
232
266
  }
233
- for (let i2 = 0; i2 < directCount; i2++) {
234
- if (pendingQueue[i2]._c) {
235
- propagateDirty(pendingQueue[i2]);
267
+ if (!hasComputedSub) {
268
+ for (let i2 = 0; i2 < directCount; i2++) {
269
+ safeInvoke(pendingQueue[i2]);
236
270
  }
237
- }
238
- for (let i2 = 0; i2 < directCount; i2++) {
239
- if (!pendingQueue[i2]._c) {
240
- if (!pendingSet.has(pendingQueue[i2])) {
241
- safeInvoke(pendingQueue[i2]);
271
+ } else {
272
+ for (let i2 = 0; i2 < directCount; i2++) {
273
+ if (pendingQueue[i2]._c) {
274
+ propagateDirty(pendingQueue[i2]);
275
+ }
276
+ }
277
+ for (let i2 = 0; i2 < directCount; i2++) {
278
+ const sub = pendingQueue[i2];
279
+ if (!sub._c && !pendingSet.has(sub)) {
280
+ pendingSet.add(sub);
281
+ safeInvoke(sub);
242
282
  }
243
283
  }
244
284
  }
245
285
  let i = directCount;
246
286
  while (i < pendingQueue.length) {
287
+ if (i - directCount >= maxDrainIterations) {
288
+ if (typeof console !== "undefined") {
289
+ console.error(
290
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
291
+ );
292
+ }
293
+ break;
294
+ }
247
295
  safeInvoke(pendingQueue[i]);
248
296
  i++;
249
297
  }
250
298
  } finally {
251
- pendingQueue.length = 0;
252
- pendingSet.clear();
253
299
  notifyDepth--;
300
+ if (notifyDepth === 0) {
301
+ pendingQueue.length = 0;
302
+ pendingSet.clear();
303
+ }
254
304
  }
255
305
  }
256
306
  function cleanup(subscriber) {
@@ -261,7 +311,9 @@ function cleanup(subscriber) {
261
311
  if (subs) {
262
312
  subs.delete(subscriber);
263
313
  if (singleDep.__f === subscriber) {
264
- singleDep.__f = void 0;
314
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
315
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
316
+ singleDep.__f = subs.values().next().value;
265
317
  }
266
318
  }
267
319
  sub._dep = void 0;
@@ -274,7 +326,9 @@ function cleanup(subscriber) {
274
326
  if (subs) {
275
327
  subs.delete(subscriber);
276
328
  if (signal2.__f === subscriber) {
277
- signal2.__f = void 0;
329
+ signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
330
+ } else if (subs.size === 1 && signal2.__f === void 0) {
331
+ signal2.__f = subs.values().next().value;
278
332
  }
279
333
  }
280
334
  }
@@ -285,6 +339,7 @@ function cleanup(subscriber) {
285
339
  function derived(getter, options) {
286
340
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
287
341
  const debugName = options?.name;
342
+ const equals = options?.equals;
288
343
  const cs = {};
289
344
  cs._d = false;
290
345
  cs._g = getter;
@@ -295,25 +350,56 @@ function derived(getter, options) {
295
350
  markDirty._c = 1;
296
351
  markDirty._sig = cs;
297
352
  track(() => {
298
- cs._d = false;
299
- cs._v = getter();
353
+ let threw = true;
354
+ try {
355
+ cs._v = getter();
356
+ cs._d = false;
357
+ threw = false;
358
+ } finally {
359
+ if (threw) cs._d = true;
360
+ }
300
361
  }, markDirty);
301
362
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
363
+ let evaluating = false;
302
364
  function computedGetter() {
365
+ if (evaluating) {
366
+ throw new Error(
367
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
368
+ );
369
+ }
303
370
  if (trackingSuspended) {
304
371
  if (cs._d) {
305
- cs._d = false;
306
- cs._v = getter();
372
+ evaluating = true;
373
+ let threw = true;
374
+ try {
375
+ retrack(() => {
376
+ cs._v = getter();
377
+ cs._d = false;
378
+ threw = false;
379
+ }, markDirty);
380
+ } finally {
381
+ evaluating = false;
382
+ if (threw) cs._d = true;
383
+ }
307
384
  }
308
385
  return cs._v;
309
386
  }
310
387
  recordDependency(cs);
311
388
  if (cs._d) {
312
389
  const oldValue = cs._v;
313
- track(() => {
314
- cs._d = false;
315
- cs._v = getter();
316
- }, markDirty);
390
+ evaluating = true;
391
+ let threw = true;
392
+ try {
393
+ retrack(() => {
394
+ const next = getter();
395
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
396
+ cs._d = false;
397
+ threw = false;
398
+ }, markDirty);
399
+ } finally {
400
+ evaluating = false;
401
+ if (threw) cs._d = true;
402
+ }
317
403
  if (hook && oldValue !== cs._v) {
318
404
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
319
405
  }
@@ -329,6 +415,121 @@ function derived(getter, options) {
329
415
  return computedGetter;
330
416
  }
331
417
 
418
+ // src/core/ssr-context.ts
419
+ var als = null;
420
+ try {
421
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
422
+ const req = Function("return typeof require==='function'?require:null")();
423
+ if (req) {
424
+ const mod = req("node:async_hooks");
425
+ als = new mod.AsyncLocalStorage();
426
+ }
427
+ }
428
+ } catch {
429
+ als = null;
430
+ }
431
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
432
+ function getSSRStore() {
433
+ if (als) {
434
+ const s = als.getStore();
435
+ if (s) return s;
436
+ }
437
+ return fallbackStore;
438
+ }
439
+ function isSSR() {
440
+ return getSSRStore().ssr;
441
+ }
442
+
443
+ // src/core/signals/effect.ts
444
+ var _g = globalThis;
445
+ function effect(effectFn, options) {
446
+ devAssert(typeof effectFn === "function", "effect: argument must be a function.");
447
+ if (isSSR()) return () => {
448
+ };
449
+ const onError = options?.onError;
450
+ let userCleanups = [];
451
+ const onCleanup = (fn) => {
452
+ userCleanups.push(fn);
453
+ };
454
+ const runUserCleanups = () => {
455
+ if (userCleanups.length === 0) return;
456
+ const list = userCleanups;
457
+ userCleanups = [];
458
+ for (let i = list.length - 1; i >= 0; i--) {
459
+ try {
460
+ list[i]();
461
+ } catch (err) {
462
+ if (typeof console !== "undefined") {
463
+ console.warn("[SibuJS effect] onCleanup threw:", err);
464
+ }
465
+ }
466
+ }
467
+ };
468
+ const invokeBody = () => effectFn(onCleanup);
469
+ const wrappedFn = onError ? () => {
470
+ try {
471
+ invokeBody();
472
+ } catch (err) {
473
+ onError(err);
474
+ }
475
+ } : invokeBody;
476
+ let cleanupHandle = () => {
477
+ };
478
+ let running = false;
479
+ const subscriber = () => {
480
+ if (running) {
481
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
482
+ console.warn(
483
+ "[SibuJS] effect re-entered itself while running \u2014 the triggering update will be ignored. Wrap mutual writes in `batch()` or split the effect to avoid this."
484
+ );
485
+ }
486
+ return;
487
+ }
488
+ running = true;
489
+ try {
490
+ runUserCleanups();
491
+ cleanupHandle();
492
+ cleanupHandle = track(wrappedFn, subscriber);
493
+ } finally {
494
+ running = false;
495
+ }
496
+ };
497
+ running = true;
498
+ try {
499
+ cleanupHandle = track(wrappedFn, subscriber);
500
+ } finally {
501
+ running = false;
502
+ }
503
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
504
+ if (hook) hook.emit("effect:create", { effectFn });
505
+ let disposed = false;
506
+ return () => {
507
+ if (disposed) return;
508
+ disposed = true;
509
+ const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
510
+ if (h) {
511
+ try {
512
+ h.emit("effect:destroy", { effectFn });
513
+ } catch {
514
+ }
515
+ }
516
+ try {
517
+ runUserCleanups();
518
+ } catch (err) {
519
+ if (typeof console !== "undefined") {
520
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
521
+ }
522
+ }
523
+ try {
524
+ cleanupHandle();
525
+ } catch (err) {
526
+ if (typeof console !== "undefined") {
527
+ console.warn("[SibuJS effect] dispose threw:", err);
528
+ }
529
+ }
530
+ };
531
+ }
532
+
332
533
  // src/reactivity/batch.ts
333
534
  var batchDepth = 0;
334
535
  var pendingSignals = /* @__PURE__ */ new Set();
@@ -349,15 +550,18 @@ function enqueueBatchedSignal(signal2) {
349
550
  return true;
350
551
  }
351
552
  function flushBatch() {
352
- for (const signal2 of pendingSignals) {
353
- queueSignalNotification(signal2);
553
+ try {
554
+ for (const signal2 of pendingSignals) {
555
+ queueSignalNotification(signal2);
556
+ }
557
+ } finally {
558
+ pendingSignals.clear();
354
559
  }
355
- pendingSignals.clear();
356
560
  drainNotificationQueue();
357
561
  }
358
562
 
359
563
  // src/core/signals/signal.ts
360
- var _g = globalThis;
564
+ var _g2 = globalThis;
361
565
  var _isDev3 = isDev();
362
566
  function signal(initial, options) {
363
567
  const state = { value: initial };
@@ -378,7 +582,7 @@ function signal(initial, options) {
378
582
  if (_isDev3) {
379
583
  const oldValue = state.value;
380
584
  state.value = newValue;
381
- const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
585
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
382
586
  if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue, newValue });
383
587
  } else {
384
588
  state.value = newValue;
@@ -388,18 +592,12 @@ function signal(initial, options) {
388
592
  }
389
593
  }
390
594
  if (_isDev3) {
391
- const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
595
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
392
596
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
393
597
  }
394
598
  return [get, set];
395
599
  }
396
600
 
397
- // src/core/ssr-context.ts
398
- var ssrMode = false;
399
- function isSSR() {
400
- return ssrMode;
401
- }
402
-
403
601
  // src/core/signals/watch.ts
404
602
  function watch(getter, callback) {
405
603
  devAssert(typeof getter === "function", "watch: first argument must be a getter function.");
@@ -425,6 +623,8 @@ function watch(getter, callback) {
425
623
  }
426
624
 
427
625
  // src/widgets/Combobox.ts
626
+ var comboboxIdCounter = 0;
627
+ var boundComboboxes = /* @__PURE__ */ new WeakMap();
428
628
  function combobox(options) {
429
629
  const { items, filterFn, itemToString } = options;
430
630
  const defaultFilterFn = (item, q) => {
@@ -481,6 +681,92 @@ function combobox(options) {
481
681
  function close() {
482
682
  setIsOpen(false);
483
683
  }
684
+ function bind(els) {
685
+ const existing = boundComboboxes.get(els.input);
686
+ if (existing) return existing;
687
+ const listboxId = `sibu-combobox-listbox-${++comboboxIdCounter}`;
688
+ els.listbox.id = listboxId;
689
+ els.listbox.setAttribute("role", "listbox");
690
+ els.input.setAttribute("role", "combobox");
691
+ els.input.setAttribute("aria-autocomplete", "list");
692
+ els.input.setAttribute("aria-controls", listboxId);
693
+ const fxTeardown = effect(() => {
694
+ const open2 = isOpen();
695
+ els.input.setAttribute("aria-expanded", open2 ? "true" : "false");
696
+ els.listbox.hidden = !open2;
697
+ const idx = highlightedIndex();
698
+ const filtered = filteredItems();
699
+ let activeId = "";
700
+ for (let i = 0; i < filtered.length; i++) {
701
+ const optEl = els.option(filtered[i], i);
702
+ if (!optEl) continue;
703
+ if (!optEl.id) optEl.id = `${listboxId}-opt-${i}`;
704
+ optEl.setAttribute("role", "option");
705
+ const isHighlighted = i === idx;
706
+ optEl.setAttribute("aria-selected", isHighlighted ? "true" : "false");
707
+ if (isHighlighted) activeId = optEl.id;
708
+ }
709
+ if (activeId) els.input.setAttribute("aria-activedescendant", activeId);
710
+ else els.input.removeAttribute("aria-activedescendant");
711
+ });
712
+ const onInput = () => {
713
+ setQuery(els.input.value);
714
+ open();
715
+ };
716
+ const onKey = (e) => {
717
+ if (e.key === "ArrowDown") {
718
+ e.preventDefault();
719
+ if (!isOpen()) open();
720
+ highlightNext();
721
+ } else if (e.key === "ArrowUp") {
722
+ e.preventDefault();
723
+ if (!isOpen()) open();
724
+ highlightPrev();
725
+ } else if (e.key === "Enter") {
726
+ if (highlightedIndex() >= 0) {
727
+ e.preventDefault();
728
+ selectHighlighted();
729
+ }
730
+ } else if (e.key === "Escape") {
731
+ e.preventDefault();
732
+ close();
733
+ } else if (e.key === "Home") {
734
+ e.preventDefault();
735
+ if (filteredItems().length > 0) setHighlightedIndex(0);
736
+ } else if (e.key === "End") {
737
+ e.preventDefault();
738
+ const len = filteredItems().length;
739
+ if (len > 0) setHighlightedIndex(len - 1);
740
+ }
741
+ };
742
+ let blurTimer = null;
743
+ const onFocus = () => open();
744
+ const onBlur = () => {
745
+ if (blurTimer !== null) clearTimeout(blurTimer);
746
+ blurTimer = setTimeout(() => {
747
+ blurTimer = null;
748
+ if (document.activeElement !== els.input) close();
749
+ }, 100);
750
+ };
751
+ els.input.addEventListener("input", onInput);
752
+ els.input.addEventListener("keydown", onKey);
753
+ els.input.addEventListener("focus", onFocus);
754
+ els.input.addEventListener("blur", onBlur);
755
+ const teardown = () => {
756
+ boundComboboxes.delete(els.input);
757
+ fxTeardown();
758
+ els.input.removeEventListener("input", onInput);
759
+ els.input.removeEventListener("keydown", onKey);
760
+ els.input.removeEventListener("focus", onFocus);
761
+ els.input.removeEventListener("blur", onBlur);
762
+ if (blurTimer !== null) {
763
+ clearTimeout(blurTimer);
764
+ blurTimer = null;
765
+ }
766
+ };
767
+ boundComboboxes.set(els.input, teardown);
768
+ return teardown;
769
+ }
484
770
  return {
485
771
  query,
486
772
  setQuery,
@@ -493,11 +779,13 @@ function combobox(options) {
493
779
  selectHighlighted,
494
780
  isOpen,
495
781
  open,
496
- close
782
+ close,
783
+ bind
497
784
  };
498
785
  }
499
786
 
500
787
  // src/widgets/Tabs.ts
788
+ var boundTablists = /* @__PURE__ */ new WeakMap();
501
789
  function tabs(options) {
502
790
  const { tabs: tabDefs, defaultTab } = options;
503
791
  const initialTab = defaultTab ?? tabDefs.find((t) => !t.disabled)?.id ?? tabDefs[0]?.id ?? "";
@@ -544,6 +832,118 @@ function tabs(options) {
544
832
  function isActive(id) {
545
833
  return activeTab() === id;
546
834
  }
835
+ function bind(els) {
836
+ const existing = boundTablists.get(els.tablist);
837
+ if (existing) return existing;
838
+ const restore = [];
839
+ const prevTablistRole = els.tablist.getAttribute("role");
840
+ els.tablist.setAttribute("role", "tablist");
841
+ restore.push(() => {
842
+ if (prevTablistRole === null) els.tablist.removeAttribute("role");
843
+ else els.tablist.setAttribute("role", prevTablistRole);
844
+ });
845
+ for (const def of tabDefs) {
846
+ const tabEl = els.tabs[def.id];
847
+ if (!tabEl) continue;
848
+ const prevRole = tabEl.getAttribute("role");
849
+ const prevId = tabEl.id;
850
+ const prevDisabled = tabEl.getAttribute("aria-disabled");
851
+ const prevControls = tabEl.getAttribute("aria-controls");
852
+ tabEl.setAttribute("role", "tab");
853
+ tabEl.setAttribute("id", `sibu-tab-${def.id}`);
854
+ if (def.disabled) tabEl.setAttribute("aria-disabled", "true");
855
+ const panelEl = els.panels?.[def.id];
856
+ let prevPanelRole = null;
857
+ let prevPanelId = "";
858
+ let prevPanelLabelledBy = null;
859
+ if (panelEl) {
860
+ prevPanelRole = panelEl.getAttribute("role");
861
+ prevPanelId = panelEl.id;
862
+ prevPanelLabelledBy = panelEl.getAttribute("aria-labelledby");
863
+ panelEl.setAttribute("role", "tabpanel");
864
+ panelEl.setAttribute("id", `sibu-tabpanel-${def.id}`);
865
+ panelEl.setAttribute("aria-labelledby", `sibu-tab-${def.id}`);
866
+ tabEl.setAttribute("aria-controls", `sibu-tabpanel-${def.id}`);
867
+ }
868
+ restore.push(() => {
869
+ if (prevRole === null) tabEl.removeAttribute("role");
870
+ else tabEl.setAttribute("role", prevRole);
871
+ if (prevId === "") tabEl.removeAttribute("id");
872
+ else tabEl.id = prevId;
873
+ if (prevDisabled === null) tabEl.removeAttribute("aria-disabled");
874
+ else tabEl.setAttribute("aria-disabled", prevDisabled);
875
+ if (prevControls === null) tabEl.removeAttribute("aria-controls");
876
+ else tabEl.setAttribute("aria-controls", prevControls);
877
+ tabEl.removeAttribute("aria-selected");
878
+ tabEl.removeAttribute("tabindex");
879
+ if (panelEl) {
880
+ if (prevPanelRole === null) panelEl.removeAttribute("role");
881
+ else panelEl.setAttribute("role", prevPanelRole);
882
+ if (prevPanelId === "") panelEl.removeAttribute("id");
883
+ else panelEl.id = prevPanelId;
884
+ if (prevPanelLabelledBy === null) panelEl.removeAttribute("aria-labelledby");
885
+ else panelEl.setAttribute("aria-labelledby", prevPanelLabelledBy);
886
+ }
887
+ });
888
+ }
889
+ const fxTeardown = effect(() => {
890
+ const active = activeTab();
891
+ for (const def of tabDefs) {
892
+ const tabEl = els.tabs[def.id];
893
+ if (!tabEl) continue;
894
+ const isAct = def.id === active;
895
+ tabEl.setAttribute("aria-selected", isAct ? "true" : "false");
896
+ tabEl.tabIndex = isAct ? 0 : -1;
897
+ const panelEl = els.panels?.[def.id];
898
+ if (panelEl) panelEl.hidden = !isAct;
899
+ }
900
+ });
901
+ const onKey = (e) => {
902
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
903
+ e.preventDefault();
904
+ nextTab();
905
+ els.tabs[activeTab()]?.focus();
906
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
907
+ e.preventDefault();
908
+ prevTab();
909
+ els.tabs[activeTab()]?.focus();
910
+ } else if (e.key === "Home") {
911
+ e.preventDefault();
912
+ const first = tabDefs.find((t) => !t.disabled);
913
+ if (first) {
914
+ setActiveTabState(first.id);
915
+ els.tabs[first.id]?.focus();
916
+ }
917
+ } else if (e.key === "End") {
918
+ e.preventDefault();
919
+ for (let i = tabDefs.length - 1; i >= 0; i--) {
920
+ if (!tabDefs[i].disabled) {
921
+ setActiveTabState(tabDefs[i].id);
922
+ els.tabs[tabDefs[i].id]?.focus();
923
+ break;
924
+ }
925
+ }
926
+ }
927
+ };
928
+ els.tablist.addEventListener("keydown", onKey);
929
+ const clickHandlers = [];
930
+ for (const def of tabDefs) {
931
+ const tabEl = els.tabs[def.id];
932
+ if (!tabEl) continue;
933
+ const fn = () => setActiveTab(def.id);
934
+ tabEl.addEventListener("click", fn);
935
+ clickHandlers.push({ el: tabEl, fn });
936
+ }
937
+ const teardown = () => {
938
+ boundTablists.delete(els.tablist);
939
+ fxTeardown();
940
+ els.tablist.removeEventListener("keydown", onKey);
941
+ for (const { el, fn } of clickHandlers) el.removeEventListener("click", fn);
942
+ for (const r of restore) r();
943
+ };
944
+ boundTablists.set(els.tablist, teardown);
945
+ return teardown;
946
+ }
547
947
  return {
548
948
  activeTab,
549
949
  setActiveTab,
@@ -551,11 +951,13 @@ function tabs(options) {
551
951
  nextTab,
552
952
  prevTab,
553
953
  /** Reactive check — use inside class/nodes bindings for per-tab reactivity */
554
- isActive
954
+ isActive,
955
+ bind
555
956
  };
556
957
  }
557
958
 
558
959
  // src/widgets/Accordion.ts
960
+ var boundAccordions = /* @__PURE__ */ new WeakMap();
559
961
  function accordion(options) {
560
962
  const { items: itemDefs, multiple = false, defaultExpanded = [] } = options;
561
963
  const [expandedIds, setExpandedIds] = signal(new Set(defaultExpanded));
@@ -602,6 +1004,86 @@ function accordion(options) {
602
1004
  function isExpanded(id) {
603
1005
  return expandedIds().has(id);
604
1006
  }
1007
+ function bind(els) {
1008
+ const idempotencyKey = els.root ?? (itemDefs.length > 0 ? els.triggers[itemDefs[0].id] : void 0);
1009
+ if (idempotencyKey) {
1010
+ const existing = boundAccordions.get(idempotencyKey);
1011
+ if (existing) return existing;
1012
+ }
1013
+ const restore = [];
1014
+ for (const item of itemDefs) {
1015
+ const trig = els.triggers[item.id];
1016
+ const panel = els.panels[item.id];
1017
+ if (!trig) continue;
1018
+ const prevTrigId = trig.id;
1019
+ const prevTrigControls = trig.getAttribute("aria-controls");
1020
+ trig.id = `sibu-accordion-trigger-${item.id}`;
1021
+ let prevPanelRole = null;
1022
+ let prevPanelId = "";
1023
+ let prevPanelLabelledBy = null;
1024
+ if (panel) {
1025
+ prevPanelRole = panel.getAttribute("role");
1026
+ prevPanelId = panel.id;
1027
+ prevPanelLabelledBy = panel.getAttribute("aria-labelledby");
1028
+ panel.setAttribute("role", "region");
1029
+ panel.id = `sibu-accordion-panel-${item.id}`;
1030
+ panel.setAttribute("aria-labelledby", trig.id);
1031
+ trig.setAttribute("aria-controls", panel.id);
1032
+ }
1033
+ restore.push(() => {
1034
+ if (prevTrigId === "") trig.removeAttribute("id");
1035
+ else trig.id = prevTrigId;
1036
+ if (prevTrigControls === null) trig.removeAttribute("aria-controls");
1037
+ else trig.setAttribute("aria-controls", prevTrigControls);
1038
+ trig.removeAttribute("aria-expanded");
1039
+ if (panel) {
1040
+ if (prevPanelRole === null) panel.removeAttribute("role");
1041
+ else panel.setAttribute("role", prevPanelRole);
1042
+ if (prevPanelId === "") panel.removeAttribute("id");
1043
+ else panel.id = prevPanelId;
1044
+ if (prevPanelLabelledBy === null) panel.removeAttribute("aria-labelledby");
1045
+ else panel.setAttribute("aria-labelledby", prevPanelLabelledBy);
1046
+ }
1047
+ });
1048
+ }
1049
+ const fxTeardown = effect(() => {
1050
+ const ids = expandedIds();
1051
+ for (const item of itemDefs) {
1052
+ const trig = els.triggers[item.id];
1053
+ const panel = els.panels[item.id];
1054
+ if (!trig) continue;
1055
+ const expanded = ids.has(item.id);
1056
+ trig.setAttribute("aria-expanded", expanded ? "true" : "false");
1057
+ if (panel) panel.hidden = !expanded;
1058
+ }
1059
+ });
1060
+ const handlers = [];
1061
+ for (const item of itemDefs) {
1062
+ const trig = els.triggers[item.id];
1063
+ if (!trig) continue;
1064
+ const click = () => toggle(item.id);
1065
+ const key = (e) => {
1066
+ if (e.key === "Enter" || e.key === " ") {
1067
+ e.preventDefault();
1068
+ toggle(item.id);
1069
+ }
1070
+ };
1071
+ trig.addEventListener("click", click);
1072
+ trig.addEventListener("keydown", key);
1073
+ handlers.push({ el: trig, click, key });
1074
+ }
1075
+ const teardown = () => {
1076
+ if (idempotencyKey) boundAccordions.delete(idempotencyKey);
1077
+ fxTeardown();
1078
+ for (const { el, click, key } of handlers) {
1079
+ el.removeEventListener("click", click);
1080
+ el.removeEventListener("keydown", key);
1081
+ }
1082
+ for (const r of restore) r();
1083
+ };
1084
+ if (idempotencyKey) boundAccordions.set(idempotencyKey, teardown);
1085
+ return teardown;
1086
+ }
605
1087
  return {
606
1088
  items,
607
1089
  toggle,
@@ -610,11 +1092,14 @@ function accordion(options) {
610
1092
  expandAll,
611
1093
  collapseAll,
612
1094
  /** Reactive check — use inside class/nodes bindings for per-item reactivity */
613
- isExpanded
1095
+ isExpanded,
1096
+ bind
614
1097
  };
615
1098
  }
616
1099
 
617
1100
  // src/widgets/Popover.ts
1101
+ var popoverIdCounter = 0;
1102
+ var boundPopovers = /* @__PURE__ */ new WeakMap();
618
1103
  function popover() {
619
1104
  const [isOpen, setIsOpen] = signal(false);
620
1105
  function open() {
@@ -626,12 +1111,88 @@ function popover() {
626
1111
  function toggle() {
627
1112
  setIsOpen((prev) => !prev);
628
1113
  }
629
- return { isOpen, open, close, toggle };
1114
+ function bind(els) {
1115
+ const existing = boundPopovers.get(els.trigger);
1116
+ if (existing) return existing;
1117
+ const id = `sibu-popover-${++popoverIdCounter}`;
1118
+ const prevPopoverRole = els.popover.getAttribute("role");
1119
+ const prevPopoverId = els.popover.id;
1120
+ const prevLabelledBy = els.popover.getAttribute("aria-labelledby");
1121
+ const prevTriggerHaspopup = els.trigger.getAttribute("aria-haspopup");
1122
+ const prevTriggerControls = els.trigger.getAttribute("aria-controls");
1123
+ els.popover.setAttribute("role", "dialog");
1124
+ els.popover.id = id;
1125
+ els.trigger.setAttribute("aria-haspopup", "dialog");
1126
+ els.trigger.setAttribute("aria-controls", id);
1127
+ let assignedLabelId = null;
1128
+ if (els.labelledBy) {
1129
+ if (!els.labelledBy.id) {
1130
+ els.labelledBy.id = `${id}-label`;
1131
+ assignedLabelId = els.labelledBy.id;
1132
+ }
1133
+ els.popover.setAttribute("aria-labelledby", els.labelledBy.id);
1134
+ }
1135
+ const fxTeardown = effect(() => {
1136
+ const open2 = isOpen();
1137
+ els.trigger.setAttribute("aria-expanded", open2 ? "true" : "false");
1138
+ els.popover.hidden = !open2;
1139
+ });
1140
+ const onTriggerClick = (e) => {
1141
+ e.preventDefault();
1142
+ toggle();
1143
+ };
1144
+ const onKey = (e) => {
1145
+ if (e.key === "Escape" && isOpen()) {
1146
+ e.stopPropagation();
1147
+ close();
1148
+ els.trigger.focus();
1149
+ }
1150
+ };
1151
+ const onDocPointer = (e) => {
1152
+ if (!isOpen()) return;
1153
+ const t = e.target;
1154
+ if (!t) return;
1155
+ if (els.trigger.contains(t) || els.popover.contains(t)) return;
1156
+ close();
1157
+ };
1158
+ els.trigger.addEventListener("click", onTriggerClick);
1159
+ els.popover.addEventListener("keydown", onKey);
1160
+ els.trigger.addEventListener("keydown", onKey);
1161
+ document.addEventListener("pointerdown", onDocPointer);
1162
+ const teardown = () => {
1163
+ boundPopovers.delete(els.trigger);
1164
+ fxTeardown();
1165
+ els.trigger.removeEventListener("click", onTriggerClick);
1166
+ els.popover.removeEventListener("keydown", onKey);
1167
+ els.trigger.removeEventListener("keydown", onKey);
1168
+ document.removeEventListener("pointerdown", onDocPointer);
1169
+ if (prevPopoverRole === null) els.popover.removeAttribute("role");
1170
+ else els.popover.setAttribute("role", prevPopoverRole);
1171
+ if (prevPopoverId === "") els.popover.removeAttribute("id");
1172
+ else els.popover.id = prevPopoverId;
1173
+ if (prevLabelledBy === null) els.popover.removeAttribute("aria-labelledby");
1174
+ else els.popover.setAttribute("aria-labelledby", prevLabelledBy);
1175
+ if (assignedLabelId && els.labelledBy?.id === assignedLabelId) {
1176
+ els.labelledBy.removeAttribute("id");
1177
+ }
1178
+ if (prevTriggerHaspopup === null) els.trigger.removeAttribute("aria-haspopup");
1179
+ else els.trigger.setAttribute("aria-haspopup", prevTriggerHaspopup);
1180
+ if (prevTriggerControls === null) els.trigger.removeAttribute("aria-controls");
1181
+ else els.trigger.setAttribute("aria-controls", prevTriggerControls);
1182
+ els.trigger.removeAttribute("aria-expanded");
1183
+ };
1184
+ boundPopovers.set(els.trigger, teardown);
1185
+ return teardown;
1186
+ }
1187
+ return { isOpen, open, close, toggle, bind };
630
1188
  }
631
1189
 
632
1190
  // src/widgets/Select.ts
1191
+ var selectIdCounter = 0;
1192
+ var boundSelects = /* @__PURE__ */ new WeakMap();
633
1193
  function select(options) {
634
- const { items, multiple = false } = options;
1194
+ const { items, multiple = false, itemToString, isDisabled } = options;
1195
+ const isItemDisabled = isDisabled ?? (() => false);
635
1196
  const [selectedItems, setSelectedItems] = signal([]);
636
1197
  const [isOpen, setIsOpen] = signal(false);
637
1198
  const [highlightedIndex, setHighlightedIndex] = signal(-1);
@@ -640,6 +1201,7 @@ function select(options) {
640
1201
  return sel.length > 0 ? sel[sel.length - 1] : null;
641
1202
  });
642
1203
  function select2(item) {
1204
+ if (isItemDisabled(item)) return;
643
1205
  if (multiple) {
644
1206
  setSelectedItems((prev) => {
645
1207
  if (prev.includes(item)) return prev;
@@ -671,18 +1233,28 @@ function select(options) {
671
1233
  function close() {
672
1234
  setIsOpen(false);
673
1235
  }
1236
+ function nextEnabled(from, dir) {
1237
+ const len = items.length;
1238
+ if (len === 0) return -1;
1239
+ let i = from;
1240
+ for (let n = 0; n < len; n++) {
1241
+ i = (i + dir + len) % len;
1242
+ if (!isItemDisabled(items[i])) return i;
1243
+ }
1244
+ return -1;
1245
+ }
674
1246
  function highlightNext() {
675
1247
  if (items.length === 0) return;
676
1248
  setHighlightedIndex((prev) => {
677
- const next = prev + 1;
678
- return next >= items.length ? 0 : next;
1249
+ const n = nextEnabled(prev < 0 ? -1 : prev, 1);
1250
+ return n === -1 ? prev : n;
679
1251
  });
680
1252
  }
681
1253
  function highlightPrev() {
682
1254
  if (items.length === 0) return;
683
1255
  setHighlightedIndex((prev) => {
684
- const next = prev - 1;
685
- return next < 0 ? items.length - 1 : next;
1256
+ const n = nextEnabled(prev < 0 ? items.length : prev, -1);
1257
+ return n === -1 ? prev : n;
686
1258
  });
687
1259
  }
688
1260
  function selectHighlighted() {
@@ -694,6 +1266,73 @@ function select(options) {
694
1266
  function clear() {
695
1267
  setSelectedItems([]);
696
1268
  }
1269
+ function bind(els) {
1270
+ const existing = boundSelects.get(els.listbox);
1271
+ if (existing) return existing;
1272
+ const listboxId = `sibu-select-${++selectIdCounter}`;
1273
+ els.listbox.id = listboxId;
1274
+ els.listbox.setAttribute("role", "listbox");
1275
+ els.listbox.setAttribute("aria-multiselectable", multiple ? "true" : "false");
1276
+ if (els.listbox.tabIndex < 0) els.listbox.tabIndex = 0;
1277
+ const toStr = els.itemToString ?? itemToString ?? ((it) => String(it));
1278
+ const fxTeardown = effect(() => {
1279
+ const idx = highlightedIndex();
1280
+ const sel = selectedItems();
1281
+ let activeId = "";
1282
+ for (let i = 0; i < items.length; i++) {
1283
+ const optEl = els.option(items[i], i);
1284
+ if (!optEl) continue;
1285
+ if (!optEl.id) optEl.id = `${listboxId}-opt-${i}`;
1286
+ optEl.setAttribute("role", "option");
1287
+ optEl.setAttribute("aria-selected", sel.includes(items[i]) ? "true" : "false");
1288
+ if (isItemDisabled(items[i])) optEl.setAttribute("aria-disabled", "true");
1289
+ else optEl.removeAttribute("aria-disabled");
1290
+ if (i === idx) activeId = optEl.id;
1291
+ }
1292
+ if (activeId) els.listbox.setAttribute("aria-activedescendant", activeId);
1293
+ else els.listbox.removeAttribute("aria-activedescendant");
1294
+ });
1295
+ let typeBuffer = "";
1296
+ let typeTimer = null;
1297
+ const onKey = (e) => {
1298
+ if (e.key === "ArrowDown") {
1299
+ e.preventDefault();
1300
+ highlightNext();
1301
+ } else if (e.key === "ArrowUp") {
1302
+ e.preventDefault();
1303
+ highlightPrev();
1304
+ } else if (e.key === "Home") {
1305
+ e.preventDefault();
1306
+ if (items.length > 0) setHighlightedIndex(0);
1307
+ } else if (e.key === "End") {
1308
+ e.preventDefault();
1309
+ if (items.length > 0) setHighlightedIndex(items.length - 1);
1310
+ } else if (e.key === "Enter" || e.key === " ") {
1311
+ if (highlightedIndex() >= 0) {
1312
+ e.preventDefault();
1313
+ selectHighlighted();
1314
+ }
1315
+ } else if (e.key.length === 1 && /\S/.test(e.key)) {
1316
+ typeBuffer += e.key.toLowerCase();
1317
+ if (typeTimer !== null) clearTimeout(typeTimer);
1318
+ typeTimer = setTimeout(() => {
1319
+ typeBuffer = "";
1320
+ typeTimer = null;
1321
+ }, 500);
1322
+ const found = items.findIndex((it) => !isItemDisabled(it) && toStr(it).toLowerCase().startsWith(typeBuffer));
1323
+ if (found !== -1) setHighlightedIndex(found);
1324
+ }
1325
+ };
1326
+ els.listbox.addEventListener("keydown", onKey);
1327
+ const teardown = () => {
1328
+ boundSelects.delete(els.listbox);
1329
+ fxTeardown();
1330
+ els.listbox.removeEventListener("keydown", onKey);
1331
+ if (typeTimer !== null) clearTimeout(typeTimer);
1332
+ };
1333
+ boundSelects.set(els.listbox, teardown);
1334
+ return teardown;
1335
+ }
697
1336
  return {
698
1337
  selectedItems,
699
1338
  selectedItem,
@@ -708,21 +1347,28 @@ function select(options) {
708
1347
  highlightNext,
709
1348
  highlightPrev,
710
1349
  selectHighlighted,
711
- clear
1350
+ clear,
1351
+ bind
712
1352
  };
713
1353
  }
714
1354
 
715
1355
  // src/widgets/Tooltip.ts
1356
+ var tooltipIdCounter = 0;
1357
+ var boundTriggers = /* @__PURE__ */ new WeakMap();
716
1358
  function tooltip(options) {
717
1359
  const delay = options?.delay ?? 0;
1360
+ const hideDelay = options?.hideDelay ?? 100;
718
1361
  const [isVisible, setIsVisible] = signal(false);
719
1362
  const [content, setContent] = signal("");
720
1363
  let delayTimer = null;
1364
+ let hideTimer = null;
721
1365
  function show() {
1366
+ if (hideTimer !== null) {
1367
+ clearTimeout(hideTimer);
1368
+ hideTimer = null;
1369
+ }
722
1370
  if (delay > 0) {
723
- if (delayTimer !== null) {
724
- clearTimeout(delayTimer);
725
- }
1371
+ if (delayTimer !== null) clearTimeout(delayTimer);
726
1372
  delayTimer = setTimeout(() => {
727
1373
  setIsVisible(true);
728
1374
  delayTimer = null;
@@ -738,10 +1384,82 @@ function tooltip(options) {
738
1384
  }
739
1385
  setIsVisible(false);
740
1386
  }
741
- return { isVisible, show, hide, content, setContent };
1387
+ function scheduleHide() {
1388
+ if (delayTimer !== null) {
1389
+ clearTimeout(delayTimer);
1390
+ delayTimer = null;
1391
+ }
1392
+ if (hideTimer !== null) clearTimeout(hideTimer);
1393
+ hideTimer = setTimeout(() => {
1394
+ hideTimer = null;
1395
+ setIsVisible(false);
1396
+ }, hideDelay);
1397
+ }
1398
+ function bind(els) {
1399
+ const existing = boundTriggers.get(els.trigger);
1400
+ if (existing) return existing;
1401
+ const id = `sibu-tooltip-${++tooltipIdCounter}`;
1402
+ els.tooltip.setAttribute("role", "tooltip");
1403
+ els.tooltip.id = id;
1404
+ const prevDescribedBy = els.trigger.getAttribute("aria-describedby");
1405
+ els.trigger.setAttribute("aria-describedby", prevDescribedBy ? `${prevDescribedBy} ${id}` : id);
1406
+ const fxTeardown = effect(() => {
1407
+ els.tooltip.hidden = !isVisible();
1408
+ });
1409
+ const onTriggerEnter = () => show();
1410
+ const onTriggerLeave = () => scheduleHide();
1411
+ const onTooltipEnter = () => {
1412
+ if (hideTimer !== null) {
1413
+ clearTimeout(hideTimer);
1414
+ hideTimer = null;
1415
+ }
1416
+ };
1417
+ const onTooltipLeave = () => scheduleHide();
1418
+ const onFocus = () => show();
1419
+ const onBlur = () => hide();
1420
+ const onKey = (e) => {
1421
+ if (e.key === "Escape" && isVisible()) {
1422
+ e.stopPropagation();
1423
+ hide();
1424
+ }
1425
+ };
1426
+ els.trigger.addEventListener("pointerenter", onTriggerEnter);
1427
+ els.trigger.addEventListener("pointerleave", onTriggerLeave);
1428
+ els.trigger.addEventListener("focus", onFocus);
1429
+ els.trigger.addEventListener("blur", onBlur);
1430
+ els.trigger.addEventListener("keydown", onKey);
1431
+ els.tooltip.addEventListener("pointerenter", onTooltipEnter);
1432
+ els.tooltip.addEventListener("pointerleave", onTooltipLeave);
1433
+ const teardown = () => {
1434
+ boundTriggers.delete(els.trigger);
1435
+ fxTeardown();
1436
+ els.trigger.removeEventListener("pointerenter", onTriggerEnter);
1437
+ els.trigger.removeEventListener("pointerleave", onTriggerLeave);
1438
+ els.trigger.removeEventListener("focus", onFocus);
1439
+ els.trigger.removeEventListener("blur", onBlur);
1440
+ els.trigger.removeEventListener("keydown", onKey);
1441
+ els.tooltip.removeEventListener("pointerenter", onTooltipEnter);
1442
+ els.tooltip.removeEventListener("pointerleave", onTooltipLeave);
1443
+ const cur = els.trigger.getAttribute("aria-describedby");
1444
+ if (cur) {
1445
+ const remaining = cur.split(/\s+/).filter((part) => part && part !== id);
1446
+ if (remaining.length > 0) els.trigger.setAttribute("aria-describedby", remaining.join(" "));
1447
+ else els.trigger.removeAttribute("aria-describedby");
1448
+ } else if (prevDescribedBy) {
1449
+ els.trigger.setAttribute("aria-describedby", prevDescribedBy);
1450
+ }
1451
+ if (delayTimer !== null) clearTimeout(delayTimer);
1452
+ if (hideTimer !== null) clearTimeout(hideTimer);
1453
+ };
1454
+ boundTriggers.set(els.trigger, teardown);
1455
+ return teardown;
1456
+ }
1457
+ return { isVisible, show, hide, content, setContent, bind };
742
1458
  }
743
1459
 
744
1460
  // src/widgets/FileUpload.ts
1461
+ var fileUploadIdCounter = 0;
1462
+ var boundFileUploads = /* @__PURE__ */ new WeakMap();
745
1463
  function fileUpload(options) {
746
1464
  const accept = options?.accept;
747
1465
  const multiple = options?.multiple ?? false;
@@ -809,6 +1527,107 @@ function fileUpload(options) {
809
1527
  setErrors([]);
810
1528
  });
811
1529
  }
1530
+ function bind(els) {
1531
+ const existing = boundFileUploads.get(els.input);
1532
+ if (existing) return existing;
1533
+ const id = `sibu-fileupload-${++fileUploadIdCounter}`;
1534
+ const restore = [];
1535
+ if (accept) els.input.accept = accept;
1536
+ els.input.multiple = multiple;
1537
+ let hintId = null;
1538
+ if (els.hint) {
1539
+ const assignedHintId = !els.hint.id;
1540
+ if (assignedHintId) els.hint.id = `${id}-hint`;
1541
+ hintId = els.hint.id;
1542
+ const prev = els.input.getAttribute("aria-describedby");
1543
+ els.input.setAttribute("aria-describedby", prev ? `${prev} ${hintId}` : hintId);
1544
+ restore.push(() => {
1545
+ const cur = els.input.getAttribute("aria-describedby");
1546
+ if (cur) {
1547
+ const parts = cur.split(/\s+/).filter((p) => p && p !== hintId);
1548
+ if (parts.length > 0) els.input.setAttribute("aria-describedby", parts.join(" "));
1549
+ else els.input.removeAttribute("aria-describedby");
1550
+ }
1551
+ if (assignedHintId && els.hint && els.hint.id === hintId) els.hint.removeAttribute("id");
1552
+ });
1553
+ }
1554
+ if (els.errorRegion) {
1555
+ const prevRole = els.errorRegion.getAttribute("role");
1556
+ const prevLive = els.errorRegion.getAttribute("aria-live");
1557
+ els.errorRegion.setAttribute("role", "status");
1558
+ els.errorRegion.setAttribute("aria-live", "polite");
1559
+ restore.push(() => {
1560
+ if (prevRole === null) els.errorRegion.removeAttribute("role");
1561
+ else els.errorRegion.setAttribute("role", prevRole);
1562
+ if (prevLive === null) els.errorRegion.removeAttribute("aria-live");
1563
+ else els.errorRegion.setAttribute("aria-live", prevLive);
1564
+ });
1565
+ }
1566
+ if (els.dropZone) {
1567
+ const prevDzRole = els.dropZone.getAttribute("role");
1568
+ const prevDzLabel = els.dropZone.getAttribute("aria-label");
1569
+ const prevDzTabindex = els.dropZone.hasAttribute("tabindex") ? els.dropZone.getAttribute("tabindex") : null;
1570
+ els.dropZone.setAttribute("role", "button");
1571
+ els.dropZone.setAttribute("aria-label", "File drop zone \u2014 click or press Enter to browse");
1572
+ if (els.dropZone.tabIndex < 0) els.dropZone.tabIndex = 0;
1573
+ restore.push(() => {
1574
+ if (prevDzRole === null) els.dropZone.removeAttribute("role");
1575
+ else els.dropZone.setAttribute("role", prevDzRole);
1576
+ if (prevDzLabel === null) els.dropZone.removeAttribute("aria-label");
1577
+ else els.dropZone.setAttribute("aria-label", prevDzLabel);
1578
+ if (prevDzTabindex === null) els.dropZone.removeAttribute("tabindex");
1579
+ else els.dropZone.setAttribute("tabindex", prevDzTabindex);
1580
+ });
1581
+ }
1582
+ const fxTeardown = effect(() => {
1583
+ const errs = errors();
1584
+ if (els.errorRegion) els.errorRegion.textContent = errs.join(". ");
1585
+ if (els.dropZone) els.dropZone.setAttribute("data-drag-over", isDragOver() ? "true" : "false");
1586
+ });
1587
+ const onChange = () => {
1588
+ if (els.input.files) addFiles(els.input.files);
1589
+ };
1590
+ const onDropClick = () => els.input.click();
1591
+ const onDropKey = (e) => {
1592
+ if (e.key === "Enter" || e.key === " ") {
1593
+ e.preventDefault();
1594
+ els.input.click();
1595
+ }
1596
+ };
1597
+ const onDragOver = (e) => {
1598
+ e.preventDefault();
1599
+ setDragOver(true);
1600
+ };
1601
+ const onDragLeave = () => setDragOver(false);
1602
+ const onDrop = (e) => {
1603
+ e.preventDefault();
1604
+ setDragOver(false);
1605
+ if (e.dataTransfer?.files) addFiles(e.dataTransfer.files);
1606
+ };
1607
+ els.input.addEventListener("change", onChange);
1608
+ if (els.dropZone) {
1609
+ els.dropZone.addEventListener("click", onDropClick);
1610
+ els.dropZone.addEventListener("keydown", onDropKey);
1611
+ els.dropZone.addEventListener("dragover", onDragOver);
1612
+ els.dropZone.addEventListener("dragleave", onDragLeave);
1613
+ els.dropZone.addEventListener("drop", onDrop);
1614
+ }
1615
+ const teardown = () => {
1616
+ boundFileUploads.delete(els.input);
1617
+ fxTeardown();
1618
+ els.input.removeEventListener("change", onChange);
1619
+ if (els.dropZone) {
1620
+ els.dropZone.removeEventListener("click", onDropClick);
1621
+ els.dropZone.removeEventListener("keydown", onDropKey);
1622
+ els.dropZone.removeEventListener("dragover", onDragOver);
1623
+ els.dropZone.removeEventListener("dragleave", onDragLeave);
1624
+ els.dropZone.removeEventListener("drop", onDrop);
1625
+ }
1626
+ for (const r of restore) r();
1627
+ };
1628
+ boundFileUploads.set(els.input, teardown);
1629
+ return teardown;
1630
+ }
812
1631
  return {
813
1632
  files,
814
1633
  addFiles,
@@ -816,14 +1635,36 @@ function fileUpload(options) {
816
1635
  clear,
817
1636
  errors,
818
1637
  isDragOver,
819
- setDragOver
1638
+ setDragOver,
1639
+ bind
820
1640
  };
821
1641
  }
822
1642
 
1643
+ // src/utils/sanitize.ts
1644
+ function stripHtml(html) {
1645
+ return String(html).replace(/<[^>]*>/g, "");
1646
+ }
1647
+
823
1648
  // src/widgets/contentEditable.ts
824
1649
  function contentEditable() {
825
- const [content, setContent] = signal("");
1650
+ const [content, setContentInternal] = signal("");
826
1651
  const [isFocused, setFocused] = signal(false);
1652
+ function setContent(input) {
1653
+ if (typeof input === "string") {
1654
+ setContentInternal(input);
1655
+ return;
1656
+ }
1657
+ if (typeof input.text === "string") {
1658
+ setContentInternal(input.text);
1659
+ return;
1660
+ }
1661
+ if (typeof input.html === "string") {
1662
+ const shouldSanitize = input.sanitize !== false;
1663
+ setContentInternal(shouldSanitize ? stripHtml(input.html) : input.html);
1664
+ return;
1665
+ }
1666
+ setContentInternal("");
1667
+ }
827
1668
  function wrapSelection(tagName) {
828
1669
  if (typeof window === "undefined") return;
829
1670
  const selection = window.getSelection();
@@ -900,6 +1741,7 @@ function contentEditable() {
900
1741
  }
901
1742
 
902
1743
  // src/widgets/datePicker.ts
1744
+ var boundDatePickers = /* @__PURE__ */ new WeakMap();
903
1745
  function datePicker(options) {
904
1746
  const minDate = options?.minDate;
905
1747
  const maxDate = options?.maxDate;
@@ -923,33 +1765,20 @@ function datePicker(options) {
923
1765
  setSelectedDate(date);
924
1766
  }
925
1767
  }
1768
+ function shiftMonth(prev, delta) {
1769
+ return new Date(prev.getFullYear(), prev.getMonth() + delta, 1);
1770
+ }
926
1771
  function nextMonth() {
927
- setViewDate((prev) => {
928
- const next = new Date(prev);
929
- next.setMonth(next.getMonth() + 1);
930
- return next;
931
- });
1772
+ setViewDate((prev) => shiftMonth(prev, 1));
932
1773
  }
933
1774
  function prevMonth() {
934
- setViewDate((prev) => {
935
- const next = new Date(prev);
936
- next.setMonth(next.getMonth() - 1);
937
- return next;
938
- });
1775
+ setViewDate((prev) => shiftMonth(prev, -1));
939
1776
  }
940
1777
  function nextYear() {
941
- setViewDate((prev) => {
942
- const next = new Date(prev);
943
- next.setFullYear(next.getFullYear() + 1);
944
- return next;
945
- });
1778
+ setViewDate((prev) => new Date(prev.getFullYear() + 1, prev.getMonth(), 1));
946
1779
  }
947
1780
  function prevYear() {
948
- setViewDate((prev) => {
949
- const next = new Date(prev);
950
- next.setFullYear(next.getFullYear() - 1);
951
- return next;
952
- });
1781
+ setViewDate((prev) => new Date(prev.getFullYear() - 1, prev.getMonth(), 1));
953
1782
  }
954
1783
  const daysInMonth = derived(() => {
955
1784
  const vd = viewDate();
@@ -1014,8 +1843,87 @@ function datePicker(options) {
1014
1843
  daysInMonth,
1015
1844
  isDateDisabled,
1016
1845
  /** Reactive check — use inside class bindings for per-day reactivity */
1017
- isSelected
1846
+ isSelected,
1847
+ bind
1018
1848
  };
1849
+ function bind(els) {
1850
+ const existing = boundDatePickers.get(els.grid);
1851
+ if (existing) return existing;
1852
+ els.grid.setAttribute("role", "grid");
1853
+ if (els.grid.tabIndex < 0) els.grid.tabIndex = 0;
1854
+ const fxTeardown = effect(() => {
1855
+ const sel = selectedDate();
1856
+ const view = viewDate();
1857
+ const days = daysInMonth();
1858
+ for (const d of days) {
1859
+ const cell = els.cell(d.date);
1860
+ if (!cell) continue;
1861
+ cell.setAttribute("role", "gridcell");
1862
+ cell.setAttribute("aria-selected", sel && isSameCalendarDay(sel, d.date) ? "true" : "false");
1863
+ if (d.isDisabled) cell.setAttribute("aria-disabled", "true");
1864
+ else cell.removeAttribute("aria-disabled");
1865
+ cell.tabIndex = isSameCalendarDay(view, d.date) ? 0 : -1;
1866
+ }
1867
+ });
1868
+ function isSameCalendarDay(a, b) {
1869
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1870
+ }
1871
+ function shiftDays(delta) {
1872
+ setViewDate((prev) => new Date(prev.getFullYear(), prev.getMonth(), prev.getDate() + delta));
1873
+ }
1874
+ const onKey = (e) => {
1875
+ switch (e.key) {
1876
+ case "ArrowLeft":
1877
+ e.preventDefault();
1878
+ shiftDays(-1);
1879
+ break;
1880
+ case "ArrowRight":
1881
+ e.preventDefault();
1882
+ shiftDays(1);
1883
+ break;
1884
+ case "ArrowUp":
1885
+ e.preventDefault();
1886
+ shiftDays(-7);
1887
+ break;
1888
+ case "ArrowDown":
1889
+ e.preventDefault();
1890
+ shiftDays(7);
1891
+ break;
1892
+ case "Home":
1893
+ e.preventDefault();
1894
+ setViewDate((p) => new Date(p.getFullYear(), p.getMonth(), p.getDate() - p.getDay()));
1895
+ break;
1896
+ case "End": {
1897
+ e.preventDefault();
1898
+ setViewDate((p) => new Date(p.getFullYear(), p.getMonth(), p.getDate() + (6 - p.getDay())));
1899
+ break;
1900
+ }
1901
+ case "PageUp":
1902
+ e.preventDefault();
1903
+ if (e.shiftKey) prevYear();
1904
+ else prevMonth();
1905
+ break;
1906
+ case "PageDown":
1907
+ e.preventDefault();
1908
+ if (e.shiftKey) nextYear();
1909
+ else nextMonth();
1910
+ break;
1911
+ case "Enter":
1912
+ case " ":
1913
+ e.preventDefault();
1914
+ select2(viewDate());
1915
+ break;
1916
+ }
1917
+ };
1918
+ els.grid.addEventListener("keydown", onKey);
1919
+ const teardown = () => {
1920
+ boundDatePickers.delete(els.grid);
1921
+ fxTeardown();
1922
+ els.grid.removeEventListener("keydown", onKey);
1923
+ };
1924
+ boundDatePickers.set(els.grid, teardown);
1925
+ return teardown;
1926
+ }
1019
1927
  }
1020
1928
  // Annotate the CommonJS export names for ESM import in node:
1021
1929
  0 && (module.exports = {