sibujs 1.5.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 (207) hide show
  1. package/dist/browser.cjs +238 -69
  2. package/dist/browser.d.cts +5 -0
  3. package/dist/browser.d.ts +5 -0
  4. package/dist/browser.js +6 -6
  5. package/dist/build.cjs +916 -292
  6. package/dist/build.js +15 -13
  7. package/dist/cdn.global.js +17 -16
  8. package/dist/chunk-2RA7SHDA.js +65 -0
  9. package/dist/chunk-2UPRY23K.js +80 -0
  10. package/dist/chunk-3JHCYHWN.js +125 -0
  11. package/dist/{chunk-VAPYJN4X.js → chunk-3LR7GLWQ.js} +93 -23
  12. package/dist/{chunk-RJ46C3CS.js → chunk-3NSGB5JN.js} +71 -20
  13. package/dist/{chunk-XUEEGU5O.js → chunk-52YJLLRO.js} +16 -4
  14. package/dist/{chunk-XHK6BDAJ.js → chunk-54EDRCEF.js} +25 -8
  15. package/dist/chunk-7JDB7I65.js +1327 -0
  16. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  17. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  18. package/dist/{chunk-BGN5ZMP4.js → chunk-GTBNNBJ6.js} +14 -2
  19. package/dist/chunk-HB24TBAF.js +121 -0
  20. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  21. package/dist/{chunk-BGTHZHJ5.js → chunk-JA6667UN.js} +188 -44
  22. package/dist/{chunk-7GRNSCFT.js → chunk-JXMMDLBY.js} +306 -183
  23. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  24. package/dist/{chunk-SFKNRVCU.js → chunk-KLRMB5ZS.js} +135 -79
  25. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  26. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  27. package/dist/{chunk-BMPL52BF.js → chunk-MIUAXB7K.js} +118 -66
  28. package/dist/{chunk-JCDUJN2F.js → chunk-ND2664SF.js} +486 -153
  29. package/dist/{chunk-VQDZK23A.js → chunk-O2MNQFLP.js} +181 -66
  30. package/dist/{chunk-NHUC2QWH.js → chunk-R73P76YZ.js} +1 -1
  31. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  32. package/dist/chunk-UCS6AMJ7.js +79 -0
  33. package/dist/{chunk-K4G4ZQNR.js → chunk-VLPPXTYG.js} +84 -38
  34. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  35. package/dist/{customElement-BL3Uo8dL.d.cts → customElement-CPfIrbvg.d.cts} +14 -10
  36. package/dist/{customElement-BL3Uo8dL.d.ts → customElement-CPfIrbvg.d.ts} +14 -10
  37. package/dist/data.cjs +410 -99
  38. package/dist/data.d.cts +20 -2
  39. package/dist/data.d.ts +20 -2
  40. package/dist/data.js +11 -9
  41. package/dist/devtools.cjs +513 -223
  42. package/dist/devtools.d.cts +1 -1
  43. package/dist/devtools.d.ts +1 -1
  44. package/dist/devtools.js +12 -6
  45. package/dist/ecosystem.cjs +475 -144
  46. package/dist/ecosystem.d.cts +9 -7
  47. package/dist/ecosystem.d.ts +9 -7
  48. package/dist/ecosystem.js +12 -11
  49. package/dist/extras.cjs +3355 -1541
  50. package/dist/extras.d.cts +9 -9
  51. package/dist/extras.d.ts +9 -9
  52. package/dist/extras.js +58 -45
  53. package/dist/index.cjs +920 -292
  54. package/dist/index.d.cts +71 -8
  55. package/dist/index.d.ts +71 -8
  56. package/dist/index.js +28 -16
  57. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  58. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  59. package/dist/motion.cjs +77 -34
  60. package/dist/motion.js +4 -4
  61. package/dist/patterns.cjs +335 -69
  62. package/dist/patterns.d.cts +11 -12
  63. package/dist/patterns.d.ts +11 -12
  64. package/dist/patterns.js +7 -7
  65. package/dist/performance.cjs +279 -108
  66. package/dist/performance.d.cts +23 -16
  67. package/dist/performance.d.ts +23 -16
  68. package/dist/performance.js +13 -8
  69. package/dist/plugin-D30wlGW5.d.cts +71 -0
  70. package/dist/plugin-D30wlGW5.d.ts +71 -0
  71. package/dist/plugins.cjs +635 -260
  72. package/dist/plugins.d.cts +10 -3
  73. package/dist/plugins.d.ts +10 -3
  74. package/dist/plugins.js +106 -38
  75. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  76. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  77. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  78. package/dist/ssr.cjs +642 -222
  79. package/dist/ssr.d.cts +26 -6
  80. package/dist/ssr.d.ts +26 -6
  81. package/dist/ssr.js +12 -11
  82. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.cts} +9 -1
  83. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.ts} +9 -1
  84. package/dist/testing.cjs +252 -63
  85. package/dist/testing.d.cts +17 -4
  86. package/dist/testing.d.ts +17 -4
  87. package/dist/testing.js +100 -44
  88. package/dist/ui.cjs +463 -137
  89. package/dist/ui.d.cts +1 -1
  90. package/dist/ui.d.ts +1 -1
  91. package/dist/ui.js +20 -17
  92. package/dist/widgets.cjs +977 -94
  93. package/dist/widgets.d.cts +104 -2
  94. package/dist/widgets.d.ts +104 -2
  95. package/dist/widgets.js +9 -7
  96. package/package.json +8 -2
  97. package/dist/chunk-32DY64NT.js +0 -282
  98. package/dist/chunk-3AIRKM3B.js +0 -1263
  99. package/dist/chunk-3ARAQO7B.js +0 -398
  100. package/dist/chunk-3CRQALYP.js +0 -877
  101. package/dist/chunk-4EI4AG32.js +0 -482
  102. package/dist/chunk-4MYMUBRS.js +0 -21
  103. package/dist/chunk-5ZYQ6KDD.js +0 -154
  104. package/dist/chunk-6BMPXPUW.js +0 -26
  105. package/dist/chunk-6HLLIF3K.js +0 -398
  106. package/dist/chunk-6LSNVCS2.js +0 -937
  107. package/dist/chunk-6SA3QQES.js +0 -61
  108. package/dist/chunk-77L6NL3X.js +0 -1097
  109. package/dist/chunk-7BF6TK55.js +0 -1097
  110. package/dist/chunk-7TQKR4PP.js +0 -294
  111. package/dist/chunk-7V26P53V.js +0 -712
  112. package/dist/chunk-AZ3ISID5.js +0 -298
  113. package/dist/chunk-B7SWRFUT.js +0 -332
  114. package/dist/chunk-BTU3TJDS.js +0 -365
  115. package/dist/chunk-BW3WT46K.js +0 -937
  116. package/dist/chunk-C6KFWOFV.js +0 -616
  117. package/dist/chunk-CHF5OHIA.js +0 -61
  118. package/dist/chunk-CHJ27IGK.js +0 -26
  119. package/dist/chunk-CMBFNA7L.js +0 -27
  120. package/dist/chunk-DAHRH4ON.js +0 -331
  121. package/dist/chunk-DKOHBI74.js +0 -924
  122. package/dist/chunk-DTCOOBMX.js +0 -725
  123. package/dist/chunk-EBGIRKQY.js +0 -616
  124. package/dist/chunk-EUZND3CB.js +0 -27
  125. package/dist/chunk-EVCZO745.js +0 -365
  126. package/dist/chunk-EWFVA3TJ.js +0 -282
  127. package/dist/chunk-F3FA4F32.js +0 -292
  128. package/dist/chunk-FGOEVHY3.js +0 -60
  129. package/dist/chunk-G3BOQPVO.js +0 -365
  130. package/dist/chunk-GCOK2LC3.js +0 -282
  131. package/dist/chunk-GJPXRJ45.js +0 -37
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-JAKHTMQU.js +0 -1000
  134. package/dist/chunk-JCI5M6U6.js +0 -956
  135. package/dist/chunk-K5ZUMYVS.js +0 -89
  136. package/dist/chunk-KQPDEVVS.js +0 -398
  137. package/dist/chunk-L6JRBDNS.js +0 -60
  138. package/dist/chunk-LA6KQEDU.js +0 -712
  139. package/dist/chunk-MB6QFH3I.js +0 -2776
  140. package/dist/chunk-MDVXJWFN.js +0 -304
  141. package/dist/chunk-MEZVEBPN.js +0 -2008
  142. package/dist/chunk-MK4ERFYL.js +0 -2249
  143. package/dist/chunk-MLKGABMK.js +0 -9
  144. package/dist/chunk-MQ5GOYPH.js +0 -2249
  145. package/dist/chunk-MYRV7VDM.js +0 -742
  146. package/dist/chunk-N6IZB6KJ.js +0 -567
  147. package/dist/chunk-NEKUBFPT.js +0 -60
  148. package/dist/chunk-NMRUZALC.js +0 -1097
  149. package/dist/chunk-NYVAC6P5.js +0 -37
  150. package/dist/chunk-NZIIMDWI.js +0 -84
  151. package/dist/chunk-OF7UZIVB.js +0 -725
  152. package/dist/chunk-P3XWXJZU.js +0 -282
  153. package/dist/chunk-P6W3STU4.js +0 -2249
  154. package/dist/chunk-PBHF5WKN.js +0 -616
  155. package/dist/chunk-PDZQY43A.js +0 -616
  156. package/dist/chunk-PTQJDMRT.js +0 -146
  157. package/dist/chunk-PZEGYCF5.js +0 -61
  158. package/dist/chunk-QBMDLBU2.js +0 -975
  159. package/dist/chunk-QWZG56ET.js +0 -2744
  160. package/dist/chunk-RQGQSLQK.js +0 -725
  161. package/dist/chunk-SDLZDHKP.js +0 -107
  162. package/dist/chunk-TDGZL5CU.js +0 -365
  163. package/dist/chunk-TNQWPPE6.js +0 -37
  164. package/dist/chunk-TSOKIX5Z.js +0 -654
  165. package/dist/chunk-UHNL42EF.js +0 -2730
  166. package/dist/chunk-UNXCEF6S.js +0 -21
  167. package/dist/chunk-V2XTI523.js +0 -347
  168. package/dist/chunk-VAU366PN.js +0 -2241
  169. package/dist/chunk-VMVDTCXB.js +0 -712
  170. package/dist/chunk-VQNQZCWJ.js +0 -61
  171. package/dist/chunk-VRW3FULF.js +0 -725
  172. package/dist/chunk-WADYRCO2.js +0 -304
  173. package/dist/chunk-WILQZRO4.js +0 -282
  174. package/dist/chunk-WR5D4EGH.js +0 -26
  175. package/dist/chunk-WUHJISPP.js +0 -298
  176. package/dist/chunk-XYU6TZOW.js +0 -182
  177. package/dist/chunk-Y6GP4QGG.js +0 -276
  178. package/dist/chunk-YECR7UIA.js +0 -347
  179. package/dist/chunk-YUTWTI4B.js +0 -654
  180. package/dist/chunk-Z65KYU7I.js +0 -26
  181. package/dist/chunk-Z6POF5YC.js +0 -975
  182. package/dist/chunk-ZBJP6WFL.js +0 -482
  183. package/dist/chunk-ZD6OAMTH.js +0 -277
  184. package/dist/chunk-ZWKZCBO6.js +0 -317
  185. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  186. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  187. package/dist/contracts-DOrhwbke.d.cts +0 -245
  188. package/dist/contracts-DOrhwbke.d.ts +0 -245
  189. package/dist/contracts-xo5ckdRP.d.cts +0 -240
  190. package/dist/contracts-xo5ckdRP.d.ts +0 -240
  191. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  192. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  193. package/dist/customElement-D2DJp_xn.d.cts +0 -313
  194. package/dist/customElement-D2DJp_xn.d.ts +0 -313
  195. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  196. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  197. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  198. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  199. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  200. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  201. package/dist/ssr-3RXHP5ES.js +0 -38
  202. package/dist/ssr-6GIMY5MX.js +0 -38
  203. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  204. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  205. package/dist/ssr-WKUPVSSK.js +0 -36
  206. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  207. 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,17 +135,17 @@ function queueSignalNotification(signal2) {
127
135
  }
128
136
  }
129
137
  }
130
- var MAX_DRAIN_ITERATIONS = 1e3;
138
+ var maxDrainIterations = 1e5;
131
139
  function drainNotificationQueue() {
132
140
  if (notifyDepth > 0) return;
133
141
  notifyDepth++;
134
142
  try {
135
143
  let i = 0;
136
144
  while (i < pendingQueue.length) {
137
- if (i >= MAX_DRAIN_ITERATIONS) {
145
+ if (i >= maxDrainIterations) {
138
146
  if (typeof console !== "undefined") {
139
147
  console.error(
140
- `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
148
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
141
149
  );
142
150
  }
143
151
  break;
@@ -146,47 +154,52 @@ function drainNotificationQueue() {
146
154
  i++;
147
155
  }
148
156
  } finally {
149
- pendingQueue.length = 0;
150
- pendingSet.clear();
151
157
  notifyDepth--;
158
+ if (notifyDepth === 0) {
159
+ pendingQueue.length = 0;
160
+ pendingSet.clear();
161
+ }
152
162
  }
153
163
  }
154
164
  function propagateDirty(sub) {
155
165
  sub();
156
- let sig = sub._sig;
157
- 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();
158
173
  const first = sig.__f;
159
174
  if (first) {
160
175
  if (first._c) {
161
176
  const nSig = first._sig;
162
- nSig._d = true;
163
- sig = nSig;
164
- continue;
165
- }
166
- if (!pendingSet.has(first)) {
177
+ if (!nSig._d) {
178
+ nSig._d = true;
179
+ stack.push(nSig);
180
+ }
181
+ } else if (!pendingSet.has(first)) {
167
182
  pendingSet.add(first);
168
183
  pendingQueue.push(first);
169
184
  }
170
- break;
185
+ continue;
171
186
  }
172
187
  const subs = sig[SUBS];
173
- if (!subs) break;
174
- let nextSig;
188
+ if (!subs) continue;
175
189
  for (const s of subs) {
176
190
  if (s._c) {
177
- s();
178
191
  const nSig = s._sig;
179
- if (nSig && !nextSig) {
180
- nextSig = nSig;
181
- } else if (nSig) {
182
- propagateDirty(s);
192
+ if (nSig && !nSig._d) {
193
+ nSig._d = true;
194
+ stack.push(nSig);
195
+ } else if (!nSig) {
196
+ s();
183
197
  }
184
198
  } else if (!pendingSet.has(s)) {
185
199
  pendingSet.add(s);
186
200
  pendingQueue.push(s);
187
201
  }
188
202
  }
189
- sig = nextSig;
190
203
  }
191
204
  }
192
205
  function notifySubscribers(signal2) {
@@ -210,13 +223,23 @@ function notifySubscribers(signal2) {
210
223
  }
211
224
  let i = 0;
212
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
+ }
213
234
  safeInvoke(pendingQueue[i]);
214
235
  i++;
215
236
  }
216
237
  } finally {
217
- pendingQueue.length = 0;
218
- pendingSet.clear();
219
238
  notifyDepth--;
239
+ if (notifyDepth === 0) {
240
+ pendingQueue.length = 0;
241
+ pendingSet.clear();
242
+ }
220
243
  }
221
244
  return;
222
245
  }
@@ -236,30 +259,48 @@ function notifySubscribers(signal2) {
236
259
  notifyDepth++;
237
260
  try {
238
261
  let directCount = 0;
262
+ let hasComputedSub = false;
239
263
  for (const sub of subs) {
264
+ if (sub._c) hasComputedSub = true;
240
265
  pendingQueue[directCount++] = sub;
241
266
  }
242
- for (let i2 = 0; i2 < directCount; i2++) {
243
- if (pendingQueue[i2]._c) {
244
- propagateDirty(pendingQueue[i2]);
267
+ if (!hasComputedSub) {
268
+ for (let i2 = 0; i2 < directCount; i2++) {
269
+ safeInvoke(pendingQueue[i2]);
245
270
  }
246
- }
247
- for (let i2 = 0; i2 < directCount; i2++) {
248
- if (!pendingQueue[i2]._c) {
249
- if (!pendingSet.has(pendingQueue[i2])) {
250
- 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);
251
282
  }
252
283
  }
253
284
  }
254
285
  let i = directCount;
255
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
+ }
256
295
  safeInvoke(pendingQueue[i]);
257
296
  i++;
258
297
  }
259
298
  } finally {
260
- pendingQueue.length = 0;
261
- pendingSet.clear();
262
299
  notifyDepth--;
300
+ if (notifyDepth === 0) {
301
+ pendingQueue.length = 0;
302
+ pendingSet.clear();
303
+ }
263
304
  }
264
305
  }
265
306
  function cleanup(subscriber) {
@@ -270,7 +311,9 @@ function cleanup(subscriber) {
270
311
  if (subs) {
271
312
  subs.delete(subscriber);
272
313
  if (singleDep.__f === subscriber) {
273
- 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;
274
317
  }
275
318
  }
276
319
  sub._dep = void 0;
@@ -283,7 +326,9 @@ function cleanup(subscriber) {
283
326
  if (subs) {
284
327
  subs.delete(subscriber);
285
328
  if (signal2.__f === subscriber) {
286
- 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;
287
332
  }
288
333
  }
289
334
  }
@@ -294,6 +339,7 @@ function cleanup(subscriber) {
294
339
  function derived(getter, options) {
295
340
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
296
341
  const debugName = options?.name;
342
+ const equals = options?.equals;
297
343
  const cs = {};
298
344
  cs._d = false;
299
345
  cs._g = getter;
@@ -304,8 +350,14 @@ function derived(getter, options) {
304
350
  markDirty._c = 1;
305
351
  markDirty._sig = cs;
306
352
  track(() => {
307
- cs._d = false;
308
- 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
+ }
309
361
  }, markDirty);
310
362
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
311
363
  let evaluating = false;
@@ -318,11 +370,16 @@ function derived(getter, options) {
318
370
  if (trackingSuspended) {
319
371
  if (cs._d) {
320
372
  evaluating = true;
373
+ let threw = true;
321
374
  try {
322
- cs._d = false;
323
- cs._v = getter();
375
+ retrack(() => {
376
+ cs._v = getter();
377
+ cs._d = false;
378
+ threw = false;
379
+ }, markDirty);
324
380
  } finally {
325
381
  evaluating = false;
382
+ if (threw) cs._d = true;
326
383
  }
327
384
  }
328
385
  return cs._v;
@@ -331,13 +388,17 @@ function derived(getter, options) {
331
388
  if (cs._d) {
332
389
  const oldValue = cs._v;
333
390
  evaluating = true;
391
+ let threw = true;
334
392
  try {
335
- track(() => {
393
+ retrack(() => {
394
+ const next = getter();
395
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
336
396
  cs._d = false;
337
- cs._v = getter();
397
+ threw = false;
338
398
  }, markDirty);
339
399
  } finally {
340
400
  evaluating = false;
401
+ if (threw) cs._d = true;
341
402
  }
342
403
  if (hook && oldValue !== cs._v) {
343
404
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
@@ -354,6 +415,121 @@ function derived(getter, options) {
354
415
  return computedGetter;
355
416
  }
356
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
+
357
533
  // src/reactivity/batch.ts
358
534
  var batchDepth = 0;
359
535
  var pendingSignals = /* @__PURE__ */ new Set();
@@ -374,15 +550,18 @@ function enqueueBatchedSignal(signal2) {
374
550
  return true;
375
551
  }
376
552
  function flushBatch() {
377
- for (const signal2 of pendingSignals) {
378
- queueSignalNotification(signal2);
553
+ try {
554
+ for (const signal2 of pendingSignals) {
555
+ queueSignalNotification(signal2);
556
+ }
557
+ } finally {
558
+ pendingSignals.clear();
379
559
  }
380
- pendingSignals.clear();
381
560
  drainNotificationQueue();
382
561
  }
383
562
 
384
563
  // src/core/signals/signal.ts
385
- var _g = globalThis;
564
+ var _g2 = globalThis;
386
565
  var _isDev3 = isDev();
387
566
  function signal(initial, options) {
388
567
  const state = { value: initial };
@@ -403,7 +582,7 @@ function signal(initial, options) {
403
582
  if (_isDev3) {
404
583
  const oldValue = state.value;
405
584
  state.value = newValue;
406
- const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
585
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
407
586
  if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue, newValue });
408
587
  } else {
409
588
  state.value = newValue;
@@ -413,18 +592,12 @@ function signal(initial, options) {
413
592
  }
414
593
  }
415
594
  if (_isDev3) {
416
- const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
595
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
417
596
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
418
597
  }
419
598
  return [get, set];
420
599
  }
421
600
 
422
- // src/core/ssr-context.ts
423
- var ssrMode = false;
424
- function isSSR() {
425
- return ssrMode;
426
- }
427
-
428
601
  // src/core/signals/watch.ts
429
602
  function watch(getter, callback) {
430
603
  devAssert(typeof getter === "function", "watch: first argument must be a getter function.");
@@ -450,6 +623,8 @@ function watch(getter, callback) {
450
623
  }
451
624
 
452
625
  // src/widgets/Combobox.ts
626
+ var comboboxIdCounter = 0;
627
+ var boundComboboxes = /* @__PURE__ */ new WeakMap();
453
628
  function combobox(options) {
454
629
  const { items, filterFn, itemToString } = options;
455
630
  const defaultFilterFn = (item, q) => {
@@ -506,6 +681,92 @@ function combobox(options) {
506
681
  function close() {
507
682
  setIsOpen(false);
508
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
+ }
509
770
  return {
510
771
  query,
511
772
  setQuery,
@@ -518,11 +779,13 @@ function combobox(options) {
518
779
  selectHighlighted,
519
780
  isOpen,
520
781
  open,
521
- close
782
+ close,
783
+ bind
522
784
  };
523
785
  }
524
786
 
525
787
  // src/widgets/Tabs.ts
788
+ var boundTablists = /* @__PURE__ */ new WeakMap();
526
789
  function tabs(options) {
527
790
  const { tabs: tabDefs, defaultTab } = options;
528
791
  const initialTab = defaultTab ?? tabDefs.find((t) => !t.disabled)?.id ?? tabDefs[0]?.id ?? "";
@@ -569,6 +832,118 @@ function tabs(options) {
569
832
  function isActive(id) {
570
833
  return activeTab() === id;
571
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
+ }
572
947
  return {
573
948
  activeTab,
574
949
  setActiveTab,
@@ -576,11 +951,13 @@ function tabs(options) {
576
951
  nextTab,
577
952
  prevTab,
578
953
  /** Reactive check — use inside class/nodes bindings for per-tab reactivity */
579
- isActive
954
+ isActive,
955
+ bind
580
956
  };
581
957
  }
582
958
 
583
959
  // src/widgets/Accordion.ts
960
+ var boundAccordions = /* @__PURE__ */ new WeakMap();
584
961
  function accordion(options) {
585
962
  const { items: itemDefs, multiple = false, defaultExpanded = [] } = options;
586
963
  const [expandedIds, setExpandedIds] = signal(new Set(defaultExpanded));
@@ -627,6 +1004,86 @@ function accordion(options) {
627
1004
  function isExpanded(id) {
628
1005
  return expandedIds().has(id);
629
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
+ }
630
1087
  return {
631
1088
  items,
632
1089
  toggle,
@@ -635,11 +1092,14 @@ function accordion(options) {
635
1092
  expandAll,
636
1093
  collapseAll,
637
1094
  /** Reactive check — use inside class/nodes bindings for per-item reactivity */
638
- isExpanded
1095
+ isExpanded,
1096
+ bind
639
1097
  };
640
1098
  }
641
1099
 
642
1100
  // src/widgets/Popover.ts
1101
+ var popoverIdCounter = 0;
1102
+ var boundPopovers = /* @__PURE__ */ new WeakMap();
643
1103
  function popover() {
644
1104
  const [isOpen, setIsOpen] = signal(false);
645
1105
  function open() {
@@ -651,12 +1111,88 @@ function popover() {
651
1111
  function toggle() {
652
1112
  setIsOpen((prev) => !prev);
653
1113
  }
654
- 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 };
655
1188
  }
656
1189
 
657
1190
  // src/widgets/Select.ts
1191
+ var selectIdCounter = 0;
1192
+ var boundSelects = /* @__PURE__ */ new WeakMap();
658
1193
  function select(options) {
659
- const { items, multiple = false } = options;
1194
+ const { items, multiple = false, itemToString, isDisabled } = options;
1195
+ const isItemDisabled = isDisabled ?? (() => false);
660
1196
  const [selectedItems, setSelectedItems] = signal([]);
661
1197
  const [isOpen, setIsOpen] = signal(false);
662
1198
  const [highlightedIndex, setHighlightedIndex] = signal(-1);
@@ -665,6 +1201,7 @@ function select(options) {
665
1201
  return sel.length > 0 ? sel[sel.length - 1] : null;
666
1202
  });
667
1203
  function select2(item) {
1204
+ if (isItemDisabled(item)) return;
668
1205
  if (multiple) {
669
1206
  setSelectedItems((prev) => {
670
1207
  if (prev.includes(item)) return prev;
@@ -696,18 +1233,28 @@ function select(options) {
696
1233
  function close() {
697
1234
  setIsOpen(false);
698
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
+ }
699
1246
  function highlightNext() {
700
1247
  if (items.length === 0) return;
701
1248
  setHighlightedIndex((prev) => {
702
- const next = prev + 1;
703
- return next >= items.length ? 0 : next;
1249
+ const n = nextEnabled(prev < 0 ? -1 : prev, 1);
1250
+ return n === -1 ? prev : n;
704
1251
  });
705
1252
  }
706
1253
  function highlightPrev() {
707
1254
  if (items.length === 0) return;
708
1255
  setHighlightedIndex((prev) => {
709
- const next = prev - 1;
710
- return next < 0 ? items.length - 1 : next;
1256
+ const n = nextEnabled(prev < 0 ? items.length : prev, -1);
1257
+ return n === -1 ? prev : n;
711
1258
  });
712
1259
  }
713
1260
  function selectHighlighted() {
@@ -719,6 +1266,73 @@ function select(options) {
719
1266
  function clear() {
720
1267
  setSelectedItems([]);
721
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
+ }
722
1336
  return {
723
1337
  selectedItems,
724
1338
  selectedItem,
@@ -733,21 +1347,28 @@ function select(options) {
733
1347
  highlightNext,
734
1348
  highlightPrev,
735
1349
  selectHighlighted,
736
- clear
1350
+ clear,
1351
+ bind
737
1352
  };
738
1353
  }
739
1354
 
740
1355
  // src/widgets/Tooltip.ts
1356
+ var tooltipIdCounter = 0;
1357
+ var boundTriggers = /* @__PURE__ */ new WeakMap();
741
1358
  function tooltip(options) {
742
1359
  const delay = options?.delay ?? 0;
1360
+ const hideDelay = options?.hideDelay ?? 100;
743
1361
  const [isVisible, setIsVisible] = signal(false);
744
1362
  const [content, setContent] = signal("");
745
1363
  let delayTimer = null;
1364
+ let hideTimer = null;
746
1365
  function show() {
1366
+ if (hideTimer !== null) {
1367
+ clearTimeout(hideTimer);
1368
+ hideTimer = null;
1369
+ }
747
1370
  if (delay > 0) {
748
- if (delayTimer !== null) {
749
- clearTimeout(delayTimer);
750
- }
1371
+ if (delayTimer !== null) clearTimeout(delayTimer);
751
1372
  delayTimer = setTimeout(() => {
752
1373
  setIsVisible(true);
753
1374
  delayTimer = null;
@@ -763,10 +1384,82 @@ function tooltip(options) {
763
1384
  }
764
1385
  setIsVisible(false);
765
1386
  }
766
- 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 };
767
1458
  }
768
1459
 
769
1460
  // src/widgets/FileUpload.ts
1461
+ var fileUploadIdCounter = 0;
1462
+ var boundFileUploads = /* @__PURE__ */ new WeakMap();
770
1463
  function fileUpload(options) {
771
1464
  const accept = options?.accept;
772
1465
  const multiple = options?.multiple ?? false;
@@ -834,6 +1527,107 @@ function fileUpload(options) {
834
1527
  setErrors([]);
835
1528
  });
836
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
+ }
837
1631
  return {
838
1632
  files,
839
1633
  addFiles,
@@ -841,14 +1635,36 @@ function fileUpload(options) {
841
1635
  clear,
842
1636
  errors,
843
1637
  isDragOver,
844
- setDragOver
1638
+ setDragOver,
1639
+ bind
845
1640
  };
846
1641
  }
847
1642
 
1643
+ // src/utils/sanitize.ts
1644
+ function stripHtml(html) {
1645
+ return String(html).replace(/<[^>]*>/g, "");
1646
+ }
1647
+
848
1648
  // src/widgets/contentEditable.ts
849
1649
  function contentEditable() {
850
- const [content, setContent] = signal("");
1650
+ const [content, setContentInternal] = signal("");
851
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
+ }
852
1668
  function wrapSelection(tagName) {
853
1669
  if (typeof window === "undefined") return;
854
1670
  const selection = window.getSelection();
@@ -925,6 +1741,7 @@ function contentEditable() {
925
1741
  }
926
1742
 
927
1743
  // src/widgets/datePicker.ts
1744
+ var boundDatePickers = /* @__PURE__ */ new WeakMap();
928
1745
  function datePicker(options) {
929
1746
  const minDate = options?.minDate;
930
1747
  const maxDate = options?.maxDate;
@@ -948,33 +1765,20 @@ function datePicker(options) {
948
1765
  setSelectedDate(date);
949
1766
  }
950
1767
  }
1768
+ function shiftMonth(prev, delta) {
1769
+ return new Date(prev.getFullYear(), prev.getMonth() + delta, 1);
1770
+ }
951
1771
  function nextMonth() {
952
- setViewDate((prev) => {
953
- const next = new Date(prev);
954
- next.setMonth(next.getMonth() + 1);
955
- return next;
956
- });
1772
+ setViewDate((prev) => shiftMonth(prev, 1));
957
1773
  }
958
1774
  function prevMonth() {
959
- setViewDate((prev) => {
960
- const next = new Date(prev);
961
- next.setMonth(next.getMonth() - 1);
962
- return next;
963
- });
1775
+ setViewDate((prev) => shiftMonth(prev, -1));
964
1776
  }
965
1777
  function nextYear() {
966
- setViewDate((prev) => {
967
- const next = new Date(prev);
968
- next.setFullYear(next.getFullYear() + 1);
969
- return next;
970
- });
1778
+ setViewDate((prev) => new Date(prev.getFullYear() + 1, prev.getMonth(), 1));
971
1779
  }
972
1780
  function prevYear() {
973
- setViewDate((prev) => {
974
- const next = new Date(prev);
975
- next.setFullYear(next.getFullYear() - 1);
976
- return next;
977
- });
1781
+ setViewDate((prev) => new Date(prev.getFullYear() - 1, prev.getMonth(), 1));
978
1782
  }
979
1783
  const daysInMonth = derived(() => {
980
1784
  const vd = viewDate();
@@ -1039,8 +1843,87 @@ function datePicker(options) {
1039
1843
  daysInMonth,
1040
1844
  isDateDisabled,
1041
1845
  /** Reactive check — use inside class bindings for per-day reactivity */
1042
- isSelected
1846
+ isSelected,
1847
+ bind
1043
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
+ }
1044
1927
  }
1045
1928
  // Annotate the CommonJS export names for ESM import in node:
1046
1929
  0 && (module.exports = {