sibujs 1.3.0 → 1.5.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 (87) hide show
  1. package/README.md +105 -119
  2. package/dist/browser.cjs +53 -14
  3. package/dist/browser.d.cts +14 -9
  4. package/dist/browser.d.ts +14 -9
  5. package/dist/browser.js +4 -4
  6. package/dist/build.cjs +125 -135
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +11 -91
  10. package/dist/cdn.global.js +6 -6
  11. package/dist/chunk-5ZYQ6KDD.js +154 -0
  12. package/dist/chunk-6BMPXPUW.js +26 -0
  13. package/dist/chunk-7GRNSCFT.js +1097 -0
  14. package/dist/chunk-BGTHZHJ5.js +1016 -0
  15. package/dist/chunk-BMPL52BF.js +654 -0
  16. package/dist/chunk-CNZ35WI2.js +178 -0
  17. package/dist/chunk-GJPXRJ45.js +37 -0
  18. package/dist/chunk-JCDUJN2F.js +2779 -0
  19. package/dist/chunk-K4G4ZQNR.js +286 -0
  20. package/dist/chunk-M4NLBH4I.js +725 -0
  21. package/dist/chunk-MB6QFH3I.js +2776 -0
  22. package/dist/chunk-MYRV7VDM.js +742 -0
  23. package/dist/chunk-NZIIMDWI.js +84 -0
  24. package/dist/chunk-P3XWXJZU.js +282 -0
  25. package/dist/chunk-PDZQY43A.js +616 -0
  26. package/dist/chunk-RJ46C3CS.js +1293 -0
  27. package/dist/chunk-SFKNRVCU.js +292 -0
  28. package/dist/chunk-TDGZL5CU.js +365 -0
  29. package/dist/chunk-UHNL42EF.js +2730 -0
  30. package/dist/chunk-VAPYJN4X.js +368 -0
  31. package/dist/chunk-VQDZK23A.js +1023 -0
  32. package/dist/chunk-VQNQZCWJ.js +61 -0
  33. package/dist/chunk-XHK6BDAJ.js +76 -0
  34. package/dist/chunk-XUEEGU5O.js +409 -0
  35. package/dist/chunk-ZWKZCBO6.js +317 -0
  36. package/dist/contracts-ey_Qh8ef.d.cts +239 -0
  37. package/dist/contracts-ey_Qh8ef.d.ts +239 -0
  38. package/dist/contracts-xo5ckdRP.d.cts +240 -0
  39. package/dist/contracts-xo5ckdRP.d.ts +240 -0
  40. package/dist/customElement-BL3Uo8dL.d.cts +318 -0
  41. package/dist/customElement-BL3Uo8dL.d.ts +318 -0
  42. package/dist/data.cjs +52 -11
  43. package/dist/data.js +6 -6
  44. package/dist/devtools.cjs +22 -24
  45. package/dist/devtools.d.cts +1 -1
  46. package/dist/devtools.d.ts +1 -1
  47. package/dist/devtools.js +26 -28
  48. package/dist/ecosystem.cjs +31 -6
  49. package/dist/ecosystem.d.cts +4 -4
  50. package/dist/ecosystem.d.ts +4 -4
  51. package/dist/ecosystem.js +7 -7
  52. package/dist/extras.cjs +305 -131
  53. package/dist/extras.d.cts +3 -3
  54. package/dist/extras.d.ts +3 -3
  55. package/dist/extras.js +21 -29
  56. package/dist/index.cjs +124 -56
  57. package/dist/index.d.cts +60 -72
  58. package/dist/index.d.ts +60 -72
  59. package/dist/index.js +10 -14
  60. package/dist/motion.cjs +13 -2
  61. package/dist/motion.d.cts +1 -1
  62. package/dist/motion.d.ts +1 -1
  63. package/dist/motion.js +3 -3
  64. package/dist/patterns.cjs +91 -46
  65. package/dist/patterns.d.cts +46 -60
  66. package/dist/patterns.d.ts +46 -60
  67. package/dist/patterns.js +6 -14
  68. package/dist/performance.cjs +97 -12
  69. package/dist/performance.d.cts +6 -1
  70. package/dist/performance.d.ts +6 -1
  71. package/dist/performance.js +5 -3
  72. package/dist/plugins.cjs +20 -14
  73. package/dist/plugins.d.cts +3 -3
  74. package/dist/plugins.d.ts +3 -3
  75. package/dist/plugins.js +17 -19
  76. package/dist/ssr.cjs +9 -0
  77. package/dist/ssr.d.cts +1 -1
  78. package/dist/ssr.d.ts +1 -1
  79. package/dist/ssr.js +7 -7
  80. package/dist/testing.js +2 -2
  81. package/dist/ui.cjs +130 -53
  82. package/dist/ui.d.cts +13 -16
  83. package/dist/ui.d.ts +13 -16
  84. package/dist/ui.js +7 -9
  85. package/dist/widgets.cjs +31 -6
  86. package/dist/widgets.js +5 -5
  87. package/package.json +1 -1
package/dist/extras.cjs CHANGED
@@ -64,7 +64,6 @@ __export(extras_exports, {
64
64
  combobox: () => combobox,
65
65
  compareSemVer: () => compareSemVer,
66
66
  componentAdapter: () => componentAdapter,
67
- composable: () => composable,
68
67
  compose: () => compose,
69
68
  composeMiddleware: () => composeMiddleware,
70
69
  conditional: () => conditional,
@@ -74,12 +73,10 @@ __export(extras_exports, {
74
73
  createBundle: () => createBundle,
75
74
  createChunkRegistry: () => createChunkRegistry,
76
75
  createDevtoolsOverlay: () => createDevtoolsOverlay,
77
- createEffect: () => createEffect,
78
76
  createErrorReporter: () => createErrorReporter,
79
77
  createGuard: () => createGuard,
80
78
  createHMRBoundary: () => createHMRBoundary,
81
79
  createISR: () => createISR,
82
- createMemo: () => createMemo,
83
80
  createMicroApp: () => createMicroApp,
84
81
  createMiddlewareChain: () => createMiddlewareChain,
85
82
  createMigrationRunner: () => createMigrationRunner,
@@ -88,7 +85,6 @@ __export(extras_exports, {
88
85
  createProfiler: () => createProfiler,
89
86
  createSSRCache: () => createSSRCache,
90
87
  createSharedScope: () => createSharedScope,
91
- createSignal: () => createSignal,
92
88
  createSlots: () => createSlots,
93
89
  createTestHarness: () => createTestHarness,
94
90
  createTheme: () => createTheme,
@@ -407,12 +403,21 @@ function queueSignalNotification(signal2) {
407
403
  }
408
404
  }
409
405
  }
406
+ var MAX_DRAIN_ITERATIONS = 1e3;
410
407
  function drainNotificationQueue() {
411
408
  if (notifyDepth > 0) return;
412
409
  notifyDepth++;
413
410
  try {
414
411
  let i2 = 0;
415
412
  while (i2 < pendingQueue.length) {
413
+ if (i2 >= MAX_DRAIN_ITERATIONS) {
414
+ if (typeof console !== "undefined") {
415
+ console.error(
416
+ `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
417
+ );
418
+ }
419
+ break;
420
+ }
416
421
  safeInvoke(pendingQueue[i2]);
417
422
  i2++;
418
423
  }
@@ -579,21 +584,37 @@ function derived(getter, options) {
579
584
  cs._v = getter();
580
585
  }, markDirty);
581
586
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
587
+ let evaluating = false;
582
588
  function computedGetter() {
589
+ if (evaluating) {
590
+ throw new Error(
591
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
592
+ );
593
+ }
583
594
  if (trackingSuspended) {
584
595
  if (cs._d) {
585
- cs._d = false;
586
- cs._v = getter();
596
+ evaluating = true;
597
+ try {
598
+ cs._d = false;
599
+ cs._v = getter();
600
+ } finally {
601
+ evaluating = false;
602
+ }
587
603
  }
588
604
  return cs._v;
589
605
  }
590
606
  recordDependency(cs);
591
607
  if (cs._d) {
592
608
  const oldValue = cs._v;
593
- track(() => {
594
- cs._d = false;
595
- cs._v = getter();
596
- }, markDirty);
609
+ evaluating = true;
610
+ try {
611
+ track(() => {
612
+ cs._d = false;
613
+ cs._v = getter();
614
+ }, markDirty);
615
+ } finally {
616
+ evaluating = false;
617
+ }
597
618
  if (hook && oldValue !== cs._v) {
598
619
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
599
620
  }
@@ -749,9 +770,13 @@ async function withRetry(fn, options, onRetry, signal2) {
749
770
  const delay = calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter);
750
771
  onRetry?.(error, attempt, delay);
751
772
  await new Promise((resolve, reject) => {
752
- const timer = setTimeout(resolve, delay);
773
+ let onAbort = null;
774
+ const timer = setTimeout(() => {
775
+ if (onAbort && signal2) signal2.removeEventListener("abort", onAbort);
776
+ resolve();
777
+ }, delay);
753
778
  if (signal2) {
754
- const onAbort = () => {
779
+ onAbort = () => {
755
780
  clearTimeout(timer);
756
781
  reject(new DOMException("Aborted", "AbortError"));
757
782
  };
@@ -902,9 +927,10 @@ function query(key, fetcher, options = {}) {
902
927
  }
903
928
  }
904
929
  }
930
+ const keyChanged = currentKey !== key2;
905
931
  currentKey = key2;
906
932
  const entry = getOrCreateEntry(key2, initialData);
907
- entry.subscribers++;
933
+ if (keyChanged || entry.subscribers === 0) entry.subscribers++;
908
934
  if (entry.gcTimer !== null) {
909
935
  clearTimeout(entry.gcTimer);
910
936
  entry.gcTimer = null;
@@ -1018,7 +1044,9 @@ function mutation(mutationFn, options = {}) {
1018
1044
  const [status, setStatus] = signal("idle");
1019
1045
  const isSuccess = derived(() => status() === "success");
1020
1046
  const isIdle = derived(() => status() === "idle");
1047
+ let runId = 0;
1021
1048
  async function execute(variables) {
1049
+ const myRun = ++runId;
1022
1050
  let context2;
1023
1051
  batch(() => {
1024
1052
  setLoading(true);
@@ -1030,6 +1058,7 @@ function mutation(mutationFn, options = {}) {
1030
1058
  context2 = await options.onMutate(variables);
1031
1059
  }
1032
1060
  const result = await withRetry(() => mutationFn(variables), options.retry);
1061
+ if (myRun !== runId) return result;
1033
1062
  batch(() => {
1034
1063
  setData(result);
1035
1064
  setLoading(false);
@@ -1040,6 +1069,7 @@ function mutation(mutationFn, options = {}) {
1040
1069
  return result;
1041
1070
  } catch (err) {
1042
1071
  const errorObj = err instanceof Error ? err : new Error(String(err));
1072
+ if (myRun !== runId) throw errorObj;
1043
1073
  batch(() => {
1044
1074
  setError(errorObj);
1045
1075
  setLoading(false);
@@ -1051,6 +1081,7 @@ function mutation(mutationFn, options = {}) {
1051
1081
  }
1052
1082
  }
1053
1083
  function reset() {
1084
+ runId++;
1054
1085
  batch(() => {
1055
1086
  setData(void 0);
1056
1087
  setError(void 0);
@@ -1300,7 +1331,10 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1300
1331
  options.onSuccess?.(result);
1301
1332
  } catch (err) {
1302
1333
  if (version !== fetchVersion || disposed) return;
1303
- if (err instanceof DOMException && err.name === "AbortError") return;
1334
+ if (err instanceof DOMException && err.name === "AbortError") {
1335
+ if (version === fetchVersion) setLoading(false);
1336
+ return;
1337
+ }
1304
1338
  const errorObj = err instanceof Error ? err : new Error(String(err));
1305
1339
  batch(() => {
1306
1340
  setError(errorObj);
@@ -1554,6 +1588,7 @@ function socket(url, options) {
1554
1588
  let reconnectTimer = null;
1555
1589
  let heartbeatTimer = null;
1556
1590
  let disposed = false;
1591
+ let manuallyClosed = false;
1557
1592
  function getUrl() {
1558
1593
  return typeof url === "function" ? url() : url;
1559
1594
  }
@@ -1577,12 +1612,13 @@ function socket(url, options) {
1577
1612
  ws.onclose = () => {
1578
1613
  setStatus("closed");
1579
1614
  stopHeartbeat();
1580
- if (autoReconnect && !disposed && reconnectCount < maxReconnects) {
1615
+ if (autoReconnect && !disposed && !manuallyClosed && reconnectCount < maxReconnects) {
1581
1616
  reconnectCount++;
1582
1617
  reconnectTimer = setTimeout(() => {
1583
1618
  connect();
1584
1619
  }, reconnectDelay);
1585
1620
  }
1621
+ manuallyClosed = false;
1586
1622
  };
1587
1623
  ws.onerror = () => {
1588
1624
  };
@@ -1608,6 +1644,7 @@ function socket(url, options) {
1608
1644
  }
1609
1645
  }
1610
1646
  function close() {
1647
+ manuallyClosed = true;
1611
1648
  if (reconnectTimer !== null) {
1612
1649
  clearTimeout(reconnectTimer);
1613
1650
  reconnectTimer = null;
@@ -1778,6 +1815,8 @@ function scroll(target) {
1778
1815
  const [y, setY] = signal(0);
1779
1816
  const [isScrolling, setIsScrolling] = signal(false);
1780
1817
  let scrollTimer = null;
1818
+ let currentTarget = null;
1819
+ let effectCleanup = null;
1781
1820
  if (typeof window === "undefined") {
1782
1821
  return { x, y, isScrolling, dispose: () => {
1783
1822
  } };
@@ -1800,11 +1839,26 @@ function scroll(target) {
1800
1839
  scrollTimer = null;
1801
1840
  }, 150);
1802
1841
  };
1803
- const scrollTarget = target ? target() : null;
1804
- const eventTarget = scrollTarget || window;
1805
- eventTarget.addEventListener("scroll", handler, { passive: true });
1842
+ function attachListener(eventTarget) {
1843
+ if (currentTarget === eventTarget) return;
1844
+ if (currentTarget) currentTarget.removeEventListener("scroll", handler);
1845
+ currentTarget = eventTarget;
1846
+ currentTarget.addEventListener("scroll", handler, { passive: true });
1847
+ }
1848
+ if (target) {
1849
+ effectCleanup = effect(() => {
1850
+ const el = target();
1851
+ attachListener(el || window);
1852
+ });
1853
+ } else {
1854
+ attachListener(window);
1855
+ }
1806
1856
  function dispose() {
1807
- eventTarget.removeEventListener("scroll", handler);
1857
+ effectCleanup?.();
1858
+ if (currentTarget) {
1859
+ currentTarget.removeEventListener("scroll", handler);
1860
+ currentTarget = null;
1861
+ }
1808
1862
  if (scrollTimer !== null) {
1809
1863
  clearTimeout(scrollTimer);
1810
1864
  scrollTimer = null;
@@ -2323,31 +2377,44 @@ function urlState() {
2323
2377
  }
2324
2378
  };
2325
2379
  }
2326
- const [params, setParamsSignal] = signal(new URLSearchParams(window.location.search));
2327
- const [hash, setHashSignal] = signal(window.location.hash);
2328
- const syncFromLocation = () => {
2329
- setParamsSignal(new URLSearchParams(window.location.search));
2330
- setHashSignal(window.location.hash);
2331
- };
2332
- const onPopState = () => syncFromLocation();
2333
- window.addEventListener("popstate", onPopState);
2380
+ let lastSearch = window.location.search;
2381
+ let lastHash = window.location.hash;
2382
+ const [params, setParamsSignal] = signal(new URLSearchParams(lastSearch));
2383
+ const [hash, setHashSignal] = signal(lastHash);
2384
+ function syncFromLocation() {
2385
+ const currentSearch = window.location.search;
2386
+ const currentHash = window.location.hash;
2387
+ if (currentSearch !== lastSearch) {
2388
+ lastSearch = currentSearch;
2389
+ setParamsSignal(new URLSearchParams(currentSearch));
2390
+ }
2391
+ if (currentHash !== lastHash) {
2392
+ lastHash = currentHash;
2393
+ setHashSignal(currentHash);
2394
+ }
2395
+ }
2396
+ window.addEventListener("popstate", syncFromLocation);
2397
+ window.addEventListener("hashchange", syncFromLocation);
2334
2398
  function setParams(next, opts = {}) {
2335
2399
  const p2 = next instanceof URLSearchParams ? next : new URLSearchParams(next);
2336
2400
  const query2 = p2.toString();
2337
2401
  const newUrl = `${window.location.pathname}${query2 ? `?${query2}` : ""}${window.location.hash}`;
2338
2402
  if (opts.replace) window.history.replaceState(null, "", newUrl);
2339
2403
  else window.history.pushState(null, "", newUrl);
2404
+ lastSearch = window.location.search;
2340
2405
  setParamsSignal(new URLSearchParams(p2));
2341
2406
  }
2342
2407
  function setHash(next, opts = {}) {
2343
- const normalized = next.startsWith("#") ? next : next ? `#${next}` : "";
2408
+ const normalized = next && next !== "#" ? next.startsWith("#") ? next : `#${next}` : "";
2344
2409
  const newUrl = `${window.location.pathname}${window.location.search}${normalized}`;
2345
2410
  if (opts.replace) window.history.replaceState(null, "", newUrl);
2346
2411
  else window.history.pushState(null, "", newUrl);
2412
+ lastHash = normalized;
2347
2413
  setHashSignal(normalized);
2348
2414
  }
2349
2415
  function dispose() {
2350
- window.removeEventListener("popstate", onPopState);
2416
+ window.removeEventListener("popstate", syncFromLocation);
2417
+ window.removeEventListener("hashchange", syncFromLocation);
2351
2418
  }
2352
2419
  return { params, hash, setParams, setHash, dispose };
2353
2420
  }
@@ -3011,7 +3078,7 @@ function persisted(key, initial, options = {}) {
3011
3078
  }
3012
3079
  const [value, setValue] = signal(restored);
3013
3080
  let applyingFromStorage = false;
3014
- effect(() => {
3081
+ const stopPersistEffect = effect(() => {
3015
3082
  const current = value();
3016
3083
  if (applyingFromStorage) return;
3017
3084
  try {
@@ -3051,6 +3118,7 @@ function persisted(key, initial, options = {}) {
3051
3118
  window.addEventListener("storage", storageListener);
3052
3119
  }
3053
3120
  const dispose = () => {
3121
+ stopPersistEffect();
3054
3122
  if (storageListener && typeof window !== "undefined") {
3055
3123
  window.removeEventListener("storage", storageListener);
3056
3124
  storageListener = null;
@@ -3068,27 +3136,53 @@ function persisted(key, initial, options = {}) {
3068
3136
  // src/patterns/optimistic.ts
3069
3137
  function optimistic(initialValue) {
3070
3138
  const [value, setValue] = signal(initialValue);
3071
- const [_pending, setPending] = signal(false);
3072
- async function addOptimistic(optimisticValue, asyncAction) {
3139
+ const [pending, setPending] = signal(false);
3140
+ let inflightCount = 0;
3141
+ let version = 0;
3142
+ async function update(optimisticValue, asyncAction) {
3143
+ const myVersion = ++version;
3073
3144
  const previousValue = value();
3074
3145
  setValue(optimisticValue);
3146
+ inflightCount++;
3075
3147
  setPending(true);
3076
3148
  try {
3077
3149
  const result = await asyncAction();
3078
- setValue(result);
3150
+ if (version === myVersion) {
3151
+ setValue(result);
3152
+ }
3079
3153
  } catch {
3080
- setValue(previousValue);
3154
+ if (version === myVersion) {
3155
+ setValue(previousValue);
3156
+ }
3081
3157
  } finally {
3082
- setPending(false);
3158
+ inflightCount--;
3159
+ if (inflightCount === 0) setPending(false);
3083
3160
  }
3084
3161
  }
3085
- return [value, addOptimistic];
3162
+ return { value, pending, update };
3086
3163
  }
3087
3164
  function optimisticList(initialValue) {
3088
3165
  const [items, setItems] = signal([...initialValue]);
3089
- async function addOptimistic(item, asyncAction) {
3166
+ const [pending, setPending] = signal(false);
3167
+ let inflightCount = 0;
3168
+ let version = 0;
3169
+ function begin() {
3170
+ const v = ++version;
3171
+ inflightCount++;
3172
+ setPending(true);
3173
+ return v;
3174
+ }
3175
+ function end(myVersion, revertFn) {
3176
+ if (revertFn && version === myVersion) {
3177
+ revertFn();
3178
+ }
3179
+ inflightCount--;
3180
+ if (inflightCount === 0) setPending(false);
3181
+ }
3182
+ async function add(item, asyncAction) {
3090
3183
  const prev = items();
3091
3184
  setItems([...prev, item]);
3185
+ const myVersion = begin();
3092
3186
  try {
3093
3187
  const result = await asyncAction();
3094
3188
  setItems((current) => {
@@ -3098,32 +3192,56 @@ function optimisticList(initialValue) {
3098
3192
  next[idx] = result;
3099
3193
  return next;
3100
3194
  }
3101
- return [...current.filter((i2) => i2 !== item), result];
3195
+ return [...current, result];
3102
3196
  });
3197
+ end(myVersion);
3103
3198
  } catch {
3104
- setItems(prev);
3199
+ end(myVersion, () => setItems(prev));
3105
3200
  }
3106
3201
  }
3107
- async function removeOptimistic(predicate, asyncAction) {
3202
+ async function remove(predicate, asyncAction) {
3108
3203
  const prev = items();
3109
3204
  setItems(prev.filter((item) => !predicate(item)));
3205
+ const myVersion = begin();
3110
3206
  try {
3111
3207
  await asyncAction();
3208
+ end(myVersion);
3112
3209
  } catch {
3113
- setItems(prev);
3210
+ end(myVersion, () => setItems(prev));
3114
3211
  }
3115
3212
  }
3116
- async function updateOptimistic(predicate, update, asyncAction) {
3213
+ async function updateItem(predicate, patch, asyncAction) {
3117
3214
  const prev = items();
3118
- setItems(prev.map((item) => predicate(item) ? { ...item, ...update } : item));
3215
+ const patchedRefs = [];
3216
+ setItems(
3217
+ prev.map((item) => {
3218
+ if (predicate(item)) {
3219
+ const patched = { ...item, ...patch };
3220
+ patchedRefs.push(patched);
3221
+ return patched;
3222
+ }
3223
+ return item;
3224
+ })
3225
+ );
3226
+ const myVersion = begin();
3119
3227
  try {
3120
3228
  const result = await asyncAction();
3121
- setItems((current) => current.map((item) => predicate(item) ? result : item));
3229
+ setItems((current) => current.map((item) => patchedRefs.includes(item) ? result : item));
3230
+ end(myVersion);
3122
3231
  } catch {
3123
- setItems(prev);
3232
+ end(myVersion, () => setItems(prev));
3124
3233
  }
3125
3234
  }
3126
- return { items, addOptimistic, removeOptimistic, updateOptimistic };
3235
+ return {
3236
+ items,
3237
+ pending,
3238
+ add,
3239
+ remove,
3240
+ update: updateItem,
3241
+ addOptimistic: add,
3242
+ removeOptimistic: remove,
3243
+ updateOptimistic: updateItem
3244
+ };
3127
3245
  }
3128
3246
 
3129
3247
  // src/patterns/timeTravel.ts
@@ -3175,7 +3293,7 @@ function timeline(initial, maxHistory = 100) {
3175
3293
 
3176
3294
  // src/patterns/globalStore.ts
3177
3295
  function globalStore(config) {
3178
- const initialState = { ...config.state };
3296
+ const initialState = JSON.parse(JSON.stringify(config.state));
3179
3297
  const [getState, setState] = signal({ ...initialState });
3180
3298
  const listeners = /* @__PURE__ */ new Set();
3181
3299
  const middlewares = config.middleware || [];
@@ -3228,17 +3346,6 @@ function globalStore(config) {
3228
3346
  return { getState, select: select3, dispatch, subscribe, reset };
3229
3347
  }
3230
3348
 
3231
- // src/patterns/primitives.ts
3232
- function createSignal(value) {
3233
- return signal(value);
3234
- }
3235
- function createMemo(fn) {
3236
- return derived(fn);
3237
- }
3238
- function createEffect(fn) {
3239
- return effect(fn);
3240
- }
3241
-
3242
3349
  // src/patterns/hoc.ts
3243
3350
  function withWrapper(WrappedComponent, wrapper) {
3244
3351
  return (props) => wrapper(WrappedComponent, props);
@@ -3251,9 +3358,6 @@ function compose(...wrappers) {
3251
3358
  }
3252
3359
 
3253
3360
  // src/patterns/composable.ts
3254
- function composable(setup) {
3255
- return setup;
3256
- }
3257
3361
  function RenderProp(props) {
3258
3362
  return props.render(props.data());
3259
3363
  }
@@ -3418,20 +3522,29 @@ function transition(element, options = {}) {
3418
3522
  onLeaveDone
3419
3523
  } = options;
3420
3524
  const transitionValue = `${property} ${duration}ms ${easing} ${delay}ms`;
3525
+ let activeTimer = null;
3526
+ function cancelPending() {
3527
+ if (activeTimer !== null) {
3528
+ clearTimeout(activeTimer);
3529
+ activeTimer = null;
3530
+ }
3531
+ }
3421
3532
  function enter() {
3422
3533
  return new Promise((resolve) => {
3534
+ cancelPending();
3423
3535
  element.style.transition = transitionValue;
3424
3536
  if (enterClass) element.classList.add(enterClass);
3425
3537
  if (leaveClass) element.classList.remove(leaveClass);
3426
3538
  void element.offsetHeight;
3427
3539
  if (activeClass) element.classList.add(activeClass);
3428
3540
  const done = () => {
3541
+ activeTimer = null;
3429
3542
  if (enterClass) element.classList.remove(enterClass);
3430
3543
  onEnterDone?.();
3431
3544
  resolve();
3432
3545
  };
3433
3546
  if (duration > 0) {
3434
- setTimeout(done, duration + delay);
3547
+ activeTimer = setTimeout(done, duration + delay);
3435
3548
  } else {
3436
3549
  done();
3437
3550
  }
@@ -3439,17 +3552,19 @@ function transition(element, options = {}) {
3439
3552
  }
3440
3553
  function leave() {
3441
3554
  return new Promise((resolve) => {
3555
+ cancelPending();
3442
3556
  element.style.transition = transitionValue;
3443
3557
  if (activeClass) element.classList.remove(activeClass);
3444
3558
  if (leaveClass) element.classList.add(leaveClass);
3445
3559
  if (enterClass) element.classList.remove(enterClass);
3446
3560
  const done = () => {
3561
+ activeTimer = null;
3447
3562
  if (leaveClass) element.classList.remove(leaveClass);
3448
3563
  onLeaveDone?.();
3449
3564
  resolve();
3450
3565
  };
3451
3566
  if (duration > 0) {
3452
- setTimeout(done, duration + delay);
3567
+ activeTimer = setTimeout(done, duration + delay);
3453
3568
  } else {
3454
3569
  done();
3455
3570
  }
@@ -3852,7 +3967,7 @@ function bindField(field, extras) {
3852
3967
  blur: () => field.touch()
3853
3968
  };
3854
3969
  const { on: extraOn, value: _ignoreValue, ...restExtras } = extras ?? {};
3855
- const mergedOn = extraOn && typeof extraOn === "object" ? { ...fieldOn, ...extraOn } : fieldOn;
3970
+ const mergedOn = extraOn && typeof extraOn === "object" ? { ...extraOn, ...fieldOn } : fieldOn;
3856
3971
  return {
3857
3972
  value: field.value,
3858
3973
  on: mergedOn,
@@ -3923,14 +4038,23 @@ function form(config) {
3923
4038
  }
3924
4039
  return result;
3925
4040
  });
4041
+ const [submitting, setSubmitting] = signal(false);
3926
4042
  function handleSubmit(onSubmit) {
3927
4043
  return (e) => {
3928
4044
  if (e) e.preventDefault();
4045
+ if (submitting()) return;
3929
4046
  for (const field of Object.values(fieldMap)) {
3930
4047
  field.touch();
3931
4048
  }
3932
4049
  if (isValid()) {
3933
- onSubmit(values());
4050
+ const result = onSubmit(values());
4051
+ if (result && typeof result.then === "function") {
4052
+ setSubmitting(true);
4053
+ result.then(
4054
+ () => setSubmitting(false),
4055
+ () => setSubmitting(false)
4056
+ );
4057
+ }
3934
4058
  }
3935
4059
  };
3936
4060
  }
@@ -3947,6 +4071,7 @@ function form(config) {
3947
4071
  errors,
3948
4072
  isValid,
3949
4073
  isDirty,
4074
+ submitting,
3950
4075
  touched: touchedState,
3951
4076
  values,
3952
4077
  handleSubmit,
@@ -3955,6 +4080,20 @@ function form(config) {
3955
4080
  };
3956
4081
  }
3957
4082
 
4083
+ // src/core/rendering/dispose.ts
4084
+ var elementDisposers = /* @__PURE__ */ new WeakMap();
4085
+ var _isDev4 = isDev();
4086
+ var activeBindingCount = 0;
4087
+ function registerDisposer(node, teardown) {
4088
+ let disposers = elementDisposers.get(node);
4089
+ if (!disposers) {
4090
+ disposers = [];
4091
+ elementDisposers.set(node, disposers);
4092
+ }
4093
+ disposers.push(teardown);
4094
+ if (_isDev4) activeBindingCount++;
4095
+ }
4096
+
3958
4097
  // src/ui/virtualList.ts
3959
4098
  function VirtualList(props) {
3960
4099
  const overscan = props.overscan ?? 3;
@@ -3972,9 +4111,9 @@ function VirtualList(props) {
3972
4111
  content.style.right = "0";
3973
4112
  spacer.appendChild(content);
3974
4113
  container.appendChild(spacer);
3975
- container.addEventListener("scroll", () => {
3976
- setScrollTop(container.scrollTop);
3977
- });
4114
+ const onScroll = () => setScrollTop(container.scrollTop);
4115
+ container.addEventListener("scroll", onScroll);
4116
+ registerDisposer(container, () => container.removeEventListener("scroll", onScroll));
3978
4117
  const update = () => {
3979
4118
  const items = props.items();
3980
4119
  const totalHeight = items.length * props.itemHeight;
@@ -4082,13 +4221,52 @@ function inputMask(options) {
4082
4221
  }
4083
4222
  return raw;
4084
4223
  }
4224
+ function isSlot(c) {
4225
+ return c === "9" || c === "A" || c === "*";
4226
+ }
4227
+ function buildStripRegex() {
4228
+ const hasDigit = options.pattern.includes("9");
4229
+ const hasLetter = options.pattern.includes("A");
4230
+ const hasAny = options.pattern.includes("*");
4231
+ if (hasAny) {
4232
+ const literals = /* @__PURE__ */ new Set();
4233
+ for (const c of options.pattern) {
4234
+ if (!isSlot(c)) literals.add(c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
4235
+ }
4236
+ return literals.size > 0 ? new RegExp(`[${Array.from(literals).join("")}]`, "g") : /(?!)/g;
4237
+ }
4238
+ if (hasDigit && hasLetter) return /[^a-zA-Z0-9]/g;
4239
+ if (hasDigit) return /[^0-9]/g;
4240
+ if (hasLetter) return /[^a-zA-Z]/g;
4241
+ return /[^a-zA-Z0-9]/g;
4242
+ }
4243
+ const stripRegex = buildStripRegex();
4244
+ const rawCharTest = options.pattern.includes("*") ? () => true : (c) => /[a-zA-Z0-9]/.test(c);
4085
4245
  function bind(input2) {
4086
4246
  input2.addEventListener("input", () => {
4087
- const raw = input2.value.replace(/[^a-zA-Z0-9]/g, "");
4247
+ const cursorBefore = input2.selectionStart ?? input2.value.length;
4248
+ const oldValue = input2.value;
4249
+ const raw = oldValue.replace(stripRegex, "");
4088
4250
  const masked = applyMask(raw);
4089
4251
  setValue(masked);
4090
4252
  setRawValue(extractRaw(masked));
4091
4253
  input2.value = masked;
4254
+ let rawBefore = 0;
4255
+ for (let i2 = 0; i2 < cursorBefore && i2 < oldValue.length; i2++) {
4256
+ if (rawCharTest(oldValue[i2])) rawBefore++;
4257
+ }
4258
+ let newCursor = 0;
4259
+ let counted = 0;
4260
+ for (; newCursor < masked.length; newCursor++) {
4261
+ if (newCursor < options.pattern.length && isSlot(options.pattern[newCursor])) {
4262
+ counted++;
4263
+ if (counted >= rawBefore) {
4264
+ newCursor++;
4265
+ break;
4266
+ }
4267
+ }
4268
+ }
4269
+ input2.setSelectionRange(newCursor, newCursor);
4092
4270
  });
4093
4271
  input2.addEventListener("focus", () => {
4094
4272
  if (!input2.value) {
@@ -4158,7 +4336,10 @@ function FocusTrap(nodes, options = {}) {
4158
4336
  const focusable = container.querySelectorAll(
4159
4337
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
4160
4338
  );
4161
- if (focusable.length === 0) return;
4339
+ if (focusable.length === 0) {
4340
+ e.preventDefault();
4341
+ return;
4342
+ }
4162
4343
  const first = focusable[0];
4163
4344
  const last = focusable[focusable.length - 1];
4164
4345
  if (e.shiftKey) {
@@ -4181,24 +4362,27 @@ function FocusTrap(nodes, options = {}) {
4181
4362
  first?.focus();
4182
4363
  });
4183
4364
  }
4365
+ let trapObserver = null;
4366
+ function restoreFocusAndCleanup() {
4367
+ if (options.restoreFocus !== false) previouslyFocused?.focus();
4368
+ if (trapObserver) {
4369
+ trapObserver.disconnect();
4370
+ trapObserver = null;
4371
+ }
4372
+ }
4184
4373
  if (options.restoreFocus !== false) {
4185
- const observer = new MutationObserver((mutations) => {
4186
- for (const mutation2 of mutations) {
4187
- for (const removed of Array.from(mutation2.removedNodes)) {
4188
- if (removed === container || removed.contains(container)) {
4189
- previouslyFocused?.focus();
4190
- observer.disconnect();
4191
- return;
4192
- }
4193
- }
4374
+ trapObserver = new MutationObserver(() => {
4375
+ if (!container.isConnected) {
4376
+ restoreFocusAndCleanup();
4194
4377
  }
4195
4378
  });
4196
4379
  queueMicrotask(() => {
4197
- if (container.parentNode) {
4198
- observer.observe(container.parentNode, { childList: true });
4380
+ if (container.isConnected) {
4381
+ trapObserver.observe(document.body, { childList: true, subtree: true });
4199
4382
  }
4200
4383
  });
4201
4384
  }
4385
+ registerDisposer(container, restoreFocusAndCleanup);
4202
4386
  return container;
4203
4387
  }
4204
4388
  function hotkey(combo, handler, options = {}) {
@@ -4284,6 +4468,10 @@ function scopedStyle(css) {
4284
4468
  if (trimmed.startsWith("@") || trimmed.startsWith("from") || trimmed.startsWith("to") || /^\d+%$/.test(trimmed)) {
4285
4469
  return match;
4286
4470
  }
4471
+ const pseudoIdx = trimmed.indexOf("::");
4472
+ if (pseudoIdx >= 0) {
4473
+ return `${trimmed.slice(0, pseudoIdx)}[${attr}]${trimmed.slice(pseudoIdx)}${delimiter}`;
4474
+ }
4287
4475
  return `${trimmed}[${attr}]${delimiter}`;
4288
4476
  });
4289
4477
  if (typeof document !== "undefined") {
@@ -4319,7 +4507,7 @@ function removeScopedStyle(scopeId) {
4319
4507
  }
4320
4508
 
4321
4509
  // src/reactivity/bindAttribute.ts
4322
- var _isDev4 = isDev();
4510
+ var _isDev5 = isDev();
4323
4511
  function isEventHandlerAttr(name) {
4324
4512
  if (name.length < 3) return false;
4325
4513
  const lower = name.toLowerCase();
@@ -4327,7 +4515,7 @@ function isEventHandlerAttr(name) {
4327
4515
  }
4328
4516
  function bindAttribute(el, attr, getter) {
4329
4517
  if (isEventHandlerAttr(attr)) {
4330
- if (_isDev4)
4518
+ if (_isDev5)
4331
4519
  devWarn(
4332
4520
  `bindAttribute: refusing to bind event-handler attribute "${attr}". Use on:{ ${attr.slice(2)}: fn } instead.`
4333
4521
  );
@@ -4339,7 +4527,7 @@ function bindAttribute(el, attr, getter) {
4339
4527
  try {
4340
4528
  value = getter();
4341
4529
  } catch (err) {
4342
- if (_isDev4)
4530
+ if (_isDev5)
4343
4531
  devWarn(`bindAttribute: getter for "${attr}" threw: ${err instanceof Error ? err.message : String(err)}`);
4344
4532
  return;
4345
4533
  }
@@ -4433,28 +4621,35 @@ function dialog() {
4433
4621
  close();
4434
4622
  }
4435
4623
  }
4436
- function open() {
4437
- setIsOpen(true);
4624
+ function attachListener() {
4438
4625
  if (typeof window !== "undefined" && !listenerAttached) {
4439
4626
  window.addEventListener("keydown", handleKeydown);
4440
4627
  listenerAttached = true;
4441
4628
  }
4442
4629
  }
4443
- function close() {
4444
- setIsOpen(false);
4630
+ function detachListener() {
4445
4631
  if (typeof window !== "undefined" && listenerAttached) {
4446
4632
  window.removeEventListener("keydown", handleKeydown);
4447
4633
  listenerAttached = false;
4448
4634
  }
4449
4635
  }
4636
+ function open() {
4637
+ setIsOpen(true);
4638
+ attachListener();
4639
+ }
4640
+ function close() {
4641
+ setIsOpen(false);
4642
+ detachListener();
4643
+ }
4450
4644
  function toggle() {
4451
- if (isOpen()) {
4452
- close();
4453
- } else {
4454
- open();
4455
- }
4645
+ if (isOpen()) close();
4646
+ else open();
4647
+ }
4648
+ function dispose() {
4649
+ detachListener();
4650
+ setIsOpen(false);
4456
4651
  }
4457
- return { open, close, isOpen, toggle };
4652
+ return { open, close, isOpen, toggle, dispose };
4458
4653
  }
4459
4654
 
4460
4655
  // src/ui/toast.ts
@@ -4775,15 +4970,12 @@ function startTransition(callback) {
4775
4970
  }
4776
4971
  function deferredValue(getter) {
4777
4972
  const [deferred, setDeferred] = signal(getter());
4778
- const sync = () => {
4779
- const current = getter();
4780
- setDeferred(current);
4781
- };
4782
- scheduleUpdate(Priority.LOW, sync);
4783
- return () => {
4784
- scheduleUpdate(Priority.LOW, sync);
4785
- return deferred();
4786
- };
4973
+ let latest = deferred();
4974
+ effect(() => {
4975
+ latest = getter();
4976
+ scheduleUpdate(Priority.LOW, () => setDeferred(latest));
4977
+ });
4978
+ return deferred;
4787
4979
  }
4788
4980
  function transitionState() {
4789
4981
  const [isPending, setIsPending] = signal(false);
@@ -5386,7 +5578,7 @@ function setCanonical(url) {
5386
5578
  }
5387
5579
 
5388
5580
  // src/platform/ssr.ts
5389
- var _isDev5 = isDev();
5581
+ var _isDev6 = isDev();
5390
5582
  var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
5391
5583
  function isSafeAttrName(name) {
5392
5584
  return SAFE_ATTR_NAME.test(name);
@@ -5411,7 +5603,7 @@ var URL_ATTRS = /* @__PURE__ */ new Set([
5411
5603
  "xlink:href"
5412
5604
  ]);
5413
5605
  function ssrErrorComment(err) {
5414
- if (_isDev5) {
5606
+ if (_isDev6) {
5415
5607
  const msg = escapeHtml(err instanceof Error ? err.message : String(err));
5416
5608
  return `<!--SSR error: ${safeCommentText(msg)}-->`;
5417
5609
  }
@@ -5457,10 +5649,10 @@ function renderToString(element) {
5457
5649
  }
5458
5650
  const tag = element.tagName.toLowerCase();
5459
5651
  if (tag === "script" || tag === "style") {
5460
- return _isDev5 ? `<!--ssr:${tag}-stripped-->` : "";
5652
+ return _isDev6 ? `<!--ssr:${tag}-stripped-->` : "";
5461
5653
  }
5462
5654
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
5463
- return _isDev5 ? "<!--ssr:invalid-tag-->" : "";
5655
+ return _isDev6 ? "<!--ssr:invalid-tag-->" : "";
5464
5656
  }
5465
5657
  let html2 = `<${tag}`;
5466
5658
  for (const attr of Array.from(element.attributes)) {
@@ -5501,7 +5693,7 @@ function hydrate(component, container, options = {}) {
5501
5693
  const first = mismatches[0];
5502
5694
  if (options.onMismatch) {
5503
5695
  options.onMismatch(first);
5504
- } else if (_isDev5) {
5696
+ } else if (_isDev6) {
5505
5697
  console.warn(
5506
5698
  `[Sibu hydration] ${first.message}
5507
5699
  at ${first.path}
@@ -5699,11 +5891,11 @@ async function* renderToStream(element) {
5699
5891
  }
5700
5892
  const tag = element.tagName.toLowerCase();
5701
5893
  if (tag === "script" || tag === "style") {
5702
- if (_isDev5) yield `<!--ssr:${tag}-stripped-->`;
5894
+ if (_isDev6) yield `<!--ssr:${tag}-stripped-->`;
5703
5895
  return;
5704
5896
  }
5705
5897
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
5706
- if (_isDev5) yield "<!--ssr:invalid-tag-->";
5898
+ if (_isDev6) yield "<!--ssr:invalid-tag-->";
5707
5899
  return;
5708
5900
  }
5709
5901
  let openTag = `<${tag}`;
@@ -6205,7 +6397,7 @@ function isWasmCached(key) {
6205
6397
  }
6206
6398
 
6207
6399
  // src/reactivity/bindChildNode.ts
6208
- var _isDev6 = isDev();
6400
+ var _isDev7 = isDev();
6209
6401
  function bindChildNode(placeholder, getter) {
6210
6402
  let lastNodes = [];
6211
6403
  function commit() {
@@ -6213,7 +6405,7 @@ function bindChildNode(placeholder, getter) {
6213
6405
  try {
6214
6406
  result = getter();
6215
6407
  } catch (err) {
6216
- if (_isDev6) devWarn(`bindChildNode: getter threw: ${err instanceof Error ? err.message : String(err)}`);
6408
+ if (_isDev7) devWarn(`bindChildNode: getter threw: ${err instanceof Error ? err.message : String(err)}`);
6217
6409
  return;
6218
6410
  }
6219
6411
  if (result == null || typeof result === "boolean") {
@@ -6273,20 +6465,6 @@ function bindChildNode(placeholder, getter) {
6273
6465
  return track(commit);
6274
6466
  }
6275
6467
 
6276
- // src/core/rendering/dispose.ts
6277
- var elementDisposers = /* @__PURE__ */ new WeakMap();
6278
- var _isDev7 = isDev();
6279
- var activeBindingCount = 0;
6280
- function registerDisposer(node, teardown) {
6281
- let disposers = elementDisposers.get(node);
6282
- if (!disposers) {
6283
- disposers = [];
6284
- elementDisposers.set(node, disposers);
6285
- }
6286
- disposers.push(teardown);
6287
- if (_isDev7) activeBindingCount++;
6288
- }
6289
-
6290
6468
  // src/core/rendering/tagFactory.ts
6291
6469
  var SVG_NS = "http://www.w3.org/2000/svg";
6292
6470
  var kebabCache = /* @__PURE__ */ new Map();
@@ -7215,7 +7393,7 @@ var bundlerMetadata = {
7215
7393
  sideEffects: false,
7216
7394
  modules: {
7217
7395
  core: ["html", "mount", "each", "slots", "fragment", "catch", "portal", "directives"],
7218
- hooks: ["signal", "effect", "derived", "watch", "store", "ref", "memo", "memoFn", "array", "deepSignal"],
7396
+ hooks: ["signal", "effect", "derived", "watch", "store", "ref", "array", "deepSignal"],
7219
7397
  plugins: ["router", "i18n"],
7220
7398
  components: ["ErrorBoundary", "Loading"],
7221
7399
  ssr: ["ssr"],
@@ -9771,7 +9949,6 @@ var materialAdapter = componentAdapter(materialConfig);
9771
9949
  combobox,
9772
9950
  compareSemVer,
9773
9951
  componentAdapter,
9774
- composable,
9775
9952
  compose,
9776
9953
  composeMiddleware,
9777
9954
  conditional,
@@ -9781,12 +9958,10 @@ var materialAdapter = componentAdapter(materialConfig);
9781
9958
  createBundle,
9782
9959
  createChunkRegistry,
9783
9960
  createDevtoolsOverlay,
9784
- createEffect,
9785
9961
  createErrorReporter,
9786
9962
  createGuard,
9787
9963
  createHMRBoundary,
9788
9964
  createISR,
9789
- createMemo,
9790
9965
  createMicroApp,
9791
9966
  createMiddlewareChain,
9792
9967
  createMigrationRunner,
@@ -9795,7 +9970,6 @@ var materialAdapter = componentAdapter(materialConfig);
9795
9970
  createProfiler,
9796
9971
  createSSRCache,
9797
9972
  createSharedScope,
9798
- createSignal,
9799
9973
  createSlots,
9800
9974
  createTestHarness,
9801
9975
  createTheme,