@rangojs/router 0.0.0-experimental.5 → 0.0.0-experimental.51
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 +884 -4
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +4567 -769
- package/package.json +77 -58
- package/skills/breadcrumbs/SKILL.md +250 -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 +167 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +89 -30
- package/skills/loader/SKILL.md +403 -43
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +204 -1
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +257 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +92 -64
- 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 +282 -557
- package/src/browser/navigation-client.ts +157 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +303 -310
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +144 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +144 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +193 -73
- package/src/browser/react/NavigationProvider.tsx +160 -13
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -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 +24 -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 +32 -79
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +188 -55
- package/src/browser/scroll-restoration.ts +117 -44
- package/src/browser/segment-reconciler.ts +221 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +118 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +265 -0
- 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 +411 -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 +479 -0
- package/src/build/route-types/scan-filter.ts +78 -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 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- 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 +3 -1
- package/src/client.tsx +106 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +15 -29
- 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 +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +153 -19
- package/src/index.ts +211 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -147
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +959 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +431 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +59 -8
- package/src/router/content-negotiation.ts +116 -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 +400 -84
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +222 -123
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +154 -35
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +440 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +27 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +55 -33
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +226 -0
- package/src/router/middleware.ts +327 -369
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +683 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1301 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -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 +239 -0
- package/src/router/types.ts +77 -3
- package/src/router.ts +665 -4182
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +764 -754
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +172 -21
- package/src/server/context.ts +278 -58
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +474 -74
- package/src/server.ts +35 -128
- package/src/ssr/index.tsx +101 -31
- package/src/static-handler.ts +114 -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 +777 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -782
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +27 -16
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -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/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -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/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- 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/warmup/connection-warmup.tsx +0 -94
- /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,8 +24,10 @@ 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";
|
|
26
|
-
import {
|
|
29
|
+
import { cancelAllPrefetches } from "../prefetch/queue.js";
|
|
30
|
+
import { handleNavigationEnd } from "../scroll-restoration.js";
|
|
27
31
|
|
|
28
32
|
/**
|
|
29
33
|
* Process handles from an async generator, updating the event controller
|
|
@@ -43,7 +47,7 @@ async function processHandles(
|
|
|
43
47
|
matched?: string[];
|
|
44
48
|
isPartial?: boolean;
|
|
45
49
|
historyKey: string;
|
|
46
|
-
}
|
|
50
|
+
},
|
|
47
51
|
): Promise<void> {
|
|
48
52
|
const { eventController, store, matched, isPartial, historyKey } = opts;
|
|
49
53
|
|
|
@@ -54,7 +58,7 @@ async function processHandles(
|
|
|
54
58
|
// the current route's breadcrumbs (e.g., quick popstate after clicking a link).
|
|
55
59
|
if (historyKey !== store.getHistoryKey()) {
|
|
56
60
|
console.log(
|
|
57
|
-
"[NavigationProvider] Stopping handle processing - user navigated away"
|
|
61
|
+
"[NavigationProvider] Stopping handle processing - user navigated away",
|
|
58
62
|
);
|
|
59
63
|
return;
|
|
60
64
|
}
|
|
@@ -101,9 +105,9 @@ export interface NavigationProviderProps {
|
|
|
101
105
|
eventController: EventController;
|
|
102
106
|
|
|
103
107
|
/**
|
|
104
|
-
* Initial
|
|
108
|
+
* Initial rendered tree + metadata from server payload
|
|
105
109
|
*/
|
|
106
|
-
initialPayload:
|
|
110
|
+
initialPayload: NavigationUpdate;
|
|
107
111
|
|
|
108
112
|
/**
|
|
109
113
|
* Navigation bridge for handling navigation
|
|
@@ -124,9 +128,15 @@ export interface NavigationProviderProps {
|
|
|
124
128
|
|
|
125
129
|
/**
|
|
126
130
|
* Whether connection warmup is enabled.
|
|
127
|
-
* When true,
|
|
131
|
+
* When true, keeps TLS alive by sending HEAD requests after idle periods.
|
|
128
132
|
*/
|
|
129
133
|
warmupEnabled?: boolean;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* App version from server payload (stable, immutable).
|
|
137
|
+
* Forwarded to prefetch requests for version mismatch detection.
|
|
138
|
+
*/
|
|
139
|
+
version?: string;
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
/**
|
|
@@ -158,6 +168,7 @@ export function NavigationProvider({
|
|
|
158
168
|
themeConfig,
|
|
159
169
|
initialTheme,
|
|
160
170
|
warmupEnabled,
|
|
171
|
+
version,
|
|
161
172
|
}: NavigationProviderProps): ReactNode {
|
|
162
173
|
// Track current payload for rendering (this triggers re-renders)
|
|
163
174
|
const [payload, setPayload] = useState(initialPayload);
|
|
@@ -169,7 +180,7 @@ export function NavigationProvider({
|
|
|
169
180
|
async (url: string, options?: NavigateOptions): Promise<void> => {
|
|
170
181
|
await bridge.navigate(url, options);
|
|
171
182
|
},
|
|
172
|
-
[]
|
|
183
|
+
[],
|
|
173
184
|
);
|
|
174
185
|
|
|
175
186
|
/**
|
|
@@ -186,18 +197,148 @@ export function NavigationProvider({
|
|
|
186
197
|
eventController,
|
|
187
198
|
navigate,
|
|
188
199
|
refresh,
|
|
200
|
+
version,
|
|
189
201
|
}),
|
|
190
|
-
[]
|
|
202
|
+
[],
|
|
191
203
|
);
|
|
192
204
|
|
|
205
|
+
// Connection warmup: keep TLS alive after idle periods.
|
|
206
|
+
// After 60s of no user interaction, marks connection as "cold".
|
|
207
|
+
// On next interaction or visibility change, sends a HEAD request to warm TLS
|
|
208
|
+
// before the user actually clicks a link.
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (!warmupEnabled) return;
|
|
211
|
+
|
|
212
|
+
const IDLE_TIMEOUT = 60_000;
|
|
213
|
+
const DEBOUNCE_DELAY = 150;
|
|
214
|
+
|
|
215
|
+
let idleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
216
|
+
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
217
|
+
let isCold = false;
|
|
218
|
+
let warmupListenersAttached = false;
|
|
219
|
+
|
|
220
|
+
function sendWarmup() {
|
|
221
|
+
isCold = false;
|
|
222
|
+
fetch("/?_rsc_warmup", { method: "HEAD" }).catch(() => {});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function triggerWarmup() {
|
|
226
|
+
if (!isCold) return;
|
|
227
|
+
clearTimeout(debounceTimer);
|
|
228
|
+
debounceTimer = setTimeout(() => {
|
|
229
|
+
sendWarmup();
|
|
230
|
+
detachWarmupListeners();
|
|
231
|
+
resetIdleTimer();
|
|
232
|
+
}, DEBOUNCE_DELAY);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function onVisibilityChange() {
|
|
236
|
+
if (document.visibilityState === "visible" && isCold) {
|
|
237
|
+
triggerWarmup();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function attachWarmupListeners() {
|
|
242
|
+
if (warmupListenersAttached) return;
|
|
243
|
+
warmupListenersAttached = true;
|
|
244
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
245
|
+
document.addEventListener("mousemove", triggerWarmup, { once: true });
|
|
246
|
+
document.addEventListener("touchstart", triggerWarmup, { once: true });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function detachWarmupListeners() {
|
|
250
|
+
warmupListenersAttached = false;
|
|
251
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
252
|
+
document.removeEventListener("mousemove", triggerWarmup);
|
|
253
|
+
document.removeEventListener("touchstart", triggerWarmup);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function markCold() {
|
|
257
|
+
isCold = true;
|
|
258
|
+
attachWarmupListeners();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function resetIdleTimer() {
|
|
262
|
+
clearTimeout(idleTimer);
|
|
263
|
+
isCold = false;
|
|
264
|
+
idleTimer = setTimeout(markCold, IDLE_TIMEOUT);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Activity events that reset the idle timer
|
|
268
|
+
const activityEvents = [
|
|
269
|
+
"mousemove",
|
|
270
|
+
"keydown",
|
|
271
|
+
"touchstart",
|
|
272
|
+
"scroll",
|
|
273
|
+
] as const;
|
|
274
|
+
const activityOptions: AddEventListenerOptions = { passive: true };
|
|
275
|
+
|
|
276
|
+
for (const event of activityEvents) {
|
|
277
|
+
document.addEventListener(event, resetIdleTimer, activityOptions);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
resetIdleTimer();
|
|
281
|
+
|
|
282
|
+
return () => {
|
|
283
|
+
clearTimeout(idleTimer);
|
|
284
|
+
clearTimeout(debounceTimer);
|
|
285
|
+
detachWarmupListeners();
|
|
286
|
+
for (const event of activityEvents) {
|
|
287
|
+
document.removeEventListener(event, resetIdleTimer);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}, [warmupEnabled]);
|
|
291
|
+
|
|
292
|
+
// Cancel speculative prefetches when navigation starts.
|
|
293
|
+
// Viewport/render prefetches should not compete with navigation fetches.
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
let wasIdle = true;
|
|
296
|
+
const unsub = eventController.subscribe(() => {
|
|
297
|
+
const state = eventController.getState();
|
|
298
|
+
const isIdle = state.state === "idle" && !state.isStreaming;
|
|
299
|
+
if (wasIdle && !isIdle) {
|
|
300
|
+
cancelAllPrefetches();
|
|
301
|
+
}
|
|
302
|
+
wasIdle = isIdle;
|
|
303
|
+
});
|
|
304
|
+
return unsub;
|
|
305
|
+
}, [eventController]);
|
|
306
|
+
|
|
307
|
+
// Pending scroll action to apply after React commits
|
|
308
|
+
const pendingScrollRef = useRef<NavigationUpdate["scroll"]>(undefined);
|
|
309
|
+
|
|
310
|
+
// Apply scroll after React commits the new content to the DOM
|
|
311
|
+
useLayoutEffect(() => {
|
|
312
|
+
const scrollAction = pendingScrollRef.current;
|
|
313
|
+
if (!scrollAction) return;
|
|
314
|
+
pendingScrollRef.current = undefined;
|
|
315
|
+
|
|
316
|
+
if (scrollAction.enabled === false) return;
|
|
317
|
+
|
|
318
|
+
handleNavigationEnd({
|
|
319
|
+
restore: scrollAction.restore,
|
|
320
|
+
scroll: scrollAction.enabled,
|
|
321
|
+
isStreaming: scrollAction.isStreaming,
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
193
325
|
// Subscribe to UI updates (for re-rendering the tree)
|
|
194
326
|
useEffect(() => {
|
|
195
327
|
const unsubscribe = store.onUpdate((update) => {
|
|
328
|
+
// Capture scroll intent — it will be applied in useLayoutEffect
|
|
329
|
+
// after React commits this state update to the DOM.
|
|
330
|
+
// Always assign (even undefined) to clear stale scroll from prior navigations,
|
|
331
|
+
// so server actions or error updates don't accidentally replay old scroll.
|
|
332
|
+
pendingScrollRef.current = update.scroll;
|
|
333
|
+
|
|
196
334
|
setPayload({
|
|
197
335
|
root: update.root,
|
|
198
336
|
metadata: update.metadata,
|
|
199
337
|
});
|
|
200
338
|
|
|
339
|
+
// Update route params
|
|
340
|
+
eventController.setParams(update.metadata.params ?? {});
|
|
341
|
+
|
|
201
342
|
// Update handle data progressively as it streams in
|
|
202
343
|
if (update.metadata.handles) {
|
|
203
344
|
// Capture historyKey now - by the time async processing completes,
|
|
@@ -211,7 +352,7 @@ export function NavigationProvider({
|
|
|
211
352
|
isPartial: update.metadata.isPartial,
|
|
212
353
|
historyKey,
|
|
213
354
|
}).catch((err) =>
|
|
214
|
-
console.error("[NavigationProvider] Error consuming handles:", err)
|
|
355
|
+
console.error("[NavigationProvider] Error consuming handles:", err),
|
|
215
356
|
);
|
|
216
357
|
} else if (update.metadata.cachedHandleData) {
|
|
217
358
|
// For back/forward navigation from cache, restore the cached handleData
|
|
@@ -219,14 +360,14 @@ export function NavigationProvider({
|
|
|
219
360
|
eventController.setHandleData(
|
|
220
361
|
update.metadata.cachedHandleData,
|
|
221
362
|
update.metadata.matched,
|
|
222
|
-
false // full replace - restore entire cached state
|
|
363
|
+
false, // full replace - restore entire cached state
|
|
223
364
|
);
|
|
224
365
|
} else if (update.metadata.matched) {
|
|
225
366
|
// For cached navigations without handleData, update segmentOrder to clean up stale data
|
|
226
367
|
eventController.setHandleData(
|
|
227
368
|
{}, // Empty data - all existing data not in matched will be cleaned up
|
|
228
369
|
update.metadata.matched,
|
|
229
|
-
true // partial update - will clean up segments not in matched
|
|
370
|
+
true, // partial update - will clean up segments not in matched
|
|
230
371
|
);
|
|
231
372
|
}
|
|
232
373
|
});
|
|
@@ -257,10 +398,16 @@ export function NavigationProvider({
|
|
|
257
398
|
);
|
|
258
399
|
}
|
|
259
400
|
|
|
401
|
+
// Match SSR tree shape: NonceContext.Provider is always present so
|
|
402
|
+
// hydration sees the same component tree. Value is undefined on the
|
|
403
|
+
// client — CSP nonces are a server-side HTML concern.
|
|
404
|
+
content = (
|
|
405
|
+
<NonceContext.Provider value={undefined}>{content}</NonceContext.Provider>
|
|
406
|
+
);
|
|
407
|
+
|
|
260
408
|
return (
|
|
261
409
|
<NavigationStoreContext.Provider value={contextValue}>
|
|
262
410
|
{content}
|
|
263
|
-
{warmupEnabled && <ConnectionWarmup />}
|
|
264
411
|
</NavigationStoreContext.Provider>
|
|
265
412
|
);
|
|
266
413
|
}
|
|
@@ -41,6 +41,12 @@ 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 server payload (stable, immutable).
|
|
47
|
+
* Used in prefetch requests for version mismatch detection.
|
|
48
|
+
*/
|
|
49
|
+
version: string | undefined;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter segment IDs to only include routes and layouts.
|
|
3
|
+
* Excludes parallels (contain .@) and loaders (contain D followed by digit).
|
|
4
|
+
*/
|
|
5
|
+
export function filterSegmentOrder(matched: string[]): string[] {
|
|
6
|
+
return matched.filter((id) => {
|
|
7
|
+
if (id.includes(".@")) return false;
|
|
8
|
+
if (/D\d+\./.test(id)) return false;
|
|
9
|
+
return true;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -1,20 +1,24 @@
|
|
|
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";
|
|
18
22
|
|
|
19
23
|
// Client cache controls hook
|
|
20
24
|
export {
|
|
@@ -35,11 +39,7 @@ export {
|
|
|
35
39
|
} from "./context.js";
|
|
36
40
|
|
|
37
41
|
// Link component
|
|
38
|
-
export {
|
|
39
|
-
Link,
|
|
40
|
-
type LinkProps,
|
|
41
|
-
type PrefetchStrategy,
|
|
42
|
-
} from "./Link.js";
|
|
42
|
+
export { Link, type LinkProps, type PrefetchStrategy } from "./Link.js";
|
|
43
43
|
|
|
44
44
|
// Link status hook
|
|
45
45
|
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
|
}
|
|
@@ -10,53 +10,98 @@ export {
|
|
|
10
10
|
resolveLocationStateEntries,
|
|
11
11
|
type LocationStateEntry,
|
|
12
12
|
type LocationStateDefinition,
|
|
13
|
+
type LocationStateOptions,
|
|
13
14
|
} from "./location-state-shared.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Hook to read location state from history.state
|
|
17
18
|
*
|
|
19
|
+
* Behavior depends on the definition:
|
|
20
|
+
* - Normal state: persists across navigations, reactive to popstate
|
|
21
|
+
* - Flash state (created with { flash: true }): read once, cleared after paint
|
|
22
|
+
*
|
|
18
23
|
* Overloaded:
|
|
19
24
|
* - With definition: Returns typed state from the specific key
|
|
20
|
-
* - With type param only: Returns
|
|
25
|
+
* - With type param only: Returns plain state from history.state.state
|
|
21
26
|
*
|
|
22
27
|
* @example
|
|
23
28
|
* ```typescript
|
|
24
|
-
* //
|
|
25
|
-
* const ProductState = createLocationState<{ name: string }>(
|
|
29
|
+
* // Persistent state
|
|
30
|
+
* const ProductState = createLocationState<{ name: string }>();
|
|
26
31
|
* const state = useLocationState(ProductState);
|
|
27
|
-
* // state: { name: string } | undefined
|
|
28
32
|
*
|
|
29
|
-
* //
|
|
30
|
-
* const
|
|
33
|
+
* // Flash state (auto-clears after paint)
|
|
34
|
+
* const FlashMsg = createLocationState<{ text: string }>({ flash: true });
|
|
35
|
+
* const flash = useLocationState(FlashMsg);
|
|
36
|
+
*
|
|
37
|
+
* // Plain state access (reads from history.state.state)
|
|
38
|
+
* const state = useLocationState<{ from?: string }>();
|
|
31
39
|
* ```
|
|
32
40
|
*/
|
|
33
41
|
export function useLocationState<TArgs extends unknown[], TState>(
|
|
34
|
-
definition: LocationStateDefinition<TArgs, TState
|
|
42
|
+
definition: LocationStateDefinition<TArgs, TState>,
|
|
35
43
|
): TState | undefined;
|
|
36
44
|
export function useLocationState<T = unknown>(): T | undefined;
|
|
37
45
|
export function useLocationState<TArgs extends unknown[], TState>(
|
|
38
|
-
definition?: LocationStateDefinition<TArgs, TState
|
|
46
|
+
definition?: LocationStateDefinition<TArgs, TState>,
|
|
39
47
|
): TState | undefined {
|
|
48
|
+
const key = definition?.__rsc_ls_key;
|
|
49
|
+
const isFlash = definition?.__rsc_ls_flash ?? false;
|
|
50
|
+
|
|
40
51
|
const [state, setState] = useState<TState | undefined>(() => {
|
|
41
52
|
if (typeof window === "undefined") return undefined;
|
|
42
|
-
if (
|
|
43
|
-
return window.history.state?.[
|
|
53
|
+
if (key) {
|
|
54
|
+
return window.history.state?.[key] as TState | undefined;
|
|
44
55
|
}
|
|
45
|
-
//
|
|
56
|
+
// Plain state: stored under history.state.state
|
|
46
57
|
return window.history.state?.state as TState | undefined;
|
|
47
58
|
});
|
|
48
59
|
|
|
60
|
+
// Subscribe to popstate and programmatic state changes
|
|
49
61
|
useEffect(() => {
|
|
50
62
|
const handlePopstate = () => {
|
|
51
|
-
if (
|
|
52
|
-
setState(window.history.state?.[
|
|
63
|
+
if (key) {
|
|
64
|
+
setState(window.history.state?.[key] as TState | undefined);
|
|
53
65
|
} else {
|
|
54
66
|
setState(window.history.state?.state as TState | undefined);
|
|
55
67
|
}
|
|
56
68
|
};
|
|
69
|
+
|
|
70
|
+
// Handle programmatic state changes (same-page navigation with
|
|
71
|
+
// ctx.setLocationState where components don't remount)
|
|
72
|
+
const handleLocationState = () => {
|
|
73
|
+
if (key) {
|
|
74
|
+
const val = window.history.state?.[key] as TState | undefined;
|
|
75
|
+
if (isFlash) {
|
|
76
|
+
// For flash state, only update if there's a new value
|
|
77
|
+
if (val !== undefined) {
|
|
78
|
+
setState(val);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
setState(val);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
setState(window.history.state?.state as TState | undefined);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
57
88
|
window.addEventListener("popstate", handlePopstate);
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
window.addEventListener("__rsc_locationstate", handleLocationState);
|
|
90
|
+
return () => {
|
|
91
|
+
window.removeEventListener("popstate", handlePopstate);
|
|
92
|
+
window.removeEventListener("__rsc_locationstate", handleLocationState);
|
|
93
|
+
};
|
|
94
|
+
}, [key, isFlash]);
|
|
95
|
+
|
|
96
|
+
// Flash: clear from history.state after paint so subsequent navigations don't see it.
|
|
97
|
+
// Depends on `state` so it re-runs when state is set via the event listener.
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (isFlash && key && state !== undefined) {
|
|
100
|
+
const cleaned = { ...window.history.state };
|
|
101
|
+
delete cleaned[key];
|
|
102
|
+
window.history.replaceState(cleaned, "", window.location.href);
|
|
103
|
+
}
|
|
104
|
+
}, [isFlash, key, state]);
|
|
60
105
|
|
|
61
106
|
return state;
|
|
62
107
|
}
|