@pyreon/router 0.12.4 → 0.12.5
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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +173 -8
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +115 -5
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/components.tsx +21 -4
- package/src/index.ts +5 -0
- package/src/router.ts +187 -13
- package/src/scroll.ts +20 -0
- package/src/tests/router.test.ts +283 -0
- package/src/types.ts +44 -4
|
@@ -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":"cec65230-1","name":"loader.ts"},{"uid":"cec65230-3","name":"match.ts"},{"uid":"cec65230-5","name":"scroll.ts"},{"uid":"cec65230-7","name":"types.ts"},{"uid":"cec65230-9","name":"router.ts"},{"uid":"cec65230-11","name":"components.tsx"},{"uid":"cec65230-13","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"cec65230-1":{"renderedLength":2855,"gzipLength":1243,"brotliLength":0,"metaUid":"cec65230-0"},"cec65230-3":{"renderedLength":12203,"gzipLength":3691,"brotliLength":0,"metaUid":"cec65230-2"},"cec65230-5":{"renderedLength":1816,"gzipLength":765,"brotliLength":0,"metaUid":"cec65230-4"},"cec65230-7":{"renderedLength":385,"gzipLength":246,"brotliLength":0,"metaUid":"cec65230-6"},"cec65230-9":{"renderedLength":22377,"gzipLength":6164,"brotliLength":0,"metaUid":"cec65230-8"},"cec65230-11":{"renderedLength":6957,"gzipLength":2564,"brotliLength":0,"metaUid":"cec65230-10"},"cec65230-13":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"cec65230-12"}},"nodeMetas":{"cec65230-0":{"id":"/src/loader.ts","moduleParts":{"index.js":"cec65230-1"},"imported":[{"uid":"cec65230-14"}],"importedBy":[{"uid":"cec65230-12"},{"uid":"cec65230-10"}]},"cec65230-2":{"id":"/src/match.ts","moduleParts":{"index.js":"cec65230-3"},"imported":[],"importedBy":[{"uid":"cec65230-12"},{"uid":"cec65230-8"}]},"cec65230-4":{"id":"/src/scroll.ts","moduleParts":{"index.js":"cec65230-5"},"imported":[],"importedBy":[{"uid":"cec65230-8"}]},"cec65230-6":{"id":"/src/types.ts","moduleParts":{"index.js":"cec65230-7"},"imported":[],"importedBy":[{"uid":"cec65230-12"},{"uid":"cec65230-8"}]},"cec65230-8":{"id":"/src/router.ts","moduleParts":{"index.js":"cec65230-9"},"imported":[{"uid":"cec65230-14"},{"uid":"cec65230-15"},{"uid":"cec65230-2"},{"uid":"cec65230-4"},{"uid":"cec65230-6"}],"importedBy":[{"uid":"cec65230-12"},{"uid":"cec65230-10"}]},"cec65230-10":{"id":"/src/components.tsx","moduleParts":{"index.js":"cec65230-11"},"imported":[{"uid":"cec65230-14"},{"uid":"cec65230-0"},{"uid":"cec65230-8"}],"importedBy":[{"uid":"cec65230-12"}]},"cec65230-12":{"id":"/src/index.ts","moduleParts":{"index.js":"cec65230-13"},"imported":[{"uid":"cec65230-10"},{"uid":"cec65230-0"},{"uid":"cec65230-2"},{"uid":"cec65230-8"},{"uid":"cec65230-6"}],"importedBy":[],"isEntry":true},"cec65230-14":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"cec65230-10"},{"uid":"cec65230-0"},{"uid":"cec65230-8"}]},"cec65230-15":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"cec65230-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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, createRef, h, onUnmount, provide, useContext } from "@pyreon/core";
|
|
1
|
+
import { ErrorBoundary, createContext, createRef, h, onUnmount, provide, useContext } from "@pyreon/core";
|
|
2
2
|
import { computed, signal } from "@pyreon/reactivity";
|
|
3
3
|
|
|
4
4
|
//#region src/loader.ts
|
|
@@ -519,6 +519,22 @@ var ScrollManager = class {
|
|
|
519
519
|
this._applyResult(behavior, to.path);
|
|
520
520
|
}
|
|
521
521
|
_applyResult(result, toPath) {
|
|
522
|
+
const hashIdx = toPath.indexOf("#");
|
|
523
|
+
if (hashIdx >= 0) {
|
|
524
|
+
const id = toPath.slice(hashIdx + 1);
|
|
525
|
+
if (id) {
|
|
526
|
+
requestAnimationFrame(() => {
|
|
527
|
+
const el = document.getElementById(id);
|
|
528
|
+
if (el) {
|
|
529
|
+
el.scrollIntoView({ behavior: "smooth" });
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const namedEl = document.querySelector(`[name="${CSS.escape(id)}"]`);
|
|
533
|
+
if (namedEl) namedEl.scrollIntoView({ behavior: "smooth" });
|
|
534
|
+
});
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
522
538
|
if (result === "none") return;
|
|
523
539
|
if (result === "top" || result === void 0) {
|
|
524
540
|
window.scrollTo({
|
|
@@ -702,9 +718,29 @@ function matchSegments(current, pattern, exact) {
|
|
|
702
718
|
if (ps.length > cs.length) return false;
|
|
703
719
|
return ps.every((seg, i) => seg.startsWith(":") || seg === cs[i]);
|
|
704
720
|
}
|
|
721
|
+
/**
|
|
722
|
+
* Read and write URL search params reactively.
|
|
723
|
+
*
|
|
724
|
+
* @example Basic (untyped)
|
|
725
|
+
* ```ts
|
|
726
|
+
* const [params, setParams] = useSearchParams({ page: "1" })
|
|
727
|
+
* params().page // "1"
|
|
728
|
+
* setParams({ page: "2" }) // updates URL
|
|
729
|
+
* ```
|
|
730
|
+
*
|
|
731
|
+
* @example Typed with schema
|
|
732
|
+
* ```ts
|
|
733
|
+
* const [params, setParams] = useSearchParams({
|
|
734
|
+
* page: 'number',
|
|
735
|
+
* sort: 'string',
|
|
736
|
+
* desc: 'boolean',
|
|
737
|
+
* })
|
|
738
|
+
* params().page // number (auto-coerced)
|
|
739
|
+
* params().desc // boolean
|
|
740
|
+
* ```
|
|
741
|
+
*/
|
|
705
742
|
function useSearchParams(defaults) {
|
|
706
|
-
const router =
|
|
707
|
-
if (!router) throw new Error("[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.");
|
|
743
|
+
const router = _getRouter();
|
|
708
744
|
const get = () => {
|
|
709
745
|
const query = router.currentRoute().query;
|
|
710
746
|
if (!defaults) return query;
|
|
@@ -723,6 +759,90 @@ function useSearchParams(defaults) {
|
|
|
723
759
|
};
|
|
724
760
|
return [get, set];
|
|
725
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Typed search params with auto-coercion.
|
|
764
|
+
*
|
|
765
|
+
* Schema values define the type: `'string'`, `'number'`, or `'boolean'`.
|
|
766
|
+
* Query string values are automatically coerced to the declared type.
|
|
767
|
+
*
|
|
768
|
+
* @example
|
|
769
|
+
* ```ts
|
|
770
|
+
* const [params, setParams] = useTypedSearchParams({
|
|
771
|
+
* page: 'number',
|
|
772
|
+
* sort: 'string',
|
|
773
|
+
* desc: 'boolean',
|
|
774
|
+
* })
|
|
775
|
+
* params().page // number (coerced from "3" → 3)
|
|
776
|
+
* params().desc // boolean (coerced from "true" → true)
|
|
777
|
+
* setParams({ page: 2 }) // updates URL with ?page=2
|
|
778
|
+
* ```
|
|
779
|
+
*/
|
|
780
|
+
function useTypedSearchParams(schema) {
|
|
781
|
+
const router = _getRouter();
|
|
782
|
+
const get = () => {
|
|
783
|
+
const query = router.currentRoute().query;
|
|
784
|
+
const result = {};
|
|
785
|
+
for (const [key, type] of Object.entries(schema)) {
|
|
786
|
+
const raw = query[key];
|
|
787
|
+
if (type === "number") result[key] = raw !== void 0 ? Number(raw) : 0;
|
|
788
|
+
else if (type === "boolean") result[key] = raw === "true" || raw === "1";
|
|
789
|
+
else result[key] = raw ?? "";
|
|
790
|
+
}
|
|
791
|
+
return result;
|
|
792
|
+
};
|
|
793
|
+
const set = (updates) => {
|
|
794
|
+
const current = get();
|
|
795
|
+
const merged = {};
|
|
796
|
+
for (const [k, v] of Object.entries({
|
|
797
|
+
...current,
|
|
798
|
+
...updates
|
|
799
|
+
})) merged[k] = String(v);
|
|
800
|
+
const path = router.currentRoute().path + stringifyQuery(merged);
|
|
801
|
+
return router.replace(path);
|
|
802
|
+
};
|
|
803
|
+
return [get, set];
|
|
804
|
+
}
|
|
805
|
+
function _getRouter() {
|
|
806
|
+
const router = useContext(RouterContext) ?? _activeRouter;
|
|
807
|
+
if (!router) throw new Error("[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.");
|
|
808
|
+
return router;
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Returns true while a navigation is in progress (guards + loaders running).
|
|
812
|
+
* Use this to show loading indicators during route transitions.
|
|
813
|
+
*
|
|
814
|
+
* @example
|
|
815
|
+
* ```tsx
|
|
816
|
+
* const isNavigating = useTransition()
|
|
817
|
+
* <Show when={isNavigating}>
|
|
818
|
+
* <LoadingBar />
|
|
819
|
+
* </Show>
|
|
820
|
+
* ```
|
|
821
|
+
*/
|
|
822
|
+
function useTransition() {
|
|
823
|
+
const router = _getRouter();
|
|
824
|
+
return () => router._loadingSignal() > 0;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Read data accumulated by route middleware.
|
|
828
|
+
*
|
|
829
|
+
* @example
|
|
830
|
+
* ```ts
|
|
831
|
+
* // In middleware:
|
|
832
|
+
* const authMiddleware: RouteMiddleware = async (ctx) => {
|
|
833
|
+
* ctx.data.user = await getUser(ctx.to)
|
|
834
|
+
* if (!ctx.data.user) return '/login'
|
|
835
|
+
* }
|
|
836
|
+
*
|
|
837
|
+
* // In component:
|
|
838
|
+
* const data = useMiddlewareData()
|
|
839
|
+
* const user = () => data().user as User
|
|
840
|
+
* ```
|
|
841
|
+
*/
|
|
842
|
+
function useMiddlewareData() {
|
|
843
|
+
const router = _getRouter();
|
|
844
|
+
return () => router.currentRoute()._middlewareData ?? {};
|
|
845
|
+
}
|
|
726
846
|
function createRouter(options) {
|
|
727
847
|
const opts = Array.isArray(options) ? { routes: options } : options;
|
|
728
848
|
const { routes, mode = "hash", scrollBehavior, onError, maxCacheSize = 100, trailingSlash = "strip" } = opts;
|
|
@@ -866,10 +986,16 @@ function createRouter(options) {
|
|
|
866
986
|
}
|
|
867
987
|
function commitNavigation(path, replace, to, from) {
|
|
868
988
|
scrollManager.save(from.path);
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
989
|
+
const doCommit = () => {
|
|
990
|
+
currentPath.set(path);
|
|
991
|
+
syncBrowserUrl(path, replace);
|
|
992
|
+
if (_isBrowser && to.meta.title) document.title = to.meta.title;
|
|
993
|
+
for (const record of router._loaderData.keys()) if (!to.matched.includes(record)) router._loaderData.delete(record);
|
|
994
|
+
};
|
|
995
|
+
if (_isBrowser && to.meta.viewTransition !== false && typeof document.startViewTransition === "function") document.startViewTransition(() => {
|
|
996
|
+
doCommit();
|
|
997
|
+
});
|
|
998
|
+
else doCommit();
|
|
873
999
|
for (const hook of afterHooks) try {
|
|
874
1000
|
hook(to, from);
|
|
875
1001
|
} catch (err) {
|
|
@@ -884,6 +1010,29 @@ function createRouter(options) {
|
|
|
884
1010
|
}
|
|
885
1011
|
return "continue";
|
|
886
1012
|
}
|
|
1013
|
+
/** Run per-route middleware chain. Middleware from all matched routes execute in order. */
|
|
1014
|
+
async function runMiddleware(to, from, gen) {
|
|
1015
|
+
const ctx = {
|
|
1016
|
+
to,
|
|
1017
|
+
from,
|
|
1018
|
+
data: {}
|
|
1019
|
+
};
|
|
1020
|
+
for (const record of to.matched) {
|
|
1021
|
+
if (!record.middleware) continue;
|
|
1022
|
+
const mws = Array.isArray(record.middleware) ? record.middleware : [record.middleware];
|
|
1023
|
+
for (const mw of mws) {
|
|
1024
|
+
if (gen !== _navGen) return { action: "cancel" };
|
|
1025
|
+
const result = await mw(ctx);
|
|
1026
|
+
if (result === false) return { action: "cancel" };
|
|
1027
|
+
if (typeof result === "string") return {
|
|
1028
|
+
action: "redirect",
|
|
1029
|
+
target: result
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
to._middlewareData = ctx.data;
|
|
1034
|
+
return { action: "continue" };
|
|
1035
|
+
}
|
|
887
1036
|
async function navigate(rawPath, replace, redirectDepth = 0) {
|
|
888
1037
|
if (redirectDepth > 10) {
|
|
889
1038
|
if (__DEV__) console.warn(`[Pyreon] Navigation to "${rawPath}" aborted: redirect depth exceeded 10 levels. This likely indicates a redirect loop in your route configuration.`);
|
|
@@ -903,6 +1052,12 @@ function createRouter(options) {
|
|
|
903
1052
|
loadingSignal.update((n) => n - 1);
|
|
904
1053
|
return;
|
|
905
1054
|
}
|
|
1055
|
+
const mwResult = await runMiddleware(to, from, gen);
|
|
1056
|
+
if (mwResult.action !== "continue") {
|
|
1057
|
+
loadingSignal.update((n) => n - 1);
|
|
1058
|
+
if (mwResult.action === "redirect") return navigate(sanitizePath(mwResult.target), replace, redirectDepth + 1);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
906
1061
|
const guardOutcome = await runAllGuards(to, from, gen);
|
|
907
1062
|
if (guardOutcome.action !== "continue") {
|
|
908
1063
|
loadingSignal.update((n) => n - 1);
|
|
@@ -1217,7 +1372,17 @@ function renderWithLoader(router, record, Comp, route) {
|
|
|
1217
1372
|
query: route.query,
|
|
1218
1373
|
meta: route.meta
|
|
1219
1374
|
};
|
|
1375
|
+
if (record.errorComponent) return h(ErrorBoundary, {
|
|
1376
|
+
fallback: (error) => h(record.errorComponent, {
|
|
1377
|
+
...routeProps,
|
|
1378
|
+
error
|
|
1379
|
+
}),
|
|
1380
|
+
children: record.loader ? renderLoaderContent(router, record, Comp, routeProps) : h(Comp, routeProps)
|
|
1381
|
+
});
|
|
1220
1382
|
if (!record.loader) return h(Comp, routeProps);
|
|
1383
|
+
return renderLoaderContent(router, record, Comp, routeProps);
|
|
1384
|
+
}
|
|
1385
|
+
function renderLoaderContent(router, record, Comp, routeProps) {
|
|
1221
1386
|
const data = router._loaderData.get(record);
|
|
1222
1387
|
if (data === void 0 && record.errorComponent) return h(record.errorComponent, routeProps);
|
|
1223
1388
|
return h(LoaderDataProvider, {
|
|
@@ -1264,5 +1429,5 @@ function isStaleChunk(err) {
|
|
|
1264
1429
|
}
|
|
1265
1430
|
|
|
1266
1431
|
//#endregion
|
|
1267
|
-
export { RouterContext, RouterLink, RouterProvider, RouterView, buildPath, createRouter, findRouteByName, hydrateLoaderData, lazy, onBeforeRouteLeave, onBeforeRouteUpdate, parseQuery, parseQueryMulti, prefetchLoaderData, resolveRoute, serializeLoaderData, stringifyQuery, useBlocker, useIsActive, useLoaderData, useRoute, useRouter, useSearchParams };
|
|
1432
|
+
export { RouterContext, RouterLink, RouterProvider, RouterView, buildPath, createRouter, findRouteByName, hydrateLoaderData, lazy, onBeforeRouteLeave, onBeforeRouteUpdate, parseQuery, parseQueryMulti, prefetchLoaderData, resolveRoute, serializeLoaderData, stringifyQuery, useBlocker, useIsActive, useLoaderData, useMiddlewareData, useRoute, useRouter, useSearchParams, useTransition, useTypedSearchParams };
|
|
1268
1433
|
//# sourceMappingURL=index.js.map
|