@pyreon/router 0.12.12 → 0.12.14
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 +53 -30
- 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/router.ts +107 -33
- package/src/scroll.ts +6 -1
- package/src/tests/loader.test.ts +52 -2
- package/src/tests/router.browser.test.tsx +442 -0
- package/src/tests/router.test.ts +253 -8
- 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":"d7a354cf-1","name":"loader.ts"},{"uid":"d7a354cf-3","name":"match.ts"},{"uid":"d7a354cf-5","name":"scroll.ts"},{"uid":"d7a354cf-7","name":"types.ts"},{"uid":"d7a354cf-9","name":"router.ts"},{"uid":"d7a354cf-11","name":"components.tsx"},{"uid":"d7a354cf-13","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"d7a354cf-1":{"renderedLength":2855,"gzipLength":1243,"brotliLength":0,"metaUid":"d7a354cf-0"},"d7a354cf-3":{"renderedLength":12203,"gzipLength":3691,"brotliLength":0,"metaUid":"d7a354cf-2"},"d7a354cf-5":{"renderedLength":1906,"gzipLength":781,"brotliLength":0,"metaUid":"d7a354cf-4"},"d7a354cf-7":{"renderedLength":385,"gzipLength":246,"brotliLength":0,"metaUid":"d7a354cf-6"},"d7a354cf-9":{"renderedLength":23190,"gzipLength":6358,"brotliLength":0,"metaUid":"d7a354cf-8"},"d7a354cf-11":{"renderedLength":7106,"gzipLength":2622,"brotliLength":0,"metaUid":"d7a354cf-10"},"d7a354cf-13":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"d7a354cf-12"}},"nodeMetas":{"d7a354cf-0":{"id":"/src/loader.ts","moduleParts":{"index.js":"d7a354cf-1"},"imported":[{"uid":"d7a354cf-14"}],"importedBy":[{"uid":"d7a354cf-12"},{"uid":"d7a354cf-10"}]},"d7a354cf-2":{"id":"/src/match.ts","moduleParts":{"index.js":"d7a354cf-3"},"imported":[],"importedBy":[{"uid":"d7a354cf-12"},{"uid":"d7a354cf-8"}]},"d7a354cf-4":{"id":"/src/scroll.ts","moduleParts":{"index.js":"d7a354cf-5"},"imported":[],"importedBy":[{"uid":"d7a354cf-8"}]},"d7a354cf-6":{"id":"/src/types.ts","moduleParts":{"index.js":"d7a354cf-7"},"imported":[],"importedBy":[{"uid":"d7a354cf-12"},{"uid":"d7a354cf-8"}]},"d7a354cf-8":{"id":"/src/router.ts","moduleParts":{"index.js":"d7a354cf-9"},"imported":[{"uid":"d7a354cf-14"},{"uid":"d7a354cf-15"},{"uid":"d7a354cf-2"},{"uid":"d7a354cf-4"},{"uid":"d7a354cf-6"}],"importedBy":[{"uid":"d7a354cf-12"},{"uid":"d7a354cf-10"}]},"d7a354cf-10":{"id":"/src/components.tsx","moduleParts":{"index.js":"d7a354cf-11"},"imported":[{"uid":"d7a354cf-14"},{"uid":"d7a354cf-0"},{"uid":"d7a354cf-8"}],"importedBy":[{"uid":"d7a354cf-12"}]},"d7a354cf-12":{"id":"/src/index.ts","moduleParts":{"index.js":"d7a354cf-13"},"imported":[{"uid":"d7a354cf-10"},{"uid":"d7a354cf-0"},{"uid":"d7a354cf-2"},{"uid":"d7a354cf-8"},{"uid":"d7a354cf-6"}],"importedBy":[],"isEntry":true},"d7a354cf-14":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"d7a354cf-10"},{"uid":"d7a354cf-0"},{"uid":"d7a354cf-8"}]},"d7a354cf-15":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"d7a354cf-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
|
@@ -506,6 +506,7 @@ 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;
|
|
509
510
|
this._positions.set(fromPath, window.scrollY);
|
|
510
511
|
}
|
|
511
512
|
/** Call after navigation is committed — applies scroll behavior */
|
|
@@ -519,6 +520,7 @@ var ScrollManager = class {
|
|
|
519
520
|
this._applyResult(behavior, to.path);
|
|
520
521
|
}
|
|
521
522
|
_applyResult(result, toPath) {
|
|
523
|
+
if (typeof window === "undefined") return;
|
|
522
524
|
const hashIdx = toPath.indexOf("#");
|
|
523
525
|
if (hashIdx >= 0) {
|
|
524
526
|
const id = toPath.slice(hashIdx + 1);
|
|
@@ -588,12 +590,12 @@ function setActiveRouter(router) {
|
|
|
588
590
|
}
|
|
589
591
|
function useRouter() {
|
|
590
592
|
const router = useContext(RouterContext) ?? _activeRouter;
|
|
591
|
-
if (!router) throw new Error("[
|
|
593
|
+
if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
|
|
592
594
|
return router;
|
|
593
595
|
}
|
|
594
596
|
function useRoute() {
|
|
595
597
|
const router = useContext(RouterContext) ?? _activeRouter;
|
|
596
|
-
if (!router) throw new Error("[
|
|
598
|
+
if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
|
|
597
599
|
return router.currentRoute;
|
|
598
600
|
}
|
|
599
601
|
/**
|
|
@@ -608,7 +610,7 @@ function useRoute() {
|
|
|
608
610
|
*/
|
|
609
611
|
function onBeforeRouteLeave(guard) {
|
|
610
612
|
const router = useContext(RouterContext) ?? _activeRouter;
|
|
611
|
-
if (!router) throw new Error("[
|
|
613
|
+
if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
|
|
612
614
|
const currentMatched = router.currentRoute().matched;
|
|
613
615
|
const wrappedGuard = (to, from) => {
|
|
614
616
|
if (!from.matched.some((r) => currentMatched.includes(r))) return void 0;
|
|
@@ -630,7 +632,7 @@ function onBeforeRouteLeave(guard) {
|
|
|
630
632
|
*/
|
|
631
633
|
function onBeforeRouteUpdate(guard) {
|
|
632
634
|
const router = useContext(RouterContext) ?? _activeRouter;
|
|
633
|
-
if (!router) throw new Error("[
|
|
635
|
+
if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
|
|
634
636
|
const currentMatched = router.currentRoute().matched;
|
|
635
637
|
const wrappedGuard = (to, from) => {
|
|
636
638
|
if (!to.matched.some((r) => currentMatched.includes(r))) return void 0;
|
|
@@ -656,7 +658,7 @@ function onBeforeRouteUpdate(guard) {
|
|
|
656
658
|
*/
|
|
657
659
|
function useBlocker(fn) {
|
|
658
660
|
const router = useContext(RouterContext) ?? _activeRouter;
|
|
659
|
-
if (!router) throw new Error("[
|
|
661
|
+
if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
|
|
660
662
|
router._blockers.add(fn);
|
|
661
663
|
const beforeUnloadHandler = _isBrowser ? (e) => {
|
|
662
664
|
e.preventDefault();
|
|
@@ -699,7 +701,7 @@ function useBlocker(fn) {
|
|
|
699
701
|
*/
|
|
700
702
|
function useIsActive(path, exact = false) {
|
|
701
703
|
const router = useContext(RouterContext) ?? _activeRouter;
|
|
702
|
-
if (!router) throw new Error("[
|
|
704
|
+
if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
|
|
703
705
|
return () => {
|
|
704
706
|
const current = router.currentRoute().path;
|
|
705
707
|
if (exact) return matchSegments(current, path, true);
|
|
@@ -804,7 +806,7 @@ function useTypedSearchParams(schema) {
|
|
|
804
806
|
}
|
|
805
807
|
function _getRouter() {
|
|
806
808
|
const router = useContext(RouterContext) ?? _activeRouter;
|
|
807
|
-
if (!router) throw new Error("[
|
|
809
|
+
if (!router) throw new Error("[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.");
|
|
808
810
|
return router;
|
|
809
811
|
}
|
|
810
812
|
/**
|
|
@@ -867,15 +869,10 @@ function createRouter(options) {
|
|
|
867
869
|
};
|
|
868
870
|
const currentPath = signal(normalizeTrailingSlash(getInitialLocation(), trailingSlash));
|
|
869
871
|
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
|
-
}
|
|
872
|
+
const _popstateHandler = _isBrowser && mode === "history" ? () => currentPath.set(getCurrentLocation()) : null;
|
|
873
|
+
const _hashchangeHandler = _isBrowser && mode !== "history" ? () => currentPath.set(getCurrentLocation()) : null;
|
|
874
|
+
if (_popstateHandler) window.addEventListener("popstate", _popstateHandler);
|
|
875
|
+
if (_hashchangeHandler) window.addEventListener("hashchange", _hashchangeHandler);
|
|
879
876
|
const componentCache = /* @__PURE__ */ new Map();
|
|
880
877
|
const loadingSignal = signal(0);
|
|
881
878
|
async function evaluateGuard(guard, to, from, gen) {
|
|
@@ -984,7 +981,7 @@ function createRouter(options) {
|
|
|
984
981
|
if (swr.length > 0) revalidateSwrLoaders(swr, to, ac);
|
|
985
982
|
return true;
|
|
986
983
|
}
|
|
987
|
-
function commitNavigation(path, replace, to, from) {
|
|
984
|
+
async function commitNavigation(path, replace, to, from) {
|
|
988
985
|
scrollManager.save(from.path);
|
|
989
986
|
const doCommit = () => {
|
|
990
987
|
currentPath.set(path);
|
|
@@ -992,10 +989,18 @@ function createRouter(options) {
|
|
|
992
989
|
if (_isBrowser && to.meta.title) document.title = to.meta.title;
|
|
993
990
|
for (const record of router._loaderData.keys()) if (!to.matched.includes(record)) router._loaderData.delete(record);
|
|
994
991
|
};
|
|
995
|
-
if (_isBrowser && to.meta.viewTransition !== false && typeof document.startViewTransition === "function")
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
992
|
+
if (_isBrowser && to.meta.viewTransition !== false && typeof document.startViewTransition === "function") {
|
|
993
|
+
const vt = document.startViewTransition(() => {
|
|
994
|
+
doCommit();
|
|
995
|
+
});
|
|
996
|
+
if (vt) {
|
|
997
|
+
vt.ready?.catch(() => {});
|
|
998
|
+
vt.finished?.catch(() => {});
|
|
999
|
+
if (vt.updateCallbackDone) try {
|
|
1000
|
+
await vt.updateCallbackDone;
|
|
1001
|
+
} catch {}
|
|
1002
|
+
}
|
|
1003
|
+
} else doCommit();
|
|
999
1004
|
for (const hook of afterHooks) try {
|
|
1000
1005
|
hook(to, from);
|
|
1001
1006
|
} catch (err) {
|
|
@@ -1071,7 +1076,7 @@ function createRouter(options) {
|
|
|
1071
1076
|
loadingSignal.update((n) => n - 1);
|
|
1072
1077
|
return;
|
|
1073
1078
|
}
|
|
1074
|
-
commitNavigation(path, replace, to, from);
|
|
1079
|
+
await commitNavigation(path, replace, to, from);
|
|
1075
1080
|
loadingSignal.update((n) => n - 1);
|
|
1076
1081
|
}
|
|
1077
1082
|
let _readyResolve = null;
|
|
@@ -1133,15 +1138,33 @@ function createRouter(options) {
|
|
|
1133
1138
|
isReady() {
|
|
1134
1139
|
return router._readyPromise;
|
|
1135
1140
|
},
|
|
1141
|
+
async preload(path) {
|
|
1142
|
+
const resolved = resolveRoute(path, routes);
|
|
1143
|
+
await Promise.all(resolved.matched.map(async (record) => {
|
|
1144
|
+
if (componentCache.has(record)) return;
|
|
1145
|
+
const raw = record.component;
|
|
1146
|
+
if (!isLazy(raw)) {
|
|
1147
|
+
componentCache.set(record, raw);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
const mod = await raw.loader();
|
|
1151
|
+
const comp = typeof mod === "function" ? mod : mod.default;
|
|
1152
|
+
componentCache.set(record, comp);
|
|
1153
|
+
}));
|
|
1154
|
+
const ac = new AbortController();
|
|
1155
|
+
router._abortController = ac;
|
|
1156
|
+
await Promise.all(resolved.matched.filter((r) => r.loader).map(async (r) => {
|
|
1157
|
+
const data = await r.loader?.({
|
|
1158
|
+
params: resolved.params,
|
|
1159
|
+
query: resolved.query,
|
|
1160
|
+
signal: ac.signal
|
|
1161
|
+
});
|
|
1162
|
+
router._loaderData.set(r, data);
|
|
1163
|
+
}));
|
|
1164
|
+
},
|
|
1136
1165
|
destroy() {
|
|
1137
|
-
if (_popstateHandler)
|
|
1138
|
-
|
|
1139
|
-
_popstateHandler = null;
|
|
1140
|
-
}
|
|
1141
|
-
if (_hashchangeHandler) {
|
|
1142
|
-
window.removeEventListener("hashchange", _hashchangeHandler);
|
|
1143
|
-
_hashchangeHandler = null;
|
|
1144
|
-
}
|
|
1166
|
+
if (_popstateHandler) window.removeEventListener("popstate", _popstateHandler);
|
|
1167
|
+
if (_hashchangeHandler) window.removeEventListener("hashchange", _hashchangeHandler);
|
|
1145
1168
|
guards.length = 0;
|
|
1146
1169
|
afterHooks.length = 0;
|
|
1147
1170
|
router._blockers.clear();
|