@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.
Files changed (270) hide show
  1. package/.generated/client/deployment.ts +161 -0
  2. package/{src/internal → .generated}/client/index.ts +3 -0
  3. package/.generated/core/environment.ts +43 -0
  4. package/{src/internal → .generated}/core/errors.ts +1 -1
  5. package/{src/internal → .generated}/core/index.ts +1 -0
  6. package/.generated/core/navigation-router.ts +107 -0
  7. package/.generated/core/navigation.ts +147 -0
  8. package/.generated/core/presentation.ts +108 -0
  9. package/.generated/core/resource-types.ts +44 -0
  10. package/.generated/core/resources.ts +444 -0
  11. package/.generated/core/responses.ts +86 -0
  12. package/{src/internal → .generated}/core/router.ts +136 -63
  13. package/{src/internal → .generated}/core/serialize.ts +7 -2
  14. package/.generated/core/store.ts +25 -0
  15. package/.generated/core/submission.ts +116 -0
  16. package/{src/internal → .generated}/core/types.ts +18 -1
  17. package/.generated/core/url.ts +29 -0
  18. package/.generated/core/utils.ts +5 -0
  19. package/.generated/preact/await.tsx +41 -0
  20. package/.generated/preact/client.tsx +93 -0
  21. package/.generated/preact/context.ts +59 -0
  22. package/.generated/preact/document.tsx +198 -0
  23. package/{src/internal → .generated}/preact/fetcher.ts +43 -23
  24. package/.generated/preact/form.ts +58 -0
  25. package/{src/internal → .generated}/preact/hooks.ts +25 -28
  26. package/.generated/preact/index.ts +34 -0
  27. package/.generated/preact/lazy-route.ts +100 -0
  28. package/.generated/preact/navigation-state.ts +9 -0
  29. package/.generated/preact/navigation.tsx +147 -0
  30. package/.generated/preact/root-context.ts +21 -0
  31. package/.generated/preact/root-data.ts +45 -0
  32. package/.generated/preact/route-hooks.tsx +170 -0
  33. package/.generated/preact/route-types.ts +76 -0
  34. package/.generated/preact/router.tsx +107 -0
  35. package/{src/internal → .generated}/preact/routes.ts +17 -9
  36. package/.generated/react/context.ts +59 -0
  37. package/.generated/react/data.ts +25 -0
  38. package/{src/internal → .generated}/react/fetcher.ts +13 -11
  39. package/.generated/react/form.ts +58 -0
  40. package/{src/internal → .generated}/react/hooks.ts +9 -14
  41. package/{src/internal/preact → .generated/react}/index.ts +5 -1
  42. package/{src/internal → .generated}/react/link.ts +13 -21
  43. package/.generated/react/resources/components.tsx +404 -0
  44. package/{src/internal → .generated}/react/routes.ts +17 -9
  45. package/.generated/server/application.tsx +185 -0
  46. package/.generated/server/assets.ts +19 -0
  47. package/.generated/server/handler.ts +69 -0
  48. package/.generated/server/http.ts +55 -0
  49. package/{src/internal → .generated}/server/index.ts +3 -3
  50. package/.generated/server/node.ts +4 -0
  51. package/.generated/server/preact.ts +7 -0
  52. package/.generated/vite/application/browser-boundary.ts +42 -0
  53. package/.generated/vite/application/lazy-files.ts +138 -0
  54. package/.generated/vite/application/vite.ts +144 -0
  55. package/.generated/vite/deployment.ts +46 -0
  56. package/{src/internal → .generated}/vite/index.ts +11 -4
  57. package/{src/preact-vite.ts → .generated/vite/preact.ts} +14 -3
  58. package/{src/react-vite.ts → .generated/vite/react.ts} +8 -3
  59. package/CHANGELOG.md +63 -0
  60. package/README.md +277 -35
  61. package/dist/client/deployment.d.ts +12 -0
  62. package/dist/client/deployment.js +102 -0
  63. package/dist/client/deserialize.js +10 -0
  64. package/dist/{internal/client → client}/index.d.ts +2 -0
  65. package/dist/client/index.js +47 -0
  66. package/dist/core/environment.d.ts +12 -0
  67. package/dist/core/environment.js +23 -0
  68. package/dist/core/errors.js +7 -0
  69. package/dist/{internal/core → core}/index.d.ts +1 -0
  70. package/dist/core/match.js +33 -0
  71. package/dist/core/navigation-router.d.ts +15 -0
  72. package/dist/core/navigation-router.js +64 -0
  73. package/dist/core/navigation.d.ts +15 -0
  74. package/dist/core/navigation.js +92 -0
  75. package/dist/core/presentation.d.ts +5 -0
  76. package/dist/core/presentation.js +69 -0
  77. package/dist/core/resource-types.d.ts +37 -0
  78. package/dist/core/resources.d.ts +19 -0
  79. package/dist/core/resources.js +345 -0
  80. package/dist/core/responses.d.ts +15 -0
  81. package/dist/core/responses.js +48 -0
  82. package/dist/{internal/core → core}/router.d.ts +1 -1
  83. package/dist/core/router.js +204 -0
  84. package/dist/{internal/core → core}/serialize.d.ts +2 -0
  85. package/dist/core/serialize.js +17 -0
  86. package/dist/core/store.d.ts +6 -0
  87. package/dist/core/store.js +21 -0
  88. package/dist/core/submission.d.ts +30 -0
  89. package/dist/core/submission.js +41 -0
  90. package/dist/{internal/core → core}/types.d.ts +16 -1
  91. package/dist/core/url.d.ts +7 -0
  92. package/dist/core/url.js +12 -0
  93. package/dist/core/utils.d.ts +5 -0
  94. package/dist/core/utils.js +5 -0
  95. package/dist/files/index.js +64 -0
  96. package/dist/preact/await.d.ts +7 -0
  97. package/dist/preact/await.js +19 -0
  98. package/dist/preact/client.d.ts +7 -0
  99. package/dist/preact/client.js +68 -0
  100. package/dist/preact/context.d.ts +18 -0
  101. package/dist/preact/context.js +23 -0
  102. package/dist/preact/document.d.ts +15 -0
  103. package/dist/preact/document.js +155 -0
  104. package/dist/{internal/preact → preact}/fetcher.d.ts +9 -5
  105. package/dist/preact/fetcher.js +45 -0
  106. package/dist/{internal/preact → preact}/form.d.ts +5 -6
  107. package/dist/preact/form.js +21 -0
  108. package/dist/{internal/preact → preact}/hooks.d.ts +7 -6
  109. package/dist/preact/hooks.js +25 -0
  110. package/dist/preact/index.d.ts +18 -0
  111. package/dist/preact/index.js +15 -0
  112. package/dist/preact/lazy-route.d.ts +19 -0
  113. package/dist/preact/lazy-route.js +51 -0
  114. package/dist/preact/navigation-state.d.ts +2 -0
  115. package/dist/preact/navigation-state.js +9 -0
  116. package/dist/preact/navigation.d.ts +28 -0
  117. package/dist/preact/navigation.js +60 -0
  118. package/dist/preact/root-context.d.ts +13 -0
  119. package/dist/preact/root-context.js +9 -0
  120. package/dist/preact/root-data.d.ts +8 -0
  121. package/dist/preact/root-data.js +28 -0
  122. package/dist/preact/route-hooks.d.ts +32 -0
  123. package/dist/preact/route-hooks.js +94 -0
  124. package/dist/preact/route-types.d.ts +54 -0
  125. package/dist/preact/router.d.ts +16 -0
  126. package/dist/preact/router.js +63 -0
  127. package/dist/{internal/preact → preact}/routes.d.ts +1 -1
  128. package/dist/preact/routes.js +12 -0
  129. package/dist/{internal/preact → preact}/runtime.js +1 -1
  130. package/dist/react/context.d.ts +18 -0
  131. package/dist/react/context.js +23 -0
  132. package/dist/react/data.d.ts +5 -0
  133. package/dist/react/data.js +3 -0
  134. package/dist/{internal/react → react}/fetcher.d.ts +2 -2
  135. package/dist/react/fetcher.js +34 -0
  136. package/dist/{internal/react → react}/form.d.ts +5 -6
  137. package/dist/react/form.js +21 -0
  138. package/dist/react/hooks.js +25 -0
  139. package/dist/{internal/react → react}/index.d.ts +1 -1
  140. package/dist/react/index.js +10 -0
  141. package/dist/{internal/react → react}/link.d.ts +2 -2
  142. package/dist/react/link.js +27 -0
  143. package/dist/react/resources/components.d.ts +64 -0
  144. package/dist/react/resources/components.js +239 -0
  145. package/dist/{internal/react → react}/routes.d.ts +1 -1
  146. package/dist/react/routes.js +12 -0
  147. package/dist/react/runtime.js +1 -0
  148. package/dist/server/application.d.ts +4 -0
  149. package/dist/server/application.js +119 -0
  150. package/dist/server/assets.d.ts +5 -0
  151. package/dist/server/assets.js +7 -0
  152. package/dist/server/handler.d.ts +19 -0
  153. package/dist/server/handler.js +22 -0
  154. package/dist/server/http.d.ts +4 -0
  155. package/dist/server/http.js +27 -0
  156. package/dist/server/index.js +76 -0
  157. package/dist/server/node.d.ts +3 -0
  158. package/dist/server/node.js +3 -0
  159. package/dist/server/preact.d.ts +3 -0
  160. package/dist/server/preact.js +4 -0
  161. package/dist/vite/application/browser-boundary.d.ts +3 -0
  162. package/dist/vite/application/browser-boundary.js +35 -0
  163. package/dist/vite/application/lazy-files.d.ts +2 -0
  164. package/dist/vite/application/lazy-files.js +88 -0
  165. package/dist/vite/application/vite.d.ts +9 -0
  166. package/dist/vite/application/vite.js +101 -0
  167. package/dist/vite/deployment.d.ts +8 -0
  168. package/dist/vite/deployment.js +36 -0
  169. package/dist/vite/index.js +109 -0
  170. package/dist/{preact-vite.d.ts → vite/preact.d.ts} +6 -3
  171. package/dist/vite/preact.js +16 -0
  172. package/dist/{react-vite.d.ts → vite/react.d.ts} +4 -3
  173. package/dist/vite/react.js +15 -0
  174. package/dist/vite/transport.js +35 -0
  175. package/jsr.json +28 -16
  176. package/package.json +115 -35
  177. package/dist/client.d.ts +0 -2
  178. package/dist/client.js +0 -2
  179. package/dist/core.d.ts +0 -2
  180. package/dist/core.js +0 -2
  181. package/dist/files.d.ts +0 -2
  182. package/dist/files.js +0 -2
  183. package/dist/internal/client/deserialize.js +0 -11
  184. package/dist/internal/client/index.js +0 -44
  185. package/dist/internal/core/errors.js +0 -6
  186. package/dist/internal/core/index.js +0 -7
  187. package/dist/internal/core/match.js +0 -27
  188. package/dist/internal/core/router.js +0 -189
  189. package/dist/internal/core/serialize.js +0 -13
  190. package/dist/internal/core/types.js +0 -5
  191. package/dist/internal/core/url.d.ts +0 -2
  192. package/dist/internal/core/url.js +0 -6
  193. package/dist/internal/files/index.js +0 -77
  194. package/dist/internal/preact/context.d.ts +0 -18
  195. package/dist/internal/preact/context.js +0 -20
  196. package/dist/internal/preact/fetcher.js +0 -28
  197. package/dist/internal/preact/form.js +0 -41
  198. package/dist/internal/preact/hooks.js +0 -35
  199. package/dist/internal/preact/index.d.ts +0 -9
  200. package/dist/internal/preact/index.js +0 -7
  201. package/dist/internal/preact/link.d.ts +0 -12
  202. package/dist/internal/preact/link.js +0 -28
  203. package/dist/internal/preact/routes.js +0 -22
  204. package/dist/internal/react/context.d.ts +0 -18
  205. package/dist/internal/react/context.js +0 -20
  206. package/dist/internal/react/fetcher.js +0 -28
  207. package/dist/internal/react/form.js +0 -41
  208. package/dist/internal/react/hooks.js +0 -35
  209. package/dist/internal/react/index.js +0 -7
  210. package/dist/internal/react/link.js +0 -28
  211. package/dist/internal/react/routes.js +0 -22
  212. package/dist/internal/react/runtime.js +0 -2
  213. package/dist/internal/server/index.js +0 -69
  214. package/dist/internal/vite/index.js +0 -132
  215. package/dist/internal/vite/transport.js +0 -42
  216. package/dist/preact-client.d.ts +0 -2
  217. package/dist/preact-client.js +0 -2
  218. package/dist/preact-server.d.ts +0 -2
  219. package/dist/preact-server.js +0 -2
  220. package/dist/preact-vite.js +0 -9
  221. package/dist/preact.d.ts +0 -2
  222. package/dist/preact.js +0 -2
  223. package/dist/react-client.d.ts +0 -2
  224. package/dist/react-client.js +0 -2
  225. package/dist/react-server.d.ts +0 -2
  226. package/dist/react-server.js +0 -2
  227. package/dist/react-vite.js +0 -9
  228. package/dist/react.d.ts +0 -2
  229. package/dist/react.js +0 -2
  230. package/dist/server.d.ts +0 -2
  231. package/dist/server.js +0 -2
  232. package/dist/utils.d.ts +0 -5
  233. package/dist/utils.js +0 -5
  234. package/dist/vite.d.ts +0 -2
  235. package/dist/vite.js +0 -2
  236. package/src/client.ts +0 -2
  237. package/src/core.ts +0 -2
  238. package/src/files.ts +0 -2
  239. package/src/internal/core/url.ts +0 -6
  240. package/src/internal/preact/context.ts +0 -46
  241. package/src/internal/preact/form.ts +0 -79
  242. package/src/internal/preact/link.ts +0 -46
  243. package/src/internal/react/context.ts +0 -46
  244. package/src/internal/react/form.ts +0 -79
  245. package/src/internal/react/index.ts +0 -13
  246. package/src/preact-client.ts +0 -2
  247. package/src/preact-server.ts +0 -2
  248. package/src/preact.ts +0 -2
  249. package/src/react-client.ts +0 -2
  250. package/src/react-server.ts +0 -2
  251. package/src/react.ts +0 -2
  252. package/src/server.ts +0 -2
  253. package/src/utils.ts +0 -5
  254. package/src/vite.ts +0 -2
  255. /package/{src/internal → .generated}/client/deserialize.ts +0 -0
  256. /package/{src/internal → .generated}/core/match.ts +0 -0
  257. /package/{src/internal → .generated}/files/index.ts +0 -0
  258. /package/{src/internal → .generated}/preact/runtime.ts +0 -0
  259. /package/{src/internal → .generated}/react/runtime.ts +0 -0
  260. /package/{src/internal → .generated}/vite/transport.ts +0 -0
  261. /package/dist/{internal/client → client}/deserialize.d.ts +0 -0
  262. /package/dist/{internal/core → core}/errors.d.ts +0 -0
  263. /package/dist/{internal/core → core}/match.d.ts +0 -0
  264. /package/dist/{internal/files → files}/index.d.ts +0 -0
  265. /package/dist/{internal/preact → preact}/runtime.d.ts +0 -0
  266. /package/dist/{internal/react → react}/hooks.d.ts +0 -0
  267. /package/dist/{internal/react → react}/runtime.d.ts +0 -0
  268. /package/dist/{internal/server → server}/index.d.ts +0 -0
  269. /package/dist/{internal/vite → vite}/index.d.ts +0 -0
  270. /package/dist/{internal/vite → vite}/transport.d.ts +0 -0
@@ -0,0 +1,100 @@
1
+ import { serverRoute } from "../client/index.ts";
2
+ import type { DataArgs, RouteModule, Router } from "../core/index.ts";
3
+ import { type ComponentType, createElement } from "preact";
4
+ import { useRouter } from "./context.ts";
5
+ import type { AppRoute } from "./route-types.ts";
6
+ import type { ErrorProps } from "./routes.ts";
7
+ import { useSyncExternalStore } from "./runtime.ts";
8
+
9
+ export interface LazyRouteOptions {
10
+ path: string;
11
+ id?: string;
12
+ loader?: boolean;
13
+ action?: boolean;
14
+ clientAction?: boolean;
15
+ meta?: boolean;
16
+ shouldRevalidate?: boolean;
17
+ ErrorBoundary?: boolean;
18
+ }
19
+
20
+ type LoadedRoute = RouteModule & Pick<AppRoute, "meta">;
21
+
22
+ interface LazyAppRoute extends AppRoute {
23
+ resolveComponent: () => ComponentType | undefined;
24
+ }
25
+
26
+ // A shared wrapper preserves component identity across routes using the same view.
27
+ function LazyView() {
28
+ const router = useRouter<Router>();
29
+ const { path } = useSyncExternalStore(router.subscribe, router.getSnapshot);
30
+ const route = router.routes.find((item) => item.path === path) as
31
+ | LazyAppRoute
32
+ | undefined;
33
+ const Component = route?.resolveComponent();
34
+
35
+ return Component && createElement(Component, {});
36
+ }
37
+
38
+ export function lazyRoute(
39
+ options: LazyRouteOptions,
40
+ importer: () => Promise<LoadedRoute>,
41
+ ): LazyAppRoute {
42
+ let module: LoadedRoute | undefined;
43
+ let pending: Promise<void> | undefined;
44
+
45
+ function preload() {
46
+ pending ??= importer()
47
+ .then((loaded) => {
48
+ module = serverRoute(options.path, loaded, {
49
+ loader: options.loader,
50
+ action: options.action,
51
+ });
52
+ })
53
+ .catch((error: unknown) => {
54
+ pending = undefined;
55
+ throw error;
56
+ });
57
+
58
+ return pending;
59
+ }
60
+
61
+ async function run(args: DataArgs, action = false) {
62
+ await preload();
63
+
64
+ if (args.signal.aborted) return new Error("Navigation cancelled");
65
+
66
+ const handler = action
67
+ ? (module!.clientAction ?? module!.action)
68
+ : (module!.clientLoader ?? module!.loader);
69
+
70
+ // serverRoute supplies serverLoader/serverAction to client handlers.
71
+ return (handler as ((args: DataArgs) => unknown) | undefined)?.(args);
72
+ }
73
+
74
+ function ErrorBoundary(props: ErrorProps) {
75
+ const Component = module?.ErrorBoundary as
76
+ | ComponentType<ErrorProps>
77
+ | undefined;
78
+
79
+ if (!Component) return props.error.message;
80
+
81
+ return createElement(Component, props);
82
+ }
83
+
84
+ return {
85
+ path: options.path,
86
+ id: options.id,
87
+ preload,
88
+ default: LazyView,
89
+ resolveComponent: () => module?.default as ComponentType | undefined,
90
+ clientLoader: (args) => run(args),
91
+ ...((options.action || options.clientAction) && {
92
+ clientAction: (args: DataArgs) => run(args, true),
93
+ }),
94
+ ...(options.meta && { meta: (args) => module?.meta?.(args) ?? [] }),
95
+ ...(options.shouldRevalidate && {
96
+ shouldRevalidate: (args) => module?.shouldRevalidate?.(args) ?? true,
97
+ }),
98
+ ...(options.ErrorBoundary && { ErrorBoundary }),
99
+ };
100
+ }
@@ -0,0 +1,9 @@
1
+ import type { Router } from "../core/index.ts";
2
+ import { navigationStore } from "../core/navigation.ts";
3
+ import { useSyncExternalStore } from "./runtime.ts";
4
+
5
+ export function useNavigationSnapshot(router: Router) {
6
+ const store = navigationStore(router);
7
+
8
+ return useSyncExternalStore(store.subscribe, store.getSnapshot);
9
+ }
@@ -0,0 +1,147 @@
1
+ import { matchRoutes, type NavigateOptions } from "../core/index.ts";
2
+ import {
3
+ handoffDocument,
4
+ resolveApplicationURL,
5
+ } from "../core/environment.ts";
6
+ import { navigationClick } from "../core/url.ts";
7
+ import type { TargetedMouseEvent } from "preact";
8
+ import type { ComponentProps, ReactNode } from "preact/compat";
9
+ import { useRouter, useSnapshot } from "./context.ts";
10
+ import { useURLLocation } from "./route-hooks.tsx";
11
+ import type { AppRoute } from "./route-types.ts";
12
+ import { createElement } from "./runtime.ts";
13
+
14
+ export type { NavigateOptions } from "../core/index.ts";
15
+
16
+ export type { NavigationType } from "../core/navigation.ts";
17
+
18
+ export type NavigateFunction = (
19
+ to: string | number | Partial<Pick<URL, "pathname" | "search" | "hash">>,
20
+ options?: NavigateOptions,
21
+ ) => void;
22
+
23
+ export interface LinkProps extends Omit<ComponentProps<"a">, "href" | "role"> {
24
+ to?: string;
25
+ href?: string;
26
+ replace?: boolean;
27
+ role?:
28
+ | "link"
29
+ | "button"
30
+ | "checkbox"
31
+ | "menuitem"
32
+ | "menuitemcheckbox"
33
+ | "menuitemradio"
34
+ | "option"
35
+ | "radio"
36
+ | "switch"
37
+ | "tab"
38
+ | "treeitem"
39
+ | "doc-backlink"
40
+ | "doc-biblioref"
41
+ | "doc-glossref"
42
+ | "doc-noteref";
43
+ prefetch?: string;
44
+ state?: unknown;
45
+ preventScrollReset?: boolean;
46
+ viewTransition?: boolean;
47
+ reloadDocument?: boolean;
48
+ }
49
+
50
+ export interface NavLinkRenderProps {
51
+ isActive: boolean;
52
+ isPending: boolean;
53
+ }
54
+
55
+ export interface NavLinkProps
56
+ extends Omit<LinkProps, "className" | "children"> {
57
+ end?: boolean;
58
+ className?: string | ((state: NavLinkRenderProps) => string);
59
+ children?: ReactNode | ((state: NavLinkRenderProps) => ReactNode);
60
+ }
61
+
62
+ export function Link(options: LinkProps): ReactNode {
63
+ const {
64
+ href: hrefProp,
65
+ to = hrefProp ?? "/",
66
+ replace,
67
+ preventScrollReset,
68
+ viewTransition,
69
+ prefetch,
70
+ state,
71
+ reloadDocument,
72
+ ...props
73
+ } = options;
74
+ const router = useRouter();
75
+ const current = new URL(useSnapshot("url"));
76
+ const destination = resolveApplicationURL(new URL(to, current));
77
+ const internal = destination.origin === current.origin;
78
+ const preload = () => {
79
+ const route = matchRoutes(router.routes, destination.pathname)?.route as
80
+ | AppRoute
81
+ | undefined;
82
+
83
+ void route?.preload?.().catch(() => {});
84
+ };
85
+
86
+ if (!reloadDocument && prefetch !== "none" && internal) {
87
+ for (const name of ["onPointerEnter", "onFocus", "onTouchStart"] as const) {
88
+ const handler = props[name] as ((event: Event) => void) | undefined;
89
+
90
+ props[name] = (event) => {
91
+ handler?.(event);
92
+ preload();
93
+ };
94
+ }
95
+ }
96
+
97
+ return createElement("a", {
98
+ ...props,
99
+ href: destination.href,
100
+ onClick(event: TargetedMouseEvent<HTMLAnchorElement>) {
101
+ props.onClick?.(event);
102
+
103
+ if (reloadDocument || !navigationClick(event, event.currentTarget))
104
+ return;
105
+
106
+ if (!internal) {
107
+ handoffDocument(event, destination);
108
+
109
+ return;
110
+ }
111
+
112
+ event.preventDefault();
113
+ void router.navigate(destination.href, options);
114
+ },
115
+ });
116
+ }
117
+
118
+ export function NavLink({
119
+ href,
120
+ to = href ?? "/",
121
+ end,
122
+ className,
123
+ children,
124
+ ...props
125
+ }: NavLinkProps): ReactNode {
126
+ const location = useURLLocation();
127
+ const target = new URL(to, location.href).pathname;
128
+ const isActive =
129
+ location.pathname === target ||
130
+ (!end && target !== "/" && location.pathname.startsWith(`${target}/`));
131
+ const state = { isActive, isPending: false };
132
+
133
+ return createElement(Link, {
134
+ ...props,
135
+ to,
136
+ className: typeof className === "function" ? className(state) : className,
137
+ ...(isActive && { "aria-current": "page" }),
138
+ children: typeof children === "function" ? children(state) : children,
139
+ });
140
+ }
141
+
142
+ export {
143
+ useNavigate,
144
+ useNavigationType,
145
+ useSearchParams,
146
+ useURLLocation,
147
+ } from "./route-hooks.tsx";
@@ -0,0 +1,21 @@
1
+ import type { Router } from "../core/index.ts";
2
+ import type { AppRoute, Hydration, RootModule } from "./route-types.ts";
3
+ import { createContext, useContext } from "./runtime.ts";
4
+
5
+ export interface ApplicationContext {
6
+ router: Router;
7
+ routes: AppRoute[];
8
+ root: unknown;
9
+ rootModule?: RootModule;
10
+ hydration?: Hydration;
11
+ entry?: string;
12
+ }
13
+
14
+ export const Application = createContext<ApplicationContext | null>(null);
15
+ export const OutletContext = createContext<unknown>(undefined);
16
+
17
+ export function useApplication() {
18
+ const app = useContext(Application);
19
+
20
+ return app!;
21
+ }
@@ -0,0 +1,45 @@
1
+ import type { Router } from "../core/index.ts";
2
+ import type { RootModule } from "./route-types.ts";
3
+
4
+ export { createStore as createRootStore } from "../core/store.ts";
5
+
6
+ import { createStore } from "../core/store.ts";
7
+
8
+ export function attachRootData(
9
+ router: Router,
10
+ initial: unknown,
11
+ rootModule?: RootModule,
12
+ ) {
13
+ const store = createStore(initial);
14
+ const loaderPath = rootModule?.loaderPath;
15
+
16
+ if (!rootModule || !loaderPath) return store;
17
+
18
+ let pending: ReturnType<Router["createFetcher"]> | undefined;
19
+ const unregister = router.registerRevalidator(
20
+ rootModule.id ?? "root",
21
+ async (args) => {
22
+ if (rootModule.shouldRevalidate?.(args) === false) return;
23
+
24
+ pending?.dispose();
25
+ const fetcher = router.createFetcher();
26
+ pending = fetcher;
27
+ const result = await fetcher.load(loaderPath);
28
+
29
+ if (pending === fetcher && !result.error) store.update(result.data);
30
+
31
+ fetcher.dispose();
32
+
33
+ if (pending === fetcher) pending = undefined;
34
+ },
35
+ );
36
+ const dispose = router.dispose;
37
+ router.dispose = () => {
38
+ pending?.dispose();
39
+ pending = undefined;
40
+ unregister();
41
+ dispose();
42
+ };
43
+
44
+ return store;
45
+ }
@@ -0,0 +1,170 @@
1
+ import type { NavigateOptions, Router, SubmitOptions } from "../core/index.ts";
2
+ import type { NavigationType } from "../core/navigation.ts";
3
+ import { pendingLocation } from "../core/presentation.ts";
4
+ import {
5
+ type Submission,
6
+ type SubmissionOptions,
7
+ submissionBody,
8
+ submissionSearch,
9
+ } from "../core/submission.ts";
10
+ import { useCallback } from "preact/hooks";
11
+ import { useRouter, useSnapshot } from "./context.ts";
12
+ import type { NavigateFunction } from "./navigation.tsx";
13
+ import { useNavigationSnapshot } from "./navigation-state.ts";
14
+ import { OutletContext, useApplication } from "./root-context.ts";
15
+ import type { RouteData } from "./route-types.ts";
16
+ import { useContext, useMemo } from "./runtime.ts";
17
+
18
+ export function useRootData<T = unknown>(): RouteData<T> {
19
+ return useApplication().root as RouteData<T>;
20
+ }
21
+
22
+ /** Read root data or the active route's data by its manifest identifier. */
23
+ export function useRouteLoaderData<T = unknown>(
24
+ id: string,
25
+ ): RouteData<T> | undefined {
26
+ const app = useApplication();
27
+ const snapshot = useSnapshot();
28
+
29
+ if (id === "root") return app.root as RouteData<T>;
30
+
31
+ const route = app.routes.find((item) => item.id === id);
32
+
33
+ if (!route || route.path !== snapshot.path) return undefined;
34
+
35
+ return snapshot.data as RouteData<T>;
36
+ }
37
+
38
+ export { useSnapshot };
39
+
40
+ export function useRouteError(): Error | undefined {
41
+ return useSnapshot("error");
42
+ }
43
+
44
+ export function useNavigation(): {
45
+ state: "idle" | "loading" | "submitting";
46
+ location: URL | undefined;
47
+ } {
48
+ const router = useRouter<Router>();
49
+ const snapshot = useSnapshot();
50
+ const location = pendingLocation(router);
51
+
52
+ return useMemo(
53
+ () => ({
54
+ state: snapshot.state,
55
+ location,
56
+ }),
57
+ [snapshot.state, location?.href],
58
+ );
59
+ }
60
+
61
+ export function useMatches(): {
62
+ id: string;
63
+ data: unknown;
64
+ params: import("../core/index.ts").Params;
65
+ }[] {
66
+ const { routes } = useApplication();
67
+ const snapshot = useSnapshot();
68
+ const route = routes.find((item) => item.path === snapshot.path);
69
+
70
+ return [
71
+ { id: route?.id ?? "", data: snapshot.data, params: snapshot.params },
72
+ ];
73
+ }
74
+
75
+ export function useOutletContext<T>(): T {
76
+ return useContext(OutletContext) as T;
77
+ }
78
+
79
+ export function useSubmit(): (
80
+ value: Submission,
81
+ options?: SubmissionOptions,
82
+ ) => Promise<void> | Promise<import("../core/index.ts").Result<unknown>> {
83
+ const router = useRouter<Router>();
84
+
85
+ return useCallback(
86
+ (value: Submission, options: SubmissionOptions = {}) => {
87
+ const body = submissionBody(value, options.encType);
88
+ const method = options.method?.toUpperCase();
89
+ const target = options.action ?? router.getSnapshot().url;
90
+
91
+ if (method === "GET") {
92
+ const url = new URL(target, router.getSnapshot().url);
93
+ url.search = submissionSearch(body);
94
+
95
+ return router.navigate(url.href, options);
96
+ }
97
+
98
+ return router.submit(target, body, {
99
+ method: method as SubmitOptions["method"],
100
+ });
101
+ },
102
+ [router],
103
+ );
104
+ }
105
+
106
+ export function useURLLocation(): URL & { state: unknown; key: string } {
107
+ const href = useSnapshot("url");
108
+ const snapshot = useNavigationSnapshot(useRouter<Router>());
109
+
110
+ return useMemo(
111
+ () =>
112
+ Object.assign(new URL(href), {
113
+ state: snapshot.state,
114
+ key: snapshot.key,
115
+ }),
116
+ [href, snapshot.state, snapshot.key],
117
+ );
118
+ }
119
+
120
+ export function useNavigate(): NavigateFunction {
121
+ const router = useRouter<Router>();
122
+
123
+ return useCallback(
124
+ (to, options) => {
125
+ if (typeof to === "number") {
126
+ window.history.go(to);
127
+
128
+ return;
129
+ }
130
+
131
+ const current = new URL(router.getSnapshot().url);
132
+ const target =
133
+ typeof to === "string"
134
+ ? to
135
+ : `${to.pathname ?? current.pathname}${to.search ?? current.search}${to.hash ?? current.hash}`;
136
+ void router.navigate(target, options);
137
+ },
138
+ [router],
139
+ );
140
+ }
141
+
142
+ export function useSearchParams(): [
143
+ URLSearchParams,
144
+ (
145
+ value: URLSearchParams | Record<string, string>,
146
+ options?: NavigateOptions,
147
+ ) => void,
148
+ ] {
149
+ const location = useURLLocation();
150
+ const navigate = useNavigate();
151
+ const params = useMemo(
152
+ () => new URLSearchParams(location.search),
153
+ [location.search],
154
+ );
155
+ const set = useCallback(
156
+ (
157
+ value: URLSearchParams | Record<string, string>,
158
+ options?: NavigateOptions,
159
+ ) => {
160
+ navigate(`${location.pathname}?${new URLSearchParams(value)}`, options);
161
+ },
162
+ [location.pathname, navigate],
163
+ );
164
+
165
+ return [params, set];
166
+ }
167
+
168
+ export function useNavigationType(): NavigationType {
169
+ return useNavigationSnapshot(useRouter<Router>()).type;
170
+ }
@@ -0,0 +1,76 @@
1
+ import type {
2
+ DataArgs,
3
+ RevalidationArgs,
4
+ Route,
5
+ Snapshot,
6
+ } from "../core/index.ts";
7
+ import type { DataResponse } from "../core/responses.ts";
8
+ import type { ComponentType, ReactNode } from "preact/compat";
9
+
10
+ export interface LoaderFunctionArgs extends DataArgs {
11
+ context?: unknown;
12
+ }
13
+
14
+ export type ActionFunctionArgs = LoaderFunctionArgs;
15
+
16
+ export interface ClientLoaderFunctionArgs extends LoaderFunctionArgs {
17
+ serverLoader: <T = unknown>() => Promise<T>;
18
+ }
19
+
20
+ export interface ClientActionFunctionArgs extends LoaderFunctionArgs {
21
+ serverAction: <T = unknown>() => Promise<T>;
22
+ }
23
+
24
+ export type ShouldRevalidateFunctionArgs = RevalidationArgs;
25
+
26
+ type UnwrapData<T> = T extends DataResponse<infer V> ? V : T;
27
+
28
+ export type RouteData<T> = T extends (...args: never[]) => infer R
29
+ ? UnwrapData<Exclude<Awaited<R>, Response | Error>>
30
+ : T;
31
+
32
+ export type MetaDescriptor = Record<string, unknown>;
33
+
34
+ export interface MetaArgs<T> {
35
+ data: RouteData<T> | undefined;
36
+ loaderData: RouteData<T> | undefined;
37
+ error?: unknown;
38
+ params: Record<string, string>;
39
+ location: URL;
40
+ }
41
+
42
+ export type MetaFunction<T = unknown> = (args: MetaArgs<T>) => MetaDescriptor[];
43
+
44
+ export type LinksFunction = () => Record<string, string>[];
45
+
46
+ export interface AppRoute extends Route {
47
+ resolveComponent?: () => ComponentType | undefined;
48
+ preload?: () => Promise<void>;
49
+ id?: string;
50
+ routePath?: string;
51
+ meta?: MetaFunction;
52
+ }
53
+
54
+ export interface RootModule {
55
+ /** Route identity used by targeted revalidation. Defaults to root. */
56
+ id?: string;
57
+ /** Optional registered route URL used to revalidate root data after mutations. */
58
+ loaderPath?: string;
59
+ ErrorBoundary?: ComponentType;
60
+ default: ComponentType;
61
+ Layout: ComponentType<{ children: ReactNode }>;
62
+ loader?: (args: LoaderFunctionArgs) => unknown;
63
+ links?: LinksFunction;
64
+ meta?: MetaFunction;
65
+ shouldRevalidate?: (args: RevalidationArgs) => boolean;
66
+ }
67
+
68
+ export interface Hydration {
69
+ root: unknown;
70
+ snapshot: Snapshot;
71
+ }
72
+
73
+ export type HandleDataRequestFunction = (
74
+ response: Response,
75
+ args: LoaderFunctionArgs,
76
+ ) => Response | Promise<Response>;
@@ -0,0 +1,107 @@
1
+ /** @jsxImportSource preact */
2
+
3
+ import type { DataFunction } from "../core/index.ts";
4
+ import { normalizeResult } from "../core/responses.ts";
5
+ import type { ReactNode } from "preact/compat";
6
+ import { RouterProvider } from "./context.ts";
7
+ import {
8
+ Application,
9
+ type ApplicationContext,
10
+ OutletContext,
11
+ } from "./root-context.ts";
12
+ import { createRootStore } from "./root-data.ts";
13
+ import type { AppRoute } from "./route-types.ts";
14
+ import { Routes } from "./routes.ts";
15
+ import { useContext, useMemo, useSyncExternalStore } from "./runtime.ts";
16
+
17
+ export interface OutletProps {
18
+ context?: unknown;
19
+ children?: ReactNode;
20
+ }
21
+
22
+ export function Outlet({
23
+ context,
24
+ children,
25
+ }: OutletProps): import("preact").JSX.Element {
26
+ const current = useContext(OutletContext);
27
+
28
+ return (
29
+ <OutletContext value={context ?? current}>
30
+ {children ?? <Routes fallback={<p role="status">Loading…</p>} />}
31
+ </OutletContext>
32
+ );
33
+ }
34
+
35
+ export interface ApplicationProps extends ApplicationContext {
36
+ document?: boolean;
37
+ rootStore?: ReturnType<typeof createRootStore>;
38
+ }
39
+
40
+ export function RouterApplication(
41
+ props: ApplicationProps,
42
+ ): import("preact").JSX.Element {
43
+ const rootStore = useMemo(
44
+ () => props.rootStore ?? createRootStore(props.root),
45
+ [props.rootStore, props.root],
46
+ );
47
+ const root = useSyncExternalStore(rootStore.subscribe, rootStore.getSnapshot);
48
+ const snapshot = useSyncExternalStore(
49
+ props.router.subscribe,
50
+ props.router.getSnapshot,
51
+ );
52
+ const route = props.routes.find((item) => item.path === snapshot.path);
53
+ const Root =
54
+ snapshot.error && !route?.ErrorBoundary
55
+ ? (props.rootModule?.ErrorBoundary ?? Outlet)
56
+ : (props.rootModule?.default ?? Outlet);
57
+ const Layout = props.rootModule?.Layout;
58
+ const children = <Root />;
59
+
60
+ return (
61
+ <RouterProvider router={props.router}>
62
+ <Application value={{ ...props, root }}>
63
+ {props.document ? Layout && <Layout>{children}</Layout> : children}
64
+ </Application>
65
+ </RouterProvider>
66
+ );
67
+ }
68
+
69
+ export function prepareRoutes(routes: AppRoute[]): AppRoute[] {
70
+ return routes.map((route) => {
71
+ const wrapped: AppRoute = {
72
+ ...route,
73
+ path: route.routePath ?? route.path,
74
+ shouldRevalidate: (args) => {
75
+ const { currentUrl, nextUrl } = args;
76
+
77
+ if (
78
+ args.reason === "navigation" &&
79
+ currentUrl.pathname === nextUrl.pathname &&
80
+ currentUrl.search === nextUrl.search &&
81
+ currentUrl.hash !== nextUrl.hash
82
+ )
83
+ return false;
84
+
85
+ return route.shouldRevalidate?.(args) ?? args.defaultShouldRevalidate;
86
+ },
87
+ };
88
+
89
+ for (const key of [
90
+ "loader",
91
+ "action",
92
+ "clientLoader",
93
+ "clientAction",
94
+ ] as const) {
95
+ const handler = route[key] as DataFunction | undefined;
96
+
97
+ if (!handler) continue;
98
+
99
+ wrapped[key] = (args) =>
100
+ Promise.resolve()
101
+ .then(() => handler(args))
102
+ .then(normalizeResult, normalizeResult);
103
+ }
104
+
105
+ return wrapped;
106
+ });
107
+ }