sibujs 3.0.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 +276 -150
  5. package/dist/build.js +35 -24
  6. package/dist/cdn.global.js +7 -7
  7. package/dist/{chunk-RJIRT46U.js → chunk-2C4E3HBM.js} +5 -5
  8. package/dist/{chunk-XDKP4T7G.js → chunk-4JCAUOLN.js} +45 -23
  9. package/dist/{chunk-VSNLICTS.js → chunk-5N74TKLD.js} +1 -1
  10. package/dist/{chunk-XVYB3J6C.js → chunk-7XDYVJLE.js} +19 -9
  11. package/dist/{chunk-L52H775O.js → chunk-BGNLPNGV.js} +20 -12
  12. package/dist/{chunk-6QZO7MMG.js → chunk-C427DVQF.js} +1 -1
  13. package/dist/{chunk-5WD7BYTZ.js → chunk-FDY42FIU.js} +3 -2
  14. package/dist/{chunk-4YTVESDX.js → chunk-FOI23UJL.js} +11 -1
  15. package/dist/{chunk-2RA7SHDA.js → chunk-GOJMFRBL.js} +20 -4
  16. package/dist/{chunk-2KM2724A.js → chunk-GOUM4JCT.js} +6 -6
  17. package/dist/chunk-H3SRKIYX.js +17 -0
  18. package/dist/{chunk-NEWH4O5U.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-DF3GTP4Q.js → chunk-JYXOEYI4.js} +12 -18
  22. package/dist/{chunk-KZA7ANXP.js → chunk-NFYWLRUO.js} +11 -18
  23. package/dist/{chunk-KH4OE6WY.js → chunk-NPIEEKPT.js} +20 -11
  24. package/dist/{chunk-V65KTDZW.js → chunk-OYLPZO4N.js} +33 -15
  25. package/dist/{chunk-LYTCUZ7H.js → chunk-RDRSWYNP.js} +1 -1
  26. package/dist/{chunk-UKMXT5T6.js → chunk-RLUJL2MV.js} +7 -12
  27. package/dist/{chunk-INBOWHQ3.js → chunk-V2MTG5FT.js} +99 -36
  28. package/dist/{chunk-CNZ35WI2.js → chunk-VJE6DDYM.js} +2 -2
  29. package/dist/{chunk-2JQUV4Y3.js → chunk-VOCE4NNK.js} +157 -75
  30. package/dist/{chunk-STFTTMO2.js → chunk-X67UYC74.js} +31 -12
  31. package/dist/{chunk-YMOIAHWA.js → chunk-YFDGQWDA.js} +1 -1
  32. package/dist/{chunk-L4DAT4WU.js → chunk-Z2FWAE4B.js} +28 -1
  33. package/dist/data.cjs +211 -93
  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 +163 -65
  42. package/dist/ecosystem.js +9 -9
  43. package/dist/extras.cjs +420 -198
  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 +255 -139
  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 +66 -39
  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 +243 -138
  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 +185 -68
  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 +76 -39
  75. package/dist/ui.js +10 -9
  76. package/dist/widgets.cjs +61 -23
  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,
@@ -459,7 +460,7 @@ function retrack(effectFn, subscriber) {
459
460
  }
460
461
  }
461
462
  function track(effectFn, subscriber) {
462
- if (!subscriber) subscriber = effectFn;
463
+ if (!subscriber) return reactiveBinding(effectFn);
463
464
  cleanup(subscriber);
464
465
  const prev = currentSubscriber;
465
466
  currentSubscriber = subscriber;
@@ -477,6 +478,32 @@ function track(effectFn, subscriber) {
477
478
  const sub2 = subscriber;
478
479
  return sub2._dispose ?? (sub2._dispose = () => cleanup(subscriber));
479
480
  }
481
+ function reactiveBinding(commit) {
482
+ const run = () => {
483
+ const s2 = subscriber;
484
+ if (s2._disposed || s2._reentrant) return;
485
+ s2._reentrant = true;
486
+ try {
487
+ retrack(commit, subscriber);
488
+ } finally {
489
+ s2._reentrant = false;
490
+ }
491
+ };
492
+ const subscriber = run;
493
+ subscriber.depsHead = null;
494
+ subscriber.depsTail = null;
495
+ subscriber._epoch = 0;
496
+ subscriber._structDirty = false;
497
+ subscriber._runEpoch = 0;
498
+ subscriber._runs = 0;
499
+ subscriber._reentrant = false;
500
+ subscriber._disposed = false;
501
+ run();
502
+ return subscriber._dispose ?? (subscriber._dispose = () => {
503
+ subscriber._disposed = true;
504
+ cleanup(subscriber);
505
+ });
506
+ }
480
507
  function recordDependency(signal2) {
481
508
  if (!currentSubscriber) return;
482
509
  const sub2 = currentSubscriber;
@@ -686,6 +713,7 @@ function derived(getter, options) {
686
713
  const equals = options?.equals;
687
714
  const cs = {};
688
715
  cs._d = false;
716
+ cs._init = false;
689
717
  cs._g = getter;
690
718
  cs.__v = 0;
691
719
  const markDirty = () => {
@@ -694,11 +722,18 @@ function derived(getter, options) {
694
722
  };
695
723
  markDirty._c = 1;
696
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
+ };
697
731
  track(() => {
698
732
  let threw = true;
699
733
  try {
700
734
  cs._v = getter();
701
735
  cs._d = false;
736
+ cs._init = true;
702
737
  threw = false;
703
738
  } finally {
704
739
  if (threw) cs._d = true;
@@ -714,20 +749,13 @@ function derived(getter, options) {
714
749
  }
715
750
  if (trackingSuspended) {
716
751
  if (cs._d) {
752
+ const prev = cs._v;
717
753
  evaluating = true;
718
- let threw = true;
719
754
  try {
720
- const prev = cs._v;
721
- retrack(() => {
722
- const next = getter();
723
- cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
724
- cs._d = false;
725
- threw = false;
726
- }, markDirty);
755
+ retrack(recompute, markDirty);
727
756
  if (!Object.is(prev, cs._v)) cs.__v++;
728
757
  } finally {
729
758
  evaluating = false;
730
- if (threw) cs._d = true;
731
759
  }
732
760
  }
733
761
  return cs._v;
@@ -736,18 +764,11 @@ function derived(getter, options) {
736
764
  if (cs._d) {
737
765
  const oldValue = cs._v;
738
766
  evaluating = true;
739
- let threw = true;
740
767
  try {
741
- retrack(() => {
742
- const next = getter();
743
- cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
744
- cs._d = false;
745
- threw = false;
746
- }, markDirty);
768
+ retrack(recompute, markDirty);
747
769
  if (!Object.is(oldValue, cs._v)) cs.__v++;
748
770
  } finally {
749
771
  evaluating = false;
750
- if (threw) cs._d = true;
751
772
  }
752
773
  if (hook && oldValue !== cs._v) {
753
774
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
@@ -768,11 +789,15 @@ function derived(getter, options) {
768
789
  var als = null;
769
790
  try {
770
791
  if (typeof process !== "undefined" && process.versions && process.versions.node) {
771
- const req = Function("return typeof require==='function'?require:null")();
772
- if (req) {
773
- const mod = req("node:async_hooks");
774
- 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");
775
799
  }
800
+ if (mod) als = new mod.AsyncLocalStorage();
776
801
  }
777
802
  } catch {
778
803
  als = null;
@@ -788,6 +813,17 @@ function getSSRStore() {
788
813
  function isSSR() {
789
814
  return getSSRStore().ssr;
790
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
+ }
791
827
 
792
828
  // src/core/signals/effect.ts
793
829
  var _g = globalThis;
@@ -874,6 +910,7 @@ function effect(effectFn, options) {
874
910
  ctx.fn(ctx.onCleanup);
875
911
  };
876
912
  const sub2 = (() => {
913
+ if (ctx.disposed) return;
877
914
  if (ctx.running) {
878
915
  ctx.rerunPending = true;
879
916
  return;
@@ -1070,9 +1107,12 @@ async function withRetry(fn, options, onRetry, signal2) {
1070
1107
  }
1071
1108
 
1072
1109
  // src/data/query.ts
1073
- var queryCache = /* @__PURE__ */ new Map();
1074
- function getOrCreateEntry(key, initialData) {
1075
- 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);
1076
1116
  if (!entry) {
1077
1117
  entry = {
1078
1118
  data: initialData,
@@ -1084,7 +1124,7 @@ function getOrCreateEntry(key, initialData) {
1084
1124
  listeners: /* @__PURE__ */ new Set(),
1085
1125
  refetchers: /* @__PURE__ */ new Set()
1086
1126
  };
1087
- queryCache.set(key, entry);
1127
+ cache.set(key, entry);
1088
1128
  }
1089
1129
  return entry;
1090
1130
  }
@@ -1104,6 +1144,7 @@ function query(key, fetcher, options = {}) {
1104
1144
  select: select3
1105
1145
  } = options;
1106
1146
  const resolveKey = typeof key === "function" ? key : () => key;
1147
+ const cache = getActiveQueryCache();
1107
1148
  const [data2, setData] = signal(initialData);
1108
1149
  const [isFetching, setIsFetching] = signal(false);
1109
1150
  const [error, setError] = signal(void 0);
@@ -1115,16 +1156,16 @@ function query(key, fetcher, options = {}) {
1115
1156
  const isStale = derived(() => {
1116
1157
  data2();
1117
1158
  if (!currentKey) return true;
1118
- const entry = queryCache.get(currentKey);
1159
+ const entry = cache.get(currentKey);
1119
1160
  if (!entry || entry.dataUpdatedAt === 0) return true;
1120
1161
  return Date.now() - entry.dataUpdatedAt >= staleTime;
1121
1162
  });
1122
1163
  async function doFetch() {
1123
1164
  if (disposed || !currentKey || !enabled) return;
1124
1165
  const key2 = currentKey;
1125
- let entry = queryCache.get(key2);
1166
+ let entry = cache.get(key2);
1126
1167
  if (!entry) {
1127
- entry = getOrCreateEntry(key2);
1168
+ entry = getOrCreateEntry(cache, key2);
1128
1169
  entry.listeners.add(onCacheUpdate);
1129
1170
  entry.refetchers.add(doFetch);
1130
1171
  }
@@ -1199,7 +1240,7 @@ function query(key, fetcher, options = {}) {
1199
1240
  }
1200
1241
  function onCacheUpdate() {
1201
1242
  if (disposed || !currentKey) return;
1202
- const entry = queryCache.get(currentKey);
1243
+ const entry = cache.get(currentKey);
1203
1244
  if (!entry) {
1204
1245
  batch(() => {
1205
1246
  setData(void 0);
@@ -1219,7 +1260,7 @@ function query(key, fetcher, options = {}) {
1219
1260
  const effectCleanup = effect(() => {
1220
1261
  const key2 = resolveKey();
1221
1262
  if (currentKey !== null && currentKey !== key2) {
1222
- const oldEntry = queryCache.get(currentKey);
1263
+ const oldEntry = cache.get(currentKey);
1223
1264
  if (oldEntry) {
1224
1265
  oldEntry.listeners.delete(onCacheUpdate);
1225
1266
  oldEntry.refetchers.delete(doFetch);
@@ -1227,13 +1268,13 @@ function query(key, fetcher, options = {}) {
1227
1268
  if (oldEntry.subscribers <= 0 && cacheTime >= 0) {
1228
1269
  const oldKey = currentKey;
1229
1270
  if (oldEntry.gcTimer !== null) clearTimeout(oldEntry.gcTimer);
1230
- oldEntry.gcTimer = setTimeout(() => queryCache.delete(oldKey), cacheTime);
1271
+ oldEntry.gcTimer = setTimeout(() => cache.delete(oldKey), cacheTime);
1231
1272
  }
1232
1273
  }
1233
1274
  }
1234
1275
  const keyChanged = currentKey !== key2;
1235
1276
  currentKey = key2;
1236
- const entry = getOrCreateEntry(key2, initialData);
1277
+ const entry = getOrCreateEntry(cache, key2, initialData);
1237
1278
  if (keyChanged) entry.subscribers++;
1238
1279
  if (entry.gcTimer !== null) {
1239
1280
  clearTimeout(entry.gcTimer);
@@ -1287,7 +1328,7 @@ function query(key, fetcher, options = {}) {
1287
1328
  effectCleanup();
1288
1329
  if (intervalTimer) clearInterval(intervalTimer);
1289
1330
  if (currentKey) {
1290
- const entry = queryCache.get(currentKey);
1331
+ const entry = cache.get(currentKey);
1291
1332
  if (entry) {
1292
1333
  entry.listeners.delete(onCacheUpdate);
1293
1334
  entry.refetchers.delete(doFetch);
@@ -1295,7 +1336,7 @@ function query(key, fetcher, options = {}) {
1295
1336
  if (entry.subscribers <= 0 && cacheTime >= 0) {
1296
1337
  const key2 = currentKey;
1297
1338
  if (entry.gcTimer !== null) clearTimeout(entry.gcTimer);
1298
- entry.gcTimer = setTimeout(() => queryCache.delete(key2), cacheTime);
1339
+ entry.gcTimer = setTimeout(() => cache.delete(key2), cacheTime);
1299
1340
  }
1300
1341
  }
1301
1342
  }
@@ -1318,7 +1359,7 @@ function query(key, fetcher, options = {}) {
1318
1359
  }
1319
1360
  function invalidateQueries(keyOrPredicate) {
1320
1361
  const predicate = typeof keyOrPredicate === "function" ? keyOrPredicate : (k) => k === keyOrPredicate;
1321
- for (const [key, entry] of queryCache.entries()) {
1362
+ for (const [key, entry] of getActiveQueryCache().entries()) {
1322
1363
  if (predicate(key)) {
1323
1364
  entry.dataUpdatedAt = 0;
1324
1365
  for (const refetcher of entry.refetchers) refetcher();
@@ -1326,10 +1367,10 @@ function invalidateQueries(keyOrPredicate) {
1326
1367
  }
1327
1368
  }
1328
1369
  function getQueryData(key) {
1329
- return queryCache.get(key)?.data;
1370
+ return getActiveQueryCache().get(key)?.data;
1330
1371
  }
1331
1372
  function setQueryData(key, data2) {
1332
- const entry = queryCache.get(key);
1373
+ const entry = getActiveQueryCache().get(key);
1333
1374
  if (!entry) return;
1334
1375
  const newData = typeof data2 === "function" ? data2(entry.data) : data2;
1335
1376
  entry.data = newData;
@@ -1339,14 +1380,15 @@ function setQueryData(key, data2) {
1339
1380
  function clearQueryCache() {
1340
1381
  const activeListeners = [];
1341
1382
  const activeRefetchers = [];
1342
- for (const entry of queryCache.values()) {
1383
+ const activeCache = getActiveQueryCache();
1384
+ for (const entry of activeCache.values()) {
1343
1385
  if (entry.gcTimer) clearTimeout(entry.gcTimer);
1344
1386
  if (entry.subscribers > 0) {
1345
1387
  for (const listener of entry.listeners) activeListeners.push(listener);
1346
1388
  for (const refetcher of entry.refetchers) activeRefetchers.push(refetcher);
1347
1389
  }
1348
1390
  }
1349
- queryCache.clear();
1391
+ activeCache.clear();
1350
1392
  for (const listener of activeListeners) listener();
1351
1393
  for (const refetcher of activeRefetchers) {
1352
1394
  refetcher().catch((err) => {
@@ -1357,10 +1399,11 @@ function clearQueryCache() {
1357
1399
  }
1358
1400
  }
1359
1401
  function __resetQueryCache() {
1360
- for (const entry of queryCache.values()) {
1402
+ const activeCache = getActiveQueryCache();
1403
+ for (const entry of activeCache.values()) {
1361
1404
  if (entry.gcTimer) clearTimeout(entry.gcTimer);
1362
1405
  }
1363
- queryCache.clear();
1406
+ activeCache.clear();
1364
1407
  }
1365
1408
 
1366
1409
  // src/data/mutation.ts
@@ -1372,7 +1415,11 @@ function mutation(mutationFn, options = {}) {
1372
1415
  const isSuccess = derived(() => status() === "success");
1373
1416
  const isIdle = derived(() => status() === "idle");
1374
1417
  let runId = 0;
1418
+ let abortController = null;
1375
1419
  async function execute(variables) {
1420
+ abortController?.abort();
1421
+ abortController = new AbortController();
1422
+ const signal2 = abortController.signal;
1376
1423
  const myRun = ++runId;
1377
1424
  let context2;
1378
1425
  batch(() => {
@@ -1384,7 +1431,7 @@ function mutation(mutationFn, options = {}) {
1384
1431
  if (options.onMutate) {
1385
1432
  context2 = await options.onMutate(variables);
1386
1433
  }
1387
- const result = await withRetry(() => mutationFn(variables), options.retry);
1434
+ const result = await withRetry(() => mutationFn(variables, signal2), options.retry, void 0, signal2);
1388
1435
  if (myRun !== runId) return result;
1389
1436
  batch(() => {
1390
1437
  setData(result);
@@ -1396,6 +1443,7 @@ function mutation(mutationFn, options = {}) {
1396
1443
  return result;
1397
1444
  } catch (err) {
1398
1445
  const errorObj = err instanceof Error ? err : new Error(String(err));
1446
+ if (errorObj instanceof DOMException && errorObj.name === "AbortError") throw errorObj;
1399
1447
  if (myRun !== runId) throw errorObj;
1400
1448
  batch(() => {
1401
1449
  setError(errorObj);
@@ -1409,6 +1457,8 @@ function mutation(mutationFn, options = {}) {
1409
1457
  }
1410
1458
  function reset() {
1411
1459
  runId++;
1460
+ abortController?.abort();
1461
+ abortController = null;
1412
1462
  batch(() => {
1413
1463
  setData(void 0);
1414
1464
  setError(void 0);
@@ -1424,6 +1474,7 @@ function mutation(mutationFn, options = {}) {
1424
1474
  isIdle,
1425
1475
  mutate: (variables) => {
1426
1476
  execute(variables).catch((err) => {
1477
+ if (err instanceof DOMException && err.name === "AbortError") return;
1427
1478
  if (typeof console !== "undefined") {
1428
1479
  console.warn("[SibuJS mutation] mutate() failed; check `.error()` signal or onError option.", err);
1429
1480
  }
@@ -1440,6 +1491,7 @@ function infiniteQuery(key, fetcher, options) {
1440
1491
  getNextPageParam,
1441
1492
  getPreviousPageParam,
1442
1493
  initialPageParam = 0,
1494
+ maxPages,
1443
1495
  enabled = true,
1444
1496
  retry: retryOptions,
1445
1497
  onSuccess,
@@ -1463,8 +1515,9 @@ function infiniteQuery(key, fetcher, options) {
1463
1515
  let abortController = null;
1464
1516
  let disposed = false;
1465
1517
  let runId = 0;
1466
- async function fetchPage(pageParam, direction) {
1467
- if (disposed) return;
1518
+ let inFlight = null;
1519
+ function fetchPage(pageParam, direction) {
1520
+ if (disposed) return Promise.resolve();
1468
1521
  abortController?.abort();
1469
1522
  abortController = new AbortController();
1470
1523
  const signal2 = abortController.signal;
@@ -1475,39 +1528,44 @@ function infiniteQuery(key, fetcher, options) {
1475
1528
  if (direction === "prev") setIsFetchingPrev(true);
1476
1529
  setError(void 0);
1477
1530
  });
1478
- try {
1479
- const page = await withRetry(() => fetcher({ signal: signal2, pageParam }), retryOptions, void 0, signal2);
1480
- if (disposed || myRun !== runId) return;
1481
- const currentPages = pages();
1482
- let newPages;
1483
- if (direction === "prev") {
1484
- newPages = [page, ...currentPages];
1485
- } else {
1486
- 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);
1487
1562
  }
1488
- const nextParam = getNextPageParam(page, newPages);
1489
- const prevParam = getPreviousPageParam?.(newPages[0], newPages);
1490
- batch(() => {
1491
- setPages(newPages);
1492
- setNextPageParam(nextParam);
1493
- setPrevPageParam(prevParam);
1494
- setIsFetching(false);
1495
- setIsFetchingNext(false);
1496
- setIsFetchingPrev(false);
1497
- });
1498
- onSuccess?.(newPages);
1499
- } catch (err) {
1500
- if (disposed || myRun !== runId) return;
1501
- if (err instanceof DOMException && err.name === "AbortError") return;
1502
- const errorObj = err instanceof Error ? err : new Error(String(err));
1503
- batch(() => {
1504
- setError(errorObj);
1505
- setIsFetching(false);
1506
- setIsFetchingNext(false);
1507
- setIsFetchingPrev(false);
1508
- });
1509
- onError?.(errorObj);
1510
- }
1563
+ })();
1564
+ inFlight = promise;
1565
+ void promise.finally(() => {
1566
+ if (inFlight === promise) inFlight = null;
1567
+ });
1568
+ return promise;
1511
1569
  }
1512
1570
  const effectCleanup = effect(() => {
1513
1571
  resolveKey();
@@ -1522,11 +1580,13 @@ function infiniteQuery(key, fetcher, options) {
1522
1580
  }
1523
1581
  });
1524
1582
  function fetchNextPage() {
1583
+ if (inFlight) return inFlight;
1525
1584
  const param2 = nextPageParam();
1526
1585
  if (param2 === void 0) return Promise.resolve();
1527
1586
  return fetchPage(param2, "next");
1528
1587
  }
1529
1588
  function fetchPreviousPage() {
1589
+ if (inFlight) return inFlight;
1530
1590
  const param2 = prevPageParam();
1531
1591
  if (param2 === void 0) return Promise.resolve();
1532
1592
  return fetchPage(param2, "prev");
@@ -1565,13 +1625,17 @@ function infiniteQuery(key, fetcher, options) {
1565
1625
  function previous(getter) {
1566
1626
  const [previous2, setPrevious] = signal(void 0);
1567
1627
  let current = getter();
1568
- effect(() => {
1628
+ const stop2 = effect(() => {
1569
1629
  const next = getter();
1570
1630
  if (!Object.is(next, current)) {
1571
1631
  setPrevious(current);
1572
1632
  current = next;
1573
1633
  }
1574
1634
  });
1635
+ Object.defineProperty(previous2, "dispose", {
1636
+ value: stop2,
1637
+ enumerable: false
1638
+ });
1575
1639
  return previous2;
1576
1640
  }
1577
1641
 
@@ -1579,7 +1643,7 @@ function previous(getter) {
1579
1643
  function debounce(getter, delay) {
1580
1644
  const [debounced, setDebounced] = signal(getter());
1581
1645
  let timer = null;
1582
- effect(() => {
1646
+ const stop2 = effect(() => {
1583
1647
  const value = getter();
1584
1648
  if (timer !== null) clearTimeout(timer);
1585
1649
  timer = setTimeout(() => {
@@ -1587,6 +1651,16 @@ function debounce(getter, delay) {
1587
1651
  timer = null;
1588
1652
  }, delay);
1589
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
+ });
1590
1664
  return debounced;
1591
1665
  }
1592
1666
 
@@ -1596,7 +1670,8 @@ function throttle(getter, interval) {
1596
1670
  let cooldown = false;
1597
1671
  let pending = null;
1598
1672
  let lastEmitted = getter();
1599
- effect(() => {
1673
+ let timer = null;
1674
+ const stop2 = effect(() => {
1600
1675
  const value = getter();
1601
1676
  if (!cooldown) {
1602
1677
  if (!Object.is(value, lastEmitted)) {
@@ -1604,7 +1679,8 @@ function throttle(getter, interval) {
1604
1679
  lastEmitted = value;
1605
1680
  cooldown = true;
1606
1681
  pending = null;
1607
- setTimeout(() => {
1682
+ timer = setTimeout(() => {
1683
+ timer = null;
1608
1684
  cooldown = false;
1609
1685
  if (pending !== null) {
1610
1686
  const trailingValue = pending.value;
@@ -1618,6 +1694,16 @@ function throttle(getter, interval) {
1618
1694
  pending = { value };
1619
1695
  }
1620
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
+ });
1621
1707
  return throttled;
1622
1708
  }
1623
1709
 
@@ -1746,28 +1832,45 @@ function idbGet(db, store, key) {
1746
1832
  req.onerror = () => reject(req.error);
1747
1833
  });
1748
1834
  }
1749
- function idbPut(db, store, item) {
1835
+ function idbPut(db, store, item, key) {
1750
1836
  return new Promise((resolve, reject) => {
1751
1837
  const tx = db.transaction(store, "readwrite");
1752
- tx.objectStore(store).put(item);
1838
+ if (key !== void 0) tx.objectStore(store).put(item, key);
1839
+ else tx.objectStore(store).put(item);
1753
1840
  tx.oncomplete = () => resolve();
1754
1841
  tx.onerror = () => reject(tx.error);
1755
1842
  });
1756
1843
  }
1757
- 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) {
1758
1861
  return new Promise((resolve, reject) => {
1759
1862
  const tx = db.transaction(["items", "_changes"], "readwrite");
1760
1863
  tx.objectStore("items").put(item);
1761
- tx.objectStore("_changes").put(change);
1864
+ coalesceAndAddChange(tx, change, keyPath);
1762
1865
  tx.oncomplete = () => resolve();
1763
1866
  tx.onerror = () => reject(tx.error);
1764
1867
  });
1765
1868
  }
1766
- function idbDeleteWithChange(db, key, change) {
1869
+ function idbDeleteWithChange(db, key, change, keyPath) {
1767
1870
  return new Promise((resolve, reject) => {
1768
1871
  const tx = db.transaction(["items", "_changes"], "readwrite");
1769
1872
  tx.objectStore("items").delete(key);
1770
- tx.objectStore("_changes").put(change);
1873
+ coalesceAndAddChange(tx, change, keyPath);
1771
1874
  tx.oncomplete = () => resolve();
1772
1875
  tx.onerror = () => reject(tx.error);
1773
1876
  });
@@ -1827,13 +1930,18 @@ async function offlineStore(options) {
1827
1930
  setPendingCount(changes.length);
1828
1931
  }
1829
1932
  async function put(item) {
1830
- await idbPutWithChange(db, item, { type: "put", item, timestamp: Date.now() });
1933
+ await idbPutWithChange(db, item, { type: "put", item, timestamp: Date.now() }, keyPath);
1831
1934
  await refreshData();
1832
1935
  }
1833
1936
  async function remove(key) {
1834
1937
  const existing = await idbGet(db, "items", key);
1835
1938
  if (existing) {
1836
- 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
+ );
1837
1945
  await refreshData();
1838
1946
  }
1839
1947
  }
@@ -1859,6 +1967,8 @@ async function offlineStore(options) {
1859
1967
  snapshot.map((e) => e.key)
1860
1968
  );
1861
1969
  if (closed) return;
1970
+ } else if (typeof console !== "undefined") {
1971
+ console.warn(`[offlineStore] push rejected by adapter${result.error ? `: ${result.error}` : ""}`);
1862
1972
  }
1863
1973
  }
1864
1974
  const remoteItems = await adapter.pull(lastSynced());
@@ -1877,7 +1987,7 @@ async function offlineStore(options) {
1877
1987
  await idbPutMany(db, "items", safeRemote);
1878
1988
  if (closed) return;
1879
1989
  const now = Date.now();
1880
- await idbPut(db, "_meta", now);
1990
+ await idbPut(db, "_meta", now, "lastSynced");
1881
1991
  if (closed) return;
1882
1992
  setLastSynced(now);
1883
1993
  await refreshData();
@@ -1976,14 +2086,20 @@ function loaderData() {
1976
2086
  async function preloadRoute(route, context2, callerSignal) {
1977
2087
  if (!route.loader) return void 0;
1978
2088
  const controller = new AbortController();
2089
+ let onAbort = null;
1979
2090
  if (callerSignal) {
1980
2091
  if (callerSignal.aborted) {
1981
2092
  controller.abort();
1982
2093
  } else {
1983
- callerSignal.addEventListener("abort", () => controller.abort(), { once: true });
2094
+ onAbort = () => controller.abort();
2095
+ callerSignal.addEventListener("abort", onAbort, { once: true });
1984
2096
  }
1985
2097
  }
1986
- 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
+ }
1987
2103
  }
1988
2104
 
1989
2105
  // src/ui/socket.ts
@@ -2089,9 +2205,17 @@ function socket(url, options) {
2089
2205
  }
2090
2206
 
2091
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
+ }
2092
2216
  var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
2093
2217
  function sanitizeUrl(url) {
2094
- const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
2218
+ const trimmed = stripControlChars(url).trim();
2095
2219
  if (!trimmed) return "";
2096
2220
  const lower = trimmed.toLowerCase();
2097
2221
  let schemeEnd = -1;
@@ -2140,7 +2264,20 @@ function sanitizeCSSValue(value) {
2140
2264
  return value;
2141
2265
  }
2142
2266
  function stripHtml(html2) {
2143
- 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(/<[^>]*$/, "");
2144
2281
  }
2145
2282
  var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
2146
2283
  "href",
@@ -2157,7 +2294,7 @@ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
2157
2294
  "data"
2158
2295
  ]);
2159
2296
  function isUrlAttribute(attr) {
2160
- return URL_ATTRIBUTES.has(attr);
2297
+ return URL_ATTRIBUTES.has(attr.toLowerCase());
2161
2298
  }
2162
2299
 
2163
2300
  // src/ui/stream.ts
@@ -2540,6 +2677,19 @@ function clipboard() {
2540
2677
  return { text: text2, copy, copied, dispose: dispose2 };
2541
2678
  }
2542
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
+
2543
2693
  // src/browser/dragDrop.ts
2544
2694
  function resolveTarget2(target) {
2545
2695
  return typeof target === "function" ? target : () => target.current;
@@ -2625,10 +2775,7 @@ function dropZone(element, options) {
2625
2775
  const raw = e.dataTransfer.getData("application/json");
2626
2776
  if (raw) {
2627
2777
  try {
2628
- transferData = JSON.parse(
2629
- raw,
2630
- (k, v) => k === "__proto__" || k === "constructor" || k === "prototype" ? void 0 : v
2631
- );
2778
+ transferData = JSON.parse(raw, (k, v) => isUnsafeKey(k) ? void 0 : v);
2632
2779
  } catch {
2633
2780
  transferData = raw;
2634
2781
  }
@@ -3561,11 +3708,7 @@ function machine(config) {
3561
3708
  }
3562
3709
  if (action) {
3563
3710
  const rawPatch = action(ctx);
3564
- const next = { ...ctx };
3565
- for (const key of Object.keys(rawPatch)) {
3566
- if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
3567
- next[key] = rawPatch[key];
3568
- }
3711
+ const next = { ...ctx, ...stripUnsafeKeys(rawPatch) };
3569
3712
  setContext(next);
3570
3713
  }
3571
3714
  setState(target);
@@ -3596,10 +3739,7 @@ function machine(config) {
3596
3739
  function persisted(key, initial, options = {}) {
3597
3740
  const storage = options.session ? sessionStorage : localStorage;
3598
3741
  const serialize = options.serialize || JSON.stringify;
3599
- const safeReviver = (k, v) => {
3600
- if (k === "__proto__" || k === "constructor" || k === "prototype") return void 0;
3601
- return v;
3602
- };
3742
+ const safeReviver = (k, v) => isUnsafeKey(k) ? void 0 : v;
3603
3743
  const deserialize = options.deserialize || ((raw) => JSON.parse(raw, safeReviver));
3604
3744
  const encrypt = options.encrypt;
3605
3745
  const decrypt = options.decrypt;
@@ -3889,6 +4029,7 @@ function deepClone(value) {
3889
4029
  if (Array.isArray(v)) return v.map(clone);
3890
4030
  const out = {};
3891
4031
  for (const k of Object.keys(v)) {
4032
+ if (k === "__proto__") continue;
3892
4033
  out[k] = clone(v[k]);
3893
4034
  }
3894
4035
  return out;
@@ -3906,12 +4047,7 @@ function globalStore(config) {
3906
4047
  const execute = () => {
3907
4048
  const current = getState();
3908
4049
  const rawPatch = actionFn(current, payload);
3909
- const patch = {};
3910
- for (const key of Object.keys(rawPatch)) {
3911
- if (key !== "__proto__" && key !== "constructor" && key !== "prototype") {
3912
- patch[key] = rawPatch[key];
3913
- }
3914
- }
4050
+ const patch = stripUnsafeKeys(rawPatch);
3915
4051
  setState({ ...current, ...patch });
3916
4052
  const newState = getState();
3917
4053
  for (const listener of listeners) {
@@ -3984,7 +4120,7 @@ function withBoundary(name, component) {
3984
4120
  function createSlots(slots) {
3985
4121
  return {
3986
4122
  renderSlot(name, fallback) {
3987
- const slotFn = slots[name];
4123
+ const slotFn = Object.hasOwn(slots, name) ? slots[name] : void 0;
3988
4124
  if (slotFn) {
3989
4125
  const result = slotFn();
3990
4126
  if (Array.isArray(result)) {
@@ -3998,7 +4134,7 @@ function createSlots(slots) {
3998
4134
  return fallback ? fallback() : null;
3999
4135
  },
4000
4136
  hasSlot(name) {
4001
- return name in slots;
4137
+ return Object.hasOwn(slots, name);
4002
4138
  }
4003
4139
  };
4004
4140
  }
@@ -4126,11 +4262,17 @@ function transition(element, options = {}) {
4126
4262
  } = options;
4127
4263
  const transitionValue = `${property} ${duration}ms ${easing} ${delay}ms`;
4128
4264
  let activeTimer = null;
4265
+ let pendingResolve = null;
4129
4266
  function cancelPending() {
4130
4267
  if (activeTimer !== null) {
4131
4268
  clearTimeout(activeTimer);
4132
4269
  activeTimer = null;
4133
4270
  }
4271
+ if (pendingResolve !== null) {
4272
+ const resolvePrev = pendingResolve;
4273
+ pendingResolve = null;
4274
+ resolvePrev();
4275
+ }
4134
4276
  }
4135
4277
  function enter() {
4136
4278
  return new Promise((resolve) => {
@@ -4142,11 +4284,13 @@ function transition(element, options = {}) {
4142
4284
  if (activeClass) element.classList.add(activeClass);
4143
4285
  const done = () => {
4144
4286
  activeTimer = null;
4287
+ pendingResolve = null;
4145
4288
  if (enterClass) element.classList.remove(enterClass);
4146
4289
  onEnterDone?.();
4147
4290
  resolve();
4148
4291
  };
4149
4292
  if (duration > 0) {
4293
+ pendingResolve = resolve;
4150
4294
  activeTimer = setTimeout(done, duration + delay);
4151
4295
  } else {
4152
4296
  done();
@@ -4162,11 +4306,13 @@ function transition(element, options = {}) {
4162
4306
  if (enterClass) element.classList.remove(enterClass);
4163
4307
  const done = () => {
4164
4308
  activeTimer = null;
4309
+ pendingResolve = null;
4165
4310
  if (leaveClass) element.classList.remove(leaveClass);
4166
4311
  onLeaveDone?.();
4167
4312
  resolve();
4168
4313
  };
4169
4314
  if (duration > 0) {
4315
+ pendingResolve = resolve;
4170
4316
  activeTimer = setTimeout(done, duration + delay);
4171
4317
  } else {
4172
4318
  done();
@@ -4794,7 +4940,10 @@ function VirtualList(props) {
4794
4940
  const visibleCount = Math.ceil(props.containerHeight / props.itemHeight) + 2 * overscan;
4795
4941
  const endIndex = Math.min(items.length, startIndex + visibleCount);
4796
4942
  content.style.top = `${startIndex * props.itemHeight}px`;
4797
- content.innerHTML = "";
4943
+ while (content.firstChild) {
4944
+ dispose(content.firstChild);
4945
+ content.removeChild(content.firstChild);
4946
+ }
4798
4947
  for (let i2 = startIndex; i2 < endIndex; i2++) {
4799
4948
  const itemEl = props.renderItem(items[i2], i2);
4800
4949
  itemEl.style.height = `${props.itemHeight}px`;
@@ -4802,7 +4951,7 @@ function VirtualList(props) {
4802
4951
  content.appendChild(itemEl);
4803
4952
  }
4804
4953
  };
4805
- effect(update);
4954
+ registerDisposer(container, effect(update));
4806
4955
  return container;
4807
4956
  }
4808
4957
 
@@ -4943,7 +5092,10 @@ function inputMask(options) {
4943
5092
  }
4944
5093
  }
4945
5094
  }
4946
- input2.setSelectionRange(newCursor, newCursor);
5095
+ try {
5096
+ input2.setSelectionRange(newCursor, newCursor);
5097
+ } catch {
5098
+ }
4947
5099
  };
4948
5100
  const onFocus = () => {
4949
5101
  if (!input2.value) {
@@ -4985,9 +5137,12 @@ function aria(element, attrs) {
4985
5137
  const ariaKey = key.startsWith("aria-") ? key : `aria-${key}`;
4986
5138
  if (typeof value === "function") {
4987
5139
  const getter = value;
4988
- track(() => {
4989
- element.setAttribute(ariaKey, String(getter()));
4990
- });
5140
+ registerDisposer(
5141
+ element,
5142
+ track(() => {
5143
+ element.setAttribute(ariaKey, String(getter()));
5144
+ })
5145
+ );
4991
5146
  } else {
4992
5147
  element.setAttribute(ariaKey, String(value));
4993
5148
  }
@@ -5261,11 +5416,6 @@ var _isDev5 = isDev();
5261
5416
  function setProp(el, key, val) {
5262
5417
  el[key] = val;
5263
5418
  }
5264
- function isEventHandlerAttr(name) {
5265
- if (name.length < 3) return false;
5266
- const lower = name.toLowerCase();
5267
- return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
5268
- }
5269
5419
  function bindAttribute(el, attr, getter) {
5270
5420
  if (isEventHandlerAttr(attr)) {
5271
5421
  if (_isDev5)
@@ -5301,8 +5451,7 @@ function bindAttribute(el, attr, getter) {
5301
5451
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
5302
5452
  }
5303
5453
  }
5304
- const teardown = track(commit);
5305
- return teardown;
5454
+ return reactiveBinding(commit);
5306
5455
  }
5307
5456
 
5308
5457
  // src/ui/reactiveAttr.ts
@@ -5662,11 +5811,26 @@ function processQueue() {
5662
5811
  scheduleFrame();
5663
5812
  }
5664
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
+ }
5665
5824
  function scheduleFrame() {
5666
- if (scheduledKind !== null || microtaskScheduled) return;
5667
5825
  const nextTask = taskQueue.find((t) => !t.cancelled);
5668
5826
  if (!nextTask) return;
5669
- 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") {
5670
5834
  microtaskScheduled = true;
5671
5835
  scheduledKind = "microtask";
5672
5836
  queueMicrotask(() => {
@@ -5674,22 +5838,20 @@ function scheduleFrame() {
5674
5838
  scheduledKind = null;
5675
5839
  processQueue();
5676
5840
  });
5677
- } else if (nextTask.priority === Priority.IDLE) {
5678
- if (typeof requestIdleCallback !== "undefined") {
5679
- scheduledKind = "idle";
5680
- scheduledHandle = requestIdleCallback(() => {
5681
- scheduledKind = null;
5682
- scheduledHandle = null;
5683
- processQueue();
5684
- });
5685
- } else {
5686
- scheduledKind = "timeout";
5687
- scheduledHandle = setTimeout(() => {
5688
- scheduledKind = null;
5689
- scheduledHandle = null;
5690
- processQueue();
5691
- }, 50);
5692
- }
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);
5693
5855
  } else {
5694
5856
  scheduledKind = "frame";
5695
5857
  scheduledHandle = requestAnimationFrame(() => {
@@ -5754,7 +5916,7 @@ function yieldToMain() {
5754
5916
  async function processInChunks(items, processor, chunkSize = 50) {
5755
5917
  for (let i2 = 0; i2 < items.length; i2++) {
5756
5918
  processor(items[i2], i2);
5757
- if (i2 > 0 && i2 % chunkSize === 0) {
5919
+ if ((i2 + 1) % chunkSize === 0 && i2 + 1 < items.length) {
5758
5920
  await yieldToMain();
5759
5921
  }
5760
5922
  }
@@ -5935,16 +6097,14 @@ function noSideEffect(fn) {
5935
6097
  }
5936
6098
 
5937
6099
  // src/platform/ssr.ts
6100
+ function sanitizeUrlAttr(name, value) {
6101
+ return name === "srcset" ? sanitizeSrcset(value) : sanitizeUrl(value);
6102
+ }
5938
6103
  var _isDev7 = isDev();
5939
6104
  var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
5940
6105
  function isSafeAttrName(name) {
5941
6106
  return SAFE_ATTR_NAME.test(name);
5942
6107
  }
5943
- function isEventHandlerAttr2(name) {
5944
- if (name.length < 3) return false;
5945
- const lower = name.toLowerCase();
5946
- return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
5947
- }
5948
6108
  var URL_ATTRS = /* @__PURE__ */ new Set([
5949
6109
  "href",
5950
6110
  "src",
@@ -6015,11 +6175,11 @@ function renderToString(element) {
6015
6175
  for (const attr of Array.from(element.attributes)) {
6016
6176
  const rawName = attr.name;
6017
6177
  if (!isSafeAttrName(rawName)) continue;
6018
- if (isEventHandlerAttr2(rawName)) continue;
6178
+ if (isEventHandlerAttr(rawName)) continue;
6019
6179
  const lowerName = rawName.toLowerCase();
6020
6180
  let value = attr.value;
6021
6181
  if (URL_ATTRS.has(lowerName)) {
6022
- value = sanitizeUrl(value);
6182
+ value = sanitizeUrlAttr(lowerName, value);
6023
6183
  if (!value) continue;
6024
6184
  }
6025
6185
  html2 += ` ${rawName}="${escapeAttr(value)}"`;
@@ -6156,11 +6316,11 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
6156
6316
  for (const rawKey of Object.keys(attrs)) {
6157
6317
  if (!Object.hasOwn(attrs, rawKey)) continue;
6158
6318
  if (!isSafeAttrName(rawKey)) continue;
6159
- if (!allowEventHandlers && isEventHandlerAttr2(rawKey)) continue;
6319
+ if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
6160
6320
  const lowerKey = rawKey.toLowerCase();
6161
6321
  let value = String(attrs[rawKey]);
6162
6322
  if (URL_ATTRS.has(lowerKey)) {
6163
- value = sanitizeUrl(value);
6323
+ value = sanitizeUrlAttr(lowerKey, value);
6164
6324
  if (!value) continue;
6165
6325
  }
6166
6326
  out.push(`${rawKey}="${escapeAttr(value)}"`);
@@ -6168,12 +6328,17 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
6168
6328
  return out.join(" ");
6169
6329
  }
6170
6330
  function isDangerousMetaRefresh(metaProps) {
6171
- 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
+ }
6172
6338
  if (typeof httpEquiv !== "string") return false;
6173
6339
  if (httpEquiv.toLowerCase() !== "refresh") return false;
6174
- const content = metaProps.content;
6175
6340
  if (typeof content !== "string") return false;
6176
- const normalized = content.replace(/[\x00-\x20\x7f-\x9f]+/g, "").toLowerCase();
6341
+ const normalized = stripControlChars(content).toLowerCase();
6177
6342
  return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
6178
6343
  }
6179
6344
  function renderToDocument(component, options = {}) {
@@ -6251,11 +6416,11 @@ async function* renderToStream(element) {
6251
6416
  for (const attr of Array.from(element.attributes)) {
6252
6417
  const rawName = attr.name;
6253
6418
  if (!isSafeAttrName(rawName)) continue;
6254
- if (isEventHandlerAttr2(rawName)) continue;
6419
+ if (isEventHandlerAttr(rawName)) continue;
6255
6420
  const lowerName = rawName.toLowerCase();
6256
6421
  let value = attr.value;
6257
6422
  if (URL_ATTRS.has(lowerName)) {
6258
- value = sanitizeUrl(value);
6423
+ value = sanitizeUrlAttr(lowerName, value);
6259
6424
  if (!value) continue;
6260
6425
  }
6261
6426
  openTag += ` ${rawName}="${escapeAttr(value)}"`;
@@ -6332,11 +6497,11 @@ function hydrateProgressively(container, islands, options) {
6332
6497
  (entries) => {
6333
6498
  for (const entry of entries) {
6334
6499
  if (entry.isIntersecting) {
6500
+ observer.disconnect();
6335
6501
  const clientTree = factory();
6336
6502
  clientTree.setAttribute("data-sibu-island", id);
6337
6503
  clientTree.setAttribute("data-sibu-hydrated", "true");
6338
6504
  marker2.replaceWith(clientTree);
6339
- observer.disconnect();
6340
6505
  break;
6341
6506
  }
6342
6507
  }
@@ -6593,6 +6758,12 @@ function denormalize(id, entities, schema) {
6593
6758
  }
6594
6759
 
6595
6760
  // src/performance/chunkLoader.ts
6761
+ function clearChildren(el) {
6762
+ while (el.firstChild) {
6763
+ dispose(el.firstChild);
6764
+ el.removeChild(el.firstChild);
6765
+ }
6766
+ }
6596
6767
  function createChunkRegistry(config = {}) {
6597
6768
  const {
6598
6769
  maxCacheSize = 50,
@@ -6757,10 +6928,10 @@ function lazyChunk(id, loader, registry, fallback) {
6757
6928
  const mod = await loader();
6758
6929
  return typeof mod === "function" ? mod : mod.default;
6759
6930
  }).then((component) => {
6760
- container.innerHTML = "";
6931
+ clearChildren(container);
6761
6932
  container.appendChild(component());
6762
6933
  }).catch((err) => {
6763
- container.innerHTML = "";
6934
+ clearChildren(container);
6764
6935
  const errorEl = document.createElement("div");
6765
6936
  errorEl.textContent = `Failed to load chunk '${id}': ${err.message}`;
6766
6937
  container.appendChild(errorEl);
@@ -6787,24 +6958,28 @@ function sanitizeHeadAttr(key, value) {
6787
6958
  if (HEAD_URL_ATTRS.has(key)) return sanitizeUrl(value);
6788
6959
  return value;
6789
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
+ }
6790
6971
  function isDangerousMetaRefresh2(metaProps) {
6791
- const httpEquiv = metaProps["http-equiv"];
6972
+ const httpEquiv = getMetaAttr(metaProps, "http-equiv");
6792
6973
  if (typeof httpEquiv !== "string") return false;
6793
6974
  if (httpEquiv.toLowerCase() !== "refresh") return false;
6794
- const content = metaProps.content;
6975
+ const content = getMetaAttr(metaProps, "content");
6795
6976
  if (typeof content !== "string") return false;
6796
- const normalized = content.replace(/[\x00-\x20\x7f-\x9f]+/g, "").toLowerCase();
6797
- return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
6977
+ return isDangerousRefreshContent(content);
6798
6978
  }
6799
6979
  var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
6800
- function isEventHandlerAttr3(name) {
6801
- if (name.length < 3) return false;
6802
- const lower = name.toLowerCase();
6803
- return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
6804
- }
6805
6980
  function isSafeHeadAttr(name) {
6806
6981
  if (!SAFE_HEAD_ATTR_NAME.test(name)) return false;
6807
- if (isEventHandlerAttr3(name)) return false;
6982
+ if (isEventHandlerAttr(name)) return false;
6808
6983
  return true;
6809
6984
  }
6810
6985
  function escapeScriptJsonLocal(json) {
@@ -6837,15 +7012,27 @@ function Head(props) {
6837
7012
  if (props.meta) {
6838
7013
  for (const metaProps of props.meta) {
6839
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
+ };
6840
7020
  const el = document.createElement("meta");
6841
7021
  for (const [key, value] of Object.entries(metaProps)) {
6842
7022
  if (!isSafeHeadAttr(key)) continue;
7023
+ const isContent = key.toLowerCase() === "content";
6843
7024
  if (typeof value === "function") {
6844
7025
  const cleanupFn = effect(() => {
6845
- 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));
6846
7032
  });
6847
7033
  effectCleanups.push(cleanupFn);
6848
7034
  } else {
7035
+ if (isContent && isRefreshNow() && isDangerousRefreshContent(value)) continue;
6849
7036
  el.setAttribute(key, sanitizeHeadAttr(key, value));
6850
7037
  }
6851
7038
  }
@@ -7379,6 +7566,7 @@ function bindChildNode(placeholder, getter) {
7379
7566
  if (result == null || typeof result === "boolean") {
7380
7567
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
7381
7568
  const node = lastNodes[i2];
7569
+ dispose(node);
7382
7570
  if (node.parentNode) node.parentNode.removeChild(node);
7383
7571
  }
7384
7572
  lastNodes.length = 0;
@@ -7420,22 +7608,20 @@ function bindChildNode(placeholder, getter) {
7420
7608
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
7421
7609
  const node = lastNodes[i2];
7422
7610
  if (reused?.has(node)) continue;
7611
+ dispose(node);
7423
7612
  if (node.parentNode) node.parentNode.removeChild(node);
7424
7613
  }
7425
- const anchor = placeholder.nextSibling;
7614
+ let prev = placeholder;
7426
7615
  for (let i2 = 0; i2 < newNodes.length; i2++) {
7427
7616
  const node = newNodes[i2];
7428
- if (reused?.has(node) && node.parentNode === parent) {
7429
- if (node.nextSibling !== anchor) {
7430
- parent.insertBefore(node, anchor);
7431
- }
7432
- } else {
7433
- parent.insertBefore(node, anchor);
7617
+ if (prev.nextSibling !== node) {
7618
+ parent.insertBefore(node, prev.nextSibling);
7434
7619
  }
7620
+ prev = node;
7435
7621
  }
7436
7622
  lastNodes = newNodes;
7437
7623
  }
7438
- return track(commit);
7624
+ return reactiveBinding(commit);
7439
7625
  }
7440
7626
 
7441
7627
  // src/core/rendering/tagFactory.ts
@@ -7464,6 +7650,18 @@ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
7464
7650
  function setProp2(el, key, val) {
7465
7651
  el[key] = val;
7466
7652
  }
7653
+ function looksLikeClassList(s2) {
7654
+ const t = s2.trim();
7655
+ if (!t) return false;
7656
+ const tokens = t.split(/\s+/);
7657
+ let sawClassish = false;
7658
+ for (let i2 = 0; i2 < tokens.length; i2++) {
7659
+ const tok = tokens[i2];
7660
+ if (!/^-?[A-Za-z_][A-Za-z0-9_:/.-]*$/.test(tok)) return false;
7661
+ if (/[-:/0-9]/.test(tok)) sawClassish = true;
7662
+ }
7663
+ return sawClassish;
7664
+ }
7467
7665
  var kebabCache = /* @__PURE__ */ new Map();
7468
7666
  function toKebab(prop) {
7469
7667
  let cached = kebabCache.get(prop);
@@ -7599,6 +7797,11 @@ var tagFactory = (tag, ns) => {
7599
7797
  appendChildren(el, second);
7600
7798
  return el;
7601
7799
  }
7800
+ if (_isDev9 && looksLikeClassList(first)) {
7801
+ devWarn(
7802
+ `tagFactory: lone string "${first}" looks like a class list but is being rendered as TEXT. For a class, use ${tag}({ class: "${first}" }) \u2014 or ${tag}("${first}", children) to set the class AND add children.`
7803
+ );
7804
+ }
7602
7805
  el.textContent = first;
7603
7806
  return el;
7604
7807
  }
@@ -7656,7 +7859,7 @@ var tagFactory = (tag, ns) => {
7656
7859
  const value = props[key];
7657
7860
  if (value == null) continue;
7658
7861
  const lkey = key.toLowerCase();
7659
- if (lkey[0] === "o" && lkey[1] === "n") continue;
7862
+ if (isEventHandlerAttr(key)) continue;
7660
7863
  if (typeof value === "function") {
7661
7864
  registerDisposer(el, bindAttribute(el, key, value));
7662
7865
  } else if (typeof value === "boolean") {
@@ -9011,6 +9214,7 @@ function createBootSequence() {
9011
9214
  // src/devtools/debug.ts
9012
9215
  var debugEnabled = false;
9013
9216
  var perfMarks = /* @__PURE__ */ new Map();
9217
+ var MAX_PERF_SAMPLES = 1e3;
9014
9218
  function enableDebug() {
9015
9219
  debugEnabled = true;
9016
9220
  console.log("[SibuJS] Debug mode enabled");
@@ -9035,7 +9239,11 @@ function perfTracker(label2) {
9035
9239
  }
9036
9240
  function endMeasure() {
9037
9241
  const elapsed = globalThis.performance.now() - startTime;
9038
- 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
+ }
9039
9247
  if (debugEnabled) {
9040
9248
  debugLog("Perf", `${label2}: ${elapsed.toFixed(2)}ms`);
9041
9249
  }
@@ -9989,7 +10197,7 @@ function withErrorTracking(name, component, reporter) {
9989
10197
  }
9990
10198
  };
9991
10199
  }
9992
- function formatError(error, context2) {
10200
+ function formatError(error, context2, seen = /* @__PURE__ */ new Set([error])) {
9993
10201
  const lines = [];
9994
10202
  const componentLabel = context2?.component ?? (error instanceof SibuError ? error.component : void 0);
9995
10203
  if (componentLabel) {
@@ -10014,10 +10222,11 @@ function formatError(error, context2) {
10014
10222
  lines.push(stackBody);
10015
10223
  }
10016
10224
  const cause = error.cause;
10017
- if (cause instanceof Error) {
10225
+ if (cause instanceof Error && !seen.has(cause)) {
10226
+ seen.add(cause);
10018
10227
  lines.push("");
10019
10228
  lines.push("Caused by:");
10020
- lines.push(formatError(cause));
10229
+ lines.push(formatError(cause, context2, seen));
10021
10230
  }
10022
10231
  return lines.join("\n");
10023
10232
  }
@@ -10031,7 +10240,11 @@ function debugValue(value, formatter) {
10031
10240
  const dispose2 = effect(() => {
10032
10241
  const resolved = value();
10033
10242
  entry.value = resolved;
10034
- 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
+ }
10035
10248
  });
10036
10249
  return () => {
10037
10250
  dispose2();
@@ -11495,6 +11708,18 @@ function mobXAdapter(options) {
11495
11708
  return createPlugin("sibu-mobx", (ctx) => {
11496
11709
  const { autorun } = options;
11497
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
+ }
11498
11723
  function fromMobX(expression) {
11499
11724
  const [getValue, setValue] = signal(void 0);
11500
11725
  const disposer = autorun(() => {
@@ -11503,22 +11728,18 @@ function mobXAdapter(options) {
11503
11728
  setValue(newValue);
11504
11729
  });
11505
11730
  });
11506
- disposers.push(disposer);
11507
11731
  const getter = (() => getValue());
11508
- getter.dispose = () => {
11509
- const i2 = disposers.indexOf(disposer);
11510
- if (i2 >= 0) disposers.splice(i2, 1);
11511
- disposer();
11512
- };
11732
+ getter.dispose = trackDisposer(disposer);
11513
11733
  return getter;
11514
11734
  }
11515
11735
  function toMobX(sibuGetter, callback) {
11516
- return effect(() => {
11736
+ const stop2 = effect(() => {
11517
11737
  callback(sibuGetter());
11518
11738
  });
11739
+ return trackDisposer(stop2);
11519
11740
  }
11520
11741
  function destroy() {
11521
- for (const disposer of disposers) {
11742
+ for (const disposer of [...disposers]) {
11522
11743
  disposer();
11523
11744
  }
11524
11745
  disposers.length = 0;
@@ -11979,6 +12200,7 @@ var materialAdapter = componentAdapter(materialConfig);
11979
12200
  inspectSignal,
11980
12201
  intersection,
11981
12202
  invalidateQueries,
12203
+ isDangerousMetaRefresh,
11982
12204
  isDebugEnabled,
11983
12205
  isHMRAvailable,
11984
12206
  isWasmCached,