@pyreon/router 0.12.13 → 0.12.15

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.
package/README.md CHANGED
@@ -98,3 +98,17 @@ const data = useLoaderData<typeof loader>()
98
98
  ### Types
99
99
 
100
100
  `ExtractParams`, `RouteMeta`, `ResolvedRoute`, `RouteRecord`, `RouterOptions`, `Router`, `NavigationGuard`, `AfterEachHook`, `ScrollBehaviorFn`, `LoaderContext`, `RouteLoaderFn`
101
+
102
+ ## View Transitions
103
+
104
+ Route changes are wrapped in `document.startViewTransition()` automatically when the browser supports it. Opt out per-route with `meta: { viewTransition: false }`.
105
+
106
+ `await router.push()` / `.replace()` resolves once the DOM has committed to the new route -- specifically, when the ViewTransition's `updateCallbackDone` promise settles. It does NOT wait for the full animation (`.finished`, 200-300ms), because blocking every programmatic navigation on an animation is unacceptable.
107
+
108
+ | Promise | Resolves when | Router awaits? |
109
+ | --- | --- | --- |
110
+ | `updateCallbackDone` | Callback done; DOM swapped; state live | yes |
111
+ | `ready` | Snapshot captured, pseudo-elements ready | no -- `.catch()` only |
112
+ | `finished` | Full animation completed | no -- `.catch()` only |
113
+
114
+ `afterEach` hooks and scroll restoration fire after the VT callback completes, so they observe the new route state when invoked.
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"a014a385-1","name":"loader.ts"},{"uid":"a014a385-3","name":"match.ts"},{"uid":"a014a385-5","name":"scroll.ts"},{"uid":"a014a385-7","name":"types.ts"},{"uid":"a014a385-9","name":"router.ts"},{"uid":"a014a385-11","name":"components.tsx"},{"uid":"a014a385-13","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"a014a385-1":{"renderedLength":2855,"gzipLength":1243,"brotliLength":0,"metaUid":"a014a385-0"},"a014a385-3":{"renderedLength":12203,"gzipLength":3691,"brotliLength":0,"metaUid":"a014a385-2"},"a014a385-5":{"renderedLength":1816,"gzipLength":765,"brotliLength":0,"metaUid":"a014a385-4"},"a014a385-7":{"renderedLength":385,"gzipLength":246,"brotliLength":0,"metaUid":"a014a385-6"},"a014a385-9":{"renderedLength":22335,"gzipLength":6159,"brotliLength":0,"metaUid":"a014a385-8"},"a014a385-11":{"renderedLength":7106,"gzipLength":2622,"brotliLength":0,"metaUid":"a014a385-10"},"a014a385-13":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"a014a385-12"}},"nodeMetas":{"a014a385-0":{"id":"/src/loader.ts","moduleParts":{"index.js":"a014a385-1"},"imported":[{"uid":"a014a385-14"}],"importedBy":[{"uid":"a014a385-12"},{"uid":"a014a385-10"}]},"a014a385-2":{"id":"/src/match.ts","moduleParts":{"index.js":"a014a385-3"},"imported":[],"importedBy":[{"uid":"a014a385-12"},{"uid":"a014a385-8"}]},"a014a385-4":{"id":"/src/scroll.ts","moduleParts":{"index.js":"a014a385-5"},"imported":[],"importedBy":[{"uid":"a014a385-8"}]},"a014a385-6":{"id":"/src/types.ts","moduleParts":{"index.js":"a014a385-7"},"imported":[],"importedBy":[{"uid":"a014a385-12"},{"uid":"a014a385-8"}]},"a014a385-8":{"id":"/src/router.ts","moduleParts":{"index.js":"a014a385-9"},"imported":[{"uid":"a014a385-14"},{"uid":"a014a385-15"},{"uid":"a014a385-2"},{"uid":"a014a385-4"},{"uid":"a014a385-6"}],"importedBy":[{"uid":"a014a385-12"},{"uid":"a014a385-10"}]},"a014a385-10":{"id":"/src/components.tsx","moduleParts":{"index.js":"a014a385-11"},"imported":[{"uid":"a014a385-14"},{"uid":"a014a385-0"},{"uid":"a014a385-8"}],"importedBy":[{"uid":"a014a385-12"}]},"a014a385-12":{"id":"/src/index.ts","moduleParts":{"index.js":"a014a385-13"},"imported":[{"uid":"a014a385-10"},{"uid":"a014a385-0"},{"uid":"a014a385-2"},{"uid":"a014a385-8"},{"uid":"a014a385-6"}],"importedBy":[],"isEntry":true},"a014a385-14":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"a014a385-10"},{"uid":"a014a385-0"},{"uid":"a014a385-8"}]},"a014a385-15":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"a014a385-8"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"792f9763-1","name":"loader.ts"},{"uid":"792f9763-3","name":"match.ts"},{"uid":"792f9763-5","name":"scroll.ts"},{"uid":"792f9763-7","name":"types.ts"},{"uid":"792f9763-9","name":"router.ts"},{"uid":"792f9763-11","name":"components.tsx"},{"uid":"792f9763-13","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"792f9763-1":{"renderedLength":2824,"gzipLength":1233,"brotliLength":0,"metaUid":"792f9763-0"},"792f9763-3":{"renderedLength":12203,"gzipLength":3691,"brotliLength":0,"metaUid":"792f9763-2"},"792f9763-5":{"renderedLength":2194,"gzipLength":899,"brotliLength":0,"metaUid":"792f9763-4"},"792f9763-7":{"renderedLength":385,"gzipLength":246,"brotliLength":0,"metaUid":"792f9763-6"},"792f9763-9":{"renderedLength":23157,"gzipLength":6355,"brotliLength":0,"metaUid":"792f9763-8"},"792f9763-11":{"renderedLength":7106,"gzipLength":2622,"brotliLength":0,"metaUid":"792f9763-10"},"792f9763-13":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"792f9763-12"}},"nodeMetas":{"792f9763-0":{"id":"/src/loader.ts","moduleParts":{"index.js":"792f9763-1"},"imported":[{"uid":"792f9763-14"}],"importedBy":[{"uid":"792f9763-12"},{"uid":"792f9763-10"}]},"792f9763-2":{"id":"/src/match.ts","moduleParts":{"index.js":"792f9763-3"},"imported":[],"importedBy":[{"uid":"792f9763-12"},{"uid":"792f9763-8"}]},"792f9763-4":{"id":"/src/scroll.ts","moduleParts":{"index.js":"792f9763-5"},"imported":[],"importedBy":[{"uid":"792f9763-8"}]},"792f9763-6":{"id":"/src/types.ts","moduleParts":{"index.js":"792f9763-7"},"imported":[],"importedBy":[{"uid":"792f9763-12"},{"uid":"792f9763-8"}]},"792f9763-8":{"id":"/src/router.ts","moduleParts":{"index.js":"792f9763-9"},"imported":[{"uid":"792f9763-14"},{"uid":"792f9763-15"},{"uid":"792f9763-2"},{"uid":"792f9763-4"},{"uid":"792f9763-6"}],"importedBy":[{"uid":"792f9763-12"},{"uid":"792f9763-10"}]},"792f9763-10":{"id":"/src/components.tsx","moduleParts":{"index.js":"792f9763-11"},"imported":[{"uid":"792f9763-14"},{"uid":"792f9763-0"},{"uid":"792f9763-8"}],"importedBy":[{"uid":"792f9763-12"}]},"792f9763-12":{"id":"/src/index.ts","moduleParts":{"index.js":"792f9763-13"},"imported":[{"uid":"792f9763-10"},{"uid":"792f9763-0"},{"uid":"792f9763-2"},{"uid":"792f9763-8"},{"uid":"792f9763-6"}],"importedBy":[],"isEntry":true},"792f9763-14":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"792f9763-10"},{"uid":"792f9763-0"},{"uid":"792f9763-8"}]},"792f9763-15":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"792f9763-8"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -34,7 +34,6 @@ function useLoaderData() {
34
34
  async function prefetchLoaderData(router, path) {
35
35
  const route = router._resolve(path);
36
36
  const ac = new AbortController();
37
- router._abortController = ac;
38
37
  await Promise.all(route.matched.filter((r) => r.loader).map(async (r) => {
39
38
  const data = await r.loader?.({
40
39
  params: route.params,
@@ -498,6 +497,7 @@ function buildNameIndex(routes) {
498
497
  * Saves scroll position before each navigation and restores it when
499
498
  * navigating back to a previously visited path.
500
499
  */
500
+ const MAX_SCROLL_POSITIONS = 100;
501
501
  var ScrollManager = class {
502
502
  _positions = /* @__PURE__ */ new Map();
503
503
  _behavior;
@@ -506,7 +506,14 @@ var ScrollManager = class {
506
506
  }
507
507
  /** Call before navigating away — saves current scroll position for `fromPath` */
508
508
  save(fromPath) {
509
+ if (typeof window === "undefined") return;
510
+ if (this._positions.has(fromPath)) this._positions.delete(fromPath);
509
511
  this._positions.set(fromPath, window.scrollY);
512
+ while (this._positions.size > MAX_SCROLL_POSITIONS) {
513
+ const oldest = this._positions.keys().next().value;
514
+ if (oldest === void 0) break;
515
+ this._positions.delete(oldest);
516
+ }
510
517
  }
511
518
  /** Call after navigation is committed — applies scroll behavior */
512
519
  restore(to, from) {
@@ -519,6 +526,7 @@ var ScrollManager = class {
519
526
  this._applyResult(behavior, to.path);
520
527
  }
521
528
  _applyResult(result, toPath) {
529
+ if (typeof window === "undefined") return;
522
530
  const hashIdx = toPath.indexOf("#");
523
531
  if (hashIdx >= 0) {
524
532
  const id = toPath.slice(hashIdx + 1);
@@ -588,12 +596,12 @@ function setActiveRouter(router) {
588
596
  }
589
597
  function useRouter() {
590
598
  const router = useContext(RouterContext) ?? _activeRouter;
591
- if (!router) throw new Error("[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.");
599
+ if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
592
600
  return router;
593
601
  }
594
602
  function useRoute() {
595
603
  const router = useContext(RouterContext) ?? _activeRouter;
596
- if (!router) throw new Error("[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.");
604
+ if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
597
605
  return router.currentRoute;
598
606
  }
599
607
  /**
@@ -608,7 +616,7 @@ function useRoute() {
608
616
  */
609
617
  function onBeforeRouteLeave(guard) {
610
618
  const router = useContext(RouterContext) ?? _activeRouter;
611
- if (!router) throw new Error("[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.");
619
+ if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
612
620
  const currentMatched = router.currentRoute().matched;
613
621
  const wrappedGuard = (to, from) => {
614
622
  if (!from.matched.some((r) => currentMatched.includes(r))) return void 0;
@@ -630,7 +638,7 @@ function onBeforeRouteLeave(guard) {
630
638
  */
631
639
  function onBeforeRouteUpdate(guard) {
632
640
  const router = useContext(RouterContext) ?? _activeRouter;
633
- if (!router) throw new Error("[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.");
641
+ if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
634
642
  const currentMatched = router.currentRoute().matched;
635
643
  const wrappedGuard = (to, from) => {
636
644
  if (!to.matched.some((r) => currentMatched.includes(r))) return void 0;
@@ -656,7 +664,7 @@ function onBeforeRouteUpdate(guard) {
656
664
  */
657
665
  function useBlocker(fn) {
658
666
  const router = useContext(RouterContext) ?? _activeRouter;
659
- if (!router) throw new Error("[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.");
667
+ if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
660
668
  router._blockers.add(fn);
661
669
  const beforeUnloadHandler = _isBrowser ? (e) => {
662
670
  e.preventDefault();
@@ -699,7 +707,7 @@ function useBlocker(fn) {
699
707
  */
700
708
  function useIsActive(path, exact = false) {
701
709
  const router = useContext(RouterContext) ?? _activeRouter;
702
- if (!router) throw new Error("[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.");
710
+ if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
703
711
  return () => {
704
712
  const current = router.currentRoute().path;
705
713
  if (exact) return matchSegments(current, path, true);
@@ -804,7 +812,7 @@ function useTypedSearchParams(schema) {
804
812
  }
805
813
  function _getRouter() {
806
814
  const router = useContext(RouterContext) ?? _activeRouter;
807
- if (!router) throw new Error("[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.");
815
+ if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
808
816
  return router;
809
817
  }
810
818
  /**
@@ -867,15 +875,10 @@ function createRouter(options) {
867
875
  };
868
876
  const currentPath = signal(normalizeTrailingSlash(getInitialLocation(), trailingSlash));
869
877
  const currentRoute = computed(() => resolveRoute(currentPath(), routes));
870
- let _popstateHandler = null;
871
- let _hashchangeHandler = null;
872
- if (_isBrowser) if (mode === "history") {
873
- _popstateHandler = () => currentPath.set(getCurrentLocation());
874
- window.addEventListener("popstate", _popstateHandler);
875
- } else {
876
- _hashchangeHandler = () => currentPath.set(getCurrentLocation());
877
- window.addEventListener("hashchange", _hashchangeHandler);
878
- }
878
+ const _popstateHandler = _isBrowser && mode === "history" ? () => currentPath.set(getCurrentLocation()) : null;
879
+ const _hashchangeHandler = _isBrowser && mode !== "history" ? () => currentPath.set(getCurrentLocation()) : null;
880
+ if (_popstateHandler) window.addEventListener("popstate", _popstateHandler);
881
+ if (_hashchangeHandler) window.addEventListener("hashchange", _hashchangeHandler);
879
882
  const componentCache = /* @__PURE__ */ new Map();
880
883
  const loadingSignal = signal(0);
881
884
  async function evaluateGuard(guard, to, from, gen) {
@@ -984,7 +987,7 @@ function createRouter(options) {
984
987
  if (swr.length > 0) revalidateSwrLoaders(swr, to, ac);
985
988
  return true;
986
989
  }
987
- function commitNavigation(path, replace, to, from) {
990
+ async function commitNavigation(path, replace, to, from) {
988
991
  scrollManager.save(from.path);
989
992
  const doCommit = () => {
990
993
  currentPath.set(path);
@@ -992,10 +995,18 @@ function createRouter(options) {
992
995
  if (_isBrowser && to.meta.title) document.title = to.meta.title;
993
996
  for (const record of router._loaderData.keys()) if (!to.matched.includes(record)) router._loaderData.delete(record);
994
997
  };
995
- if (_isBrowser && to.meta.viewTransition !== false && typeof document.startViewTransition === "function") document.startViewTransition(() => {
996
- doCommit();
997
- });
998
- else doCommit();
998
+ if (_isBrowser && to.meta.viewTransition !== false && typeof document.startViewTransition === "function") {
999
+ const vt = document.startViewTransition(() => {
1000
+ doCommit();
1001
+ });
1002
+ if (vt) {
1003
+ vt.ready?.catch(() => {});
1004
+ vt.finished?.catch(() => {});
1005
+ if (vt.updateCallbackDone) try {
1006
+ await vt.updateCallbackDone;
1007
+ } catch {}
1008
+ }
1009
+ } else doCommit();
999
1010
  for (const hook of afterHooks) try {
1000
1011
  hook(to, from);
1001
1012
  } catch (err) {
@@ -1071,7 +1082,7 @@ function createRouter(options) {
1071
1082
  loadingSignal.update((n) => n - 1);
1072
1083
  return;
1073
1084
  }
1074
- commitNavigation(path, replace, to, from);
1085
+ await commitNavigation(path, replace, to, from);
1075
1086
  loadingSignal.update((n) => n - 1);
1076
1087
  }
1077
1088
  let _readyResolve = null;
@@ -1133,15 +1144,32 @@ function createRouter(options) {
1133
1144
  isReady() {
1134
1145
  return router._readyPromise;
1135
1146
  },
1147
+ async preload(path) {
1148
+ const resolved = resolveRoute(path, routes);
1149
+ await Promise.all(resolved.matched.map(async (record) => {
1150
+ if (componentCache.has(record)) return;
1151
+ const raw = record.component;
1152
+ if (!isLazy(raw)) {
1153
+ componentCache.set(record, raw);
1154
+ return;
1155
+ }
1156
+ const mod = await raw.loader();
1157
+ const comp = typeof mod === "function" ? mod : mod.default;
1158
+ componentCache.set(record, comp);
1159
+ }));
1160
+ const ac = new AbortController();
1161
+ await Promise.all(resolved.matched.filter((r) => r.loader).map(async (r) => {
1162
+ const data = await r.loader?.({
1163
+ params: resolved.params,
1164
+ query: resolved.query,
1165
+ signal: ac.signal
1166
+ });
1167
+ router._loaderData.set(r, data);
1168
+ }));
1169
+ },
1136
1170
  destroy() {
1137
- if (_popstateHandler) {
1138
- window.removeEventListener("popstate", _popstateHandler);
1139
- _popstateHandler = null;
1140
- }
1141
- if (_hashchangeHandler) {
1142
- window.removeEventListener("hashchange", _hashchangeHandler);
1143
- _hashchangeHandler = null;
1144
- }
1171
+ if (_popstateHandler) window.removeEventListener("popstate", _popstateHandler);
1172
+ if (_hashchangeHandler) window.removeEventListener("hashchange", _hashchangeHandler);
1145
1173
  guards.length = 0;
1146
1174
  afterHooks.length = 0;
1147
1175
  router._blockers.clear();