@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,93 @@
|
|
|
1
|
+
/** Browser mounting and hydration for Preact applications. @module */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
|
|
4
|
+
import { createDataRouter, matchRoutes, type Router } from "../core/index.ts";
|
|
5
|
+
import { connectNavigation } from "../core/navigation.ts";
|
|
6
|
+
import { retainRoute } from "../core/presentation.ts";
|
|
7
|
+
import { hydrate, render } from "preact";
|
|
8
|
+
import { flushSync } from "preact/compat";
|
|
9
|
+
import { attachRootData } from "./root-data.ts";
|
|
10
|
+
import type { AppRoute, Hydration, RootModule } from "./route-types.ts";
|
|
11
|
+
import { prepareRoutes, RouterApplication } from "./router.tsx";
|
|
12
|
+
|
|
13
|
+
export * from "../client/index.ts";
|
|
14
|
+
export { configureApplication } from "../core/environment.ts";
|
|
15
|
+
|
|
16
|
+
export async function mountApplication(
|
|
17
|
+
routes: AppRoute[],
|
|
18
|
+
rootModule?: RootModule,
|
|
19
|
+
hash = false,
|
|
20
|
+
): Promise<Router> {
|
|
21
|
+
const initialURL = new URL(
|
|
22
|
+
hash ? location.hash.slice(1) || "/" : location.href,
|
|
23
|
+
location.origin,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const initialRoute = matchRoutes(routes, initialURL.pathname)?.route as
|
|
27
|
+
| AppRoute
|
|
28
|
+
| undefined;
|
|
29
|
+
|
|
30
|
+
await initialRoute?.preload?.();
|
|
31
|
+
|
|
32
|
+
routes = prepareRoutes(routes);
|
|
33
|
+
const element = document.getElementById("smolter-data");
|
|
34
|
+
const hydration: Hydration | undefined = element?.textContent
|
|
35
|
+
? JSON.parse(element.textContent)
|
|
36
|
+
: undefined;
|
|
37
|
+
|
|
38
|
+
if (hydration?.snapshot.error)
|
|
39
|
+
hydration.snapshot.error = Object.assign(
|
|
40
|
+
new Error(hydration.snapshot.error.message),
|
|
41
|
+
hydration.snapshot.error,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const router = createDataRouter(routes, {
|
|
45
|
+
browser: !hash,
|
|
46
|
+
url: hash
|
|
47
|
+
? new URL(location.hash.slice(1) || "/", location.origin).href
|
|
48
|
+
: location.href,
|
|
49
|
+
...(hydration && { hydrationData: hydration.snapshot }),
|
|
50
|
+
});
|
|
51
|
+
retainRoute(router, flushSync);
|
|
52
|
+
|
|
53
|
+
if (hash) connectHashHistory(router);
|
|
54
|
+
|
|
55
|
+
connectNavigation(router);
|
|
56
|
+
const rootStore = attachRootData(router, hydration?.root, rootModule);
|
|
57
|
+
const props = {
|
|
58
|
+
routes,
|
|
59
|
+
router,
|
|
60
|
+
root: hydration?.root,
|
|
61
|
+
rootModule,
|
|
62
|
+
rootStore,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (hydration) {
|
|
66
|
+
hydrate(<RouterApplication {...props} document />, document);
|
|
67
|
+
|
|
68
|
+
return router;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const target = document.getElementById("root");
|
|
72
|
+
|
|
73
|
+
if (target) render(<RouterApplication {...props} />, target);
|
|
74
|
+
|
|
75
|
+
return router;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function connectHashHistory(router: Router) {
|
|
79
|
+
const navigate = router.navigate;
|
|
80
|
+
router.navigate = async (url, options) => {
|
|
81
|
+
const target = new URL(url, router.getSnapshot().url);
|
|
82
|
+
const hash = `#${target.pathname}${target.search}`;
|
|
83
|
+
history[options?.replace ? "replaceState" : "pushState"](null, "", hash);
|
|
84
|
+
await navigate(url, options);
|
|
85
|
+
};
|
|
86
|
+
const onHashChange = () => void navigate(location.hash.slice(1) || "/");
|
|
87
|
+
window.addEventListener("hashchange", onHashChange);
|
|
88
|
+
const dispose = router.dispose;
|
|
89
|
+
router.dispose = () => {
|
|
90
|
+
window.removeEventListener("hashchange", onHashChange);
|
|
91
|
+
dispose();
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { NavigationRouter, Snapshot } from "../core/index.ts";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
createContext,
|
|
10
|
+
createElement,
|
|
11
|
+
type ReactNode,
|
|
12
|
+
useContext,
|
|
13
|
+
useEffect,
|
|
14
|
+
useSyncExternalStore,
|
|
15
|
+
} from "./runtime.ts";
|
|
16
|
+
|
|
17
|
+
const RouterContext = createContext<NavigationRouter | undefined>(undefined);
|
|
18
|
+
|
|
19
|
+
/** Router instance and children rendered within its context. */
|
|
20
|
+
export interface RouterProviderProps {
|
|
21
|
+
router: NavigationRouter;
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Provide a router to descendant hooks and connect its browser lifecycle. */
|
|
26
|
+
export function RouterProvider({
|
|
27
|
+
router,
|
|
28
|
+
children,
|
|
29
|
+
}: RouterProviderProps): ReactNode {
|
|
30
|
+
useEffect(router.start, [router]);
|
|
31
|
+
|
|
32
|
+
return createElement(RouterContext, { value: router }, children);
|
|
33
|
+
}
|
|
34
|
+
/** Hooks require a RouterProvider. */
|
|
35
|
+
export function useRouter<T extends NavigationRouter = NavigationRouter>(): T {
|
|
36
|
+
return useContext(RouterContext) as T;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useSnapshot(): Snapshot;
|
|
40
|
+
|
|
41
|
+
export function useSnapshot<K extends keyof Snapshot>(key: K): Snapshot[K];
|
|
42
|
+
|
|
43
|
+
export function useSnapshot<K extends keyof Snapshot>(
|
|
44
|
+
key: K,
|
|
45
|
+
path: string | undefined,
|
|
46
|
+
): Snapshot[K] | undefined;
|
|
47
|
+
|
|
48
|
+
export function useSnapshot(key?: keyof Snapshot, path?: string) {
|
|
49
|
+
const router = useRouter();
|
|
50
|
+
const getSnapshot = () => {
|
|
51
|
+
const snapshot = router.getSnapshot();
|
|
52
|
+
|
|
53
|
+
if (path !== undefined && path !== snapshot.path) return undefined;
|
|
54
|
+
|
|
55
|
+
return key ? snapshot[key] : snapshot;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return useSyncExternalStore(router.subscribe, getSnapshot);
|
|
59
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/** @jsxImportSource preact */
|
|
2
|
+
|
|
3
|
+
import { applicationEnvironment } from "../core/environment.ts";
|
|
4
|
+
import { scrollPositions } from "../core/navigation.ts";
|
|
5
|
+
import { serializeData } from "../core/serialize.ts";
|
|
6
|
+
import { createElement, Fragment } from "preact";
|
|
7
|
+
import type { ComponentProps } from "preact/compat";
|
|
8
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
|
9
|
+
import { useNavigationSnapshot } from "./navigation-state.ts";
|
|
10
|
+
import { useApplication } from "./root-context.ts";
|
|
11
|
+
import { useSnapshot } from "./route-hooks.tsx";
|
|
12
|
+
import type { MetaDescriptor } from "./route-types.ts";
|
|
13
|
+
|
|
14
|
+
function renderMeta(item: MetaDescriptor, index: number) {
|
|
15
|
+
if (typeof item.title === "string")
|
|
16
|
+
return <title key={index}>{item.title}</title>;
|
|
17
|
+
|
|
18
|
+
if ("script:ld+json" in item)
|
|
19
|
+
return (
|
|
20
|
+
<script
|
|
21
|
+
key={index}
|
|
22
|
+
type="application/ld+json"
|
|
23
|
+
dangerouslySetInnerHTML={{
|
|
24
|
+
__html: serializeData(item["script:ld+json"]),
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const { tagName, ...props } = item;
|
|
30
|
+
|
|
31
|
+
return createElement(tagName === "link" ? "link" : "meta", {
|
|
32
|
+
...props,
|
|
33
|
+
key: index,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function Meta(): import("preact").JSX.Element {
|
|
38
|
+
const { routes, rootModule } = useApplication();
|
|
39
|
+
const snapshot = useSnapshot();
|
|
40
|
+
const route = routes.find((item) => item.path === snapshot.path);
|
|
41
|
+
const args = {
|
|
42
|
+
loaderData: snapshot.data,
|
|
43
|
+
data: snapshot.data,
|
|
44
|
+
params: snapshot.params,
|
|
45
|
+
error: snapshot.error,
|
|
46
|
+
location: new URL(snapshot.url),
|
|
47
|
+
};
|
|
48
|
+
const values = route?.meta?.(args) ?? rootModule?.meta?.(args) ?? [];
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const title = values.find((item) => typeof item.title === "string")?.title;
|
|
51
|
+
|
|
52
|
+
if (typeof title === "string") document.title = title;
|
|
53
|
+
}, [values]);
|
|
54
|
+
|
|
55
|
+
return <Fragment>{values.map(renderMeta)}</Fragment>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function Links(): import("preact").JSX.Element {
|
|
59
|
+
const { rootModule } = useApplication();
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Fragment>
|
|
63
|
+
{rootModule?.links?.().map((props, index) => (
|
|
64
|
+
<link key={index} {...props} />
|
|
65
|
+
))}
|
|
66
|
+
</Fragment>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ScriptProps {
|
|
71
|
+
nonce?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function Scripts({ nonce }: ScriptProps): import("preact").JSX.Element {
|
|
75
|
+
const { root, router, entry } = useApplication();
|
|
76
|
+
// The bootstrap payload describes this document's initial SSR snapshot.
|
|
77
|
+
// Navigation and revalidation update the router, not the hydration script.
|
|
78
|
+
const [json] = useState(() => {
|
|
79
|
+
const current = router.getSnapshot();
|
|
80
|
+
const snapshot = {
|
|
81
|
+
...current,
|
|
82
|
+
error: current.error && {
|
|
83
|
+
...current.error,
|
|
84
|
+
name: current.error.name,
|
|
85
|
+
message: current.error.message,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return serializeData({ root, snapshot });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Fragment>
|
|
94
|
+
<script
|
|
95
|
+
{...(nonce && { nonce })}
|
|
96
|
+
dangerouslySetInnerHTML={{
|
|
97
|
+
__html: applicationEnvironment?.entryScript ?? "",
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
<script
|
|
101
|
+
id="smolter-data"
|
|
102
|
+
type="application/json"
|
|
103
|
+
{...(nonce && { nonce })}
|
|
104
|
+
dangerouslySetInnerHTML={{ __html: json }}
|
|
105
|
+
/>
|
|
106
|
+
{entry && <script type="module" src={entry} {...(nonce && { nonce })} />}
|
|
107
|
+
</Fragment>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function ScrollRestoration(_props: ScriptProps): null {
|
|
112
|
+
const { router } = useApplication();
|
|
113
|
+
const { url, state } = useSnapshot();
|
|
114
|
+
const navigation = useNavigationSnapshot(router);
|
|
115
|
+
const positions = scrollPositions(router);
|
|
116
|
+
const restoredLocation = useRef("");
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
const previous = history.scrollRestoration;
|
|
120
|
+
history.scrollRestoration = "manual";
|
|
121
|
+
|
|
122
|
+
return () => {
|
|
123
|
+
history.scrollRestoration = previous;
|
|
124
|
+
};
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
useLayoutEffect(() => {
|
|
128
|
+
if (state !== "idle") return;
|
|
129
|
+
|
|
130
|
+
if ((history.state?.key ?? "initial") !== navigation.key) return;
|
|
131
|
+
|
|
132
|
+
const locationKey = `${navigation.key}:${url}`;
|
|
133
|
+
|
|
134
|
+
if (restoredLocation.current === locationKey) return;
|
|
135
|
+
|
|
136
|
+
// Lazy views subscribe independently; restore after their DOM is committed.
|
|
137
|
+
const frame = requestAnimationFrame(() => {
|
|
138
|
+
restoredLocation.current = locationKey;
|
|
139
|
+
const target = new URL(location.href);
|
|
140
|
+
const saved = navigation.type === "POP" && positions.get(navigation.key);
|
|
141
|
+
|
|
142
|
+
if (!saved && target.hash) {
|
|
143
|
+
document.getElementById(target.hash.slice(1))?.scrollIntoView();
|
|
144
|
+
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!saved && navigation.preventScrollReset) return;
|
|
149
|
+
|
|
150
|
+
const [left, top] = saved || [0, 0];
|
|
151
|
+
window.scrollTo({ left, top, behavior: "instant" });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return () => cancelAnimationFrame(frame);
|
|
155
|
+
}, [
|
|
156
|
+
url,
|
|
157
|
+
state,
|
|
158
|
+
positions,
|
|
159
|
+
navigation.key,
|
|
160
|
+
navigation.type,
|
|
161
|
+
navigation.preventScrollReset,
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** HTML attributes for server markup and client document reconciliation. */
|
|
168
|
+
export interface DocumentProps extends ComponentProps<"html"> {
|
|
169
|
+
"data-debug"?: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Render the server document and preserve client-managed theme classes on hydration. */
|
|
173
|
+
export function Document(props: DocumentProps): import("preact").JSX.Element {
|
|
174
|
+
const { children, ...attributes } = props;
|
|
175
|
+
const className = String(props.className ?? "");
|
|
176
|
+
const previousClassName = useRef(className);
|
|
177
|
+
const lang = String(props.lang ?? "en");
|
|
178
|
+
const debug = String(props["data-debug"] ?? false);
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
// Hydration must preserve classes already applied by the browser theme store.
|
|
181
|
+
// Only reconcile classes when the document's own props actually change.
|
|
182
|
+
if (previousClassName.current !== className) {
|
|
183
|
+
const previous = previousClassName.current.split(/\s+/).filter(Boolean);
|
|
184
|
+
const next = className.split(/\s+/).filter(Boolean);
|
|
185
|
+
document.documentElement.classList.remove(...previous);
|
|
186
|
+
document.documentElement.classList.add(...next);
|
|
187
|
+
previousClassName.current = className;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
document.documentElement.lang = lang;
|
|
191
|
+
document.documentElement.dataset.debug = debug;
|
|
192
|
+
}, [className, lang, debug]);
|
|
193
|
+
|
|
194
|
+
if (typeof document === "undefined")
|
|
195
|
+
return <html {...attributes}>{children}</html>;
|
|
196
|
+
|
|
197
|
+
return <>{children}</>;
|
|
198
|
+
}
|
|
@@ -3,20 +3,29 @@
|
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
AnyDataFunction,
|
|
8
|
-
DataFunction,
|
|
9
|
-
DataOf,
|
|
10
|
-
FetcherSnapshot,
|
|
11
|
-
Result,
|
|
12
|
-
Route as RouteDefinition,
|
|
13
|
-
RouteModule,
|
|
14
|
-
|
|
6
|
+
import {
|
|
7
|
+
type AnyDataFunction,
|
|
8
|
+
type DataFunction,
|
|
9
|
+
type DataOf,
|
|
10
|
+
type FetcherSnapshot,
|
|
11
|
+
type Result,
|
|
12
|
+
type Route as RouteDefinition,
|
|
13
|
+
type RouteModule,
|
|
14
|
+
type Router,
|
|
15
|
+
type SubmitOptions,
|
|
15
16
|
} from "../core/index.ts";
|
|
17
|
+
import {
|
|
18
|
+
type Submission,
|
|
19
|
+
type SubmissionOptions,
|
|
20
|
+
submissionBody,
|
|
21
|
+
submissionSearch,
|
|
22
|
+
} from "../core/submission.ts";
|
|
16
23
|
import { useRouter } from "./context.ts";
|
|
17
24
|
import { Form, type FormProps } from "./form.ts";
|
|
18
|
-
import type {
|
|
25
|
+
import type { RouteData } from "./route-types.ts";
|
|
26
|
+
|
|
19
27
|
import {
|
|
28
|
+
type ComponentType,
|
|
20
29
|
createElement,
|
|
21
30
|
useEffect,
|
|
22
31
|
useMemo,
|
|
@@ -24,7 +33,7 @@ import {
|
|
|
24
33
|
} from "./runtime.ts";
|
|
25
34
|
|
|
26
35
|
/** Action method and optional explicit route URL for a fetcher submission. */
|
|
27
|
-
export interface FetcherSubmitOptions extends
|
|
36
|
+
export interface FetcherSubmitOptions extends SubmissionOptions {
|
|
28
37
|
action?: string;
|
|
29
38
|
}
|
|
30
39
|
|
|
@@ -33,7 +42,7 @@ export interface TypedFetcher<T> extends Omit<FetcherSnapshot, "data"> {
|
|
|
33
42
|
data?: T;
|
|
34
43
|
Form: ComponentType<FormProps>;
|
|
35
44
|
load: (url?: string) => Promise<Result>;
|
|
36
|
-
submit: (body:
|
|
45
|
+
submit: (body: Submission, options?: FetcherSubmitOptions) => Promise<Result>;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
/** Create a fetcher for independent route reads and mutations; its data stays local. */
|
|
@@ -45,13 +54,11 @@ export function useFetcher<T extends RouteModule>(
|
|
|
45
54
|
>;
|
|
46
55
|
|
|
47
56
|
/** Create a fetcher for independent route reads and mutations; its data stays local. */
|
|
48
|
-
export function useFetcher<
|
|
49
|
-
T extends AnyDataFunction = DataFunction,
|
|
50
|
-
>(): TypedFetcher<DataOf<T>>;
|
|
57
|
+
export function useFetcher<T = DataFunction>(): TypedFetcher<RouteData<T>>;
|
|
51
58
|
|
|
52
59
|
/** Create a fetcher for independent route reads and mutations; its data stays local. */
|
|
53
60
|
export function useFetcher(route?: RouteDefinition): TypedFetcher<unknown> {
|
|
54
|
-
const router = useRouter();
|
|
61
|
+
const router = useRouter<Router>();
|
|
55
62
|
const path = route?.path;
|
|
56
63
|
const fetcher = useMemo(() => {
|
|
57
64
|
const store = router.createFetcher();
|
|
@@ -66,18 +73,31 @@ export function useFetcher(route?: RouteDefinition): TypedFetcher<unknown> {
|
|
|
66
73
|
Form: (props: FormProps) =>
|
|
67
74
|
createElement(Form, { action: target(), ...props, fetcher: store }),
|
|
68
75
|
load: (url?: string) => store.load(url ?? target()),
|
|
69
|
-
submit: (body:
|
|
70
|
-
|
|
76
|
+
submit: (body: Submission, options: FetcherSubmitOptions = {}) => {
|
|
77
|
+
const action = options.action ?? target();
|
|
78
|
+
const data = submissionBody(body, options.encType);
|
|
79
|
+
const method = options.method?.toUpperCase();
|
|
80
|
+
|
|
81
|
+
if (method === "GET") {
|
|
82
|
+
const url = new URL(action, router.getSnapshot().url);
|
|
83
|
+
url.search = submissionSearch(data);
|
|
84
|
+
|
|
85
|
+
return store.load(url.href);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return store.submit(action, data, {
|
|
89
|
+
method: method as SubmitOptions["method"],
|
|
90
|
+
});
|
|
91
|
+
},
|
|
71
92
|
};
|
|
72
93
|
}, [router, path]);
|
|
73
94
|
|
|
74
95
|
useEffect(() => fetcher.dispose, [fetcher]);
|
|
75
96
|
|
|
76
|
-
const state = useSyncExternalStore(
|
|
77
|
-
fetcher.subscribe,
|
|
78
|
-
fetcher.getSnapshot,
|
|
79
|
-
fetcher.getSnapshot,
|
|
80
|
-
);
|
|
97
|
+
const state = useSyncExternalStore(fetcher.subscribe, fetcher.getSnapshot);
|
|
81
98
|
|
|
82
99
|
return { ...fetcher, ...state };
|
|
83
100
|
}
|
|
101
|
+
|
|
102
|
+
/** A fetcher with a bound form and inferred result data. */
|
|
103
|
+
export type FetcherWithComponents<T> = TypedFetcher<RouteData<T>>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preact components and typed data hooks. Import from @kasperrt/smolter/preact.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { type Fetcher, type Router } from "../core/index.ts";
|
|
7
|
+
import { type RouteFormOptions, submitForm } from "../core/submission.ts";
|
|
8
|
+
import { useRouter } from "./context.ts";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
createElement,
|
|
12
|
+
type FormHTMLAttributes,
|
|
13
|
+
type ReactNode,
|
|
14
|
+
} from "./runtime.ts";
|
|
15
|
+
|
|
16
|
+
/** Form attributes with GET loader requests or POST, PUT, PATCH, and DELETE actions. */
|
|
17
|
+
export interface FormProps
|
|
18
|
+
extends Omit<FormHTMLAttributes<HTMLFormElement>, "action" | "method">,
|
|
19
|
+
RouteFormOptions {}
|
|
20
|
+
|
|
21
|
+
type FormSubmitEvent = Parameters<NonNullable<FormProps["onSubmit"]>>[0];
|
|
22
|
+
|
|
23
|
+
interface InternalFormProps extends FormProps {
|
|
24
|
+
fetcher?: Fetcher;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Submit form data to a registered route without a document reload. */
|
|
28
|
+
export function Form(options: InternalFormProps): ReactNode {
|
|
29
|
+
const {
|
|
30
|
+
action,
|
|
31
|
+
method,
|
|
32
|
+
fetcher,
|
|
33
|
+
onSubmit,
|
|
34
|
+
reloadDocument,
|
|
35
|
+
preventScrollReset,
|
|
36
|
+
replace,
|
|
37
|
+
...props
|
|
38
|
+
} = options;
|
|
39
|
+
const router = useRouter<Router>();
|
|
40
|
+
const submit = (event: FormSubmitEvent) => {
|
|
41
|
+
onSubmit?.(event);
|
|
42
|
+
|
|
43
|
+
submitForm(
|
|
44
|
+
router,
|
|
45
|
+
event,
|
|
46
|
+
(event as SubmitEvent).submitter,
|
|
47
|
+
options,
|
|
48
|
+
fetcher,
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return createElement("form", {
|
|
53
|
+
...props,
|
|
54
|
+
action,
|
|
55
|
+
method: method === "get" ? "get" : "post",
|
|
56
|
+
onSubmit: submit,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -5,68 +5,65 @@
|
|
|
5
5
|
|
|
6
6
|
import type {
|
|
7
7
|
AnyDataFunction,
|
|
8
|
-
DataOf,
|
|
9
8
|
Route,
|
|
10
9
|
RouteModule,
|
|
11
10
|
Router,
|
|
12
11
|
Snapshot,
|
|
13
12
|
} from "../core/index.ts";
|
|
14
|
-
|
|
15
13
|
import { useRouter, useSnapshot } from "./context.ts";
|
|
14
|
+
import type { RouteData } from "./route-types.ts";
|
|
16
15
|
|
|
17
16
|
/** A route module or loader function used for data inference. */
|
|
18
17
|
export type LoaderSource = RouteModule | AnyDataFunction;
|
|
19
18
|
|
|
20
19
|
/** Infer server and client loader data from a route module or function. */
|
|
21
20
|
export type LoaderData<T> = T extends AnyDataFunction
|
|
22
|
-
?
|
|
21
|
+
? RouteData<T>
|
|
23
22
|
: T extends RouteModule
|
|
24
|
-
?
|
|
25
|
-
:
|
|
23
|
+
? RouteData<Extract<T["loader"] | T["clientLoader"], AnyDataFunction>>
|
|
24
|
+
: RouteData<T>;
|
|
26
25
|
|
|
27
26
|
/** Infer server and client action data from a route module or function. */
|
|
28
27
|
export type ActionData<T> = T extends AnyDataFunction
|
|
29
|
-
?
|
|
28
|
+
? RouteData<T>
|
|
30
29
|
: T extends RouteModule
|
|
31
|
-
?
|
|
32
|
-
:
|
|
33
|
-
function useData(key: "data" | "actionData", route?: LoaderSource): unknown {
|
|
34
|
-
const path = (route as Route | undefined)?.path;
|
|
35
|
-
|
|
36
|
-
return useSnapshot((snapshot) => {
|
|
37
|
-
if (path !== undefined && path !== snapshot.path) return undefined;
|
|
38
|
-
|
|
39
|
-
return snapshot[key];
|
|
40
|
-
});
|
|
41
|
-
}
|
|
30
|
+
? RouteData<Extract<T["action"] | T["clientAction"], AnyDataFunction>>
|
|
31
|
+
: RouteData<T>;
|
|
42
32
|
/** Pass a route for inference, or use useLoaderData<typeof loader>() in its module. */
|
|
43
|
-
export function useLoaderData<T
|
|
44
|
-
route?: T,
|
|
45
|
-
): LoaderData<T>
|
|
46
|
-
return
|
|
33
|
+
export function useLoaderData<T = RouteModule>(
|
|
34
|
+
route?: T & LoaderSource,
|
|
35
|
+
): LoaderData<T> {
|
|
36
|
+
return useSnapshot(
|
|
37
|
+
"data",
|
|
38
|
+
(route as Route | undefined)?.path,
|
|
39
|
+
) as LoaderData<T>;
|
|
47
40
|
}
|
|
48
41
|
|
|
49
42
|
/** Read the last page action result, optionally inferring its type from a route. */
|
|
50
|
-
export function useActionData<T
|
|
51
|
-
route?: T,
|
|
43
|
+
export function useActionData<T = RouteModule>(
|
|
44
|
+
route?: T & LoaderSource,
|
|
52
45
|
): ActionData<T> | undefined {
|
|
53
|
-
return
|
|
46
|
+
return useSnapshot("actionData", (route as Route | undefined)?.path) as
|
|
47
|
+
| ActionData<T>
|
|
48
|
+
| undefined;
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
/** Read path parameters for the active route. */
|
|
57
|
-
export function useParams
|
|
58
|
-
|
|
52
|
+
export function useParams<
|
|
53
|
+
T extends Snapshot["params"] = Snapshot["params"],
|
|
54
|
+
>(): T {
|
|
55
|
+
return useSnapshot("params") as T;
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
/** Read the current path, search, and hash together with the navigate function. */
|
|
62
59
|
export function useLocation(): readonly [string, Router["navigate"]] {
|
|
63
60
|
const router = useRouter();
|
|
64
|
-
const url = useSnapshot(
|
|
61
|
+
const url = useSnapshot("url");
|
|
65
62
|
|
|
66
63
|
return [url.replace(/^\w+:\/\/[^/]+/, ""), router.navigate];
|
|
67
64
|
}
|
|
68
65
|
|
|
69
66
|
/** Get the function that revalidates the current loader subject to shouldRevalidate. */
|
|
70
67
|
export function useRevalidator(): Router["revalidate"] {
|
|
71
|
-
return useRouter().revalidate;
|
|
68
|
+
return useRouter<Router>().revalidate;
|
|
72
69
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Framework components and typed data hooks. @module */
|
|
2
|
+
export type * from "../core/index.ts";
|
|
3
|
+
export {
|
|
4
|
+
createDataRouter,
|
|
5
|
+
createRouter,
|
|
6
|
+
defineRoute,
|
|
7
|
+
} from "../core/index.ts";
|
|
8
|
+
export type { ApplicationEnvironment } from "../core/environment.ts";
|
|
9
|
+
export * from "../core/responses.ts";
|
|
10
|
+
export type { Submission, SubmissionOptions } from "../core/submission.ts";
|
|
11
|
+
export { readSubmission } from "../core/submission.ts";
|
|
12
|
+
export { Await } from "./await.tsx";
|
|
13
|
+
export {
|
|
14
|
+
RouterProvider,
|
|
15
|
+
type RouterProviderProps,
|
|
16
|
+
useRouter,
|
|
17
|
+
} from "./context.ts";
|
|
18
|
+
export * from "./fetcher.ts";
|
|
19
|
+
export * from "./form.ts";
|
|
20
|
+
export * from "./hooks.ts";
|
|
21
|
+
export type { NavigateOptions } from "./navigation.tsx";
|
|
22
|
+
export * from "./navigation.tsx";
|
|
23
|
+
export {
|
|
24
|
+
useMatches,
|
|
25
|
+
useNavigation,
|
|
26
|
+
useOutletContext,
|
|
27
|
+
useRootData,
|
|
28
|
+
useRouteError,
|
|
29
|
+
useRouteLoaderData,
|
|
30
|
+
useSubmit,
|
|
31
|
+
} from "./route-hooks.tsx";
|
|
32
|
+
export * from "./route-types.ts";
|
|
33
|
+
export { Outlet, RouterApplication } from "./router.tsx";
|
|
34
|
+
export * from "./routes.ts";
|