@kasperrt/smolter 0.4.2 → 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.
Files changed (151) hide show
  1. package/.generated/client/hash-history.ts +27 -0
  2. package/.generated/client/hydration.ts +98 -0
  3. package/.generated/client/root-data.ts +52 -0
  4. package/.generated/core/environment.ts +1 -0
  5. package/.generated/core/navigation.ts +1 -0
  6. package/.generated/core/prepare-routes.ts +50 -0
  7. package/.generated/core/presentation.ts +1 -1
  8. package/.generated/core/responses.ts +12 -0
  9. package/.generated/core/utils.ts +5 -2
  10. package/.generated/preact/await.tsx +1 -0
  11. package/.generated/preact/client.tsx +8 -27
  12. package/.generated/preact/deferred-hydration.ts +1 -98
  13. package/.generated/preact/document.tsx +10 -0
  14. package/.generated/preact/index.ts +11 -2
  15. package/.generated/preact/lazy-route.ts +7 -0
  16. package/.generated/preact/navigation.tsx +19 -5
  17. package/.generated/preact/root-data.ts +1 -45
  18. package/.generated/preact/route-hooks.tsx +12 -2
  19. package/.generated/preact/route-types.ts +14 -0
  20. package/.generated/preact/router.tsx +5 -48
  21. package/.generated/react/client.tsx +130 -0
  22. package/.generated/react/document.tsx +235 -0
  23. package/.generated/react/fetcher.ts +29 -7
  24. package/.generated/react/hooks.ts +20 -18
  25. package/.generated/react/index.ts +28 -2
  26. package/.generated/react/lazy-route.ts +116 -0
  27. package/.generated/react/link.ts +1 -38
  28. package/.generated/react/navigation-state.ts +13 -0
  29. package/.generated/react/navigation.ts +145 -0
  30. package/.generated/react/outlet.ts +48 -0
  31. package/.generated/react/root-context.ts +22 -0
  32. package/.generated/react/route-hooks.ts +159 -0
  33. package/.generated/react/route-types.ts +91 -0
  34. package/.generated/react/router.ts +50 -0
  35. package/.generated/react/runtime.ts +1 -0
  36. package/.generated/server/application.tsx +18 -250
  37. package/.generated/server/assets.ts +7 -0
  38. package/.generated/server/dispatch.ts +87 -0
  39. package/.generated/server/handler.ts +3 -0
  40. package/.generated/server/http.ts +2 -0
  41. package/.generated/server/index.ts +2 -0
  42. package/.generated/server/node.ts +5 -2
  43. package/.generated/server/preact.ts +5 -2
  44. package/.generated/server/react.ts +34 -0
  45. package/.generated/server/render-application.ts +203 -0
  46. package/.generated/vite/application/lazy-files.ts +2 -1
  47. package/.generated/vite/application/vite.ts +12 -4
  48. package/.generated/vite/react.ts +11 -0
  49. package/CHANGELOG.md +20 -0
  50. package/README.md +48 -48
  51. package/dist/client/hash-history.d.ts +2 -0
  52. package/dist/client/hash-history.js +26 -0
  53. package/dist/client/hydration.d.ts +10 -0
  54. package/dist/{preact/deferred-hydration.js → client/hydration.js} +1 -1
  55. package/dist/client/root-data.d.ts +14 -0
  56. package/dist/{preact → client}/root-data.js +1 -1
  57. package/dist/core/environment.d.ts +1 -0
  58. package/dist/core/navigation.d.ts +1 -0
  59. package/dist/core/prepare-routes.d.ts +7 -0
  60. package/dist/core/prepare-routes.js +28 -0
  61. package/dist/core/presentation.js +1 -1
  62. package/dist/core/responses.d.ts +12 -0
  63. package/dist/core/responses.js +5 -0
  64. package/dist/core/utils.d.ts +5 -2
  65. package/dist/core/utils.js +1 -2
  66. package/dist/preact/await.d.ts +1 -0
  67. package/dist/preact/await.js +1 -0
  68. package/dist/preact/client.d.ts +7 -1
  69. package/dist/preact/client.js +11 -27
  70. package/dist/preact/deferred-hydration.d.ts +1 -10
  71. package/dist/preact/document.d.ts +10 -0
  72. package/dist/preact/document.js +11 -2
  73. package/dist/preact/index.d.ts +6 -2
  74. package/dist/preact/index.js +1 -1
  75. package/dist/preact/lazy-route.d.ts +2 -0
  76. package/dist/preact/lazy-route.js +6 -0
  77. package/dist/preact/navigation-state.js +1 -1
  78. package/dist/preact/navigation.d.ts +6 -0
  79. package/dist/preact/navigation.js +9 -5
  80. package/dist/preact/root-data.d.ts +1 -8
  81. package/dist/preact/route-hooks.d.ts +10 -0
  82. package/dist/preact/route-hooks.js +13 -3
  83. package/dist/preact/route-types.d.ts +14 -0
  84. package/dist/preact/router.d.ts +5 -2
  85. package/dist/preact/router.js +4 -26
  86. package/dist/react/client.d.ts +20 -0
  87. package/dist/react/client.js +86 -0
  88. package/dist/react/document.d.ts +25 -0
  89. package/dist/react/document.js +169 -0
  90. package/dist/react/fetcher.d.ts +8 -4
  91. package/dist/react/fetcher.js +12 -1
  92. package/dist/react/form.js +1 -1
  93. package/dist/react/hooks.d.ts +7 -6
  94. package/dist/react/index.d.ts +13 -2
  95. package/dist/react/index.js +7 -2
  96. package/dist/react/lazy-route.d.ts +22 -0
  97. package/dist/react/lazy-route.js +57 -0
  98. package/dist/react/link.d.ts +1 -12
  99. package/dist/react/navigation-state.d.ts +2 -0
  100. package/dist/react/navigation-state.js +9 -0
  101. package/dist/react/navigation.d.ts +33 -0
  102. package/dist/react/navigation.js +64 -0
  103. package/dist/react/outlet.d.ts +16 -0
  104. package/dist/react/outlet.js +26 -0
  105. package/dist/react/root-context.d.ts +14 -0
  106. package/dist/react/root-context.js +10 -0
  107. package/dist/react/route-hooks.d.ts +34 -0
  108. package/dist/react/route-hooks.js +89 -0
  109. package/dist/react/route-types.d.ts +69 -0
  110. package/dist/react/router.d.ts +10 -0
  111. package/dist/react/router.js +22 -0
  112. package/dist/react/runtime.d.ts +1 -1
  113. package/dist/react/runtime.js +1 -1
  114. package/dist/server/application.d.ts +4 -5
  115. package/dist/server/application.js +9 -154
  116. package/dist/server/assets.d.ts +2 -0
  117. package/dist/server/assets.js +6 -0
  118. package/dist/server/dispatch.d.ts +5 -0
  119. package/dist/server/dispatch.js +50 -0
  120. package/dist/server/handler.d.ts +3 -0
  121. package/dist/server/http.d.ts +2 -0
  122. package/dist/server/http.js +2 -0
  123. package/dist/server/index.d.ts +1 -0
  124. package/dist/server/index.js +2 -1
  125. package/dist/server/node.d.ts +5 -1
  126. package/dist/server/preact.d.ts +5 -1
  127. package/dist/server/preact.js +2 -1
  128. package/dist/server/react.d.ts +10 -0
  129. package/dist/server/react.js +22 -0
  130. package/dist/server/render-application.d.ts +31 -0
  131. package/dist/server/render-application.js +118 -0
  132. package/dist/vite/application/lazy-files.d.ts +1 -1
  133. package/dist/vite/application/lazy-files.js +2 -2
  134. package/dist/vite/application/vite.d.ts +2 -1
  135. package/dist/vite/application/vite.js +12 -5
  136. package/dist/vite/react.d.ts +4 -0
  137. package/dist/vite/react.js +9 -1
  138. package/jsr.json +10 -6
  139. package/package.json +21 -12
  140. package/.generated/core/resource-types.ts +0 -44
  141. package/.generated/core/resources.ts +0 -444
  142. package/.generated/react/data.ts +0 -25
  143. package/.generated/react/resources/components.tsx +0 -404
  144. package/dist/core/resource-types.d.ts +0 -37
  145. package/dist/core/resources.d.ts +0 -19
  146. package/dist/core/resources.js +0 -345
  147. package/dist/react/data.d.ts +0 -5
  148. package/dist/react/data.js +0 -3
  149. package/dist/react/link.js +0 -27
  150. package/dist/react/resources/components.d.ts +0 -64
  151. package/dist/react/resources/components.js +0 -239
@@ -1,20 +1,28 @@
1
1
  import type { DataArgs, RevalidationArgs, Route, Snapshot } from "../core/index.ts";
2
2
  import type { DataResponse } from "../core/responses.ts";
3
3
  import type { ComponentType, ReactNode } from "preact/compat";
4
+ /** Request, route parameters and cancellation signal supplied to a loader. */
4
5
  export interface LoaderFunctionArgs extends DataArgs {
5
6
  context?: unknown;
6
7
  }
8
+ /** Request, route parameters and cancellation signal supplied to an action. */
7
9
  export type ActionFunctionArgs = LoaderFunctionArgs;
10
+ /** Loader arguments with a serverLoader callback for the server transport. */
8
11
  export interface ClientLoaderFunctionArgs extends LoaderFunctionArgs {
9
12
  serverLoader: <T = unknown>() => Promise<T>;
10
13
  }
14
+ /** Action arguments with a serverAction callback for the server transport. */
11
15
  export interface ClientActionFunctionArgs extends LoaderFunctionArgs {
12
16
  serverAction: <T = unknown>() => Promise<T>;
13
17
  }
18
+ /** Navigation or mutation context used to decide whether loader data should refresh. */
14
19
  export type ShouldRevalidateFunctionArgs = RevalidationArgs;
15
20
  type UnwrapData<T> = T extends DataResponse<infer V> ? V : T;
21
+ /** Infer successful loader data, unwrapping promises and data responses. */
16
22
  export type RouteData<T> = T extends (...args: never[]) => infer R ? UnwrapData<Exclude<Awaited<R>, Response | Error>> : T;
23
+ /** A document metadata descriptor, such as a title, meta tag or structured-data record. */
17
24
  export type MetaDescriptor = Record<string, unknown>;
25
+ /** Loader data, error, parameters and location supplied to a route metadata function. */
18
26
  export interface MetaArgs<T> {
19
27
  data: RouteData<T> | undefined;
20
28
  loaderData: RouteData<T> | undefined;
@@ -22,8 +30,11 @@ export interface MetaArgs<T> {
22
30
  params: Record<string, string>;
23
31
  location: URL;
24
32
  }
33
+ /** Derive document metadata descriptors from the current route data and location. */
25
34
  export type MetaFunction<T = unknown> = (args: MetaArgs<T>) => MetaDescriptor[];
35
+ /** Return link descriptors rendered in the document head. */
26
36
  export type LinksFunction = () => Record<string, string>[];
37
+ /** Data route with optional lazy component, preload and document metadata integration. */
27
38
  export interface AppRoute extends Route {
28
39
  resolveComponent?: () => ComponentType | undefined;
29
40
  preload?: () => Promise<void>;
@@ -31,6 +42,7 @@ export interface AppRoute extends Route {
31
42
  routePath?: string;
32
43
  meta?: MetaFunction;
33
44
  }
45
+ /** Root document components, loader, metadata and revalidation policy for a Preact application. */
34
46
  export interface RootModule {
35
47
  /** Route identity used by targeted revalidation. Defaults to root. */
36
48
  id?: string;
@@ -46,10 +58,12 @@ export interface RootModule {
46
58
  meta?: MetaFunction;
47
59
  shouldRevalidate?: (args: RevalidationArgs) => boolean;
48
60
  }
61
+ /** Serialized root data and route snapshot used to hydrate without repeating initial loaders. */
49
62
  export interface Hydration {
50
63
  rootError?: boolean;
51
64
  root: unknown;
52
65
  snapshot: Snapshot;
53
66
  }
67
+ /** Transform a data transport response using the current request context. */
54
68
  export type HandleDataRequestFunction = (response: Response, args: LoaderFunctionArgs) => Response | Promise<Response>;
55
69
  export {};
@@ -2,15 +2,18 @@
2
2
  import type { ReactNode } from "preact/compat";
3
3
  import { type ApplicationContext } from "./root-context.ts";
4
4
  import { createRootStore } from "./root-data.ts";
5
- import type { AppRoute } from "./route-types.ts";
5
+ /** Optional children and context shared with descendants through useOutletContext. */
6
6
  export interface OutletProps {
7
7
  context?: unknown;
8
8
  children?: ReactNode;
9
9
  }
10
+ /** Render supplied children or the active route, and provide outlet context to descendants. */
10
11
  export declare function Outlet({ context, children, }: OutletProps): import("preact").JSX.Element;
12
+ /** Root data, document components and request-scoped router for an application. */
11
13
  export interface ApplicationProps extends ApplicationContext {
12
14
  document?: boolean;
13
15
  rootStore?: ReturnType<typeof createRootStore>;
14
16
  }
17
+ /** Render a Preact root document and active route with shared root data and application context. */
15
18
  export declare function RouterApplication(props: ApplicationProps): import("preact").JSX.Element;
16
- export declare function prepareRoutes(routes: AppRoute[], preserveDeferred?: boolean): AppRoute[];
19
+ export { prepareRoutes } from "../core/prepare-routes.ts";
@@ -1,11 +1,12 @@
1
- import { normalizeResult } from "../core/responses.js";
1
+ import { createStore } from "../core/store.js";
2
2
  import { useContext, useMemo, useSyncExternalStore } from "./runtime.js";
3
3
  import { Application, OutletContext } from "./root-context.js";
4
4
  import { RouterProvider } from "./context.js";
5
- import { createStore } from "../core/store.js";
6
5
  import { Routes } from "./routes.js";
6
+ import { prepareRoutes } from "../core/prepare-routes.js";
7
7
  import { jsx } from "preact/jsx-runtime";
8
8
  //#region .generated/preact/router.tsx
9
+ /** Render supplied children or the active route, and provide outlet context to descendants. */
9
10
  function Outlet({ context, children }) {
10
11
  const current = useContext(OutletContext);
11
12
  return /* @__PURE__ */ jsx(OutletContext, {
@@ -16,6 +17,7 @@ function Outlet({ context, children }) {
16
17
  }) })
17
18
  });
18
19
  }
20
+ /** Render a Preact root document and active route with shared root data and application context. */
19
21
  function RouterApplication(props) {
20
22
  const rootStore = useMemo(() => props.rootStore ?? createStore(props.root), [props.rootStore, props.root]);
21
23
  const root = useSyncExternalStore(rootStore.subscribe, rootStore.getSnapshot);
@@ -35,29 +37,5 @@ function RouterApplication(props) {
35
37
  })
36
38
  });
37
39
  }
38
- function prepareRoutes(routes, preserveDeferred = false) {
39
- return routes.map((route) => {
40
- const wrapped = {
41
- ...route,
42
- path: route.routePath ?? route.path,
43
- shouldRevalidate: (args) => {
44
- const { currentUrl, nextUrl } = args;
45
- if (args.reason === "navigation" && currentUrl.pathname === nextUrl.pathname && currentUrl.search === nextUrl.search && currentUrl.hash !== nextUrl.hash) return false;
46
- return route.shouldRevalidate?.(args) ?? args.defaultShouldRevalidate;
47
- }
48
- };
49
- for (const key of [
50
- "loader",
51
- "action",
52
- "clientLoader",
53
- "clientAction"
54
- ]) {
55
- const handler = route[key];
56
- if (!handler) continue;
57
- wrapped[key] = (args) => Promise.resolve().then(() => handler(args)).then((value) => normalizeResult(value, !preserveDeferred), (error) => normalizeResult(error, !preserveDeferred));
58
- }
59
- return wrapped;
60
- });
61
- }
62
40
  //#endregion
63
41
  export { Outlet, RouterApplication, prepareRoutes };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * React document hydration, browser history and deployment recovery.
3
+ *
4
+ * @module
5
+ */
6
+ /** @jsxImportSource react */
7
+ import { type DeploymentOptions } from "../client/index.ts";
8
+ import { type Router } from "../core/index.ts";
9
+ import type { AppRoute, RootModule } from "./route-types.ts";
10
+ export * from "../client/index.ts";
11
+ export { configureApplication } from "../core/environment.ts";
12
+ /** Browser hydration settings for hash history, deployment recovery and deferred-data timeout. */
13
+ export interface MountApplicationOptions {
14
+ hash?: boolean;
15
+ deployment?: DeploymentOptions | null;
16
+ /** Total initial deferred-data budget in integer milliseconds; not an inactivity timeout. Defaults to 30,000. */
17
+ deferredTimeout?: number;
18
+ }
19
+ /** Create the browser application router and hydrate the document, installing recovery before initial lazy loading. */
20
+ export declare function mountApplication(routes: AppRoute[], rootModule?: RootModule, options?: boolean | MountApplicationOptions): Promise<Router>;
@@ -0,0 +1,86 @@
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 { RouterApplication } from "./router.js";
7
+ import { attachRootData } from "../client/root-data.js";
8
+ import { prepareRoutes } from "../core/prepare-routes.js";
9
+ import { deserialize } from "../client/deserialize.js";
10
+ import { recoverDeployment, watchDeployment } from "../client/deployment.js";
11
+ import { serverRoute } from "../client/index.js";
12
+ import { connectHashHistory } from "../client/hash-history.js";
13
+ import { readHydration } from "../client/hydration.js";
14
+ import { flushSync } from "react-dom";
15
+ import { createRoot, hydrateRoot } from "react-dom/client";
16
+ import { jsx } from "react/jsx-runtime";
17
+ //#region .generated/react/client.tsx
18
+ /**
19
+ * React document hydration, browser history and deployment recovery.
20
+ *
21
+ * @module
22
+ */
23
+ /** @jsxImportSource react */
24
+ /** Create the browser application router and hydrate the document, installing recovery before initial lazy loading. */
25
+ async function mountApplication(routes, rootModule, options = false) {
26
+ const { hash = false, deployment = null, deferredTimeout } = typeof options === "boolean" ? { hash: options } : options;
27
+ const initialURL = new URL(hash ? location.hash.slice(1) || "/" : location.href, location.origin);
28
+ await (matchRoutes(routes, initialURL.pathname)?.route)?.preload?.().catch(async (error) => {
29
+ await recoverDeployment(deployment, initialURL);
30
+ throw error;
31
+ });
32
+ routes = prepareRoutes(routes);
33
+ const element = document.getElementById("smolter-data");
34
+ const initial = element?.textContent ? readHydration(element.textContent, deferredTimeout) : void 0;
35
+ const hydration = initial?.hydration;
36
+ if (hydration?.snapshot.error) hydration.snapshot.error = Object.assign(new Error(hydration.snapshot.error.message), hydration.snapshot.error);
37
+ const router = createDataRouter(routes, {
38
+ browser: !hash,
39
+ url: hash ? new URL(location.hash.slice(1) || "/", location.origin).href : location.href,
40
+ ...hydration && { hydrationData: hydration.snapshot }
41
+ });
42
+ const dispose = router.dispose;
43
+ router.dispose = () => {
44
+ initial?.dispose();
45
+ dispose();
46
+ };
47
+ watchDeployment(router, deployment);
48
+ if (hash) connectHashHistory(router);
49
+ retainRoute(router, flushSync);
50
+ connectNavigation(router);
51
+ const rootStore = attachRootData(router, hydration?.root, rootModule);
52
+ const props = {
53
+ routes,
54
+ router,
55
+ root: hydration?.root,
56
+ rootModule,
57
+ rootStore,
58
+ rootError: hydration?.rootError,
59
+ initialData: element?.textContent ?? void 0
60
+ };
61
+ if (hydration) {
62
+ const mounted = hydrateRoot(document, /* @__PURE__ */ jsx(RouterApplication, {
63
+ ...props,
64
+ document: true
65
+ }));
66
+ const cleanup = router.dispose;
67
+ router.dispose = () => {
68
+ mounted.unmount();
69
+ cleanup();
70
+ };
71
+ return router;
72
+ }
73
+ const target = document.getElementById("root");
74
+ if (target) {
75
+ const mounted = createRoot(target);
76
+ mounted.render(/* @__PURE__ */ jsx(RouterApplication, { ...props }));
77
+ const cleanup = router.dispose;
78
+ router.dispose = () => {
79
+ mounted.unmount();
80
+ cleanup();
81
+ };
82
+ }
83
+ return router;
84
+ }
85
+ //#endregion
86
+ export { configureApplication, deserialize, mountApplication, recoverDeployment, serverRoute, watchDeployment };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * React document metadata, resource links and hydration scripts.
3
+ *
4
+ * @module
5
+ */
6
+ /** @jsxImportSource react */
7
+ import type { ComponentProps } from "react";
8
+ /** Render root or active-route metadata in the document head. */
9
+ export declare function Meta(): import("react").ReactElement;
10
+ /** Render stylesheet and other link descriptors from the application root. */
11
+ export declare function Links(): import("react").ReactElement;
12
+ /** Nonce and browser entry options for the document hydration scripts. */
13
+ export interface ScriptProps {
14
+ nonce?: string;
15
+ }
16
+ /** Render the hydration snapshot and browser entry script with an optional CSP nonce. */
17
+ export declare function Scripts({ nonce }: ScriptProps): import("react").ReactElement;
18
+ /** Reset scroll after navigation unless the navigation requests preservation. */
19
+ export declare function ScrollRestoration(_props: ScriptProps): null;
20
+ /** HTML attributes for server markup and client document reconciliation. */
21
+ export interface DocumentProps extends ComponentProps<"html"> {
22
+ "data-debug"?: boolean;
23
+ }
24
+ /** Render the server document and preserve client-managed theme classes on hydration. */
25
+ export declare function Document(props: DocumentProps): import("react").ReactElement;
@@ -0,0 +1,169 @@
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 { Fragment, createElement, useEffect, useLayoutEffect, useRef, useState } from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ //#region .generated/react/document.tsx
10
+ /**
11
+ * React document metadata, resource links and hydration scripts.
12
+ *
13
+ * @module
14
+ */
15
+ /** @jsxImportSource react */
16
+ function renderMeta(item, index) {
17
+ if (typeof item.title === "string") return /* @__PURE__ */ jsx("title", { children: item.title }, index);
18
+ if ("script:ld+json" in item) return /* @__PURE__ */ jsx("script", {
19
+ type: "application/ld+json",
20
+ dangerouslySetInnerHTML: { __html: serializeData(item["script:ld+json"]) }
21
+ }, index);
22
+ const { tagName, ...props } = item;
23
+ return createElement(tagName === "link" ? "link" : "meta", {
24
+ ...props,
25
+ key: index
26
+ });
27
+ }
28
+ /** Render root or active-route metadata in the document head. */
29
+ function Meta() {
30
+ const { routes, rootModule } = useApplication();
31
+ const snapshot = useSnapshot();
32
+ const route = routes.find((item) => item.path === snapshot.path);
33
+ const args = {
34
+ loaderData: snapshot.data,
35
+ data: snapshot.data,
36
+ params: snapshot.params,
37
+ error: snapshot.error,
38
+ location: new URL(snapshot.url)
39
+ };
40
+ const values = route?.meta?.(args) ?? rootModule?.meta?.(args) ?? [];
41
+ useEffect(() => {
42
+ const title = values.find((item) => typeof item.title === "string")?.title;
43
+ if (typeof title === "string") document.title = title;
44
+ }, [values]);
45
+ return /* @__PURE__ */ jsx(Fragment, { children: values.map(renderMeta) });
46
+ }
47
+ /** Render stylesheet and other link descriptors from the application root. */
48
+ function Links() {
49
+ const { rootModule } = useApplication();
50
+ return /* @__PURE__ */ jsx(Fragment, { children: rootModule?.links?.().map((props, index) => /* @__PURE__ */ jsx("link", { ...props }, index)) });
51
+ }
52
+ /** Render the hydration snapshot and browser entry script with an optional CSP nonce. */
53
+ function Scripts({ nonce }) {
54
+ const { root, rootError, router, entry, streaming, initialData } = useApplication();
55
+ const [json] = useState(() => {
56
+ if (initialData !== void 0) return initialData;
57
+ if (streaming) return "";
58
+ const current = router.getSnapshot();
59
+ const snapshot = {
60
+ ...current,
61
+ error: current.error && {
62
+ ...current.error,
63
+ name: current.error.name,
64
+ message: current.error.message
65
+ }
66
+ };
67
+ return serializeData({
68
+ root,
69
+ snapshot,
70
+ ...rootError && { rootError }
71
+ });
72
+ });
73
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
74
+ /* @__PURE__ */ jsx("script", {
75
+ ...nonce && { nonce },
76
+ dangerouslySetInnerHTML: { __html: applicationEnvironment?.entryScript ?? "" }
77
+ }),
78
+ /* @__PURE__ */ jsx("script", {
79
+ id: "smolter-data",
80
+ type: "application/json",
81
+ ...nonce && { nonce },
82
+ dangerouslySetInnerHTML: { __html: json }
83
+ }),
84
+ entry && /* @__PURE__ */ jsx("script", {
85
+ type: "module",
86
+ async: initialData !== void 0,
87
+ src: entry,
88
+ ...nonce && { nonce }
89
+ })
90
+ ] });
91
+ }
92
+ /** Reset scroll after navigation unless the navigation requests preservation. */
93
+ function ScrollRestoration(_props) {
94
+ const { router } = useApplication();
95
+ const { url, state } = useSnapshot();
96
+ const navigation = useNavigationSnapshot(router);
97
+ const positions = scrollPositions(router);
98
+ const restoredLocation = useRef("");
99
+ useEffect(() => {
100
+ const previous = history.scrollRestoration;
101
+ history.scrollRestoration = "manual";
102
+ return () => {
103
+ history.scrollRestoration = previous;
104
+ };
105
+ }, []);
106
+ useLayoutEffect(() => {
107
+ if (state !== "idle") return;
108
+ if ((history.state?.key ?? "initial") !== navigation.key) return;
109
+ const locationKey = `${navigation.key}:${url}`;
110
+ if (restoredLocation.current === locationKey) return;
111
+ const frame = requestAnimationFrame(() => {
112
+ restoredLocation.current = locationKey;
113
+ const target = new URL(location.href);
114
+ const saved = navigation.type === "POP" && positions.get(navigation.key);
115
+ if (!saved && target.hash) {
116
+ document.getElementById(target.hash.slice(1))?.scrollIntoView();
117
+ return;
118
+ }
119
+ if (!saved && navigation.preventScrollReset) return;
120
+ const [left, top] = saved || [0, 0];
121
+ window.scrollTo({
122
+ left,
123
+ top,
124
+ behavior: "instant"
125
+ });
126
+ });
127
+ return () => cancelAnimationFrame(frame);
128
+ }, [
129
+ url,
130
+ state,
131
+ positions,
132
+ navigation.key,
133
+ navigation.type,
134
+ navigation.preventScrollReset
135
+ ]);
136
+ return null;
137
+ }
138
+ /** Render the server document and preserve client-managed theme classes on hydration. */
139
+ function Document(props) {
140
+ const { children, ...attributes } = props;
141
+ const className = String(props.className ?? "");
142
+ const previousClassName = useRef(className);
143
+ const lang = String(props.lang ?? "en");
144
+ const debug = String(props["data-debug"] ?? false);
145
+ useEffect(() => {
146
+ if (previousClassName.current !== className) {
147
+ const previous = previousClassName.current.split(/\s+/).filter(Boolean);
148
+ const next = className.split(/\s+/).filter(Boolean);
149
+ document.documentElement.classList.remove(...previous);
150
+ document.documentElement.classList.add(...next);
151
+ previousClassName.current = className;
152
+ }
153
+ document.documentElement.lang = lang;
154
+ document.documentElement.dataset.debug = debug;
155
+ }, [
156
+ className,
157
+ lang,
158
+ debug
159
+ ]);
160
+ const classes = typeof document !== "undefined" && previousClassName.current !== className ? [...document.documentElement.classList].filter((value) => !previousClassName.current.split(/\s+/).includes(value)).concat(className.split(/\s+/).filter(Boolean)).join(" ") : props.className;
161
+ return /* @__PURE__ */ jsx("html", {
162
+ ...attributes,
163
+ className: classes,
164
+ suppressHydrationWarning: true,
165
+ children
166
+ });
167
+ }
168
+ //#endregion
169
+ export { Document, Links, Meta, Scripts, ScrollRestoration };
@@ -2,11 +2,13 @@
2
2
  * React components and typed data hooks. Import from @kasperrt/smolter/react.
3
3
  * @module
4
4
  */
5
- import { type AnyDataFunction, type DataFunction, type DataOf, type FetcherSnapshot, type Result, type Route as RouteDefinition, type RouteModule, type SubmitOptions } from "../core/index.ts";
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";
8
+ import type { RouteData } from "./route-types.ts";
7
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 SubmitOptions {
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: FormData, options?: FetcherSubmitOptions) => Promise<Result>;
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 extends AnyDataFunction = DataFunction>(): TypedFetcher<DataOf<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>>;
@@ -1,3 +1,4 @@
1
+ import { submissionBody, submissionSearch } from "../core/submission.js";
1
2
  import { createElement, useEffect, useMemo, useSyncExternalStore } from "./runtime.js";
2
3
  import { useRouter } from "./context.js";
3
4
  import { Form } from "./form.js";
@@ -20,7 +21,17 @@ function useFetcher(route) {
20
21
  fetcher: store
21
22
  }),
22
23
  load: (url) => store.load(url ?? target()),
23
- submit: (body, options) => store.submit(options?.action ?? target(), body, options)
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
+ }
24
35
  };
25
36
  }, [router, path]);
26
37
  useEffect(() => fetcher.dispose, [fetcher]);
@@ -1,6 +1,6 @@
1
+ import { submitForm } from "../core/submission.js";
1
2
  import { createElement } from "./runtime.js";
2
3
  import { useRouter } from "./context.js";
3
- import { submitForm } from "../core/submission.js";
4
4
  //#region .generated/react/form.ts
5
5
  /** Submit form data to a registered route without a document reload. */
6
6
  function Form(options) {
@@ -2,19 +2,20 @@
2
2
  * React components and typed data hooks. Import from @kasperrt/smolter/react.
3
3
  * @module
4
4
  */
5
- import type { AnyDataFunction, DataOf, RouteModule, Router, Snapshot } from "../core/index.ts";
5
+ import type { AnyDataFunction, RouteModule, Router, Snapshot } from "../core/index.ts";
6
+ import type { RouteData } from "./route-types.ts";
6
7
  /** A route module or loader function used for data inference. */
7
8
  export type LoaderSource = RouteModule | AnyDataFunction;
8
9
  /** Infer server and client loader data from a route module or function. */
9
- export type LoaderData<T> = T extends AnyDataFunction ? DataOf<T> : T extends RouteModule ? DataOf<Extract<T["loader"] | T["clientLoader"], AnyDataFunction>> : unknown;
10
+ export type LoaderData<T> = T extends AnyDataFunction ? RouteData<T> : T extends RouteModule ? RouteData<Extract<T["loader"] | T["clientLoader"], AnyDataFunction>> : RouteData<T>;
10
11
  /** Infer server and client action data from a route module or function. */
11
- export type ActionData<T> = T extends AnyDataFunction ? DataOf<T> : T extends RouteModule ? DataOf<Extract<T["action"] | T["clientAction"], AnyDataFunction>> : unknown;
12
+ export type ActionData<T> = T extends AnyDataFunction ? RouteData<T> : T extends RouteModule ? RouteData<Extract<T["action"] | T["clientAction"], AnyDataFunction>> : RouteData<T>;
12
13
  /** Pass a route for inference, or use useLoaderData<typeof loader>() in its module. */
13
- export declare function useLoaderData<T extends LoaderSource = RouteModule>(route?: T): LoaderData<T> | undefined;
14
+ export declare function useLoaderData<T = RouteModule>(route?: T & LoaderSource): LoaderData<T>;
14
15
  /** Read the last page action result, optionally inferring its type from a route. */
15
- export declare function useActionData<T extends LoaderSource = RouteModule>(route?: T): ActionData<T> | undefined;
16
+ export declare function useActionData<T = RouteModule>(route?: T & LoaderSource): ActionData<T> | undefined;
16
17
  /** Read path parameters for the active route. */
17
- export declare function useParams(): Snapshot["params"];
18
+ export declare function useParams<T extends Snapshot["params"] = Snapshot["params"]>(): T;
18
19
  /** Read the current path, search, and hash together with the navigate function. */
19
20
  export declare function useLocation(): readonly [string, Router["navigate"]];
20
21
  /** Get the function that revalidates the current loader subject to shouldRevalidate. */
@@ -1,9 +1,20 @@
1
- /** Framework components and typed data hooks. @module */
1
+ /**
2
+ * React routing components, typed data hooks and forms.
3
+ *
4
+ * @module
5
+ */
2
6
  export type * from "../core/index.ts";
3
7
  export { createDataRouter, createRouter, defineRoute, } from "../core/index.ts";
8
+ export type { ApplicationEnvironment } from "../core/environment.ts";
9
+ export * from "../core/responses.ts";
10
+ export { readSubmission, type Submission, type SubmissionOptions, } from "../core/submission.ts";
4
11
  export { RouterProvider, type RouterProviderProps, useRouter, } from "./context.ts";
5
12
  export * from "./fetcher.ts";
6
13
  export * from "./form.ts";
7
14
  export * from "./hooks.ts";
8
- export * from "./link.ts";
15
+ export * from "./navigation.ts";
16
+ export { Outlet, type OutletProps, useMatches, useOutletContext, } from "./outlet.ts";
17
+ export { useNavigation, useRootData, useRouteError, useRouteLoaderData, useSubmit, } from "./route-hooks.ts";
18
+ export * from "./route-types.ts";
19
+ export { type ApplicationProps, RouterApplication } from "./router.ts";
9
20
  export * from "./routes.ts";
@@ -1,10 +1,15 @@
1
1
  import { defineRoute } from "../core/match.js";
2
2
  import { createRouter } from "../core/navigation-router.js";
3
3
  import { createDataRouter } from "../core/router.js";
4
+ import { data, isRouteErrorResponse, normalizeResult, redirect, resolveData } from "../core/responses.js";
5
+ import { readSubmission } from "../core/submission.js";
4
6
  import { RouterProvider, useRouter } from "./context.js";
5
7
  import { Form } from "./form.js";
6
8
  import { useFetcher } from "./fetcher.js";
7
9
  import { useActionData, useLoaderData, useLocation, useParams, useRevalidator } from "./hooks.js";
8
- import { Link } from "./link.js";
10
+ import { useNavigate, useNavigation, useNavigationType, useRootData, useRouteError, useRouteLoaderData, useSearchParams, useSubmit, useURLLocation } from "./route-hooks.js";
11
+ import { Link, NavLink } from "./navigation.js";
9
12
  import { Routes } from "./routes.js";
10
- export { Form, Link, RouterProvider, Routes, createDataRouter, createRouter, defineRoute, useActionData, useFetcher, useLoaderData, useLocation, useParams, useRevalidator, useRouter };
13
+ import { Outlet, useMatches, useOutletContext } from "./outlet.js";
14
+ import { RouterApplication } from "./router.js";
15
+ export { Form, Link, NavLink, Outlet, RouterApplication, RouterProvider, Routes, createDataRouter, createRouter, data, defineRoute, isRouteErrorResponse, normalizeResult, readSubmission, redirect, resolveData, useActionData, useFetcher, useLoaderData, useLocation, useMatches, useNavigate, useNavigation, useNavigationType, useOutletContext, useParams, useRevalidator, useRootData, useRouteError, useRouteLoaderData, useRouter, useSearchParams, useSubmit, useURLLocation };
@@ -0,0 +1,22 @@
1
+ import type { RouteModule } from "../core/index.ts";
2
+ import type { AppRoute } from "./route-types.ts";
3
+ import { type ComponentType } from "./runtime.ts";
4
+ /** Lazy route path, server transport endpoint and available handler capabilities. */
5
+ export interface LazyRouteOptions {
6
+ path: string;
7
+ dataEndpoint?: string;
8
+ id?: string;
9
+ loader?: boolean;
10
+ action?: boolean;
11
+ clientAction?: boolean;
12
+ meta?: boolean;
13
+ shouldRevalidate?: boolean;
14
+ ErrorBoundary?: boolean;
15
+ }
16
+ type LoadedRoute = RouteModule & Pick<AppRoute, "meta">;
17
+ interface LazyAppRoute extends AppRoute {
18
+ resolveComponent: () => ComponentType | undefined;
19
+ }
20
+ /** Create a route that imports its module on demand and shares the import with intent prefetching. */
21
+ export declare function lazyRoute(options: LazyRouteOptions, importer: () => Promise<LoadedRoute>): LazyAppRoute;
22
+ export {};
@@ -0,0 +1,57 @@
1
+ import { createElement, useSyncExternalStore } from "./runtime.js";
2
+ import { useRouter } from "./context.js";
3
+ import { serverRoute } from "../client/index.js";
4
+ //#region .generated/react/lazy-route.ts
5
+ /**
6
+ * On-demand React route modules with shared intent prefetching.
7
+ *
8
+ * @module
9
+ */
10
+ function LazyView() {
11
+ const router = useRouter();
12
+ const { path } = useSyncExternalStore(router.subscribe, router.getSnapshot, router.getSnapshot);
13
+ const Component = router.routes.find((item) => item.path === path)?.resolveComponent();
14
+ return Component && createElement(Component, {});
15
+ }
16
+ /** Create a route that imports its module on demand and shares the import with intent prefetching. */
17
+ function lazyRoute(options, importer) {
18
+ let module;
19
+ let pending;
20
+ function preload() {
21
+ pending ??= importer().then((loaded) => {
22
+ module = serverRoute(options.path, loaded, {
23
+ loader: options.loader,
24
+ action: options.action,
25
+ dataEndpoint: options.dataEndpoint
26
+ });
27
+ }).catch((error) => {
28
+ pending = void 0;
29
+ throw error;
30
+ });
31
+ return pending;
32
+ }
33
+ async function run(args, action = false) {
34
+ await preload();
35
+ if (args.signal.aborted) return /* @__PURE__ */ new Error("Navigation cancelled");
36
+ return (action ? module.clientAction ?? module.action : module.clientLoader ?? module.loader)?.(args);
37
+ }
38
+ function ErrorBoundary(props) {
39
+ const Component = module?.ErrorBoundary;
40
+ if (!Component) return props.error.message;
41
+ return createElement(Component, props);
42
+ }
43
+ return {
44
+ path: options.path,
45
+ id: options.id,
46
+ preload,
47
+ default: LazyView,
48
+ resolveComponent: () => module?.default,
49
+ clientLoader: (args) => run(args),
50
+ ...(options.action || options.clientAction) && { clientAction: (args) => run(args, true) },
51
+ ...options.meta && { meta: (args) => module?.meta?.(args) ?? [] },
52
+ ...options.shouldRevalidate && { shouldRevalidate: (args) => module?.shouldRevalidate?.(args) ?? true },
53
+ ...options.ErrorBoundary && { ErrorBoundary }
54
+ };
55
+ }
56
+ //#endregion
57
+ export { lazyRoute };