@kasperrt/smolter 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) 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/prepare-routes.ts +50 -0
  5. package/.generated/core/presentation.ts +1 -1
  6. package/.generated/preact/client.tsx +1 -26
  7. package/.generated/preact/deferred-hydration.ts +1 -98
  8. package/.generated/preact/index.ts +6 -1
  9. package/.generated/preact/navigation.tsx +13 -5
  10. package/.generated/preact/root-data.ts +1 -45
  11. package/.generated/preact/router.tsx +2 -48
  12. package/.generated/react/client.tsx +130 -0
  13. package/.generated/react/document.tsx +235 -0
  14. package/.generated/react/fetcher.ts +29 -7
  15. package/.generated/react/hooks.ts +20 -18
  16. package/.generated/react/index.ts +17 -1
  17. package/.generated/react/lazy-route.ts +116 -0
  18. package/.generated/react/link.ts +1 -38
  19. package/.generated/react/navigation-state.ts +13 -0
  20. package/.generated/react/navigation.ts +145 -0
  21. package/.generated/react/root-context.ts +22 -0
  22. package/.generated/react/route-hooks.ts +159 -0
  23. package/.generated/react/route-types.ts +91 -0
  24. package/.generated/react/router.ts +50 -0
  25. package/.generated/react/runtime.ts +1 -0
  26. package/.generated/server/application.tsx +18 -253
  27. package/.generated/server/dispatch.ts +87 -0
  28. package/.generated/server/index.ts +2 -0
  29. package/.generated/server/react.ts +34 -0
  30. package/.generated/server/render-application.ts +203 -0
  31. package/.generated/vite/application/lazy-files.ts +2 -1
  32. package/.generated/vite/application/vite.ts +12 -4
  33. package/.generated/vite/react.ts +11 -0
  34. package/CHANGELOG.md +10 -0
  35. package/README.md +41 -41
  36. package/dist/client/hash-history.d.ts +2 -0
  37. package/dist/client/hash-history.js +26 -0
  38. package/dist/client/hydration.d.ts +10 -0
  39. package/dist/{preact/deferred-hydration.js → client/hydration.js} +1 -1
  40. package/dist/client/root-data.d.ts +14 -0
  41. package/dist/{preact → client}/root-data.js +1 -1
  42. package/dist/core/prepare-routes.d.ts +7 -0
  43. package/dist/core/prepare-routes.js +28 -0
  44. package/dist/core/presentation.js +1 -1
  45. package/dist/preact/client.js +5 -26
  46. package/dist/preact/deferred-hydration.d.ts +1 -10
  47. package/dist/preact/document.js +2 -2
  48. package/dist/preact/index.d.ts +1 -1
  49. package/dist/preact/index.js +1 -1
  50. package/dist/preact/navigation-state.js +1 -1
  51. package/dist/preact/navigation.js +7 -5
  52. package/dist/preact/root-data.d.ts +1 -8
  53. package/dist/preact/route-hooks.js +1 -1
  54. package/dist/preact/router.d.ts +2 -2
  55. package/dist/preact/router.js +2 -26
  56. package/dist/react/client.d.ts +20 -0
  57. package/dist/react/client.js +86 -0
  58. package/dist/react/document.d.ts +25 -0
  59. package/dist/react/document.js +169 -0
  60. package/dist/react/fetcher.d.ts +8 -4
  61. package/dist/react/fetcher.js +12 -1
  62. package/dist/react/form.js +1 -1
  63. package/dist/react/hooks.d.ts +7 -6
  64. package/dist/react/index.d.ts +7 -1
  65. package/dist/react/index.js +6 -2
  66. package/dist/react/lazy-route.d.ts +22 -0
  67. package/dist/react/lazy-route.js +57 -0
  68. package/dist/react/link.d.ts +1 -12
  69. package/dist/react/navigation-state.d.ts +2 -0
  70. package/dist/react/navigation-state.js +9 -0
  71. package/dist/react/navigation.d.ts +33 -0
  72. package/dist/react/navigation.js +64 -0
  73. package/dist/react/root-context.d.ts +14 -0
  74. package/dist/react/root-context.js +10 -0
  75. package/dist/react/route-hooks.d.ts +34 -0
  76. package/dist/react/route-hooks.js +89 -0
  77. package/dist/react/route-types.d.ts +69 -0
  78. package/dist/react/router.d.ts +10 -0
  79. package/dist/react/router.js +22 -0
  80. package/dist/react/runtime.d.ts +1 -1
  81. package/dist/react/runtime.js +1 -1
  82. package/dist/server/application.d.ts +4 -8
  83. package/dist/server/application.js +9 -156
  84. package/dist/server/dispatch.d.ts +5 -0
  85. package/dist/server/dispatch.js +50 -0
  86. package/dist/server/index.d.ts +1 -0
  87. package/dist/server/index.js +2 -1
  88. package/dist/server/preact.js +2 -1
  89. package/dist/server/react.d.ts +10 -0
  90. package/dist/server/react.js +22 -0
  91. package/dist/server/render-application.d.ts +31 -0
  92. package/dist/server/render-application.js +118 -0
  93. package/dist/vite/application/lazy-files.d.ts +1 -1
  94. package/dist/vite/application/lazy-files.js +2 -2
  95. package/dist/vite/application/vite.d.ts +2 -1
  96. package/dist/vite/application/vite.js +12 -5
  97. package/dist/vite/react.d.ts +4 -0
  98. package/dist/vite/react.js +9 -1
  99. package/jsr.json +10 -6
  100. package/package.json +18 -10
  101. package/.generated/core/resource-types.ts +0 -48
  102. package/.generated/core/resources.ts +0 -446
  103. package/.generated/react/data.ts +0 -29
  104. package/.generated/react/resources/components.tsx +0 -416
  105. package/dist/core/resource-types.d.ts +0 -41
  106. package/dist/core/resources.d.ts +0 -21
  107. package/dist/core/resources.js +0 -347
  108. package/dist/react/data.d.ts +0 -9
  109. package/dist/react/data.js +0 -3
  110. package/dist/react/link.js +0 -27
  111. package/dist/react/resources/components.d.ts +0 -76
  112. package/dist/react/resources/components.js +0 -250
@@ -17,7 +17,18 @@ export function routerPlugin(options: FileRouterOptions = {}): Plugin {
17
17
  /** File routing is the default; this alias keeps configuration explicit. */
18
18
  export const fileRouter = routerPlugin;
19
19
 
20
+ export type { ApplicationOptions } from "./application/vite.ts";
20
21
  export {
21
22
  type DeploymentPluginOptions,
22
23
  deploymentPlugin,
23
24
  } from "./deployment.ts";
25
+
26
+ import {
27
+ type ApplicationOptions,
28
+ applicationPlugins as plugins,
29
+ } from "./application/vite.ts";
30
+
31
+ /** Configure React application SSR, scoped data transport and lazy file routing. */
32
+ export function applicationPlugins(options: ApplicationOptions = {}): Plugin[] {
33
+ return plugins({ ...options, framework: "react" });
34
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.4
4
+
5
+ - Remove the separate `/react/data` resource router. Both main adapters provide route data, forms, submissions and independent fetchers through `createDataRouter`; migrate ResourceProvider/Resource hooks to RouterProvider and the main data hooks. The old pathless resource store and tuple fetcher API are removed, not aliased to incompatible signatures.
6
+
7
+ - Complete React routing-hook parity: navigation/submission state, URL/search/history helpers, route errors, root data and route-ID data. Add React RouterApplication and match Preact loader/fetcher type inference.
8
+ - Support React application SSR and hydration, document helpers, lazy routes and application Vite middleware. React DOM 19+ powers integrated documents; routing hooks and manual loadRouter SSR retain React 18 support. Share transport, root revalidation, hash history, hydration payloads and server orchestration across renderers.
9
+ - Export renderer-independent dispatchRoute from both server entries. Preserve scoped endpoints, private caching, redirects, cancellation and mutation ownership.
10
+ - Add React link intent prefetching, structured/GET fetcher submissions and real NavLink pending state in both adapters. Report pending destinations under plain providers as well as retained application views.
11
+ - Update compatibility badges, guides, package-isolation checks and bundle tables. Verify React production early hydration, persistent input, root refresh, CSP and abandoned deferred boundaries.
12
+
3
13
  ## 0.4.3
4
14
 
5
15
  - Prefetch individual guide articles on pointer, keyboard and touch intent. Keep the shared header, sidebar and current article visible during cold guide navigation; show a quiet pending status without replacing the layout. Link the header directly to the canonical Getting started guide.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # smolter _
2
2
 
3
3
  <!-- bundlejs:start -->
4
- [![react gzip size](https://deno.bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.3%2Freact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%2C%22react%2F*%22%5D%7D%7D&badge)](https://bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.3%2Freact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%2C%22react%2F*%22%5D%7D%7D&bundle)
5
- [![preact gzip size](https://deno.bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.3%2Fpreact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22preact%22%2C%22preact%2F*%22%5D%7D%7D&badge)](https://bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.3%2Fpreact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22preact%22%2C%22preact%2F*%22%5D%7D%7D&bundle)
4
+ [![react gzip size](https://deno.bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.4%2Freact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%2C%22react%2F*%22%5D%7D%7D&badge)](https://bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.4%2Freact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%2C%22react%2F*%22%5D%7D%7D&bundle)
5
+ [![preact gzip size](https://deno.bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.4%2Fpreact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22preact%22%2C%22preact%2F*%22%5D%7D%7D&badge)](https://bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.4%2Fpreact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22preact%22%2C%22preact%2F*%22%5D%7D%7D&bundle)
6
6
  <!-- bundlejs:end -->
7
7
 
8
8
  A typed data router for React and Preact. **Zero runtime dependencies.**
@@ -18,6 +18,12 @@ and [React Router](https://reactrouter.com/).
18
18
 
19
19
  Start with [loaders](https://kasperrt.github.io/smolter/docs/loaders/), [actions](https://kasperrt.github.io/smolter/docs/actions/), [forms and validation](https://kasperrt.github.io/smolter/docs/forms/), or [fetcher reads and mutations](https://kasperrt.github.io/smolter/docs/fetchers/). Each guide connects the handler to an actual route URL and its consuming view.
20
20
 
21
+ ## React and Preact application APIs
22
+
23
+ Both adapters expose navigation, submission, error, root-data and route-data hooks. Both server entries expose `dispatchRoute` and `renderApplication`; both client entries provide `mountApplication`, scoped transport and deployment recovery. Import `applicationPlugins` from the matching renderer's `/vite` entry for lazy file routes and development SSR.
24
+
25
+ Integrated React documents use React DOM 19+ (`react/document`, `react/client`, `react/server`). React 18 remains supported for routing hooks and manual `loadRouter` SSR. Use React's `use(promise)` inside `Suspense` for deferred rendering. Preact uses `Await` with an explicit `fallback` for early hydration. These renderer choices share transport and data ownership, without loading the other framework.
26
+
21
27
  ## Contents
22
28
 
23
29
  - [Deployment updates](#deployment-updates)
@@ -456,7 +462,7 @@ and `internalURL`. Move `/client`, `/server`,
456
462
  `/files`, and `/vite` imports to the corresponding renderer-prefixed path above.
457
463
  The bare package root and old top-level subpaths are no longer exported.
458
464
 
459
- See the [0.3 migration guide](https://kasperrt.github.io/smolter/docs/migration-0.3/) and [changelog](./CHANGELOG.md).
465
+ See the [changelog](./CHANGELOG.md) for release history.
460
466
 
461
467
  **Router factory migration:** code that previously used `createRouter` with
462
468
  loaders, actions, fetchers or revalidation must now use `createDataRouter`.
@@ -540,36 +546,30 @@ The adapter has no Express dependency and forwards failures to `next(error)`.
540
546
  See the [Express guide](https://kasperrt.github.io/smolter/docs/express)
541
547
  for a complete server example and streaming and hydration behavior.
542
548
 
543
- ## Resource subscriptions
549
+ ## Independent data requests
544
550
 
545
- `@kasperrt/smolter/react/data` adds subscriptions to named resources when their
546
- lifetime is independent of URL navigation. The state and cancellation logic
547
- lives in core; the entry adds React bindings without DOM or HTTP globals.
551
+ Both adapters include `useFetcher`, `useSubmit`, `Form` and the loader/action hooks
552
+ in their main entrypoint. Use `createDataRouter` for data routes and `useFetcher`
553
+ for reads or mutations that should not navigate the page. There is no separate
554
+ React resource router or `/react/data` entrypoint.
548
555
 
549
- ```ts
550
- import { createResourceRouter } from "@kasperrt/smolter/react/data";
551
-
552
- const resources = createResourceRouter(new Map([
553
- ["settings", {
554
- loader: async ({ signal }) => ({
555
- data: await readSettings(signal),
556
- error: "",
557
- }),
558
- action: async ({ input, signal }) => ({
559
- data: await saveSettings(input, signal),
560
- error: "",
561
- }),
562
- }],
563
- ]));
564
- ```
565
-
566
- `ResourceProvider` owns the store lifetime. `Resource` selects a resource by
567
- `routeId` and params. `useResourceData` reads it; `useRouteLoaderData` reads a
568
- named resource. `useResourceFetcher` returns `[state, operations]`, with
569
- independent `load` and `submit` operations. Successful mutations consult the
570
- resource's `shouldRevalidate` before reloading its data. Last-subscriber cleanup
571
- aborts pending work; `persistent` keeps a resource until explicitly disposed.
572
- This optional entry does not contribute to the base React bundle measurement.
556
+ ```tsx
557
+ import { useFetcher } from "@kasperrt/smolter/react";
558
+
559
+ function Settings() {
560
+ const settings = useFetcher();
561
+ return (
562
+ <settings.Form method="post" action="/settings">
563
+ <input name="name" aria-label="Profile name" />
564
+ <button disabled={settings.state !== "idle"}>Save</button>
565
+ </settings.Form>
566
+ );
567
+ }
568
+ ```
569
+
570
+ Use the same imports from `/preact` in a Preact application. Independent
571
+ fetchers share the application router's cancellation, revalidation and deployment
572
+ recovery ownership; they do not require another provider or store.
573
573
 
574
574
  ## Size and checks
575
575
 
@@ -581,7 +581,6 @@ This optional entry does not contribute to the base React bundle measurement.
581
581
  | Preact application hydration | `@kasperrt/smolter/preact/client` |
582
582
  | Preact document components | `@kasperrt/smolter/preact/document` |
583
583
  | Lazy Preact route modules | `@kasperrt/smolter/preact/lazy-route` |
584
- | Optional React resources | `@kasperrt/smolter/react/data` |
585
584
  | Server rendering / Vite integration | Renderer `/server` / `/vite` subpaths; keep out of browser imports |
586
585
 
587
586
  The measurements below describe retained exports, not the cost of an import
@@ -605,16 +604,17 @@ a claim of identical features.
605
604
  | --- | ---: |
606
605
  | `React Router: declarative routing subset` | 12882 |
607
606
  | `React Router: data routing subset` | 29780 |
608
- | `React: routing-only named exports` | 1245 |
607
+ | `React: routing-only named exports` | 1419 |
609
608
  | `Preact: routing-only named exports` | 1405 |
610
- | `@kasperrt/smolter/react` | 3082 |
611
- | `@kasperrt/smolter/preact` | 5016 |
612
- | `@kasperrt/smolter/react + @kasperrt/smolter/react/client` | 4419 |
613
- | `@kasperrt/smolter/preact + server transport` | 6291 |
614
- | `@kasperrt/smolter/preact + client + document (application without lazy routes)` | 9398 |
615
- | `@kasperrt/smolter/preact + client + document + lazy-route (file-routed application)` | 9685 |
616
- | `@kasperrt/smolter/react/data (optional resource API)` | 2962 |
617
- | `React: common routing/data exports` | 2761 |
609
+ | `@kasperrt/smolter/react` | 4801 |
610
+ | `@kasperrt/smolter/preact` | 5076 |
611
+ | `@kasperrt/smolter/react + server transport (named client exports)` | 6060 |
612
+ | `@kasperrt/smolter/preact + server transport` | 6345 |
613
+ | `@kasperrt/smolter/react + client + document (application)` | 9124 |
614
+ | `@kasperrt/smolter/react + client + document + lazy-route` | 9401 |
615
+ | `@kasperrt/smolter/preact + client + document (application without lazy routes)` | 9435 |
616
+ | `@kasperrt/smolter/preact + client + document + lazy-route (file-routed application)` | 9721 |
617
+ | `React: common routing/data exports` | 3071 |
618
618
  | `Preact: common routing/data exports` | 3046 |
619
619
  | `wouter: routing subset (Router, Route, Switch, Link, useParams, useLocation)` | 2854 |
620
620
  | `wouter-preact: same routing subset` | 2211 |
@@ -0,0 +1,2 @@
1
+ import type { Router } from "../core/index.ts";
2
+ export declare function connectHashHistory(router: Router): void;
@@ -0,0 +1,26 @@
1
+ //#region .generated/client/hash-history.ts
2
+ function connectHashHistory(router) {
3
+ const read = router.getSnapshot;
4
+ const navigate = router.navigate;
5
+ router.navigate = async (url, options) => {
6
+ const target = new URL(url, router.getSnapshot().url);
7
+ const hash = `#${target.pathname}${target.search}${target.hash}`;
8
+ history[options?.replace ? "replaceState" : "pushState"](null, "", hash);
9
+ await navigate(url, options);
10
+ };
11
+ const unsubscribe = router.subscribe(() => {
12
+ const target = new URL(read().url);
13
+ const hash = `#${target.pathname}${target.search}${target.hash}`;
14
+ if (location.hash !== hash) history.replaceState(history.state, "", hash);
15
+ });
16
+ const onHashChange = () => void navigate(location.hash.slice(1) || "/");
17
+ window.addEventListener("hashchange", onHashChange);
18
+ const dispose = router.dispose;
19
+ router.dispose = () => {
20
+ unsubscribe();
21
+ window.removeEventListener("hashchange", onHashChange);
22
+ dispose();
23
+ };
24
+ }
25
+ //#endregion
26
+ export { connectHashHistory };
@@ -0,0 +1,10 @@
1
+ type Settlement = [number, unknown, string | null];
2
+ export interface DeferredWindow extends Window {
3
+ __smolterDeferred?: Settlement[];
4
+ }
5
+ /** Restore promise identity before hydrating Suspense boundaries. */
6
+ export declare function readHydration(json: string, timeout?: number): {
7
+ hydration: any;
8
+ dispose: () => void;
9
+ };
10
+ export {};
@@ -1,4 +1,4 @@
1
- //#region .generated/preact/deferred-hydration.ts
1
+ //#region .generated/client/hydration.ts
2
2
  /** Restore promise identity before hydrating Suspense boundaries. */
3
3
  function readHydration(json, timeout = 3e4) {
4
4
  if (!Number.isInteger(timeout) || timeout < 1 || timeout > 2147483647) throw new RangeError("deferredTimeout must be an integer between 1 and 2147483647 ms");
@@ -0,0 +1,14 @@
1
+ import type { RevalidationArgs, Router } from "../core/index.ts";
2
+ interface RootModule {
3
+ default?: unknown;
4
+ Layout?: unknown;
5
+ id?: string;
6
+ loaderPath?: string;
7
+ shouldRevalidate?: (args: RevalidationArgs) => boolean;
8
+ }
9
+ export { createStore as createRootStore } from "../core/store.ts";
10
+ export declare function attachRootData(router: Router, initial: unknown, rootModule?: RootModule): {
11
+ getSnapshot: () => unknown;
12
+ subscribe(listener: () => void): () => void;
13
+ update(next: unknown): void;
14
+ };
@@ -1,5 +1,5 @@
1
1
  import { createStore } from "../core/store.js";
2
- //#region .generated/preact/root-data.ts
2
+ //#region .generated/client/root-data.ts
3
3
  function attachRootData(router, initial, rootModule) {
4
4
  const store = createStore(initial);
5
5
  const loaderPath = rootModule?.loaderPath;
@@ -0,0 +1,7 @@
1
+ import type { Route } from "./types.ts";
2
+ type AppRoute = Route & {
3
+ routePath?: string;
4
+ preload?: () => Promise<void>;
5
+ };
6
+ export declare function prepareRoutes(routes: AppRoute[], preserveDeferred?: boolean): AppRoute[];
7
+ export {};
@@ -0,0 +1,28 @@
1
+ import { normalizeResult } from "./responses.js";
2
+ //#region .generated/core/prepare-routes.ts
3
+ function prepareRoutes(routes, preserveDeferred = false) {
4
+ return routes.map((route) => {
5
+ const wrapped = {
6
+ ...route,
7
+ path: route.routePath ?? route.path,
8
+ shouldRevalidate: (args) => {
9
+ const { currentUrl, nextUrl } = args;
10
+ if (args.reason === "navigation" && currentUrl.pathname === nextUrl.pathname && currentUrl.search === nextUrl.search && currentUrl.hash !== nextUrl.hash) return false;
11
+ return route.shouldRevalidate?.(args) ?? args.defaultShouldRevalidate;
12
+ }
13
+ };
14
+ for (const key of [
15
+ "loader",
16
+ "action",
17
+ "clientLoader",
18
+ "clientAction"
19
+ ]) {
20
+ const handler = route[key];
21
+ if (!handler) continue;
22
+ wrapped[key] = (args) => Promise.resolve().then(() => handler(args)).then((value) => normalizeResult(value, !preserveDeferred), (error) => normalizeResult(error, !preserveDeferred));
23
+ }
24
+ return wrapped;
25
+ });
26
+ }
27
+ //#endregion
28
+ export { prepareRoutes };
@@ -1,7 +1,7 @@
1
1
  //#region .generated/core/presentation.ts
2
2
  var pending = /* @__PURE__ */ new WeakMap();
3
3
  function pendingLocation(router) {
4
- const snapshot = pending.get(router)?.();
4
+ const snapshot = pending.get(router)?.() ?? router.getSnapshot();
5
5
  return snapshot?.state !== "idle" && snapshot?.url ? new URL(snapshot.url) : void 0;
6
6
  }
7
7
  function retainRoute(router, commitView) {
@@ -3,12 +3,14 @@ import { createDataRouter } from "../core/router.js";
3
3
  import { configureApplication } from "../core/environment.js";
4
4
  import { retainRoute } from "../core/presentation.js";
5
5
  import { connectNavigation } from "../core/navigation.js";
6
- import { attachRootData } from "./root-data.js";
7
- import { RouterApplication, prepareRoutes } from "./router.js";
6
+ import { attachRootData } from "../client/root-data.js";
7
+ import { prepareRoutes } from "../core/prepare-routes.js";
8
+ import { RouterApplication } from "./router.js";
8
9
  import { deserialize } from "../client/deserialize.js";
9
10
  import { recoverDeployment, watchDeployment } from "../client/deployment.js";
10
11
  import { serverRoute } from "../client/index.js";
11
- import { readHydration } from "./deferred-hydration.js";
12
+ import { connectHashHistory } from "../client/hash-history.js";
13
+ import { readHydration } from "../client/hydration.js";
12
14
  import { flushSync } from "preact/compat";
13
15
  import { jsx } from "preact/jsx-runtime";
14
16
  import { hydrate, render } from "preact";
@@ -74,28 +76,5 @@ async function mountApplication(routes, rootModule, options = false) {
74
76
  if (target) render(/* @__PURE__ */ jsx(RouterApplication, { ...props }), target);
75
77
  return router;
76
78
  }
77
- function connectHashHistory(router) {
78
- const read = router.getSnapshot;
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}${target.hash}`;
83
- history[options?.replace ? "replaceState" : "pushState"](null, "", hash);
84
- await navigate(url, options);
85
- };
86
- const unsubscribe = router.subscribe(() => {
87
- const target = new URL(read().url);
88
- const hash = `#${target.pathname}${target.search}${target.hash}`;
89
- if (location.hash !== hash) history.replaceState(history.state, "", hash);
90
- });
91
- const onHashChange = () => void navigate(location.hash.slice(1) || "/");
92
- window.addEventListener("hashchange", onHashChange);
93
- const dispose = router.dispose;
94
- router.dispose = () => {
95
- unsubscribe();
96
- window.removeEventListener("hashchange", onHashChange);
97
- dispose();
98
- };
99
- }
100
79
  //#endregion
101
80
  export { configureApplication, deserialize, mountApplication, recoverDeployment, serverRoute, watchDeployment };
@@ -1,10 +1 @@
1
- type Settlement = [number, unknown, string | null];
2
- export interface DeferredWindow extends Window {
3
- __smolterDeferred?: Settlement[];
4
- }
5
- /** Restore promise identity before hydrating Suspense boundaries. */
6
- export declare function readHydration(json: string, timeout?: number): {
7
- hydration: any;
8
- dispose: () => void;
9
- };
10
- export {};
1
+ export { type DeferredWindow, readHydration } from "../client/hydration.ts";
@@ -1,8 +1,8 @@
1
1
  import { serializeData } from "../core/serialize.js";
2
- import { useApplication } from "./root-context.js";
3
- import { useSnapshot } from "./context.js";
4
2
  import { applicationEnvironment } from "../core/environment.js";
5
3
  import { scrollPositions } from "../core/navigation.js";
4
+ import { useApplication } from "./root-context.js";
5
+ import { useSnapshot } from "./context.js";
6
6
  import { useNavigationSnapshot } from "./navigation-state.js";
7
7
  import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
8
8
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
@@ -18,5 +18,5 @@ export type { NavigateOptions } from "./navigation.tsx";
18
18
  export * from "./navigation.tsx";
19
19
  export { useMatches, useNavigation, useOutletContext, useRootData, useRouteError, useRouteLoaderData, useSubmit, } from "./route-hooks.tsx";
20
20
  export * from "./route-types.ts";
21
- export { Outlet, type OutletProps, RouterApplication } from "./router.tsx";
21
+ export { type ApplicationProps, Outlet, type OutletProps, RouterApplication, } from "./router.tsx";
22
22
  export * from "./routes.ts";
@@ -1,8 +1,8 @@
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 { readSubmission } from "../core/submission.js";
5
4
  import { data, isRouteErrorResponse, normalizeResult, redirect, resolveData } from "../core/responses.js";
5
+ import { readSubmission } from "../core/submission.js";
6
6
  import { Await } from "./await.js";
7
7
  import { RouterProvider, useRouter } from "./context.js";
8
8
  import { Form } from "./form.js";
@@ -1,5 +1,5 @@
1
- import { useSyncExternalStore } from "./runtime.js";
2
1
  import { navigationStore } from "../core/navigation.js";
2
+ import { useSyncExternalStore } from "./runtime.js";
3
3
  //#region .generated/preact/navigation-state.ts
4
4
  function useNavigationSnapshot(router) {
5
5
  const store = navigationStore(router);
@@ -1,9 +1,9 @@
1
1
  import { matchRoutes } from "../core/match.js";
2
2
  import { navigationClick } from "../core/url.js";
3
+ import { handoffDocument, resolveApplicationURL } from "../core/environment.js";
3
4
  import { createElement } from "./runtime.js";
4
5
  import { useRouter, useSnapshot } from "./context.js";
5
- import { handoffDocument, resolveApplicationURL } from "../core/environment.js";
6
- import { useNavigate, useNavigationType, useSearchParams, useURLLocation } from "./route-hooks.js";
6
+ import { useNavigate, useNavigation, useNavigationType, useSearchParams, useURLLocation } from "./route-hooks.js";
7
7
  //#region .generated/preact/navigation.tsx
8
8
  /** Render an anchor with same-origin router navigation and optional intent prefetching. */
9
9
  function Link(options) {
@@ -44,11 +44,13 @@ function Link(options) {
44
44
  /** Render a Link with active-route state and aria-current for the matching destination. */
45
45
  function NavLink({ href, to = href ?? "/", end, className, children, ...props }) {
46
46
  const location = useURLLocation();
47
- const target = new URL(to, location.href).pathname;
48
- const isActive = location.pathname === target || !end && target !== "/" && location.pathname.startsWith(`${target}/`);
47
+ const destination = resolveApplicationURL(new URL(to, location.href));
48
+ const target = destination.pathname;
49
+ const isActive = destination.origin === location.origin && (location.pathname === target || !end && target !== "/" && location.pathname.startsWith(`${target}/`));
50
+ const pending = useNavigation().location;
49
51
  const state = {
50
52
  isActive,
51
- isPending: false
53
+ isPending: !!pending && pending.origin === destination.origin && (pending.pathname === target || !end && target !== "/" && pending.pathname.startsWith(`${target}/`))
52
54
  };
53
55
  return createElement(Link, {
54
56
  ...props,
@@ -1,8 +1 @@
1
- import type { Router } from "../core/index.ts";
2
- import type { RootModule } from "./route-types.ts";
3
- export { createStore as createRootStore } from "../core/store.ts";
4
- export declare function attachRootData(router: Router, initial: unknown, rootModule?: RootModule): {
5
- getSnapshot: () => unknown;
6
- subscribe(listener: () => void): () => void;
7
- update(next: unknown): void;
8
- };
1
+ export { attachRootData, createRootStore } from "../client/root-data.ts";
@@ -1,8 +1,8 @@
1
1
  import { submissionBody, submissionSearch } from "../core/submission.js";
2
+ import { pendingLocation } from "../core/presentation.js";
2
3
  import { useContext, useMemo } from "./runtime.js";
3
4
  import { OutletContext, useApplication } from "./root-context.js";
4
5
  import { useRouter, useSnapshot } from "./context.js";
5
- import { pendingLocation } from "../core/presentation.js";
6
6
  import { useNavigationSnapshot } from "./navigation-state.js";
7
7
  import { useCallback } from "preact/hooks";
8
8
  //#region .generated/preact/route-hooks.tsx
@@ -2,7 +2,6 @@
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";
6
5
  /** Optional children and context shared with descendants through useOutletContext. */
7
6
  export interface OutletProps {
8
7
  context?: unknown;
@@ -10,10 +9,11 @@ export interface OutletProps {
10
9
  }
11
10
  /** Render supplied children or the active route, and provide outlet context to descendants. */
12
11
  export declare function Outlet({ context, children, }: OutletProps): import("preact").JSX.Element;
12
+ /** Root data, document components and request-scoped router for an application. */
13
13
  export interface ApplicationProps extends ApplicationContext {
14
14
  document?: boolean;
15
15
  rootStore?: ReturnType<typeof createRootStore>;
16
16
  }
17
17
  /** Render a Preact root document and active route with shared root data and application context. */
18
18
  export declare function RouterApplication(props: ApplicationProps): import("preact").JSX.Element;
19
- export declare function prepareRoutes(routes: AppRoute[], preserveDeferred?: boolean): AppRoute[];
19
+ export { prepareRoutes } from "../core/prepare-routes.ts";
@@ -1,9 +1,9 @@
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
9
  /** Render supplied children or the active route, and provide outlet context to descendants. */
@@ -37,29 +37,5 @@ function RouterApplication(props) {
37
37
  })
38
38
  });
39
39
  }
40
- function prepareRoutes(routes, preserveDeferred = false) {
41
- return routes.map((route) => {
42
- const wrapped = {
43
- ...route,
44
- path: route.routePath ?? route.path,
45
- shouldRevalidate: (args) => {
46
- const { currentUrl, nextUrl } = args;
47
- if (args.reason === "navigation" && currentUrl.pathname === nextUrl.pathname && currentUrl.search === nextUrl.search && currentUrl.hash !== nextUrl.hash) return false;
48
- return route.shouldRevalidate?.(args) ?? args.defaultShouldRevalidate;
49
- }
50
- };
51
- for (const key of [
52
- "loader",
53
- "action",
54
- "clientLoader",
55
- "clientAction"
56
- ]) {
57
- const handler = route[key];
58
- if (!handler) continue;
59
- wrapped[key] = (args) => Promise.resolve().then(() => handler(args)).then((value) => normalizeResult(value, !preserveDeferred), (error) => normalizeResult(error, !preserveDeferred));
60
- }
61
- return wrapped;
62
- });
63
- }
64
40
  //#endregion
65
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;