@kasperrt/smolter 0.4.3 → 0.4.4
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/.generated/client/hash-history.ts +27 -0
- package/.generated/client/hydration.ts +98 -0
- package/.generated/client/root-data.ts +52 -0
- package/.generated/core/prepare-routes.ts +50 -0
- package/.generated/core/presentation.ts +1 -1
- package/.generated/preact/client.tsx +1 -26
- package/.generated/preact/deferred-hydration.ts +1 -98
- package/.generated/preact/index.ts +6 -1
- package/.generated/preact/navigation.tsx +13 -5
- package/.generated/preact/root-data.ts +1 -45
- package/.generated/preact/router.tsx +2 -48
- package/.generated/react/client.tsx +130 -0
- package/.generated/react/document.tsx +235 -0
- package/.generated/react/fetcher.ts +29 -7
- package/.generated/react/hooks.ts +20 -18
- package/.generated/react/index.ts +17 -1
- package/.generated/react/lazy-route.ts +116 -0
- package/.generated/react/link.ts +1 -38
- package/.generated/react/navigation-state.ts +13 -0
- package/.generated/react/navigation.ts +145 -0
- package/.generated/react/root-context.ts +22 -0
- package/.generated/react/route-hooks.ts +159 -0
- package/.generated/react/route-types.ts +91 -0
- package/.generated/react/router.ts +50 -0
- package/.generated/react/runtime.ts +1 -0
- package/.generated/server/application.tsx +18 -253
- package/.generated/server/dispatch.ts +87 -0
- package/.generated/server/index.ts +2 -0
- package/.generated/server/react.ts +34 -0
- package/.generated/server/render-application.ts +203 -0
- package/.generated/vite/application/lazy-files.ts +2 -1
- package/.generated/vite/application/vite.ts +12 -4
- package/.generated/vite/react.ts +11 -0
- package/CHANGELOG.md +10 -0
- package/README.md +41 -41
- package/dist/client/hash-history.d.ts +2 -0
- package/dist/client/hash-history.js +26 -0
- package/dist/client/hydration.d.ts +10 -0
- package/dist/{preact/deferred-hydration.js → client/hydration.js} +1 -1
- package/dist/client/root-data.d.ts +14 -0
- package/dist/{preact → client}/root-data.js +1 -1
- package/dist/core/prepare-routes.d.ts +7 -0
- package/dist/core/prepare-routes.js +28 -0
- package/dist/core/presentation.js +1 -1
- package/dist/preact/client.js +5 -26
- package/dist/preact/deferred-hydration.d.ts +1 -10
- package/dist/preact/document.js +2 -2
- package/dist/preact/index.d.ts +1 -1
- package/dist/preact/index.js +1 -1
- package/dist/preact/navigation-state.js +1 -1
- package/dist/preact/navigation.js +7 -5
- package/dist/preact/root-data.d.ts +1 -8
- package/dist/preact/route-hooks.js +1 -1
- package/dist/preact/router.d.ts +2 -2
- package/dist/preact/router.js +2 -26
- package/dist/react/client.d.ts +20 -0
- package/dist/react/client.js +86 -0
- package/dist/react/document.d.ts +25 -0
- package/dist/react/document.js +169 -0
- package/dist/react/fetcher.d.ts +8 -4
- package/dist/react/fetcher.js +12 -1
- package/dist/react/form.js +1 -1
- package/dist/react/hooks.d.ts +7 -6
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +6 -2
- package/dist/react/lazy-route.d.ts +22 -0
- package/dist/react/lazy-route.js +57 -0
- package/dist/react/link.d.ts +1 -12
- package/dist/react/navigation-state.d.ts +2 -0
- package/dist/react/navigation-state.js +9 -0
- package/dist/react/navigation.d.ts +33 -0
- package/dist/react/navigation.js +64 -0
- package/dist/react/root-context.d.ts +14 -0
- package/dist/react/root-context.js +10 -0
- package/dist/react/route-hooks.d.ts +34 -0
- package/dist/react/route-hooks.js +89 -0
- package/dist/react/route-types.d.ts +69 -0
- package/dist/react/router.d.ts +10 -0
- package/dist/react/router.js +22 -0
- package/dist/react/runtime.d.ts +1 -1
- package/dist/react/runtime.js +1 -1
- package/dist/server/application.d.ts +4 -8
- package/dist/server/application.js +9 -156
- package/dist/server/dispatch.d.ts +5 -0
- package/dist/server/dispatch.js +50 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2 -1
- package/dist/server/preact.js +2 -1
- package/dist/server/react.d.ts +10 -0
- package/dist/server/react.js +22 -0
- package/dist/server/render-application.d.ts +31 -0
- package/dist/server/render-application.js +118 -0
- package/dist/vite/application/lazy-files.d.ts +1 -1
- package/dist/vite/application/lazy-files.js +2 -2
- package/dist/vite/application/vite.d.ts +2 -1
- package/dist/vite/application/vite.js +12 -5
- package/dist/vite/react.d.ts +4 -0
- package/dist/vite/react.js +9 -1
- package/jsr.json +10 -6
- package/package.json +18 -10
- package/.generated/core/resource-types.ts +0 -48
- package/.generated/core/resources.ts +0 -446
- package/.generated/react/data.ts +0 -29
- package/.generated/react/resources/components.tsx +0 -416
- package/dist/core/resource-types.d.ts +0 -41
- package/dist/core/resources.d.ts +0 -21
- package/dist/core/resources.js +0 -347
- package/dist/react/data.d.ts +0 -9
- package/dist/react/data.js +0 -3
- package/dist/react/link.js +0 -27
- package/dist/react/resources/components.d.ts +0 -76
- package/dist/react/resources/components.js +0 -250
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { matchRoutes, type NavigateOptions } from "../core/index.ts";
|
|
2
|
+
import {
|
|
3
|
+
handoffDocument,
|
|
4
|
+
resolveApplicationURL,
|
|
5
|
+
} from "../core/environment.ts";
|
|
6
|
+
import { navigationClick } from "../core/url.ts";
|
|
7
|
+
import { useRouter, useSnapshot } from "./context.ts";
|
|
8
|
+
import { useNavigation, useURLLocation } from "./route-hooks.ts";
|
|
9
|
+
import type { AppRoute } from "./route-types.ts";
|
|
10
|
+
import type { AnchorHTMLAttributes, MouseEvent, ReactNode } from "./runtime.ts";
|
|
11
|
+
import { createElement } from "./runtime.ts";
|
|
12
|
+
|
|
13
|
+
export type { NavigateOptions } from "../core/index.ts";
|
|
14
|
+
|
|
15
|
+
export type { NavigationType } from "../core/navigation.ts";
|
|
16
|
+
|
|
17
|
+
/** Navigate to a URL or move through browser history using a numeric offset. */
|
|
18
|
+
export type NavigateFunction = (
|
|
19
|
+
to: string | number | Partial<Pick<URL, "pathname" | "search" | "hash">>,
|
|
20
|
+
options?: NavigateOptions,
|
|
21
|
+
) => void;
|
|
22
|
+
|
|
23
|
+
/** Anchor properties and router navigation options, including document reload and prefetch controls. */
|
|
24
|
+
export interface LinkProps
|
|
25
|
+
extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
|
|
26
|
+
to?: string;
|
|
27
|
+
href?: string;
|
|
28
|
+
replace?: boolean;
|
|
29
|
+
prefetch?: string;
|
|
30
|
+
state?: unknown;
|
|
31
|
+
preventScrollReset?: boolean;
|
|
32
|
+
viewTransition?: boolean;
|
|
33
|
+
reloadDocument?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Active and pending flags supplied to NavLink render callbacks. */
|
|
37
|
+
export interface NavLinkRenderProps {
|
|
38
|
+
isActive: boolean;
|
|
39
|
+
isPending: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Link options with matching rules and state-aware class or children callbacks. */
|
|
43
|
+
export interface NavLinkProps
|
|
44
|
+
extends Omit<LinkProps, "className" | "children"> {
|
|
45
|
+
end?: boolean;
|
|
46
|
+
className?: string | ((state: NavLinkRenderProps) => string);
|
|
47
|
+
children?: ReactNode | ((state: NavLinkRenderProps) => ReactNode);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Render an anchor with same-origin router navigation and optional intent prefetching. */
|
|
51
|
+
export function Link(options: LinkProps): ReactNode {
|
|
52
|
+
const {
|
|
53
|
+
href: hrefProp,
|
|
54
|
+
to = hrefProp ?? "/",
|
|
55
|
+
replace,
|
|
56
|
+
preventScrollReset,
|
|
57
|
+
viewTransition,
|
|
58
|
+
prefetch,
|
|
59
|
+
state,
|
|
60
|
+
reloadDocument,
|
|
61
|
+
...props
|
|
62
|
+
} = options;
|
|
63
|
+
const router = useRouter();
|
|
64
|
+
const current = new URL(useSnapshot("url"));
|
|
65
|
+
const destination = resolveApplicationURL(new URL(to, current));
|
|
66
|
+
const internal = destination.origin === current.origin;
|
|
67
|
+
const preload = () => {
|
|
68
|
+
const route = matchRoutes(router.routes, destination.pathname)?.route as
|
|
69
|
+
| AppRoute
|
|
70
|
+
| undefined;
|
|
71
|
+
|
|
72
|
+
void route?.preload?.().catch(() => {});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (!reloadDocument && prefetch !== "none" && internal) {
|
|
76
|
+
for (const name of ["onPointerEnter", "onFocus", "onTouchStart"] as const) {
|
|
77
|
+
const handler = props[name] as ((event: unknown) => void) | undefined;
|
|
78
|
+
|
|
79
|
+
props[name] = (event) => {
|
|
80
|
+
handler?.(event);
|
|
81
|
+
preload();
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return createElement("a", {
|
|
87
|
+
...props,
|
|
88
|
+
href: destination.href,
|
|
89
|
+
onClick(event: MouseEvent<HTMLAnchorElement>) {
|
|
90
|
+
props.onClick?.(event);
|
|
91
|
+
|
|
92
|
+
if (reloadDocument || !navigationClick(event, event.currentTarget))
|
|
93
|
+
return;
|
|
94
|
+
|
|
95
|
+
if (!internal) {
|
|
96
|
+
handoffDocument(event.nativeEvent, destination);
|
|
97
|
+
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
void router.navigate(destination.href, options);
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Render a Link with active-route state and aria-current for the matching destination. */
|
|
108
|
+
export function NavLink({
|
|
109
|
+
href,
|
|
110
|
+
to = href ?? "/",
|
|
111
|
+
end,
|
|
112
|
+
className,
|
|
113
|
+
children,
|
|
114
|
+
...props
|
|
115
|
+
}: NavLinkProps): ReactNode {
|
|
116
|
+
const location = useURLLocation();
|
|
117
|
+
const destination = resolveApplicationURL(new URL(to, location.href));
|
|
118
|
+
const target = destination.pathname;
|
|
119
|
+
const isActive =
|
|
120
|
+
destination.origin === location.origin &&
|
|
121
|
+
(location.pathname === target ||
|
|
122
|
+
(!end && target !== "/" && location.pathname.startsWith(`${target}/`)));
|
|
123
|
+
const pending = useNavigation().location;
|
|
124
|
+
const isPending =
|
|
125
|
+
!!pending &&
|
|
126
|
+
pending.origin === destination.origin &&
|
|
127
|
+
(pending.pathname === target ||
|
|
128
|
+
(!end && target !== "/" && pending.pathname.startsWith(`${target}/`)));
|
|
129
|
+
const state = { isActive, isPending };
|
|
130
|
+
|
|
131
|
+
return createElement(Link, {
|
|
132
|
+
...props,
|
|
133
|
+
to,
|
|
134
|
+
className: typeof className === "function" ? className(state) : className,
|
|
135
|
+
...(isActive && { "aria-current": "page" }),
|
|
136
|
+
children: typeof children === "function" ? children(state) : children,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
useNavigate,
|
|
142
|
+
useNavigationType,
|
|
143
|
+
useSearchParams,
|
|
144
|
+
useURLLocation,
|
|
145
|
+
} from "./route-hooks.ts";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Router } from "../core/index.ts";
|
|
2
|
+
import type { AppRoute, RootModule } from "./route-types.ts";
|
|
3
|
+
import { createContext, useContext } from "./runtime.ts";
|
|
4
|
+
|
|
5
|
+
export interface ApplicationContext {
|
|
6
|
+
router: Router;
|
|
7
|
+
routes: AppRoute[];
|
|
8
|
+
root: unknown;
|
|
9
|
+
rootError?: boolean;
|
|
10
|
+
entry?: string;
|
|
11
|
+
streaming?: boolean;
|
|
12
|
+
initialData?: string;
|
|
13
|
+
rootModule?: RootModule;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Application = createContext<ApplicationContext | null>(null);
|
|
17
|
+
|
|
18
|
+
export function useApplication() {
|
|
19
|
+
const app = useContext(Application);
|
|
20
|
+
if (!app) throw new Error("Root data requires RouterApplication.");
|
|
21
|
+
return app;
|
|
22
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { NavigateOptions, Router, SubmitOptions } from "../core/index.ts";
|
|
2
|
+
import type { NavigationType } from "../core/navigation.ts";
|
|
3
|
+
import { pendingLocation } from "../core/presentation.ts";
|
|
4
|
+
import {
|
|
5
|
+
type Submission,
|
|
6
|
+
type SubmissionOptions,
|
|
7
|
+
submissionBody,
|
|
8
|
+
submissionSearch,
|
|
9
|
+
} from "../core/submission.ts";
|
|
10
|
+
import { useRouter, useSnapshot } from "./context.ts";
|
|
11
|
+
import type { NavigateFunction } from "./navigation.ts";
|
|
12
|
+
import { useNavigationSnapshot } from "./navigation-state.ts";
|
|
13
|
+
import { useApplication } from "./root-context.ts";
|
|
14
|
+
import type { RouteData } from "./route-types.ts";
|
|
15
|
+
import { useCallback, useMemo } from "./runtime.ts";
|
|
16
|
+
|
|
17
|
+
/** Read root loader data from the application context. */
|
|
18
|
+
export function useRootData<T = unknown>(): RouteData<T> {
|
|
19
|
+
return useApplication().root as RouteData<T>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Read root data or the active route's data by its manifest identifier. */
|
|
23
|
+
export function useRouteLoaderData<T = unknown>(
|
|
24
|
+
id: string,
|
|
25
|
+
): RouteData<T> | undefined {
|
|
26
|
+
const app = useApplication();
|
|
27
|
+
const snapshot = useSnapshot();
|
|
28
|
+
|
|
29
|
+
if (id === "root") return app.root as RouteData<T>;
|
|
30
|
+
|
|
31
|
+
const route = app.routes.find((item) => item.id === id);
|
|
32
|
+
|
|
33
|
+
if (!route || route.path !== snapshot.path) return undefined;
|
|
34
|
+
|
|
35
|
+
return snapshot.data as RouteData<T>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { useSnapshot };
|
|
39
|
+
|
|
40
|
+
/** Read the current route error for an error boundary. */
|
|
41
|
+
export function useRouteError(): Error | undefined {
|
|
42
|
+
return useSnapshot("error");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Read the current page navigation or submission state. */
|
|
46
|
+
export function useNavigation(): {
|
|
47
|
+
state: "idle" | "loading" | "submitting";
|
|
48
|
+
location: URL | undefined;
|
|
49
|
+
} {
|
|
50
|
+
const router = useRouter<Router>();
|
|
51
|
+
const snapshot = useSnapshot();
|
|
52
|
+
const location = pendingLocation(router);
|
|
53
|
+
|
|
54
|
+
return useMemo(
|
|
55
|
+
() => ({
|
|
56
|
+
state: snapshot.state,
|
|
57
|
+
location,
|
|
58
|
+
}),
|
|
59
|
+
[snapshot.state, location?.href],
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Return a function that submits form data through the current router. */
|
|
64
|
+
export function useSubmit(): (
|
|
65
|
+
value: Submission,
|
|
66
|
+
options?: SubmissionOptions,
|
|
67
|
+
) => Promise<void> | Promise<import("../core/index.ts").Result<unknown>> {
|
|
68
|
+
const router = useRouter<Router>();
|
|
69
|
+
|
|
70
|
+
return useCallback(
|
|
71
|
+
(value: Submission, options: SubmissionOptions = {}) => {
|
|
72
|
+
const body = submissionBody(value, options.encType);
|
|
73
|
+
const method = options.method?.toUpperCase();
|
|
74
|
+
const target = options.action ?? router.getSnapshot().url;
|
|
75
|
+
|
|
76
|
+
if (method === "GET") {
|
|
77
|
+
const url = new URL(target, router.getSnapshot().url);
|
|
78
|
+
url.search = submissionSearch(body);
|
|
79
|
+
|
|
80
|
+
return router.navigate(url.href, options);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return router.submit(target, body, {
|
|
84
|
+
method: method as SubmitOptions["method"],
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
[router],
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Read the current router location as a URL. */
|
|
92
|
+
export function useURLLocation(): URL & { state: unknown; key: string } {
|
|
93
|
+
const href = useSnapshot("url");
|
|
94
|
+
const snapshot = useNavigationSnapshot(useRouter<Router>());
|
|
95
|
+
|
|
96
|
+
return useMemo(
|
|
97
|
+
() =>
|
|
98
|
+
Object.assign(new URL(href), {
|
|
99
|
+
state: snapshot.state,
|
|
100
|
+
key: snapshot.key,
|
|
101
|
+
}),
|
|
102
|
+
[href, snapshot.state, snapshot.key],
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Return the application navigation function, including history offsets and relative destinations. */
|
|
107
|
+
export function useNavigate(): NavigateFunction {
|
|
108
|
+
const router = useRouter<Router>();
|
|
109
|
+
|
|
110
|
+
return useCallback(
|
|
111
|
+
(to, options) => {
|
|
112
|
+
if (typeof to === "number") {
|
|
113
|
+
window.history.go(to);
|
|
114
|
+
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const current = new URL(router.getSnapshot().url);
|
|
119
|
+
const target =
|
|
120
|
+
typeof to === "string"
|
|
121
|
+
? to
|
|
122
|
+
: `${to.pathname ?? current.pathname}${to.search ?? current.search}${to.hash ?? current.hash}`;
|
|
123
|
+
void router.navigate(target, options);
|
|
124
|
+
},
|
|
125
|
+
[router],
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Read URL search parameters and update them through router navigation. */
|
|
130
|
+
export function useSearchParams(): [
|
|
131
|
+
URLSearchParams,
|
|
132
|
+
(
|
|
133
|
+
value: URLSearchParams | Record<string, string>,
|
|
134
|
+
options?: NavigateOptions,
|
|
135
|
+
) => void,
|
|
136
|
+
] {
|
|
137
|
+
const location = useURLLocation();
|
|
138
|
+
const navigate = useNavigate();
|
|
139
|
+
const params = useMemo(
|
|
140
|
+
() => new URLSearchParams(location.search),
|
|
141
|
+
[location.search],
|
|
142
|
+
);
|
|
143
|
+
const set = useCallback(
|
|
144
|
+
(
|
|
145
|
+
value: URLSearchParams | Record<string, string>,
|
|
146
|
+
options?: NavigateOptions,
|
|
147
|
+
) => {
|
|
148
|
+
navigate(`${location.pathname}?${new URLSearchParams(value)}`, options);
|
|
149
|
+
},
|
|
150
|
+
[location.pathname, navigate],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return [params, set];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Read the last browser history operation. */
|
|
157
|
+
export function useNavigationType(): NavigationType {
|
|
158
|
+
return useNavigationSnapshot(useRouter<Router>()).type;
|
|
159
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DataArgs,
|
|
3
|
+
RevalidationArgs,
|
|
4
|
+
Route,
|
|
5
|
+
Snapshot,
|
|
6
|
+
} from "../core/index.ts";
|
|
7
|
+
import type { DataResponse } from "../core/responses.ts";
|
|
8
|
+
import type { ComponentType, ReactNode } from "./runtime.ts";
|
|
9
|
+
|
|
10
|
+
/** Request, route parameters and cancellation signal supplied to a loader. */
|
|
11
|
+
export interface LoaderFunctionArgs extends DataArgs {
|
|
12
|
+
context?: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Request, route parameters and cancellation signal supplied to an action. */
|
|
16
|
+
export type ActionFunctionArgs = LoaderFunctionArgs;
|
|
17
|
+
|
|
18
|
+
/** Loader arguments with a serverLoader callback for the server transport. */
|
|
19
|
+
export interface ClientLoaderFunctionArgs extends LoaderFunctionArgs {
|
|
20
|
+
serverLoader: <T = unknown>() => Promise<T>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Action arguments with a serverAction callback for the server transport. */
|
|
24
|
+
export interface ClientActionFunctionArgs extends LoaderFunctionArgs {
|
|
25
|
+
serverAction: <T = unknown>() => Promise<T>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Navigation or mutation context used to decide whether loader data should refresh. */
|
|
29
|
+
export type ShouldRevalidateFunctionArgs = RevalidationArgs;
|
|
30
|
+
|
|
31
|
+
type UnwrapData<T> = T extends DataResponse<infer V> ? V : T;
|
|
32
|
+
|
|
33
|
+
/** Infer successful loader data, unwrapping promises and data responses. */
|
|
34
|
+
export type RouteData<T> = T extends (...args: never[]) => infer R
|
|
35
|
+
? UnwrapData<Exclude<Awaited<R>, Response | Error>>
|
|
36
|
+
: T;
|
|
37
|
+
|
|
38
|
+
/** A document metadata descriptor, such as a title, meta tag or structured-data record. */
|
|
39
|
+
export type MetaDescriptor = Record<string, unknown>;
|
|
40
|
+
|
|
41
|
+
/** Loader data, error, parameters and location supplied to a route metadata function. */
|
|
42
|
+
export interface MetaArgs<T> {
|
|
43
|
+
data: RouteData<T> | undefined;
|
|
44
|
+
loaderData: RouteData<T> | undefined;
|
|
45
|
+
error?: unknown;
|
|
46
|
+
params: Record<string, string>;
|
|
47
|
+
location: URL;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Derive document metadata descriptors from the current route data and location. */
|
|
51
|
+
export type MetaFunction<T = unknown> = (args: MetaArgs<T>) => MetaDescriptor[];
|
|
52
|
+
|
|
53
|
+
/** Return link descriptors rendered in the document head. */
|
|
54
|
+
export type LinksFunction = () => Record<string, string>[];
|
|
55
|
+
|
|
56
|
+
/** Data route with optional lazy component, preload and document metadata integration. */
|
|
57
|
+
export interface AppRoute extends Route {
|
|
58
|
+
resolveComponent?: () => ComponentType | undefined;
|
|
59
|
+
preload?: () => Promise<void>;
|
|
60
|
+
id?: string;
|
|
61
|
+
routePath?: string;
|
|
62
|
+
meta?: MetaFunction;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Root document components, loader, metadata and revalidation policy for a React application. */
|
|
66
|
+
export interface RootModule {
|
|
67
|
+
/** Route identity used by targeted revalidation. Defaults to root. */
|
|
68
|
+
id?: string;
|
|
69
|
+
/** Optional registered route URL used to revalidate root data after mutations. */
|
|
70
|
+
loaderPath?: string;
|
|
71
|
+
ErrorBoundary?: ComponentType;
|
|
72
|
+
default: ComponentType;
|
|
73
|
+
Layout: ComponentType<{ children: ReactNode }>;
|
|
74
|
+
loader?: (args: LoaderFunctionArgs) => unknown;
|
|
75
|
+
links?: LinksFunction;
|
|
76
|
+
meta?: MetaFunction;
|
|
77
|
+
shouldRevalidate?: (args: RevalidationArgs) => boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Serialized root data and route snapshot used to hydrate without repeating initial loaders. */
|
|
81
|
+
export interface Hydration {
|
|
82
|
+
rootError?: boolean;
|
|
83
|
+
root: unknown;
|
|
84
|
+
snapshot: Snapshot;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Transform a data transport response using the current request context. */
|
|
88
|
+
export type HandleDataRequestFunction = (
|
|
89
|
+
response: Response,
|
|
90
|
+
args: LoaderFunctionArgs,
|
|
91
|
+
) => Response | Promise<Response>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createStore } from "../core/store.ts";
|
|
2
|
+
import { RouterProvider } from "./context.ts";
|
|
3
|
+
import { Outlet } from "./outlet.ts";
|
|
4
|
+
import { Application, type ApplicationContext } from "./root-context.ts";
|
|
5
|
+
import {
|
|
6
|
+
createElement,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
useMemo,
|
|
9
|
+
useSyncExternalStore,
|
|
10
|
+
} from "./runtime.ts";
|
|
11
|
+
|
|
12
|
+
/** Root data, document components and request-scoped router for an application. */
|
|
13
|
+
export interface ApplicationProps extends ApplicationContext {
|
|
14
|
+
document?: boolean;
|
|
15
|
+
rootStore?: ReturnType<typeof createStore>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Compose a persistent root layout with route views and observable root loader data. */
|
|
19
|
+
export function RouterApplication(props: ApplicationProps): ReactNode {
|
|
20
|
+
const store = useMemo(
|
|
21
|
+
() => props.rootStore ?? createStore(props.root),
|
|
22
|
+
[props.rootStore, props.root],
|
|
23
|
+
);
|
|
24
|
+
const root = useSyncExternalStore(
|
|
25
|
+
store.subscribe,
|
|
26
|
+
store.getSnapshot,
|
|
27
|
+
store.getSnapshot,
|
|
28
|
+
);
|
|
29
|
+
const snapshot = useSyncExternalStore(
|
|
30
|
+
props.router.subscribe,
|
|
31
|
+
props.router.getSnapshot,
|
|
32
|
+
props.router.getSnapshot,
|
|
33
|
+
);
|
|
34
|
+
const route = props.routes.find((item) => item.path === snapshot.path);
|
|
35
|
+
const Root =
|
|
36
|
+
snapshot.error && (props.rootError || !route?.ErrorBoundary)
|
|
37
|
+
? (props.rootModule?.ErrorBoundary ?? Outlet)
|
|
38
|
+
: (props.rootModule?.default ?? Outlet);
|
|
39
|
+
const Layout = props.rootModule?.Layout;
|
|
40
|
+
const children = createElement(Root);
|
|
41
|
+
return createElement(
|
|
42
|
+
RouterProvider,
|
|
43
|
+
{ router: props.router },
|
|
44
|
+
createElement(
|
|
45
|
+
Application.Provider,
|
|
46
|
+
{ value: { ...props, root } },
|
|
47
|
+
props.document && Layout ? createElement(Layout, { children }) : children,
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
}
|