@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -0
- package/README.md +1037 -4
- package/dist/bin/rango.js +1619 -157
- package/dist/vite/index.js +5762 -2301
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +71 -63
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +367 -71
- package/skills/host-router/SKILL.md +218 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +176 -8
- package/skills/layout/SKILL.md +124 -3
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +474 -47
- package/skills/middleware/SKILL.md +207 -37
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +15 -11
- package/skills/parallel/SKILL.md +272 -1
- package/skills/prerender/SKILL.md +467 -65
- package/skills/rango/SKILL.md +89 -21
- package/skills/response-routes/SKILL.md +152 -91
- package/skills/route/SKILL.md +305 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/server-actions/SKILL.md +739 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +333 -86
- package/skills/use-cache/SKILL.md +324 -0
- package/skills/view-transitions/SKILL.md +212 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +136 -68
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +374 -561
- package/src/browser/navigation-client.ts +228 -70
- package/src/browser/navigation-store.ts +97 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +376 -315
- package/src/browser/prefetch/cache.ts +314 -0
- package/src/browser/prefetch/fetch.ts +282 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +191 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +152 -0
- package/src/browser/react/Link.tsx +255 -71
- package/src/browser/react/NavigationProvider.tsx +152 -24
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +55 -0
- package/src/browser/react/index.ts +15 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -120
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +78 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +99 -0
- package/src/browser/react/use-router.ts +83 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +85 -99
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +246 -64
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +158 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +84 -23
- package/src/build/generate-route-types.ts +39 -828
- package/src/build/index.ts +4 -5
- package/src/build/route-trie.ts +85 -32
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -307
- package/src/cache/cf/cf-cache-store.ts +573 -21
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +6 -1
- package/src/client.tsx +118 -302
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +55 -10
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +65 -45
- package/src/index.rsc.ts +138 -21
- package/src/index.ts +206 -51
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +25 -143
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +159 -13
- package/src/prerender.ts +397 -29
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +231 -121
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1134 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +483 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition.ts +1 -1431
- package/src/route-map-builder.ts +162 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +66 -9
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +418 -86
- package/src/router/intercept-resolution.ts +35 -20
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +359 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +98 -32
- package/src/router/match-api.ts +196 -261
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +441 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +415 -86
- package/src/router/match-middleware/cache-store.ts +91 -29
- package/src/router/match-middleware/intercept-resolution.ts +48 -21
- package/src/router/match-middleware/segment-resolution.ts +73 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +154 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +209 -0
- package/src/router/middleware.ts +373 -371
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +292 -52
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +152 -39
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +756 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1407 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1315
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +111 -39
- package/src/router/types.ts +17 -9
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +642 -2011
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +864 -1114
- package/src/rsc/helpers.ts +181 -19
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +395 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +360 -0
- package/src/rsc/rsc-rendering.ts +256 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +360 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +52 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +187 -38
- package/src/server/context.ts +333 -59
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +603 -109
- package/src/server.ts +35 -155
- package/src/ssr/index.tsx +107 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +764 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +209 -0
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +167 -0
- package/src/types.ts +1 -1757
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +108 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1282
- package/src/use-loader.tsx +161 -81
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +376 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +486 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -2063
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +98 -0
- package/src/vite/plugins/client-ref-dedup.ts +131 -0
- package/src/vite/plugins/client-ref-hashing.ts +117 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +816 -0
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +336 -0
- package/src/vite/plugins/version-injector.ts +109 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +497 -0
- package/src/vite/router-discovery.ts +1423 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +161 -0
- package/src/vite/utils/prerender-utils.ts +222 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/vite/expose-prerender-handler-id.ts +0 -429
- package/src/vite/package-resolution.ts +0 -125
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import React, {
|
|
4
4
|
useState,
|
|
5
5
|
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
6
7
|
useCallback,
|
|
7
8
|
useMemo,
|
|
9
|
+
useRef,
|
|
8
10
|
use,
|
|
9
11
|
type ReactNode,
|
|
10
12
|
} from "react";
|
|
@@ -14,7 +16,7 @@ import {
|
|
|
14
16
|
} from "./context.js";
|
|
15
17
|
import type {
|
|
16
18
|
NavigationStore,
|
|
17
|
-
|
|
19
|
+
NavigationUpdate,
|
|
18
20
|
NavigateOptions,
|
|
19
21
|
NavigationBridge,
|
|
20
22
|
} from "../types.js";
|
|
@@ -22,7 +24,11 @@ import type { EventController } from "../event-controller.js";
|
|
|
22
24
|
import { RootErrorBoundary } from "../../root-error-boundary.js";
|
|
23
25
|
import type { HandleData } from "../types.js";
|
|
24
26
|
import { ThemeProvider } from "../../theme/ThemeProvider.js";
|
|
27
|
+
import { NonceContext } from "./nonce-context.js";
|
|
25
28
|
import type { ResolvedThemeConfig, Theme } from "../../theme/types.js";
|
|
29
|
+
import { cancelAllPrefetches } from "../prefetch/queue.js";
|
|
30
|
+
import { handleNavigationEnd } from "../scroll-restoration.js";
|
|
31
|
+
import type { AppShellRef } from "../app-shell.js";
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
34
|
* Process handles from an async generator, updating the event controller
|
|
@@ -41,10 +47,22 @@ async function processHandles(
|
|
|
41
47
|
store: NavigationStore;
|
|
42
48
|
matched?: string[];
|
|
43
49
|
isPartial?: boolean;
|
|
50
|
+
/** Server's `resolvedIds`: every segment re-resolved this request,
|
|
51
|
+
* including null-component ones excluded from `diff`/`segments`.
|
|
52
|
+
* Drives cleanup of stale handle buckets when a re-resolved segment
|
|
53
|
+
* pushed nothing. */
|
|
54
|
+
resolvedIds?: string[];
|
|
44
55
|
historyKey: string;
|
|
45
|
-
}
|
|
56
|
+
},
|
|
46
57
|
): Promise<void> {
|
|
47
|
-
const {
|
|
58
|
+
const {
|
|
59
|
+
eventController,
|
|
60
|
+
store,
|
|
61
|
+
matched,
|
|
62
|
+
isPartial,
|
|
63
|
+
resolvedIds,
|
|
64
|
+
historyKey,
|
|
65
|
+
} = opts;
|
|
48
66
|
|
|
49
67
|
let yieldCount = 0;
|
|
50
68
|
for await (const handleData of handlesGenerator) {
|
|
@@ -53,13 +71,13 @@ async function processHandles(
|
|
|
53
71
|
// the current route's breadcrumbs (e.g., quick popstate after clicking a link).
|
|
54
72
|
if (historyKey !== store.getHistoryKey()) {
|
|
55
73
|
console.log(
|
|
56
|
-
"[NavigationProvider] Stopping handle processing - user navigated away"
|
|
74
|
+
"[NavigationProvider] Stopping handle processing - user navigated away",
|
|
57
75
|
);
|
|
58
76
|
return;
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
yieldCount++;
|
|
62
|
-
eventController.setHandleData(handleData, matched, isPartial);
|
|
80
|
+
eventController.setHandleData(handleData, matched, isPartial, resolvedIds);
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
// Check again before final updates
|
|
@@ -67,12 +85,11 @@ async function processHandles(
|
|
|
67
85
|
return;
|
|
68
86
|
}
|
|
69
87
|
|
|
70
|
-
// For partial updates where the generator yielded nothing (
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// route might not push any breadcrumbs, but we still need to remove the old ones.
|
|
88
|
+
// For partial updates where the generator yielded nothing (every
|
|
89
|
+
// re-resolved handler pushed nothing), still call setHandleData so the
|
|
90
|
+
// cleanup pass can clear out stale buckets for those segments.
|
|
74
91
|
if (yieldCount === 0 && matched) {
|
|
75
|
-
eventController.setHandleData({}, matched, true);
|
|
92
|
+
eventController.setHandleData({}, matched, true, resolvedIds);
|
|
76
93
|
}
|
|
77
94
|
|
|
78
95
|
// After handles processing completes, update the cache's handleData.
|
|
@@ -100,9 +117,9 @@ export interface NavigationProviderProps {
|
|
|
100
117
|
eventController: EventController;
|
|
101
118
|
|
|
102
119
|
/**
|
|
103
|
-
* Initial
|
|
120
|
+
* Initial rendered tree + metadata from server payload
|
|
104
121
|
*/
|
|
105
|
-
initialPayload:
|
|
122
|
+
initialPayload: NavigationUpdate;
|
|
106
123
|
|
|
107
124
|
/**
|
|
108
125
|
* Navigation bridge for handling navigation
|
|
@@ -126,6 +143,25 @@ export interface NavigationProviderProps {
|
|
|
126
143
|
* When true, keeps TLS alive by sending HEAD requests after idle periods.
|
|
127
144
|
*/
|
|
128
145
|
warmupEnabled?: boolean;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* App version from server payload.
|
|
149
|
+
* Used only as a fallback when `appShellRef` is not supplied.
|
|
150
|
+
*/
|
|
151
|
+
version?: string;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* URL prefix for all routes (from createRouter({ basename })).
|
|
155
|
+
* Used only as a fallback when `appShellRef` is not supplied.
|
|
156
|
+
*/
|
|
157
|
+
basename?: string;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Live app-shell ref. When provided, the context's `basename` and `version`
|
|
161
|
+
* properties become live getters that track app-switch updates without
|
|
162
|
+
* invalidating the memoized context value.
|
|
163
|
+
*/
|
|
164
|
+
appShellRef?: AppShellRef;
|
|
129
165
|
}
|
|
130
166
|
|
|
131
167
|
/**
|
|
@@ -157,6 +193,9 @@ export function NavigationProvider({
|
|
|
157
193
|
themeConfig,
|
|
158
194
|
initialTheme,
|
|
159
195
|
warmupEnabled,
|
|
196
|
+
version,
|
|
197
|
+
basename,
|
|
198
|
+
appShellRef,
|
|
160
199
|
}: NavigationProviderProps): ReactNode {
|
|
161
200
|
// Track current payload for rendering (this triggers re-renders)
|
|
162
201
|
const [payload, setPayload] = useState(initialPayload);
|
|
@@ -168,7 +207,7 @@ export function NavigationProvider({
|
|
|
168
207
|
async (url: string, options?: NavigateOptions): Promise<void> => {
|
|
169
208
|
await bridge.navigate(url, options);
|
|
170
209
|
},
|
|
171
|
-
[]
|
|
210
|
+
[],
|
|
172
211
|
);
|
|
173
212
|
|
|
174
213
|
/**
|
|
@@ -178,16 +217,39 @@ export function NavigationProvider({
|
|
|
178
217
|
await bridge.refresh();
|
|
179
218
|
}, []);
|
|
180
219
|
|
|
181
|
-
// Context value is stable (store, eventController, navigate, refresh never
|
|
182
|
-
|
|
183
|
-
|
|
220
|
+
// Context value is stable (store, eventController, navigate, refresh never
|
|
221
|
+
// change). When an appShellRef is supplied, `basename` and `version` are
|
|
222
|
+
// installed as live getters so app-switch transitions (which update the ref)
|
|
223
|
+
// propagate to consumers without forcing a tree-wide rerender.
|
|
224
|
+
const contextValue = useMemo<NavigationStoreContextValue>(() => {
|
|
225
|
+
if (appShellRef) {
|
|
226
|
+
const value = {
|
|
227
|
+
store,
|
|
228
|
+
eventController,
|
|
229
|
+
navigate,
|
|
230
|
+
refresh,
|
|
231
|
+
} as NavigationStoreContextValue;
|
|
232
|
+
Object.defineProperty(value, "basename", {
|
|
233
|
+
configurable: true,
|
|
234
|
+
enumerable: true,
|
|
235
|
+
get: () => appShellRef.get().basename,
|
|
236
|
+
});
|
|
237
|
+
Object.defineProperty(value, "version", {
|
|
238
|
+
configurable: true,
|
|
239
|
+
enumerable: true,
|
|
240
|
+
get: () => appShellRef.get().version,
|
|
241
|
+
});
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
184
245
|
store,
|
|
185
246
|
eventController,
|
|
186
247
|
navigate,
|
|
187
248
|
refresh,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
249
|
+
version,
|
|
250
|
+
basename,
|
|
251
|
+
};
|
|
252
|
+
}, []);
|
|
191
253
|
|
|
192
254
|
// Connection warmup: keep TLS alive after idle periods.
|
|
193
255
|
// After 60s of no user interaction, marks connection as "cold".
|
|
@@ -252,7 +314,12 @@ export function NavigationProvider({
|
|
|
252
314
|
}
|
|
253
315
|
|
|
254
316
|
// Activity events that reset the idle timer
|
|
255
|
-
const activityEvents = [
|
|
317
|
+
const activityEvents = [
|
|
318
|
+
"mousemove",
|
|
319
|
+
"keydown",
|
|
320
|
+
"touchstart",
|
|
321
|
+
"scroll",
|
|
322
|
+
] as const;
|
|
256
323
|
const activityOptions: AddEventListenerOptions = { passive: true };
|
|
257
324
|
|
|
258
325
|
for (const event of activityEvents) {
|
|
@@ -271,14 +338,62 @@ export function NavigationProvider({
|
|
|
271
338
|
};
|
|
272
339
|
}, [warmupEnabled]);
|
|
273
340
|
|
|
341
|
+
// Cancel non-matching prefetches when navigation starts.
|
|
342
|
+
// Frees connections so the navigation fetch isn't competing with
|
|
343
|
+
// speculative prefetches. The prefetch matching the navigation target
|
|
344
|
+
// is kept alive so it can be reused via consumeInflightPrefetch.
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
let wasIdle = true;
|
|
347
|
+
const unsub = eventController.subscribe(() => {
|
|
348
|
+
const state = eventController.getState();
|
|
349
|
+
const isIdle = state.state === "idle" && !state.isStreaming;
|
|
350
|
+
if (wasIdle && !isIdle) {
|
|
351
|
+
cancelAllPrefetches(state.pendingUrl);
|
|
352
|
+
}
|
|
353
|
+
wasIdle = isIdle;
|
|
354
|
+
});
|
|
355
|
+
return unsub;
|
|
356
|
+
}, [eventController]);
|
|
357
|
+
|
|
358
|
+
// Pending scroll action to apply after React commits
|
|
359
|
+
const pendingScrollRef = useRef<NavigationUpdate["scroll"]>(undefined);
|
|
360
|
+
|
|
361
|
+
// Apply scroll after React commits the new content to the DOM
|
|
362
|
+
useLayoutEffect(() => {
|
|
363
|
+
const scrollAction = pendingScrollRef.current;
|
|
364
|
+
if (!scrollAction) return;
|
|
365
|
+
pendingScrollRef.current = undefined;
|
|
366
|
+
|
|
367
|
+
if (scrollAction.enabled === false) return;
|
|
368
|
+
|
|
369
|
+
handleNavigationEnd({
|
|
370
|
+
restore: scrollAction.restore,
|
|
371
|
+
scroll: scrollAction.enabled,
|
|
372
|
+
isStreaming: scrollAction.isStreaming,
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
274
376
|
// Subscribe to UI updates (for re-rendering the tree)
|
|
275
377
|
useEffect(() => {
|
|
276
378
|
const unsubscribe = store.onUpdate((update) => {
|
|
379
|
+
// Capture scroll intent — it will be applied in useLayoutEffect
|
|
380
|
+
// after React commits this state update to the DOM.
|
|
381
|
+
// Always assign (even undefined) to clear stale scroll from prior navigations,
|
|
382
|
+
// so server actions or error updates don't accidentally replay old scroll.
|
|
383
|
+
pendingScrollRef.current = update.scroll;
|
|
384
|
+
|
|
277
385
|
setPayload({
|
|
278
386
|
root: update.root,
|
|
279
387
|
metadata: update.metadata,
|
|
280
388
|
});
|
|
281
389
|
|
|
390
|
+
// Update route params. Only reset when the server actually sends a params
|
|
391
|
+
// map — an absent `params` field means "no change" (e.g., legacy action
|
|
392
|
+
// responses that omitted params). Explicit `{}` still clears correctly.
|
|
393
|
+
if (update.metadata.params !== undefined) {
|
|
394
|
+
eventController.setParams(update.metadata.params);
|
|
395
|
+
}
|
|
396
|
+
|
|
282
397
|
// Update handle data progressively as it streams in
|
|
283
398
|
if (update.metadata.handles) {
|
|
284
399
|
// Capture historyKey now - by the time async processing completes,
|
|
@@ -290,9 +405,10 @@ export function NavigationProvider({
|
|
|
290
405
|
store,
|
|
291
406
|
matched: update.metadata.matched,
|
|
292
407
|
isPartial: update.metadata.isPartial,
|
|
408
|
+
resolvedIds: update.metadata.resolvedIds,
|
|
293
409
|
historyKey,
|
|
294
410
|
}).catch((err) =>
|
|
295
|
-
console.error("[NavigationProvider] Error consuming handles:", err)
|
|
411
|
+
console.error("[NavigationProvider] Error consuming handles:", err),
|
|
296
412
|
);
|
|
297
413
|
} else if (update.metadata.cachedHandleData) {
|
|
298
414
|
// For back/forward navigation from cache, restore the cached handleData
|
|
@@ -300,14 +416,15 @@ export function NavigationProvider({
|
|
|
300
416
|
eventController.setHandleData(
|
|
301
417
|
update.metadata.cachedHandleData,
|
|
302
418
|
update.metadata.matched,
|
|
303
|
-
false // full replace - restore entire cached state
|
|
419
|
+
false, // full replace - restore entire cached state
|
|
304
420
|
);
|
|
305
421
|
} else if (update.metadata.matched) {
|
|
306
422
|
// For cached navigations without handleData, update segmentOrder to clean up stale data
|
|
307
423
|
eventController.setHandleData(
|
|
308
424
|
{}, // Empty data - all existing data not in matched will be cleaned up
|
|
309
425
|
update.metadata.matched,
|
|
310
|
-
true // partial update - will clean up segments not in matched
|
|
426
|
+
true, // partial update - will clean up segments not in matched
|
|
427
|
+
update.metadata.resolvedIds,
|
|
311
428
|
);
|
|
312
429
|
}
|
|
313
430
|
});
|
|
@@ -329,7 +446,11 @@ export function NavigationProvider({
|
|
|
329
446
|
// Build the content tree
|
|
330
447
|
let content = <RootErrorBoundary>{root}</RootErrorBoundary>;
|
|
331
448
|
|
|
332
|
-
// Wrap with ThemeProvider when theme is enabled
|
|
449
|
+
// Wrap with ThemeProvider when theme is enabled. The ThemeProvider is
|
|
450
|
+
// document-lifetime: its config comes from the initial load and does NOT
|
|
451
|
+
// swap on cross-app transitions, because the ThemeProvider sits above the
|
|
452
|
+
// segment tree and a smooth (no-reload) app switch cannot safely remount
|
|
453
|
+
// it. A new theme config only takes effect on a full document load.
|
|
333
454
|
if (themeConfig) {
|
|
334
455
|
content = (
|
|
335
456
|
<ThemeProvider config={themeConfig} initialTheme={initialTheme}>
|
|
@@ -338,6 +459,13 @@ export function NavigationProvider({
|
|
|
338
459
|
);
|
|
339
460
|
}
|
|
340
461
|
|
|
462
|
+
// Match SSR tree shape: NonceContext.Provider is always present so
|
|
463
|
+
// hydration sees the same component tree. Value is undefined on the
|
|
464
|
+
// client — CSP nonces are a server-side HTML concern.
|
|
465
|
+
content = (
|
|
466
|
+
<NonceContext.Provider value={undefined}>{content}</NonceContext.Provider>
|
|
467
|
+
);
|
|
468
|
+
|
|
341
469
|
return (
|
|
342
470
|
<NavigationStoreContext.Provider value={contextValue}>
|
|
343
471
|
{content}
|
|
@@ -41,6 +41,17 @@ export interface NavigationStoreContextValue {
|
|
|
41
41
|
* @returns Promise that resolves when refresh is complete
|
|
42
42
|
*/
|
|
43
43
|
refresh: () => Promise<void>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* App version from the initial server payload.
|
|
47
|
+
*/
|
|
48
|
+
version: string | undefined;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* URL prefix for all routes (from createRouter({ basename })).
|
|
52
|
+
* Used by Link and useRouter() to auto-prefix app-local paths.
|
|
53
|
+
*/
|
|
54
|
+
basename: string | undefined;
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
/**
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the handle-collection segment order from a raw `matched` list.
|
|
3
|
+
*
|
|
4
|
+
* Two responsibilities:
|
|
5
|
+
*
|
|
6
|
+
* 1. Drop loader sub-ids ("D" followed by a digit, e.g. "M0L0D1.user") —
|
|
7
|
+
* loaders never push handles.
|
|
8
|
+
*
|
|
9
|
+
* 2. Place each parallel slot id (contains ".@") immediately after its
|
|
10
|
+
* parent layout/route id. Raw segment-resolution emission order does NOT
|
|
11
|
+
* guarantee this: route-mounted parallels are resolved/pushed BEFORE the
|
|
12
|
+
* route handler's segment is appended (see fresh.ts:resolveSegment for
|
|
13
|
+
* routes, and revalidation.ts ~915-919), so matched can read
|
|
14
|
+
* `[..., R0.@panel, R0]`. collectHandleData consumes segmentOrder verbatim
|
|
15
|
+
* with later-wins semantics, so without normalization the route handler's
|
|
16
|
+
* Meta would override the slot's more-specific Meta — backwards.
|
|
17
|
+
*
|
|
18
|
+
* Slot-id format is `<parentShortCode>.@<slotName>`; `parentShortCode` never
|
|
19
|
+
* contains ".@", so splitting at the first ".@" reliably yields the parent.
|
|
20
|
+
*/
|
|
21
|
+
export function filterSegmentOrder(matched: string[]): string[] {
|
|
22
|
+
const slotsByParent = new Map<string, string[]>();
|
|
23
|
+
const nonSlots: string[] = [];
|
|
24
|
+
const nonSlotSet = new Set<string>();
|
|
25
|
+
|
|
26
|
+
for (const id of matched) {
|
|
27
|
+
if (/D\d+\./.test(id)) continue;
|
|
28
|
+
const slotIdx = id.indexOf(".@");
|
|
29
|
+
if (slotIdx >= 0) {
|
|
30
|
+
const parent = id.slice(0, slotIdx);
|
|
31
|
+
const list = slotsByParent.get(parent);
|
|
32
|
+
if (list) {
|
|
33
|
+
list.push(id);
|
|
34
|
+
} else {
|
|
35
|
+
slotsByParent.set(parent, [id]);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
nonSlots.push(id);
|
|
39
|
+
nonSlotSet.add(id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result: string[] = [];
|
|
44
|
+
for (const id of nonSlots) {
|
|
45
|
+
result.push(id);
|
|
46
|
+
const slots = slotsByParent.get(id);
|
|
47
|
+
if (slots) result.push(...slots);
|
|
48
|
+
}
|
|
49
|
+
// Defensive: any slot whose parent is missing from the filtered list still
|
|
50
|
+
// gets included rather than silently dropped. Shouldn't happen in practice.
|
|
51
|
+
for (const [parent, slots] of slotsByParent) {
|
|
52
|
+
if (!nonSlotSet.has(parent)) result.push(...slots);
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
// React exports for browser navigation
|
|
2
2
|
|
|
3
3
|
// Hook with Zustand-style selectors
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export { useNavigation } from "./use-navigation.js";
|
|
5
|
+
|
|
6
|
+
// Router actions hook (stable reference, no re-renders)
|
|
7
|
+
export { useRouter } from "./use-router.js";
|
|
8
|
+
|
|
9
|
+
// URL hooks
|
|
10
|
+
export { usePathname } from "./use-pathname.js";
|
|
11
|
+
export { useSearchParams } from "./use-search-params.js";
|
|
12
|
+
export { useParams } from "./use-params.js";
|
|
9
13
|
|
|
10
14
|
// Action state tracking hook
|
|
11
15
|
export { useAction, type TrackedActionState } from "./use-action.js";
|
|
12
16
|
|
|
13
17
|
// Segments state hook
|
|
14
|
-
export { useSegments,
|
|
18
|
+
export { useSegments, type SegmentsState } from "./use-segments.js";
|
|
15
19
|
|
|
16
20
|
// Handle data hook
|
|
17
|
-
export { useHandle
|
|
21
|
+
export { useHandle } from "./use-handle.js";
|
|
22
|
+
|
|
23
|
+
// Mount-aware reverse hook
|
|
24
|
+
export { useReverse } from "./use-reverse.js";
|
|
18
25
|
|
|
19
26
|
// Client cache controls hook
|
|
20
27
|
export {
|
|
@@ -35,11 +42,7 @@ export {
|
|
|
35
42
|
} from "./context.js";
|
|
36
43
|
|
|
37
44
|
// Link component
|
|
38
|
-
export {
|
|
39
|
-
Link,
|
|
40
|
-
type LinkProps,
|
|
41
|
-
type PrefetchStrategy,
|
|
42
|
-
} from "./Link.js";
|
|
45
|
+
export { Link, type LinkProps, type PrefetchStrategy } from "./Link.js";
|
|
43
46
|
|
|
44
47
|
// Link status hook
|
|
45
48
|
export { useLinkStatus, type LinkStatus } from "./use-link-status.js";
|
|
@@ -4,11 +4,22 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Internal entry representing a state value with its unique key
|
|
7
|
+
* Internal entry representing a state value with its unique key.
|
|
8
|
+
* When __rsc_ls_lazy is true, __rsc_ls_value holds a getter function
|
|
9
|
+
* that is called at navigation time (not at entry creation time).
|
|
8
10
|
*/
|
|
9
11
|
export interface LocationStateEntry {
|
|
10
12
|
readonly __rsc_ls_key: string;
|
|
11
13
|
readonly __rsc_ls_value: unknown;
|
|
14
|
+
readonly __rsc_ls_lazy?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for createLocationState
|
|
19
|
+
*/
|
|
20
|
+
export interface LocationStateOptions {
|
|
21
|
+
/** When true, the state is cleared from history after first read (flash message pattern) */
|
|
22
|
+
flash?: boolean;
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
/**
|
|
@@ -19,84 +30,113 @@ export interface LocationStateEntry {
|
|
|
19
30
|
*/
|
|
20
31
|
export interface LocationStateDefinition<TArgs extends unknown[], TState> {
|
|
21
32
|
(...args: TArgs): LocationStateEntry;
|
|
22
|
-
|
|
33
|
+
/** Injected by Vite plugin - do not set manually */
|
|
34
|
+
__rsc_ls_key: string;
|
|
35
|
+
/** Whether this state auto-clears after first read */
|
|
36
|
+
readonly __rsc_ls_flash: boolean;
|
|
37
|
+
/** Read the current value from history.state (client-side only, undefined during SSR) */
|
|
38
|
+
read(): TState | undefined;
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
// Track used keys to detect duplicates in development
|
|
26
|
-
const usedKeys = new Set<string>();
|
|
27
|
-
|
|
28
41
|
/**
|
|
29
42
|
* Create a type-safe location state definition
|
|
30
43
|
*
|
|
31
|
-
* The key is auto-
|
|
32
|
-
* file path and export name. No manual key required.
|
|
44
|
+
* The key is auto-injected by the Vite exposeInternalIds plugin as a property
|
|
45
|
+
* based on file path and export name. No manual key required.
|
|
33
46
|
*
|
|
34
|
-
* @param
|
|
47
|
+
* @param options Optional configuration
|
|
35
48
|
* @returns A typed state definition for use with Link and useLocationState
|
|
36
49
|
*
|
|
37
50
|
* @example
|
|
38
51
|
* ```typescript
|
|
39
|
-
* //
|
|
52
|
+
* // Persistent state (survives back/forward)
|
|
40
53
|
* export const ProductState = createLocationState<{ name: string; price: number }>();
|
|
41
54
|
*
|
|
42
|
-
* //
|
|
43
|
-
*
|
|
44
|
-
* View Product
|
|
45
|
-
* </Link>
|
|
55
|
+
* // Flash state (cleared after first read)
|
|
56
|
+
* export const FlashMessage = createLocationState<{ text: string }>({ flash: true });
|
|
46
57
|
*
|
|
47
|
-
* //
|
|
48
|
-
* <Link to="/
|
|
49
|
-
* Checkout
|
|
50
|
-
* </Link>
|
|
58
|
+
* // Use in Link
|
|
59
|
+
* <Link to="/product/123" state={[ProductState({ name: "Widget", price: 9.99 })]}>
|
|
51
60
|
*
|
|
52
|
-
* //
|
|
53
|
-
*
|
|
61
|
+
* // Just-in-time typed state (getter called at click time, not render time).
|
|
62
|
+
* // Must be in a client component — the getter function can't cross the RSC boundary.
|
|
63
|
+
* <Link
|
|
64
|
+
* to="/product/123"
|
|
65
|
+
* state={[ProductState(() => ({ name: product.name, price: product.price }))]}
|
|
66
|
+
* >
|
|
54
67
|
*
|
|
55
|
-
* // Read with
|
|
56
|
-
* const
|
|
57
|
-
*
|
|
68
|
+
* // Read with hook (reactive)
|
|
69
|
+
* const product = useLocationState(ProductState);
|
|
70
|
+
*
|
|
71
|
+
* // Read without hook (snapshot, client-side only)
|
|
72
|
+
* const snap = ProductState.read();
|
|
58
73
|
* ```
|
|
59
74
|
*/
|
|
60
75
|
export function createLocationState<TState>(
|
|
61
|
-
|
|
76
|
+
options?: LocationStateOptions,
|
|
62
77
|
): LocationStateDefinition<[TState | (() => TState)], TState> {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"[rsc-router] createLocationState is missing a key. " +
|
|
66
|
-
"Make sure the exposeLocationStateId Vite plugin is enabled and " +
|
|
67
|
-
"the state is exported with: export const MyState = createLocationState(...)"
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
const fullKey = `__rsc_ls_${key}`;
|
|
78
|
+
const flash = options?.flash ?? false;
|
|
79
|
+
let _key: string | undefined;
|
|
71
80
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
function getKey(): string {
|
|
82
|
+
if (!_key && process.env.NODE_ENV === "development") {
|
|
83
|
+
throw new Error(
|
|
84
|
+
"[rsc-router] createLocationState key not set. " +
|
|
85
|
+
"Make sure the exposeInternalIds Vite plugin is enabled and " +
|
|
86
|
+
"the state is exported with: export const MyState = createLocationState(...)",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return _key!;
|
|
78
90
|
}
|
|
79
|
-
usedKeys.add(fullKey);
|
|
80
91
|
|
|
81
|
-
const
|
|
82
|
-
(stateOrGetter
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
+
const fn = (stateOrGetter: TState | (() => TState)): LocationStateEntry => {
|
|
93
|
+
if (typeof stateOrGetter === "function") {
|
|
94
|
+
// Store getter as-is; resolved at navigation time by resolveLocationStateEntries()
|
|
95
|
+
return {
|
|
96
|
+
__rsc_ls_key: getKey(),
|
|
97
|
+
__rsc_ls_value: stateOrGetter,
|
|
98
|
+
__rsc_ls_lazy: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
__rsc_ls_key: getKey(),
|
|
103
|
+
__rsc_ls_value: stateOrGetter,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Use defineProperty for __rsc_ls_key to avoid Object.assign evaluating
|
|
108
|
+
// the getter during construction (before the Vite plugin sets the key).
|
|
109
|
+
Object.defineProperty(fn, "__rsc_ls_key", {
|
|
110
|
+
get: () => getKey(),
|
|
111
|
+
set: (k: string) => {
|
|
112
|
+
_key = k;
|
|
113
|
+
},
|
|
114
|
+
enumerable: true,
|
|
115
|
+
configurable: true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
Object.defineProperty(fn, "__rsc_ls_flash", {
|
|
119
|
+
value: flash,
|
|
120
|
+
enumerable: true,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
Object.defineProperty(fn, "read", {
|
|
124
|
+
value: (): TState | undefined => {
|
|
125
|
+
if (typeof window === "undefined") return undefined;
|
|
126
|
+
return window.history.state?.[getKey()] as TState | undefined;
|
|
127
|
+
},
|
|
128
|
+
enumerable: true,
|
|
129
|
+
});
|
|
92
130
|
|
|
93
|
-
return
|
|
131
|
+
return fn as LocationStateDefinition<[TState | (() => TState)], TState>;
|
|
94
132
|
}
|
|
95
133
|
|
|
96
134
|
/**
|
|
97
135
|
* Check if a value is a LocationStateEntry
|
|
98
136
|
*/
|
|
99
|
-
export function isLocationStateEntry(
|
|
137
|
+
export function isLocationStateEntry(
|
|
138
|
+
value: unknown,
|
|
139
|
+
): value is LocationStateEntry {
|
|
100
140
|
return (
|
|
101
141
|
value !== null &&
|
|
102
142
|
typeof value === "object" &&
|
|
@@ -110,11 +150,13 @@ export function isLocationStateEntry(value: unknown): value is LocationStateEntr
|
|
|
110
150
|
* Resolve state entries into a flat object for history.state
|
|
111
151
|
*/
|
|
112
152
|
export function resolveLocationStateEntries(
|
|
113
|
-
entries: LocationStateEntry[]
|
|
153
|
+
entries: LocationStateEntry[],
|
|
114
154
|
): Record<string, unknown> {
|
|
115
155
|
const result: Record<string, unknown> = {};
|
|
116
156
|
for (const entry of entries) {
|
|
117
|
-
result[entry.__rsc_ls_key] = entry.
|
|
157
|
+
result[entry.__rsc_ls_key] = entry.__rsc_ls_lazy
|
|
158
|
+
? (entry.__rsc_ls_value as () => unknown)()
|
|
159
|
+
: entry.__rsc_ls_value;
|
|
118
160
|
}
|
|
119
161
|
return result;
|
|
120
162
|
}
|