sibujs 4.14.1 → 4.15.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 (91) hide show
  1. package/dist/browser.cjs +297 -45
  2. package/dist/browser.js +6 -7
  3. package/dist/build.cjs +2189 -573
  4. package/dist/build.js +16 -15
  5. package/dist/cdn.dev.global.js +13 -12
  6. package/dist/cdn.full.dev.global.js +14 -13
  7. package/dist/cdn.full.global.js +12 -12
  8. package/dist/cdn.global.js +12 -12
  9. package/dist/{chunk-4XCWNGRY.js → chunk-2VKTFA7C.js} +1 -1
  10. package/dist/{chunk-YPLQQTWS.js → chunk-4AEFGRYW.js} +1 -1
  11. package/dist/{chunk-VIAVYBNQ.js → chunk-6IV5ZRG6.js} +5 -7
  12. package/dist/{chunk-KTJC7QPA.js → chunk-75EAQH3U.js} +2 -2
  13. package/dist/{chunk-LJ2LR5P4.js → chunk-7JO7DPSS.js} +4 -4
  14. package/dist/{chunk-B2NLNZ3E.js → chunk-AQH7WDSE.js} +6 -8
  15. package/dist/{chunk-S37PZD6I.js → chunk-BF3WBJJQ.js} +60 -10
  16. package/dist/{chunk-LJMX4SQ7.js → chunk-BG4BP44S.js} +3 -3
  17. package/dist/{chunk-ZYK4NZZA.js → chunk-D44TAGCT.js} +2 -2
  18. package/dist/{chunk-OPRQW4LI.js → chunk-DAEXQJQA.js} +1 -1
  19. package/dist/chunk-DNCQDFJJ.js +22 -0
  20. package/dist/{chunk-WFMMSMA5.js → chunk-DNCT2UXB.js} +4 -4
  21. package/dist/{chunk-TOV4PN3P.js → chunk-DRWOD7RZ.js} +4 -4
  22. package/dist/chunk-DS5V2PRE.js +64 -0
  23. package/dist/{chunk-L2IPKGUJ.js → chunk-EHSJESRN.js} +595 -123
  24. package/dist/{chunk-LAHX7XFG.js → chunk-GFM5WWPU.js} +252 -5
  25. package/dist/{chunk-52XFPGSN.js → chunk-HTPOIAAL.js} +1 -1
  26. package/dist/{chunk-3HEX6QJQ.js → chunk-I2R5QALY.js} +2 -4
  27. package/dist/{chunk-FCWOMATN.js → chunk-IUBV5WO5.js} +1 -1
  28. package/dist/{chunk-TZNR6E6D.js → chunk-KIDETDMU.js} +5 -7
  29. package/dist/{chunk-TS6JDK7Y.js → chunk-KPAXG5IR.js} +7 -9
  30. package/dist/{chunk-FFXZ6ELZ.js → chunk-LGUXYHTZ.js} +3 -3
  31. package/dist/{chunk-NIHGSXOX.js → chunk-LQWLPQOK.js} +6 -8
  32. package/dist/{chunk-YIXMS546.js → chunk-MINLMJUY.js} +5 -8
  33. package/dist/{chunk-OLMZF4RU.js → chunk-OURTGOJT.js} +8 -10
  34. package/dist/{chunk-55TAOW5M.js → chunk-TQ24Y3IV.js} +2 -2
  35. package/dist/{chunk-LUZXFMKH.js → chunk-UM3M5PXS.js} +8 -8
  36. package/dist/{chunk-ZKI4SDZ2.js → chunk-VSTX3XSX.js} +205 -15
  37. package/dist/{chunk-3T7TWMSD.js → chunk-XPDZ2LDR.js} +2 -2
  38. package/dist/{chunk-RBRITLFL.js → chunk-Y7JM7GY2.js} +5 -7
  39. package/dist/{chunk-LZKKMDQF.js → chunk-YAFITBOK.js} +1269 -162
  40. package/dist/data.cjs +285 -33
  41. package/dist/data.js +6 -7
  42. package/dist/devtools.cjs +289 -39
  43. package/dist/devtools.js +5 -6
  44. package/dist/{dispose-EIHHHDC7.js → dispose-3WDBYJMH.js} +3 -1
  45. package/dist/ecosystem.cjs +345 -61
  46. package/dist/ecosystem.d.cts +1 -1
  47. package/dist/ecosystem.d.ts +1 -1
  48. package/dist/ecosystem.js +10 -10
  49. package/dist/extras.cjs +525 -247
  50. package/dist/extras.d.cts +1 -1
  51. package/dist/extras.d.ts +1 -1
  52. package/dist/extras.js +27 -27
  53. package/dist/index.cjs +2134 -515
  54. package/dist/index.d.cts +628 -211
  55. package/dist/index.d.ts +628 -211
  56. package/dist/index.js +22 -15
  57. package/dist/motion.cjs +286 -30
  58. package/dist/motion.js +4 -4
  59. package/dist/patterns.cjs +301 -43
  60. package/dist/patterns.js +6 -6
  61. package/dist/performance.cjs +286 -36
  62. package/dist/performance.js +9 -9
  63. package/dist/plugins.cjs +1194 -290
  64. package/dist/plugins.d.cts +3 -1
  65. package/dist/plugins.d.ts +3 -1
  66. package/dist/plugins.js +20 -19
  67. package/dist/{renderRoute-fxgsm2Ir.d.ts → renderRoute-B84MONaO.d.cts} +2 -2
  68. package/dist/{renderRoute-DP20XwMS.d.cts → renderRoute-_yxPuEQY.d.ts} +2 -2
  69. package/dist/{routeHead-CE9SV-cH.d.cts → routeHead-DLxlq15W.d.cts} +94 -0
  70. package/dist/{routeHead-DzVxPviY.d.ts → routeHead-w8nANZ3M.d.ts} +94 -0
  71. package/dist/{ssr-HHU3DAD2.js → ssr-EF3IUHTE.js} +6 -6
  72. package/dist/ssr-dom.cjs +21 -8
  73. package/dist/ssr-dom.js +2 -1
  74. package/dist/ssr.cjs +807 -169
  75. package/dist/ssr.d.cts +5 -3
  76. package/dist/ssr.d.ts +5 -3
  77. package/dist/ssr.js +16 -15
  78. package/dist/{tagFactory-DkytnJws.d.cts → tagFactory-BfEh69VF.d.cts} +7 -0
  79. package/dist/{tagFactory-Bnd_Nph9.d.ts → tagFactory-pSYiLYyh.d.ts} +7 -0
  80. package/dist/testing.cjs +774 -131
  81. package/dist/testing.d.cts +4 -2
  82. package/dist/testing.d.ts +4 -2
  83. package/dist/testing.js +18 -17
  84. package/dist/track-core-0edROAF5.d.ts +187 -0
  85. package/dist/track-core-DN5fYLWi.d.cts +187 -0
  86. package/dist/ui.cjs +293 -43
  87. package/dist/ui.js +10 -12
  88. package/dist/widgets.cjs +285 -27
  89. package/dist/widgets.js +7 -7
  90. package/package.json +1 -1
  91. package/dist/chunk-7ZHH77QA.js +0 -9
package/dist/testing.cjs CHANGED
@@ -71,6 +71,12 @@ __export(testing_exports, {
71
71
  });
72
72
  module.exports = __toCommonJS(testing_exports);
73
73
 
74
+ // src/utils/globalSingleton.ts
75
+ function globalSingleton(key, create) {
76
+ const g = globalThis;
77
+ return g[key] ?? (g[key] = create());
78
+ }
79
+
74
80
  // src/core/dev.ts
75
81
  var DEV = typeof __SIBU_DEV__ !== "undefined" ? !!__SIBU_DEV__ : typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
76
82
  // safe default: off in browser, on in test/dev Node
@@ -303,7 +309,12 @@ function beginHydration() {
303
309
  }
304
310
 
305
311
  // src/core/rendering/dispose.ts
306
- var elementDisposers = /* @__PURE__ */ new WeakMap();
312
+ var state2 = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.disposers.v1"), () => ({
313
+ disposers: /* @__PURE__ */ new WeakMap(),
314
+ captures: [],
315
+ bindings: { count: 0 }
316
+ }));
317
+ var elementDisposers = state2.disposers;
307
318
  var MAX_DRAIN_TEARDOWNS = 1e4;
308
319
  function reportDrainRunaway(label, executed, remaining) {
309
320
  if (typeof console === "undefined") return;
@@ -312,13 +323,13 @@ function reportDrainRunaway(label, executed, remaining) {
312
323
  { executed, remaining }
313
324
  );
314
325
  }
315
- var activeBindingCount = 0;
326
+ var bindings = state2.bindings;
316
327
  function resolveCapture(capture) {
317
328
  let frame = capture;
318
329
  while (frame?.closed) frame = frame.forwardTo;
319
330
  return frame ?? null;
320
331
  }
321
- var registrationCaptures = [];
332
+ var registrationCaptures = state2.captures;
322
333
  function currentDisposerCapture() {
323
334
  return resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
324
335
  }
@@ -337,7 +348,7 @@ function registerDisposer(node, teardown) {
337
348
  elementDisposers.set(node, disposers);
338
349
  }
339
350
  disposers.push(teardown);
340
- if (DEV) activeBindingCount++;
351
+ if (DEV) bindings.count++;
341
352
  const capture = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
342
353
  if (capture) capture.entries.push([node, teardown]);
343
354
  }
@@ -361,17 +372,17 @@ function disposeNodeOwn(node) {
361
372
  let executed = 0;
362
373
  let runaway = false;
363
374
  while (!runaway) {
364
- const pending = elementDisposers.get(node);
365
- if (!pending || pending.length === 0) break;
366
- const snapshot = pending.slice();
375
+ const pending2 = elementDisposers.get(node);
376
+ if (!pending2 || pending2.length === 0) break;
377
+ const snapshot = pending2.slice();
367
378
  elementDisposers.delete(node);
368
- if (DEV) activeBindingCount -= snapshot.length;
379
+ if (DEV) bindings.count -= snapshot.length;
369
380
  for (let i = 0; i < snapshot.length; i++) {
370
381
  if (executed >= MAX_DRAIN_TEARDOWNS) {
371
382
  const rest = snapshot.slice(i);
372
383
  const added = elementDisposers.get(node);
373
384
  elementDisposers.set(node, added ? rest.concat(added) : rest);
374
- if (DEV) activeBindingCount += rest.length;
385
+ if (DEV) bindings.count += rest.length;
375
386
  reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
376
387
  runaway = true;
377
388
  break;
@@ -411,12 +422,6 @@ function replaceChildrenSafely(parent, ...next) {
411
422
  parent.replaceChildren(...next);
412
423
  }
413
424
 
414
- // src/utils/globalSingleton.ts
415
- function globalSingleton(key, create) {
416
- const g = globalThis;
417
- return g[key] ?? (g[key] = create());
418
- }
419
-
420
425
  // src/reactivity/track-core.ts
421
426
  var POOL_MAX = 4096;
422
427
  var nodePool = [];
@@ -580,6 +585,201 @@ function bindOwnerScope(fn) {
580
585
  return (...args) => runWithOwnerScope(scope, () => fn(...args));
581
586
  }
582
587
  var currentStructOwner = null;
588
+ var frozenOwners = 0;
589
+ var ACTIVE = 0;
590
+ var PAUSED = 1;
591
+ var ENDED = 2;
592
+ var NOOP = () => {
593
+ };
594
+ var skippedWhileHeld = /* @__PURE__ */ new Set();
595
+ var waiters = /* @__PURE__ */ new Set();
596
+ var heldRoots = /* @__PURE__ */ new Set();
597
+ function ownerState(tok) {
598
+ let state5 = ACTIVE;
599
+ for (let t = tok; t !== null; ) {
600
+ const o = t.sub;
601
+ if (o === null) return ENDED;
602
+ if (o._frozen === true) state5 = PAUSED;
603
+ t = o._owner ?? null;
604
+ }
605
+ return state5;
606
+ }
607
+ var endWatchers = /* @__PURE__ */ new Set();
608
+ var stateWatchers = /* @__PURE__ */ new Set();
609
+ var stateCheckQueued = false;
610
+ function queueStateCheck() {
611
+ if (stateCheckQueued || stateWatchers.size === 0) return;
612
+ stateCheckQueued = true;
613
+ queueMicrotask(() => {
614
+ stateCheckQueued = false;
615
+ for (const watcher of [...stateWatchers]) {
616
+ if (!stateWatchers.has(watcher)) continue;
617
+ const state5 = watcher.check.state();
618
+ if (state5 === ENDED) stateWatchers.delete(watcher);
619
+ if (state5 === ENDED || state5 === watcher.last) continue;
620
+ watcher.last = state5;
621
+ try {
622
+ watcher.listener(state5);
623
+ } catch (err) {
624
+ reportError(err, { phase: "effect" });
625
+ }
626
+ }
627
+ });
628
+ }
629
+ var ALWAYS_ACTIVE = Object.assign(() => false, {
630
+ state: () => ACTIVE,
631
+ wait: (onActive) => {
632
+ onActive();
633
+ return NOOP;
634
+ },
635
+ onEnd: () => NOOP,
636
+ run: (fn) => fn(),
637
+ onChange: () => NOOP,
638
+ within: () => false
639
+ });
640
+ function captureFrozen() {
641
+ const tok = currentStructOwner;
642
+ if (tok === null) return ALWAYS_ACTIVE;
643
+ const state5 = () => ownerState(tok);
644
+ const check = Object.assign(() => state5() !== ACTIVE, {
645
+ state: state5,
646
+ wait: (onActive, onEnd) => {
647
+ const now = state5();
648
+ if (now !== PAUSED) {
649
+ (now === ACTIVE ? onActive : onEnd)();
650
+ return NOOP;
651
+ }
652
+ const waiter = { check, onActive, onEnd };
653
+ waiters.add(waiter);
654
+ return () => {
655
+ waiters.delete(waiter);
656
+ };
657
+ },
658
+ onEnd: (onEnd) => {
659
+ if (state5() === ENDED) {
660
+ onEnd();
661
+ return NOOP;
662
+ }
663
+ const watcher = { check, onActive: NOOP, onEnd };
664
+ endWatchers.add(watcher);
665
+ return () => {
666
+ endWatchers.delete(watcher);
667
+ };
668
+ },
669
+ run: (fn) => {
670
+ const prev = currentStructOwner;
671
+ currentStructOwner = tok.sub === null ? null : tok;
672
+ try {
673
+ return fn();
674
+ } finally {
675
+ currentStructOwner = prev;
676
+ }
677
+ },
678
+ onChange: (listener) => {
679
+ const last = state5();
680
+ if (last === ENDED) return NOOP;
681
+ const watcher = { check, last, listener };
682
+ stateWatchers.add(watcher);
683
+ return () => {
684
+ stateWatchers.delete(watcher);
685
+ };
686
+ },
687
+ within: (owner) => {
688
+ const target = owner.run(() => currentStructOwner);
689
+ if (target === null) return false;
690
+ for (let t = tok; t !== null; ) {
691
+ if (t === target) return true;
692
+ const o = t.sub;
693
+ if (o === null) return false;
694
+ t = o._owner ?? null;
695
+ }
696
+ return false;
697
+ }
698
+ });
699
+ return check;
700
+ }
701
+ function holdSubtree(root) {
702
+ heldRoots.add(root);
703
+ return () => {
704
+ if (heldRoots.delete(root)) settleHeld();
705
+ };
706
+ }
707
+ function isHeld(sub) {
708
+ if (frozenOwners !== 0 && hasFrozenOwner(sub)) return true;
709
+ if (heldRoots.size !== 0) {
710
+ const node = sub._errorNode;
711
+ if (node != null) {
712
+ for (const root of heldRoots) if (root.contains(node)) return true;
713
+ }
714
+ }
715
+ return false;
716
+ }
717
+ function notifyEnded() {
718
+ for (const watcher of stateWatchers) if (watcher.check.state() === ENDED) stateWatchers.delete(watcher);
719
+ let ended = null;
720
+ for (const watcher of endWatchers) {
721
+ if (watcher.check.state() !== ENDED) continue;
722
+ endWatchers.delete(watcher);
723
+ (ended ?? (ended = [])).push(watcher);
724
+ }
725
+ if (ended === null) return;
726
+ for (const watcher of ended) {
727
+ try {
728
+ watcher.onEnd();
729
+ } catch (err) {
730
+ reportError(err, { phase: "cleanup" });
731
+ }
732
+ }
733
+ }
734
+ function dropReleasedSkipped() {
735
+ for (const sub of skippedWhileHeld) {
736
+ if (sub._disposed === true || sub.depsHead == null) skippedWhileHeld.delete(sub);
737
+ }
738
+ }
739
+ function settleHeld() {
740
+ for (const sub of skippedWhileHeld) {
741
+ if (sub._disposed === true) skippedWhileHeld.delete(sub);
742
+ else if (!isHeld(sub)) {
743
+ skippedWhileHeld.delete(sub);
744
+ if (!pendingSet.has(sub)) {
745
+ pendingSet.add(sub);
746
+ pendingQueue.push(sub);
747
+ }
748
+ }
749
+ }
750
+ if (frozenOwners === 0 && heldRoots.size === 0) skippedWhileHeld.clear();
751
+ let ready = null;
752
+ for (const waiter of waiters) {
753
+ if (waiter.check.state() === PAUSED) continue;
754
+ waiters.delete(waiter);
755
+ (ready ?? (ready = [])).push(waiter);
756
+ }
757
+ if (ready !== null) {
758
+ const settle = ready;
759
+ queueMicrotask(() => {
760
+ for (const waiter of settle) {
761
+ const state5 = waiter.check.state();
762
+ if (state5 === PAUSED) waiters.add(waiter);
763
+ else
764
+ try {
765
+ (state5 === ACTIVE ? waiter.onActive : waiter.onEnd)();
766
+ } catch (err) {
767
+ reportError(err, { phase: "effect" });
768
+ }
769
+ }
770
+ });
771
+ }
772
+ if (pendingQueue.length > 0) drainNotificationQueue();
773
+ }
774
+ function hasFrozenOwner(sub) {
775
+ for (let t = sub._owner ?? null; t !== null; ) {
776
+ const o = t.sub;
777
+ if (o === null) return false;
778
+ if (o._frozen === true) return true;
779
+ t = o._owner ?? null;
780
+ }
781
+ return false;
782
+ }
583
783
  var subscriberEpochCounter = 0;
584
784
  function retrack(effectFn, subscriber) {
585
785
  const prev = currentSubscriber;
@@ -687,6 +887,20 @@ function structuralBinding(commit, ownerNode) {
687
887
  } finally {
688
888
  currentStructOwner = prev;
689
889
  }
890
+ },
891
+ freeze: () => {
892
+ const s = sub;
893
+ if (s._disposed || s._frozen) return;
894
+ s._frozen = true;
895
+ frozenOwners++;
896
+ queueStateCheck();
897
+ },
898
+ thaw: () => {
899
+ if (!sub._frozen) return;
900
+ sub._frozen = false;
901
+ frozenOwners--;
902
+ queueStateCheck();
903
+ settleHeld();
690
904
  }
691
905
  };
692
906
  }
@@ -715,20 +929,34 @@ function createBinding(commit, ownerNode, structural) {
715
929
  subscriber._errorNode = ownerNode;
716
930
  subscriber._tok = null;
717
931
  subscriber._owner = null;
932
+ subscriber._frozen = false;
718
933
  if (structural) subscriber._tok = { sub: subscriber };
719
934
  try {
720
935
  run();
721
936
  } catch (err) {
722
937
  subscriber._disposed = true;
723
938
  subscriber._errorNode = void 0;
724
- if (subscriber._tok != null) subscriber._tok.sub = null;
939
+ if (subscriber._tok != null) {
940
+ subscriber._tok.sub = null;
941
+ if (endWatchers.size !== 0 || stateWatchers.size !== 0) notifyEnded();
942
+ }
725
943
  cleanup(subscriber);
726
944
  throw err;
727
945
  }
728
946
  subscriber._dispose = () => {
729
947
  subscriber._disposed = true;
948
+ const wasFrozen = subscriber._frozen === true;
949
+ if (wasFrozen) {
950
+ subscriber._frozen = false;
951
+ frozenOwners--;
952
+ }
730
953
  subscriber._errorNode = void 0;
731
- if (subscriber._tok != null) subscriber._tok.sub = null;
954
+ if (subscriber._tok != null) {
955
+ subscriber._tok.sub = null;
956
+ if (endWatchers.size !== 0 || stateWatchers.size !== 0) notifyEnded();
957
+ if (skippedWhileHeld.size !== 0) dropReleasedSkipped();
958
+ if (wasFrozen || waiters.size !== 0) settleHeld();
959
+ }
732
960
  cleanup(subscriber);
733
961
  };
734
962
  return subscriber;
@@ -838,6 +1066,10 @@ function runPendingOwners(sub) {
838
1066
  }
839
1067
  }
840
1068
  function runQueued(sub) {
1069
+ if ((frozenOwners !== 0 || heldRoots.size !== 0) && isHeld(sub)) {
1070
+ skippedWhileHeld.add(sub);
1071
+ return;
1072
+ }
841
1073
  if (quarantined.size > 0 && quarantined.has(sub)) return;
842
1074
  if (sub._hasComputedDep === true && !depsChanged(sub)) return;
843
1075
  if (tickRepeat(sub)) {
@@ -985,7 +1217,7 @@ function forEachSubscriber(signal2, visit) {
985
1217
  }
986
1218
 
987
1219
  // src/reactivity/track.ts
988
- var _runtimeVersion = true ? "4.14.1" : "dev";
1220
+ var _runtimeVersion = true ? "4.15.0" : "dev";
989
1221
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
990
1222
  function resolveReactiveApi() {
991
1223
  const g = globalThis;
@@ -1021,6 +1253,8 @@ function resolveReactiveApi() {
1021
1253
  runWithOwnerScope,
1022
1254
  bindOwnerScope,
1023
1255
  structuralBinding,
1256
+ captureFrozen,
1257
+ holdSubtree,
1024
1258
  version: _runtimeVersion
1025
1259
  };
1026
1260
  g[REGISTRY_KEY] = local;
@@ -1048,6 +1282,22 @@ var getOwnerScope2 = API.getOwnerScope ?? getOwnerScope;
1048
1282
  var runWithOwnerScope2 = API.runWithOwnerScope ?? runWithOwnerScope;
1049
1283
  var bindOwnerScope2 = API.bindOwnerScope ?? bindOwnerScope;
1050
1284
  var structuralBinding2 = API.structuralBinding ?? ((commit, ownerNode) => ({ stop: API.reactiveBinding(commit, ownerNode), run: (fn) => fn() }));
1285
+ var captureFrozen2 = API.captureFrozen ?? (() => Object.assign(() => false, {
1286
+ state: () => 0,
1287
+ wait: (onActive) => {
1288
+ onActive();
1289
+ return () => {
1290
+ };
1291
+ },
1292
+ onEnd: () => () => {
1293
+ },
1294
+ run: (fn) => fn(),
1295
+ onChange: () => () => {
1296
+ },
1297
+ within: () => false
1298
+ }));
1299
+ var holdSubtree2 = API.holdSubtree ?? (() => () => {
1300
+ });
1051
1301
 
1052
1302
  // src/core/devtoolsHook.ts
1053
1303
  function emitDevtools(hook, event, payload) {
@@ -1132,6 +1382,10 @@ function requestSlot(name, create) {
1132
1382
  if (!slots.has(name) && create) slots.set(name, create());
1133
1383
  return slots.get(name);
1134
1384
  }
1385
+ var PRERENDER_HOST = /* @__PURE__ */ Symbol.for("sibujs.prerender.v1");
1386
+ function isServer() {
1387
+ return getSSRStore().ssr || globalThis[PRERENDER_HOST] !== void 0;
1388
+ }
1135
1389
  function runInSSRContext(fn) {
1136
1390
  const store = {
1137
1391
  ssr: true,
@@ -1160,14 +1414,14 @@ function runInSSRContext(fn) {
1160
1414
  }
1161
1415
  function trackPending(promise, label, hydration = true) {
1162
1416
  const store = getRequestStore();
1163
- const pending = store?.pending;
1164
- const sessions = pending || !hydration ? [] : hydrationOwners();
1165
- if (!pending && sessions.length === 0) return promise;
1417
+ const pending2 = store?.pending;
1418
+ const sessions = pending2 || !hydration ? [] : hydrationOwners();
1419
+ if (!pending2 && sessions.length === 0) return promise;
1166
1420
  const entry = { label, since: Date.now(), boundary: currentBoundary() ?? void 0 };
1167
- pending?.add(entry);
1421
+ pending2?.add(entry);
1168
1422
  const release = holdHydration(sessions, entry);
1169
1423
  const done = () => {
1170
- pending?.delete(entry);
1424
+ pending2?.delete(entry);
1171
1425
  store?.wake?.();
1172
1426
  release();
1173
1427
  };
@@ -2367,6 +2621,25 @@ function assertA11y(root, options) {
2367
2621
  }
2368
2622
  }
2369
2623
 
2624
+ // src/utils/styleKey.ts
2625
+ var EXCEPTIONS = { cssFloat: "float" };
2626
+ var VENDOR_PREFIX = /^(webkit|moz|ms|o)-/;
2627
+ var cache = /* @__PURE__ */ new Map();
2628
+ var CACHE_MAX = 1e3;
2629
+ function styleKeyToProperty(key) {
2630
+ let name = cache.get(key);
2631
+ if (name !== void 0) return name;
2632
+ if (key.startsWith("--")) name = key;
2633
+ else if (Object.hasOwn(EXCEPTIONS, key)) name = EXCEPTIONS[key];
2634
+ else {
2635
+ name = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
2636
+ if (VENDOR_PREFIX.test(name)) name = `-${name}`;
2637
+ }
2638
+ if (cache.size >= CACHE_MAX) cache.clear();
2639
+ cache.set(key, name);
2640
+ return name;
2641
+ }
2642
+
2370
2643
  // src/testing/adapters.ts
2371
2644
  function escapeAttrValue(value) {
2372
2645
  return value.replace(/[\\"]/g, "\\$&").replace(/\n/g, "\\a ").replace(/\r/g, "\\d ").replace(/\0/g, "\\fffd ");
@@ -2483,8 +2756,7 @@ function createJestAdapter() {
2483
2756
  toHaveStyle(element, style) {
2484
2757
  const mismatches = [];
2485
2758
  for (const [prop, expected] of Object.entries(style)) {
2486
- const kebabProp = prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
2487
- const actual = element.style.getPropertyValue(kebabProp);
2759
+ const actual = element.style.getPropertyValue(styleKeyToProperty(prop));
2488
2760
  if (actual !== expected) {
2489
2761
  mismatches.push(`${prop}: expected "${expected}" but got "${actual}"`);
2490
2762
  }
@@ -3226,6 +3498,133 @@ ${result.diff ?? ""}`);
3226
3498
  };
3227
3499
  }
3228
3500
 
3501
+ // src/core/rendering/heldCommit.ts
3502
+ var NOOP2 = () => {
3503
+ };
3504
+ function commitWhenActive(slot3, c) {
3505
+ let over = false;
3506
+ let unhold = null;
3507
+ let drop = NOOP2;
3508
+ const finish = () => {
3509
+ over = true;
3510
+ if (slot3.cancel === cancel) slot3.cancel = NOOP2;
3511
+ drop();
3512
+ drop = NOOP2;
3513
+ };
3514
+ const cancel = () => {
3515
+ if (over) return;
3516
+ finish();
3517
+ const release = unhold;
3518
+ unhold = null;
3519
+ try {
3520
+ c.discard();
3521
+ } finally {
3522
+ release?.();
3523
+ }
3524
+ };
3525
+ const attempt = () => {
3526
+ drop = NOOP2;
3527
+ let clean = false;
3528
+ while (!over) {
3529
+ const state5 = c.held.state();
3530
+ if (state5 === 2 || c.owned !== void 0 && !c.owned()) {
3531
+ cancel();
3532
+ return;
3533
+ }
3534
+ if (state5 === 1) {
3535
+ if (c.node !== null && unhold === null) unhold = holdSubtree2(c.node);
3536
+ drop = c.held.wait(attempt, attempt);
3537
+ return;
3538
+ }
3539
+ if (unhold !== null) {
3540
+ const release = unhold;
3541
+ unhold = null;
3542
+ release();
3543
+ continue;
3544
+ }
3545
+ if (!clean) {
3546
+ clean = c.teardown?.() !== true;
3547
+ continue;
3548
+ }
3549
+ finish();
3550
+ c.insert();
3551
+ return;
3552
+ }
3553
+ };
3554
+ slot3.cancel = cancel;
3555
+ attempt();
3556
+ }
3557
+
3558
+ // src/core/rendering/range.ts
3559
+ var state3 = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.ranges.v1"), () => ({
3560
+ ends: /* @__PURE__ */ new WeakMap(),
3561
+ pending: /* @__PURE__ */ new WeakMap(),
3562
+ registrations: /* @__PURE__ */ new Set(),
3563
+ listeners: /* @__PURE__ */ new Set(),
3564
+ notice: { queued: false, batch: [] }
3565
+ }));
3566
+ var ends = state3.ends;
3567
+ function registerRange(start, last) {
3568
+ ends.set(start, last);
3569
+ }
3570
+ var pending = state3.pending;
3571
+ var NOTHING = () => {
3572
+ };
3573
+ var registrations = state3.registrations;
3574
+ var loadListeners = state3.listeners;
3575
+ var notice = state3.notice;
3576
+ function notifyLoad(settled2) {
3577
+ if (loadListeners.size === 0) return;
3578
+ if (settled2 !== null) notice.batch.push(settled2);
3579
+ if (notice.queued) return;
3580
+ notice.queued = true;
3581
+ queueMicrotask(() => {
3582
+ notice.queued = false;
3583
+ const batch2 = notice.batch;
3584
+ notice.batch = [];
3585
+ for (const listener of [...loadListeners]) listener(batch2);
3586
+ });
3587
+ }
3588
+ function beginPending(at, held) {
3589
+ const entry = { at, held, settled: false, cancelled: false, committed: null };
3590
+ registrations.add(entry);
3591
+ notifyLoad(null);
3592
+ const settle = (cancelled, committed) => {
3593
+ if (entry.settled) return;
3594
+ entry.settled = true;
3595
+ entry.cancelled = cancelled;
3596
+ entry.committed = committed;
3597
+ registrations.delete(entry);
3598
+ notifyLoad(entry);
3599
+ };
3600
+ return {
3601
+ entry,
3602
+ done: (committed) => settle(false, committed ?? null),
3603
+ cancel: () => settle(true, null)
3604
+ };
3605
+ }
3606
+ var INERT = { done: NOTHING, cancel: NOTHING };
3607
+ function beginLoading(anchor) {
3608
+ if (isServer()) return INERT;
3609
+ const { done, cancel } = beginPending(anchor, null);
3610
+ return { done: () => done(), cancel };
3611
+ }
3612
+ function registerNodesRange(anchor, nodes) {
3613
+ registerRange(anchor, () => {
3614
+ const parent = anchor.parentNode;
3615
+ const wanted = nodes().filter((n) => n !== null && n.parentNode === parent);
3616
+ let last = null;
3617
+ let left = wanted.length;
3618
+ for (let n = anchor.nextSibling; n !== null && left > 0; n = n.nextSibling) {
3619
+ if (wanted.includes(n)) {
3620
+ last = n;
3621
+ left--;
3622
+ }
3623
+ }
3624
+ return last;
3625
+ });
3626
+ }
3627
+
3229
3628
  // src/utils/sanitize.ts
3230
3629
  function stripControlChars(value) {
3231
3630
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
@@ -3746,7 +4145,7 @@ var isBatching = API2.isBatching;
3746
4145
  // src/core/signals/signal.ts
3747
4146
  var _g2 = globalThis;
3748
4147
  function signal(initial, options) {
3749
- const state3 = {
4148
+ const state5 = {
3750
4149
  value: initial,
3751
4150
  __v: 0,
3752
4151
  __sc: 0,
@@ -3759,57 +4158,57 @@ function signal(initial, options) {
3759
4158
  };
3760
4159
  const debugName = DEV ? options?.name : void 0;
3761
4160
  const equalsFn = options?.equals;
3762
- if (debugName) state3.__name = debugName;
4161
+ if (debugName) state5.__name = debugName;
3763
4162
  function get() {
3764
- recordDependency2(state3);
3765
- return state3.value;
4163
+ recordDependency2(state5);
4164
+ return state5.value;
3766
4165
  }
3767
- get.__signal = state3;
4166
+ get.__signal = state5;
3768
4167
  if (debugName) get.__name = debugName;
3769
4168
  let set;
3770
4169
  if (equalsFn) {
3771
4170
  set = (next) => {
3772
- const prev = state3.value;
4171
+ const prev = state5.value;
3773
4172
  const newValue = typeof next === "function" ? next(prev) : next;
3774
4173
  if (equalsFn(prev, newValue)) return;
3775
- state3.value = newValue;
3776
- state3.__v++;
4174
+ state5.value = newValue;
4175
+ state5.__v++;
3777
4176
  if (DEV) {
3778
4177
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3779
- if (hook) emitDevtools(hook, "signal:update", { signal: state3, name: debugName, oldValue: prev, newValue });
4178
+ if (hook) emitDevtools(hook, "signal:update", { signal: state5, name: debugName, oldValue: prev, newValue });
3780
4179
  }
3781
- if (!enqueueBatchedSignal(state3)) {
3782
- notifySubscribers2(state3);
4180
+ if (!enqueueBatchedSignal(state5)) {
4181
+ notifySubscribers2(state5);
3783
4182
  }
3784
4183
  };
3785
4184
  } else if (DEV) {
3786
4185
  set = (next) => {
3787
- const prev = state3.value;
4186
+ const prev = state5.value;
3788
4187
  const newValue = typeof next === "function" ? next(prev) : next;
3789
4188
  if (Object.is(newValue, prev)) return;
3790
- state3.value = newValue;
3791
- state3.__v++;
4189
+ state5.value = newValue;
4190
+ state5.__v++;
3792
4191
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3793
- if (hook) emitDevtools(hook, "signal:update", { signal: state3, name: debugName, oldValue: prev, newValue });
3794
- if (!enqueueBatchedSignal(state3)) {
3795
- notifySubscribers2(state3);
4192
+ if (hook) emitDevtools(hook, "signal:update", { signal: state5, name: debugName, oldValue: prev, newValue });
4193
+ if (!enqueueBatchedSignal(state5)) {
4194
+ notifySubscribers2(state5);
3796
4195
  }
3797
4196
  };
3798
4197
  } else {
3799
4198
  set = (next) => {
3800
- const prev = state3.value;
4199
+ const prev = state5.value;
3801
4200
  const newValue = typeof next === "function" ? next(prev) : next;
3802
4201
  if (Object.is(newValue, prev)) return;
3803
- state3.value = newValue;
3804
- state3.__v++;
3805
- if (!enqueueBatchedSignal(state3)) {
3806
- notifySubscribers2(state3);
4202
+ state5.value = newValue;
4203
+ state5.__v++;
4204
+ if (!enqueueBatchedSignal(state5)) {
4205
+ notifySubscribers2(state5);
3807
4206
  }
3808
4207
  };
3809
4208
  }
3810
4209
  if (DEV) {
3811
4210
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3812
- if (hook) emitDevtools(hook, "signal:create", { signal: state3, name: debugName, getter: get, initial });
4211
+ if (hook) emitDevtools(hook, "signal:create", { signal: state5, name: debugName, getter: get, initial });
3813
4212
  }
3814
4213
  return [get, set];
3815
4214
  }
@@ -4856,15 +5255,15 @@ var _ComponentLoader = class _ComponentLoader {
4856
5255
  const cached = this.planCache.get(cacheKey);
4857
5256
  if (cached && cached.kind === "factory") return cached.component;
4858
5257
  this.assertNotHot(cacheKey);
4859
- let pending = this.moduleLoads.get(cacheKey);
4860
- const owner = pending === void 0;
4861
- if (pending === void 0) {
4862
- pending = plan.load();
4863
- this.moduleLoads.set(cacheKey, pending);
5258
+ let pending2 = this.moduleLoads.get(cacheKey);
5259
+ const owner = pending2 === void 0;
5260
+ if (pending2 === void 0) {
5261
+ pending2 = plan.load();
5262
+ this.moduleLoads.set(cacheKey, pending2);
4864
5263
  }
4865
5264
  let resolved;
4866
5265
  try {
4867
- resolved = await pending;
5266
+ resolved = await pending2;
4868
5267
  } catch (error) {
4869
5268
  this.noteError(cacheKey);
4870
5269
  throw error;
@@ -4886,7 +5285,14 @@ var _ComponentLoader = class _ComponentLoader {
4886
5285
  * is a configuration error, not something to chase.
4887
5286
  */
4888
5287
  async instantiateFactory(component, route2, routePath, scope, depth = 0) {
4889
- const build = () => scope ? runWithOwnerScope2(scope, component) : component();
5288
+ const gate = scope?.gate;
5289
+ if (gate) {
5290
+ while (gate.held()) if (!await gate.until()) throw gate.cancellation();
5291
+ if (scope?.current?.() === false) throw gate.cancellation();
5292
+ }
5293
+ const owner = scope?.owner;
5294
+ const invoke = () => scope ? runWithOwnerScope2(scope, component) : component();
5295
+ const build = owner ? () => owner.run(invoke) : invoke;
4890
5296
  const result = scope?.adopt ? inScope(scope.adopt, build) : build();
4891
5297
  const value = this.isThenable(result) ? await result : result;
4892
5298
  if (this.isElement(value)) return value;
@@ -5095,7 +5501,7 @@ var _SibuRouter = class _SibuRouter {
5095
5501
  return this.loadedPromise;
5096
5502
  }
5097
5503
  createInitialRoute() {
5098
- return {
5504
+ const route2 = {
5099
5505
  path: "/",
5100
5506
  params: {},
5101
5507
  query: {},
@@ -5104,6 +5510,8 @@ var _SibuRouter = class _SibuRouter {
5104
5510
  meta: {},
5105
5511
  matched: []
5106
5512
  };
5513
+ bootstrapRoutes.add(route2);
5514
+ return route2;
5107
5515
  }
5108
5516
  handleLocationChange(skipHistory = true, initialResolution = false) {
5109
5517
  const path = this.getCurrentPath();
@@ -5144,8 +5552,31 @@ var _SibuRouter = class _SibuRouter {
5144
5552
  matched
5145
5553
  };
5146
5554
  }
5555
+ /**
5556
+ * Once no navigation is in flight: if the route is still the placeholder
5557
+ * the router started with — every navigation that could have replaced it
5558
+ * was cancelled by a guard, failed, or was a duplicate — that IS the
5559
+ * outcome, and the outlets that rendered nothing for it settle (see
5560
+ * `settleEmpty`). Checked when a navigation has definitively completed,
5561
+ * never from `isReady` (set before the guards run). A navigation still
5562
+ * pending — a newer one, or a guard that never resolves — keeps them
5563
+ * pending; an outlet's enter is bounded by its own deadline then.
5564
+ */
5565
+ settleBootstrap() {
5566
+ if (this.destroyed || this.navigator.isNavigating) return;
5567
+ const current2 = untracked2(() => this.currentRouteGetter());
5568
+ if (!bootstrapRoutes.delete(current2)) return;
5569
+ const waiting = bootstrapWaiters.get(current2);
5570
+ bootstrapWaiters.delete(current2);
5571
+ if (waiting) for (const settle of waiting) settle();
5572
+ }
5147
5573
  // Public API
5148
5574
  async navigate(to, options = {}) {
5575
+ const result = await this.attemptNavigation(to, options);
5576
+ this.settleBootstrap();
5577
+ return result;
5578
+ }
5579
+ async attemptNavigation(to, options) {
5149
5580
  try {
5150
5581
  await this.navigator.navigate(async (signal2) => {
5151
5582
  const targetPath = this.resolvePath(to);
@@ -5399,6 +5830,13 @@ var _SibuRouter = class _SibuRouter {
5399
5830
  /**
5400
5831
  * Create exactly one real instance from an already-resolved plan. With a
5401
5832
  * `scope`, the factory runs in it (see `RouteScope`).
5833
+ *
5834
+ * When the scope belongs to an outlet whose branch is paused (playing a
5835
+ * leave it may still be taken back from), a lazy module that resolves does
5836
+ * not run its factory yet: the promise stays pending and continues once the
5837
+ * branch resumes. If the branch ends, or the outlet's pass was superseded
5838
+ * meanwhile, it rejects with a `DOMException` named `"AbortError"`. Genuine
5839
+ * loader and component failures reject with their own error, as before.
5402
5840
  */
5403
5841
  async instantiateComponent(plan, route2, routePath, scope) {
5404
5842
  return prerenderWait(this.loader.instantiate(plan, route2, routePath, scope), `router:render ${route2.path}`);
@@ -5643,6 +6081,21 @@ var RouteScope = class {
5643
6081
  constructor(route2) {
5644
6082
  /** Creation scope the instance is built in (adopting hydration); see `adoptScope`. */
5645
6083
  this.adopt = null;
6084
+ /**
6085
+ * The outlet's pause gate: while the branch holding the outlet is paused (it
6086
+ * began a leave and may still be taken back), a lazy module that resolves
6087
+ * does not get its factory invoked yet; instantiation waits for the branch
6088
+ * to resume, and is cancelled if it ends.
6089
+ */
6090
+ this.gate = null;
6091
+ /** Whether the outlet pass this instance is for is still the current one. */
6092
+ this.current = null;
6093
+ /**
6094
+ * The instance's structural owner, nested under the outlet's: its factory
6095
+ * runs as it, so what it creates is paused with the branch holding the
6096
+ * outlet, and ends (`lifecycleScope()` work included) with the instance.
6097
+ */
6098
+ this.owner = null;
5646
6099
  this.released = false;
5647
6100
  const [get, set] = signal(route2);
5648
6101
  this.get = get;
@@ -5669,6 +6122,112 @@ var RouteScope = class {
5669
6122
  queueMicrotask(() => this.set(null));
5670
6123
  }
5671
6124
  };
6125
+ var NOOP3 = () => {
6126
+ };
6127
+ var bootstrapRoutes = /* @__PURE__ */ new WeakSet();
6128
+ var bootstrapWaiters = /* @__PURE__ */ new WeakMap();
6129
+ function settleEmpty(route2, loaded, current2) {
6130
+ if (!bootstrapRoutes.has(route2)) {
6131
+ loaded.done();
6132
+ return;
6133
+ }
6134
+ let waiting = bootstrapWaiters.get(route2);
6135
+ if (waiting === void 0) bootstrapWaiters.set(route2, waiting = /* @__PURE__ */ new Set());
6136
+ waiting.add(() => {
6137
+ if (current2()) loaded.done();
6138
+ });
6139
+ }
6140
+ var cancellations = /* @__PURE__ */ new WeakSet();
6141
+ function pauseGate(frozen) {
6142
+ let closed = false;
6143
+ const pending2 = /* @__PURE__ */ new Set();
6144
+ return {
6145
+ held: () => closed || frozen(),
6146
+ until: () => {
6147
+ if (closed) return Promise.resolve(false);
6148
+ return new Promise((resolve) => {
6149
+ const settle = (active) => {
6150
+ pending2.delete(cancel);
6151
+ resolve(active && !closed);
6152
+ };
6153
+ const drop = frozen.wait(
6154
+ () => settle(true),
6155
+ () => settle(false)
6156
+ );
6157
+ const cancel = () => {
6158
+ drop();
6159
+ settle(false);
6160
+ };
6161
+ pending2.add(cancel);
6162
+ });
6163
+ },
6164
+ cancellation: () => {
6165
+ const error = new DOMException("Route instantiation cancelled: its branch was released", "AbortError");
6166
+ cancellations.add(error);
6167
+ return error;
6168
+ },
6169
+ close: () => {
6170
+ closed = true;
6171
+ for (const cancel of [...pending2]) cancel();
6172
+ },
6173
+ run: frozen.run,
6174
+ frozen,
6175
+ closed: () => closed,
6176
+ onClose: (fn) => {
6177
+ pending2.add(fn);
6178
+ return () => {
6179
+ pending2.delete(fn);
6180
+ };
6181
+ }
6182
+ };
6183
+ }
6184
+ function whenActive(gate, fn) {
6185
+ if (!gate.held()) fn();
6186
+ else
6187
+ void gate.until().then((active) => {
6188
+ if (active) whenActive(gate, fn);
6189
+ });
6190
+ }
6191
+ function commitPass(gate, c) {
6192
+ const frozen = gate.frozen;
6193
+ const held = {
6194
+ state: frozen.state,
6195
+ wait: (onActive, onEnd) => {
6196
+ let live = true;
6197
+ const later = (fn) => () => {
6198
+ void Promise.resolve().then(() => {
6199
+ if (live) fn();
6200
+ });
6201
+ };
6202
+ const drop = frozen.wait(later(onActive), later(onEnd));
6203
+ return () => {
6204
+ live = false;
6205
+ drop();
6206
+ };
6207
+ }
6208
+ };
6209
+ return new Promise((resolve, reject) => {
6210
+ const slot3 = { cancel: NOOP3 };
6211
+ const untrack = gate.onClose(() => slot3.cancel());
6212
+ const settle = (fn) => () => {
6213
+ untrack();
6214
+ try {
6215
+ fn();
6216
+ resolve();
6217
+ } catch (error) {
6218
+ reject(error);
6219
+ }
6220
+ };
6221
+ commitWhenActive(slot3, {
6222
+ held,
6223
+ node: c.node,
6224
+ owned: () => !gate.closed() && c.owned(),
6225
+ teardown: c.teardown,
6226
+ insert: settle(c.insert),
6227
+ discard: settle(c.discard)
6228
+ });
6229
+ });
6230
+ }
5672
6231
  function activeRouteScope() {
5673
6232
  const scope = getOwnerScope2();
5674
6233
  return scope instanceof RouteScope ? scope : null;
@@ -5682,14 +6241,27 @@ function scopedRoute(router) {
5682
6241
  return router.currentRoute;
5683
6242
  }
5684
6243
  var instanceScopes = /* @__PURE__ */ new WeakMap();
5685
- async function instantiateScoped(router, plan, routeDef, routePath, route2, adopt = null) {
6244
+ async function instantiateScoped(router, plan, routeDef, routePath, route2, adopt = null, gate = null, current2 = null) {
5686
6245
  const scope = new RouteScope(route2);
5687
6246
  scope.adopt = adopt;
5688
- const node = await router.instantiateComponent(plan, routeDef, routePath, scope);
6247
+ scope.gate = gate;
6248
+ scope.current = current2;
6249
+ const owner = gate ? gate.run(() => structuralBinding2(NOOP3)) : structuralBinding2(NOOP3);
6250
+ scope.owner = owner;
6251
+ let node;
6252
+ try {
6253
+ node = await router.instantiateComponent(plan, routeDef, routePath, scope);
6254
+ } catch (error) {
6255
+ owner.stop();
6256
+ throw error;
6257
+ }
5689
6258
  if (node) {
5690
6259
  instanceScopes.set(node, scope);
5691
- registerDisposer(node, () => scope.release());
5692
- }
6260
+ registerDisposer(node, () => {
6261
+ owner.stop();
6262
+ scope.release();
6263
+ });
6264
+ } else owner.stop();
5693
6265
  return node;
5694
6266
  }
5695
6267
  function updateInstanceRoute(node, route2) {
@@ -5732,6 +6304,8 @@ function instanceKey(owner, records, route2) {
5732
6304
  function Route() {
5733
6305
  const anchor = document.createComment("route-outlet");
5734
6306
  const adoptScope = captureScope();
6307
+ const frozen = captureFrozen2();
6308
+ const gate = pauseGate(frozen);
5735
6309
  let currentNode = null;
5736
6310
  let loadingNode = null;
5737
6311
  let errorNode = null;
@@ -5739,6 +6313,7 @@ function Route() {
5739
6313
  let currentTopRoute = null;
5740
6314
  let currentKey = null;
5741
6315
  let routeTorn = false;
6316
+ const loaded = beginLoading(anchor);
5742
6317
  const commitTarget = (seq) => routeTorn || seq !== navSeq ? null : anchor.parentNode;
5743
6318
  const retryIfDetached = (seq) => {
5744
6319
  if (!routeTorn && seq === navSeq && !anchor.parentNode) attachWait.request();
@@ -5783,24 +6358,54 @@ function Route() {
5783
6358
  loadingNode = null;
5784
6359
  }
5785
6360
  };
5786
- const showError = (error, routeDef) => {
6361
+ const showError = (seq, error, routeDef) => {
5787
6362
  if (!anchor.parentNode) return;
5788
- cleanupNodes();
5789
- errorNode = document.createElement("div");
5790
- errorNode.className = "route-error";
5791
- errorNode.setAttribute("role", "alert");
5792
- errorNode.setAttribute("aria-live", "assertive");
6363
+ const view = errorView(error, routeDef);
6364
+ void commitPass(gate, {
6365
+ node: view,
6366
+ owned: () => !routeTorn && seq === navSeq,
6367
+ discard: () => dispose(view),
6368
+ teardown: () => {
6369
+ if (!anchor.parentNode || !(currentNode || loadingNode || errorNode)) return false;
6370
+ cleanupNodes();
6371
+ return true;
6372
+ },
6373
+ insert: () => {
6374
+ const parent = anchor.parentNode;
6375
+ if (!parent) {
6376
+ dispose(view);
6377
+ unshownFailure = { seq, error, routeDef };
6378
+ attachWait.request();
6379
+ return;
6380
+ }
6381
+ errorNode = view;
6382
+ try {
6383
+ parent.insertBefore(view, anchor.nextSibling);
6384
+ } catch (err) {
6385
+ if (errorNode === view) errorNode = null;
6386
+ release(view);
6387
+ throw err;
6388
+ }
6389
+ if (!routeTorn && seq === navSeq) loaded.done();
6390
+ }
6391
+ }).catch((err) => console.error("[Route] Error view failed:", err));
6392
+ };
6393
+ const errorView = (error, routeDef) => {
6394
+ const errorNode2 = document.createElement("div");
6395
+ errorNode2.className = "route-error";
6396
+ errorNode2.setAttribute("role", "alert");
6397
+ errorNode2.setAttribute("aria-live", "assertive");
5793
6398
  if (routeDef && "component" in routeDef) {
5794
6399
  const src = routeDef.component.toString();
5795
6400
  const importMatch = src.match(/import\(["']([^"']+)["']\)/);
5796
6401
  if (importMatch) {
5797
- errorNode.setAttribute("data-component-source", importMatch[1]);
6402
+ errorNode2.setAttribute("data-component-source", importMatch[1]);
5798
6403
  }
5799
6404
  if (routeDef.component.name) {
5800
- errorNode.setAttribute("data-component-name", routeDef.component.name);
6405
+ errorNode2.setAttribute("data-component-name", routeDef.component.name);
5801
6406
  }
5802
6407
  }
5803
- errorNode.__routeError = error;
6408
+ errorNode2.__routeError = error;
5804
6409
  const title = document.createElement("h3");
5805
6410
  title.textContent = "Route Error";
5806
6411
  title.className = "route-error-title";
@@ -5819,10 +6424,10 @@ function Route() {
5819
6424
  };
5820
6425
  retryButton.addEventListener("click", onRetryClick);
5821
6426
  registerDisposer(retryButton, () => retryButton.removeEventListener("click", onRetryClick));
5822
- errorNode.appendChild(title);
5823
- errorNode.appendChild(message);
5824
- errorNode.appendChild(retryButton);
5825
- anchor.parentNode.insertBefore(errorNode, anchor.nextSibling);
6427
+ errorNode2.appendChild(title);
6428
+ errorNode2.appendChild(message);
6429
+ errorNode2.appendChild(retryButton);
6430
+ return errorNode2;
5826
6431
  };
5827
6432
  const update = async () => {
5828
6433
  routerRevision();
@@ -5835,12 +6440,14 @@ function Route() {
5835
6440
  currentTopRoute = null;
5836
6441
  currentKey = null;
5837
6442
  cleanupNodes();
6443
+ settleEmpty(route2, loaded, () => !routeTorn && seq === navSeq);
5838
6444
  return;
5839
6445
  }
5840
6446
  const routeDef = match.matched[0] || match.route;
5841
6447
  const key = instanceKey(routeDef, [routeDef], route2);
5842
6448
  if (routeDef === currentTopRoute && key === currentKey && currentNode) {
5843
6449
  updateInstanceRoute(currentNode, route2);
6450
+ loaded.done();
5844
6451
  return;
5845
6452
  }
5846
6453
  if ("redirect" in routeDef) {
@@ -5859,6 +6466,7 @@ function Route() {
5859
6466
  showLoading();
5860
6467
  }
5861
6468
  const plan = await _routerRef.current.loadPlan(routeDef, route2.path);
6469
+ while (gate.held()) if (!await gate.until()) return;
5862
6470
  if (!commitTarget(seq)) {
5863
6471
  retryIfDetached(seq);
5864
6472
  return;
@@ -5869,28 +6477,57 @@ function Route() {
5869
6477
  routeDef,
5870
6478
  route2.path,
5871
6479
  route2,
5872
- keyedScope(adoptScope, routeDef.path)
6480
+ keyedScope(adoptScope, routeDef.path),
6481
+ gate,
6482
+ () => commitTarget(seq) !== null
5873
6483
  );
5874
- const parent = commitTarget(seq);
5875
- if (!parent) {
5876
- release(node);
5877
- retryIfDetached(seq);
5878
- return;
5879
- }
5880
- if (node) {
5881
- currentTopRoute = routeDef;
5882
- currentKey = key;
5883
- cleanupNodes();
5884
- parent.insertBefore(node, anchor.nextSibling);
5885
- currentNode = node;
5886
- }
6484
+ await commitPass(gate, {
6485
+ node,
6486
+ owned: () => !routeTorn && seq === navSeq,
6487
+ discard: () => release(node),
6488
+ // The outgoing views go first — only when there is a page to
6489
+ // show, and a parent to show it in.
6490
+ teardown: () => {
6491
+ if (!node || !anchor.parentNode || !(currentNode || loadingNode || errorNode)) return false;
6492
+ cleanupNodes();
6493
+ return true;
6494
+ },
6495
+ insert: () => {
6496
+ const parent = commitTarget(seq);
6497
+ if (!parent) {
6498
+ release(node);
6499
+ retryIfDetached(seq);
6500
+ return;
6501
+ }
6502
+ if (node) {
6503
+ currentTopRoute = routeDef;
6504
+ currentKey = key;
6505
+ currentNode = node;
6506
+ try {
6507
+ parent.insertBefore(node, anchor.nextSibling);
6508
+ } catch (error) {
6509
+ if (currentNode === node) {
6510
+ currentNode = null;
6511
+ currentTopRoute = null;
6512
+ currentKey = null;
6513
+ }
6514
+ release(node);
6515
+ throw error;
6516
+ }
6517
+ }
6518
+ if (!routeTorn && seq === navSeq) loaded.done();
6519
+ }
6520
+ });
5887
6521
  } catch (error) {
6522
+ if (cancellations.has(error)) return;
5888
6523
  if (routeTorn || seq !== navSeq) return;
5889
- hideLoading();
5890
6524
  console.error("[Route] Component error:", error);
6525
+ while (gate.held()) if (!await gate.until()) return;
6526
+ if (routeTorn || seq !== navSeq) return;
6527
+ hideLoading();
5891
6528
  fail(seq, error, routeDef);
5892
6529
  }
5893
- }
6530
+ } else settleEmpty(route2, loaded, () => !routeTorn && seq === navSeq);
5894
6531
  } catch (error) {
5895
6532
  if (seq !== navSeq) return;
5896
6533
  if (routeTorn) return;
@@ -5902,16 +6539,20 @@ function Route() {
5902
6539
  const fail = (seq, error, routeDef) => {
5903
6540
  const err = error instanceof Error ? error : new Error(String(error));
5904
6541
  if (anchor.parentNode) {
5905
- showError(err, routeDef);
6542
+ showError(seq, err, routeDef);
5906
6543
  return;
5907
6544
  }
5908
6545
  unshownFailure = { seq, error: err, routeDef };
5909
6546
  attachWait.request();
5910
6547
  };
5911
6548
  const onAttached = () => {
6549
+ if (gate.held()) {
6550
+ void gate.until().then((active) => active && onAttached());
6551
+ return;
6552
+ }
5912
6553
  const failure = unshownFailure;
5913
6554
  unshownFailure = null;
5914
- if (failure && failure.seq === navSeq) showError(failure.error, failure.routeDef);
6555
+ if (failure && failure.seq === navSeq) showError(failure.seq, failure.error, failure.routeDef);
5915
6556
  else update();
5916
6557
  };
5917
6558
  let routeInitialized = false;
@@ -5921,11 +6562,16 @@ function Route() {
5921
6562
  routeInitialized = true;
5922
6563
  };
5923
6564
  const attachWait = attachRetry(anchor, onAttached);
5924
- const routeTeardown = track2(wrappedUpdate);
6565
+ const routeTeardown = reactiveBinding2(wrappedUpdate, anchor);
5925
6566
  if (!routeInitialized) {
5926
6567
  queueMicrotask(
5927
6568
  bindOwnerScope2(() => {
5928
- if (!routeInitialized && anchor.parentNode) wrappedUpdate();
6569
+ whenActive(
6570
+ gate,
6571
+ bindOwnerScope2(() => {
6572
+ if (!routeInitialized && anchor.parentNode) wrappedUpdate();
6573
+ })
6574
+ );
5929
6575
  })
5930
6576
  );
5931
6577
  }
@@ -5933,6 +6579,8 @@ function Route() {
5933
6579
  if (routeTorn) return;
5934
6580
  routeTorn = true;
5935
6581
  navSeq++;
6582
+ gate.close();
6583
+ loaded.cancel();
5936
6584
  routeTeardown();
5937
6585
  attachWait.cancel();
5938
6586
  unshownFailure = null;
@@ -5940,6 +6588,7 @@ function Route() {
5940
6588
  currentTopRoute = null;
5941
6589
  currentKey = null;
5942
6590
  };
6591
+ registerNodesRange(anchor, () => [currentNode, loadingNode, errorNode]);
5943
6592
  registerDisposer(anchor, routeCleanup);
5944
6593
  routeCleanups.push(routeCleanup);
5945
6594
  return anchor;
@@ -5969,14 +6618,14 @@ function ensureRouterPagehide() {
5969
6618
  }
5970
6619
 
5971
6620
  // src/utils/embedPolicy.ts
5972
- var state2 = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.embed.v2"), () => ({
6621
+ var state4 = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.embed.v2"), () => ({
5973
6622
  allowed: [],
5974
6623
  frames: /* @__PURE__ */ new WeakMap()
5975
6624
  }));
5976
6625
  var REQUEST_CACHE = "embed";
5977
6626
  function currentEmbedAllowlist() {
5978
6627
  const scoped = getRequestStore()?.caches?.get(REQUEST_CACHE)?.get("allowed");
5979
- return scoped ?? state2.allowed;
6628
+ return scoped ?? state4.allowed;
5980
6629
  }
5981
6630
  function resolveEmbedSrc(value, allowed = currentEmbedAllowlist()) {
5982
6631
  if (typeof value !== "string" || sanitizeUrl(value) === "") return null;
@@ -6036,7 +6685,7 @@ function narrowEmbedAllow(value) {
6036
6685
  return granted.filter((feature) => !denied.includes(feature)).join("; ");
6037
6686
  }
6038
6687
  function getEmbedRecord(frame) {
6039
- return state2.frames.get(frame);
6688
+ return state4.frames.get(frame);
6040
6689
  }
6041
6690
  function intersect(current2, permitted, separator) {
6042
6691
  const allowed = permitted.split(separator);
@@ -6797,12 +7446,6 @@ function splitDeclarations(text) {
6797
7446
  out.push(text.slice(start));
6798
7447
  return out;
6799
7448
  }
6800
- var CAMEL_EXCEPTIONS = { cssFloat: "float" };
6801
- function toKebab(prop) {
6802
- if (prop.startsWith("--")) return prop;
6803
- if (Object.hasOwn(CAMEL_EXCEPTIONS, prop)) return CAMEL_EXCEPTIONS[prop];
6804
- return prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`).replace(/^(webkit|moz|ms)-/, "-$1-");
6805
- }
6806
7449
  var ServerStyle = class {
6807
7450
  constructor(el) {
6808
7451
  this.el = el;
@@ -6897,13 +7540,13 @@ function styleProxy(style) {
6897
7540
  get(target, key, receiver) {
6898
7541
  if (typeof key === "string" && !(key in target)) {
6899
7542
  if (/^\d+$/.test(key)) return target.item(Number(key));
6900
- return target.getPropertyValue(toKebab(key));
7543
+ return target.getPropertyValue(styleKeyToProperty(key));
6901
7544
  }
6902
7545
  return Reflect.get(target, key, receiver);
6903
7546
  },
6904
7547
  set(target, key, value, receiver) {
6905
7548
  if (typeof key === "string" && !(key in target)) {
6906
- target.setProperty(toKebab(key), value == null ? "" : String(value));
7549
+ target.setProperty(styleKeyToProperty(key), value == null ? "" : String(value));
6907
7550
  return true;
6908
7551
  }
6909
7552
  return Reflect.set(target, key, value, receiver);
@@ -7735,21 +8378,21 @@ function compileSelector(selector) {
7735
8378
  const tokens = text.match(/(?:\\.|\[[^\]]*\]|[^\s>])+|>/g) ?? [];
7736
8379
  const compounds = [];
7737
8380
  const combinators = [];
7738
- let pending = " ";
8381
+ let pending2 = " ";
7739
8382
  let scoped = false;
7740
8383
  for (const token of tokens) {
7741
8384
  if (token === ">") {
7742
- pending = ">";
8385
+ pending2 = ">";
7743
8386
  continue;
7744
8387
  }
7745
8388
  if (token === ":scope") {
7746
8389
  scoped = true;
7747
- pending = " ";
8390
+ pending2 = " ";
7748
8391
  continue;
7749
8392
  }
7750
- if (compounds.length > 0) combinators.unshift(pending);
8393
+ if (compounds.length > 0) combinators.unshift(pending2);
7751
8394
  compounds.unshift(parseCompound(token, selector));
7752
- pending = " ";
8395
+ pending2 = " ";
7753
8396
  }
7754
8397
  if (compounds.length === 0) throw syntaxError(selector);
7755
8398
  groups.push({ compounds, combinators, scoped });
@@ -8016,14 +8659,14 @@ async function settled(_root, options = {}) {
8016
8659
  const deadline = Date.now() + timeout;
8017
8660
  for (; ; ) {
8018
8661
  await macrotask();
8019
- const pending = store?.pending;
8020
- if (!pending || pending.size === 0) {
8662
+ const pending2 = store?.pending;
8663
+ if (!pending2 || pending2.size === 0) {
8021
8664
  await macrotask();
8022
- if (!pending || pending.size === 0) return { timedOut: false, pending: [] };
8665
+ if (!pending2 || pending2.size === 0) return { timedOut: false, pending: [] };
8023
8666
  continue;
8024
8667
  }
8025
8668
  const remaining = deadline - Date.now();
8026
- if (remaining <= 0) return { timedOut: true, pending: describePending(pending) };
8669
+ if (remaining <= 0) return { timedOut: true, pending: describePending(pending2) };
8027
8670
  await new Promise((resolve) => {
8028
8671
  const done = () => {
8029
8672
  clearTimeout(timer);
@@ -8035,9 +8678,9 @@ async function settled(_root, options = {}) {
8035
8678
  });
8036
8679
  }
8037
8680
  }
8038
- function describePending(pending) {
8681
+ function describePending(pending2) {
8039
8682
  const now = Date.now();
8040
- return Array.from(pending, (work) => `${work.label} (${now - work.since} ms)`);
8683
+ return Array.from(pending2, (work) => `${work.label} (${now - work.since} ms)`);
8041
8684
  }
8042
8685
  function formatRenderDiagnostics(d, label = "render") {
8043
8686
  const lines = [
@@ -8165,7 +8808,7 @@ async function renderCore(input, options) {
8165
8808
  releaseTree();
8166
8809
  throw err;
8167
8810
  }
8168
- let state3 = "";
8811
+ let state5 = "";
8169
8812
  let stateData = null;
8170
8813
  let stateBytes = 0;
8171
8814
  let largestState = [];
@@ -8174,7 +8817,7 @@ async function renderCore(input, options) {
8174
8817
  const collected = collectTransfer();
8175
8818
  if (collected) {
8176
8819
  const built = buildTransfer(collected, options.maxStateBytes ?? DEFAULT_TRANSFER_MAX_BYTES, options.nonce);
8177
- state3 = built.script;
8820
+ state5 = built.script;
8178
8821
  stateData = built.data;
8179
8822
  stateBytes = built.bytes;
8180
8823
  largestState = built.largest;
@@ -8195,7 +8838,7 @@ async function renderCore(input, options) {
8195
8838
  if (DEV && options.log) {
8196
8839
  console.info(formatRenderDiagnostics(diagnostics, typeof options.log === "string" ? options.log : void 0));
8197
8840
  }
8198
- return { content, result: { head, state: state3, stateData, status: 200, diagnostics } };
8841
+ return { content, result: { head, state: state5, stateData, status: 200, diagnostics } };
8199
8842
  }
8200
8843
  function inRequest(api, fn) {
8201
8844
  try {
@@ -8733,12 +9376,12 @@ async function hydrateAdopt(component, container, options = {}) {
8733
9376
  inScope(rootScope, () => session.place(container, tree));
8734
9377
  }
8735
9378
  const deadline = Date.now() + (options.timeout ?? 1e4);
8736
- const { pending, wake } = hydration;
9379
+ const { pending: pending2, wake } = hydration;
8737
9380
  for (; ; ) {
8738
9381
  await macrotask2();
8739
- if (pending.size === 0) {
9382
+ if (pending2.size === 0) {
8740
9383
  await macrotask2();
8741
- if (pending.size === 0) break;
9384
+ if (pending2.size === 0) break;
8742
9385
  continue;
8743
9386
  }
8744
9387
  const remaining = deadline - Date.now();
@@ -9201,12 +9844,12 @@ function escapeScriptJson(json) {
9201
9844
  }
9202
9845
  var DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
9203
9846
  var SAFE_GLOBAL_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
9204
- function serializeState(state3, nonce, options) {
9847
+ function serializeState(state5, nonce, options) {
9205
9848
  const globalName = options?.global ?? SSR_DATA_ATTR;
9206
9849
  if (!SAFE_GLOBAL_NAME.test(globalName)) {
9207
9850
  throw new Error(`[SibuJS SSR] serializeState: global must be an identifier (got: ${JSON.stringify(globalName)})`);
9208
9851
  }
9209
- const rawJson = JSON.stringify(state3);
9852
+ const rawJson = JSON.stringify(state5);
9210
9853
  const maxBytes = options?.maxBytes ?? DEFAULT_MAX_SSR_BYTES;
9211
9854
  const byteLen = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(rawJson).byteLength : Buffer.byteLength(rawJson, "utf8");
9212
9855
  if (byteLen > maxBytes) {
@@ -9272,17 +9915,17 @@ function renderRoute(routes, url, options = {}) {
9272
9915
  const started = Date.now();
9273
9916
  const deadline = started + (options.timeout ?? DEFAULT_SETTLE_TIMEOUT);
9274
9917
  const router = createRouter(routes, { ...options.router, mode: "memory", url });
9275
- const wrap = (body, headHtml, state3) => options.document ? assembleDocument(body, options.document, headHtml, state3) : body;
9918
+ const wrap = (body, headHtml, state5) => options.document ? assembleDocument(body, options.document, headHtml, state5) : body;
9276
9919
  const timedOut = (phase) => {
9277
9920
  const now = Date.now();
9278
- const pending = [
9921
+ const pending2 = [
9279
9922
  `${phase} (${now - started} ms)`,
9280
9923
  ...Array.from(getRequestStore()?.pending ?? [], (work) => `${work.label} (${now - work.since} ms)`)
9281
9924
  ];
9282
- const diagnostics = { ...emptyDiagnostics(), timedOut: true, pending, durationMs: now - started };
9925
+ const diagnostics = { ...emptyDiagnostics(), timedOut: true, pending: pending2, durationMs: now - started };
9283
9926
  if (DEV) {
9284
9927
  console.warn(
9285
- `[SibuJS SSR] ${url}: the route did not render within ${options.timeout ?? DEFAULT_SETTLE_TIMEOUT} ms; pending: ${pending.join(", ")}`
9928
+ `[SibuJS SSR] ${url}: the route did not render within ${options.timeout ?? DEFAULT_SETTLE_TIMEOUT} ms; pending: ${pending2.join(", ")}`
9286
9929
  );
9287
9930
  }
9288
9931
  return {
@@ -9832,14 +10475,14 @@ function mockRouter(initialPath = "/") {
9832
10475
  return { currentPath, navigate, history: history2 };
9833
10476
  }
9834
10477
  function mockStore(initialState) {
9835
- let state3 = { ...initialState };
10478
+ let state5 = { ...initialState };
9836
10479
  return {
9837
- getState: () => ({ ...state3 }),
10480
+ getState: () => ({ ...state5 }),
9838
10481
  setState: (patch) => {
9839
- state3 = { ...state3, ...patch };
10482
+ state5 = { ...state5, ...patch };
9840
10483
  },
9841
10484
  reset: () => {
9842
- state3 = { ...initialState };
10485
+ state5 = { ...initialState };
9843
10486
  }
9844
10487
  };
9845
10488
  }