sibujs 1.5.0 → 2.1.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 (208) hide show
  1. package/dist/browser.cjs +332 -121
  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 +1049 -344
  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-BMPL52BF.js → chunk-3DZP6OIT.js} +118 -66
  11. package/dist/chunk-3JHCYHWN.js +125 -0
  12. package/dist/{chunk-CZUGLNJS.js → chunk-45YP72ZQ.js} +3 -3
  13. package/dist/{chunk-JCDUJN2F.js → chunk-AMK2TYNW.js} +490 -153
  14. package/dist/{chunk-NHUC2QWH.js → chunk-CWBVQML6.js} +1 -1
  15. package/dist/{chunk-XHK6BDAJ.js → chunk-DRUZZAK4.js} +25 -8
  16. package/dist/{chunk-RJ46C3CS.js → chunk-GWWURC5M.js} +71 -20
  17. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  18. package/dist/{chunk-2BYQDGN3.js → chunk-KGYT6UO6.js} +234 -63
  19. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  20. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  21. package/dist/{chunk-XUEEGU5O.js → chunk-NASX6ST2.js} +16 -4
  22. package/dist/{chunk-VQDZK23A.js → chunk-O6EFQ3KT.js} +181 -66
  23. package/dist/{chunk-BGN5ZMP4.js → chunk-OJ3P4ECI.js} +14 -2
  24. package/dist/chunk-ON5MMR2J.js +1327 -0
  25. package/dist/{chunk-SFKNRVCU.js → chunk-P2HSJDDN.js} +135 -79
  26. package/dist/chunk-QO3WC6FS.js +384 -0
  27. package/dist/{chunk-WZSPOOER.js → chunk-RDTDJCAB.js} +8 -5
  28. package/dist/{chunk-7GRNSCFT.js → chunk-TH2ILCYW.js} +312 -185
  29. package/dist/chunk-UCS6AMJ7.js +79 -0
  30. package/dist/{chunk-VAPYJN4X.js → chunk-V6C4FADE.js} +93 -23
  31. package/dist/{chunk-OUZZEE4S.js → chunk-WANSMF2L.js} +17 -11
  32. package/dist/{chunk-23VV7YD3.js → chunk-WIPZPFBQ.js} +25 -30
  33. package/dist/chunk-WZA53FXU.js +149 -0
  34. package/dist/{chunk-BGTHZHJ5.js → chunk-ZAQSMOED.js} +188 -44
  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 +536 -151
  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 +613 -266
  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 +602 -197
  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 +3500 -1608
  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 +1055 -344
  54. package/dist/index.d.cts +85 -8
  55. package/dist/index.d.ts +85 -8
  56. package/dist/index.js +32 -16
  57. package/dist/{introspect-BumjnBKr.d.cts → introspect-2TOlQ7oa.d.cts} +25 -3
  58. package/dist/{introspect-CZrlcaYy.d.ts → introspect-DnIpHQQz.d.ts} +25 -3
  59. package/dist/motion.cjs +122 -63
  60. package/dist/motion.js +4 -4
  61. package/dist/patterns.cjs +450 -110
  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 +373 -149
  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 +729 -301
  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 +736 -274
  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 +303 -76
  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 +589 -178
  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 +1103 -146
  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-K4G4ZQNR.js +0 -286
  136. package/dist/chunk-K5ZUMYVS.js +0 -89
  137. package/dist/chunk-KQPDEVVS.js +0 -398
  138. package/dist/chunk-L6JRBDNS.js +0 -60
  139. package/dist/chunk-LA6KQEDU.js +0 -712
  140. package/dist/chunk-MB6QFH3I.js +0 -2776
  141. package/dist/chunk-MDVXJWFN.js +0 -304
  142. package/dist/chunk-MEZVEBPN.js +0 -2008
  143. package/dist/chunk-MK4ERFYL.js +0 -2249
  144. package/dist/chunk-MLKGABMK.js +0 -9
  145. package/dist/chunk-MQ5GOYPH.js +0 -2249
  146. package/dist/chunk-MYRV7VDM.js +0 -742
  147. package/dist/chunk-N6IZB6KJ.js +0 -567
  148. package/dist/chunk-NEKUBFPT.js +0 -60
  149. package/dist/chunk-NMRUZALC.js +0 -1097
  150. package/dist/chunk-NYVAC6P5.js +0 -37
  151. package/dist/chunk-NZIIMDWI.js +0 -84
  152. package/dist/chunk-OF7UZIVB.js +0 -725
  153. package/dist/chunk-P3XWXJZU.js +0 -282
  154. package/dist/chunk-P6W3STU4.js +0 -2249
  155. package/dist/chunk-PBHF5WKN.js +0 -616
  156. package/dist/chunk-PDZQY43A.js +0 -616
  157. package/dist/chunk-PTQJDMRT.js +0 -146
  158. package/dist/chunk-PZEGYCF5.js +0 -61
  159. package/dist/chunk-QBMDLBU2.js +0 -975
  160. package/dist/chunk-QWZG56ET.js +0 -2744
  161. package/dist/chunk-RQGQSLQK.js +0 -725
  162. package/dist/chunk-SDLZDHKP.js +0 -107
  163. package/dist/chunk-TDGZL5CU.js +0 -365
  164. package/dist/chunk-TNQWPPE6.js +0 -37
  165. package/dist/chunk-TSOKIX5Z.js +0 -654
  166. package/dist/chunk-UHNL42EF.js +0 -2730
  167. package/dist/chunk-UNXCEF6S.js +0 -21
  168. package/dist/chunk-V2XTI523.js +0 -347
  169. package/dist/chunk-VAU366PN.js +0 -2241
  170. package/dist/chunk-VMVDTCXB.js +0 -712
  171. package/dist/chunk-VQNQZCWJ.js +0 -61
  172. package/dist/chunk-VRW3FULF.js +0 -725
  173. package/dist/chunk-WADYRCO2.js +0 -304
  174. package/dist/chunk-WILQZRO4.js +0 -282
  175. package/dist/chunk-WR5D4EGH.js +0 -26
  176. package/dist/chunk-WUHJISPP.js +0 -298
  177. package/dist/chunk-XYU6TZOW.js +0 -182
  178. package/dist/chunk-Y6GP4QGG.js +0 -276
  179. package/dist/chunk-YECR7UIA.js +0 -347
  180. package/dist/chunk-YUTWTI4B.js +0 -654
  181. package/dist/chunk-Z65KYU7I.js +0 -26
  182. package/dist/chunk-Z6POF5YC.js +0 -975
  183. package/dist/chunk-ZBJP6WFL.js +0 -482
  184. package/dist/chunk-ZD6OAMTH.js +0 -277
  185. package/dist/chunk-ZWKZCBO6.js +0 -317
  186. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  187. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  188. package/dist/contracts-DOrhwbke.d.cts +0 -245
  189. package/dist/contracts-DOrhwbke.d.ts +0 -245
  190. package/dist/contracts-xo5ckdRP.d.cts +0 -240
  191. package/dist/contracts-xo5ckdRP.d.ts +0 -240
  192. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  193. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  194. package/dist/customElement-D2DJp_xn.d.cts +0 -313
  195. package/dist/customElement-D2DJp_xn.d.ts +0 -313
  196. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  197. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  198. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  199. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  200. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  201. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  202. package/dist/ssr-3RXHP5ES.js +0 -38
  203. package/dist/ssr-6GIMY5MX.js +0 -38
  204. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  205. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  206. package/dist/ssr-WKUPVSSK.js +0 -36
  207. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  208. package/dist/tagFactory-Dl8QCLga.d.ts +0 -23
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  context
3
- } from "./chunk-6BMPXPUW.js";
3
+ } from "./chunk-OJ3P4ECI.js";
4
4
  import {
5
5
  derived
6
- } from "./chunk-XHK6BDAJ.js";
6
+ } from "./chunk-DRUZZAK4.js";
7
7
  import {
8
8
  sanitizeUrl
9
- } from "./chunk-CMBFNA7L.js";
9
+ } from "./chunk-UCS6AMJ7.js";
10
10
  import {
11
11
  effect
12
- } from "./chunk-VQNQZCWJ.js";
12
+ } from "./chunk-WZA53FXU.js";
13
13
  import {
14
14
  batch,
15
15
  signal
16
- } from "./chunk-NZIIMDWI.js";
16
+ } from "./chunk-RDTDJCAB.js";
17
17
 
18
18
  // src/data/retry.ts
19
19
  function calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter) {
@@ -30,10 +30,12 @@ function calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter) {
30
30
  break;
31
31
  }
32
32
  delay = Math.min(delay, maxDelay);
33
+ if (!Number.isFinite(delay)) delay = Number.MAX_SAFE_INTEGER;
33
34
  if (jitter > 0) {
34
35
  const jitterRange = delay * jitter;
35
36
  delay += (Math.random() * 2 - 1) * jitterRange;
36
37
  }
38
+ if (!Number.isFinite(delay) || Number.isNaN(delay)) delay = 0;
37
39
  return Math.max(0, delay);
38
40
  }
39
41
  async function withRetry(fn, options, onRetry, signal2) {
@@ -47,6 +49,7 @@ async function withRetry(fn, options, onRetry, signal2) {
47
49
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
48
50
  if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
49
51
  try {
52
+ if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
50
53
  return await fn();
51
54
  } catch (error) {
52
55
  lastError = error;
@@ -128,24 +131,46 @@ function query(key, fetcher, options = {}) {
128
131
  let entry = queryCache.get(key2);
129
132
  if (!entry) {
130
133
  entry = getOrCreateEntry(key2);
131
- entry.subscribers++;
132
134
  entry.listeners.add(onCacheUpdate);
133
135
  entry.refetchers.add(doFetch);
134
136
  }
135
137
  if (entry.promise) {
136
138
  setIsFetching(true);
139
+ const captured = entry.promise;
137
140
  try {
138
- await entry.promise;
141
+ await captured;
142
+ if (disposed || currentKey !== key2) return;
143
+ if (entry.promise === captured) {
144
+ onCacheUpdate();
145
+ if (entry.error) onError?.(entry.error);
146
+ else if (entry.data !== void 0) onSuccess?.(entry.data);
147
+ }
139
148
  } catch {
149
+ if (disposed || currentKey !== key2) return;
150
+ if (entry.promise === captured) {
151
+ onCacheUpdate();
152
+ if (entry.error) onError?.(entry.error);
153
+ }
154
+ } finally {
155
+ if (!disposed && currentKey === key2) onSettled?.();
140
156
  }
141
- onCacheUpdate();
142
157
  return;
143
158
  }
144
159
  abortController?.abort();
145
160
  abortController = new AbortController();
146
161
  const signal2 = abortController.signal;
147
162
  setIsFetching(true);
148
- const promise = withRetry(() => fetcher({ signal: signal2, key: key2 }), retryOptions, void 0, signal2);
163
+ let promise;
164
+ try {
165
+ promise = withRetry(() => fetcher({ signal: signal2, key: key2 }), retryOptions, void 0, signal2);
166
+ } catch (err) {
167
+ setIsFetching(false);
168
+ const errorObj = err instanceof Error ? err : new Error(String(err));
169
+ entry.error = errorObj;
170
+ onError?.(errorObj);
171
+ onSettled?.();
172
+ return;
173
+ }
149
174
  entry.promise = promise;
150
175
  try {
151
176
  const result = await promise;
@@ -207,6 +232,7 @@ function query(key, fetcher, options = {}) {
207
232
  oldEntry.subscribers--;
208
233
  if (oldEntry.subscribers <= 0 && cacheTime >= 0) {
209
234
  const oldKey = currentKey;
235
+ if (oldEntry.gcTimer !== null) clearTimeout(oldEntry.gcTimer);
210
236
  oldEntry.gcTimer = setTimeout(() => queryCache.delete(oldKey), cacheTime);
211
237
  }
212
238
  }
@@ -214,7 +240,7 @@ function query(key, fetcher, options = {}) {
214
240
  const keyChanged = currentKey !== key2;
215
241
  currentKey = key2;
216
242
  const entry = getOrCreateEntry(key2, initialData);
217
- if (keyChanged || entry.subscribers === 0) entry.subscribers++;
243
+ if (keyChanged) entry.subscribers++;
218
244
  if (entry.gcTimer !== null) {
219
245
  clearTimeout(entry.gcTimer);
220
246
  entry.gcTimer = null;
@@ -229,6 +255,11 @@ function query(key, fetcher, options = {}) {
229
255
  setError(entry.error);
230
256
  });
231
257
  }
258
+ if (!keyChanged && currentKey === key2 && entry.data !== void 0) {
259
+ const isDataStale2 = entry.dataUpdatedAt === 0 || Date.now() - entry.dataUpdatedAt >= staleTime;
260
+ if (enabled && isDataStale2 && !entry.promise) doFetch();
261
+ return;
262
+ }
232
263
  const isDataStale = entry.dataUpdatedAt === 0 || Date.now() - entry.dataUpdatedAt >= staleTime;
233
264
  if (enabled && (entry.data === void 0 || isDataStale)) {
234
265
  doFetch();
@@ -256,6 +287,7 @@ function query(key, fetcher, options = {}) {
256
287
  }
257
288
  }
258
289
  function dispose() {
290
+ if (disposed) return;
259
291
  disposed = true;
260
292
  abortController?.abort();
261
293
  effectCleanup();
@@ -268,12 +300,17 @@ function query(key, fetcher, options = {}) {
268
300
  entry.subscribers--;
269
301
  if (entry.subscribers <= 0 && cacheTime >= 0) {
270
302
  const key2 = currentKey;
303
+ if (entry.gcTimer !== null) clearTimeout(entry.gcTimer);
271
304
  entry.gcTimer = setTimeout(() => queryCache.delete(key2), cacheTime);
272
305
  }
273
306
  }
274
307
  }
275
- if (focusHandler) globalThis.removeEventListener("focus", focusHandler);
276
- if (onlineHandler) globalThis.removeEventListener("online", onlineHandler);
308
+ if (focusHandler && typeof globalThis.removeEventListener === "function") {
309
+ globalThis.removeEventListener("focus", focusHandler);
310
+ }
311
+ if (onlineHandler && typeof globalThis.removeEventListener === "function") {
312
+ globalThis.removeEventListener("online", onlineHandler);
313
+ }
277
314
  }
278
315
  return {
279
316
  data,
@@ -317,7 +354,19 @@ function clearQueryCache() {
317
354
  }
318
355
  queryCache.clear();
319
356
  for (const listener of activeListeners) listener();
320
- for (const refetcher of activeRefetchers) refetcher();
357
+ for (const refetcher of activeRefetchers) {
358
+ refetcher().catch((err) => {
359
+ if (typeof console !== "undefined") {
360
+ console.warn("[SibuJS query] refetch after clearQueryCache failed:", err);
361
+ }
362
+ });
363
+ }
364
+ }
365
+ function __resetQueryCache() {
366
+ for (const entry of queryCache.values()) {
367
+ if (entry.gcTimer) clearTimeout(entry.gcTimer);
368
+ }
369
+ queryCache.clear();
321
370
  }
322
371
 
323
372
  // src/data/mutation.ts
@@ -380,7 +429,10 @@ function mutation(mutationFn, options = {}) {
380
429
  isSuccess,
381
430
  isIdle,
382
431
  mutate: (variables) => {
383
- execute(variables).catch(() => {
432
+ execute(variables).catch((err) => {
433
+ if (typeof console !== "undefined") {
434
+ console.warn("[SibuJS mutation] mutate() failed; check `.error()` signal or onError option.", err);
435
+ }
384
436
  });
385
437
  },
386
438
  mutateAsync: execute,
@@ -416,11 +468,13 @@ function infiniteQuery(key, fetcher, options) {
416
468
  const hasPreviousPage = derived(() => prevPageParam() !== void 0);
417
469
  let abortController = null;
418
470
  let disposed = false;
471
+ let runId = 0;
419
472
  async function fetchPage(pageParam, direction) {
420
473
  if (disposed) return;
421
474
  abortController?.abort();
422
475
  abortController = new AbortController();
423
476
  const signal2 = abortController.signal;
477
+ const myRun = ++runId;
424
478
  batch(() => {
425
479
  setIsFetching(true);
426
480
  if (direction === "next") setIsFetchingNext(true);
@@ -429,7 +483,7 @@ function infiniteQuery(key, fetcher, options) {
429
483
  });
430
484
  try {
431
485
  const page = await withRetry(() => fetcher({ signal: signal2, pageParam }), retryOptions, void 0, signal2);
432
- if (disposed) return;
486
+ if (disposed || myRun !== runId) return;
433
487
  const currentPages = pages();
434
488
  let newPages;
435
489
  if (direction === "prev") {
@@ -449,7 +503,7 @@ function infiniteQuery(key, fetcher, options) {
449
503
  });
450
504
  onSuccess?.(newPages);
451
505
  } catch (err) {
452
- if (disposed) return;
506
+ if (disposed || myRun !== runId) return;
453
507
  if (err instanceof DOMException && err.name === "AbortError") return;
454
508
  const errorObj = err instanceof Error ? err : new Error(String(err));
455
509
  batch(() => {
@@ -464,9 +518,12 @@ function infiniteQuery(key, fetcher, options) {
464
518
  const effectCleanup = effect(() => {
465
519
  resolveKey();
466
520
  if (enabled) {
467
- setPages([]);
468
- setNextPageParam(initialPageParam);
469
- setPrevPageParam(void 0);
521
+ abortController?.abort();
522
+ batch(() => {
523
+ setPages([]);
524
+ setNextPageParam(initialPageParam);
525
+ setPrevPageParam(void 0);
526
+ });
470
527
  fetchPage(initialPageParam, "initial");
471
528
  }
472
529
  });
@@ -703,18 +760,57 @@ function idbPut(db, store, item) {
703
760
  tx.onerror = () => reject(tx.error);
704
761
  });
705
762
  }
706
- function idbDelete(db, store, key) {
763
+ function idbPutWithChange(db, item, change) {
764
+ return new Promise((resolve, reject) => {
765
+ const tx = db.transaction(["items", "_changes"], "readwrite");
766
+ tx.objectStore("items").put(item);
767
+ tx.objectStore("_changes").put(change);
768
+ tx.oncomplete = () => resolve();
769
+ tx.onerror = () => reject(tx.error);
770
+ });
771
+ }
772
+ function idbDeleteWithChange(db, key, change) {
773
+ return new Promise((resolve, reject) => {
774
+ const tx = db.transaction(["items", "_changes"], "readwrite");
775
+ tx.objectStore("items").delete(key);
776
+ tx.objectStore("_changes").put(change);
777
+ tx.oncomplete = () => resolve();
778
+ tx.onerror = () => reject(tx.error);
779
+ });
780
+ }
781
+ function idbGetAllWithKeys(db, store) {
782
+ return new Promise((resolve, reject) => {
783
+ const tx = db.transaction(store, "readonly");
784
+ const out = [];
785
+ const req = tx.objectStore(store).openCursor();
786
+ req.onsuccess = () => {
787
+ const cursor = req.result;
788
+ if (cursor) {
789
+ out.push({ key: cursor.primaryKey, value: cursor.value });
790
+ cursor.continue();
791
+ } else {
792
+ resolve(out);
793
+ }
794
+ };
795
+ req.onerror = () => reject(req.error);
796
+ });
797
+ }
798
+ function idbDeleteKeys(db, store, keys) {
799
+ if (keys.length === 0) return Promise.resolve();
707
800
  return new Promise((resolve, reject) => {
708
801
  const tx = db.transaction(store, "readwrite");
709
- tx.objectStore(store).delete(key);
802
+ const objStore = tx.objectStore(store);
803
+ for (const k of keys) objStore.delete(k);
710
804
  tx.oncomplete = () => resolve();
711
805
  tx.onerror = () => reject(tx.error);
712
806
  });
713
807
  }
714
- function idbClear(db, store) {
808
+ function idbPutMany(db, store, items) {
809
+ if (items.length === 0) return Promise.resolve();
715
810
  return new Promise((resolve, reject) => {
716
811
  const tx = db.transaction(store, "readwrite");
717
- tx.objectStore(store).clear();
812
+ const objStore = tx.objectStore(store);
813
+ for (const item of items) objStore.put(item);
718
814
  tx.oncomplete = () => resolve();
719
815
  tx.onerror = () => reject(tx.error);
720
816
  });
@@ -737,15 +833,13 @@ async function offlineStore(options) {
737
833
  setPendingCount(changes.length);
738
834
  }
739
835
  async function put(item) {
740
- await idbPut(db, "items", item);
741
- await idbPut(db, "_changes", { type: "put", item, timestamp: Date.now() });
836
+ await idbPutWithChange(db, item, { type: "put", item, timestamp: Date.now() });
742
837
  await refreshData();
743
838
  }
744
839
  async function remove(key) {
745
840
  const existing = await idbGet(db, "items", key);
746
841
  if (existing) {
747
- await idbDelete(db, "items", key);
748
- await idbPut(db, "_changes", { type: "delete", item: existing, timestamp: Date.now() });
842
+ await idbDeleteWithChange(db, key, { type: "delete", item: existing, timestamp: Date.now() });
749
843
  await refreshData();
750
844
  }
751
845
  }
@@ -756,25 +850,45 @@ async function offlineStore(options) {
756
850
  return data().filter(filter);
757
851
  }
758
852
  async function sync() {
759
- if (!adapter || isSyncing()) return;
853
+ if (!adapter || isSyncing() || closed) return;
760
854
  setIsSyncing(true);
761
855
  try {
762
- const changes = await idbGetAll(db, "_changes");
763
- if (changes.length > 0) {
764
- const result = await adapter.push(changes);
856
+ const snapshot = await idbGetAllWithKeys(db, "_changes");
857
+ if (closed) return;
858
+ if (snapshot.length > 0) {
859
+ const result = await adapter.push(snapshot.map((e) => e.value));
860
+ if (closed) return;
765
861
  if (result.ok) {
766
- await idbClear(db, "_changes");
862
+ await idbDeleteKeys(
863
+ db,
864
+ "_changes",
865
+ snapshot.map((e) => e.key)
866
+ );
867
+ if (closed) return;
767
868
  }
768
869
  }
769
870
  const remoteItems = await adapter.pull(lastSynced());
770
- for (const item of remoteItems) {
771
- await idbPut(db, "items", item);
871
+ if (closed) return;
872
+ const pendingChanges = await idbGetAll(db, "_changes");
873
+ if (closed) return;
874
+ const pendingKeys = /* @__PURE__ */ new Set();
875
+ for (const c of pendingChanges) {
876
+ const k = c.item[keyPath];
877
+ if (k != null) pendingKeys.add(k);
772
878
  }
879
+ const safeRemote = remoteItems.filter((item) => {
880
+ const k = item[keyPath];
881
+ return k == null || !pendingKeys.has(k);
882
+ });
883
+ await idbPutMany(db, "items", safeRemote);
884
+ if (closed) return;
773
885
  const now = Date.now();
774
886
  await idbPut(db, "_meta", now);
887
+ if (closed) return;
775
888
  setLastSynced(now);
776
889
  await refreshData();
777
- } catch {
890
+ } catch (err) {
891
+ if (typeof console !== "undefined") console.warn("[offlineStore] sync failed", err);
778
892
  } finally {
779
893
  setIsSyncing(false);
780
894
  }
@@ -782,13 +896,21 @@ async function offlineStore(options) {
782
896
  function attach(newAdapter) {
783
897
  adapter = newAdapter;
784
898
  }
899
+ let onlineHandler = null;
900
+ let closed = false;
785
901
  function close() {
902
+ closed = true;
903
+ if (onlineHandler && typeof window !== "undefined") {
904
+ window.removeEventListener("online", onlineHandler);
905
+ onlineHandler = null;
906
+ }
786
907
  db.close();
787
908
  }
788
909
  if (autoSync && typeof window !== "undefined") {
789
- window.addEventListener("online", () => {
910
+ onlineHandler = () => {
790
911
  sync();
791
- });
912
+ };
913
+ window.addEventListener("online", onlineHandler);
792
914
  }
793
915
  return {
794
916
  data,
@@ -826,9 +948,16 @@ function loaderData() {
826
948
  error: resource2.error
827
949
  };
828
950
  }
829
- async function preloadRoute(route, context2) {
951
+ async function preloadRoute(route, context2, callerSignal) {
830
952
  if (!route.loader) return void 0;
831
953
  const controller = new AbortController();
954
+ if (callerSignal) {
955
+ if (callerSignal.aborted) {
956
+ controller.abort();
957
+ } else {
958
+ callerSignal.addEventListener("abort", () => controller.abort(), { once: true });
959
+ }
960
+ }
832
961
  return route.loader(context2, { signal: controller.signal });
833
962
  }
834
963
 
@@ -843,7 +972,7 @@ function validateWsUrl(raw) {
843
972
  function socket(url, options) {
844
973
  const autoReconnect = options?.autoReconnect ?? false;
845
974
  const reconnectDelay = options?.reconnectDelay ?? 1e3;
846
- const maxReconnects = options?.maxReconnects ?? Infinity;
975
+ const maxReconnects = options?.maxReconnects ?? 10;
847
976
  const heartbeat = options?.heartbeat;
848
977
  const protocols = options?.protocols;
849
978
  const [data, setData] = signal(null);
@@ -877,13 +1006,19 @@ function socket(url, options) {
877
1006
  ws.onclose = () => {
878
1007
  setStatus("closed");
879
1008
  stopHeartbeat();
880
- if (autoReconnect && !disposed && !manuallyClosed && reconnectCount < maxReconnects) {
1009
+ const wasManual = manuallyClosed;
1010
+ manuallyClosed = false;
1011
+ if (autoReconnect && !disposed && !wasManual && reconnectCount < maxReconnects) {
1012
+ const cap = 3e4;
1013
+ const delay = Math.min(cap, reconnectDelay * 2 ** reconnectCount);
1014
+ const jittered = delay * (0.5 + Math.random() * 0.5);
881
1015
  reconnectCount++;
882
1016
  reconnectTimer = setTimeout(() => {
1017
+ reconnectTimer = null;
1018
+ if (disposed || manuallyClosed) return;
883
1019
  connect();
884
- }, reconnectDelay);
1020
+ }, jittered);
885
1021
  }
886
- manuallyClosed = false;
887
1022
  };
888
1023
  ws.onerror = () => {
889
1024
  };
@@ -936,12 +1071,16 @@ function validateSseUrl(raw) {
936
1071
  }
937
1072
  function stream(url, options) {
938
1073
  const autoReconnect = options?.autoReconnect ?? false;
1074
+ const maxReconnects = options?.maxReconnects ?? 10;
1075
+ const baseMs = options?.reconnectBaseMs ?? 1e3;
1076
+ const maxMs = options?.reconnectMaxMs ?? 3e4;
939
1077
  const [data, setData] = signal(null);
940
1078
  const [event, setEvent] = signal(null);
941
1079
  const [status, setStatus] = signal("connecting");
942
1080
  let source = null;
943
1081
  let disposed = false;
944
1082
  let reconnectTimer = null;
1083
+ let attempts = 0;
945
1084
  function connect() {
946
1085
  if (disposed) return;
947
1086
  const safeUrl = validateSseUrl(url);
@@ -955,6 +1094,7 @@ function stream(url, options) {
955
1094
  });
956
1095
  source.onopen = () => {
957
1096
  setStatus("open");
1097
+ attempts = 0;
958
1098
  };
959
1099
  source.onmessage = (evt) => {
960
1100
  setData(evt.data);
@@ -964,11 +1104,14 @@ function stream(url, options) {
964
1104
  if (source && source.readyState === EventSource.CLOSED) {
965
1105
  setStatus("closed");
966
1106
  source = null;
967
- if (autoReconnect && !disposed) {
1107
+ if (autoReconnect && !disposed && attempts < maxReconnects) {
1108
+ const delay = Math.min(maxMs, baseMs * 2 ** attempts);
1109
+ const jittered = delay * (0.5 + Math.random() * 0.5);
1110
+ attempts++;
968
1111
  reconnectTimer = setTimeout(() => {
969
1112
  reconnectTimer = null;
970
1113
  connect();
971
- }, 1e3);
1114
+ }, jittered);
972
1115
  }
973
1116
  }
974
1117
  };
@@ -1000,6 +1143,7 @@ export {
1000
1143
  getQueryData,
1001
1144
  setQueryData,
1002
1145
  clearQueryCache,
1146
+ __resetQueryCache,
1003
1147
  mutation,
1004
1148
  infiniteQuery,
1005
1149
  previous,
@@ -114,7 +114,7 @@ interface MaskOptions {
114
114
  declare function inputMask(options: MaskOptions): {
115
115
  value: () => string;
116
116
  rawValue: () => string;
117
- bind: (input: HTMLInputElement) => void;
117
+ bind: (input: HTMLInputElement) => () => void;
118
118
  };
119
119
  /** Phone number mask: (999) 999-9999 */
120
120
  declare function phoneMask(): MaskOptions;
@@ -140,7 +140,7 @@ declare function focus(): {
140
140
  isFocused: () => boolean;
141
141
  focus: () => void;
142
142
  blur: () => void;
143
- bind: (element: HTMLElement) => void;
143
+ bind: (element: HTMLElement) => () => void;
144
144
  };
145
145
  /**
146
146
  * FocusTrap traps focus within a container element.
@@ -152,7 +152,12 @@ declare function FocusTrap(nodes: HTMLElement, options?: {
152
152
  }): HTMLElement;
153
153
  /**
154
154
  * hotkey registers a keyboard shortcut handler.
155
- * Returns a cleanup function.
155
+ *
156
+ * Returns a `dispose()` cleanup function — call it from the owning
157
+ * component's unmount path to remove the `keydown` listener from
158
+ * `document`. The returned function is idempotent only via the
159
+ * browser's default `removeEventListener` semantics, so callers
160
+ * should invoke it exactly once.
156
161
  *
157
162
  * Supports two calling styles:
158
163
  * - String combo: hotkey("ctrl+shift+z", handler)
@@ -165,9 +170,6 @@ declare function hotkey(combo: string, handler: (e: KeyboardEvent) => void, opti
165
170
  meta?: boolean;
166
171
  preventDefault?: boolean;
167
172
  }): () => void;
168
- /**
169
- * announce creates a screen reader announcement using ARIA live regions.
170
- */
171
173
  declare function announce(message: string, priority?: "polite" | "assertive"): void;
172
174
 
173
175
  /**
@@ -214,11 +216,13 @@ declare function bindBoolAttr(el: HTMLElement, attr: string, getter: boolean | (
214
216
  declare function bindData(el: HTMLElement, key: string, getter: string | (() => string)): () => void;
215
217
 
216
218
  /**
217
- * dialog provides reactive dialog state management with escape-to-close support.
219
+ * Test-only helper to reset the module-level stack between specs. Client-only:
220
+ * in SSR dialog() is never meaningfully invoked. In production the stack is
221
+ * bounded by open dialog count and cleaned via removeFromStack/dispose.
218
222
  *
219
- * Call `dispose()` when the owning component unmounts to ensure the global
220
- * keydown listener is removed even if the dialog is still open.
223
+ * @internal
221
224
  */
225
+ declare function __resetDialogStack(): void;
222
226
  declare function dialog(): {
223
227
  open: () => void;
224
228
  close: () => void;
@@ -315,4 +319,4 @@ declare function defineElement(name: string, component: (props: Record<string, u
315
319
  */
316
320
  declare function svgElement(tag: string, props?: Record<string, unknown>, ...nodes: (SVGElement | string)[]): SVGElement;
317
321
 
318
- export { lazyLoad as A, type BoundFieldProps as B, type CustomElementOptions as C, matchesPattern as D, max as E, type FieldConfig as F, maxLength as G, min as H, type IntersectionResult as I, minLength as J, pagination as K, phoneMask as L, type MaskOptions as M, removeScopedStyle as N, required as O, scopedStyle as P, ssnMask as Q, svgElement as R, timeMask as S, type Toast as T, toast as U, type ValidatorFn as V, withScopedStyle as W, zipMask as X, FocusTrap as a, type FormConfig as b, type FormField as c, type FormReturn as d, type ToastInstance as e, VirtualList as f, type VirtualListProps as g, announce as h, aria as i, bindAttrs as j, bindBoolAttr as k, bindData as l, bindField as m, creditCardMask as n, custom as o, dateMask as p, defineElement as q, dialog as r, email as s, eventBus as t, focus as u, form as v, hotkey as w, infiniteScroll as x, inputMask as y, intersection as z };
322
+ export { lazyLoad as A, type BoundFieldProps as B, type CustomElementOptions as C, matchesPattern as D, max as E, type FieldConfig as F, maxLength as G, min as H, type IntersectionResult as I, minLength as J, pagination as K, phoneMask as L, type MaskOptions as M, removeScopedStyle as N, required as O, scopedStyle as P, ssnMask as Q, svgElement as R, timeMask as S, type Toast as T, toast as U, type ValidatorFn as V, withScopedStyle as W, zipMask as X, __resetDialogStack as _, FocusTrap as a, type FormConfig as b, type FormField as c, type FormReturn as d, type ToastInstance as e, VirtualList as f, type VirtualListProps as g, announce as h, aria as i, bindAttrs as j, bindBoolAttr as k, bindData as l, bindField as m, creditCardMask as n, custom as o, dateMask as p, defineElement as q, dialog as r, email as s, eventBus as t, focus as u, form as v, hotkey as w, infiniteScroll as x, inputMask as y, intersection as z };
@@ -114,7 +114,7 @@ interface MaskOptions {
114
114
  declare function inputMask(options: MaskOptions): {
115
115
  value: () => string;
116
116
  rawValue: () => string;
117
- bind: (input: HTMLInputElement) => void;
117
+ bind: (input: HTMLInputElement) => () => void;
118
118
  };
119
119
  /** Phone number mask: (999) 999-9999 */
120
120
  declare function phoneMask(): MaskOptions;
@@ -140,7 +140,7 @@ declare function focus(): {
140
140
  isFocused: () => boolean;
141
141
  focus: () => void;
142
142
  blur: () => void;
143
- bind: (element: HTMLElement) => void;
143
+ bind: (element: HTMLElement) => () => void;
144
144
  };
145
145
  /**
146
146
  * FocusTrap traps focus within a container element.
@@ -152,7 +152,12 @@ declare function FocusTrap(nodes: HTMLElement, options?: {
152
152
  }): HTMLElement;
153
153
  /**
154
154
  * hotkey registers a keyboard shortcut handler.
155
- * Returns a cleanup function.
155
+ *
156
+ * Returns a `dispose()` cleanup function — call it from the owning
157
+ * component's unmount path to remove the `keydown` listener from
158
+ * `document`. The returned function is idempotent only via the
159
+ * browser's default `removeEventListener` semantics, so callers
160
+ * should invoke it exactly once.
156
161
  *
157
162
  * Supports two calling styles:
158
163
  * - String combo: hotkey("ctrl+shift+z", handler)
@@ -165,9 +170,6 @@ declare function hotkey(combo: string, handler: (e: KeyboardEvent) => void, opti
165
170
  meta?: boolean;
166
171
  preventDefault?: boolean;
167
172
  }): () => void;
168
- /**
169
- * announce creates a screen reader announcement using ARIA live regions.
170
- */
171
173
  declare function announce(message: string, priority?: "polite" | "assertive"): void;
172
174
 
173
175
  /**
@@ -214,11 +216,13 @@ declare function bindBoolAttr(el: HTMLElement, attr: string, getter: boolean | (
214
216
  declare function bindData(el: HTMLElement, key: string, getter: string | (() => string)): () => void;
215
217
 
216
218
  /**
217
- * dialog provides reactive dialog state management with escape-to-close support.
219
+ * Test-only helper to reset the module-level stack between specs. Client-only:
220
+ * in SSR dialog() is never meaningfully invoked. In production the stack is
221
+ * bounded by open dialog count and cleaned via removeFromStack/dispose.
218
222
  *
219
- * Call `dispose()` when the owning component unmounts to ensure the global
220
- * keydown listener is removed even if the dialog is still open.
223
+ * @internal
221
224
  */
225
+ declare function __resetDialogStack(): void;
222
226
  declare function dialog(): {
223
227
  open: () => void;
224
228
  close: () => void;
@@ -315,4 +319,4 @@ declare function defineElement(name: string, component: (props: Record<string, u
315
319
  */
316
320
  declare function svgElement(tag: string, props?: Record<string, unknown>, ...nodes: (SVGElement | string)[]): SVGElement;
317
321
 
318
- export { lazyLoad as A, type BoundFieldProps as B, type CustomElementOptions as C, matchesPattern as D, max as E, type FieldConfig as F, maxLength as G, min as H, type IntersectionResult as I, minLength as J, pagination as K, phoneMask as L, type MaskOptions as M, removeScopedStyle as N, required as O, scopedStyle as P, ssnMask as Q, svgElement as R, timeMask as S, type Toast as T, toast as U, type ValidatorFn as V, withScopedStyle as W, zipMask as X, FocusTrap as a, type FormConfig as b, type FormField as c, type FormReturn as d, type ToastInstance as e, VirtualList as f, type VirtualListProps as g, announce as h, aria as i, bindAttrs as j, bindBoolAttr as k, bindData as l, bindField as m, creditCardMask as n, custom as o, dateMask as p, defineElement as q, dialog as r, email as s, eventBus as t, focus as u, form as v, hotkey as w, infiniteScroll as x, inputMask as y, intersection as z };
322
+ export { lazyLoad as A, type BoundFieldProps as B, type CustomElementOptions as C, matchesPattern as D, max as E, type FieldConfig as F, maxLength as G, min as H, type IntersectionResult as I, minLength as J, pagination as K, phoneMask as L, type MaskOptions as M, removeScopedStyle as N, required as O, scopedStyle as P, ssnMask as Q, svgElement as R, timeMask as S, type Toast as T, toast as U, type ValidatorFn as V, withScopedStyle as W, zipMask as X, __resetDialogStack as _, FocusTrap as a, type FormConfig as b, type FormField as c, type FormReturn as d, type ToastInstance as e, VirtualList as f, type VirtualListProps as g, announce as h, aria as i, bindAttrs as j, bindBoolAttr as k, bindData as l, bindField as m, creditCardMask as n, custom as o, dateMask as p, defineElement as q, dialog as r, email as s, eventBus as t, focus as u, form as v, hotkey as w, infiniteScroll as x, inputMask as y, intersection as z };