@rangojs/router 0.33.2 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/{chunk-YP4HENIU.js → chunk-4HHFQSDC.js} +6 -1
- package/dist/bin/{chunk-3B7CKBRO.js → chunk-MWOHBDPM.js} +9 -9
- package/dist/bin/{client-urls-projection-J2BGDKQ4.js → client-urls-projection-4Q5EQDAL.js} +2 -2
- package/dist/bin/rango.js +1 -1
- package/dist/bin/{runtime-EPUDEHO4.js → runtime-6WLCJCN7.js} +1 -1
- package/dist/bin/{runtime-discovery-O6KQ5KO2.js → runtime-discovery-RY6NMQM3.js} +2 -2
- package/dist/bin/{temp-rsc-server-BOHXBOR7.js → temp-rsc-server-F7AHOPV4.js} +3 -3
- package/dist/types/client-urls/server-projection.d.ts +7 -0
- package/dist/types/client-urls/types.d.ts +13 -14
- package/dist/types/errors.d.ts +5 -0
- package/dist/types/loader-store.d.ts +19 -7
- package/dist/types/prefetch-markers.d.ts +13 -4
- package/dist/types/route-definition/helpers-types.d.ts +14 -10
- package/dist/types/router/match-context.d.ts +12 -0
- package/dist/types/router/navigation-snapshot.d.ts +17 -0
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +39 -21
- package/dist/types/server/context.d.ts +6 -5
- package/dist/types/testing/serve-shell-request.d.ts +7 -7
- package/dist/types/types/loader-types.d.ts +10 -8
- package/dist/types/urls/path-helper-types.d.ts +9 -8
- package/dist/vite/index.js +26 -12
- package/package.json +1 -1
- package/skills/app-structure/large-app.md +3 -0
- package/skills/client-urls/SKILL.md +13 -10
- package/skills/hooks/data.md +4 -1
- package/skills/intercept/SKILL.md +8 -4
- package/skills/links/SKILL.md +15 -12
- package/skills/loader/SKILL.md +52 -39
- package/skills/ppr/SKILL.md +1 -1
- package/skills/route/SKILL.md +15 -9
- package/skills/testing/serve-shell-request.md +19 -7
- package/skills/ux-architecture/SKILL.md +16 -11
- package/src/browser/navigation-bridge.ts +6 -0
- package/src/browser/partial-update.ts +12 -4
- package/src/browser/prefetch/fetch.ts +3 -3
- package/src/client-urls/client-urls.ts +11 -7
- package/src/client-urls/server-projection.ts +21 -3
- package/src/client-urls/types.ts +13 -14
- package/src/errors.ts +17 -0
- package/src/loader-store.ts +28 -12
- package/src/prefetch-markers.ts +18 -4
- package/src/route-definition/dsl-helpers.ts +13 -6
- package/src/route-definition/helpers-types.ts +14 -10
- package/src/router/loader-resolution.ts +4 -4
- package/src/router/match-api.ts +3 -3
- package/src/router/match-context.ts +22 -1
- package/src/router/match-middleware/cache-lookup.ts +17 -8
- package/src/router/match-middleware/intercept-resolution.ts +3 -3
- package/src/router/navigation-snapshot.ts +39 -8
- package/src/router/segment-resolution/prefetch-deferral.ts +108 -50
- package/src/rsc/rsc-rendering.ts +1 -1
- package/src/server/context.ts +6 -5
- package/src/testing/serve-shell-request.ts +7 -7
- package/src/types/loader-types.ts +10 -8
- package/src/urls/path-helper-types.ts +9 -8
- package/src/use-loader.tsx +5 -3
|
@@ -25,7 +25,11 @@ import {
|
|
|
25
25
|
type InterceptConfig,
|
|
26
26
|
type LoaderEntry,
|
|
27
27
|
} from "../server/context";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
assertBooleanOption,
|
|
30
|
+
assertPrefetchOption,
|
|
31
|
+
invariant,
|
|
32
|
+
} from "../errors";
|
|
29
33
|
import { validateUserRouteName } from "../route-name.js";
|
|
30
34
|
import { isCachedFunction } from "../cache/taint.js";
|
|
31
35
|
import { RangoContext } from "../server/context";
|
|
@@ -48,6 +52,7 @@ import type {
|
|
|
48
52
|
UseItems,
|
|
49
53
|
} from "../route-types.js";
|
|
50
54
|
import { isRenderableLoading } from "../types/boundaries.js";
|
|
55
|
+
import { isPrefetchFlag } from "../prefetch-markers.js";
|
|
51
56
|
import type { RouteHelpers } from "./helpers-types.js";
|
|
52
57
|
import {
|
|
53
58
|
resolveHandlerUse,
|
|
@@ -922,7 +927,7 @@ const loader: RouteHelpers<any, any>["loader"] = (
|
|
|
922
927
|
"loader() ssr",
|
|
923
928
|
"stream on every render",
|
|
924
929
|
);
|
|
925
|
-
|
|
930
|
+
assertPrefetchOption(
|
|
926
931
|
optionsGiven?.prefetch,
|
|
927
932
|
"loader() prefetch",
|
|
928
933
|
"run the loader in prefetches",
|
|
@@ -938,7 +943,9 @@ const loader: RouteHelpers<any, any>["loader"] = (
|
|
|
938
943
|
loader: loaderDef,
|
|
939
944
|
revalidate: [] as ShouldRevalidateFn<any, any>[],
|
|
940
945
|
...(optionsGiven?.ssr === false && { bake: true as const }),
|
|
941
|
-
...(optionsGiven?.prefetch
|
|
946
|
+
...(isPrefetchFlag(optionsGiven?.prefetch) && {
|
|
947
|
+
prefetch: optionsGiven.prefetch,
|
|
948
|
+
}),
|
|
942
949
|
// The declaring layout's / parallel's / cache()'s scope. A route's (and
|
|
943
950
|
// an intercept's) loaders have none: they resolve from the route's name,
|
|
944
951
|
// like the handler.
|
|
@@ -997,7 +1004,7 @@ const loading: RouteHelpers<any, any>["loading"] = (component, options) => {
|
|
|
997
1004
|
invariant(false, "No parent entry available for loading()");
|
|
998
1005
|
}
|
|
999
1006
|
|
|
1000
|
-
|
|
1007
|
+
assertPrefetchOption(
|
|
1001
1008
|
options?.prefetch,
|
|
1002
1009
|
"loading() prefetch",
|
|
1003
1010
|
"render what the fallback covers in prefetches",
|
|
@@ -1018,8 +1025,8 @@ const loading: RouteHelpers<any, any>["loading"] = (component, options) => {
|
|
|
1018
1025
|
// `parent.loading`, so the document evaluation (ssr: false) agrees with
|
|
1019
1026
|
// the navigation one. Last loading() wins, like `loading` itself.
|
|
1020
1027
|
parent.loadingPrefetch =
|
|
1021
|
-
options?.prefetch
|
|
1022
|
-
?
|
|
1028
|
+
isPrefetchFlag(options?.prefetch) && isRenderableLoading(resolved)
|
|
1029
|
+
? options.prefetch
|
|
1023
1030
|
: undefined;
|
|
1024
1031
|
|
|
1025
1032
|
const name = `$${store.getNextIndex("loading")}`;
|
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
UseItems,
|
|
36
36
|
} from "../route-types.js";
|
|
37
37
|
import type { StaticHandlerRef } from "../static-handler.js";
|
|
38
|
+
import type { PrefetchOption } from "../prefetch-markers.js";
|
|
38
39
|
|
|
39
40
|
// Re-export route item types for backward compatibility
|
|
40
41
|
export type {
|
|
@@ -333,15 +334,16 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
333
334
|
* loader(RecommendationsLoader), // still streams behind loading()
|
|
334
335
|
* ```
|
|
335
336
|
*
|
|
336
|
-
* Pass `{ prefetch: false }` to keep the loader out of
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
337
|
+
* Pass `{ prefetch: false }` to keep the loader out of prefetches: a
|
|
338
|
+
* prefetch skips it and the click that adopts it fetches it in one
|
|
339
|
+
* follow-up request. `{ prefetch: "intent" }` defers it in viewport and
|
|
340
|
+
* render prefetches only (see {@link LoaderOptions}). Only on a segment the
|
|
340
341
|
* client does not have yet: a loader the client already holds runs in the
|
|
341
342
|
* prefetch when it revalidates.
|
|
342
343
|
*
|
|
343
344
|
* ```typescript
|
|
344
345
|
* loader(ReviewsLoader, { prefetch: false }),
|
|
346
|
+
* loader(RelatedLoader, { prefetch: "intent" }),
|
|
345
347
|
* ```
|
|
346
348
|
*
|
|
347
349
|
* @param loaderDef - Loader created with createLoader()
|
|
@@ -362,16 +364,18 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
362
364
|
* // Skip loading on SSR, only show on client navigation
|
|
363
365
|
* loading(<Skeleton />, { ssr: false })
|
|
364
366
|
*
|
|
365
|
-
* // Keep everything behind this fallback out of
|
|
366
|
-
* // prefetches (a hover prefetch runs it)
|
|
367
|
+
* // Keep everything behind this fallback out of prefetches
|
|
367
368
|
* loading(<Skeleton />, { prefetch: false })
|
|
369
|
+
*
|
|
370
|
+
* // Deferred in viewport and render prefetches only
|
|
371
|
+
* loading(<Skeleton />, { prefetch: "intent" })
|
|
368
372
|
* ```
|
|
369
373
|
* @param component - The loading UI to show during navigation
|
|
370
374
|
* @param options - Configuration options
|
|
371
375
|
* @param options.ssr - If false, skip showing loading on document requests (SSR)
|
|
372
|
-
* @param options.prefetch -
|
|
373
|
-
*
|
|
374
|
-
*
|
|
376
|
+
* @param options.prefetch - `false` or `"intent"` (see
|
|
377
|
+
* {@link LoaderOptions}); the flag applies when this entry's segment is
|
|
378
|
+
* new to the client. A prefetch then skips the whole unit: the
|
|
375
379
|
* handler, its loaders, its orphan layouts and parallel slots and, for a
|
|
376
380
|
* layout, every deeper entry; the click that adopts the prefetch shows
|
|
377
381
|
* this fallback and fetches them in one follow-up request, which reads
|
|
@@ -386,7 +390,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
386
390
|
*/
|
|
387
391
|
loading: (
|
|
388
392
|
component: ReactNode | (() => ReactNode),
|
|
389
|
-
options?: { ssr?: boolean; prefetch?:
|
|
393
|
+
options?: { ssr?: boolean; prefetch?: PrefetchOption },
|
|
390
394
|
) => LoadingItem;
|
|
391
395
|
/**
|
|
392
396
|
* Attach an error boundary to catch errors in this segment and children
|
|
@@ -731,17 +731,17 @@ function createLoaderExecutor<TEnv>(
|
|
|
731
731
|
);
|
|
732
732
|
}
|
|
733
733
|
|
|
734
|
-
// prefetch: false (segment-resolution/prefetch-deferral.ts).
|
|
735
|
-
// deferrable loader runs in a fill request, where its entry is held
|
|
734
|
+
// prefetch: false or "intent" (segment-resolution/prefetch-deferral.ts).
|
|
735
|
+
// A deferrable loader runs in a fill request, where its entry is held
|
|
736
736
|
// and its handler does not run: there is no render to wait for.
|
|
737
737
|
// Refused on every request kind, so it is seen on a document load.
|
|
738
738
|
const deferral = internal._prefetchDeferral;
|
|
739
739
|
if (deferral?.scope.loaderIds.has(currentLoaderId)) {
|
|
740
740
|
throw new Error(
|
|
741
741
|
`ctx.rendered() is not available to loader "${currentLoaderId}": it is deferred in prefetches ` +
|
|
742
|
-
`(registered with loader(Def, { prefetch: false }), or behind a loading(fallback, { prefetch: false })), ` +
|
|
742
|
+
`(registered with loader(Def, { prefetch: false }) or { prefetch: "intent" }, or behind a loading(fallback, { prefetch: false }) or { prefetch: "intent" }), ` +
|
|
743
743
|
`and the fill request that runs it afterwards does not re-run its entry's handler, so there is no render to wait for and no handle data to read. ` +
|
|
744
|
-
`Remove { prefetch: false } from the loader (or from the loading() above it), or stop calling ctx.rendered() in this loader.`,
|
|
744
|
+
`Remove { prefetch: false } or { prefetch: "intent" } from the loader (or from the loading() above it), or stop calling ctx.rendered() in this loader.`,
|
|
745
745
|
);
|
|
746
746
|
}
|
|
747
747
|
if (deferral?.mode === "fill") {
|
package/src/router/match-api.ts
CHANGED
|
@@ -203,6 +203,7 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
203
203
|
prevUrl: cleanUrl,
|
|
204
204
|
prevParams: {},
|
|
205
205
|
prevMatch: null,
|
|
206
|
+
interceptPrev: { url: cleanUrl, params: {} },
|
|
206
207
|
matched,
|
|
207
208
|
manifestEntry: snapshot.manifestEntry,
|
|
208
209
|
entries: snapshot.entries,
|
|
@@ -491,9 +492,7 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
491
492
|
const deferral = planPrefetchDeferral(
|
|
492
493
|
snapshot.entries,
|
|
493
494
|
partialDeferralMode({
|
|
494
|
-
|
|
495
|
-
// Speculative only: an intent prefetch defers nothing.
|
|
496
|
-
prefetch: kind === "prefetch",
|
|
495
|
+
kind,
|
|
497
496
|
method: request.method,
|
|
498
497
|
isAction,
|
|
499
498
|
isShellCapture: reqCtx?._shellCaptureState?.run === true,
|
|
@@ -522,6 +521,7 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
522
521
|
prevUrl: nav.prevUrl,
|
|
523
522
|
prevParams: nav.prevParams,
|
|
524
523
|
prevMatch: nav.prevMatch,
|
|
524
|
+
interceptPrev: nav.interceptPrev,
|
|
525
525
|
matched,
|
|
526
526
|
manifestEntry: snapshot.manifestEntry,
|
|
527
527
|
entries: snapshot.entries,
|
|
@@ -116,6 +116,7 @@ import type {
|
|
|
116
116
|
} from "../server/context.js";
|
|
117
117
|
import type { HandlerContext, ResolvedSegment } from "../types.js";
|
|
118
118
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
119
|
+
import type { PrevPage } from "./navigation-snapshot.js";
|
|
119
120
|
import type { InterceptResult } from "./router-context.js";
|
|
120
121
|
import type { ResolutionPassInput } from "./segment-resolution/resolution-pass.js";
|
|
121
122
|
import type { ActionContext } from "./types.js";
|
|
@@ -139,10 +140,13 @@ export interface MatchContext<TEnv = any> {
|
|
|
139
140
|
/** NavigationSnapshot.heldBoundary: the walk stops above it. */
|
|
140
141
|
heldBoundary?: string;
|
|
141
142
|
|
|
142
|
-
// Previous navigation state
|
|
143
|
+
// Previous navigation state: see NavigationSnapshot.
|
|
143
144
|
prevUrl: URL;
|
|
144
145
|
prevParams: Record<string, string>;
|
|
146
|
+
/** The match of the page left: the intercept's URL while one is open. */
|
|
145
147
|
prevMatch: RouteMatchResult | null;
|
|
148
|
+
/** The page an open intercept's slot was rendered for. */
|
|
149
|
+
interceptPrev: PrevPage;
|
|
146
150
|
|
|
147
151
|
// Current route match
|
|
148
152
|
matched: RouteMatchResult;
|
|
@@ -225,6 +229,23 @@ export function resolutionPassInput<TEnv>(
|
|
|
225
229
|
};
|
|
226
230
|
}
|
|
227
231
|
|
|
232
|
+
/**
|
|
233
|
+
* This match as the input of the pass that resolves its intercept's slot: its
|
|
234
|
+
* held segments compare with the intercept's own URL (`interceptPrev`), not
|
|
235
|
+
* with the source page under it. From /item/2 over /list/1, a link to /item/1
|
|
236
|
+
* compared with the source's params would read as param-equal and keep item
|
|
237
|
+
* 2's slot.
|
|
238
|
+
*/
|
|
239
|
+
export function interceptPassInput<TEnv>(
|
|
240
|
+
ctx: MatchContext<TEnv>,
|
|
241
|
+
): ResolutionPassInput<TEnv> {
|
|
242
|
+
return {
|
|
243
|
+
...resolutionPassInput(ctx),
|
|
244
|
+
prevUrl: ctx.interceptPrev.url,
|
|
245
|
+
prevParams: ctx.interceptPrev.params,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
228
249
|
/**
|
|
229
250
|
* Mutable state that flows through the pipeline
|
|
230
251
|
*/
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
type MatchPipelineState,
|
|
50
50
|
} from "../match-context.js";
|
|
51
51
|
import { getRouterContext, type RouterContext } from "../router-context.js";
|
|
52
|
+
import type { PrevPage } from "../navigation-snapshot.js";
|
|
52
53
|
import { freshPassInput } from "../segment-resolution/resolution-pass.js";
|
|
53
54
|
import { observeEvent } from "../instrument.js";
|
|
54
55
|
import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
|
|
@@ -336,7 +337,12 @@ function storedEntrySends<TEnv>(
|
|
|
336
337
|
router: RouterContext<TEnv>,
|
|
337
338
|
): (segment: ResolvedSegment) => boolean | Promise<boolean> {
|
|
338
339
|
if (ctx.isFullMatch) return () => true;
|
|
339
|
-
|
|
340
|
+
// An intercept variant's slot compares with the intercept's own page, the
|
|
341
|
+
// page under it with the source (MatchContext.interceptPrev).
|
|
342
|
+
const pagePrev: PrevPage = { url: ctx.prevUrl, params: ctx.prevParams };
|
|
343
|
+
const slotPrev = ctx.interceptPrev;
|
|
344
|
+
const pageParamsChanged = !paramsEqual(ctx.matched.params, pagePrev.params);
|
|
345
|
+
const slotParamsChanged = !paramsEqual(ctx.matched.params, slotPrev.params);
|
|
340
346
|
const resendRouteSegments = replaceable && (ctx.isAction || ctx.stale);
|
|
341
347
|
const entryRevalidateMap =
|
|
342
348
|
ctx.clientSegmentSet.size > 0
|
|
@@ -345,11 +351,11 @@ function storedEntrySends<TEnv>(
|
|
|
345
351
|
// A keep that hangs on the source's params is right for the pages with
|
|
346
352
|
// the same params only: scope a prefetch of the answer to them, as a
|
|
347
353
|
// predicate that compared every param would (#1331).
|
|
348
|
-
const heldForSource = (): false => {
|
|
354
|
+
const heldForSource = (prevParams: Record<string, string>): false => {
|
|
349
355
|
markSourceHeld(
|
|
350
356
|
ctx.handlerContext as InternalHandlerContext<any, TEnv>,
|
|
351
|
-
readsOfAllParams(
|
|
352
|
-
{ params:
|
|
357
|
+
readsOfAllParams(prevParams),
|
|
358
|
+
{ params: prevParams, routeName: undefined },
|
|
353
359
|
);
|
|
354
360
|
return false;
|
|
355
361
|
};
|
|
@@ -362,23 +368,26 @@ function storedEntrySends<TEnv>(
|
|
|
362
368
|
ctx.clientSegmentSet,
|
|
363
369
|
);
|
|
364
370
|
if (fill !== undefined) return fill;
|
|
371
|
+
const ofSlot = isInterceptSegment(segment);
|
|
372
|
+
const prev = ofSlot ? slotPrev : pagePrev;
|
|
373
|
+
const paramsChanged = ofSlot ? slotParamsChanged : pageParamsChanged;
|
|
365
374
|
if (segment.type === "route" || segment.belongsToRoute === true) {
|
|
366
375
|
if (paramsChanged || resendRouteSegments) return true;
|
|
367
376
|
// Kept on equal params, whatever the search: a source with other
|
|
368
377
|
// params is sent it.
|
|
369
|
-
return heldForSource();
|
|
378
|
+
return heldForSource(prev.params);
|
|
370
379
|
}
|
|
371
380
|
const revalidate = entryRevalidateMap?.get(segment.id)?.revalidate;
|
|
372
381
|
if (!revalidate?.length) return false;
|
|
373
382
|
// Kept on equal params: from another source the rules run and may send
|
|
374
383
|
// it.
|
|
375
|
-
if (!paramsChanged) return heldForSource();
|
|
384
|
+
if (!paramsChanged) return heldForSource(prev.params);
|
|
376
385
|
return router.evaluateRevalidation({
|
|
377
386
|
segment,
|
|
378
|
-
prevParams:
|
|
387
|
+
prevParams: prev.params,
|
|
379
388
|
getPrevSegment: null,
|
|
380
389
|
request: ctx.request,
|
|
381
|
-
prevUrl:
|
|
390
|
+
prevUrl: prev.url,
|
|
382
391
|
nextUrl: ctx.url,
|
|
383
392
|
revalidations: revalidate.map((fn, i) => ({
|
|
384
393
|
name: `revalidate${i}`,
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
*/
|
|
104
104
|
import type { ResolvedSegment } from "../../types.js";
|
|
105
105
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
106
|
-
import {
|
|
106
|
+
import { interceptPassInput } from "../match-context.js";
|
|
107
107
|
import { getRouterContext } from "../router-context.js";
|
|
108
108
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
109
109
|
import { debugLog } from "../logging.js";
|
|
@@ -176,7 +176,7 @@ export function withInterceptResolution<TEnv>(
|
|
|
176
176
|
const Store = ctx.Store;
|
|
177
177
|
const interceptSegments = await Store.run(() =>
|
|
178
178
|
resolveInterceptEntry(
|
|
179
|
-
|
|
179
|
+
interceptPassInput(ctx),
|
|
180
180
|
ctx.interceptResult!.intercept,
|
|
181
181
|
ctx.interceptResult!.entry,
|
|
182
182
|
true, // belongsToRoute
|
|
@@ -232,7 +232,7 @@ async function handleCacheHitIntercept<TEnv>(
|
|
|
232
232
|
const loaderSegments = resolveInterceptLoadersOnly
|
|
233
233
|
? await ctx.Store.run(() =>
|
|
234
234
|
resolveInterceptLoadersOnly(
|
|
235
|
-
|
|
235
|
+
interceptPassInput(ctx),
|
|
236
236
|
ctx.interceptResult!.intercept,
|
|
237
237
|
ctx.interceptResult!.entry,
|
|
238
238
|
true, // belongsToRoute
|
|
@@ -3,12 +3,30 @@ import { requestHeaders } from "../server/request-headers.js";
|
|
|
3
3
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
4
4
|
import { WIRE } from "../wire-params.js";
|
|
5
5
|
|
|
6
|
+
/** A page held segments were rendered for: what their revalidation compares with. */
|
|
7
|
+
export interface PrevPage {
|
|
8
|
+
url: URL;
|
|
9
|
+
params: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
export interface NavigationSnapshot {
|
|
13
|
+
/**
|
|
14
|
+
* `prevUrl` and `prevParams`: the page the held page segments were rendered
|
|
15
|
+
* for. That is the page left, or the source page while an intercept is open
|
|
16
|
+
* (`hasInterceptSource`).
|
|
17
|
+
*/
|
|
7
18
|
prevUrl: URL;
|
|
8
19
|
/** The visitor's URL of the page left, when `prevUrl` is its rewrite target. */
|
|
9
20
|
prevVisitorUrl: URL | null;
|
|
10
21
|
prevParams: Record<string, string>;
|
|
22
|
+
/** The match of the page left: the intercept's URL while one is open. */
|
|
11
23
|
prevMatch: RouteMatchResult | null;
|
|
24
|
+
/**
|
|
25
|
+
* The page an open intercept's slot was rendered for: the intercept's own
|
|
26
|
+
* URL (match-context.ts interceptPassInput). Equals `prevUrl`/`prevParams`
|
|
27
|
+
* when the request names no intercept source.
|
|
28
|
+
*/
|
|
29
|
+
interceptPrev: PrevPage;
|
|
12
30
|
|
|
13
31
|
interceptContextUrl: URL;
|
|
14
32
|
interceptContextMatch: RouteMatchResult | null;
|
|
@@ -176,29 +194,40 @@ export async function resolveNavigation(
|
|
|
176
194
|
}
|
|
177
195
|
// An intercept's request diffs from its source, which _rsc_rt does not name.
|
|
178
196
|
const shown = interceptSourceUrl ? null : getShownRouteUrl(url);
|
|
179
|
-
const
|
|
197
|
+
const left = shown ?? visitor;
|
|
180
198
|
|
|
181
199
|
let interceptContextUrl: URL;
|
|
182
200
|
try {
|
|
183
201
|
interceptContextUrl = interceptSourceUrl
|
|
184
202
|
? withoutHash(new URL(interceptSourceUrl, url.origin))
|
|
185
|
-
:
|
|
203
|
+
: left;
|
|
186
204
|
} catch {
|
|
187
|
-
interceptContextUrl =
|
|
205
|
+
interceptContextUrl = left;
|
|
188
206
|
}
|
|
189
207
|
|
|
190
|
-
const prevMatch = await deps.findMatch(
|
|
191
|
-
const prevParams = prevMatch?.params || {};
|
|
208
|
+
const prevMatch = await deps.findMatch(left.pathname);
|
|
192
209
|
const interceptContextMatch = interceptSourceUrl
|
|
193
210
|
? await deps.findMatch(interceptContextUrl.pathname)
|
|
194
211
|
: prevMatch;
|
|
212
|
+
const interceptPrev: PrevPage = {
|
|
213
|
+
url: left,
|
|
214
|
+
params: prevMatch?.params || {},
|
|
215
|
+
};
|
|
216
|
+
// The page under an open intercept was rendered for the source, with its
|
|
217
|
+
// search, not for the intercept's URL.
|
|
218
|
+
const prev: PrevPage = interceptSourceUrl
|
|
219
|
+
? {
|
|
220
|
+
url: formSearch(new URL(interceptContextUrl)),
|
|
221
|
+
params: interceptContextMatch?.params || {},
|
|
222
|
+
}
|
|
223
|
+
: interceptPrev;
|
|
195
224
|
|
|
196
225
|
const isSameRouteNavigation = !!(
|
|
197
226
|
interceptContextMatch && interceptContextMatch.routeKey === currentRouteKey
|
|
198
227
|
);
|
|
199
228
|
|
|
200
229
|
const hasInterceptSource = !!interceptSourceUrl;
|
|
201
|
-
const effectiveFromUrl = hasInterceptSource ? interceptContextUrl :
|
|
230
|
+
const effectiveFromUrl = hasInterceptSource ? interceptContextUrl : left;
|
|
202
231
|
const effectiveFromMatch = hasInterceptSource
|
|
203
232
|
? interceptContextMatch
|
|
204
233
|
: prevMatch;
|
|
@@ -212,10 +241,11 @@ export async function resolveNavigation(
|
|
|
212
241
|
const clientSegmentSet = new Set(clientSegmentIds);
|
|
213
242
|
|
|
214
243
|
return {
|
|
215
|
-
prevUrl,
|
|
244
|
+
prevUrl: prev.url,
|
|
216
245
|
prevVisitorUrl: shown && visitor,
|
|
217
|
-
prevParams,
|
|
246
|
+
prevParams: prev.params,
|
|
218
247
|
prevMatch,
|
|
248
|
+
interceptPrev,
|
|
219
249
|
interceptContextUrl,
|
|
220
250
|
interceptContextMatch,
|
|
221
251
|
clientSegmentIds,
|
|
@@ -240,6 +270,7 @@ export function createNavigationSnapshot(
|
|
|
240
270
|
prevVisitorUrl: null,
|
|
241
271
|
prevParams: {},
|
|
242
272
|
prevMatch: null,
|
|
273
|
+
interceptPrev: { url: defaultUrl, params: {} },
|
|
243
274
|
interceptContextUrl: defaultUrl,
|
|
244
275
|
interceptContextMatch: null,
|
|
245
276
|
clientSegmentIds: [],
|