@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 +14 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +59 -31
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +13 -0
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/loader.ts +4 -1
- package/src/router.ts +110 -33
- package/src/scroll.ts +19 -1
- package/src/tests/loader.test.ts +70 -2
- package/src/tests/router.browser.test.tsx +442 -0
- package/src/tests/router.test.ts +253 -8
- package/src/tests/scroll.test.ts +31 -0
- package/src/types.ts +13 -0
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":"
|
|
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("[
|
|
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("[
|
|
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("[
|
|
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("[
|
|
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("[
|
|
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("[
|
|
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("[
|
|
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
|
-
|
|
871
|
-
|
|
872
|
-
if (
|
|
873
|
-
|
|
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")
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
-
|
|
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();
|