@solidjs/router 1.0.0 → 2.0.0-next.12
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 +606 -697
- package/dist/claims.d.ts +21 -0
- package/dist/claims.js +115 -0
- package/dist/data/action.d.ts +40 -11
- package/dist/data/action.js +303 -101
- package/dist/data/events.d.ts +8 -0
- package/dist/data/events.js +24 -23
- package/dist/data/index.d.ts +2 -4
- package/dist/data/index.js +2 -4
- package/dist/data/query.d.ts +1 -3
- package/dist/data/query.js +39 -22
- package/dist/data/serverForms.d.ts +1 -0
- package/dist/data/serverForms.js +5 -0
- package/dist/fs.d.ts +94 -0
- package/dist/fs.js +51 -0
- package/dist/index.d.ts +24 -3
- package/dist/index.js +1890 -1200
- package/dist/index.jsx +2 -2
- package/dist/lifecycle.d.ts +29 -4
- package/dist/lifecycle.js +40 -37
- package/dist/paths.d.ts +117 -0
- package/dist/paths.js +41 -0
- package/dist/routers/components.d.ts +12 -26
- package/dist/routers/components.jsx +68 -54
- package/dist/routers/factory.d.ts +121 -0
- package/dist/routers/factory.jsx +154 -0
- package/dist/routers/history.d.ts +26 -0
- package/dist/routers/history.js +180 -0
- package/dist/routers/index.d.ts +5 -11
- package/dist/routers/index.js +2 -6
- package/dist/routers/scrollRestoration.d.ts +10 -1
- package/dist/routers/scrollRestoration.js +49 -11
- package/dist/routing.d.ts +96 -52
- package/dist/routing.js +399 -167
- package/dist/server.d.ts +47 -0
- package/dist/server.js +156 -0
- package/dist/types.d.ts +148 -44
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +2 -0
- package/package.json +12 -7
- package/dist/components.d.ts +0 -31
- package/dist/components.jsx +0 -40
- package/dist/data/createAsync.d.ts +0 -32
- package/dist/data/createAsync.js +0 -96
- package/dist/data/response.d.ts +0 -4
- package/dist/data/response.js +0 -42
- package/dist/routers/HashRouter.d.ts +0 -9
- package/dist/routers/HashRouter.js +0 -41
- package/dist/routers/MemoryRouter.d.ts +0 -24
- package/dist/routers/MemoryRouter.js +0 -57
- package/dist/routers/Router.d.ts +0 -17
- package/dist/routers/Router.js +0 -59
- package/dist/routers/StaticRouter.d.ts +0 -6
- package/dist/routers/StaticRouter.js +0 -15
- package/dist/routers/createRouter.d.ts +0 -10
- package/dist/routers/createRouter.js +0 -41
package/dist/index.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "./routers/index.js";
|
|
2
|
-
export * from "./components.jsx";
|
|
3
2
|
export * from "./lifecycle.js";
|
|
4
|
-
export { useHref, useIsRouting, useLocation, useMatch,
|
|
3
|
+
export { useHref, useIsRouting, useLinkState, useLocation, useMatch, useNavigate, usePreloadRoute, useParams, useResolvedPath, useRouteMatches, useSearchParams, RouterContextObj as RouterContext } from "./routing.js";
|
|
5
4
|
export { mergeSearchString as _mergeSearchString } from "./utils.js";
|
|
5
|
+
export { int } from "./paths.js";
|
|
6
6
|
export * from "./data/index.js";
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BeforeLeaveEventArgs, BeforeLeaveLifecycle } from "./types.js";
|
|
2
2
|
export declare function createBeforeLeave(): BeforeLeaveLifecycle;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* useBeforeLeave takes a function that will be called prior to leaving a route.
|
|
5
|
+
* The function will be called with:
|
|
6
|
+
*
|
|
7
|
+
* - from (*Location*): current location (before change).
|
|
8
|
+
* - to (*string | number*): path passed to `navigate`.
|
|
9
|
+
* - options (*NavigateOptions*): options passed to navigate.
|
|
10
|
+
* - preventDefault (*function*): call to block the route change.
|
|
11
|
+
* - defaultPrevented (*readonly boolean*): `true` if any previously called leave handlers called `preventDefault`.
|
|
12
|
+
* - retry (*function*, force?: boolean ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (i.e. force navigate without confirming).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```js
|
|
16
|
+
* useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
|
17
|
+
* if (form.isDirty && !e.defaultPrevented) {
|
|
18
|
+
* // preventDefault to block immediately and prompt user async
|
|
19
|
+
* e.preventDefault();
|
|
20
|
+
* setTimeout(() => {
|
|
21
|
+
* if (window.confirm("Discard unsaved changes - are you sure?")) {
|
|
22
|
+
* // user wants to proceed anyway so retry with force=true
|
|
23
|
+
* e.retry(true);
|
|
24
|
+
* }
|
|
25
|
+
* }, 100);
|
|
26
|
+
* }
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const useBeforeLeave: (listener: (e: BeforeLeaveEventArgs) => void) => void;
|
package/dist/lifecycle.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { onCleanup } from "solid-js";
|
|
2
|
+
import { useLocation, useNavigate, useRouter } from "./routing.js";
|
|
2
3
|
export function createBeforeLeave() {
|
|
3
4
|
let listeners = new Set();
|
|
4
5
|
function subscribe(listener) {
|
|
@@ -38,39 +39,41 @@ export function createBeforeLeave() {
|
|
|
38
39
|
confirm
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
42
|
+
/**
|
|
43
|
+
* useBeforeLeave takes a function that will be called prior to leaving a route.
|
|
44
|
+
* The function will be called with:
|
|
45
|
+
*
|
|
46
|
+
* - from (*Location*): current location (before change).
|
|
47
|
+
* - to (*string | number*): path passed to `navigate`.
|
|
48
|
+
* - options (*NavigateOptions*): options passed to navigate.
|
|
49
|
+
* - preventDefault (*function*): call to block the route change.
|
|
50
|
+
* - defaultPrevented (*readonly boolean*): `true` if any previously called leave handlers called `preventDefault`.
|
|
51
|
+
* - retry (*function*, force?: boolean ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (i.e. force navigate without confirming).
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```js
|
|
55
|
+
* useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
|
56
|
+
* if (form.isDirty && !e.defaultPrevented) {
|
|
57
|
+
* // preventDefault to block immediately and prompt user async
|
|
58
|
+
* e.preventDefault();
|
|
59
|
+
* setTimeout(() => {
|
|
60
|
+
* if (window.confirm("Discard unsaved changes - are you sure?")) {
|
|
61
|
+
* // user wants to proceed anyway so retry with force=true
|
|
62
|
+
* e.retry(true);
|
|
63
|
+
* }
|
|
64
|
+
* }, 100);
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export const useBeforeLeave = (listener) => {
|
|
70
|
+
// Installing the guard here (instead of eagerly in the router/history) keeps
|
|
71
|
+
// all of createBeforeLeave tree-shakeable for apps that never block leaves.
|
|
72
|
+
const slot = useRouter().beforeLeave;
|
|
73
|
+
const s = (slot.current || (slot.current = createBeforeLeave())).subscribe({
|
|
74
|
+
listener,
|
|
75
|
+
location: useLocation(),
|
|
76
|
+
navigate: useNavigate()
|
|
77
|
+
});
|
|
78
|
+
onCleanup(s);
|
|
79
|
+
};
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Params, RouteDefinition, SearchParams, SetSearchParams, StandardSchemaV1, TypedPath, TypedSearchPath } from "./types.js";
|
|
2
|
+
declare const FILTER_TYPE: unique symbol;
|
|
3
|
+
/**
|
|
4
|
+
* A match filter that also declares the TypeScript type accepted for the
|
|
5
|
+
* param at `paths` callsites (the runtime param remains a string).
|
|
6
|
+
*/
|
|
7
|
+
export interface TypedMatchFilter<T> {
|
|
8
|
+
(s: string): boolean;
|
|
9
|
+
readonly [FILTER_TYPE]: T;
|
|
10
|
+
}
|
|
11
|
+
/** Matches integer segments; types the param as `number` in the path proxy. */
|
|
12
|
+
export declare const int: TypedMatchFilter<number>;
|
|
13
|
+
type Flat<T> = {
|
|
14
|
+
[K in keyof T]: T[K];
|
|
15
|
+
} & {};
|
|
16
|
+
type SegmentsOf<P extends string> = P extends `${infer A}/${infer B}` ? [...SegmentsOf<A>, ...SegmentsOf<B>] : P extends "" ? [] : [P];
|
|
17
|
+
type FilterArg<F> = F extends TypedMatchFilter<infer T> ? T : F extends readonly (infer S)[] ? S : string | number;
|
|
18
|
+
type ParamArg<Name extends string, F> = Name extends keyof F ? FilterArg<F[Name]> : string | number;
|
|
19
|
+
/** Collects a maximal run of required params (and a trailing splat) into one call's argument tuple. */
|
|
20
|
+
type ParamRun<Segs extends readonly string[], F, Args extends readonly unknown[] = [], PAcc extends Params = {}> = Segs extends readonly [infer H extends string, ...infer R extends readonly string[]] ? H extends `:${string}?` ? {
|
|
21
|
+
args: Args;
|
|
22
|
+
params: PAcc;
|
|
23
|
+
rest: Segs;
|
|
24
|
+
} : H extends `:${infer N}` ? ParamRun<R, F, [...Args, ParamArg<N, F>], PAcc & {
|
|
25
|
+
[K in N]: string;
|
|
26
|
+
}> : H extends `*${infer N}` ? {
|
|
27
|
+
args: [...Args, string | number];
|
|
28
|
+
params: PAcc & {
|
|
29
|
+
[K in N]: string;
|
|
30
|
+
};
|
|
31
|
+
rest: R;
|
|
32
|
+
} : {
|
|
33
|
+
args: Args;
|
|
34
|
+
params: PAcc;
|
|
35
|
+
rest: Segs;
|
|
36
|
+
} : {
|
|
37
|
+
args: Args;
|
|
38
|
+
params: PAcc;
|
|
39
|
+
rest: Segs;
|
|
40
|
+
};
|
|
41
|
+
/** The search param types a route end carries: input builds URLs, output is what parsing returns. */
|
|
42
|
+
export interface SearchTypes {
|
|
43
|
+
input: any;
|
|
44
|
+
output: any;
|
|
45
|
+
}
|
|
46
|
+
export interface DefaultSearchTypes {
|
|
47
|
+
input: SetSearchParams;
|
|
48
|
+
output: SearchParams;
|
|
49
|
+
}
|
|
50
|
+
/** Terminating calls available on every route end: zero-arg, or search object plus optional hash. */
|
|
51
|
+
export interface PathEnd<Sch extends SearchTypes = DefaultSearchTypes, P extends Params = Params> extends TypedPath<P>, TypedSearchPath<Sch["input"], Sch["output"]> {
|
|
52
|
+
(): string;
|
|
53
|
+
(search: Sch["input"], hash?: string): string;
|
|
54
|
+
}
|
|
55
|
+
type SearchTypesOf<Def> = Def extends {
|
|
56
|
+
search: infer S;
|
|
57
|
+
} ? S extends StandardSchemaV1<any, any> ? {
|
|
58
|
+
input: NonNullable<S["~standard"]["types"]>["input"];
|
|
59
|
+
output: NonNullable<S["~standard"]["types"]>["output"];
|
|
60
|
+
} : DefaultSearchTypes : DefaultSearchTypes;
|
|
61
|
+
type FiltersOf<Def> = Def extends {
|
|
62
|
+
matchFilters: infer F;
|
|
63
|
+
} ? F : {};
|
|
64
|
+
type ChildrenOf<Def> = Def extends {
|
|
65
|
+
children: infer C;
|
|
66
|
+
} ? C : undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Sees through a lazy `children` thunk: the routes the import's promise
|
|
69
|
+
* resolves to (its `default` or `routes` export, matching the runtime) type
|
|
70
|
+
* exactly like inline children. Only tables genuinely built at runtime —
|
|
71
|
+
* where the thunk's return type is a plain `RouteDefinition[]` — degrade to
|
|
72
|
+
* untyped, definitionally.
|
|
73
|
+
*/
|
|
74
|
+
type ResolvedChildren<C> = C extends () => infer R ? Awaited<R> extends infer M ? M extends {
|
|
75
|
+
default: infer D;
|
|
76
|
+
} ? D : M extends {
|
|
77
|
+
routes: infer D;
|
|
78
|
+
} ? D : M : never : C;
|
|
79
|
+
type ChildPaths<C, PAcc extends Params> = [C] extends [undefined] ? {} : ResolvedChildren<C> extends infer RC ? RC extends readonly unknown[] ? TuplePaths<RC, PAcc> : RouteContrib<RC, PAcc> : never;
|
|
80
|
+
type TuplePaths<R extends readonly unknown[], PAcc extends Params> = R extends readonly [
|
|
81
|
+
infer H,
|
|
82
|
+
...infer T extends readonly unknown[]
|
|
83
|
+
] ? RouteContrib<H, PAcc> & TuplePaths<T, PAcc> : {};
|
|
84
|
+
type PathLeaf<Sch extends SearchTypes, C, PAcc extends Params> = PathEnd<Sch, Flat<PAcc>> & ChildPaths<C, PAcc>;
|
|
85
|
+
type PathNode<Segs extends readonly string[], F, Sch extends SearchTypes, C, PAcc extends Params> = Segs extends readonly [infer H extends string, ...infer R extends readonly string[]] ? H extends `:${infer N}?` ? ((arg: ParamArg<N, F>) => PathNode<R, F, Sch, C, PAcc & {
|
|
86
|
+
[K in N]?: string;
|
|
87
|
+
}>) & PathNode<R, F, Sch, C, PAcc & {
|
|
88
|
+
[K in N]?: string;
|
|
89
|
+
}> : H extends `:${string}` | `*${string}` ? ParamCallNode<Segs, F, Sch, C, PAcc> : {
|
|
90
|
+
[K in H]: PathNode<R, F, Sch, C, PAcc>;
|
|
91
|
+
} : PathLeaf<Sch, C, PAcc>;
|
|
92
|
+
type ParamCallNode<Segs extends readonly string[], F, Sch extends SearchTypes, C, PAcc extends Params> = ParamRun<Segs, F> extends {
|
|
93
|
+
args: infer A extends readonly unknown[];
|
|
94
|
+
params: infer P2 extends Params;
|
|
95
|
+
rest: infer R2 extends readonly string[];
|
|
96
|
+
} ? ((...args: A) => PathNode<R2, F, Sch, C, PAcc & P2>) & (R2 extends readonly [] ? {
|
|
97
|
+
(...args: [...A, Sch["input"], string?]): string;
|
|
98
|
+
} : {}) & TypedPath<Flat<PAcc & P2>> : never;
|
|
99
|
+
type RouteContrib<Def, PAcc extends Params> = Def extends {
|
|
100
|
+
path: infer P;
|
|
101
|
+
} ? [P] extends [undefined] ? ChildPaths<ChildrenOf<Def>, PAcc> : P extends readonly string[] ? MultiPathContrib<P, Def, PAcc> : P extends string ? string extends P ? any : PathNode<SegmentsOf<P>, FiltersOf<Def>, SearchTypesOf<Def>, ChildrenOf<Def>, PAcc> : any : ChildPaths<ChildrenOf<Def>, PAcc>;
|
|
102
|
+
type MultiPathContrib<Ps extends readonly string[], Def, PAcc extends Params> = Ps extends readonly [infer H extends string, ...infer T extends readonly string[]] ? PathNode<SegmentsOf<H>, FiltersOf<Def>, SearchTypesOf<Def>, ChildrenOf<Def>, PAcc> & MultiPathContrib<T, Def, PAcc> : {};
|
|
103
|
+
/**
|
|
104
|
+
* The type of a router instance's `paths` proxy for a given route tree.
|
|
105
|
+
* Requires the tree to be a literal tuple (`as const` or a `const` type
|
|
106
|
+
* param); non-literal trees fall back to an untyped proxy.
|
|
107
|
+
*/
|
|
108
|
+
export type RoutePaths<R extends readonly RouteDefinition[]> = number extends R["length"] ? any : PathEnd<DefaultSearchTypes, {}> & TuplePaths<R, {}>;
|
|
109
|
+
/** Extracts the params record a paths node binds, as runtime (string-valued) params. */
|
|
110
|
+
export type PathParamsOf<N> = N extends TypedPath<infer P> ? Flat<P> : Params;
|
|
111
|
+
/**
|
|
112
|
+
* Creates the runtime path proxy. It is instance-scoped: `renderPath` comes
|
|
113
|
+
* from the router's history adapter (eg. hash routing prefixes `#`), and
|
|
114
|
+
* `base` is baked into every produced path.
|
|
115
|
+
*/
|
|
116
|
+
export declare function createPathsProxy(renderPath?: (path: string) => string, base?: string): any;
|
|
117
|
+
export {};
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { mergeSearchString, normalizePath } from "./utils.js";
|
|
2
|
+
/** Matches integer segments; types the param as `number` in the path proxy. */
|
|
3
|
+
export const int = ((s) => /^-?\d+$/.test(s));
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Runtime
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
const encodeParam = (value) => String(value).split("/").map(encodeURIComponent).join("/");
|
|
8
|
+
/**
|
|
9
|
+
* Creates the runtime path proxy. It is instance-scoped: `renderPath` comes
|
|
10
|
+
* from the router's history adapter (eg. hash routing prefixes `#`), and
|
|
11
|
+
* `base` is baked into every produced path.
|
|
12
|
+
*/
|
|
13
|
+
export function createPathsProxy(renderPath = p => p, base = "") {
|
|
14
|
+
const toHref = (pathname, suffix = "") => renderPath(pathname || "/") + suffix;
|
|
15
|
+
function node(pathname) {
|
|
16
|
+
const build = (...args) => {
|
|
17
|
+
let path = pathname;
|
|
18
|
+
for (let i = 0; i < args.length; i++) {
|
|
19
|
+
const arg = args[i];
|
|
20
|
+
if (typeof arg === "object" && arg !== null) {
|
|
21
|
+
// a search object terminates; an optional hash string may follow
|
|
22
|
+
const hash = typeof args[i + 1] === "string" ? `#${args[i + 1]}` : "";
|
|
23
|
+
return toHref(path, mergeSearchString("", arg) + hash);
|
|
24
|
+
}
|
|
25
|
+
path += `/${encodeParam(arg)}`;
|
|
26
|
+
}
|
|
27
|
+
// zero-arg calls terminate; param-only calls stay chainable
|
|
28
|
+
return args.length ? node(path) : toHref(path);
|
|
29
|
+
};
|
|
30
|
+
return new Proxy(build, {
|
|
31
|
+
get(_, prop) {
|
|
32
|
+
if (prop === "toString")
|
|
33
|
+
return () => toHref(pathname);
|
|
34
|
+
if (typeof prop === "symbol")
|
|
35
|
+
return prop === Symbol.toPrimitive ? () => toHref(pathname) : undefined;
|
|
36
|
+
return node(`${pathname}/${prop}`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return node(normalizePath(base));
|
|
41
|
+
}
|
|
@@ -1,27 +1,13 @@
|
|
|
1
|
-
import type { Component
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* A component that wraps the content of every route.
|
|
7
|
-
*/
|
|
1
|
+
import type { Component } from "solid-js";
|
|
2
|
+
import type { JSX } from "@solidjs/web";
|
|
3
|
+
import type { Branch, RoutePreloadFunc, RouterContext, RouteSectionProps } from "../types.js";
|
|
4
|
+
export declare function Root(props: {
|
|
5
|
+
routerState: RouterContext;
|
|
8
6
|
root?: Component<RouteSectionProps>;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
16
|
-
export declare const createRouterComponent: (router: RouterIntegration) => (props: BaseRouterProps) => JSX.Element;
|
|
17
|
-
export type RouteProps<S extends string, T = unknown> = {
|
|
18
|
-
path?: S | S[];
|
|
19
|
-
children?: JSX.Element;
|
|
20
|
-
preload?: RoutePreloadFunc<T>;
|
|
21
|
-
matchFilters?: MatchFilters<S>;
|
|
22
|
-
component?: RouteSectionComponent<T>;
|
|
23
|
-
info?: Record<string, any>;
|
|
24
|
-
/** @deprecated use preload */
|
|
25
|
-
load?: RoutePreloadFunc<T>;
|
|
26
|
-
};
|
|
27
|
-
export declare const Route: <S extends string, T = unknown>(props: RouteProps<S, T>) => JSX.Element;
|
|
7
|
+
preload?: RoutePreloadFunc;
|
|
8
|
+
children: JSX.Element;
|
|
9
|
+
}): JSX.Element;
|
|
10
|
+
export declare function Routes(props: {
|
|
11
|
+
routerState: RouterContext;
|
|
12
|
+
branches: () => Branch[];
|
|
13
|
+
}): JSX.Element;
|
|
@@ -1,67 +1,72 @@
|
|
|
1
1
|
/*@refresh skip*/
|
|
2
|
-
import {
|
|
3
|
-
import { getRequestEvent, isServer } from "
|
|
4
|
-
import {
|
|
5
|
-
export
|
|
6
|
-
const { base } = props;
|
|
7
|
-
const routeDefs = children(() => props.children);
|
|
8
|
-
const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
|
|
9
|
-
let context;
|
|
10
|
-
const routerState = createRouterContext(router, branches, () => context, {
|
|
11
|
-
base,
|
|
12
|
-
singleFlight: props.singleFlight,
|
|
13
|
-
transformUrl: props.transformUrl,
|
|
14
|
-
});
|
|
15
|
-
router.create && router.create(routerState);
|
|
16
|
-
return (<RouterContextObj.Provider value={routerState}>
|
|
17
|
-
<Root routerState={routerState} root={props.root} preload={props.rootPreload || props.rootLoad}>
|
|
18
|
-
{(context = getOwner()) && null}
|
|
19
|
-
<Routes routerState={routerState} branches={branches()}/>
|
|
20
|
-
</Root>
|
|
21
|
-
</RouterContextObj.Provider>);
|
|
22
|
-
};
|
|
23
|
-
function Root(props) {
|
|
2
|
+
import { createMemo, createRoot, getOwner, onCleanup, runWithOwner, untrack } from "solid-js";
|
|
3
|
+
import { getRequestEvent, isServer } from "@solidjs/web";
|
|
4
|
+
import { createRouteContext, getIntent, getRouteMatches, resolveLazySubtree, RouteContextObj, setInPreloadFn, unresolvedLazyMatches } from "../routing.js";
|
|
5
|
+
export function Root(props) {
|
|
24
6
|
const location = props.routerState.location;
|
|
25
7
|
const params = props.routerState.params;
|
|
26
8
|
const data = createMemo(() => props.preload &&
|
|
27
9
|
untrack(() => {
|
|
28
10
|
setInPreloadFn(true);
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
try {
|
|
12
|
+
return props.preload({ params, location, intent: getIntent() || "initial" });
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
setInPreloadFn(false);
|
|
16
|
+
}
|
|
31
17
|
}));
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
const RootComp = props.root;
|
|
19
|
+
if (RootComp) {
|
|
20
|
+
return (<RootComp params={params} location={location} data={data()}>
|
|
21
|
+
{props.children}
|
|
22
|
+
</RootComp>);
|
|
23
|
+
}
|
|
24
|
+
return props.children;
|
|
37
25
|
}
|
|
38
|
-
function Routes(props) {
|
|
26
|
+
export function Routes(props) {
|
|
39
27
|
if (isServer) {
|
|
40
28
|
const e = getRequestEvent();
|
|
41
29
|
if (e && e.router && e.router.dataOnly) {
|
|
42
|
-
dataOnly(e, props.routerState, props.branches);
|
|
30
|
+
dataOnly(e, props.routerState, props.branches());
|
|
43
31
|
return;
|
|
44
32
|
}
|
|
45
|
-
e &&
|
|
46
|
-
|
|
47
|
-
|
|
33
|
+
if (e && !(e.router && e.router.matches)) {
|
|
34
|
+
// A lazy getter rather than a snapshot: unresolved lazy subtrees mean
|
|
35
|
+
// the match chain can still improve while the render streams, and
|
|
36
|
+
// frameworks read this after the render settles.
|
|
37
|
+
Object.defineProperty((e.router || (e.router = {})), "matches", {
|
|
38
|
+
configurable: true,
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: () => props.routerState.matches().map(({ route, path, params }) => ({
|
|
48
41
|
path: route.originalPath,
|
|
49
42
|
pattern: route.pattern,
|
|
50
43
|
match: path,
|
|
51
44
|
params,
|
|
52
45
|
info: route.info
|
|
53
|
-
}))
|
|
46
|
+
}))
|
|
47
|
+
});
|
|
48
|
+
}
|
|
54
49
|
}
|
|
55
50
|
const disposers = [];
|
|
56
51
|
let root;
|
|
52
|
+
let prevMatches;
|
|
57
53
|
// dispose the detached per-route roots when this component unmounts, otherwise
|
|
58
54
|
// they stay subscribed to `matches` and crash on a later navigation (#451)
|
|
59
55
|
onCleanup(() => disposers.forEach(dispose => dispose()));
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
// Route roots must outlive re-runs of the `routeStates` memo below, so they
|
|
57
|
+
// are created under the owner of this component rather than the memo's
|
|
58
|
+
// computation (which disposes its children every time it re-runs).
|
|
59
|
+
const owner = getOwner();
|
|
60
|
+
const routeStates = createMemo((prev) => {
|
|
61
|
+
// While a lazy subtree resolves, `matches()` is not ready and this
|
|
62
|
+
// computation parks with it — no route contexts are created against
|
|
63
|
+
// placeholder matches.
|
|
64
|
+
const nextMatches = props.routerState.matches();
|
|
65
|
+
const previousMatches = prevMatches;
|
|
66
|
+
let equal = previousMatches && nextMatches.length === previousMatches.length;
|
|
62
67
|
const next = [];
|
|
63
68
|
for (let i = 0, len = nextMatches.length; i < len; i++) {
|
|
64
|
-
const prevMatch =
|
|
69
|
+
const prevMatch = previousMatches && previousMatches[i];
|
|
65
70
|
const nextMatch = nextMatches[i];
|
|
66
71
|
if (prev && prevMatch && nextMatch.route.key === prevMatch.route.key) {
|
|
67
72
|
next[i] = prev[i];
|
|
@@ -71,42 +76,51 @@ function Routes(props) {
|
|
|
71
76
|
if (disposers[i]) {
|
|
72
77
|
disposers[i]();
|
|
73
78
|
}
|
|
74
|
-
createRoot(dispose => {
|
|
79
|
+
runWithOwner(owner, () => createRoot(dispose => {
|
|
75
80
|
disposers[i] = dispose;
|
|
76
|
-
|
|
81
|
+
const routeKey = nextMatch.route.key;
|
|
82
|
+
// Retain the last matches in which this route participated so
|
|
83
|
+
// that its components and preloads never observe another
|
|
84
|
+
// route's params/path while this route is being torn down.
|
|
85
|
+
const matchesAtLevel = createMemo((prev) => {
|
|
77
86
|
const routeMatches = props.routerState.matches();
|
|
78
|
-
|
|
87
|
+
const m = routeMatches[i];
|
|
88
|
+
return m && m.route.key === routeKey ? routeMatches : prev || nextMatches;
|
|
79
89
|
});
|
|
80
|
-
|
|
90
|
+
next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()?.[i + 1]), () => matchesAtLevel()[i], matchesAtLevel);
|
|
91
|
+
}));
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
disposers.splice(nextMatches.length).forEach(dispose => dispose());
|
|
84
95
|
if (prev && equal) {
|
|
96
|
+
prevMatches = nextMatches;
|
|
85
97
|
return prev;
|
|
86
98
|
}
|
|
87
99
|
root = next[0];
|
|
100
|
+
prevMatches = nextMatches;
|
|
88
101
|
return next;
|
|
89
|
-
})
|
|
90
|
-
|
|
102
|
+
});
|
|
103
|
+
const outlet = createOutlet(() => routeStates() && root);
|
|
104
|
+
return <>{outlet()}</>;
|
|
91
105
|
}
|
|
92
106
|
const createOutlet = (child) => {
|
|
93
|
-
return () =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
export const Route = (props) => {
|
|
98
|
-
const childRoutes = children(() => props.children);
|
|
99
|
-
return mergeProps(props, {
|
|
100
|
-
get children() {
|
|
101
|
-
return childRoutes();
|
|
107
|
+
return () => {
|
|
108
|
+
const c = child();
|
|
109
|
+
if (c) {
|
|
110
|
+
return <RouteContextObj value={c}>{c.outlet()}</RouteContextObj>;
|
|
102
111
|
}
|
|
103
|
-
|
|
112
|
+
return undefined;
|
|
113
|
+
};
|
|
104
114
|
};
|
|
105
115
|
// for data only mode with single flight mutations
|
|
106
116
|
function dataOnly(event, routerState, branches) {
|
|
107
117
|
const url = new URL(event.request.url);
|
|
108
118
|
const prevMatches = getRouteMatches(branches, new URL(event.router.previousUrl || event.request.url).pathname);
|
|
109
119
|
const matches = getRouteMatches(branches, url.pathname);
|
|
120
|
+
// This pass is synchronous — an unresolved lazy subtree can only be
|
|
121
|
+
// kicked, not awaited (the flight collector's own runner awaits; see
|
|
122
|
+
// src/server.ts). Best effort so a subsequent pass sees the table.
|
|
123
|
+
unresolvedLazyMatches([...prevMatches, ...matches]).forEach(resolveLazySubtree);
|
|
110
124
|
for (let match = 0; match < matches.length; match++) {
|
|
111
125
|
if (!prevMatches[match] || matches[match].route !== prevMatches[match].route)
|
|
112
126
|
event.router.dataOnly = true;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { JSX } from "@solidjs/web";
|
|
2
|
+
import type { RoutePaths } from "../paths.js";
|
|
3
|
+
import type { DefinedRouteFilters, LazyRouteChildren, OutputMatch, Params, RouteDefinition, RouteInfo, RouteParams, RoutePreloadFunc, RoutePreloadFuncArgs, RouteSectionComponent, RouteSectionProps, StandardSchemaV1, ValidFilters } from "../types.js";
|
|
4
|
+
import type { RouterHistory } from "./history.js";
|
|
5
|
+
/**
|
|
6
|
+
* Identity helper that preserves literal types when the route tree is
|
|
7
|
+
* declared as a separate variable. `createRouter` infers literally from
|
|
8
|
+
* inline arrays, but an extracted `const routes = [...]` widens paths to
|
|
9
|
+
* `string` and silently degrades `paths` and the typed hooks unless it is
|
|
10
|
+
* declared `as const` — `defineRoutes` makes that impossible to forget.
|
|
11
|
+
*/
|
|
12
|
+
export declare function defineRoutes<const R extends readonly RouteDefinition[]>(routes: R): R;
|
|
13
|
+
type RouteChildren = RouteDefinition | readonly RouteDefinition[] | LazyRouteChildren;
|
|
14
|
+
type DefinedRouteComponent<T, P extends Params> = (props: RouteSectionProps<T, P> & {
|
|
15
|
+
children?: any;
|
|
16
|
+
}) => JSX.Element;
|
|
17
|
+
/**
|
|
18
|
+
* The type `defineRoute` hands back: `path`, `matchFilters`, `children`, and
|
|
19
|
+
* `search` stay literal — and only present when provided precisely, which is
|
|
20
|
+
* what the `paths` machinery keys on — while `component`/`preload` widen back
|
|
21
|
+
* to the plain `RouteDefinition` contract so the route drops into any route
|
|
22
|
+
* tree. Generics that stayed at their fallback (not provided, or deferred by
|
|
23
|
+
* a context-sensitive value inside) are omitted rather than widening the
|
|
24
|
+
* whole route.
|
|
25
|
+
*/
|
|
26
|
+
export type DefinedRoute<S = undefined, T = unknown, F = undefined, C = undefined, Sch = undefined> = ([S] extends [undefined] ? {} : {
|
|
27
|
+
path: S;
|
|
28
|
+
}) & ([F] extends [undefined] ? {} : DefinedRouteFilters<S> extends F ? {} : {
|
|
29
|
+
matchFilters: F;
|
|
30
|
+
}) & ([C] extends [undefined] ? {} : [RouteChildren | undefined] extends [C] ? {} : {
|
|
31
|
+
children: C;
|
|
32
|
+
}) & ([Sch] extends [undefined] ? {} : {
|
|
33
|
+
search: Sch;
|
|
34
|
+
}) & {
|
|
35
|
+
component?: RouteSectionComponent<T>;
|
|
36
|
+
preload?: RoutePreloadFunc<T>;
|
|
37
|
+
info?: RouteInfo;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Identity helper that types a single route from its own `path` pattern:
|
|
41
|
+
* inside `component` and `preload`, `props.params`/`args.params` carry the
|
|
42
|
+
* params the pattern guarantees (`:id` is `string`, `:tab?` is
|
|
43
|
+
* `string | undefined`) instead of the open `Params` record. Params
|
|
44
|
+
* inherited from parent routes remain accessible as `string | undefined`.
|
|
45
|
+
*
|
|
46
|
+
* Purely a definition-site convenience — plain object routes behave
|
|
47
|
+
* identically at runtime, and nested `children` only get typed params if
|
|
48
|
+
* they use `defineRoute` themselves.
|
|
49
|
+
*/
|
|
50
|
+
export declare function defineRoute<const S extends string | readonly string[], T = unknown, const F = DefinedRouteFilters<S>, const C extends RouteChildren | undefined = RouteChildren | undefined, Sch extends StandardSchemaV1<any, any> | undefined = undefined>(route: {
|
|
51
|
+
path: S;
|
|
52
|
+
matchFilters?: F & ValidFilters<F, S>;
|
|
53
|
+
preload?: (args: RoutePreloadFuncArgs<RouteParams<S>>) => T;
|
|
54
|
+
component?: DefinedRouteComponent<T, RouteParams<S>>;
|
|
55
|
+
children?: C;
|
|
56
|
+
/** Standard Schema validator for this route's search params; its input type flows into the typed path proxy. */
|
|
57
|
+
search?: Sch;
|
|
58
|
+
info?: RouteInfo;
|
|
59
|
+
}): DefinedRoute<S, T, F, C, Sch>;
|
|
60
|
+
export declare function defineRoute<T = unknown, const C extends RouteChildren | undefined = RouteChildren | undefined, Sch extends StandardSchemaV1<any, any> | undefined = undefined>(route: {
|
|
61
|
+
preload?: (args: RoutePreloadFuncArgs) => T;
|
|
62
|
+
component?: DefinedRouteComponent<T, Params>;
|
|
63
|
+
children?: C;
|
|
64
|
+
search?: Sch;
|
|
65
|
+
info?: RouteInfo;
|
|
66
|
+
}): DefinedRoute<undefined, T, undefined, C, Sch>;
|
|
67
|
+
export interface RouterConfig<R extends readonly RouteDefinition[] = RouteDefinition[]> {
|
|
68
|
+
/** The route tree. Immutable per instance — it is the source of truth for matching *and* types. */
|
|
69
|
+
routes: R;
|
|
70
|
+
base?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Runs once per mount/request (and on single-flight collection passes) to
|
|
73
|
+
* warm app-wide data. Its result is passed to the render-prop child as
|
|
74
|
+
* `props.data`.
|
|
75
|
+
*/
|
|
76
|
+
preload?: RoutePreloadFunc;
|
|
77
|
+
/**
|
|
78
|
+
* History adapter for client navigation; defaults to browser history. On
|
|
79
|
+
* the server only its `utils` apply — the location comes from the request
|
|
80
|
+
* event or the provider's `url` prop.
|
|
81
|
+
*/
|
|
82
|
+
history?: RouterHistory;
|
|
83
|
+
singleFlight?: boolean;
|
|
84
|
+
actionBase?: string;
|
|
85
|
+
explicitLinks?: boolean;
|
|
86
|
+
/** Preload route code/data on link hover and focus. Defaults to `true`. */
|
|
87
|
+
preloadLinks?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Explicit scroll restoration for back/forward navigation: positions are
|
|
90
|
+
* saved per history entry and restored once the navigation settles,
|
|
91
|
+
* replacing the browser heuristic that loses offsets when the destination
|
|
92
|
+
* route forces a layout while rendering. Defaults to `true` with the
|
|
93
|
+
* default browser history; a custom `history` adapter owns its session and
|
|
94
|
+
* must opt in explicitly.
|
|
95
|
+
*/
|
|
96
|
+
scrollRestoration?: boolean;
|
|
97
|
+
transformUrl?: (url: string) => string;
|
|
98
|
+
}
|
|
99
|
+
export interface RouterProps {
|
|
100
|
+
/**
|
|
101
|
+
* Server-only: the location for this render when no request event is in
|
|
102
|
+
* scope (SSG scripts, tests, runtimes without `node:async_hooks`). A
|
|
103
|
+
* request event established by the server harness takes precedence.
|
|
104
|
+
* Ignored on the client, where the history adapter owns the location.
|
|
105
|
+
*/
|
|
106
|
+
url?: string;
|
|
107
|
+
children?: (props: RouteSectionProps) => JSX.Element;
|
|
108
|
+
}
|
|
109
|
+
export interface RouterInstance<R extends readonly RouteDefinition[] = RouteDefinition[]> {
|
|
110
|
+
/** The instance is the provider component; the render-prop child receives the matched content as `props.children`. */
|
|
111
|
+
(props: RouterProps): JSX.Element;
|
|
112
|
+
/** Typed path proxy — builds URLs through property access and calls. */
|
|
113
|
+
readonly paths: RoutePaths<R>;
|
|
114
|
+
readonly routes: R;
|
|
115
|
+
/** The config the instance was created with — lets server integrations (flight collector, no-JS handler) consume the instance directly. */
|
|
116
|
+
readonly config: RouterConfig<R>;
|
|
117
|
+
/** Pure matching against an arbitrary URL — no rendering or request context involved. Root→leaf; `[]` when nothing matches. */
|
|
118
|
+
match(url: string): OutputMatch[];
|
|
119
|
+
}
|
|
120
|
+
export declare function createRouter<const R extends readonly RouteDefinition[]>(config: RouterConfig<R>): RouterInstance<R>;
|
|
121
|
+
export {};
|