sibujs 3.1.0 → 3.2.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 (78) hide show
  1. package/README.md +6 -0
  2. package/dist/browser.cjs +16 -8
  3. package/dist/browser.js +6 -5
  4. package/dist/build.cjs +235 -147
  5. package/dist/build.js +35 -24
  6. package/dist/cdn.global.js +7 -7
  7. package/dist/{chunk-WYU7CYJ3.js → chunk-2C4E3HBM.js} +5 -5
  8. package/dist/{chunk-3DYB5B3S.js → chunk-4JCAUOLN.js} +45 -23
  9. package/dist/{chunk-2HAGQWDV.js → chunk-5N74TKLD.js} +1 -1
  10. package/dist/{chunk-SVVAUX7J.js → chunk-7XDYVJLE.js} +19 -9
  11. package/dist/{chunk-2N2UL7O4.js → chunk-BGNLPNGV.js} +20 -12
  12. package/dist/{chunk-RK4BQG25.js → chunk-C427DVQF.js} +1 -1
  13. package/dist/{chunk-ZIBE2SAT.js → chunk-FDY42FIU.js} +3 -2
  14. package/dist/{chunk-GQ7RRFPU.js → chunk-FOI23UJL.js} +11 -1
  15. package/dist/{chunk-2RA7SHDA.js → chunk-GOJMFRBL.js} +20 -4
  16. package/dist/{chunk-IVOUCSZL.js → chunk-GOUM4JCT.js} +6 -6
  17. package/dist/chunk-H3SRKIYX.js +17 -0
  18. package/dist/{chunk-3DJH25UO.js → chunk-H6PCHJZQ.js} +2 -2
  19. package/dist/{chunk-UCS6AMJ7.js → chunk-HMJFCBRR.js} +26 -3
  20. package/dist/{chunk-JYD2PWXH.js → chunk-HXMS4SNP.js} +22 -15
  21. package/dist/{chunk-SC437AMI.js → chunk-JYXOEYI4.js} +12 -18
  22. package/dist/{chunk-KB3BA2XK.js → chunk-NFYWLRUO.js} +11 -18
  23. package/dist/{chunk-QNQY5DUS.js → chunk-NPIEEKPT.js} +20 -11
  24. package/dist/{chunk-UYX2NDOH.js → chunk-OYLPZO4N.js} +33 -15
  25. package/dist/{chunk-LYTCUZ7H.js → chunk-RDRSWYNP.js} +1 -1
  26. package/dist/{chunk-2ZJ7TSW4.js → chunk-RLUJL2MV.js} +4 -8
  27. package/dist/{chunk-CR4MXPHB.js → chunk-V2MTG5FT.js} +99 -36
  28. package/dist/{chunk-CNZ35WI2.js → chunk-VJE6DDYM.js} +2 -2
  29. package/dist/{chunk-PMSDFTK3.js → chunk-VOCE4NNK.js} +157 -75
  30. package/dist/{chunk-WKUXSE7V.js → chunk-X67UYC74.js} +12 -11
  31. package/dist/{chunk-EFOAE5NC.js → chunk-YFDGQWDA.js} +1 -1
  32. package/dist/{chunk-3U4ZVXVD.js → chunk-Z2FWAE4B.js} +6 -2
  33. package/dist/data.cjs +190 -94
  34. package/dist/data.d.cts +7 -1
  35. package/dist/data.d.ts +7 -1
  36. package/dist/data.js +8 -8
  37. package/dist/devtools.cjs +38 -10
  38. package/dist/devtools.d.cts +1 -1
  39. package/dist/devtools.d.ts +1 -1
  40. package/dist/devtools.js +6 -6
  41. package/dist/ecosystem.cjs +123 -63
  42. package/dist/ecosystem.js +9 -9
  43. package/dist/extras.cjs +380 -196
  44. package/dist/extras.d.cts +2 -2
  45. package/dist/extras.d.ts +2 -2
  46. package/dist/extras.js +27 -24
  47. package/dist/index.cjs +214 -136
  48. package/dist/index.d.cts +15 -2
  49. package/dist/index.d.ts +15 -2
  50. package/dist/index.js +15 -13
  51. package/dist/{introspect-BZWKvQUZ.d.ts → introspect-DOZfmC-4.d.ts} +1 -1
  52. package/dist/{introspect-DsJlDD2T.d.cts → introspect-RjLfIFpL.d.cts} +1 -1
  53. package/dist/motion.cjs +10 -0
  54. package/dist/motion.js +3 -3
  55. package/dist/patterns.cjs +45 -40
  56. package/dist/patterns.js +8 -7
  57. package/dist/performance.cjs +101 -25
  58. package/dist/performance.d.cts +2 -2
  59. package/dist/performance.d.ts +2 -2
  60. package/dist/performance.js +8 -7
  61. package/dist/plugins.cjs +203 -136
  62. package/dist/plugins.d.cts +1 -1
  63. package/dist/plugins.d.ts +1 -1
  64. package/dist/plugins.js +96 -45
  65. package/dist/{ssr-FXD2PPMC.js → ssr-2QDQ27EV.js} +5 -3
  66. package/dist/{ssr-CrVNy6Pa.d.cts → ssr-D62yFwuw.d.cts} +8 -1
  67. package/dist/{ssr-CrVNy6Pa.d.ts → ssr-D62yFwuw.d.ts} +8 -1
  68. package/dist/ssr.cjs +145 -66
  69. package/dist/ssr.d.cts +1 -1
  70. package/dist/ssr.d.ts +1 -1
  71. package/dist/ssr.js +12 -10
  72. package/dist/testing.cjs +9 -4
  73. package/dist/testing.js +3 -3
  74. package/dist/ui.cjs +54 -38
  75. package/dist/ui.js +10 -9
  76. package/dist/widgets.cjs +40 -24
  77. package/dist/widgets.js +8 -8
  78. package/package.json +3 -1
package/dist/extras.cjs CHANGED
@@ -164,6 +164,7 @@ __export(extras_exports, {
164
164
  inspectSignal: () => inspectSignal,
165
165
  intersection: () => intersection,
166
166
  invalidateQueries: () => invalidateQueries,
167
+ isDangerousMetaRefresh: () => isDangerousMetaRefresh,
167
168
  isDebugEnabled: () => isDebugEnabled,
168
169
  isHMRAvailable: () => isHMRAvailable,
169
170
  isWasmCached: () => isWasmCached,
@@ -480,7 +481,7 @@ function track(effectFn, subscriber) {
480
481
  function reactiveBinding(commit) {
481
482
  const run = () => {
482
483
  const s2 = subscriber;
483
- if (s2._reentrant) return;
484
+ if (s2._disposed || s2._reentrant) return;
484
485
  s2._reentrant = true;
485
486
  try {
486
487
  retrack(commit, subscriber);
@@ -496,8 +497,12 @@ function reactiveBinding(commit) {
496
497
  subscriber._runEpoch = 0;
497
498
  subscriber._runs = 0;
498
499
  subscriber._reentrant = false;
500
+ subscriber._disposed = false;
499
501
  run();
500
- return subscriber._dispose ?? (subscriber._dispose = () => cleanup(subscriber));
502
+ return subscriber._dispose ?? (subscriber._dispose = () => {
503
+ subscriber._disposed = true;
504
+ cleanup(subscriber);
505
+ });
501
506
  }
502
507
  function recordDependency(signal2) {
503
508
  if (!currentSubscriber) return;
@@ -708,6 +713,7 @@ function derived(getter, options) {
708
713
  const equals = options?.equals;
709
714
  const cs = {};
710
715
  cs._d = false;
716
+ cs._init = false;
711
717
  cs._g = getter;
712
718
  cs.__v = 0;
713
719
  const markDirty = () => {
@@ -716,11 +722,18 @@ function derived(getter, options) {
716
722
  };
717
723
  markDirty._c = 1;
718
724
  markDirty._sig = cs;
725
+ const recompute = () => {
726
+ const next = getter();
727
+ cs._v = equals && cs._init ? equals(cs._v, next) ? cs._v : next : next;
728
+ cs._d = false;
729
+ cs._init = true;
730
+ };
719
731
  track(() => {
720
732
  let threw = true;
721
733
  try {
722
734
  cs._v = getter();
723
735
  cs._d = false;
736
+ cs._init = true;
724
737
  threw = false;
725
738
  } finally {
726
739
  if (threw) cs._d = true;
@@ -736,20 +749,13 @@ function derived(getter, options) {
736
749
  }
737
750
  if (trackingSuspended) {
738
751
  if (cs._d) {
752
+ const prev = cs._v;
739
753
  evaluating = true;
740
- let threw = true;
741
754
  try {
742
- const prev = cs._v;
743
- retrack(() => {
744
- const next = getter();
745
- cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
746
- cs._d = false;
747
- threw = false;
748
- }, markDirty);
755
+ retrack(recompute, markDirty);
749
756
  if (!Object.is(prev, cs._v)) cs.__v++;
750
757
  } finally {
751
758
  evaluating = false;
752
- if (threw) cs._d = true;
753
759
  }
754
760
  }
755
761
  return cs._v;
@@ -758,18 +764,11 @@ function derived(getter, options) {
758
764
  if (cs._d) {
759
765
  const oldValue = cs._v;
760
766
  evaluating = true;
761
- let threw = true;
762
767
  try {
763
- retrack(() => {
764
- const next = getter();
765
- cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
766
- cs._d = false;
767
- threw = false;
768
- }, markDirty);
768
+ retrack(recompute, markDirty);
769
769
  if (!Object.is(oldValue, cs._v)) cs.__v++;
770
770
  } finally {
771
771
  evaluating = false;
772
- if (threw) cs._d = true;
773
772
  }
774
773
  if (hook && oldValue !== cs._v) {
775
774
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
@@ -790,11 +789,15 @@ function derived(getter, options) {
790
789
  var als = null;
791
790
  try {
792
791
  if (typeof process !== "undefined" && process.versions && process.versions.node) {
793
- const req = Function("return typeof require==='function'?require:null")();
794
- if (req) {
795
- const mod = req("node:async_hooks");
796
- als = new mod.AsyncLocalStorage();
792
+ let mod = null;
793
+ const getBuiltin = process.getBuiltinModule;
794
+ if (typeof getBuiltin === "function") {
795
+ mod = getBuiltin("node:async_hooks");
796
+ } else {
797
+ const req = Function("return typeof require==='function'?require:null")();
798
+ if (req) mod = req("node:async_hooks");
797
799
  }
800
+ if (mod) als = new mod.AsyncLocalStorage();
798
801
  }
799
802
  } catch {
800
803
  als = null;
@@ -810,6 +813,17 @@ function getSSRStore() {
810
813
  function isSSR() {
811
814
  return getSSRStore().ssr;
812
815
  }
816
+ function getRequestScopedCache(name) {
817
+ if (!isSSR()) return null;
818
+ const store = getSSRStore();
819
+ const caches = store.caches ?? (store.caches = /* @__PURE__ */ new Map());
820
+ let c = caches.get(name);
821
+ if (!c) {
822
+ c = /* @__PURE__ */ new Map();
823
+ caches.set(name, c);
824
+ }
825
+ return c;
826
+ }
813
827
 
814
828
  // src/core/signals/effect.ts
815
829
  var _g = globalThis;
@@ -896,6 +910,7 @@ function effect(effectFn, options) {
896
910
  ctx.fn(ctx.onCleanup);
897
911
  };
898
912
  const sub2 = (() => {
913
+ if (ctx.disposed) return;
899
914
  if (ctx.running) {
900
915
  ctx.rerunPending = true;
901
916
  return;
@@ -1092,9 +1107,12 @@ async function withRetry(fn, options, onRetry, signal2) {
1092
1107
  }
1093
1108
 
1094
1109
  // src/data/query.ts
1095
- var queryCache = /* @__PURE__ */ new Map();
1096
- function getOrCreateEntry(key, initialData) {
1097
- let entry = queryCache.get(key);
1110
+ var globalQueryCache = /* @__PURE__ */ new Map();
1111
+ function getActiveQueryCache() {
1112
+ return getRequestScopedCache("query") ?? globalQueryCache;
1113
+ }
1114
+ function getOrCreateEntry(cache, key, initialData) {
1115
+ let entry = cache.get(key);
1098
1116
  if (!entry) {
1099
1117
  entry = {
1100
1118
  data: initialData,
@@ -1106,7 +1124,7 @@ function getOrCreateEntry(key, initialData) {
1106
1124
  listeners: /* @__PURE__ */ new Set(),
1107
1125
  refetchers: /* @__PURE__ */ new Set()
1108
1126
  };
1109
- queryCache.set(key, entry);
1127
+ cache.set(key, entry);
1110
1128
  }
1111
1129
  return entry;
1112
1130
  }
@@ -1126,6 +1144,7 @@ function query(key, fetcher, options = {}) {
1126
1144
  select: select3
1127
1145
  } = options;
1128
1146
  const resolveKey = typeof key === "function" ? key : () => key;
1147
+ const cache = getActiveQueryCache();
1129
1148
  const [data2, setData] = signal(initialData);
1130
1149
  const [isFetching, setIsFetching] = signal(false);
1131
1150
  const [error, setError] = signal(void 0);
@@ -1137,16 +1156,16 @@ function query(key, fetcher, options = {}) {
1137
1156
  const isStale = derived(() => {
1138
1157
  data2();
1139
1158
  if (!currentKey) return true;
1140
- const entry = queryCache.get(currentKey);
1159
+ const entry = cache.get(currentKey);
1141
1160
  if (!entry || entry.dataUpdatedAt === 0) return true;
1142
1161
  return Date.now() - entry.dataUpdatedAt >= staleTime;
1143
1162
  });
1144
1163
  async function doFetch() {
1145
1164
  if (disposed || !currentKey || !enabled) return;
1146
1165
  const key2 = currentKey;
1147
- let entry = queryCache.get(key2);
1166
+ let entry = cache.get(key2);
1148
1167
  if (!entry) {
1149
- entry = getOrCreateEntry(key2);
1168
+ entry = getOrCreateEntry(cache, key2);
1150
1169
  entry.listeners.add(onCacheUpdate);
1151
1170
  entry.refetchers.add(doFetch);
1152
1171
  }
@@ -1221,7 +1240,7 @@ function query(key, fetcher, options = {}) {
1221
1240
  }
1222
1241
  function onCacheUpdate() {
1223
1242
  if (disposed || !currentKey) return;
1224
- const entry = queryCache.get(currentKey);
1243
+ const entry = cache.get(currentKey);
1225
1244
  if (!entry) {
1226
1245
  batch(() => {
1227
1246
  setData(void 0);
@@ -1241,7 +1260,7 @@ function query(key, fetcher, options = {}) {
1241
1260
  const effectCleanup = effect(() => {
1242
1261
  const key2 = resolveKey();
1243
1262
  if (currentKey !== null && currentKey !== key2) {
1244
- const oldEntry = queryCache.get(currentKey);
1263
+ const oldEntry = cache.get(currentKey);
1245
1264
  if (oldEntry) {
1246
1265
  oldEntry.listeners.delete(onCacheUpdate);
1247
1266
  oldEntry.refetchers.delete(doFetch);
@@ -1249,13 +1268,13 @@ function query(key, fetcher, options = {}) {
1249
1268
  if (oldEntry.subscribers <= 0 && cacheTime >= 0) {
1250
1269
  const oldKey = currentKey;
1251
1270
  if (oldEntry.gcTimer !== null) clearTimeout(oldEntry.gcTimer);
1252
- oldEntry.gcTimer = setTimeout(() => queryCache.delete(oldKey), cacheTime);
1271
+ oldEntry.gcTimer = setTimeout(() => cache.delete(oldKey), cacheTime);
1253
1272
  }
1254
1273
  }
1255
1274
  }
1256
1275
  const keyChanged = currentKey !== key2;
1257
1276
  currentKey = key2;
1258
- const entry = getOrCreateEntry(key2, initialData);
1277
+ const entry = getOrCreateEntry(cache, key2, initialData);
1259
1278
  if (keyChanged) entry.subscribers++;
1260
1279
  if (entry.gcTimer !== null) {
1261
1280
  clearTimeout(entry.gcTimer);
@@ -1309,7 +1328,7 @@ function query(key, fetcher, options = {}) {
1309
1328
  effectCleanup();
1310
1329
  if (intervalTimer) clearInterval(intervalTimer);
1311
1330
  if (currentKey) {
1312
- const entry = queryCache.get(currentKey);
1331
+ const entry = cache.get(currentKey);
1313
1332
  if (entry) {
1314
1333
  entry.listeners.delete(onCacheUpdate);
1315
1334
  entry.refetchers.delete(doFetch);
@@ -1317,7 +1336,7 @@ function query(key, fetcher, options = {}) {
1317
1336
  if (entry.subscribers <= 0 && cacheTime >= 0) {
1318
1337
  const key2 = currentKey;
1319
1338
  if (entry.gcTimer !== null) clearTimeout(entry.gcTimer);
1320
- entry.gcTimer = setTimeout(() => queryCache.delete(key2), cacheTime);
1339
+ entry.gcTimer = setTimeout(() => cache.delete(key2), cacheTime);
1321
1340
  }
1322
1341
  }
1323
1342
  }
@@ -1340,7 +1359,7 @@ function query(key, fetcher, options = {}) {
1340
1359
  }
1341
1360
  function invalidateQueries(keyOrPredicate) {
1342
1361
  const predicate = typeof keyOrPredicate === "function" ? keyOrPredicate : (k) => k === keyOrPredicate;
1343
- for (const [key, entry] of queryCache.entries()) {
1362
+ for (const [key, entry] of getActiveQueryCache().entries()) {
1344
1363
  if (predicate(key)) {
1345
1364
  entry.dataUpdatedAt = 0;
1346
1365
  for (const refetcher of entry.refetchers) refetcher();
@@ -1348,10 +1367,10 @@ function invalidateQueries(keyOrPredicate) {
1348
1367
  }
1349
1368
  }
1350
1369
  function getQueryData(key) {
1351
- return queryCache.get(key)?.data;
1370
+ return getActiveQueryCache().get(key)?.data;
1352
1371
  }
1353
1372
  function setQueryData(key, data2) {
1354
- const entry = queryCache.get(key);
1373
+ const entry = getActiveQueryCache().get(key);
1355
1374
  if (!entry) return;
1356
1375
  const newData = typeof data2 === "function" ? data2(entry.data) : data2;
1357
1376
  entry.data = newData;
@@ -1361,14 +1380,15 @@ function setQueryData(key, data2) {
1361
1380
  function clearQueryCache() {
1362
1381
  const activeListeners = [];
1363
1382
  const activeRefetchers = [];
1364
- for (const entry of queryCache.values()) {
1383
+ const activeCache = getActiveQueryCache();
1384
+ for (const entry of activeCache.values()) {
1365
1385
  if (entry.gcTimer) clearTimeout(entry.gcTimer);
1366
1386
  if (entry.subscribers > 0) {
1367
1387
  for (const listener of entry.listeners) activeListeners.push(listener);
1368
1388
  for (const refetcher of entry.refetchers) activeRefetchers.push(refetcher);
1369
1389
  }
1370
1390
  }
1371
- queryCache.clear();
1391
+ activeCache.clear();
1372
1392
  for (const listener of activeListeners) listener();
1373
1393
  for (const refetcher of activeRefetchers) {
1374
1394
  refetcher().catch((err) => {
@@ -1379,10 +1399,11 @@ function clearQueryCache() {
1379
1399
  }
1380
1400
  }
1381
1401
  function __resetQueryCache() {
1382
- for (const entry of queryCache.values()) {
1402
+ const activeCache = getActiveQueryCache();
1403
+ for (const entry of activeCache.values()) {
1383
1404
  if (entry.gcTimer) clearTimeout(entry.gcTimer);
1384
1405
  }
1385
- queryCache.clear();
1406
+ activeCache.clear();
1386
1407
  }
1387
1408
 
1388
1409
  // src/data/mutation.ts
@@ -1394,7 +1415,11 @@ function mutation(mutationFn, options = {}) {
1394
1415
  const isSuccess = derived(() => status() === "success");
1395
1416
  const isIdle = derived(() => status() === "idle");
1396
1417
  let runId = 0;
1418
+ let abortController = null;
1397
1419
  async function execute(variables) {
1420
+ abortController?.abort();
1421
+ abortController = new AbortController();
1422
+ const signal2 = abortController.signal;
1398
1423
  const myRun = ++runId;
1399
1424
  let context2;
1400
1425
  batch(() => {
@@ -1406,7 +1431,7 @@ function mutation(mutationFn, options = {}) {
1406
1431
  if (options.onMutate) {
1407
1432
  context2 = await options.onMutate(variables);
1408
1433
  }
1409
- const result = await withRetry(() => mutationFn(variables), options.retry);
1434
+ const result = await withRetry(() => mutationFn(variables, signal2), options.retry, void 0, signal2);
1410
1435
  if (myRun !== runId) return result;
1411
1436
  batch(() => {
1412
1437
  setData(result);
@@ -1418,6 +1443,7 @@ function mutation(mutationFn, options = {}) {
1418
1443
  return result;
1419
1444
  } catch (err) {
1420
1445
  const errorObj = err instanceof Error ? err : new Error(String(err));
1446
+ if (errorObj instanceof DOMException && errorObj.name === "AbortError") throw errorObj;
1421
1447
  if (myRun !== runId) throw errorObj;
1422
1448
  batch(() => {
1423
1449
  setError(errorObj);
@@ -1431,6 +1457,8 @@ function mutation(mutationFn, options = {}) {
1431
1457
  }
1432
1458
  function reset() {
1433
1459
  runId++;
1460
+ abortController?.abort();
1461
+ abortController = null;
1434
1462
  batch(() => {
1435
1463
  setData(void 0);
1436
1464
  setError(void 0);
@@ -1446,6 +1474,7 @@ function mutation(mutationFn, options = {}) {
1446
1474
  isIdle,
1447
1475
  mutate: (variables) => {
1448
1476
  execute(variables).catch((err) => {
1477
+ if (err instanceof DOMException && err.name === "AbortError") return;
1449
1478
  if (typeof console !== "undefined") {
1450
1479
  console.warn("[SibuJS mutation] mutate() failed; check `.error()` signal or onError option.", err);
1451
1480
  }
@@ -1462,6 +1491,7 @@ function infiniteQuery(key, fetcher, options) {
1462
1491
  getNextPageParam,
1463
1492
  getPreviousPageParam,
1464
1493
  initialPageParam = 0,
1494
+ maxPages,
1465
1495
  enabled = true,
1466
1496
  retry: retryOptions,
1467
1497
  onSuccess,
@@ -1485,8 +1515,9 @@ function infiniteQuery(key, fetcher, options) {
1485
1515
  let abortController = null;
1486
1516
  let disposed = false;
1487
1517
  let runId = 0;
1488
- async function fetchPage(pageParam, direction) {
1489
- if (disposed) return;
1518
+ let inFlight = null;
1519
+ function fetchPage(pageParam, direction) {
1520
+ if (disposed) return Promise.resolve();
1490
1521
  abortController?.abort();
1491
1522
  abortController = new AbortController();
1492
1523
  const signal2 = abortController.signal;
@@ -1497,39 +1528,44 @@ function infiniteQuery(key, fetcher, options) {
1497
1528
  if (direction === "prev") setIsFetchingPrev(true);
1498
1529
  setError(void 0);
1499
1530
  });
1500
- try {
1501
- const page = await withRetry(() => fetcher({ signal: signal2, pageParam }), retryOptions, void 0, signal2);
1502
- if (disposed || myRun !== runId) return;
1503
- const currentPages = pages();
1504
- let newPages;
1505
- if (direction === "prev") {
1506
- newPages = [page, ...currentPages];
1507
- } else {
1508
- newPages = [...currentPages, page];
1531
+ const promise = (async () => {
1532
+ try {
1533
+ const page = await withRetry(() => fetcher({ signal: signal2, pageParam }), retryOptions, void 0, signal2);
1534
+ if (disposed || myRun !== runId) return;
1535
+ const currentPages = pages();
1536
+ let newPages = direction === "prev" ? [page, ...currentPages] : [...currentPages, page];
1537
+ if (maxPages != null && maxPages > 0 && newPages.length > maxPages) {
1538
+ newPages = direction === "prev" ? newPages.slice(0, maxPages) : newPages.slice(newPages.length - maxPages);
1539
+ }
1540
+ const nextParam = getNextPageParam(newPages[newPages.length - 1], newPages);
1541
+ const prevParam = getPreviousPageParam?.(newPages[0], newPages);
1542
+ batch(() => {
1543
+ setPages(newPages);
1544
+ setNextPageParam(nextParam);
1545
+ setPrevPageParam(prevParam);
1546
+ setIsFetching(false);
1547
+ setIsFetchingNext(false);
1548
+ setIsFetchingPrev(false);
1549
+ });
1550
+ onSuccess?.(newPages);
1551
+ } catch (err) {
1552
+ if (disposed || myRun !== runId) return;
1553
+ if (err instanceof DOMException && err.name === "AbortError") return;
1554
+ const errorObj = err instanceof Error ? err : new Error(String(err));
1555
+ batch(() => {
1556
+ setError(errorObj);
1557
+ setIsFetching(false);
1558
+ setIsFetchingNext(false);
1559
+ setIsFetchingPrev(false);
1560
+ });
1561
+ onError?.(errorObj);
1509
1562
  }
1510
- const nextParam = getNextPageParam(page, newPages);
1511
- const prevParam = getPreviousPageParam?.(newPages[0], newPages);
1512
- batch(() => {
1513
- setPages(newPages);
1514
- setNextPageParam(nextParam);
1515
- setPrevPageParam(prevParam);
1516
- setIsFetching(false);
1517
- setIsFetchingNext(false);
1518
- setIsFetchingPrev(false);
1519
- });
1520
- onSuccess?.(newPages);
1521
- } catch (err) {
1522
- if (disposed || myRun !== runId) return;
1523
- if (err instanceof DOMException && err.name === "AbortError") return;
1524
- const errorObj = err instanceof Error ? err : new Error(String(err));
1525
- batch(() => {
1526
- setError(errorObj);
1527
- setIsFetching(false);
1528
- setIsFetchingNext(false);
1529
- setIsFetchingPrev(false);
1530
- });
1531
- onError?.(errorObj);
1532
- }
1563
+ })();
1564
+ inFlight = promise;
1565
+ void promise.finally(() => {
1566
+ if (inFlight === promise) inFlight = null;
1567
+ });
1568
+ return promise;
1533
1569
  }
1534
1570
  const effectCleanup = effect(() => {
1535
1571
  resolveKey();
@@ -1544,11 +1580,13 @@ function infiniteQuery(key, fetcher, options) {
1544
1580
  }
1545
1581
  });
1546
1582
  function fetchNextPage() {
1583
+ if (inFlight) return inFlight;
1547
1584
  const param2 = nextPageParam();
1548
1585
  if (param2 === void 0) return Promise.resolve();
1549
1586
  return fetchPage(param2, "next");
1550
1587
  }
1551
1588
  function fetchPreviousPage() {
1589
+ if (inFlight) return inFlight;
1552
1590
  const param2 = prevPageParam();
1553
1591
  if (param2 === void 0) return Promise.resolve();
1554
1592
  return fetchPage(param2, "prev");
@@ -1587,13 +1625,17 @@ function infiniteQuery(key, fetcher, options) {
1587
1625
  function previous(getter) {
1588
1626
  const [previous2, setPrevious] = signal(void 0);
1589
1627
  let current = getter();
1590
- effect(() => {
1628
+ const stop2 = effect(() => {
1591
1629
  const next = getter();
1592
1630
  if (!Object.is(next, current)) {
1593
1631
  setPrevious(current);
1594
1632
  current = next;
1595
1633
  }
1596
1634
  });
1635
+ Object.defineProperty(previous2, "dispose", {
1636
+ value: stop2,
1637
+ enumerable: false
1638
+ });
1597
1639
  return previous2;
1598
1640
  }
1599
1641
 
@@ -1601,7 +1643,7 @@ function previous(getter) {
1601
1643
  function debounce(getter, delay) {
1602
1644
  const [debounced, setDebounced] = signal(getter());
1603
1645
  let timer = null;
1604
- effect(() => {
1646
+ const stop2 = effect(() => {
1605
1647
  const value = getter();
1606
1648
  if (timer !== null) clearTimeout(timer);
1607
1649
  timer = setTimeout(() => {
@@ -1609,6 +1651,16 @@ function debounce(getter, delay) {
1609
1651
  timer = null;
1610
1652
  }, delay);
1611
1653
  });
1654
+ Object.defineProperty(debounced, "dispose", {
1655
+ value: () => {
1656
+ stop2();
1657
+ if (timer !== null) {
1658
+ clearTimeout(timer);
1659
+ timer = null;
1660
+ }
1661
+ },
1662
+ enumerable: false
1663
+ });
1612
1664
  return debounced;
1613
1665
  }
1614
1666
 
@@ -1618,7 +1670,8 @@ function throttle(getter, interval) {
1618
1670
  let cooldown = false;
1619
1671
  let pending = null;
1620
1672
  let lastEmitted = getter();
1621
- effect(() => {
1673
+ let timer = null;
1674
+ const stop2 = effect(() => {
1622
1675
  const value = getter();
1623
1676
  if (!cooldown) {
1624
1677
  if (!Object.is(value, lastEmitted)) {
@@ -1626,7 +1679,8 @@ function throttle(getter, interval) {
1626
1679
  lastEmitted = value;
1627
1680
  cooldown = true;
1628
1681
  pending = null;
1629
- setTimeout(() => {
1682
+ timer = setTimeout(() => {
1683
+ timer = null;
1630
1684
  cooldown = false;
1631
1685
  if (pending !== null) {
1632
1686
  const trailingValue = pending.value;
@@ -1640,6 +1694,16 @@ function throttle(getter, interval) {
1640
1694
  pending = { value };
1641
1695
  }
1642
1696
  });
1697
+ Object.defineProperty(throttled, "dispose", {
1698
+ value: () => {
1699
+ stop2();
1700
+ if (timer !== null) {
1701
+ clearTimeout(timer);
1702
+ timer = null;
1703
+ }
1704
+ },
1705
+ enumerable: false
1706
+ });
1643
1707
  return throttled;
1644
1708
  }
1645
1709
 
@@ -1768,28 +1832,45 @@ function idbGet(db, store, key) {
1768
1832
  req.onerror = () => reject(req.error);
1769
1833
  });
1770
1834
  }
1771
- function idbPut(db, store, item) {
1835
+ function idbPut(db, store, item, key) {
1772
1836
  return new Promise((resolve, reject) => {
1773
1837
  const tx = db.transaction(store, "readwrite");
1774
- tx.objectStore(store).put(item);
1838
+ if (key !== void 0) tx.objectStore(store).put(item, key);
1839
+ else tx.objectStore(store).put(item);
1775
1840
  tx.oncomplete = () => resolve();
1776
1841
  tx.onerror = () => reject(tx.error);
1777
1842
  });
1778
1843
  }
1779
- function idbPutWithChange(db, item, change) {
1844
+ function coalesceAndAddChange(tx, change, keyPath) {
1845
+ const store = tx.objectStore("_changes");
1846
+ const targetKey = change.item[keyPath];
1847
+ const cursorReq = store.openCursor();
1848
+ cursorReq.onsuccess = () => {
1849
+ const cursor = cursorReq.result;
1850
+ if (cursor) {
1851
+ const existing = cursor.value;
1852
+ const k = existing.item[keyPath];
1853
+ if (targetKey != null && k === targetKey) cursor.delete();
1854
+ cursor.continue();
1855
+ } else {
1856
+ store.put(change);
1857
+ }
1858
+ };
1859
+ }
1860
+ function idbPutWithChange(db, item, change, keyPath) {
1780
1861
  return new Promise((resolve, reject) => {
1781
1862
  const tx = db.transaction(["items", "_changes"], "readwrite");
1782
1863
  tx.objectStore("items").put(item);
1783
- tx.objectStore("_changes").put(change);
1864
+ coalesceAndAddChange(tx, change, keyPath);
1784
1865
  tx.oncomplete = () => resolve();
1785
1866
  tx.onerror = () => reject(tx.error);
1786
1867
  });
1787
1868
  }
1788
- function idbDeleteWithChange(db, key, change) {
1869
+ function idbDeleteWithChange(db, key, change, keyPath) {
1789
1870
  return new Promise((resolve, reject) => {
1790
1871
  const tx = db.transaction(["items", "_changes"], "readwrite");
1791
1872
  tx.objectStore("items").delete(key);
1792
- tx.objectStore("_changes").put(change);
1873
+ coalesceAndAddChange(tx, change, keyPath);
1793
1874
  tx.oncomplete = () => resolve();
1794
1875
  tx.onerror = () => reject(tx.error);
1795
1876
  });
@@ -1849,13 +1930,18 @@ async function offlineStore(options) {
1849
1930
  setPendingCount(changes.length);
1850
1931
  }
1851
1932
  async function put(item) {
1852
- await idbPutWithChange(db, item, { type: "put", item, timestamp: Date.now() });
1933
+ await idbPutWithChange(db, item, { type: "put", item, timestamp: Date.now() }, keyPath);
1853
1934
  await refreshData();
1854
1935
  }
1855
1936
  async function remove(key) {
1856
1937
  const existing = await idbGet(db, "items", key);
1857
1938
  if (existing) {
1858
- await idbDeleteWithChange(db, key, { type: "delete", item: existing, timestamp: Date.now() });
1939
+ await idbDeleteWithChange(
1940
+ db,
1941
+ key,
1942
+ { type: "delete", item: existing, timestamp: Date.now() },
1943
+ keyPath
1944
+ );
1859
1945
  await refreshData();
1860
1946
  }
1861
1947
  }
@@ -1881,6 +1967,8 @@ async function offlineStore(options) {
1881
1967
  snapshot.map((e) => e.key)
1882
1968
  );
1883
1969
  if (closed) return;
1970
+ } else if (typeof console !== "undefined") {
1971
+ console.warn(`[offlineStore] push rejected by adapter${result.error ? `: ${result.error}` : ""}`);
1884
1972
  }
1885
1973
  }
1886
1974
  const remoteItems = await adapter.pull(lastSynced());
@@ -1899,7 +1987,7 @@ async function offlineStore(options) {
1899
1987
  await idbPutMany(db, "items", safeRemote);
1900
1988
  if (closed) return;
1901
1989
  const now = Date.now();
1902
- await idbPut(db, "_meta", now);
1990
+ await idbPut(db, "_meta", now, "lastSynced");
1903
1991
  if (closed) return;
1904
1992
  setLastSynced(now);
1905
1993
  await refreshData();
@@ -1998,14 +2086,20 @@ function loaderData() {
1998
2086
  async function preloadRoute(route, context2, callerSignal) {
1999
2087
  if (!route.loader) return void 0;
2000
2088
  const controller = new AbortController();
2089
+ let onAbort = null;
2001
2090
  if (callerSignal) {
2002
2091
  if (callerSignal.aborted) {
2003
2092
  controller.abort();
2004
2093
  } else {
2005
- callerSignal.addEventListener("abort", () => controller.abort(), { once: true });
2094
+ onAbort = () => controller.abort();
2095
+ callerSignal.addEventListener("abort", onAbort, { once: true });
2006
2096
  }
2007
2097
  }
2008
- return route.loader(context2, { signal: controller.signal });
2098
+ try {
2099
+ return await route.loader(context2, { signal: controller.signal });
2100
+ } finally {
2101
+ if (onAbort) callerSignal?.removeEventListener("abort", onAbort);
2102
+ }
2009
2103
  }
2010
2104
 
2011
2105
  // src/ui/socket.ts
@@ -2111,9 +2205,17 @@ function socket(url, options) {
2111
2205
  }
2112
2206
 
2113
2207
  // src/utils/sanitize.ts
2208
+ function stripControlChars(value) {
2209
+ return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
2210
+ }
2211
+ function isEventHandlerAttr(name) {
2212
+ if (name.length < 3) return false;
2213
+ const lower = name.toLowerCase();
2214
+ return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
2215
+ }
2114
2216
  var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
2115
2217
  function sanitizeUrl(url) {
2116
- const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
2218
+ const trimmed = stripControlChars(url).trim();
2117
2219
  if (!trimmed) return "";
2118
2220
  const lower = trimmed.toLowerCase();
2119
2221
  let schemeEnd = -1;
@@ -2162,7 +2264,20 @@ function sanitizeCSSValue(value) {
2162
2264
  return value;
2163
2265
  }
2164
2266
  function stripHtml(html2) {
2165
- return String(html2).replace(/<[^>]*>/g, "");
2267
+ const input2 = String(html2);
2268
+ if (typeof DOMParser !== "undefined") {
2269
+ try {
2270
+ return new DOMParser().parseFromString(input2, "text/html").body.textContent ?? "";
2271
+ } catch {
2272
+ }
2273
+ }
2274
+ let prev;
2275
+ let out = input2;
2276
+ do {
2277
+ prev = out;
2278
+ out = out.replace(/<[^>]*>/g, "");
2279
+ } while (out !== prev);
2280
+ return out.replace(/<[^>]*$/, "");
2166
2281
  }
2167
2282
  var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
2168
2283
  "href",
@@ -2179,7 +2294,7 @@ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
2179
2294
  "data"
2180
2295
  ]);
2181
2296
  function isUrlAttribute(attr) {
2182
- return URL_ATTRIBUTES.has(attr);
2297
+ return URL_ATTRIBUTES.has(attr.toLowerCase());
2183
2298
  }
2184
2299
 
2185
2300
  // src/ui/stream.ts
@@ -2562,6 +2677,19 @@ function clipboard() {
2562
2677
  return { text: text2, copy, copied, dispose: dispose2 };
2563
2678
  }
2564
2679
 
2680
+ // src/utils/guards.ts
2681
+ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2682
+ function isUnsafeKey(key) {
2683
+ return UNSAFE_KEYS.has(key);
2684
+ }
2685
+ function stripUnsafeKeys(obj) {
2686
+ const out = {};
2687
+ for (const k of Object.keys(obj)) {
2688
+ if (!isUnsafeKey(k)) out[k] = obj[k];
2689
+ }
2690
+ return out;
2691
+ }
2692
+
2565
2693
  // src/browser/dragDrop.ts
2566
2694
  function resolveTarget2(target) {
2567
2695
  return typeof target === "function" ? target : () => target.current;
@@ -2647,10 +2775,7 @@ function dropZone(element, options) {
2647
2775
  const raw = e.dataTransfer.getData("application/json");
2648
2776
  if (raw) {
2649
2777
  try {
2650
- transferData = JSON.parse(
2651
- raw,
2652
- (k, v) => k === "__proto__" || k === "constructor" || k === "prototype" ? void 0 : v
2653
- );
2778
+ transferData = JSON.parse(raw, (k, v) => isUnsafeKey(k) ? void 0 : v);
2654
2779
  } catch {
2655
2780
  transferData = raw;
2656
2781
  }
@@ -3583,11 +3708,7 @@ function machine(config) {
3583
3708
  }
3584
3709
  if (action) {
3585
3710
  const rawPatch = action(ctx);
3586
- const next = { ...ctx };
3587
- for (const key of Object.keys(rawPatch)) {
3588
- if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
3589
- next[key] = rawPatch[key];
3590
- }
3711
+ const next = { ...ctx, ...stripUnsafeKeys(rawPatch) };
3591
3712
  setContext(next);
3592
3713
  }
3593
3714
  setState(target);
@@ -3618,10 +3739,7 @@ function machine(config) {
3618
3739
  function persisted(key, initial, options = {}) {
3619
3740
  const storage = options.session ? sessionStorage : localStorage;
3620
3741
  const serialize = options.serialize || JSON.stringify;
3621
- const safeReviver = (k, v) => {
3622
- if (k === "__proto__" || k === "constructor" || k === "prototype") return void 0;
3623
- return v;
3624
- };
3742
+ const safeReviver = (k, v) => isUnsafeKey(k) ? void 0 : v;
3625
3743
  const deserialize = options.deserialize || ((raw) => JSON.parse(raw, safeReviver));
3626
3744
  const encrypt = options.encrypt;
3627
3745
  const decrypt = options.decrypt;
@@ -3911,6 +4029,7 @@ function deepClone(value) {
3911
4029
  if (Array.isArray(v)) return v.map(clone);
3912
4030
  const out = {};
3913
4031
  for (const k of Object.keys(v)) {
4032
+ if (k === "__proto__") continue;
3914
4033
  out[k] = clone(v[k]);
3915
4034
  }
3916
4035
  return out;
@@ -3928,12 +4047,7 @@ function globalStore(config) {
3928
4047
  const execute = () => {
3929
4048
  const current = getState();
3930
4049
  const rawPatch = actionFn(current, payload);
3931
- const patch = {};
3932
- for (const key of Object.keys(rawPatch)) {
3933
- if (key !== "__proto__" && key !== "constructor" && key !== "prototype") {
3934
- patch[key] = rawPatch[key];
3935
- }
3936
- }
4050
+ const patch = stripUnsafeKeys(rawPatch);
3937
4051
  setState({ ...current, ...patch });
3938
4052
  const newState = getState();
3939
4053
  for (const listener of listeners) {
@@ -4006,7 +4120,7 @@ function withBoundary(name, component) {
4006
4120
  function createSlots(slots) {
4007
4121
  return {
4008
4122
  renderSlot(name, fallback) {
4009
- const slotFn = slots[name];
4123
+ const slotFn = Object.hasOwn(slots, name) ? slots[name] : void 0;
4010
4124
  if (slotFn) {
4011
4125
  const result = slotFn();
4012
4126
  if (Array.isArray(result)) {
@@ -4020,7 +4134,7 @@ function createSlots(slots) {
4020
4134
  return fallback ? fallback() : null;
4021
4135
  },
4022
4136
  hasSlot(name) {
4023
- return name in slots;
4137
+ return Object.hasOwn(slots, name);
4024
4138
  }
4025
4139
  };
4026
4140
  }
@@ -4148,11 +4262,17 @@ function transition(element, options = {}) {
4148
4262
  } = options;
4149
4263
  const transitionValue = `${property} ${duration}ms ${easing} ${delay}ms`;
4150
4264
  let activeTimer = null;
4265
+ let pendingResolve = null;
4151
4266
  function cancelPending() {
4152
4267
  if (activeTimer !== null) {
4153
4268
  clearTimeout(activeTimer);
4154
4269
  activeTimer = null;
4155
4270
  }
4271
+ if (pendingResolve !== null) {
4272
+ const resolvePrev = pendingResolve;
4273
+ pendingResolve = null;
4274
+ resolvePrev();
4275
+ }
4156
4276
  }
4157
4277
  function enter() {
4158
4278
  return new Promise((resolve) => {
@@ -4164,11 +4284,13 @@ function transition(element, options = {}) {
4164
4284
  if (activeClass) element.classList.add(activeClass);
4165
4285
  const done = () => {
4166
4286
  activeTimer = null;
4287
+ pendingResolve = null;
4167
4288
  if (enterClass) element.classList.remove(enterClass);
4168
4289
  onEnterDone?.();
4169
4290
  resolve();
4170
4291
  };
4171
4292
  if (duration > 0) {
4293
+ pendingResolve = resolve;
4172
4294
  activeTimer = setTimeout(done, duration + delay);
4173
4295
  } else {
4174
4296
  done();
@@ -4184,11 +4306,13 @@ function transition(element, options = {}) {
4184
4306
  if (enterClass) element.classList.remove(enterClass);
4185
4307
  const done = () => {
4186
4308
  activeTimer = null;
4309
+ pendingResolve = null;
4187
4310
  if (leaveClass) element.classList.remove(leaveClass);
4188
4311
  onLeaveDone?.();
4189
4312
  resolve();
4190
4313
  };
4191
4314
  if (duration > 0) {
4315
+ pendingResolve = resolve;
4192
4316
  activeTimer = setTimeout(done, duration + delay);
4193
4317
  } else {
4194
4318
  done();
@@ -4816,7 +4940,10 @@ function VirtualList(props) {
4816
4940
  const visibleCount = Math.ceil(props.containerHeight / props.itemHeight) + 2 * overscan;
4817
4941
  const endIndex = Math.min(items.length, startIndex + visibleCount);
4818
4942
  content.style.top = `${startIndex * props.itemHeight}px`;
4819
- content.innerHTML = "";
4943
+ while (content.firstChild) {
4944
+ dispose(content.firstChild);
4945
+ content.removeChild(content.firstChild);
4946
+ }
4820
4947
  for (let i2 = startIndex; i2 < endIndex; i2++) {
4821
4948
  const itemEl = props.renderItem(items[i2], i2);
4822
4949
  itemEl.style.height = `${props.itemHeight}px`;
@@ -4824,7 +4951,7 @@ function VirtualList(props) {
4824
4951
  content.appendChild(itemEl);
4825
4952
  }
4826
4953
  };
4827
- effect(update);
4954
+ registerDisposer(container, effect(update));
4828
4955
  return container;
4829
4956
  }
4830
4957
 
@@ -4965,7 +5092,10 @@ function inputMask(options) {
4965
5092
  }
4966
5093
  }
4967
5094
  }
4968
- input2.setSelectionRange(newCursor, newCursor);
5095
+ try {
5096
+ input2.setSelectionRange(newCursor, newCursor);
5097
+ } catch {
5098
+ }
4969
5099
  };
4970
5100
  const onFocus = () => {
4971
5101
  if (!input2.value) {
@@ -5007,9 +5137,12 @@ function aria(element, attrs) {
5007
5137
  const ariaKey = key.startsWith("aria-") ? key : `aria-${key}`;
5008
5138
  if (typeof value === "function") {
5009
5139
  const getter = value;
5010
- track(() => {
5011
- element.setAttribute(ariaKey, String(getter()));
5012
- });
5140
+ registerDisposer(
5141
+ element,
5142
+ track(() => {
5143
+ element.setAttribute(ariaKey, String(getter()));
5144
+ })
5145
+ );
5013
5146
  } else {
5014
5147
  element.setAttribute(ariaKey, String(value));
5015
5148
  }
@@ -5283,11 +5416,6 @@ var _isDev5 = isDev();
5283
5416
  function setProp(el, key, val) {
5284
5417
  el[key] = val;
5285
5418
  }
5286
- function isEventHandlerAttr(name) {
5287
- if (name.length < 3) return false;
5288
- const lower = name.toLowerCase();
5289
- return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
5290
- }
5291
5419
  function bindAttribute(el, attr, getter) {
5292
5420
  if (isEventHandlerAttr(attr)) {
5293
5421
  if (_isDev5)
@@ -5683,11 +5811,26 @@ function processQueue() {
5683
5811
  scheduleFrame();
5684
5812
  }
5685
5813
  }
5814
+ var TIER_SPEED = { microtask: 0, frame: 1, timeout: 2, idle: 3 };
5815
+ function cancelScheduled() {
5816
+ if (scheduledHandle !== null) {
5817
+ if (scheduledKind === "frame") cancelAnimationFrame(scheduledHandle);
5818
+ else if (scheduledKind === "idle" && typeof cancelIdleCallback !== "undefined") cancelIdleCallback(scheduledHandle);
5819
+ else if (scheduledKind === "timeout") clearTimeout(scheduledHandle);
5820
+ }
5821
+ scheduledHandle = null;
5822
+ scheduledKind = null;
5823
+ }
5686
5824
  function scheduleFrame() {
5687
- if (scheduledKind !== null || microtaskScheduled) return;
5688
5825
  const nextTask = taskQueue.find((t) => !t.cancelled);
5689
5826
  if (!nextTask) return;
5690
- if (nextTask.priority <= Priority.USER_BLOCKING) {
5827
+ const desired = nextTask.priority <= Priority.USER_BLOCKING ? "microtask" : nextTask.priority === Priority.IDLE ? typeof requestIdleCallback !== "undefined" ? "idle" : "timeout" : "frame";
5828
+ if (microtaskScheduled) return;
5829
+ if (scheduledKind !== null) {
5830
+ if (TIER_SPEED[scheduledKind] <= TIER_SPEED[desired]) return;
5831
+ cancelScheduled();
5832
+ }
5833
+ if (desired === "microtask") {
5691
5834
  microtaskScheduled = true;
5692
5835
  scheduledKind = "microtask";
5693
5836
  queueMicrotask(() => {
@@ -5695,22 +5838,20 @@ function scheduleFrame() {
5695
5838
  scheduledKind = null;
5696
5839
  processQueue();
5697
5840
  });
5698
- } else if (nextTask.priority === Priority.IDLE) {
5699
- if (typeof requestIdleCallback !== "undefined") {
5700
- scheduledKind = "idle";
5701
- scheduledHandle = requestIdleCallback(() => {
5702
- scheduledKind = null;
5703
- scheduledHandle = null;
5704
- processQueue();
5705
- });
5706
- } else {
5707
- scheduledKind = "timeout";
5708
- scheduledHandle = setTimeout(() => {
5709
- scheduledKind = null;
5710
- scheduledHandle = null;
5711
- processQueue();
5712
- }, 50);
5713
- }
5841
+ } else if (desired === "idle") {
5842
+ scheduledKind = "idle";
5843
+ scheduledHandle = requestIdleCallback(() => {
5844
+ scheduledKind = null;
5845
+ scheduledHandle = null;
5846
+ processQueue();
5847
+ });
5848
+ } else if (desired === "timeout") {
5849
+ scheduledKind = "timeout";
5850
+ scheduledHandle = setTimeout(() => {
5851
+ scheduledKind = null;
5852
+ scheduledHandle = null;
5853
+ processQueue();
5854
+ }, 50);
5714
5855
  } else {
5715
5856
  scheduledKind = "frame";
5716
5857
  scheduledHandle = requestAnimationFrame(() => {
@@ -5775,7 +5916,7 @@ function yieldToMain() {
5775
5916
  async function processInChunks(items, processor, chunkSize = 50) {
5776
5917
  for (let i2 = 0; i2 < items.length; i2++) {
5777
5918
  processor(items[i2], i2);
5778
- if (i2 > 0 && i2 % chunkSize === 0) {
5919
+ if ((i2 + 1) % chunkSize === 0 && i2 + 1 < items.length) {
5779
5920
  await yieldToMain();
5780
5921
  }
5781
5922
  }
@@ -5956,16 +6097,14 @@ function noSideEffect(fn) {
5956
6097
  }
5957
6098
 
5958
6099
  // src/platform/ssr.ts
6100
+ function sanitizeUrlAttr(name, value) {
6101
+ return name === "srcset" ? sanitizeSrcset(value) : sanitizeUrl(value);
6102
+ }
5959
6103
  var _isDev7 = isDev();
5960
6104
  var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
5961
6105
  function isSafeAttrName(name) {
5962
6106
  return SAFE_ATTR_NAME.test(name);
5963
6107
  }
5964
- function isEventHandlerAttr2(name) {
5965
- if (name.length < 3) return false;
5966
- const lower = name.toLowerCase();
5967
- return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
5968
- }
5969
6108
  var URL_ATTRS = /* @__PURE__ */ new Set([
5970
6109
  "href",
5971
6110
  "src",
@@ -6036,11 +6175,11 @@ function renderToString(element) {
6036
6175
  for (const attr of Array.from(element.attributes)) {
6037
6176
  const rawName = attr.name;
6038
6177
  if (!isSafeAttrName(rawName)) continue;
6039
- if (isEventHandlerAttr2(rawName)) continue;
6178
+ if (isEventHandlerAttr(rawName)) continue;
6040
6179
  const lowerName = rawName.toLowerCase();
6041
6180
  let value = attr.value;
6042
6181
  if (URL_ATTRS.has(lowerName)) {
6043
- value = sanitizeUrl(value);
6182
+ value = sanitizeUrlAttr(lowerName, value);
6044
6183
  if (!value) continue;
6045
6184
  }
6046
6185
  html2 += ` ${rawName}="${escapeAttr(value)}"`;
@@ -6177,11 +6316,11 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
6177
6316
  for (const rawKey of Object.keys(attrs)) {
6178
6317
  if (!Object.hasOwn(attrs, rawKey)) continue;
6179
6318
  if (!isSafeAttrName(rawKey)) continue;
6180
- if (!allowEventHandlers && isEventHandlerAttr2(rawKey)) continue;
6319
+ if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
6181
6320
  const lowerKey = rawKey.toLowerCase();
6182
6321
  let value = String(attrs[rawKey]);
6183
6322
  if (URL_ATTRS.has(lowerKey)) {
6184
- value = sanitizeUrl(value);
6323
+ value = sanitizeUrlAttr(lowerKey, value);
6185
6324
  if (!value) continue;
6186
6325
  }
6187
6326
  out.push(`${rawKey}="${escapeAttr(value)}"`);
@@ -6189,12 +6328,17 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
6189
6328
  return out.join(" ");
6190
6329
  }
6191
6330
  function isDangerousMetaRefresh(metaProps) {
6192
- const httpEquiv = metaProps["http-equiv"];
6331
+ let httpEquiv;
6332
+ let content;
6333
+ for (const k in metaProps) {
6334
+ const lk = k.toLowerCase();
6335
+ if (lk === "http-equiv") httpEquiv = metaProps[k];
6336
+ else if (lk === "content") content = metaProps[k];
6337
+ }
6193
6338
  if (typeof httpEquiv !== "string") return false;
6194
6339
  if (httpEquiv.toLowerCase() !== "refresh") return false;
6195
- const content = metaProps.content;
6196
6340
  if (typeof content !== "string") return false;
6197
- const normalized = content.replace(/[\x00-\x20\x7f-\x9f]+/g, "").toLowerCase();
6341
+ const normalized = stripControlChars(content).toLowerCase();
6198
6342
  return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
6199
6343
  }
6200
6344
  function renderToDocument(component, options = {}) {
@@ -6272,11 +6416,11 @@ async function* renderToStream(element) {
6272
6416
  for (const attr of Array.from(element.attributes)) {
6273
6417
  const rawName = attr.name;
6274
6418
  if (!isSafeAttrName(rawName)) continue;
6275
- if (isEventHandlerAttr2(rawName)) continue;
6419
+ if (isEventHandlerAttr(rawName)) continue;
6276
6420
  const lowerName = rawName.toLowerCase();
6277
6421
  let value = attr.value;
6278
6422
  if (URL_ATTRS.has(lowerName)) {
6279
- value = sanitizeUrl(value);
6423
+ value = sanitizeUrlAttr(lowerName, value);
6280
6424
  if (!value) continue;
6281
6425
  }
6282
6426
  openTag += ` ${rawName}="${escapeAttr(value)}"`;
@@ -6353,11 +6497,11 @@ function hydrateProgressively(container, islands, options) {
6353
6497
  (entries) => {
6354
6498
  for (const entry of entries) {
6355
6499
  if (entry.isIntersecting) {
6500
+ observer.disconnect();
6356
6501
  const clientTree = factory();
6357
6502
  clientTree.setAttribute("data-sibu-island", id);
6358
6503
  clientTree.setAttribute("data-sibu-hydrated", "true");
6359
6504
  marker2.replaceWith(clientTree);
6360
- observer.disconnect();
6361
6505
  break;
6362
6506
  }
6363
6507
  }
@@ -6614,6 +6758,12 @@ function denormalize(id, entities, schema) {
6614
6758
  }
6615
6759
 
6616
6760
  // src/performance/chunkLoader.ts
6761
+ function clearChildren(el) {
6762
+ while (el.firstChild) {
6763
+ dispose(el.firstChild);
6764
+ el.removeChild(el.firstChild);
6765
+ }
6766
+ }
6617
6767
  function createChunkRegistry(config = {}) {
6618
6768
  const {
6619
6769
  maxCacheSize = 50,
@@ -6778,10 +6928,10 @@ function lazyChunk(id, loader, registry, fallback) {
6778
6928
  const mod = await loader();
6779
6929
  return typeof mod === "function" ? mod : mod.default;
6780
6930
  }).then((component) => {
6781
- container.innerHTML = "";
6931
+ clearChildren(container);
6782
6932
  container.appendChild(component());
6783
6933
  }).catch((err) => {
6784
- container.innerHTML = "";
6934
+ clearChildren(container);
6785
6935
  const errorEl = document.createElement("div");
6786
6936
  errorEl.textContent = `Failed to load chunk '${id}': ${err.message}`;
6787
6937
  container.appendChild(errorEl);
@@ -6808,24 +6958,28 @@ function sanitizeHeadAttr(key, value) {
6808
6958
  if (HEAD_URL_ATTRS.has(key)) return sanitizeUrl(value);
6809
6959
  return value;
6810
6960
  }
6961
+ function isDangerousRefreshContent(content) {
6962
+ const normalized = stripControlChars(content).toLowerCase();
6963
+ return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
6964
+ }
6965
+ function getMetaAttr(metaProps, name) {
6966
+ for (const k in metaProps) {
6967
+ if (k.toLowerCase() === name) return metaProps[k];
6968
+ }
6969
+ return void 0;
6970
+ }
6811
6971
  function isDangerousMetaRefresh2(metaProps) {
6812
- const httpEquiv = metaProps["http-equiv"];
6972
+ const httpEquiv = getMetaAttr(metaProps, "http-equiv");
6813
6973
  if (typeof httpEquiv !== "string") return false;
6814
6974
  if (httpEquiv.toLowerCase() !== "refresh") return false;
6815
- const content = metaProps.content;
6975
+ const content = getMetaAttr(metaProps, "content");
6816
6976
  if (typeof content !== "string") return false;
6817
- const normalized = content.replace(/[\x00-\x20\x7f-\x9f]+/g, "").toLowerCase();
6818
- return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
6977
+ return isDangerousRefreshContent(content);
6819
6978
  }
6820
6979
  var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
6821
- function isEventHandlerAttr3(name) {
6822
- if (name.length < 3) return false;
6823
- const lower = name.toLowerCase();
6824
- return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
6825
- }
6826
6980
  function isSafeHeadAttr(name) {
6827
6981
  if (!SAFE_HEAD_ATTR_NAME.test(name)) return false;
6828
- if (isEventHandlerAttr3(name)) return false;
6982
+ if (isEventHandlerAttr(name)) return false;
6829
6983
  return true;
6830
6984
  }
6831
6985
  function escapeScriptJsonLocal(json) {
@@ -6858,15 +7012,27 @@ function Head(props) {
6858
7012
  if (props.meta) {
6859
7013
  for (const metaProps of props.meta) {
6860
7014
  if (isDangerousMetaRefresh2(metaProps)) continue;
7015
+ const httpEquiv = getMetaAttr(metaProps, "http-equiv");
7016
+ const isRefreshNow = () => {
7017
+ const eq = typeof httpEquiv === "function" ? httpEquiv() : httpEquiv;
7018
+ return typeof eq === "string" && eq.toLowerCase() === "refresh";
7019
+ };
6861
7020
  const el = document.createElement("meta");
6862
7021
  for (const [key, value] of Object.entries(metaProps)) {
6863
7022
  if (!isSafeHeadAttr(key)) continue;
7023
+ const isContent = key.toLowerCase() === "content";
6864
7024
  if (typeof value === "function") {
6865
7025
  const cleanupFn = effect(() => {
6866
- el.setAttribute(key, sanitizeHeadAttr(key, value()));
7026
+ const resolved = value();
7027
+ if (isContent && isRefreshNow() && isDangerousRefreshContent(resolved)) {
7028
+ el.removeAttribute(key);
7029
+ return;
7030
+ }
7031
+ el.setAttribute(key, sanitizeHeadAttr(key, resolved));
6867
7032
  });
6868
7033
  effectCleanups.push(cleanupFn);
6869
7034
  } else {
7035
+ if (isContent && isRefreshNow() && isDangerousRefreshContent(value)) continue;
6870
7036
  el.setAttribute(key, sanitizeHeadAttr(key, value));
6871
7037
  }
6872
7038
  }
@@ -7400,6 +7566,7 @@ function bindChildNode(placeholder, getter) {
7400
7566
  if (result == null || typeof result === "boolean") {
7401
7567
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
7402
7568
  const node = lastNodes[i2];
7569
+ dispose(node);
7403
7570
  if (node.parentNode) node.parentNode.removeChild(node);
7404
7571
  }
7405
7572
  lastNodes.length = 0;
@@ -7441,18 +7608,16 @@ function bindChildNode(placeholder, getter) {
7441
7608
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
7442
7609
  const node = lastNodes[i2];
7443
7610
  if (reused?.has(node)) continue;
7611
+ dispose(node);
7444
7612
  if (node.parentNode) node.parentNode.removeChild(node);
7445
7613
  }
7446
- const anchor = placeholder.nextSibling;
7614
+ let prev = placeholder;
7447
7615
  for (let i2 = 0; i2 < newNodes.length; i2++) {
7448
7616
  const node = newNodes[i2];
7449
- if (reused?.has(node) && node.parentNode === parent) {
7450
- if (node.nextSibling !== anchor) {
7451
- parent.insertBefore(node, anchor);
7452
- }
7453
- } else {
7454
- parent.insertBefore(node, anchor);
7617
+ if (prev.nextSibling !== node) {
7618
+ parent.insertBefore(node, prev.nextSibling);
7455
7619
  }
7620
+ prev = node;
7456
7621
  }
7457
7622
  lastNodes = newNodes;
7458
7623
  }
@@ -7694,7 +7859,7 @@ var tagFactory = (tag, ns) => {
7694
7859
  const value = props[key];
7695
7860
  if (value == null) continue;
7696
7861
  const lkey = key.toLowerCase();
7697
- if (lkey[0] === "o" && lkey[1] === "n") continue;
7862
+ if (isEventHandlerAttr(key)) continue;
7698
7863
  if (typeof value === "function") {
7699
7864
  registerDisposer(el, bindAttribute(el, key, value));
7700
7865
  } else if (typeof value === "boolean") {
@@ -9049,6 +9214,7 @@ function createBootSequence() {
9049
9214
  // src/devtools/debug.ts
9050
9215
  var debugEnabled = false;
9051
9216
  var perfMarks = /* @__PURE__ */ new Map();
9217
+ var MAX_PERF_SAMPLES = 1e3;
9052
9218
  function enableDebug() {
9053
9219
  debugEnabled = true;
9054
9220
  console.log("[SibuJS] Debug mode enabled");
@@ -9073,7 +9239,11 @@ function perfTracker(label2) {
9073
9239
  }
9074
9240
  function endMeasure() {
9075
9241
  const elapsed = globalThis.performance.now() - startTime;
9076
- perfMarks.get(label2)?.push(elapsed);
9242
+ const marks = perfMarks.get(label2);
9243
+ if (marks) {
9244
+ marks.push(elapsed);
9245
+ if (marks.length > MAX_PERF_SAMPLES) marks.shift();
9246
+ }
9077
9247
  if (debugEnabled) {
9078
9248
  debugLog("Perf", `${label2}: ${elapsed.toFixed(2)}ms`);
9079
9249
  }
@@ -10027,7 +10197,7 @@ function withErrorTracking(name, component, reporter) {
10027
10197
  }
10028
10198
  };
10029
10199
  }
10030
- function formatError(error, context2) {
10200
+ function formatError(error, context2, seen = /* @__PURE__ */ new Set([error])) {
10031
10201
  const lines = [];
10032
10202
  const componentLabel = context2?.component ?? (error instanceof SibuError ? error.component : void 0);
10033
10203
  if (componentLabel) {
@@ -10052,10 +10222,11 @@ function formatError(error, context2) {
10052
10222
  lines.push(stackBody);
10053
10223
  }
10054
10224
  const cause = error.cause;
10055
- if (cause instanceof Error) {
10225
+ if (cause instanceof Error && !seen.has(cause)) {
10226
+ seen.add(cause);
10056
10227
  lines.push("");
10057
10228
  lines.push("Caused by:");
10058
- lines.push(formatError(cause));
10229
+ lines.push(formatError(cause, context2, seen));
10059
10230
  }
10060
10231
  return lines.join("\n");
10061
10232
  }
@@ -10069,7 +10240,11 @@ function debugValue(value, formatter) {
10069
10240
  const dispose2 = effect(() => {
10070
10241
  const resolved = value();
10071
10242
  entry.value = resolved;
10072
- entry.label = format(resolved);
10243
+ try {
10244
+ entry.label = format(resolved);
10245
+ } catch (err) {
10246
+ entry.label = `<format error: ${err instanceof Error ? err.message : String(err)}>`;
10247
+ }
10073
10248
  });
10074
10249
  return () => {
10075
10250
  dispose2();
@@ -11533,6 +11708,18 @@ function mobXAdapter(options) {
11533
11708
  return createPlugin("sibu-mobx", (ctx) => {
11534
11709
  const { autorun } = options;
11535
11710
  const disposers = [];
11711
+ function trackDisposer(rawDispose) {
11712
+ let done = false;
11713
+ const wrapped = () => {
11714
+ if (done) return;
11715
+ done = true;
11716
+ const i2 = disposers.indexOf(wrapped);
11717
+ if (i2 >= 0) disposers.splice(i2, 1);
11718
+ rawDispose();
11719
+ };
11720
+ disposers.push(wrapped);
11721
+ return wrapped;
11722
+ }
11536
11723
  function fromMobX(expression) {
11537
11724
  const [getValue, setValue] = signal(void 0);
11538
11725
  const disposer = autorun(() => {
@@ -11541,22 +11728,18 @@ function mobXAdapter(options) {
11541
11728
  setValue(newValue);
11542
11729
  });
11543
11730
  });
11544
- disposers.push(disposer);
11545
11731
  const getter = (() => getValue());
11546
- getter.dispose = () => {
11547
- const i2 = disposers.indexOf(disposer);
11548
- if (i2 >= 0) disposers.splice(i2, 1);
11549
- disposer();
11550
- };
11732
+ getter.dispose = trackDisposer(disposer);
11551
11733
  return getter;
11552
11734
  }
11553
11735
  function toMobX(sibuGetter, callback) {
11554
- return effect(() => {
11736
+ const stop2 = effect(() => {
11555
11737
  callback(sibuGetter());
11556
11738
  });
11739
+ return trackDisposer(stop2);
11557
11740
  }
11558
11741
  function destroy() {
11559
- for (const disposer of disposers) {
11742
+ for (const disposer of [...disposers]) {
11560
11743
  disposer();
11561
11744
  }
11562
11745
  disposers.length = 0;
@@ -12017,6 +12200,7 @@ var materialAdapter = componentAdapter(materialConfig);
12017
12200
  inspectSignal,
12018
12201
  intersection,
12019
12202
  invalidateQueries,
12203
+ isDangerousMetaRefresh,
12020
12204
  isDebugEnabled,
12021
12205
  isHMRAvailable,
12022
12206
  isWasmCached,