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/ui.cjs CHANGED
@@ -23,6 +23,7 @@ __export(ui_exports, {
23
23
  FocusTrap: () => FocusTrap,
24
24
  RenderProp: () => RenderProp,
25
25
  VirtualList: () => VirtualList,
26
+ __resetDialogStack: () => __resetDialogStack,
26
27
  announce: () => announce,
27
28
  aria: () => aria,
28
29
  assertType: () => assertType,
@@ -91,12 +92,12 @@ function isDev() {
91
92
  var _isDev = isDev();
92
93
  function devAssert(condition, message) {
93
94
  if (_isDev && !condition) {
94
- throw new Error(`[Sibu] ${message}`);
95
+ throw new Error(`[SibuJS] ${message}`);
95
96
  }
96
97
  }
97
98
  function devWarn(message) {
98
99
  if (_isDev) {
99
- console.warn(`[Sibu] ${message}`);
100
+ console.warn(`[SibuJS] ${message}`);
100
101
  }
101
102
  }
102
103
 
@@ -106,11 +107,11 @@ var subscriberStack = new Array(32);
106
107
  var stackCapacity = 32;
107
108
  var stackTop = -1;
108
109
  var currentSubscriber = null;
109
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
110
110
  var SUBS = "__s";
111
111
  var notifyDepth = 0;
112
112
  var pendingQueue = [];
113
113
  var pendingSet = /* @__PURE__ */ new Set();
114
+ var propagateStack = [];
114
115
  function safeInvoke(sub) {
115
116
  try {
116
117
  sub();
@@ -119,6 +120,15 @@ function safeInvoke(sub) {
119
120
  }
120
121
  }
121
122
  var trackingSuspended = false;
123
+ function retrack(effectFn, subscriber) {
124
+ const prev = currentSubscriber;
125
+ currentSubscriber = subscriber;
126
+ try {
127
+ effectFn();
128
+ } finally {
129
+ currentSubscriber = prev;
130
+ }
131
+ }
122
132
  function track(effectFn, subscriber) {
123
133
  if (!subscriber) subscriber = effectFn;
124
134
  cleanup(subscriber);
@@ -157,7 +167,6 @@ function recordDependency(signal2) {
157
167
  let subs = signal2[SUBS];
158
168
  if (!subs) {
159
169
  subs = /* @__PURE__ */ new Set();
160
- signalSubscribers.set(signal2, subs);
161
170
  signal2[SUBS] = subs;
162
171
  }
163
172
  subs.add(currentSubscriber);
@@ -167,42 +176,46 @@ function recordDependency(signal2) {
167
176
  signal2.__f = void 0;
168
177
  }
169
178
  }
179
+ var maxDrainIterations = 1e5;
170
180
  function propagateDirty(sub) {
171
181
  sub();
172
- let sig = sub._sig;
173
- while (sig) {
182
+ const rootSig = sub._sig;
183
+ if (!rootSig) return;
184
+ const stack = propagateStack;
185
+ const baseLen = stack.length;
186
+ stack.push(rootSig);
187
+ while (stack.length > baseLen) {
188
+ const sig = stack.pop();
174
189
  const first = sig.__f;
175
190
  if (first) {
176
191
  if (first._c) {
177
192
  const nSig = first._sig;
178
- nSig._d = true;
179
- sig = nSig;
180
- continue;
181
- }
182
- if (!pendingSet.has(first)) {
193
+ if (!nSig._d) {
194
+ nSig._d = true;
195
+ stack.push(nSig);
196
+ }
197
+ } else if (!pendingSet.has(first)) {
183
198
  pendingSet.add(first);
184
199
  pendingQueue.push(first);
185
200
  }
186
- break;
201
+ continue;
187
202
  }
188
203
  const subs = sig[SUBS];
189
- if (!subs) break;
190
- let nextSig;
204
+ if (!subs) continue;
191
205
  for (const s of subs) {
192
206
  if (s._c) {
193
- s();
194
207
  const nSig = s._sig;
195
- if (nSig && !nextSig) {
196
- nextSig = nSig;
197
- } else if (nSig) {
198
- propagateDirty(s);
208
+ if (nSig && !nSig._d) {
209
+ nSig._d = true;
210
+ stack.push(nSig);
211
+ } else if (!nSig) {
212
+ s();
199
213
  }
200
214
  } else if (!pendingSet.has(s)) {
201
215
  pendingSet.add(s);
202
216
  pendingQueue.push(s);
203
217
  }
204
218
  }
205
- sig = nextSig;
206
219
  }
207
220
  }
208
221
  function notifySubscribers(signal2) {
@@ -226,13 +239,23 @@ function notifySubscribers(signal2) {
226
239
  }
227
240
  let i = 0;
228
241
  while (i < pendingQueue.length) {
242
+ if (i >= maxDrainIterations) {
243
+ if (typeof console !== "undefined") {
244
+ console.error(
245
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
246
+ );
247
+ }
248
+ break;
249
+ }
229
250
  safeInvoke(pendingQueue[i]);
230
251
  i++;
231
252
  }
232
253
  } finally {
233
- pendingQueue.length = 0;
234
- pendingSet.clear();
235
254
  notifyDepth--;
255
+ if (notifyDepth === 0) {
256
+ pendingQueue.length = 0;
257
+ pendingSet.clear();
258
+ }
236
259
  }
237
260
  return;
238
261
  }
@@ -252,30 +275,48 @@ function notifySubscribers(signal2) {
252
275
  notifyDepth++;
253
276
  try {
254
277
  let directCount = 0;
278
+ let hasComputedSub = false;
255
279
  for (const sub of subs) {
280
+ if (sub._c) hasComputedSub = true;
256
281
  pendingQueue[directCount++] = sub;
257
282
  }
258
- for (let i2 = 0; i2 < directCount; i2++) {
259
- if (pendingQueue[i2]._c) {
260
- propagateDirty(pendingQueue[i2]);
283
+ if (!hasComputedSub) {
284
+ for (let i2 = 0; i2 < directCount; i2++) {
285
+ safeInvoke(pendingQueue[i2]);
261
286
  }
262
- }
263
- for (let i2 = 0; i2 < directCount; i2++) {
264
- if (!pendingQueue[i2]._c) {
265
- if (!pendingSet.has(pendingQueue[i2])) {
266
- safeInvoke(pendingQueue[i2]);
287
+ } else {
288
+ for (let i2 = 0; i2 < directCount; i2++) {
289
+ if (pendingQueue[i2]._c) {
290
+ propagateDirty(pendingQueue[i2]);
291
+ }
292
+ }
293
+ for (let i2 = 0; i2 < directCount; i2++) {
294
+ const sub = pendingQueue[i2];
295
+ if (!sub._c && !pendingSet.has(sub)) {
296
+ pendingSet.add(sub);
297
+ safeInvoke(sub);
267
298
  }
268
299
  }
269
300
  }
270
301
  let i = directCount;
271
302
  while (i < pendingQueue.length) {
303
+ if (i - directCount >= maxDrainIterations) {
304
+ if (typeof console !== "undefined") {
305
+ console.error(
306
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
307
+ );
308
+ }
309
+ break;
310
+ }
272
311
  safeInvoke(pendingQueue[i]);
273
312
  i++;
274
313
  }
275
314
  } finally {
276
- pendingQueue.length = 0;
277
- pendingSet.clear();
278
315
  notifyDepth--;
316
+ if (notifyDepth === 0) {
317
+ pendingQueue.length = 0;
318
+ pendingSet.clear();
319
+ }
279
320
  }
280
321
  }
281
322
  function cleanup(subscriber) {
@@ -286,7 +327,9 @@ function cleanup(subscriber) {
286
327
  if (subs) {
287
328
  subs.delete(subscriber);
288
329
  if (singleDep.__f === subscriber) {
289
- singleDep.__f = void 0;
330
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
331
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
332
+ singleDep.__f = subs.values().next().value;
290
333
  }
291
334
  }
292
335
  sub._dep = void 0;
@@ -299,7 +342,9 @@ function cleanup(subscriber) {
299
342
  if (subs) {
300
343
  subs.delete(subscriber);
301
344
  if (signal2.__f === subscriber) {
302
- signal2.__f = void 0;
345
+ signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
346
+ } else if (subs.size === 1 && signal2.__f === void 0) {
347
+ signal2.__f = subs.values().next().value;
303
348
  }
304
349
  }
305
350
  }
@@ -310,6 +355,7 @@ function cleanup(subscriber) {
310
355
  function derived(getter, options) {
311
356
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
312
357
  const debugName = options?.name;
358
+ const equals = options?.equals;
313
359
  const cs = {};
314
360
  cs._d = false;
315
361
  cs._g = getter;
@@ -320,25 +366,56 @@ function derived(getter, options) {
320
366
  markDirty._c = 1;
321
367
  markDirty._sig = cs;
322
368
  track(() => {
323
- cs._d = false;
324
- cs._v = getter();
369
+ let threw = true;
370
+ try {
371
+ cs._v = getter();
372
+ cs._d = false;
373
+ threw = false;
374
+ } finally {
375
+ if (threw) cs._d = true;
376
+ }
325
377
  }, markDirty);
326
378
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
379
+ let evaluating = false;
327
380
  function computedGetter() {
381
+ if (evaluating) {
382
+ throw new Error(
383
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
384
+ );
385
+ }
328
386
  if (trackingSuspended) {
329
387
  if (cs._d) {
330
- cs._d = false;
331
- cs._v = getter();
388
+ evaluating = true;
389
+ let threw = true;
390
+ try {
391
+ retrack(() => {
392
+ cs._v = getter();
393
+ cs._d = false;
394
+ threw = false;
395
+ }, markDirty);
396
+ } finally {
397
+ evaluating = false;
398
+ if (threw) cs._d = true;
399
+ }
332
400
  }
333
401
  return cs._v;
334
402
  }
335
403
  recordDependency(cs);
336
404
  if (cs._d) {
337
405
  const oldValue = cs._v;
338
- track(() => {
339
- cs._d = false;
340
- cs._v = getter();
341
- }, markDirty);
406
+ evaluating = true;
407
+ let threw = true;
408
+ try {
409
+ retrack(() => {
410
+ const next = getter();
411
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
412
+ cs._d = false;
413
+ threw = false;
414
+ }, markDirty);
415
+ } finally {
416
+ evaluating = false;
417
+ if (threw) cs._d = true;
418
+ }
342
419
  if (hook && oldValue !== cs._v) {
343
420
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
344
421
  }
@@ -477,7 +554,7 @@ function bindField(field, extras) {
477
554
  blur: () => field.touch()
478
555
  };
479
556
  const { on: extraOn, value: _ignoreValue, ...restExtras } = extras ?? {};
480
- const mergedOn = extraOn && typeof extraOn === "object" ? { ...fieldOn, ...extraOn } : fieldOn;
557
+ const mergedOn = extraOn && typeof extraOn === "object" ? { ...extraOn, ...fieldOn } : fieldOn;
481
558
  return {
482
559
  value: field.value,
483
560
  on: mergedOn,
@@ -502,9 +579,18 @@ function form(config) {
502
579
  }
503
580
  return null;
504
581
  });
582
+ const wrappedSet = (next) => {
583
+ setValue(next);
584
+ setManualErrors((prev) => {
585
+ if (!(name in prev) || prev[name] == null) return prev;
586
+ const copy = { ...prev };
587
+ copy[name] = null;
588
+ return copy;
589
+ });
590
+ };
505
591
  fieldMap[name] = {
506
592
  value,
507
- set: setValue,
593
+ set: wrappedSet,
508
594
  error,
509
595
  touched: isTouched,
510
596
  touch: () => setTouched(true),
@@ -548,14 +634,23 @@ function form(config) {
548
634
  }
549
635
  return result;
550
636
  });
637
+ const [submitting, setSubmitting] = signal(false);
551
638
  function handleSubmit(onSubmit) {
552
639
  return (e) => {
553
640
  if (e) e.preventDefault();
641
+ if (submitting()) return;
554
642
  for (const field of Object.values(fieldMap)) {
555
643
  field.touch();
556
644
  }
557
645
  if (isValid()) {
558
- onSubmit(values());
646
+ const result = onSubmit(values());
647
+ if (result && typeof result.then === "function") {
648
+ setSubmitting(true);
649
+ result.then(
650
+ () => setSubmitting(false),
651
+ () => setSubmitting(false)
652
+ );
653
+ }
559
654
  }
560
655
  };
561
656
  }
@@ -572,6 +667,7 @@ function form(config) {
572
667
  errors,
573
668
  isValid,
574
669
  isDirty,
670
+ submitting,
575
671
  touched: touchedState,
576
672
  values,
577
673
  handleSubmit,
@@ -615,10 +711,90 @@ function formAction(fn) {
615
711
  return { run, pending, error, result, reset, onSubmit };
616
712
  }
617
713
 
714
+ // src/core/rendering/dispose.ts
715
+ var elementDisposers = /* @__PURE__ */ new WeakMap();
716
+ var _isDev4 = isDev();
717
+ var activeBindingCount = 0;
718
+ function registerDisposer(node, teardown) {
719
+ let disposers = elementDisposers.get(node);
720
+ if (!disposers) {
721
+ disposers = [];
722
+ elementDisposers.set(node, disposers);
723
+ }
724
+ disposers.push(teardown);
725
+ if (_isDev4) activeBindingCount++;
726
+ }
727
+ function dispose(node) {
728
+ const stack = [node];
729
+ const order = [];
730
+ while (stack.length > 0) {
731
+ const current = stack.pop();
732
+ order.push(current);
733
+ const children = Array.from(current.childNodes);
734
+ for (let i = 0; i < children.length; i++) {
735
+ stack.push(children[i]);
736
+ }
737
+ }
738
+ for (let i = order.length - 1; i >= 0; i--) {
739
+ const current = order[i];
740
+ const disposers = elementDisposers.get(current);
741
+ if (disposers) {
742
+ const snapshot = disposers.slice();
743
+ elementDisposers.delete(current);
744
+ if (_isDev4) activeBindingCount -= snapshot.length;
745
+ for (const d of snapshot) {
746
+ try {
747
+ d();
748
+ } catch (err) {
749
+ if (_isDev4 && typeof console !== "undefined") {
750
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
751
+ }
752
+ }
753
+ }
754
+ let extraPasses = 0;
755
+ while (extraPasses++ < 8) {
756
+ const added = elementDisposers.get(current);
757
+ if (!added || added.length === 0) break;
758
+ const moreSnapshot = added.slice();
759
+ elementDisposers.delete(current);
760
+ if (_isDev4) activeBindingCount -= moreSnapshot.length;
761
+ for (const d of moreSnapshot) {
762
+ try {
763
+ d();
764
+ } catch (err) {
765
+ if (_isDev4 && typeof console !== "undefined") {
766
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
767
+ }
768
+ }
769
+ }
770
+ }
771
+ }
772
+ }
773
+ }
774
+
618
775
  // src/core/ssr-context.ts
619
- var ssrMode = false;
776
+ var als = null;
777
+ try {
778
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
779
+ const req = Function("return typeof require==='function'?require:null")();
780
+ if (req) {
781
+ const mod = req("node:async_hooks");
782
+ als = new mod.AsyncLocalStorage();
783
+ }
784
+ }
785
+ } catch {
786
+ als = null;
787
+ }
788
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
789
+ function getSSRStore() {
790
+ if (als) {
791
+ const s = als.getStore();
792
+ if (s) return s;
793
+ }
794
+ return fallbackStore;
795
+ }
620
796
  function isSSR() {
621
- return ssrMode;
797
+ return getSSRStore().ssr;
622
798
  }
623
799
 
624
800
  // src/core/signals/effect.ts
@@ -628,26 +804,86 @@ function effect(effectFn, options) {
628
804
  if (isSSR()) return () => {
629
805
  };
630
806
  const onError = options?.onError;
807
+ let userCleanups = [];
808
+ const onCleanup = (fn) => {
809
+ userCleanups.push(fn);
810
+ };
811
+ const runUserCleanups = () => {
812
+ if (userCleanups.length === 0) return;
813
+ const list = userCleanups;
814
+ userCleanups = [];
815
+ for (let i = list.length - 1; i >= 0; i--) {
816
+ try {
817
+ list[i]();
818
+ } catch (err) {
819
+ if (typeof console !== "undefined") {
820
+ console.warn("[SibuJS effect] onCleanup threw:", err);
821
+ }
822
+ }
823
+ }
824
+ };
825
+ const invokeBody = () => effectFn(onCleanup);
631
826
  const wrappedFn = onError ? () => {
632
827
  try {
633
- effectFn();
828
+ invokeBody();
634
829
  } catch (err) {
635
830
  onError(err);
636
831
  }
637
- } : effectFn;
832
+ } : invokeBody;
638
833
  let cleanupHandle = () => {
639
834
  };
835
+ let running = false;
640
836
  const subscriber = () => {
641
- cleanupHandle();
642
- cleanupHandle = track(wrappedFn, subscriber);
837
+ if (running) {
838
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
839
+ console.warn(
840
+ "[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."
841
+ );
842
+ }
843
+ return;
844
+ }
845
+ running = true;
846
+ try {
847
+ runUserCleanups();
848
+ cleanupHandle();
849
+ cleanupHandle = track(wrappedFn, subscriber);
850
+ } finally {
851
+ running = false;
852
+ }
643
853
  };
644
- cleanupHandle = track(wrappedFn, subscriber);
854
+ running = true;
855
+ try {
856
+ cleanupHandle = track(wrappedFn, subscriber);
857
+ } finally {
858
+ running = false;
859
+ }
645
860
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
646
861
  if (hook) hook.emit("effect:create", { effectFn });
862
+ let disposed = false;
647
863
  return () => {
864
+ if (disposed) return;
865
+ disposed = true;
648
866
  const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
649
- if (h) h.emit("effect:destroy", { effectFn });
650
- cleanupHandle();
867
+ if (h) {
868
+ try {
869
+ h.emit("effect:destroy", { effectFn });
870
+ } catch {
871
+ }
872
+ }
873
+ try {
874
+ runUserCleanups();
875
+ } catch (err) {
876
+ if (typeof console !== "undefined") {
877
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
878
+ }
879
+ }
880
+ try {
881
+ cleanupHandle();
882
+ } catch (err) {
883
+ if (typeof console !== "undefined") {
884
+ console.warn("[SibuJS effect] dispose threw:", err);
885
+ }
886
+ }
651
887
  };
652
888
  }
653
889
 
@@ -668,9 +904,9 @@ function VirtualList(props) {
668
904
  content.style.right = "0";
669
905
  spacer.appendChild(content);
670
906
  container.appendChild(spacer);
671
- container.addEventListener("scroll", () => {
672
- setScrollTop(container.scrollTop);
673
- });
907
+ const onScroll = () => setScrollTop(container.scrollTop);
908
+ container.addEventListener("scroll", onScroll);
909
+ registerDisposer(container, () => container.removeEventListener("scroll", onScroll));
674
910
  const update = () => {
675
911
  const items = props.items();
676
912
  const totalHeight = items.length * props.itemHeight;
@@ -699,6 +935,7 @@ function intersection(options) {
699
935
  let observer = null;
700
936
  let currentElement = null;
701
937
  function observe(element) {
938
+ if (typeof IntersectionObserver === "undefined") return;
702
939
  unobserve();
703
940
  currentElement = element;
704
941
  observer = new IntersectionObserver((entries) => {
@@ -726,6 +963,11 @@ function intersection(options) {
726
963
  };
727
964
  }
728
965
  function lazyLoad(element, loader, options) {
966
+ if (typeof IntersectionObserver === "undefined") {
967
+ loader();
968
+ return () => {
969
+ };
970
+ }
729
971
  const observer = new IntersectionObserver((entries) => {
730
972
  for (const entry of entries) {
731
973
  if (entry.isIntersecting) {
@@ -778,20 +1020,65 @@ function inputMask(options) {
778
1020
  }
779
1021
  return raw;
780
1022
  }
1023
+ function isSlot(c) {
1024
+ return c === "9" || c === "A" || c === "*";
1025
+ }
1026
+ function buildStripRegex() {
1027
+ const hasDigit = options.pattern.includes("9");
1028
+ const hasLetter = options.pattern.includes("A");
1029
+ const hasAny = options.pattern.includes("*");
1030
+ if (hasAny) {
1031
+ const literals = /* @__PURE__ */ new Set();
1032
+ for (const c of options.pattern) {
1033
+ if (!isSlot(c)) literals.add(c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1034
+ }
1035
+ return literals.size > 0 ? new RegExp(`[${Array.from(literals).join("")}]`, "g") : /(?!)/g;
1036
+ }
1037
+ if (hasDigit && hasLetter) return /[^a-zA-Z0-9]/g;
1038
+ if (hasDigit) return /[^0-9]/g;
1039
+ if (hasLetter) return /[^a-zA-Z]/g;
1040
+ return /[^a-zA-Z0-9]/g;
1041
+ }
1042
+ const stripRegex = buildStripRegex();
1043
+ const rawCharTest = options.pattern.includes("*") ? () => true : (c) => /[a-zA-Z0-9]/.test(c);
781
1044
  function bind(input) {
782
- input.addEventListener("input", () => {
783
- const raw = input.value.replace(/[^a-zA-Z0-9]/g, "");
1045
+ const onInput = () => {
1046
+ const cursorBefore = input.selectionStart ?? input.value.length;
1047
+ const oldValue = input.value;
1048
+ const raw = oldValue.replace(stripRegex, "");
784
1049
  const masked = applyMask(raw);
785
1050
  setValue(masked);
786
1051
  setRawValue(extractRaw(masked));
787
1052
  input.value = masked;
788
- });
789
- input.addEventListener("focus", () => {
1053
+ let rawBefore = 0;
1054
+ for (let i = 0; i < cursorBefore && i < oldValue.length; i++) {
1055
+ if (rawCharTest(oldValue[i])) rawBefore++;
1056
+ }
1057
+ let newCursor = 0;
1058
+ let counted = 0;
1059
+ for (; newCursor < masked.length; newCursor++) {
1060
+ if (newCursor < options.pattern.length && isSlot(options.pattern[newCursor])) {
1061
+ counted++;
1062
+ if (counted >= rawBefore) {
1063
+ newCursor++;
1064
+ break;
1065
+ }
1066
+ }
1067
+ }
1068
+ input.setSelectionRange(newCursor, newCursor);
1069
+ };
1070
+ const onFocus = () => {
790
1071
  if (!input.value) {
791
1072
  const display = options.pattern.replace(/9/g, placeholder).replace(/A/g, placeholder).replace(/\*/g, placeholder);
792
1073
  input.placeholder = display;
793
1074
  }
794
- });
1075
+ };
1076
+ input.addEventListener("input", onInput);
1077
+ input.addEventListener("focus", onFocus);
1078
+ return () => {
1079
+ input.removeEventListener("input", onInput);
1080
+ input.removeEventListener("focus", onFocus);
1081
+ };
795
1082
  }
796
1083
  return { value, rawValue, bind };
797
1084
  }
@@ -833,8 +1120,20 @@ function focus() {
833
1120
  let currentElement = null;
834
1121
  function bind(element) {
835
1122
  currentElement = element;
836
- element.addEventListener("focus", () => setIsFocused(true));
837
- element.addEventListener("blur", () => setIsFocused(false));
1123
+ const onFocus = () => setIsFocused(true);
1124
+ const onBlur = () => setIsFocused(false);
1125
+ element.addEventListener("focus", onFocus);
1126
+ element.addEventListener("blur", onBlur);
1127
+ let disposed = false;
1128
+ const dispose2 = () => {
1129
+ if (disposed) return;
1130
+ disposed = true;
1131
+ element.removeEventListener("focus", onFocus);
1132
+ element.removeEventListener("blur", onBlur);
1133
+ if (currentElement === element) currentElement = null;
1134
+ };
1135
+ registerDisposer(element, dispose2);
1136
+ return dispose2;
838
1137
  }
839
1138
  function focus2() {
840
1139
  currentElement?.focus();
@@ -849,12 +1148,39 @@ function FocusTrap(nodes, options = {}) {
849
1148
  container.setAttribute("data-sibu-focus-trap", "true");
850
1149
  container.appendChild(nodes);
851
1150
  const previouslyFocused = document.activeElement;
852
- container.addEventListener("keydown", (e) => {
1151
+ const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable]';
1152
+ function isEffectivelyVisible(el) {
1153
+ let node = el;
1154
+ while (node) {
1155
+ if (node.hasAttribute("inert")) return false;
1156
+ if (node.getAttribute("aria-hidden") === "true") return false;
1157
+ if (node.hidden) return false;
1158
+ node = node.parentElement;
1159
+ }
1160
+ if (el.offsetParent === null && el.getClientRects().length === 0) return false;
1161
+ return true;
1162
+ }
1163
+ function getFocusable() {
1164
+ const raw = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
1165
+ const out = [];
1166
+ for (const el of raw) {
1167
+ if (el.hasAttribute("disabled")) continue;
1168
+ if (el.getAttribute("aria-hidden") === "true") continue;
1169
+ if (el.hasAttribute("inert")) continue;
1170
+ const ce = el.getAttribute("contenteditable");
1171
+ if (ce !== null && ce === "false") continue;
1172
+ if (!isEffectivelyVisible(el)) continue;
1173
+ out.push(el);
1174
+ }
1175
+ return out;
1176
+ }
1177
+ const onTrapKeydown = (e) => {
853
1178
  if (e.key !== "Tab") return;
854
- const focusable = container.querySelectorAll(
855
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
856
- );
857
- if (focusable.length === 0) return;
1179
+ const focusable = getFocusable();
1180
+ if (focusable.length === 0) {
1181
+ e.preventDefault();
1182
+ return;
1183
+ }
858
1184
  const first = focusable[0];
859
1185
  const last = focusable[focusable.length - 1];
860
1186
  if (e.shiftKey) {
@@ -868,33 +1194,36 @@ function FocusTrap(nodes, options = {}) {
868
1194
  first.focus();
869
1195
  }
870
1196
  }
871
- });
1197
+ };
1198
+ container.addEventListener("keydown", onTrapKeydown);
872
1199
  if (options.autoFocus !== false) {
873
1200
  queueMicrotask(() => {
874
- const first = container.querySelector(
875
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
876
- );
1201
+ const first = getFocusable()[0];
877
1202
  first?.focus();
878
1203
  });
879
1204
  }
1205
+ let trapObserver = null;
1206
+ function restoreFocusAndCleanup() {
1207
+ if (options.restoreFocus !== false) previouslyFocused?.focus();
1208
+ container.removeEventListener("keydown", onTrapKeydown);
1209
+ if (trapObserver) {
1210
+ trapObserver.disconnect();
1211
+ trapObserver = null;
1212
+ }
1213
+ }
880
1214
  if (options.restoreFocus !== false) {
881
- const observer = new MutationObserver((mutations) => {
882
- for (const mutation of mutations) {
883
- for (const removed of Array.from(mutation.removedNodes)) {
884
- if (removed === container || removed.contains(container)) {
885
- previouslyFocused?.focus();
886
- observer.disconnect();
887
- return;
888
- }
889
- }
1215
+ trapObserver = new MutationObserver(() => {
1216
+ if (!container.isConnected) {
1217
+ restoreFocusAndCleanup();
890
1218
  }
891
1219
  });
892
1220
  queueMicrotask(() => {
893
- if (container.parentNode) {
894
- observer.observe(container.parentNode, { childList: true });
1221
+ if (container.isConnected) {
1222
+ trapObserver.observe(container, { childList: true, subtree: true });
895
1223
  }
896
1224
  });
897
1225
  }
1226
+ registerDisposer(container, restoreFocusAndCleanup);
898
1227
  return container;
899
1228
  }
900
1229
  function hotkey(combo, handler, options = {}) {
@@ -927,7 +1256,16 @@ function hotkey(combo, handler, options = {}) {
927
1256
  document.addEventListener("keydown", listener);
928
1257
  return () => document.removeEventListener("keydown", listener);
929
1258
  }
930
- function announce(message, priority = "polite") {
1259
+ var announceQueues = {
1260
+ polite: [],
1261
+ assertive: []
1262
+ };
1263
+ var announceDraining = {
1264
+ polite: false,
1265
+ assertive: false
1266
+ };
1267
+ var ANNOUNCE_INTERVAL_MS = 150;
1268
+ function ensureLiveRegion(priority) {
931
1269
  let region = document.getElementById(`sibu-announce-${priority}`);
932
1270
  if (!region) {
933
1271
  region = document.createElement("div");
@@ -938,11 +1276,33 @@ function announce(message, priority = "polite") {
938
1276
  region.style.cssText = "position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;";
939
1277
  document.body.appendChild(region);
940
1278
  }
1279
+ return region;
1280
+ }
1281
+ function drainAnnounceQueue(priority) {
1282
+ if (announceDraining[priority]) return;
1283
+ const queue = announceQueues[priority];
1284
+ if (queue.length === 0) return;
1285
+ announceDraining[priority] = true;
1286
+ const region = ensureLiveRegion(priority);
1287
+ const next = queue.shift();
941
1288
  region.textContent = "";
942
1289
  requestAnimationFrame(() => {
943
- if (region) region.textContent = message;
1290
+ if (!region.isConnected) {
1291
+ announceDraining[priority] = false;
1292
+ return;
1293
+ }
1294
+ region.textContent = next;
1295
+ setTimeout(() => {
1296
+ announceDraining[priority] = false;
1297
+ drainAnnounceQueue(priority);
1298
+ }, ANNOUNCE_INTERVAL_MS);
944
1299
  });
945
1300
  }
1301
+ function announce(message, priority = "polite") {
1302
+ if (typeof document === "undefined") return;
1303
+ announceQueues[priority].push(message);
1304
+ drainAnnounceQueue(priority);
1305
+ }
946
1306
 
947
1307
  // src/core/rendering/createId.ts
948
1308
  var idCounter = 0;
@@ -951,20 +1311,6 @@ function createId(prefix = "sibu") {
951
1311
  return `${prefix}-${idCounter}`;
952
1312
  }
953
1313
 
954
- // src/core/rendering/dispose.ts
955
- var elementDisposers = /* @__PURE__ */ new WeakMap();
956
- var _isDev4 = isDev();
957
- var activeBindingCount = 0;
958
- function registerDisposer(node, teardown) {
959
- let disposers = elementDisposers.get(node);
960
- if (!disposers) {
961
- disposers = [];
962
- elementDisposers.set(node, disposers);
963
- }
964
- disposers.push(teardown);
965
- if (_isDev4) activeBindingCount++;
966
- }
967
-
968
1314
  // src/ui/a11yPrimitives.ts
969
1315
  var DEFAULT_FOCUS_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
970
1316
  function createFocusManager(container, options = {}) {
@@ -1050,22 +1396,22 @@ function createListbox(container, options = {}) {
1050
1396
  }
1051
1397
  }
1052
1398
  function select(value) {
1399
+ const previous = selectedValue();
1400
+ let nextSelectedSet;
1053
1401
  if (multiple) {
1054
- const current2 = selectedValue();
1055
- const set = new Set((current2 ?? "").split(",").filter(Boolean));
1056
- if (set.has(value)) set.delete(value);
1057
- else set.add(value);
1058
- setSelectedValue(Array.from(set).join(","));
1402
+ nextSelectedSet = new Set((previous ?? "").split(",").filter(Boolean));
1403
+ if (nextSelectedSet.has(value)) nextSelectedSet.delete(value);
1404
+ else nextSelectedSet.add(value);
1405
+ setSelectedValue(Array.from(nextSelectedSet).join(","));
1059
1406
  } else {
1407
+ nextSelectedSet = /* @__PURE__ */ new Set([value]);
1060
1408
  setSelectedValue(value);
1061
1409
  }
1062
1410
  options.onSelect?.(value);
1063
1411
  const opts = getOptions();
1064
- const current = selectedValue();
1065
- const selected = new Set((current ?? "").split(",").filter(Boolean));
1066
1412
  for (const opt of opts) {
1067
1413
  const ov = opt.dataset.value ?? "";
1068
- opt.setAttribute("aria-selected", selected.has(ov) ? "true" : "false");
1414
+ opt.setAttribute("aria-selected", nextSelectedSet.has(ov) ? "true" : "false");
1069
1415
  }
1070
1416
  }
1071
1417
  function moveActive(delta) {
@@ -1123,12 +1469,12 @@ function createListbox(container, options = {}) {
1123
1469
  }
1124
1470
  container.addEventListener("keydown", onKeyDown);
1125
1471
  container.addEventListener("click", onClick);
1126
- function dispose() {
1472
+ function dispose2() {
1127
1473
  container.removeEventListener("keydown", onKeyDown);
1128
1474
  container.removeEventListener("click", onClick);
1129
1475
  }
1130
- registerDisposer(container, dispose);
1131
- return { activeValue, selectedValue, activeDescendantId, dispose };
1476
+ registerDisposer(container, dispose2);
1477
+ return { activeValue, selectedValue, activeDescendantId, dispose: dispose2 };
1132
1478
  }
1133
1479
  function createDialogAria(element, options = {}) {
1134
1480
  const titleId = options.labelledBy ?? createId("dialog-title");
@@ -1177,6 +1523,10 @@ function scopedStyle(css) {
1177
1523
  if (trimmed.startsWith("@") || trimmed.startsWith("from") || trimmed.startsWith("to") || /^\d+%$/.test(trimmed)) {
1178
1524
  return match;
1179
1525
  }
1526
+ const pseudoIdx = trimmed.indexOf("::");
1527
+ if (pseudoIdx >= 0) {
1528
+ return `${trimmed.slice(0, pseudoIdx)}[${attr}]${trimmed.slice(pseudoIdx)}${delimiter}`;
1529
+ }
1180
1530
  return `${trimmed}[${attr}]${delimiter}`;
1181
1531
  });
1182
1532
  if (typeof document !== "undefined") {
@@ -1212,22 +1562,49 @@ function removeScopedStyle(scopeId) {
1212
1562
  }
1213
1563
 
1214
1564
  // src/utils/sanitize.ts
1565
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
1215
1566
  function sanitizeUrl(url) {
1216
1567
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
1217
1568
  if (!trimmed) return "";
1218
1569
  const lower = trimmed.toLowerCase();
1219
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
1220
- return "";
1570
+ let schemeEnd = -1;
1571
+ for (let i = 0; i < lower.length; i++) {
1572
+ const ch = lower.charCodeAt(i);
1573
+ if (ch === 58) {
1574
+ schemeEnd = i;
1575
+ break;
1576
+ }
1577
+ if (ch === 47 || ch === 63 || ch === 35) break;
1221
1578
  }
1579
+ if (schemeEnd === -1) return trimmed;
1580
+ const scheme = lower.slice(0, schemeEnd + 1);
1581
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
1582
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
1222
1583
  return trimmed;
1223
1584
  }
1224
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
1585
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
1586
+ "href",
1587
+ "xlink:href",
1588
+ "src",
1589
+ "action",
1590
+ "formaction",
1591
+ "formtarget",
1592
+ "cite",
1593
+ "poster",
1594
+ "background",
1595
+ "srcset",
1596
+ "ping",
1597
+ "data"
1598
+ ]);
1225
1599
  function isUrlAttribute(attr) {
1226
1600
  return URL_ATTRIBUTES.has(attr);
1227
1601
  }
1228
1602
 
1229
1603
  // src/reactivity/bindAttribute.ts
1230
1604
  var _isDev5 = isDev();
1605
+ function setProp(el, key, val) {
1606
+ el[key] = val;
1607
+ }
1231
1608
  function isEventHandlerAttr(name) {
1232
1609
  if (name.length < 3) return false;
1233
1610
  const lower = name.toLowerCase();
@@ -1253,7 +1630,7 @@ function bindAttribute(el, attr, getter) {
1253
1630
  }
1254
1631
  if (typeof value === "boolean") {
1255
1632
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
1256
- el[attr] = value;
1633
+ setProp(el, attr, value);
1257
1634
  } else if (value) {
1258
1635
  el.setAttribute(attr, "");
1259
1636
  } else {
@@ -1263,7 +1640,7 @@ function bindAttribute(el, attr, getter) {
1263
1640
  }
1264
1641
  const str = String(value);
1265
1642
  if ((attr === "value" || attr === "checked") && attr in el) {
1266
- el[attr] = attr === "checked" ? Boolean(value) : str;
1643
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
1267
1644
  } else {
1268
1645
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
1269
1646
  }
@@ -1333,59 +1710,91 @@ function bindData(el, key, getter) {
1333
1710
  }
1334
1711
 
1335
1712
  // src/ui/dialog.ts
1713
+ var dialogStack = [];
1714
+ var globalListenerAttached = false;
1715
+ function __resetDialogStack() {
1716
+ while (dialogStack.length > 0) dialogStack.pop();
1717
+ if (typeof window !== "undefined" && globalListenerAttached) {
1718
+ window.removeEventListener("keydown", handleGlobalKeydown);
1719
+ globalListenerAttached = false;
1720
+ }
1721
+ }
1722
+ function handleGlobalKeydown(event) {
1723
+ if (event.key !== "Escape") return;
1724
+ const top = dialogStack[dialogStack.length - 1];
1725
+ if (top) top.close();
1726
+ }
1727
+ function ensureGlobalListener() {
1728
+ if (typeof window === "undefined" || globalListenerAttached) return;
1729
+ window.addEventListener("keydown", handleGlobalKeydown);
1730
+ globalListenerAttached = true;
1731
+ }
1732
+ function removeGlobalListenerIfIdle() {
1733
+ if (typeof window === "undefined") return;
1734
+ if (!globalListenerAttached) return;
1735
+ if (dialogStack.length > 0) return;
1736
+ window.removeEventListener("keydown", handleGlobalKeydown);
1737
+ globalListenerAttached = false;
1738
+ }
1336
1739
  function dialog() {
1337
1740
  const [isOpen, setIsOpen] = signal(false);
1338
- let listenerAttached = false;
1339
- function handleKeydown(event) {
1340
- if (event.key === "Escape") {
1341
- close();
1342
- }
1741
+ const entry = { close: () => close() };
1742
+ function pushOnStack() {
1743
+ if (dialogStack.indexOf(entry) !== -1) return;
1744
+ dialogStack.push(entry);
1745
+ ensureGlobalListener();
1746
+ }
1747
+ function removeFromStack() {
1748
+ const idx = dialogStack.indexOf(entry);
1749
+ if (idx !== -1) dialogStack.splice(idx, 1);
1750
+ removeGlobalListenerIfIdle();
1343
1751
  }
1344
1752
  function open() {
1753
+ if (isOpen()) return;
1345
1754
  setIsOpen(true);
1346
- if (typeof window !== "undefined" && !listenerAttached) {
1347
- window.addEventListener("keydown", handleKeydown);
1348
- listenerAttached = true;
1349
- }
1755
+ pushOnStack();
1350
1756
  }
1351
1757
  function close() {
1352
- setIsOpen(false);
1353
- if (typeof window !== "undefined" && listenerAttached) {
1354
- window.removeEventListener("keydown", handleKeydown);
1355
- listenerAttached = false;
1758
+ if (!isOpen()) {
1759
+ removeFromStack();
1760
+ return;
1356
1761
  }
1762
+ setIsOpen(false);
1763
+ removeFromStack();
1357
1764
  }
1358
1765
  function toggle() {
1359
- if (isOpen()) {
1360
- close();
1361
- } else {
1362
- open();
1363
- }
1766
+ if (isOpen()) close();
1767
+ else open();
1364
1768
  }
1365
- return { open, close, isOpen, toggle };
1769
+ function dispose2() {
1770
+ removeFromStack();
1771
+ setIsOpen(false);
1772
+ }
1773
+ return { open, close, isOpen, toggle, dispose: dispose2 };
1366
1774
  }
1367
1775
 
1368
1776
  // src/ui/toast.ts
1369
- var toastCounter = 0;
1370
1777
  function toast(options) {
1371
1778
  const duration = options?.duration ?? 3e3;
1372
1779
  const maxToasts = options?.maxToasts ?? Infinity;
1373
1780
  const [toasts, setToasts] = signal([]);
1374
1781
  const timers = /* @__PURE__ */ new Map();
1782
+ let toastCounter = 0;
1375
1783
  function show(message, type) {
1376
1784
  const id = `toast-${++toastCounter}`;
1377
1785
  const toast2 = { id, message, type };
1786
+ const trimmedIds = [];
1378
1787
  setToasts((prev) => {
1379
1788
  const next = [...prev, toast2];
1380
1789
  if (next.length > maxToasts) {
1381
1790
  const removed = next.splice(0, next.length - maxToasts);
1382
- for (const r of removed) {
1383
- clearTimerForToast(r.id);
1384
- }
1791
+ for (const r of removed) trimmedIds.push(r.id);
1385
1792
  }
1386
1793
  return next;
1387
1794
  });
1388
- if (duration > 0) {
1795
+ for (const tid of trimmedIds) clearTimerForToast(tid);
1796
+ const wasTrimmed = trimmedIds.indexOf(id) !== -1;
1797
+ if (duration > 0 && !wasTrimmed) {
1389
1798
  const timer = setTimeout(() => {
1390
1799
  dismiss(id);
1391
1800
  }, duration);
@@ -1471,14 +1880,14 @@ function infiniteScroll(options) {
1471
1880
  },
1472
1881
  configurable: true
1473
1882
  });
1474
- function dispose() {
1883
+ function dispose2() {
1475
1884
  disposed = true;
1476
1885
  if (observer) {
1477
1886
  observer.disconnect();
1478
1887
  observer = null;
1479
1888
  }
1480
1889
  }
1481
- return { sentinelRef: originalRef, loading, dispose };
1890
+ return { sentinelRef: originalRef, loading, dispose: dispose2 };
1482
1891
  }
1483
1892
 
1484
1893
  // src/ui/pagination.ts
@@ -1553,21 +1962,21 @@ function eventBus() {
1553
1962
  // src/ui/lazyEffect.ts
1554
1963
  function lazyEffect(element, effectFn, options) {
1555
1964
  if (typeof IntersectionObserver === "undefined") {
1556
- const dispose2 = effect(effectFn);
1557
- return dispose2;
1965
+ const dispose3 = effect(effectFn);
1966
+ return dispose3;
1558
1967
  }
1559
- let dispose = null;
1968
+ let dispose2 = null;
1560
1969
  let disposed = false;
1561
1970
  const observer = new IntersectionObserver(
1562
1971
  (entries) => {
1563
1972
  if (disposed) return;
1564
1973
  const entry = entries[0];
1565
1974
  if (!entry) return;
1566
- if (entry.isIntersecting && !dispose) {
1567
- dispose = effect(effectFn);
1568
- } else if (!entry.isIntersecting && dispose) {
1569
- dispose();
1570
- dispose = null;
1975
+ if (entry.isIntersecting && !dispose2) {
1976
+ dispose2 = effect(effectFn);
1977
+ } else if (!entry.isIntersecting && dispose2) {
1978
+ dispose2();
1979
+ dispose2 = null;
1571
1980
  }
1572
1981
  },
1573
1982
  { threshold: 0, ...options }
@@ -1576,9 +1985,9 @@ function lazyEffect(element, effectFn, options) {
1576
1985
  return () => {
1577
1986
  disposed = true;
1578
1987
  observer.disconnect();
1579
- if (dispose) {
1580
- dispose();
1581
- dispose = null;
1988
+ if (dispose2) {
1989
+ dispose2();
1990
+ dispose2 = null;
1582
1991
  }
1583
1992
  };
1584
1993
  }
@@ -1635,11 +2044,11 @@ function hover(target) {
1635
2044
  const onLeave = () => setHovered(false);
1636
2045
  target.addEventListener("pointerenter", onEnter);
1637
2046
  target.addEventListener("pointerleave", onLeave);
1638
- function dispose() {
2047
+ function dispose2() {
1639
2048
  target.removeEventListener("pointerenter", onEnter);
1640
2049
  target.removeEventListener("pointerleave", onLeave);
1641
2050
  }
1642
- return { hovered, dispose };
2051
+ return { hovered, dispose: dispose2 };
1643
2052
  }
1644
2053
 
1645
2054
  // src/ui/scrollLock.ts
@@ -1683,7 +2092,7 @@ function defineElement(name, component, options = {}) {
1683
2092
  class SibuElement extends HTMLElement {
1684
2093
  constructor() {
1685
2094
  super();
1686
- this._rendered = false;
2095
+ this._rendered = null;
1687
2096
  if (options.shadow !== false) {
1688
2097
  this._root = this.attachShadow({ mode: options.mode || "open" });
1689
2098
  } else {
@@ -1697,25 +2106,23 @@ function defineElement(name, component, options = {}) {
1697
2106
  this._render();
1698
2107
  }
1699
2108
  disconnectedCallback() {
1700
- if (this._root instanceof ShadowRoot) {
1701
- this._root.innerHTML = "";
1702
- }
1703
- this._rendered = false;
2109
+ this._teardown();
1704
2110
  }
1705
2111
  attributeChangedCallback() {
1706
2112
  if (this._rendered) {
1707
2113
  this._render();
1708
2114
  }
1709
2115
  }
2116
+ _teardown() {
2117
+ if (this._rendered) {
2118
+ dispose(this._rendered);
2119
+ this._rendered = null;
2120
+ }
2121
+ this._root.replaceChildren();
2122
+ }
1710
2123
  _render() {
2124
+ this._teardown();
1711
2125
  const props = this._getProps();
1712
- if (this._root instanceof ShadowRoot) {
1713
- this._root.innerHTML = "";
1714
- } else {
1715
- while (this._root.firstChild) {
1716
- this._root.removeChild(this._root.firstChild);
1717
- }
1718
- }
1719
2126
  if (options.styles && this._root instanceof ShadowRoot) {
1720
2127
  const styleEl = document.createElement("style");
1721
2128
  styleEl.textContent = options.styles;
@@ -1723,7 +2130,7 @@ function defineElement(name, component, options = {}) {
1723
2130
  }
1724
2131
  const el = component(props, this);
1725
2132
  this._root.appendChild(el);
1726
- this._rendered = true;
2133
+ this._rendered = el;
1727
2134
  }
1728
2135
  _getProps() {
1729
2136
  const props = {};
@@ -1922,6 +2329,7 @@ function createGuard(validator) {
1922
2329
  FocusTrap,
1923
2330
  RenderProp,
1924
2331
  VirtualList,
2332
+ __resetDialogStack,
1925
2333
  announce,
1926
2334
  aria,
1927
2335
  assertType,