@rangojs/router 0.17.0 → 0.19.0
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/README.md +3 -2
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +18 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/cache-key-utils.d.ts +48 -2
- package/dist/types/cache/cache-policy.d.ts +18 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +192 -26
- package/dist/types/cache/cache-tag.d.ts +116 -11
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +41 -8
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +141 -105
- package/dist/types/cache/tag-invalidation.d.ts +54 -3
- package/dist/types/cache/types.d.ts +267 -96
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/middleware-types.d.ts +6 -1
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
- package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +21 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
- package/dist/types/rsc/shell-capture.d.ts +202 -74
- package/dist/types/rsc/shell-serve.d.ts +144 -50
- package/dist/types/rsc/types.d.ts +9 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +155 -33
- package/dist/types/server/cookie-store.d.ts +33 -0
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +210 -63
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +133 -0
- package/dist/types/testing/shell-status.d.ts +44 -19
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +14 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +21 -18
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1005 -105
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +175 -76
- package/skills/caching/SKILL.md +361 -33
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +12 -7
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +52 -37
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +32 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +101 -7
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +197 -44
- package/skills/middleware/SKILL.md +34 -9
- package/skills/migrate-nextjs/SKILL.md +68 -37
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +65 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +715 -371
- package/skills/prerender/SKILL.md +30 -17
- package/skills/rango/SKILL.md +72 -34
- package/skills/route/SKILL.md +14 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +49 -43
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +19 -1
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +202 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +56 -4
- package/skills/typesafety/env-and-bindings.md +18 -2
- package/skills/use-cache/SKILL.md +91 -23
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +75 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +9 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +48 -1
- package/src/cache/cache-key-utils.ts +62 -2
- package/src/cache/cache-policy.ts +31 -4
- package/src/cache/cache-runtime.ts +209 -129
- package/src/cache/cache-scope.ts +573 -69
- package/src/cache/cache-tag.ts +475 -19
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +1094 -376
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +53 -9
- package/src/cache/index.ts +2 -0
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +249 -277
- package/src/cache/tag-invalidation.ts +109 -18
- package/src/cache/types.ts +274 -102
- package/src/cache/vercel/vercel-cache-store.ts +561 -74
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +173 -110
- package/src/route-definition/dsl-helpers.ts +6 -3
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +42 -5
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +30 -8
- package/src/router/loader-resolution.ts +89 -33
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +89 -42
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +14 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +52 -19
- package/src/router/match-middleware/cache-store.ts +36 -10
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +20 -4
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +10 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +18 -13
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +366 -70
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +215 -126
- package/src/router/segment-resolution/mask-nested.ts +161 -42
- package/src/router/segment-resolution/revalidation.ts +27 -13
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +47 -3
- package/src/rsc/handler.ts +24 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +10 -32
- package/src/rsc/response-cache-serve.ts +69 -78
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +712 -338
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +49 -29
- package/src/rsc/shell-capture-constants.ts +182 -12
- package/src/rsc/shell-capture.ts +1126 -612
- package/src/rsc/shell-serve.ts +331 -85
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +9 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +328 -72
- package/src/server/cookie-store.ts +184 -133
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +364 -100
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +67 -5
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +381 -0
- package/src/testing/shell-status.ts +73 -47
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/handler-context.ts +14 -1
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +21 -18
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +9 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +89 -9
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -36,6 +36,13 @@ import {
|
|
|
36
36
|
collectClientRevalidationDecisions,
|
|
37
37
|
setActiveInterceptTargets,
|
|
38
38
|
} from "../client-urls/navigation.js";
|
|
39
|
+
import {
|
|
40
|
+
decideCommitGatedOff,
|
|
41
|
+
mergeSegmentParams,
|
|
42
|
+
type TransitionWhenActionInput,
|
|
43
|
+
} from "./transition-when.js";
|
|
44
|
+
import { buildHistoryState } from "./history-state.js";
|
|
45
|
+
import type { TransitionWhenKind } from "../types/segments.js";
|
|
39
46
|
|
|
40
47
|
function toScrollPayload(
|
|
41
48
|
scroll: boolean | undefined,
|
|
@@ -43,9 +50,16 @@ function toScrollPayload(
|
|
|
43
50
|
return { enabled: scroll !== false ? scroll : false };
|
|
44
51
|
}
|
|
45
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Hold/animate decision for a commit: a non-intercept segment carries a
|
|
55
|
+
* transition, no intercept is presented, and this navigation's
|
|
56
|
+
* transition({ when }) decision did not gate it off.
|
|
57
|
+
*/
|
|
46
58
|
export function shouldStartViewTransition(
|
|
47
|
-
segments: ResolvedSegment[],
|
|
59
|
+
segments: readonly ResolvedSegment[],
|
|
60
|
+
transitionGatedOff: boolean = false,
|
|
48
61
|
): boolean {
|
|
62
|
+
if (transitionGatedOff) return false;
|
|
49
63
|
let hasIntercept = false;
|
|
50
64
|
let hasTransition = false;
|
|
51
65
|
for (const s of segments) {
|
|
@@ -107,6 +121,8 @@ export interface CommitOverrides {
|
|
|
107
121
|
interceptSourceUrl?: string;
|
|
108
122
|
/** Server-set location state to merge into history.pushState */
|
|
109
123
|
serverState?: Record<string, unknown>;
|
|
124
|
+
/** The committed route's name (payload metadata), remembered per history entry. */
|
|
125
|
+
routeName?: string;
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
/**
|
|
@@ -132,10 +148,21 @@ export type UpdateMode =
|
|
|
132
148
|
* boundaries do not animate the identical repaint.
|
|
133
149
|
*/
|
|
134
150
|
optimisticPresented?: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* A router.refresh() (or cross-tab refresh) of the current URL:
|
|
153
|
+
* transition({ when }) sees kind "revalidate", not "replace".
|
|
154
|
+
*/
|
|
155
|
+
refresh?: boolean;
|
|
135
156
|
}
|
|
136
157
|
| { type: "leave-intercept"; interceptSourceUrl?: string }
|
|
137
158
|
| { type: "stale-revalidation"; interceptSourceUrl?: string }
|
|
138
|
-
| {
|
|
159
|
+
| {
|
|
160
|
+
type: "action";
|
|
161
|
+
interceptSourceUrl?: string;
|
|
162
|
+
actionId?: string;
|
|
163
|
+
/** The action's FormData and result, for transition({ when }) `action`. */
|
|
164
|
+
action?: TransitionWhenActionInput;
|
|
165
|
+
};
|
|
139
166
|
|
|
140
167
|
/**
|
|
141
168
|
* Type for the fetchPartialUpdate function
|
|
@@ -213,6 +240,54 @@ export function createPartialUpdater(
|
|
|
213
240
|
}
|
|
214
241
|
}
|
|
215
242
|
|
|
243
|
+
// transition({ when }). Every decision below runs before tx.commit()
|
|
244
|
+
// moves the store, so `from` is still the location being left:
|
|
245
|
+
// segmentState.currentUrl is the committed URL even on a popstate cache
|
|
246
|
+
// miss, where tx.currentUrl and window.location already point at the
|
|
247
|
+
// target.
|
|
248
|
+
const whenKind: TransitionWhenKind =
|
|
249
|
+
mode.type === "action"
|
|
250
|
+
? "action"
|
|
251
|
+
: mode.type === "stale-revalidation" ||
|
|
252
|
+
(mode.type === "navigate" && mode.refresh)
|
|
253
|
+
? "revalidate"
|
|
254
|
+
: tx.traversal
|
|
255
|
+
? "pop"
|
|
256
|
+
: tx.replace
|
|
257
|
+
? "replace"
|
|
258
|
+
: "push";
|
|
259
|
+
// Decide once per navigation (browser/transition-when.ts): reuse the
|
|
260
|
+
// decision the optimistic clientUrls() swap made; otherwise decide over
|
|
261
|
+
// every segment this commit presents, kept or re-sent (#989). The
|
|
262
|
+
// destination state is what the commit will leave on the entry.
|
|
263
|
+
const decideGatedOff = (
|
|
264
|
+
segmentsToCommit: readonly ResolvedSegment[],
|
|
265
|
+
metadata: {
|
|
266
|
+
params?: Record<string, string>;
|
|
267
|
+
routeName?: string;
|
|
268
|
+
locationState?: Record<string, unknown>;
|
|
269
|
+
},
|
|
270
|
+
): boolean =>
|
|
271
|
+
tx.transitionGatedOff ??
|
|
272
|
+
// A stale revalidation the user navigated away from is discarded after
|
|
273
|
+
// the render (history-key check below): its predicate must not run.
|
|
274
|
+
((mode.type === "stale-revalidation" &&
|
|
275
|
+
store.getHistoryKey() !== historyKeyAtStart) ||
|
|
276
|
+
decideCommitGatedOff(store, segmentsToCommit, whenKind, {
|
|
277
|
+
to: () => ({
|
|
278
|
+
url,
|
|
279
|
+
params: metadata.params ?? mergeSegmentParams(segmentsToCommit),
|
|
280
|
+
routeName: metadata.routeName,
|
|
281
|
+
state: tx.traversal
|
|
282
|
+
? { ...window.history.state, ...metadata.locationState }
|
|
283
|
+
: buildHistoryState(tx.state, undefined, metadata.locationState),
|
|
284
|
+
}),
|
|
285
|
+
action:
|
|
286
|
+
mode.type === "action"
|
|
287
|
+
? { ...mode.action, id: mode.actionId }
|
|
288
|
+
: undefined,
|
|
289
|
+
}));
|
|
290
|
+
|
|
216
291
|
const targetCache =
|
|
217
292
|
mode.type === "navigate" && mode.targetCacheSegments?.length
|
|
218
293
|
? mode.targetCacheSegments
|
|
@@ -349,13 +424,19 @@ export function createPartialUpdater(
|
|
|
349
424
|
"[Browser] No diff but navigating with cached segments - rendering target route",
|
|
350
425
|
);
|
|
351
426
|
|
|
427
|
+
const cachedGatedOff = decideGatedOff(
|
|
428
|
+
existingSegments,
|
|
429
|
+
payload.metadata,
|
|
430
|
+
);
|
|
352
431
|
const newTree = await renderSegments(existingSegments, {
|
|
353
432
|
forceAwait: true,
|
|
433
|
+
transitionGatedOff: cachedGatedOff,
|
|
354
434
|
});
|
|
355
435
|
|
|
356
436
|
const { scroll: commitScroll } = tx.commit(
|
|
357
437
|
matchedIds,
|
|
358
438
|
existingSegments,
|
|
439
|
+
{ routeName: payload.metadata.routeName },
|
|
359
440
|
);
|
|
360
441
|
|
|
361
442
|
if (mode.targetCacheHandleData) {
|
|
@@ -376,7 +457,7 @@ export function createPartialUpdater(
|
|
|
376
457
|
scroll: toScrollPayload(commitScroll),
|
|
377
458
|
};
|
|
378
459
|
|
|
379
|
-
if (shouldStartViewTransition(existingSegments)) {
|
|
460
|
+
if (shouldStartViewTransition(existingSegments, cachedGatedOff)) {
|
|
380
461
|
commitInTransition(onUpdate, existingSegments, cachedUpdate, [
|
|
381
462
|
"navigation",
|
|
382
463
|
]);
|
|
@@ -400,6 +481,7 @@ export function createPartialUpdater(
|
|
|
400
481
|
const { scroll: leaveScroll } = tx.commit(
|
|
401
482
|
matchedIds,
|
|
402
483
|
existingSegments,
|
|
484
|
+
{ routeName: payload.metadata.routeName },
|
|
403
485
|
);
|
|
404
486
|
|
|
405
487
|
onUpdate({
|
|
@@ -415,7 +497,9 @@ export function createPartialUpdater(
|
|
|
415
497
|
debugLog(
|
|
416
498
|
"[Browser] No changes - all revalidations returned false, keeping existing UI",
|
|
417
499
|
);
|
|
418
|
-
tx.commit(matchedIds, existingSegments
|
|
500
|
+
tx.commit(matchedIds, existingSegments, {
|
|
501
|
+
routeName: payload.metadata.routeName,
|
|
502
|
+
});
|
|
419
503
|
debugLog("[Browser] Navigation complete (no re-render)");
|
|
420
504
|
return;
|
|
421
505
|
}
|
|
@@ -477,7 +561,13 @@ export function createPartialUpdater(
|
|
|
477
561
|
return;
|
|
478
562
|
}
|
|
479
563
|
|
|
564
|
+
// Decide before the tree is built: on a gated-off commit every segment
|
|
565
|
+
// keeps its <ViewTransition> with "none" classes, so the decision must
|
|
566
|
+
// reach renderSegments. The response carries no decision, so a reused
|
|
567
|
+
// prefetch is decided against the real source.
|
|
568
|
+
const gatedOff = decideGatedOff(reconciled.segments, payload.metadata);
|
|
480
569
|
const renderOptions = {
|
|
570
|
+
transitionGatedOff: gatedOff,
|
|
481
571
|
isAction: mode.type === "action",
|
|
482
572
|
// forceAwait unwraps the ROUTER loader promises during render so they
|
|
483
573
|
// land without a loading()/fallback frame. A fully-prefetched nav has
|
|
@@ -557,16 +647,15 @@ export function createPartialUpdater(
|
|
|
557
647
|
|
|
558
648
|
const allSegmentIds = matchedIds;
|
|
559
649
|
const serverLocationState = payload.metadata?.locationState;
|
|
560
|
-
const overrides: CommitOverrides
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
: serverLocationState
|
|
568
|
-
|
|
569
|
-
: undefined;
|
|
650
|
+
const overrides: CommitOverrides = {
|
|
651
|
+
routeName: payload.metadata?.routeName,
|
|
652
|
+
...(isInterceptResponse && {
|
|
653
|
+
scroll: false,
|
|
654
|
+
intercept: true,
|
|
655
|
+
interceptSourceUrl: effectiveInterceptSource,
|
|
656
|
+
}),
|
|
657
|
+
...(serverLocationState && { serverState: serverLocationState }),
|
|
658
|
+
};
|
|
570
659
|
const { scroll: navScroll } = tx.commit(
|
|
571
660
|
allSegmentIds,
|
|
572
661
|
reconciled.segments,
|
|
@@ -585,7 +674,10 @@ export function createPartialUpdater(
|
|
|
585
674
|
|
|
586
675
|
debugLog("[partial-update] updating document");
|
|
587
676
|
|
|
588
|
-
const hasTransition = shouldStartViewTransition(
|
|
677
|
+
const hasTransition = shouldStartViewTransition(
|
|
678
|
+
reconciled.segments,
|
|
679
|
+
gatedOff,
|
|
680
|
+
);
|
|
589
681
|
const optimisticPresented =
|
|
590
682
|
mode.type === "navigate" && mode.optimisticPresented === true;
|
|
591
683
|
// [VT-DIAG] Gated behind INTERNAL_RANGO_DEBUG. Reports which reconciled
|
|
@@ -600,6 +692,7 @@ export function createPartialUpdater(
|
|
|
600
692
|
withTransition: reconciled.segments
|
|
601
693
|
.filter((s) => s.transition)
|
|
602
694
|
.map((s) => s.id),
|
|
695
|
+
transitionGatedOff: gatedOff,
|
|
603
696
|
all: reconciled.segments.map((s) => s.id),
|
|
604
697
|
});
|
|
605
698
|
}
|
|
@@ -617,7 +710,10 @@ export function createPartialUpdater(
|
|
|
617
710
|
metadata: payload.metadata!,
|
|
618
711
|
scroll: scrollPayload,
|
|
619
712
|
};
|
|
620
|
-
if (
|
|
713
|
+
if (
|
|
714
|
+
!gatedOff &&
|
|
715
|
+
(mode.type === "action" || mode.type === "stale-revalidation")
|
|
716
|
+
) {
|
|
621
717
|
commitInTransition(
|
|
622
718
|
onUpdate,
|
|
623
719
|
reconciled.mainSegments,
|
|
@@ -633,7 +729,10 @@ export function createPartialUpdater(
|
|
|
633
729
|
? ["navigation", OPTIMISTIC_COMMIT_TRANSITION_TYPE]
|
|
634
730
|
: ["navigation"],
|
|
635
731
|
);
|
|
636
|
-
} else if (
|
|
732
|
+
} else if (
|
|
733
|
+
!gatedOff &&
|
|
734
|
+
(fullyPrefetched || isSameStructureNav || optimisticPresented)
|
|
735
|
+
) {
|
|
637
736
|
// Content-hold commit, two triggers. Fully-prefetched nav: the payload
|
|
638
737
|
// is fully resolved (forceAwait above), so the transition commits
|
|
639
738
|
// synchronously — no fallback flash. Same-structure nav: the re-run
|
|
@@ -663,10 +762,14 @@ export function createPartialUpdater(
|
|
|
663
762
|
optimisticPresented ? [OPTIMISTIC_COMMIT_TRANSITION_TYPE] : [],
|
|
664
763
|
);
|
|
665
764
|
} else {
|
|
666
|
-
// Cold/partially-prefetched nav that mounts NEW segments
|
|
667
|
-
// commit
|
|
668
|
-
//
|
|
669
|
-
//
|
|
765
|
+
// Cold/partially-prefetched nav that mounts NEW segments, or any
|
|
766
|
+
// commit transition({ when }) gated off (navigation, action refetch,
|
|
767
|
+
// stale revalidation): normal commit so fallbacks stream
|
|
768
|
+
// like a first load and the click has visible feedback. A gated-off
|
|
769
|
+
// segment keeps its key, so its loading() fallback comes from its
|
|
770
|
+
// boundary re-suspending on this urgent commit, not from a remount
|
|
771
|
+
// (#995). Explicit transition() routes keep the content-hold via the
|
|
772
|
+
// hasTransition branch above (the opt-in).
|
|
670
773
|
onUpdate(update);
|
|
671
774
|
}
|
|
672
775
|
|
|
@@ -684,7 +787,10 @@ export function createPartialUpdater(
|
|
|
684
787
|
|
|
685
788
|
const segmentIds = segments.map((s: ResolvedSegment) => s.id);
|
|
686
789
|
|
|
687
|
-
const
|
|
790
|
+
const fullGatedOff = decideGatedOff(segments, payload.metadata ?? {});
|
|
791
|
+
const newTree = await renderSegments(segments, {
|
|
792
|
+
transitionGatedOff: fullGatedOff,
|
|
793
|
+
});
|
|
688
794
|
|
|
689
795
|
if (signal?.aborted) {
|
|
690
796
|
debugLog("[Browser] Ignoring stale navigation (aborted before commit)");
|
|
@@ -692,13 +798,15 @@ export function createPartialUpdater(
|
|
|
692
798
|
}
|
|
693
799
|
|
|
694
800
|
const fullUpdateServerState = payload.metadata?.locationState;
|
|
695
|
-
const { scroll: fullScroll } =
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
: tx.commit(segmentIds, segments);
|
|
801
|
+
const { scroll: fullScroll } = tx.commit(segmentIds, segments, {
|
|
802
|
+
routeName: payload.metadata?.routeName,
|
|
803
|
+
...(fullUpdateServerState && { serverState: fullUpdateServerState }),
|
|
804
|
+
});
|
|
700
805
|
|
|
701
|
-
const fullHasTransition = shouldStartViewTransition(
|
|
806
|
+
const fullHasTransition = shouldStartViewTransition(
|
|
807
|
+
segments,
|
|
808
|
+
fullGatedOff,
|
|
809
|
+
);
|
|
702
810
|
const fullScrollPayload = toScrollPayload(fullScroll);
|
|
703
811
|
const fullUpdate: NavigationUpdate = {
|
|
704
812
|
root: newTree,
|
|
@@ -720,13 +828,16 @@ export function createPartialUpdater(
|
|
|
720
828
|
);
|
|
721
829
|
return;
|
|
722
830
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
831
|
+
if (fullGatedOff) onUpdate(fullUpdate);
|
|
832
|
+
else {
|
|
833
|
+
commitInTransition(
|
|
834
|
+
onUpdate,
|
|
835
|
+
segments,
|
|
836
|
+
fullUpdate,
|
|
837
|
+
fullHasTransition ? ["action"] : [],
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
} else if (mode.type === "action" && !fullGatedOff) {
|
|
730
841
|
commitInTransition(
|
|
731
842
|
onUpdate,
|
|
732
843
|
segments,
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
* - Source-scoped: built by `buildSourceKey(rangoState, sourceHref, target)`
|
|
16
16
|
* — shape `rangoState\0sourceHref\0/target?...`. Embeds the Rango state
|
|
17
17
|
* (so rotation invalidates source-scoped entries too), the source href, and
|
|
18
|
-
* the exact source segment tree. Populated when the
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* both cases so source-sensitive responses
|
|
22
|
-
* from other pages.
|
|
18
|
+
* the exact source segment tree. Populated when the server tags a
|
|
19
|
+
* response with `X-RSC-Prefetch-Scope: source` (a route an intercept
|
|
20
|
+
* targets, matched or not), OR when a Link opts in with
|
|
21
|
+
* `prefetchKey=":source"` — in both cases so source-sensitive responses
|
|
22
|
+
* cannot bleed into navigations from other pages.
|
|
23
23
|
*
|
|
24
24
|
* Also tracks in-flight prefetch promises. Each promise resolves to the
|
|
25
25
|
* decoded prefetch entry (or null), letting navigation adopt a
|
|
@@ -172,7 +172,7 @@ let generation = 0;
|
|
|
172
172
|
* rotation invalidates source-scoped entries alongside wildcard ones,
|
|
173
173
|
* plus the source page href so the key is unique per originating page.
|
|
174
174
|
* Populated either when the server tags a response with
|
|
175
|
-
* `X-RSC-Prefetch-Scope: source` (intercept
|
|
175
|
+
* `X-RSC-Prefetch-Scope: source` (a route an intercept targets) or when a
|
|
176
176
|
* Link opts in via `prefetchKey=":source"`.
|
|
177
177
|
*
|
|
178
178
|
* Source-scoped keys retain `_rsc_segments` because their exact source tree is
|
|
@@ -189,8 +189,8 @@ function buildPrefetchUrl(
|
|
|
189
189
|
* - Otherwise: dual inflight registration under both `wildcardKey` and
|
|
190
190
|
* `sourceKey` so same-source navigations adopt directly via their own
|
|
191
191
|
* source key. Storage key is chosen at response time from the
|
|
192
|
-
* `X-RSC-Prefetch-Scope` header — `"source"` → `sourceKey` (
|
|
193
|
-
*
|
|
192
|
+
* `X-RSC-Prefetch-Scope` header — `"source"` → `sourceKey` (a route an
|
|
193
|
+
* intercept targets), anything else → `wildcardKey`. The entry records its scope
|
|
194
194
|
* so cross-source navigations that adopted via `wildcardKey` can bail out
|
|
195
195
|
* in `navigation-client.ts` when the adopted entry turns out source-scoped.
|
|
196
196
|
*/
|
|
@@ -13,30 +13,23 @@ import React, {
|
|
|
13
13
|
} from "react";
|
|
14
14
|
import { NavigationStoreContext } from "./context.js";
|
|
15
15
|
import { LinkContext } from "./use-link-status.js";
|
|
16
|
-
import type {
|
|
16
|
+
import type { HistoryState } from "../types.js";
|
|
17
17
|
import {
|
|
18
18
|
isHashOnlyNavigation,
|
|
19
19
|
isPrefetchScopeDisabled,
|
|
20
20
|
subscribeToPrefetchScopeChange,
|
|
21
21
|
} from "../link-interceptor.js";
|
|
22
22
|
import { subscribeToLocationChange } from "../event-controller.js";
|
|
23
|
-
import {
|
|
24
|
-
isLocationStateEntry,
|
|
25
|
-
type LocationStateEntry,
|
|
26
|
-
resolveLocationStateEntries,
|
|
27
|
-
} from "./location-state.js";
|
|
23
|
+
import { isLocationStateDefinition } from "./location-state-shared.js";
|
|
28
24
|
|
|
29
|
-
/**
|
|
30
|
-
* State prop type for Link component.
|
|
31
|
-
* - LocationStateEntry[]: Type-safe state entries via createLocationState()
|
|
32
|
-
* - StateOrGetter: Plain state object or click-time getter function
|
|
33
|
-
* - Record<string, unknown>: Plain state object passed to history.pushState
|
|
34
|
-
*/
|
|
35
25
|
export type StateOrGetter<T = unknown> = T | (() => T);
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
/**
|
|
28
|
+
* State prop type for Link component: the `router.push()` state type
|
|
29
|
+
* ({@link HistoryState}: `LocationStateEntry[]` from createLocationState(), or
|
|
30
|
+
* plain structured-clone-safe state), or a getter for it called at click time.
|
|
31
|
+
*/
|
|
32
|
+
export type LinkState = StateOrGetter<HistoryState>;
|
|
40
33
|
|
|
41
34
|
import {
|
|
42
35
|
prefetchDirect,
|
|
@@ -93,6 +86,14 @@ export interface LinkProps extends Omit<
|
|
|
93
86
|
* @default true
|
|
94
87
|
*/
|
|
95
88
|
revalidate?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Set to `false` to present this navigation without a transition (urgent
|
|
91
|
+
* commit, `<ViewTransition>` classes `"none"`); no `transition({ when })`
|
|
92
|
+
* predicate is called. See `NavigateOptions.transition`.
|
|
93
|
+
*
|
|
94
|
+
* @default true
|
|
95
|
+
*/
|
|
96
|
+
transition?: boolean;
|
|
96
97
|
/**
|
|
97
98
|
* Prefetch strategy for the link destination. When omitted, falls back to
|
|
98
99
|
* the router-wide default (`createRouter({ defaultPrefetch })`: `"none"` in
|
|
@@ -118,8 +119,11 @@ export interface LinkProps extends Omit<
|
|
|
118
119
|
* therefore serve the wrong diff to a navigation from a different
|
|
119
120
|
* source.
|
|
120
121
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
122
|
+
* A route an intercept targets is scoped to the source automatically
|
|
123
|
+
* (server-side `x-rsc-prefetch-scope: source`), whether or not the
|
|
124
|
+
* intercept applies from this page: the modal and the full page are
|
|
125
|
+
* both source-specific. Every other route stays shared, so `":source"`
|
|
126
|
+
* is only needed for custom revalidation logic.
|
|
123
127
|
*
|
|
124
128
|
* @example
|
|
125
129
|
* ```tsx
|
|
@@ -226,6 +230,7 @@ export const Link: ForwardRefExoticComponent<
|
|
|
226
230
|
scroll = true,
|
|
227
231
|
reloadDocument = false,
|
|
228
232
|
revalidate,
|
|
233
|
+
transition,
|
|
229
234
|
prefetch,
|
|
230
235
|
prefetchKey,
|
|
231
236
|
state,
|
|
@@ -334,28 +339,26 @@ export const Link: ForwardRefExoticComponent<
|
|
|
334
339
|
// Stop propagation to prevent link-interceptor from also handling this
|
|
335
340
|
e.stopPropagation();
|
|
336
341
|
|
|
342
|
+
// Call a click-time getter; typed entries are resolved (and, in dev,
|
|
343
|
+
// checked) by the bridge's resolveNavigationState, like router.push().
|
|
344
|
+
// In dev an uncalled definition passes through to that check instead of
|
|
345
|
+
// being called as a getter.
|
|
337
346
|
const currentState = stateRef.current;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
currentState as LocationStateEntry[],
|
|
347
|
-
);
|
|
348
|
-
} else if (typeof currentState === "function") {
|
|
349
|
-
resolvedState = currentState();
|
|
350
|
-
} else if (currentState != null) {
|
|
351
|
-
resolvedState = currentState;
|
|
352
|
-
}
|
|
347
|
+
const state =
|
|
348
|
+
typeof currentState === "function" &&
|
|
349
|
+
!(
|
|
350
|
+
process.env.NODE_ENV !== "production" &&
|
|
351
|
+
isLocationStateDefinition(currentState)
|
|
352
|
+
)
|
|
353
|
+
? currentState()
|
|
354
|
+
: (currentState ?? undefined);
|
|
353
355
|
|
|
354
356
|
ctx.navigate(resolvedTo, {
|
|
355
357
|
replace,
|
|
356
358
|
scroll,
|
|
357
|
-
state
|
|
359
|
+
state,
|
|
358
360
|
revalidate,
|
|
361
|
+
transition,
|
|
359
362
|
});
|
|
360
363
|
},
|
|
361
364
|
[
|
|
@@ -365,6 +368,7 @@ export const Link: ForwardRefExoticComponent<
|
|
|
365
368
|
replace,
|
|
366
369
|
scroll,
|
|
367
370
|
revalidate,
|
|
371
|
+
transition,
|
|
368
372
|
ctx,
|
|
369
373
|
onClick,
|
|
370
374
|
],
|
|
@@ -487,6 +491,7 @@ export const Link: ForwardRefExoticComponent<
|
|
|
487
491
|
data-scroll={scroll === false ? "false" : undefined}
|
|
488
492
|
data-replace={replace ? "true" : undefined}
|
|
489
493
|
data-revalidate={revalidate === false ? "false" : undefined}
|
|
494
|
+
data-transition={transition === false ? "false" : undefined}
|
|
490
495
|
{...props}
|
|
491
496
|
>
|
|
492
497
|
<LinkContext.Provider value={resolvedTo}>{children}</LinkContext.Provider>
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import type {
|
|
18
18
|
NavigationStore,
|
|
19
19
|
NavigationUpdate,
|
|
20
|
-
|
|
20
|
+
NavigateOptionsInternal,
|
|
21
21
|
NavigationBridge,
|
|
22
22
|
} from "../types.js";
|
|
23
23
|
import type { EventController } from "../event-controller.js";
|
|
@@ -320,7 +320,7 @@ export function NavigationProvider({
|
|
|
320
320
|
* Navigate to a URL (delegates to bridge)
|
|
321
321
|
*/
|
|
322
322
|
const navigate = useCallback(
|
|
323
|
-
async (url: string, options?:
|
|
323
|
+
async (url: string, options?: NavigateOptionsInternal): Promise<void> => {
|
|
324
324
|
await bridge.navigate(url, options);
|
|
325
325
|
},
|
|
326
326
|
[],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { createContext, type Context } from "react";
|
|
4
|
-
import type { NavigationStore,
|
|
4
|
+
import type { NavigationStore, NavigateOptionsInternal } from "../types.js";
|
|
5
5
|
import type { EventController } from "../event-controller.js";
|
|
6
6
|
import type { PrefetchStrategy } from "../../router/prefetch-default.js";
|
|
7
7
|
|
|
@@ -34,7 +34,7 @@ export interface NavigationStoreContextValue {
|
|
|
34
34
|
* @param options - Navigation options (replace, scroll)
|
|
35
35
|
* @returns Promise that resolves when navigation is complete
|
|
36
36
|
*/
|
|
37
|
-
navigate: (url: string, options?:
|
|
37
|
+
navigate: (url: string, options?: NavigateOptionsInternal) => Promise<void>;
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Refresh the current route
|