@kasperrt/smolter 0.2.0 → 0.3.1
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/deployment.ts +161 -0
- package/{src/internal → .generated}/client/index.ts +3 -0
- package/.generated/core/environment.ts +43 -0
- package/{src/internal → .generated}/core/errors.ts +1 -1
- package/{src/internal → .generated}/core/index.ts +1 -0
- package/.generated/core/navigation-router.ts +107 -0
- package/.generated/core/navigation.ts +147 -0
- package/.generated/core/presentation.ts +108 -0
- package/.generated/core/resource-types.ts +44 -0
- package/.generated/core/resources.ts +444 -0
- package/.generated/core/responses.ts +86 -0
- package/{src/internal → .generated}/core/router.ts +136 -63
- package/{src/internal → .generated}/core/serialize.ts +7 -2
- package/.generated/core/store.ts +25 -0
- package/.generated/core/submission.ts +116 -0
- package/{src/internal → .generated}/core/types.ts +18 -1
- package/.generated/core/url.ts +29 -0
- package/.generated/core/utils.ts +5 -0
- package/.generated/preact/await.tsx +41 -0
- package/.generated/preact/client.tsx +93 -0
- package/.generated/preact/context.ts +59 -0
- package/.generated/preact/document.tsx +198 -0
- package/{src/internal → .generated}/preact/fetcher.ts +43 -23
- package/.generated/preact/form.ts +58 -0
- package/{src/internal → .generated}/preact/hooks.ts +25 -28
- package/.generated/preact/index.ts +34 -0
- package/.generated/preact/lazy-route.ts +100 -0
- package/.generated/preact/navigation-state.ts +9 -0
- package/.generated/preact/navigation.tsx +147 -0
- package/.generated/preact/root-context.ts +21 -0
- package/.generated/preact/root-data.ts +45 -0
- package/.generated/preact/route-hooks.tsx +170 -0
- package/.generated/preact/route-types.ts +76 -0
- package/.generated/preact/router.tsx +107 -0
- package/{src/internal → .generated}/preact/routes.ts +17 -9
- package/.generated/react/context.ts +59 -0
- package/.generated/react/data.ts +25 -0
- package/{src/internal → .generated}/react/fetcher.ts +13 -11
- package/.generated/react/form.ts +58 -0
- package/{src/internal → .generated}/react/hooks.ts +9 -14
- package/{src/internal/preact → .generated/react}/index.ts +5 -1
- package/{src/internal → .generated}/react/link.ts +13 -21
- package/.generated/react/resources/components.tsx +404 -0
- package/{src/internal → .generated}/react/routes.ts +17 -9
- package/.generated/server/application.tsx +185 -0
- package/.generated/server/assets.ts +19 -0
- package/.generated/server/handler.ts +69 -0
- package/.generated/server/http.ts +55 -0
- package/{src/internal → .generated}/server/index.ts +3 -3
- package/.generated/server/node.ts +4 -0
- package/.generated/server/preact.ts +7 -0
- package/.generated/vite/application/browser-boundary.ts +42 -0
- package/.generated/vite/application/lazy-files.ts +138 -0
- package/.generated/vite/application/vite.ts +144 -0
- package/.generated/vite/deployment.ts +46 -0
- package/{src/internal → .generated}/vite/index.ts +11 -4
- package/{src/preact-vite.ts → .generated/vite/preact.ts} +14 -3
- package/{src/react-vite.ts → .generated/vite/react.ts} +8 -3
- package/CHANGELOG.md +63 -0
- package/README.md +277 -35
- package/dist/client/deployment.d.ts +12 -0
- package/dist/client/deployment.js +102 -0
- package/dist/client/deserialize.js +10 -0
- package/dist/{internal/client → client}/index.d.ts +2 -0
- package/dist/client/index.js +47 -0
- package/dist/core/environment.d.ts +12 -0
- package/dist/core/environment.js +23 -0
- package/dist/core/errors.js +7 -0
- package/dist/{internal/core → core}/index.d.ts +1 -0
- package/dist/core/match.js +33 -0
- package/dist/core/navigation-router.d.ts +15 -0
- package/dist/core/navigation-router.js +64 -0
- package/dist/core/navigation.d.ts +15 -0
- package/dist/core/navigation.js +92 -0
- package/dist/core/presentation.d.ts +5 -0
- package/dist/core/presentation.js +69 -0
- package/dist/core/resource-types.d.ts +37 -0
- package/dist/core/resources.d.ts +19 -0
- package/dist/core/resources.js +345 -0
- package/dist/core/responses.d.ts +15 -0
- package/dist/core/responses.js +48 -0
- package/dist/{internal/core → core}/router.d.ts +1 -1
- package/dist/core/router.js +204 -0
- package/dist/{internal/core → core}/serialize.d.ts +2 -0
- package/dist/core/serialize.js +17 -0
- package/dist/core/store.d.ts +6 -0
- package/dist/core/store.js +21 -0
- package/dist/core/submission.d.ts +30 -0
- package/dist/core/submission.js +41 -0
- package/dist/{internal/core → core}/types.d.ts +16 -1
- package/dist/core/url.d.ts +7 -0
- package/dist/core/url.js +12 -0
- package/dist/core/utils.d.ts +5 -0
- package/dist/core/utils.js +5 -0
- package/dist/files/index.js +64 -0
- package/dist/preact/await.d.ts +7 -0
- package/dist/preact/await.js +19 -0
- package/dist/preact/client.d.ts +7 -0
- package/dist/preact/client.js +68 -0
- package/dist/preact/context.d.ts +18 -0
- package/dist/preact/context.js +23 -0
- package/dist/preact/document.d.ts +15 -0
- package/dist/preact/document.js +155 -0
- package/dist/{internal/preact → preact}/fetcher.d.ts +9 -5
- package/dist/preact/fetcher.js +45 -0
- package/dist/{internal/preact → preact}/form.d.ts +5 -6
- package/dist/preact/form.js +21 -0
- package/dist/{internal/preact → preact}/hooks.d.ts +7 -6
- package/dist/preact/hooks.js +25 -0
- package/dist/preact/index.d.ts +18 -0
- package/dist/preact/index.js +15 -0
- package/dist/preact/lazy-route.d.ts +19 -0
- package/dist/preact/lazy-route.js +51 -0
- package/dist/preact/navigation-state.d.ts +2 -0
- package/dist/preact/navigation-state.js +9 -0
- package/dist/preact/navigation.d.ts +28 -0
- package/dist/preact/navigation.js +60 -0
- package/dist/preact/root-context.d.ts +13 -0
- package/dist/preact/root-context.js +9 -0
- package/dist/preact/root-data.d.ts +8 -0
- package/dist/preact/root-data.js +28 -0
- package/dist/preact/route-hooks.d.ts +32 -0
- package/dist/preact/route-hooks.js +94 -0
- package/dist/preact/route-types.d.ts +54 -0
- package/dist/preact/router.d.ts +16 -0
- package/dist/preact/router.js +63 -0
- package/dist/{internal/preact → preact}/routes.d.ts +1 -1
- package/dist/preact/routes.js +12 -0
- package/dist/{internal/preact → preact}/runtime.js +1 -1
- package/dist/react/context.d.ts +18 -0
- package/dist/react/context.js +23 -0
- package/dist/react/data.d.ts +5 -0
- package/dist/react/data.js +3 -0
- package/dist/{internal/react → react}/fetcher.d.ts +2 -2
- package/dist/react/fetcher.js +34 -0
- package/dist/{internal/react → react}/form.d.ts +5 -6
- package/dist/react/form.js +21 -0
- package/dist/react/hooks.js +25 -0
- package/dist/{internal/react → react}/index.d.ts +1 -1
- package/dist/react/index.js +10 -0
- package/dist/{internal/react → react}/link.d.ts +2 -2
- package/dist/react/link.js +27 -0
- package/dist/react/resources/components.d.ts +64 -0
- package/dist/react/resources/components.js +239 -0
- package/dist/{internal/react → react}/routes.d.ts +1 -1
- package/dist/react/routes.js +12 -0
- package/dist/react/runtime.js +1 -0
- package/dist/server/application.d.ts +4 -0
- package/dist/server/application.js +119 -0
- package/dist/server/assets.d.ts +5 -0
- package/dist/server/assets.js +7 -0
- package/dist/server/handler.d.ts +19 -0
- package/dist/server/handler.js +22 -0
- package/dist/server/http.d.ts +4 -0
- package/dist/server/http.js +27 -0
- package/dist/server/index.js +76 -0
- package/dist/server/node.d.ts +3 -0
- package/dist/server/node.js +3 -0
- package/dist/server/preact.d.ts +3 -0
- package/dist/server/preact.js +4 -0
- package/dist/vite/application/browser-boundary.d.ts +3 -0
- package/dist/vite/application/browser-boundary.js +35 -0
- package/dist/vite/application/lazy-files.d.ts +2 -0
- package/dist/vite/application/lazy-files.js +88 -0
- package/dist/vite/application/vite.d.ts +9 -0
- package/dist/vite/application/vite.js +101 -0
- package/dist/vite/deployment.d.ts +8 -0
- package/dist/vite/deployment.js +36 -0
- package/dist/vite/index.js +109 -0
- package/dist/{preact-vite.d.ts → vite/preact.d.ts} +6 -3
- package/dist/vite/preact.js +16 -0
- package/dist/{react-vite.d.ts → vite/react.d.ts} +4 -3
- package/dist/vite/react.js +15 -0
- package/dist/vite/transport.js +35 -0
- package/jsr.json +28 -16
- package/package.json +115 -35
- package/dist/client.d.ts +0 -2
- package/dist/client.js +0 -2
- package/dist/core.d.ts +0 -2
- package/dist/core.js +0 -2
- package/dist/files.d.ts +0 -2
- package/dist/files.js +0 -2
- package/dist/internal/client/deserialize.js +0 -11
- package/dist/internal/client/index.js +0 -44
- package/dist/internal/core/errors.js +0 -6
- package/dist/internal/core/index.js +0 -7
- package/dist/internal/core/match.js +0 -27
- package/dist/internal/core/router.js +0 -189
- package/dist/internal/core/serialize.js +0 -13
- package/dist/internal/core/types.js +0 -5
- package/dist/internal/core/url.d.ts +0 -2
- package/dist/internal/core/url.js +0 -6
- package/dist/internal/files/index.js +0 -77
- package/dist/internal/preact/context.d.ts +0 -18
- package/dist/internal/preact/context.js +0 -20
- package/dist/internal/preact/fetcher.js +0 -28
- package/dist/internal/preact/form.js +0 -41
- package/dist/internal/preact/hooks.js +0 -35
- package/dist/internal/preact/index.d.ts +0 -9
- package/dist/internal/preact/index.js +0 -7
- package/dist/internal/preact/link.d.ts +0 -12
- package/dist/internal/preact/link.js +0 -28
- package/dist/internal/preact/routes.js +0 -22
- package/dist/internal/react/context.d.ts +0 -18
- package/dist/internal/react/context.js +0 -20
- package/dist/internal/react/fetcher.js +0 -28
- package/dist/internal/react/form.js +0 -41
- package/dist/internal/react/hooks.js +0 -35
- package/dist/internal/react/index.js +0 -7
- package/dist/internal/react/link.js +0 -28
- package/dist/internal/react/routes.js +0 -22
- package/dist/internal/react/runtime.js +0 -2
- package/dist/internal/server/index.js +0 -69
- package/dist/internal/vite/index.js +0 -132
- package/dist/internal/vite/transport.js +0 -42
- package/dist/preact-client.d.ts +0 -2
- package/dist/preact-client.js +0 -2
- package/dist/preact-server.d.ts +0 -2
- package/dist/preact-server.js +0 -2
- package/dist/preact-vite.js +0 -9
- package/dist/preact.d.ts +0 -2
- package/dist/preact.js +0 -2
- package/dist/react-client.d.ts +0 -2
- package/dist/react-client.js +0 -2
- package/dist/react-server.d.ts +0 -2
- package/dist/react-server.js +0 -2
- package/dist/react-vite.js +0 -9
- package/dist/react.d.ts +0 -2
- package/dist/react.js +0 -2
- package/dist/server.d.ts +0 -2
- package/dist/server.js +0 -2
- package/dist/utils.d.ts +0 -5
- package/dist/utils.js +0 -5
- package/dist/vite.d.ts +0 -2
- package/dist/vite.js +0 -2
- package/src/client.ts +0 -2
- package/src/core.ts +0 -2
- package/src/files.ts +0 -2
- package/src/internal/core/url.ts +0 -6
- package/src/internal/preact/context.ts +0 -46
- package/src/internal/preact/form.ts +0 -79
- package/src/internal/preact/link.ts +0 -46
- package/src/internal/react/context.ts +0 -46
- package/src/internal/react/form.ts +0 -79
- package/src/internal/react/index.ts +0 -13
- package/src/preact-client.ts +0 -2
- package/src/preact-server.ts +0 -2
- package/src/preact.ts +0 -2
- package/src/react-client.ts +0 -2
- package/src/react-server.ts +0 -2
- package/src/react.ts +0 -2
- package/src/server.ts +0 -2
- package/src/utils.ts +0 -5
- package/src/vite.ts +0 -2
- /package/{src/internal → .generated}/client/deserialize.ts +0 -0
- /package/{src/internal → .generated}/core/match.ts +0 -0
- /package/{src/internal → .generated}/files/index.ts +0 -0
- /package/{src/internal → .generated}/preact/runtime.ts +0 -0
- /package/{src/internal → .generated}/react/runtime.ts +0 -0
- /package/{src/internal → .generated}/vite/transport.ts +0 -0
- /package/dist/{internal/client → client}/deserialize.d.ts +0 -0
- /package/dist/{internal/core → core}/errors.d.ts +0 -0
- /package/dist/{internal/core → core}/match.d.ts +0 -0
- /package/dist/{internal/files → files}/index.d.ts +0 -0
- /package/dist/{internal/preact → preact}/runtime.d.ts +0 -0
- /package/dist/{internal/react → react}/hooks.d.ts +0 -0
- /package/dist/{internal/react → react}/runtime.d.ts +0 -0
- /package/dist/{internal/server → server}/index.d.ts +0 -0
- /package/dist/{internal/vite → vite}/index.d.ts +0 -0
- /package/dist/{internal/vite → vite}/transport.d.ts +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Fetcher, NavigateOptions, Router } from "./types.ts";
|
|
2
|
+
/** Values accepted by programmatic route submissions. */
|
|
3
|
+
export type Submission = FormData | HTMLFormElement | Record<string, unknown>;
|
|
4
|
+
/** Destination and encoding for a programmatic route submission. */
|
|
5
|
+
export interface SubmissionOptions {
|
|
6
|
+
action?: string;
|
|
7
|
+
encType?: string;
|
|
8
|
+
method?: string;
|
|
9
|
+
replace?: boolean;
|
|
10
|
+
preventScrollReset?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function formData(value: Submission): FormData;
|
|
13
|
+
export declare function submissionBody(value: Submission, encoding?: string): FormData;
|
|
14
|
+
/** Encode successful form controls for a GET route request. */
|
|
15
|
+
export declare function submissionSearch(body: FormData): string;
|
|
16
|
+
/** Read the JSON payload of a programmatic submission. */
|
|
17
|
+
export declare function readSubmission(request: Request): Promise<unknown>;
|
|
18
|
+
/** Browser form behavior shared by the React and Preact adapters. */
|
|
19
|
+
export interface RouteFormOptions extends NavigateOptions {
|
|
20
|
+
action?: string;
|
|
21
|
+
method?: "get" | "post" | "put" | "patch" | "delete";
|
|
22
|
+
reloadDocument?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface FormSubmissionEvent {
|
|
25
|
+
currentTarget: HTMLFormElement;
|
|
26
|
+
defaultPrevented: boolean;
|
|
27
|
+
preventDefault: () => void;
|
|
28
|
+
}
|
|
29
|
+
/** Submit an internal form while leaving external actions to the browser. */
|
|
30
|
+
export declare function submitForm(router: Router, event: FormSubmissionEvent, submitter: HTMLElement | null, options: RouteFormOptions, fetcher?: Fetcher): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { internalURL } from "./url.js";
|
|
2
|
+
//#region .generated/core/submission.ts
|
|
3
|
+
function formData(value) {
|
|
4
|
+
if (value instanceof FormData) return value;
|
|
5
|
+
if (typeof HTMLFormElement !== "undefined" && value instanceof HTMLFormElement) return new FormData(value);
|
|
6
|
+
const body = new FormData();
|
|
7
|
+
for (const [key, field] of Object.entries(value)) body.append(key, String(field));
|
|
8
|
+
return body;
|
|
9
|
+
}
|
|
10
|
+
function submissionBody(value, encoding) {
|
|
11
|
+
return formData(encoding === "application/json" ? { _payload: JSON.stringify(value) } : value);
|
|
12
|
+
}
|
|
13
|
+
/** Encode successful form controls for a GET route request. */
|
|
14
|
+
function submissionSearch(body) {
|
|
15
|
+
const search = new URLSearchParams();
|
|
16
|
+
for (const [key, value] of body) search.append(key, typeof value === "string" ? value : value.name);
|
|
17
|
+
return String(search);
|
|
18
|
+
}
|
|
19
|
+
/** Read the JSON payload of a programmatic submission. */
|
|
20
|
+
async function readSubmission(request) {
|
|
21
|
+
if (request.headers.get("Content-Type")?.includes("application/json")) return request.json();
|
|
22
|
+
const body = await request.formData();
|
|
23
|
+
return JSON.parse(String(body.get("_payload") ?? "{}"));
|
|
24
|
+
}
|
|
25
|
+
/** Submit an internal form while leaving external actions to the browser. */
|
|
26
|
+
function submitForm(router, event, submitter, options, fetcher) {
|
|
27
|
+
if (event.defaultPrevented || options.reloadDocument) return;
|
|
28
|
+
const target = internalURL(router.getSnapshot().url, submitter?.getAttribute("formaction") ?? options.action);
|
|
29
|
+
if (!target) return;
|
|
30
|
+
const data = new FormData(event.currentTarget, submitter);
|
|
31
|
+
const method = (submitter?.getAttribute("formmethod") ?? options.method)?.toUpperCase();
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
if (method === "GET") {
|
|
34
|
+
target.search = submissionSearch(data);
|
|
35
|
+
fetcher ? fetcher.load(target.href) : router.navigate(target.href, options);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
(fetcher ?? router).submit(target.href, data, { method });
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { formData, readSubmission, submissionBody, submissionSearch, submitForm };
|
|
@@ -41,6 +41,8 @@ export interface RevalidationArgs extends RevalidationContext {
|
|
|
41
41
|
}
|
|
42
42
|
/** A route component and its optional server and client data handlers. */
|
|
43
43
|
export interface RouteModule {
|
|
44
|
+
/** Stable identifier for targeted revalidation; defaults to the path. */
|
|
45
|
+
id?: string;
|
|
44
46
|
default?: unknown;
|
|
45
47
|
HydrateFallback?: unknown;
|
|
46
48
|
ErrorBoundary?: unknown;
|
|
@@ -80,7 +82,11 @@ export interface RouterOptions {
|
|
|
80
82
|
}
|
|
81
83
|
/** Options for replacing rather than appending a browser history entry. */
|
|
82
84
|
export interface NavigateOptions {
|
|
85
|
+
state?: unknown;
|
|
86
|
+
viewTransition?: boolean;
|
|
83
87
|
replace?: boolean;
|
|
88
|
+
/** Preserve scroll in integrations that manage scroll restoration. */
|
|
89
|
+
preventScrollReset?: boolean;
|
|
84
90
|
}
|
|
85
91
|
/** HTTP method for a route action submission; defaults to POST. */
|
|
86
92
|
export interface SubmitOptions {
|
|
@@ -104,6 +110,13 @@ export interface Fetcher {
|
|
|
104
110
|
/** Abort work and release subscriptions when this imperative fetcher is no longer used. */
|
|
105
111
|
dispose: () => void;
|
|
106
112
|
}
|
|
113
|
+
/** Refresh active route data without navigation, optionally selecting route IDs or paths. */
|
|
114
|
+
export interface RevalidateOptions extends RevalidationContext {
|
|
115
|
+
route?: string | readonly string[];
|
|
116
|
+
reason?: RevalidationArgs["reason"];
|
|
117
|
+
}
|
|
118
|
+
/** A registered layout loader's revalidation callback. */
|
|
119
|
+
export type Revalidator = (args: RevalidationArgs) => void | Promise<void>;
|
|
107
120
|
/** Route navigation, subscriptions, fetchers, and loader revalidation. */
|
|
108
121
|
export interface Router {
|
|
109
122
|
routes: readonly Route[];
|
|
@@ -111,7 +124,9 @@ export interface Router {
|
|
|
111
124
|
subscribe: (listener: () => void) => () => void;
|
|
112
125
|
start: () => () => void;
|
|
113
126
|
navigate: (url: string, options?: NavigateOptions) => Promise<void>;
|
|
114
|
-
revalidate: (
|
|
127
|
+
revalidate: (options?: RevalidateOptions | RevalidationArgs["reason"], context?: RevalidationContext) => Promise<void>;
|
|
128
|
+
/** Register layout data under a route ID. The returned function unregisters it. */
|
|
129
|
+
registerRevalidator: (id: string, revalidate: Revalidator) => () => void;
|
|
115
130
|
submit: (url: string, body: FormData, options?: SubmitOptions) => Promise<Result>;
|
|
116
131
|
/** Create an independent fetcher. Framework useFetcher hooks manage disposal automatically. */
|
|
117
132
|
createFetcher: () => Fetcher;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Resolve an internal URL; external links remain the browser's responsibility. */
|
|
2
|
+
export declare function internalURL(base: string, href?: string): URL | undefined;
|
|
3
|
+
/** Browser and framework event fields used by navigation interception. */
|
|
4
|
+
type NavigationClickEvent = Pick<MouseEvent, "defaultPrevented" | "button" | "metaKey" | "ctrlKey" | "altKey" | "shiftKey">;
|
|
5
|
+
/** Whether an anchor click should use router navigation. */
|
|
6
|
+
export declare function navigationClick(event: NavigationClickEvent, anchor: HTMLAnchorElement): boolean;
|
|
7
|
+
export {};
|
package/dist/core/url.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region .generated/core/url.ts
|
|
2
|
+
/** Resolve an internal URL; external links remain the browser's responsibility. */
|
|
3
|
+
function internalURL(base, href = base) {
|
|
4
|
+
const url = new URL(href, base);
|
|
5
|
+
if (url.origin === new URL(base).origin) return url;
|
|
6
|
+
}
|
|
7
|
+
/** Whether an anchor click should use router navigation. */
|
|
8
|
+
function navigationClick(event, anchor) {
|
|
9
|
+
return !(event.defaultPrevented || event.button || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || anchor.hasAttribute("download") || anchor.target && anchor.target !== "_self");
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { internalURL, navigationClick };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { safeWrapAsync } from "./errors.js";
|
|
2
|
+
import { defineRoute, matchRoutes } from "./match.js";
|
|
3
|
+
import { internalURL, navigationClick } from "./url.js";
|
|
4
|
+
import { serialize, serializeData } from "./serialize.js";
|
|
5
|
+
export { defineRoute, internalURL, matchRoutes, navigationClick, safeWrapAsync, serialize, serializeData };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { safeWrapAsync } from "../core/errors.js";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
//#region .generated/files/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Build-time discovery of flat file routes.
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
var routeFile = /^route\.[jt]sx?$/;
|
|
10
|
+
var flatFile = /\.[jt]sx?$/;
|
|
11
|
+
/** Read only flat files and immediate route folders. Colocated helpers are ignored. */
|
|
12
|
+
async function flatRoutes(directory) {
|
|
13
|
+
const result = await safeWrapAsync(async () => {
|
|
14
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
15
|
+
const routes = [];
|
|
16
|
+
const patterns = /* @__PURE__ */ new Set();
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
let name = entry.name;
|
|
19
|
+
let file = resolve(directory, name);
|
|
20
|
+
if (entry.isDirectory()) {
|
|
21
|
+
const candidates = (await readdir(file)).filter((name) => routeFile.test(name));
|
|
22
|
+
if (candidates.length > 1) return /* @__PURE__ */ new Error(`Multiple route files in ${file}`);
|
|
23
|
+
if (!candidates.length) continue;
|
|
24
|
+
file = resolve(file, candidates[0]);
|
|
25
|
+
} else {
|
|
26
|
+
if (!entry.isFile() || !flatFile.test(name) || name.endsWith(".d.ts")) continue;
|
|
27
|
+
name = name.replace(flatFile, "");
|
|
28
|
+
}
|
|
29
|
+
if (/[()[\]:*?]/.test(name)) return /* @__PURE__ */ new Error(`Unsupported flat route name: ${name}`);
|
|
30
|
+
const segments = name.split(".");
|
|
31
|
+
const parts = [];
|
|
32
|
+
for (const [index, segment] of segments.entries()) {
|
|
33
|
+
if (!segment) return /* @__PURE__ */ new Error(`Empty route segment: ${name}`);
|
|
34
|
+
if (segment === "_index" && index !== segments.length - 1) return /* @__PURE__ */ new Error(`_index must be last: ${name}`);
|
|
35
|
+
if (segment === "$" && index !== segments.length - 1) return /* @__PURE__ */ new Error(`Splat must be last: ${name}`);
|
|
36
|
+
if (segment.startsWith("_")) continue;
|
|
37
|
+
const part = segment.replace(/_$/, "");
|
|
38
|
+
parts.push(part === "$" ? "*" : part.replace(/^\$/, ":"));
|
|
39
|
+
}
|
|
40
|
+
if (segments.at(-1)?.startsWith("_") && segments.at(-1) !== "_index") continue;
|
|
41
|
+
const path = `/${parts.join("/")}`;
|
|
42
|
+
const pattern = path.replace(/:[^/]+/g, ":");
|
|
43
|
+
if (patterns.has(pattern)) return /* @__PURE__ */ new Error(`Conflicting flat route: ${path}`);
|
|
44
|
+
patterns.add(pattern);
|
|
45
|
+
routes.push({
|
|
46
|
+
path,
|
|
47
|
+
file
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return routes.sort((a, b) => {
|
|
51
|
+
const left = a.path.split("/");
|
|
52
|
+
const right = b.path.split("/");
|
|
53
|
+
const rank = (part) => part === "*" ? 0 : part?.startsWith(":") ? 1 : part === void 0 ? 2 : 3;
|
|
54
|
+
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
55
|
+
const difference = rank(right[index]) - rank(left[index]);
|
|
56
|
+
if (difference) return difference;
|
|
57
|
+
}
|
|
58
|
+
return a.path.localeCompare(b.path);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
return result.error ?? result.data;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { flatRoutes };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComponentChildren } from "preact";
|
|
2
|
+
export interface AwaitProps<T> {
|
|
3
|
+
resolve: T | Promise<T>;
|
|
4
|
+
children: (value: Awaited<T>) => ComponentChildren;
|
|
5
|
+
errorElement?: ComponentChildren;
|
|
6
|
+
}
|
|
7
|
+
export declare function Await<T>({ resolve, children, errorElement, }: AwaitProps<T>): ComponentChildren;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { use } from "preact/compat";
|
|
2
|
+
//#region .generated/preact/await.tsx
|
|
3
|
+
var resolutions = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
function Await({ resolve, children, errorElement }) {
|
|
5
|
+
if (!(resolve instanceof Promise)) return children(resolve);
|
|
6
|
+
let pending = resolutions.get(resolve);
|
|
7
|
+
if (!pending) {
|
|
8
|
+
pending = resolve.then((value) => ({ value }), (error) => ({ error }));
|
|
9
|
+
resolutions.set(resolve, pending);
|
|
10
|
+
}
|
|
11
|
+
const result = use(pending);
|
|
12
|
+
if ("error" in result) {
|
|
13
|
+
if (errorElement !== void 0) return errorElement;
|
|
14
|
+
throw result.error;
|
|
15
|
+
}
|
|
16
|
+
return children(result.value);
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { Await };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Browser mounting and hydration for Preact applications. @module */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import { type Router } from "../core/index.ts";
|
|
4
|
+
import type { AppRoute, RootModule } from "./route-types.ts";
|
|
5
|
+
export * from "../client/index.ts";
|
|
6
|
+
export { configureApplication } from "../core/environment.ts";
|
|
7
|
+
export declare function mountApplication(routes: AppRoute[], rootModule?: RootModule, hash?: boolean): Promise<Router>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { matchRoutes } from "../core/match.js";
|
|
2
|
+
import { createDataRouter } from "../core/router.js";
|
|
3
|
+
import { configureApplication } from "../core/environment.js";
|
|
4
|
+
import { retainRoute } from "../core/presentation.js";
|
|
5
|
+
import { connectNavigation } from "../core/navigation.js";
|
|
6
|
+
import { attachRootData } from "./root-data.js";
|
|
7
|
+
import { RouterApplication, prepareRoutes } from "./router.js";
|
|
8
|
+
import { deserialize } from "../client/deserialize.js";
|
|
9
|
+
import { watchDeployment } from "../client/deployment.js";
|
|
10
|
+
import { serverRoute } from "../client/index.js";
|
|
11
|
+
import { flushSync } from "preact/compat";
|
|
12
|
+
import { jsx } from "preact/jsx-runtime";
|
|
13
|
+
import { hydrate, render } from "preact";
|
|
14
|
+
//#region .generated/preact/client.tsx
|
|
15
|
+
/** Browser mounting and hydration for Preact applications. @module */
|
|
16
|
+
/** @jsxImportSource preact */
|
|
17
|
+
async function mountApplication(routes, rootModule, hash = false) {
|
|
18
|
+
const initialURL = new URL(hash ? location.hash.slice(1) || "/" : location.href, location.origin);
|
|
19
|
+
await (matchRoutes(routes, initialURL.pathname)?.route)?.preload?.();
|
|
20
|
+
routes = prepareRoutes(routes);
|
|
21
|
+
const element = document.getElementById("smolter-data");
|
|
22
|
+
const hydration = element?.textContent ? JSON.parse(element.textContent) : void 0;
|
|
23
|
+
if (hydration?.snapshot.error) hydration.snapshot.error = Object.assign(new Error(hydration.snapshot.error.message), hydration.snapshot.error);
|
|
24
|
+
const router = createDataRouter(routes, {
|
|
25
|
+
browser: !hash,
|
|
26
|
+
url: hash ? new URL(location.hash.slice(1) || "/", location.origin).href : location.href,
|
|
27
|
+
...hydration && { hydrationData: hydration.snapshot }
|
|
28
|
+
});
|
|
29
|
+
retainRoute(router, flushSync);
|
|
30
|
+
if (hash) connectHashHistory(router);
|
|
31
|
+
connectNavigation(router);
|
|
32
|
+
const rootStore = attachRootData(router, hydration?.root, rootModule);
|
|
33
|
+
const props = {
|
|
34
|
+
routes,
|
|
35
|
+
router,
|
|
36
|
+
root: hydration?.root,
|
|
37
|
+
rootModule,
|
|
38
|
+
rootStore
|
|
39
|
+
};
|
|
40
|
+
if (hydration) {
|
|
41
|
+
hydrate(/* @__PURE__ */ jsx(RouterApplication, {
|
|
42
|
+
...props,
|
|
43
|
+
document: true
|
|
44
|
+
}), document);
|
|
45
|
+
return router;
|
|
46
|
+
}
|
|
47
|
+
const target = document.getElementById("root");
|
|
48
|
+
if (target) render(/* @__PURE__ */ jsx(RouterApplication, { ...props }), target);
|
|
49
|
+
return router;
|
|
50
|
+
}
|
|
51
|
+
function connectHashHistory(router) {
|
|
52
|
+
const navigate = router.navigate;
|
|
53
|
+
router.navigate = async (url, options) => {
|
|
54
|
+
const target = new URL(url, router.getSnapshot().url);
|
|
55
|
+
const hash = `#${target.pathname}${target.search}`;
|
|
56
|
+
history[options?.replace ? "replaceState" : "pushState"](null, "", hash);
|
|
57
|
+
await navigate(url, options);
|
|
58
|
+
};
|
|
59
|
+
const onHashChange = () => void navigate(location.hash.slice(1) || "/");
|
|
60
|
+
window.addEventListener("hashchange", onHashChange);
|
|
61
|
+
const dispose = router.dispose;
|
|
62
|
+
router.dispose = () => {
|
|
63
|
+
window.removeEventListener("hashchange", onHashChange);
|
|
64
|
+
dispose();
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { configureApplication, deserialize, mountApplication, serverRoute, watchDeployment };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import type { NavigationRouter, Snapshot } from "../core/index.ts";
|
|
6
|
+
import { type ReactNode } from "./runtime.ts";
|
|
7
|
+
/** Router instance and children rendered within its context. */
|
|
8
|
+
export interface RouterProviderProps {
|
|
9
|
+
router: NavigationRouter;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/** Provide a router to descendant hooks and connect its browser lifecycle. */
|
|
13
|
+
export declare function RouterProvider({ router, children, }: RouterProviderProps): ReactNode;
|
|
14
|
+
/** Hooks require a RouterProvider. */
|
|
15
|
+
export declare function useRouter<T extends NavigationRouter = NavigationRouter>(): T;
|
|
16
|
+
export declare function useSnapshot(): Snapshot;
|
|
17
|
+
export declare function useSnapshot<K extends keyof Snapshot>(key: K): Snapshot[K];
|
|
18
|
+
export declare function useSnapshot<K extends keyof Snapshot>(key: K, path: string | undefined): Snapshot[K] | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createContext, createElement, useContext, useEffect, useSyncExternalStore } from "./runtime.js";
|
|
2
|
+
//#region .generated/preact/context.ts
|
|
3
|
+
var RouterContext = createContext(void 0);
|
|
4
|
+
/** Provide a router to descendant hooks and connect its browser lifecycle. */
|
|
5
|
+
function RouterProvider({ router, children }) {
|
|
6
|
+
useEffect(router.start, [router]);
|
|
7
|
+
return createElement(RouterContext, { value: router }, children);
|
|
8
|
+
}
|
|
9
|
+
/** Hooks require a RouterProvider. */
|
|
10
|
+
function useRouter() {
|
|
11
|
+
return useContext(RouterContext);
|
|
12
|
+
}
|
|
13
|
+
function useSnapshot(key, path) {
|
|
14
|
+
const router = useRouter();
|
|
15
|
+
const getSnapshot = () => {
|
|
16
|
+
const snapshot = router.getSnapshot();
|
|
17
|
+
if (path !== void 0 && path !== snapshot.path) return void 0;
|
|
18
|
+
return key ? snapshot[key] : snapshot;
|
|
19
|
+
};
|
|
20
|
+
return useSyncExternalStore(router.subscribe, getSnapshot);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { RouterProvider, useRouter, useSnapshot };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @jsxImportSource preact */
|
|
2
|
+
import type { ComponentProps } from "preact/compat";
|
|
3
|
+
export declare function Meta(): import("preact").JSX.Element;
|
|
4
|
+
export declare function Links(): import("preact").JSX.Element;
|
|
5
|
+
export interface ScriptProps {
|
|
6
|
+
nonce?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function Scripts({ nonce }: ScriptProps): import("preact").JSX.Element;
|
|
9
|
+
export declare function ScrollRestoration(_props: ScriptProps): null;
|
|
10
|
+
/** HTML attributes for server markup and client document reconciliation. */
|
|
11
|
+
export interface DocumentProps extends ComponentProps<"html"> {
|
|
12
|
+
"data-debug"?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Render the server document and preserve client-managed theme classes on hydration. */
|
|
15
|
+
export declare function Document(props: DocumentProps): import("preact").JSX.Element;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { serializeData } from "../core/serialize.js";
|
|
2
|
+
import { useSnapshot } from "./context.js";
|
|
3
|
+
import { applicationEnvironment } from "../core/environment.js";
|
|
4
|
+
import { scrollPositions } from "../core/navigation.js";
|
|
5
|
+
import { useNavigationSnapshot } from "./navigation-state.js";
|
|
6
|
+
import { useApplication } from "./root-context.js";
|
|
7
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
|
|
9
|
+
import { Fragment as Fragment$1, createElement } from "preact";
|
|
10
|
+
//#region .generated/preact/document.tsx
|
|
11
|
+
/** @jsxImportSource preact */
|
|
12
|
+
function renderMeta(item, index) {
|
|
13
|
+
if (typeof item.title === "string") return /* @__PURE__ */ jsx("title", { children: item.title }, index);
|
|
14
|
+
if ("script:ld+json" in item) return /* @__PURE__ */ jsx("script", {
|
|
15
|
+
type: "application/ld+json",
|
|
16
|
+
dangerouslySetInnerHTML: { __html: serializeData(item["script:ld+json"]) }
|
|
17
|
+
}, index);
|
|
18
|
+
const { tagName, ...props } = item;
|
|
19
|
+
return createElement(tagName === "link" ? "link" : "meta", {
|
|
20
|
+
...props,
|
|
21
|
+
key: index
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function Meta() {
|
|
25
|
+
const { routes, rootModule } = useApplication();
|
|
26
|
+
const snapshot = useSnapshot();
|
|
27
|
+
const route = routes.find((item) => item.path === snapshot.path);
|
|
28
|
+
const args = {
|
|
29
|
+
loaderData: snapshot.data,
|
|
30
|
+
data: snapshot.data,
|
|
31
|
+
params: snapshot.params,
|
|
32
|
+
error: snapshot.error,
|
|
33
|
+
location: new URL(snapshot.url)
|
|
34
|
+
};
|
|
35
|
+
const values = route?.meta?.(args) ?? rootModule?.meta?.(args) ?? [];
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const title = values.find((item) => typeof item.title === "string")?.title;
|
|
38
|
+
if (typeof title === "string") document.title = title;
|
|
39
|
+
}, [values]);
|
|
40
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: values.map(renderMeta) });
|
|
41
|
+
}
|
|
42
|
+
function Links() {
|
|
43
|
+
const { rootModule } = useApplication();
|
|
44
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: rootModule?.links?.().map((props, index) => /* @__PURE__ */ jsx("link", { ...props }, index)) });
|
|
45
|
+
}
|
|
46
|
+
function Scripts({ nonce }) {
|
|
47
|
+
const { root, router, entry } = useApplication();
|
|
48
|
+
const [json] = useState(() => {
|
|
49
|
+
const current = router.getSnapshot();
|
|
50
|
+
const snapshot = {
|
|
51
|
+
...current,
|
|
52
|
+
error: current.error && {
|
|
53
|
+
...current.error,
|
|
54
|
+
name: current.error.name,
|
|
55
|
+
message: current.error.message
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return serializeData({
|
|
59
|
+
root,
|
|
60
|
+
snapshot
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
64
|
+
/* @__PURE__ */ jsx("script", {
|
|
65
|
+
...nonce && { nonce },
|
|
66
|
+
dangerouslySetInnerHTML: { __html: applicationEnvironment?.entryScript ?? "" }
|
|
67
|
+
}),
|
|
68
|
+
/* @__PURE__ */ jsx("script", {
|
|
69
|
+
id: "smolter-data",
|
|
70
|
+
type: "application/json",
|
|
71
|
+
...nonce && { nonce },
|
|
72
|
+
dangerouslySetInnerHTML: { __html: json }
|
|
73
|
+
}),
|
|
74
|
+
entry && /* @__PURE__ */ jsx("script", {
|
|
75
|
+
type: "module",
|
|
76
|
+
src: entry,
|
|
77
|
+
...nonce && { nonce }
|
|
78
|
+
})
|
|
79
|
+
] });
|
|
80
|
+
}
|
|
81
|
+
function ScrollRestoration(_props) {
|
|
82
|
+
const { router } = useApplication();
|
|
83
|
+
const { url, state } = useSnapshot();
|
|
84
|
+
const navigation = useNavigationSnapshot(router);
|
|
85
|
+
const positions = scrollPositions(router);
|
|
86
|
+
const restoredLocation = useRef("");
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const previous = history.scrollRestoration;
|
|
89
|
+
history.scrollRestoration = "manual";
|
|
90
|
+
return () => {
|
|
91
|
+
history.scrollRestoration = previous;
|
|
92
|
+
};
|
|
93
|
+
}, []);
|
|
94
|
+
useLayoutEffect(() => {
|
|
95
|
+
if (state !== "idle") return;
|
|
96
|
+
if ((history.state?.key ?? "initial") !== navigation.key) return;
|
|
97
|
+
const locationKey = `${navigation.key}:${url}`;
|
|
98
|
+
if (restoredLocation.current === locationKey) return;
|
|
99
|
+
const frame = requestAnimationFrame(() => {
|
|
100
|
+
restoredLocation.current = locationKey;
|
|
101
|
+
const target = new URL(location.href);
|
|
102
|
+
const saved = navigation.type === "POP" && positions.get(navigation.key);
|
|
103
|
+
if (!saved && target.hash) {
|
|
104
|
+
document.getElementById(target.hash.slice(1))?.scrollIntoView();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!saved && navigation.preventScrollReset) return;
|
|
108
|
+
const [left, top] = saved || [0, 0];
|
|
109
|
+
window.scrollTo({
|
|
110
|
+
left,
|
|
111
|
+
top,
|
|
112
|
+
behavior: "instant"
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
return () => cancelAnimationFrame(frame);
|
|
116
|
+
}, [
|
|
117
|
+
url,
|
|
118
|
+
state,
|
|
119
|
+
positions,
|
|
120
|
+
navigation.key,
|
|
121
|
+
navigation.type,
|
|
122
|
+
navigation.preventScrollReset
|
|
123
|
+
]);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
/** Render the server document and preserve client-managed theme classes on hydration. */
|
|
127
|
+
function Document(props) {
|
|
128
|
+
const { children, ...attributes } = props;
|
|
129
|
+
const className = String(props.className ?? "");
|
|
130
|
+
const previousClassName = useRef(className);
|
|
131
|
+
const lang = String(props.lang ?? "en");
|
|
132
|
+
const debug = String(props["data-debug"] ?? false);
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (previousClassName.current !== className) {
|
|
135
|
+
const previous = previousClassName.current.split(/\s+/).filter(Boolean);
|
|
136
|
+
const next = className.split(/\s+/).filter(Boolean);
|
|
137
|
+
document.documentElement.classList.remove(...previous);
|
|
138
|
+
document.documentElement.classList.add(...next);
|
|
139
|
+
previousClassName.current = className;
|
|
140
|
+
}
|
|
141
|
+
document.documentElement.lang = lang;
|
|
142
|
+
document.documentElement.dataset.debug = debug;
|
|
143
|
+
}, [
|
|
144
|
+
className,
|
|
145
|
+
lang,
|
|
146
|
+
debug
|
|
147
|
+
]);
|
|
148
|
+
if (typeof document === "undefined") return /* @__PURE__ */ jsx("html", {
|
|
149
|
+
...attributes,
|
|
150
|
+
children
|
|
151
|
+
});
|
|
152
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
export { Document, Links, Meta, Scripts, ScrollRestoration };
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
|
-
import type
|
|
5
|
+
import { type AnyDataFunction, type DataFunction, type DataOf, type FetcherSnapshot, type Result, type Route as RouteDefinition, type RouteModule } from "../core/index.ts";
|
|
6
|
+
import { type Submission, type SubmissionOptions } from "../core/submission.ts";
|
|
6
7
|
import { type FormProps } from "./form.ts";
|
|
7
|
-
import type {
|
|
8
|
+
import type { RouteData } from "./route-types.ts";
|
|
9
|
+
import { type ComponentType } from "./runtime.ts";
|
|
8
10
|
/** Action method and optional explicit route URL for a fetcher submission. */
|
|
9
|
-
export interface FetcherSubmitOptions extends
|
|
11
|
+
export interface FetcherSubmitOptions extends SubmissionOptions {
|
|
10
12
|
action?: string;
|
|
11
13
|
}
|
|
12
14
|
/** Typed independent request state, methods, and a bound Form component. */
|
|
@@ -14,9 +16,11 @@ export interface TypedFetcher<T> extends Omit<FetcherSnapshot, "data"> {
|
|
|
14
16
|
data?: T;
|
|
15
17
|
Form: ComponentType<FormProps>;
|
|
16
18
|
load: (url?: string) => Promise<Result>;
|
|
17
|
-
submit: (body:
|
|
19
|
+
submit: (body: Submission, options?: FetcherSubmitOptions) => Promise<Result>;
|
|
18
20
|
}
|
|
19
21
|
/** Create a fetcher for independent route reads and mutations; its data stays local. */
|
|
20
22
|
export declare function useFetcher<T extends RouteModule>(route: RouteDefinition<T>): TypedFetcher<DataOf<Extract<T["action"] | T["clientAction"], AnyDataFunction>> | DataOf<Extract<T["loader"] | T["clientLoader"], AnyDataFunction>>>;
|
|
21
23
|
/** Create a fetcher for independent route reads and mutations; its data stays local. */
|
|
22
|
-
export declare function useFetcher<T
|
|
24
|
+
export declare function useFetcher<T = DataFunction>(): TypedFetcher<RouteData<T>>;
|
|
25
|
+
/** A fetcher with a bound form and inferred result data. */
|
|
26
|
+
export type FetcherWithComponents<T> = TypedFetcher<RouteData<T>>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { submissionBody, submissionSearch } from "../core/submission.js";
|
|
2
|
+
import { createElement, useEffect, useMemo, useSyncExternalStore } from "./runtime.js";
|
|
3
|
+
import { useRouter } from "./context.js";
|
|
4
|
+
import { Form } from "./form.js";
|
|
5
|
+
//#region .generated/preact/fetcher.ts
|
|
6
|
+
/** Create a fetcher for independent route reads and mutations; its data stays local. */
|
|
7
|
+
function useFetcher(route) {
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
const path = route?.path;
|
|
10
|
+
const fetcher = useMemo(() => {
|
|
11
|
+
const store = router.createFetcher();
|
|
12
|
+
const target = () => {
|
|
13
|
+
const snapshot = router.getSnapshot();
|
|
14
|
+
return path === void 0 || path === snapshot.path ? snapshot.url : path;
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
...store,
|
|
18
|
+
Form: (props) => createElement(Form, {
|
|
19
|
+
action: target(),
|
|
20
|
+
...props,
|
|
21
|
+
fetcher: store
|
|
22
|
+
}),
|
|
23
|
+
load: (url) => store.load(url ?? target()),
|
|
24
|
+
submit: (body, options = {}) => {
|
|
25
|
+
const action = options.action ?? target();
|
|
26
|
+
const data = submissionBody(body, options.encType);
|
|
27
|
+
const method = options.method?.toUpperCase();
|
|
28
|
+
if (method === "GET") {
|
|
29
|
+
const url = new URL(action, router.getSnapshot().url);
|
|
30
|
+
url.search = submissionSearch(data);
|
|
31
|
+
return store.load(url.href);
|
|
32
|
+
}
|
|
33
|
+
return store.submit(action, data, { method });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}, [router, path]);
|
|
37
|
+
useEffect(() => fetcher.dispose, [fetcher]);
|
|
38
|
+
const state = useSyncExternalStore(fetcher.subscribe, fetcher.getSnapshot);
|
|
39
|
+
return {
|
|
40
|
+
...fetcher,
|
|
41
|
+
...state
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { useFetcher };
|
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
* Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
|
-
import type
|
|
6
|
-
import
|
|
5
|
+
import { type Fetcher } from "../core/index.ts";
|
|
6
|
+
import { type RouteFormOptions } from "../core/submission.ts";
|
|
7
|
+
import { type FormHTMLAttributes, type ReactNode } from "./runtime.ts";
|
|
7
8
|
/** Form attributes with GET loader requests or POST, PUT, PATCH, and DELETE actions. */
|
|
8
|
-
export interface FormProps extends Omit<FormHTMLAttributes<HTMLFormElement>, "action" | "method"
|
|
9
|
-
action?: string;
|
|
10
|
-
method?: "get" | "post" | "put" | "patch" | "delete";
|
|
9
|
+
export interface FormProps extends Omit<FormHTMLAttributes<HTMLFormElement>, "action" | "method">, RouteFormOptions {
|
|
11
10
|
}
|
|
12
11
|
interface InternalFormProps extends FormProps {
|
|
13
12
|
fetcher?: Fetcher;
|
|
14
13
|
}
|
|
15
14
|
/** Submit form data to a registered route without a document reload. */
|
|
16
|
-
export declare function Form(
|
|
15
|
+
export declare function Form(options: InternalFormProps): ReactNode;
|
|
17
16
|
export {};
|