@rangojs/router 0.18.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.
Files changed (244) hide show
  1. package/dist/types/browser/event-controller.d.ts +3 -3
  2. package/dist/types/browser/history-state.d.ts +2 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +20 -2
  4. package/dist/types/browser/partial-update.d.ts +16 -1
  5. package/dist/types/browser/prefetch/cache.d.ts +6 -6
  6. package/dist/types/browser/react/Link.d.ts +19 -9
  7. package/dist/types/browser/react/context.d.ts +2 -2
  8. package/dist/types/browser/react/location-state-shared.d.ts +95 -56
  9. package/dist/types/browser/react/location-state.d.ts +1 -1
  10. package/dist/types/browser/transition-when.d.ts +80 -0
  11. package/dist/types/browser/types.d.ts +104 -9
  12. package/dist/types/build/generate-manifest.d.ts +8 -0
  13. package/dist/types/cache/background-task.d.ts +24 -2
  14. package/dist/types/cache/cache-exec-scope.d.ts +31 -0
  15. package/dist/types/cache/cache-key-utils.d.ts +44 -13
  16. package/dist/types/cache/cache-policy.d.ts +1 -1
  17. package/dist/types/cache/cache-runtime.d.ts +7 -0
  18. package/dist/types/cache/cache-scope.d.ts +117 -35
  19. package/dist/types/cache/cache-tag.d.ts +24 -4
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
  21. package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
  22. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
  23. package/dist/types/cache/document-cache.d.ts +10 -0
  24. package/dist/types/cache/handle-snapshot.d.ts +40 -8
  25. package/dist/types/cache/invalidation-order.d.ts +23 -0
  26. package/dist/types/cache/request-tag-mask.d.ts +54 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +88 -141
  28. package/dist/types/cache/tag-invalidation.d.ts +38 -14
  29. package/dist/types/cache/types.d.ts +68 -61
  30. package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
  31. package/dist/types/client-urls/navigation.d.ts +28 -2
  32. package/dist/types/client-urls/server-projection.d.ts +5 -2
  33. package/dist/types/client-urls/types.d.ts +5 -5
  34. package/dist/types/client.d.ts +1 -1
  35. package/dist/types/index.d.ts +1 -1
  36. package/dist/types/index.rsc.d.ts +1 -1
  37. package/dist/types/route-definition/helpers-types.d.ts +11 -11
  38. package/dist/types/route-name.d.ts +7 -0
  39. package/dist/types/router/error-handling.d.ts +1 -1
  40. package/dist/types/router/intercept-resolution.d.ts +2 -0
  41. package/dist/types/router/match-context.d.ts +5 -0
  42. package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
  43. package/dist/types/router/router-options.d.ts +4 -1
  44. package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
  45. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
  46. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
  47. package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
  48. package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
  49. package/dist/types/router/transition-when.d.ts +24 -10
  50. package/dist/types/rsc/attach-transition-when.d.ts +18 -0
  51. package/dist/types/rsc/full-payload.d.ts +14 -7
  52. package/dist/types/rsc/response-cache-serve.d.ts +15 -12
  53. package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
  54. package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
  55. package/dist/types/rsc/shell-capture.d.ts +78 -81
  56. package/dist/types/rsc/shell-serve.d.ts +19 -48
  57. package/dist/types/rsc/types.d.ts +2 -0
  58. package/dist/types/segment-system.d.ts +7 -0
  59. package/dist/types/server/context.d.ts +89 -61
  60. package/dist/types/server/cookie-store.d.ts +30 -18
  61. package/dist/types/server/handle-store.d.ts +52 -1
  62. package/dist/types/server/request-context.d.ts +66 -45
  63. package/dist/types/server/request-headers.d.ts +44 -0
  64. package/dist/types/server.d.ts +1 -0
  65. package/dist/types/testing/e2e/index.d.ts +1 -1
  66. package/dist/types/testing/index.d.ts +3 -2
  67. package/dist/types/testing/internal/action-id.d.ts +7 -0
  68. package/dist/types/testing/location-state-key.d.ts +24 -0
  69. package/dist/types/testing/render-route.d.ts +17 -1
  70. package/dist/types/testing/run-transition-when.d.ts +44 -69
  71. package/dist/types/testing/serve-shell-request.d.ts +7 -6
  72. package/dist/types/testing/shell-status.d.ts +34 -22
  73. package/dist/types/transition-when-ref.d.ts +79 -0
  74. package/dist/types/types/cache-types.d.ts +15 -3
  75. package/dist/types/types/index.d.ts +1 -1
  76. package/dist/types/types/segments.d.ts +86 -71
  77. package/dist/types/urls/path-helper-types.d.ts +3 -4
  78. package/dist/types/urls/pattern-types.d.ts +3 -4
  79. package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
  80. package/dist/vite/index.js +960 -97
  81. package/package.json +1 -1
  82. package/skills/cache-guide/SKILL.md +76 -49
  83. package/skills/caching/SKILL.md +167 -69
  84. package/skills/client-urls/SKILL.md +1 -1
  85. package/skills/comparison/references/framework-comparison.md +8 -5
  86. package/skills/document-cache/SKILL.md +24 -0
  87. package/skills/hooks/SKILL.md +3 -1
  88. package/skills/hooks/navigation.md +23 -2
  89. package/skills/hooks/state.md +98 -6
  90. package/skills/intercept/SKILL.md +29 -12
  91. package/skills/loader/SKILL.md +52 -21
  92. package/skills/middleware/SKILL.md +18 -0
  93. package/skills/migrate-nextjs/SKILL.md +3 -3
  94. package/skills/observability/SKILL.md +6 -5
  95. package/skills/parallel/SKILL.md +1 -1
  96. package/skills/ppr/SKILL.md +129 -77
  97. package/skills/prerender/SKILL.md +3 -3
  98. package/skills/rango/SKILL.md +25 -6
  99. package/skills/route/SKILL.md +2 -0
  100. package/skills/testing/SKILL.md +27 -25
  101. package/skills/testing/cache-prerender.md +9 -9
  102. package/skills/testing/client-components.md +45 -3
  103. package/skills/testing/loader.md +3 -2
  104. package/skills/testing/middleware.md +1 -0
  105. package/skills/testing/predicates.md +49 -49
  106. package/skills/testing/render-handler.md +1 -0
  107. package/skills/testing/serve-shell-request.md +14 -0
  108. package/skills/testing/server-actions.md +9 -3
  109. package/skills/theme/SKILL.md +8 -4
  110. package/skills/typesafety/env-and-bindings.md +8 -0
  111. package/skills/use-cache/SKILL.md +64 -19
  112. package/skills/view-transitions/SKILL.md +150 -29
  113. package/src/__augment-tests__/augmented.check.ts +23 -0
  114. package/src/browser/event-controller.ts +4 -4
  115. package/src/browser/history-state.ts +52 -1
  116. package/src/browser/link-interceptor.ts +4 -0
  117. package/src/browser/navigation-bridge.ts +77 -18
  118. package/src/browser/navigation-client.ts +4 -3
  119. package/src/browser/navigation-store.ts +43 -1
  120. package/src/browser/navigation-transaction.ts +31 -11
  121. package/src/browser/partial-update.ts +146 -35
  122. package/src/browser/prefetch/cache.ts +6 -6
  123. package/src/browser/prefetch/fetch.ts +2 -2
  124. package/src/browser/react/Link.tsx +38 -33
  125. package/src/browser/react/NavigationProvider.tsx +2 -2
  126. package/src/browser/react/context.ts +2 -2
  127. package/src/browser/react/location-state-shared.ts +241 -49
  128. package/src/browser/react/location-state.ts +5 -2
  129. package/src/browser/rsc-router.tsx +4 -0
  130. package/src/browser/server-action-bridge.ts +84 -15
  131. package/src/browser/transition-when.ts +205 -0
  132. package/src/browser/types.ts +124 -10
  133. package/src/build/generate-manifest.ts +77 -1
  134. package/src/cache/background-task.ts +55 -2
  135. package/src/cache/cache-exec-scope.ts +48 -1
  136. package/src/cache/cache-key-utils.ts +56 -16
  137. package/src/cache/cache-policy.ts +9 -4
  138. package/src/cache/cache-runtime.ts +197 -144
  139. package/src/cache/cache-scope.ts +304 -112
  140. package/src/cache/cache-tag.ts +105 -13
  141. package/src/cache/cf/cf-cache-store.ts +152 -76
  142. package/src/cache/cf/cf-shell-frame.ts +2 -2
  143. package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
  144. package/src/cache/document-cache.ts +53 -7
  145. package/src/cache/handle-snapshot.ts +52 -9
  146. package/src/cache/invalidation-order.ts +84 -0
  147. package/src/cache/request-tag-mask.ts +147 -0
  148. package/src/cache/shell-snapshot.ts +130 -348
  149. package/src/cache/tag-invalidation.ts +57 -51
  150. package/src/cache/types.ts +71 -67
  151. package/src/cache/vercel/vercel-cache-store.ts +61 -58
  152. package/src/client-urls/client-root.tsx +8 -4
  153. package/src/client-urls/client-urls.ts +8 -6
  154. package/src/client-urls/navigation.ts +55 -4
  155. package/src/client-urls/server-projection.ts +31 -10
  156. package/src/client-urls/types.ts +12 -5
  157. package/src/client.tsx +2 -0
  158. package/src/host/cookie-handler.ts +2 -1
  159. package/src/index.rsc.ts +3 -0
  160. package/src/index.ts +3 -0
  161. package/src/loader-redirect.tsx +7 -3
  162. package/src/prerender/build-shell-capture.ts +155 -146
  163. package/src/route-definition/dsl-helpers.ts +2 -0
  164. package/src/route-definition/helpers-types.ts +11 -11
  165. package/src/route-name.ts +12 -0
  166. package/src/router/content-negotiation.ts +4 -1
  167. package/src/router/error-handling.ts +5 -2
  168. package/src/router/handler-context.ts +10 -7
  169. package/src/router/intercept-resolution.ts +11 -1
  170. package/src/router/loader-resolution.ts +28 -3
  171. package/src/router/logging.ts +5 -3
  172. package/src/router/match-api.ts +62 -34
  173. package/src/router/match-context.ts +5 -0
  174. package/src/router/match-handlers.ts +9 -29
  175. package/src/router/match-middleware/background-revalidation.ts +3 -5
  176. package/src/router/match-middleware/cache-lookup.ts +35 -19
  177. package/src/router/match-middleware/cache-store.ts +15 -9
  178. package/src/router/match-result.ts +1 -0
  179. package/src/router/middleware.ts +2 -5
  180. package/src/router/navigation-snapshot.ts +6 -5
  181. package/src/router/prerender-match.ts +4 -0
  182. package/src/router/request-classification.ts +7 -4
  183. package/src/router/router-options.ts +4 -1
  184. package/src/router/segment-resolution/fresh.ts +0 -4
  185. package/src/router/segment-resolution/loader-cache.ts +150 -25
  186. package/src/router/segment-resolution/loader-mask.ts +3 -4
  187. package/src/router/segment-resolution/loader-snapshot.ts +15 -78
  188. package/src/router/segment-resolution/mask-nested.ts +47 -22
  189. package/src/router/segment-resolution/revalidation.ts +0 -3
  190. package/src/router/segment-resolution/view-transition-default.ts +6 -21
  191. package/src/router/telemetry.ts +6 -3
  192. package/src/router/transition-when.ts +34 -52
  193. package/src/router.ts +2 -1
  194. package/src/rsc/attach-transition-when.ts +47 -0
  195. package/src/rsc/full-payload.ts +30 -10
  196. package/src/rsc/handler.ts +12 -10
  197. package/src/rsc/loader-fetch.ts +2 -1
  198. package/src/rsc/manifest-init.ts +4 -0
  199. package/src/rsc/origin-guard.ts +8 -5
  200. package/src/rsc/progressive-enhancement.ts +7 -30
  201. package/src/rsc/response-cache-serve.ts +68 -80
  202. package/src/rsc/response-route-handler.ts +2 -2
  203. package/src/rsc/rsc-rendering.ts +206 -252
  204. package/src/rsc/server-action.ts +8 -20
  205. package/src/rsc/shell-build-manifest.ts +22 -20
  206. package/src/rsc/shell-capture-constants.ts +136 -20
  207. package/src/rsc/shell-capture.ts +352 -589
  208. package/src/rsc/shell-serve.ts +73 -109
  209. package/src/rsc/ssr-setup.ts +3 -2
  210. package/src/rsc/types.ts +2 -0
  211. package/src/segment-system.tsx +40 -7
  212. package/src/server/context.ts +189 -124
  213. package/src/server/cookie-store.ts +153 -127
  214. package/src/server/handle-store.ts +245 -27
  215. package/src/server/request-context.ts +123 -84
  216. package/src/server/request-headers.ts +134 -0
  217. package/src/server.ts +4 -0
  218. package/src/testing/dispatch.ts +4 -7
  219. package/src/testing/e2e/index.ts +1 -0
  220. package/src/testing/index.ts +5 -0
  221. package/src/testing/internal/action-id.ts +25 -0
  222. package/src/testing/internal/context.ts +1 -1
  223. package/src/testing/location-state-key.ts +45 -0
  224. package/src/testing/render-handler.ts +1 -1
  225. package/src/testing/render-route.tsx +109 -16
  226. package/src/testing/run-client-revalidate.ts +3 -19
  227. package/src/testing/run-loader.ts +5 -1
  228. package/src/testing/run-transition-when.ts +109 -172
  229. package/src/testing/serve-shell-request.ts +10 -7
  230. package/src/testing/shell-status.ts +60 -56
  231. package/src/transition-when-ref.ts +220 -0
  232. package/src/types/cache-types.ts +15 -3
  233. package/src/types/index.ts +3 -0
  234. package/src/types/segments.ts +94 -91
  235. package/src/urls/path-helper-types.ts +3 -4
  236. package/src/urls/pattern-types.ts +3 -4
  237. package/src/vite/discovery/discover-routers.ts +3 -0
  238. package/src/vite/discovery/shell-prerender-phase.ts +0 -2
  239. package/src/vite/discovery/virtual-module-codegen.ts +16 -6
  240. package/src/vite/plugins/transition-when-hoist.ts +1064 -0
  241. package/src/vite/rango.ts +2 -0
  242. package/src/vite/router-discovery.ts +75 -5
  243. package/dist/types/rsc/transition-gate.d.ts +0 -23
  244. package/src/rsc/transition-gate.ts +0 -86
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import type { NavigationLocation, NavigateOptions, TrackedActionState, InflightAction, RscMetadata, HandleData, StreamingToken } from "./types.js";
2
+ import type { NavigationLocation, NavigateOptionsInternal, TrackedActionState, InflightAction, RscMetadata, HandleData, StreamingToken } from "./types.js";
3
3
  /**
4
4
  * Phase of a navigation operation
5
5
  */
@@ -16,7 +16,7 @@ export interface NavigationEntry {
16
16
  abort: AbortController;
17
17
  phase: NavigationPhase;
18
18
  startedAt: number;
19
- options?: NavigateOptions & {
19
+ options?: NavigateOptionsInternal & {
20
20
  skipLoadingState?: boolean;
21
21
  };
22
22
  }
@@ -165,7 +165,7 @@ export interface ActionHandle extends Disposable {
165
165
  * Event controller interface
166
166
  */
167
167
  export interface EventController {
168
- startNavigation(url: string, options?: NavigateOptions & {
168
+ startNavigation(url: string, options?: NavigateOptionsInternal & {
169
169
  skipLoadingState?: boolean;
170
170
  }): NavigationHandle;
171
171
  abortNavigation(): void;
@@ -11,6 +11,8 @@ export declare function buildHistoryState(userState: unknown, routerState?: {
11
11
  intercept?: boolean;
12
12
  sourceUrl?: string;
13
13
  }, serverState?: Record<string, unknown>): Record<string, unknown> | null;
14
+ /** Check if a history state object contains location state keys. */
15
+ export declare function hasLocationState(state: unknown): boolean;
14
16
  /**
15
17
  * Stamp an `idx` on the next history entry's state and call push/replaceState.
16
18
  * Push increments the current idx; replace keeps it. Initial entry idx is 0.
@@ -1,4 +1,4 @@
1
- import type { NavigateOptions, NavigationStore, ResolvedSegment, StreamingToken } from "./types.js";
1
+ import type { NavigateOptionsInternal, NavigationStore, ResolvedSegment, StreamingToken } from "./types.js";
2
2
  import type { EventController, NavigationHandle } from "./event-controller.js";
3
3
  export { resolveNavigationState } from "./history-state.js";
4
4
  /**
@@ -28,6 +28,14 @@ interface CommitOptions {
28
28
  * merged in. The page left was saved by handleTraversalStart.
29
29
  */
30
30
  traversal?: boolean;
31
+ /** The committed route's name (payload metadata), remembered per history entry. */
32
+ routeName?: string;
33
+ /**
34
+ * transition({ when }) decision already made at this navigation's first
35
+ * presentation (the optimistic clientUrls() swap). Not a commit input: the
36
+ * partial updater reuses it instead of deciding again.
37
+ */
38
+ transitionGatedOff?: boolean;
31
39
  }
32
40
  /**
33
41
  * Options that can override the pre-configured commit settings
@@ -47,12 +55,22 @@ interface BoundCommitOverrides {
47
55
  cacheOnly?: boolean;
48
56
  /** Server-set location state to merge into history.pushState */
49
57
  serverState?: Record<string, unknown>;
58
+ /** The committed route's name (payload metadata), remembered per history entry. */
59
+ routeName?: string;
50
60
  }
51
61
  /**
52
62
  * Bound transaction with pre-configured commit options (without segmentIds/segments)
53
63
  */
54
64
  export interface BoundTransaction {
55
65
  readonly currentUrl: string;
66
+ /** Bound `replace` option (history replace instead of push). */
67
+ readonly replace?: boolean;
68
+ /** Bound `traversal` option: a back/forward navigation. */
69
+ readonly traversal?: boolean;
70
+ /** Bound user state (resolved), pushed with the entry at commit. */
71
+ readonly state?: unknown;
72
+ /** Bound `transitionGatedOff`: the decision made at first presentation. */
73
+ readonly transitionGatedOff?: boolean;
56
74
  /** Start streaming and get a token to end it when the stream completes */
57
75
  startStreaming(): StreamingToken;
58
76
  /** Commit the navigation. Returns the effective scroll option for the caller to handle. */
@@ -76,6 +94,6 @@ interface NavigationTransaction extends Disposable {
76
94
  * Creates a navigation transaction that coordinates with the event controller.
77
95
  * Handles loading state transitions and cleanup on completion/abort.
78
96
  */
79
- export declare function createNavigationTransaction(store: NavigationStore, eventController: EventController, url: string, options?: NavigateOptions & {
97
+ export declare function createNavigationTransaction(store: NavigationStore, eventController: EventController, url: string, options?: NavigateOptionsInternal & {
80
98
  skipLoadingState?: boolean;
81
99
  }): NavigationTransaction;
@@ -3,7 +3,13 @@ import type { ReactNode } from "react";
3
3
  import type { RenderSegmentsOptions } from "../segment-system.js";
4
4
  import type { BoundTransaction } from "./navigation-transaction.js";
5
5
  import type { NavigationUpdate } from "./types.js";
6
- export declare function shouldStartViewTransition(segments: ResolvedSegment[]): boolean;
6
+ import { type TransitionWhenActionInput } from "./transition-when.js";
7
+ /**
8
+ * Hold/animate decision for a commit: a non-intercept segment carries a
9
+ * transition, no intercept is presented, and this navigation's
10
+ * transition({ when }) decision did not gate it off.
11
+ */
12
+ export declare function shouldStartViewTransition(segments: readonly ResolvedSegment[], transitionGatedOff?: boolean): boolean;
7
13
  /**
8
14
  * Transition commit. Every held commit announces the loader streams the
9
15
  * committed tree is still receiving (loader-store.ts announcePendingStreams)
@@ -38,6 +44,8 @@ export interface CommitOverrides {
38
44
  interceptSourceUrl?: string;
39
45
  /** Server-set location state to merge into history.pushState */
40
46
  serverState?: Record<string, unknown>;
47
+ /** The committed route's name (payload metadata), remembered per history entry. */
48
+ routeName?: string;
41
49
  }
42
50
  /**
43
51
  * Discriminated update mode for partial updates.
@@ -61,6 +69,11 @@ export type UpdateMode = {
61
69
  * boundaries do not animate the identical repaint.
62
70
  */
63
71
  optimisticPresented?: boolean;
72
+ /**
73
+ * A router.refresh() (or cross-tab refresh) of the current URL:
74
+ * transition({ when }) sees kind "revalidate", not "replace".
75
+ */
76
+ refresh?: boolean;
64
77
  } | {
65
78
  type: "leave-intercept";
66
79
  interceptSourceUrl?: string;
@@ -71,6 +84,8 @@ export type UpdateMode = {
71
84
  type: "action";
72
85
  interceptSourceUrl?: string;
73
86
  actionId?: string;
87
+ /** The action's FormData and result, for transition({ when }) `action`. */
88
+ action?: TransitionWhenActionInput;
74
89
  };
75
90
  /**
76
91
  * Type for the fetchPartialUpdate function
@@ -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
- * server tags a response with `X-RSC-Prefetch-Scope: source` (intercept
20
- * modals etc.), OR when a Link opts in with `prefetchKey=":source"` — in
21
- * both cases so source-sensitive responses cannot bleed into navigations
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
@@ -122,7 +122,7 @@ export declare function isPrefetchCacheDisabled(): boolean;
122
122
  * rotation invalidates source-scoped entries alongside wildcard ones,
123
123
  * plus the source page href so the key is unique per originating page.
124
124
  * Populated either when the server tags a response with
125
- * `X-RSC-Prefetch-Scope: source` (intercept modals, etc.) or when a
125
+ * `X-RSC-Prefetch-Scope: source` (a route an intercept targets) or when a
126
126
  * Link opts in via `prefetchKey=":source"`.
127
127
  *
128
128
  * Source-scoped keys retain `_rsc_segments` because their exact source tree is
@@ -1,13 +1,12 @@
1
1
  import React, { type ForwardRefExoticComponent, type RefAttributes } from "react";
2
- import { type LocationStateEntry } from "./location-state.js";
2
+ import type { HistoryState } from "../types.js";
3
+ export type StateOrGetter<T = unknown> = T | (() => T);
3
4
  /**
4
- * State prop type for Link component.
5
- * - LocationStateEntry[]: Type-safe state entries via createLocationState()
6
- * - StateOrGetter: Plain state object or click-time getter function
7
- * - Record<string, unknown>: Plain state object passed to history.pushState
5
+ * State prop type for Link component: the `router.push()` state type
6
+ * ({@link HistoryState}: `LocationStateEntry[]` from createLocationState(), or
7
+ * plain structured-clone-safe state), or a getter for it called at click time.
8
8
  */
9
- export type StateOrGetter<T = unknown> = T | (() => T);
10
- export type LinkState = LocationStateEntry[] | StateOrGetter<Record<string, unknown>>;
9
+ export type LinkState = StateOrGetter<HistoryState>;
11
10
  import type { PrefetchStrategy } from "../../router/prefetch-default.js";
12
11
  export type { PrefetchStrategy } from "../../router/prefetch-default.js";
13
12
  export { resolveAdaptiveStrategy } from "../prefetch/default-strategy.js";
@@ -41,6 +40,14 @@ export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorEle
41
40
  * @default true
42
41
  */
43
42
  revalidate?: boolean;
43
+ /**
44
+ * Set to `false` to present this navigation without a transition (urgent
45
+ * commit, `<ViewTransition>` classes `"none"`); no `transition({ when })`
46
+ * predicate is called. See `NavigateOptions.transition`.
47
+ *
48
+ * @default true
49
+ */
50
+ transition?: boolean;
44
51
  /**
45
52
  * Prefetch strategy for the link destination. When omitted, falls back to
46
53
  * the router-wide default (`createRouter({ defaultPrefetch })`: `"none"` in
@@ -66,8 +73,11 @@ export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorEle
66
73
  * therefore serve the wrong diff to a navigation from a different
67
74
  * source.
68
75
  *
69
- * Intercept responses are auto-scoped to the source via a server-side
70
- * tag, so `":source"` is only needed for custom revalidation logic.
76
+ * A route an intercept targets is scoped to the source automatically
77
+ * (server-side `x-rsc-prefetch-scope: source`), whether or not the
78
+ * intercept applies from this page: the modal and the full page are
79
+ * both source-specific. Every other route stays shared, so `":source"`
80
+ * is only needed for custom revalidation logic.
71
81
  *
72
82
  * @example
73
83
  * ```tsx
@@ -1,5 +1,5 @@
1
1
  import { type Context } from "react";
2
- import type { NavigationStore, NavigateOptions } from "../types.js";
2
+ import type { NavigationStore, NavigateOptionsInternal } from "../types.js";
3
3
  import type { EventController } from "../event-controller.js";
4
4
  import type { PrefetchStrategy } from "../../router/prefetch-default.js";
5
5
  /**
@@ -29,7 +29,7 @@ export interface NavigationStoreContextValue {
29
29
  * @param options - Navigation options (replace, scroll)
30
30
  * @returns Promise that resolves when navigation is complete
31
31
  */
32
- navigate: (url: string, options?: NavigateOptions) => Promise<void>;
32
+ navigate: (url: string, options?: NavigateOptionsInternal) => Promise<void>;
33
33
  /**
34
34
  * Refresh the current route
35
35
  *
@@ -20,16 +20,32 @@ type LocationStateUnsafeFn = (...args: never[]) => unknown;
20
20
  type LocationStateUnsafeCtor = abstract new (...args: never[]) => unknown;
21
21
  type IsAny<T> = 0 extends 1 & T ? true : false;
22
22
  type IsUnknown<T> = IsAny<T> extends true ? false : unknown extends T ? true : false;
23
+ type UnknownReason = "`unknown` cannot be verified as serializable; give it a concrete type";
24
+ type FunctionReason = "functions cannot be stored in location state";
25
+ type ConstructorReason = "class constructors cannot be stored in location state";
26
+ type SymbolReason = "symbols cannot be stored in location state";
27
+ type ReactReason = "React/RSC content cannot be stored in location state; store plain data and render it on arrival";
28
+ type DepthReason = "nested too deep to locate the unsafe field; it is below this path";
23
29
  /**
24
30
  * Branded error surfaced when a value that cannot live in location state is
25
31
  * used. Location state is written into `history.state`, which uses the
26
32
  * structured clone algorithm; React elements, functions, and symbols throw a
27
- * `DataCloneError` at runtime. Carries a human-readable reason so the compile
28
- * error explains the fix.
33
+ * `DataCloneError` at runtime. Carries the reason and the offending field's
34
+ * path (`items[].info.values`; `<root>` for the value itself) so the compile
35
+ * error names the field to fix.
29
36
  */
30
- export type LocationStateUnsafe<Reason extends string> = {
37
+ export type LocationStateUnsafe<Reason extends string, Path extends string = "<root>"> = {
31
38
  readonly __rango_location_state_unsafe: Reason;
39
+ readonly __rango_location_state_path: Path;
32
40
  };
41
+ /**
42
+ * Why a value cannot be structured-cloned, for the kinds that fail on their
43
+ * own (not through something nested in them), or `never`. Distributes over a
44
+ * union, one reason per unsafe member.
45
+ */
46
+ type UnsafeReason<T> = T extends LocationStateUnsafeFn ? FunctionReason : T extends LocationStateUnsafeCtor ? ConstructorReason : T extends symbol ? SymbolReason : T extends ReactElement ? ReactReason : never;
47
+ /** Primitives and structured-clone built-ins, stored as-is. */
48
+ type LocationStatePassthrough = string | number | boolean | bigint | null | undefined | Date | RegExp | ArrayBuffer | ArrayBufferView | Blob | File | FormData;
33
49
  /**
34
50
  * Maps `T` to itself when it is safe to store in location state, or to a branded
35
51
  * {@link LocationStateUnsafe} error for the disallowed parts: `unknown`, React
@@ -40,21 +56,56 @@ export type LocationStateUnsafe<Reason extends string> = {
40
56
  * definition's value parameter so posting RSC content is a COMPILE error, not a
41
57
  * runtime `DataCloneError`. (`any` is unguardable and remains an escape hatch.)
42
58
  */
43
- export type LocationStateSafe<T> = IsUnknown<T> extends true ? LocationStateUnsafe<"location state needs an explicit, concrete type; `unknown` cannot be verified as serializable"> : T extends LocationStateUnsafeFn ? LocationStateUnsafe<"functions cannot be stored in location state"> : T extends LocationStateUnsafeCtor ? LocationStateUnsafe<"class constructors cannot be stored in location state"> : T extends symbol ? LocationStateUnsafe<"symbols cannot be stored in location state"> : T extends ReactElement ? LocationStateUnsafe<"React/RSC content cannot be stored in location state; store plain data and render it on arrival"> : T extends string | number | boolean | bigint | null | undefined ? T : T extends Date | RegExp | ArrayBuffer | ArrayBufferView | Blob | File | FormData ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<LocationStateSafe<K>, LocationStateSafe<V>> : T extends ReadonlySet<infer V> ? ReadonlySet<LocationStateSafe<V>> : T extends readonly unknown[] ? {
59
+ export type LocationStateSafe<T> = IsUnknown<T> extends true ? LocationStateUnsafe<UnknownReason> : T extends unknown ? [UnsafeReason<T>] extends [never] ? T extends LocationStatePassthrough ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<LocationStateSafe<K>, LocationStateSafe<V>> : T extends ReadonlySet<infer V> ? ReadonlySet<LocationStateSafe<V>> : T extends object ? {
44
60
  [K in keyof T]: LocationStateSafe<T[K]>;
45
- } : T extends object ? {
46
- [K in keyof T]: LocationStateSafe<T[K]>;
47
- } : T;
61
+ } : T : LocationStateUnsafe<UnsafeReason<T>> : never;
62
+ type AtPath<P extends string> = P extends "" ? "<root>" : P;
63
+ /** `a.b` for a named key; `a[string]` / `a[number]` for an index signature. */
64
+ type ChildPath<P extends string, K> = string extends K ? `${P}[string]` : number extends K ? `${P}[number]` : K extends string | number ? P extends "" ? `${K}` : `${P}.${K}` : `${P}[symbol]`;
65
+ type UnsafeAt<R, P extends string> = R extends string ? LocationStateUnsafe<R, AtPath<P>> : never;
66
+ /**
67
+ * Walks `T` to the fields {@link LocationStateSafe} rejects and returns one
68
+ * {@link LocationStateUnsafe} brand per offending path (a union when several
69
+ * fail). Reports the shallowest failures: a value that is itself unsafe stops
70
+ * the walk, and an object with unsafe fields of its own reports only those, so
71
+ * a class instance or DOM node stops at its methods instead of walking its
72
+ * whole object graph (`{ el: HTMLElement }` took 8.4M instantiations and hit
73
+ * TS2589 before). Descends only into children that fail the cached
74
+ * `LocationStateSafe` check, and stops at depth 8.
75
+ */
76
+ type LocateUnsafe<T, P extends string, D extends unknown[]> = D["length"] extends 8 ? LocationStateUnsafe<DepthReason, AtPath<P>> : IsUnknown<T> extends true ? LocationStateUnsafe<UnknownReason, AtPath<P>> : [UnsafeReason<T>] extends [never] ? LocateNested<T, P, D> : UnsafeAt<UnsafeReason<T>, P>;
77
+ type LocateNested<T, P extends string, D extends unknown[]> = T extends LocationStatePassthrough ? never : T extends ReadonlyMap<infer K, infer V> ? LocateChild<K, `${P}<key>`, D> | LocateChild<V, `${P}<value>`, D> : T extends ReadonlySet<infer V> ? LocateChild<V, `${P}[]`, D> : T extends readonly unknown[] ? number extends T["length"] ? LocateChild<T[number], `${P}[]`, D> : {
78
+ [K in keyof T & `${number}`]-?: LocateChild<T[K], `${P}[${K}]`, D>;
79
+ }[keyof T & `${number}`] : T extends object ? LocateFields<T, P, D, OwnUnsafeKeys<T>> : never;
80
+ /**
81
+ * Keys whose value is unsafe itself, not through something nested in it.
82
+ * `any` is never one: it passes LocationStateSafe, so reporting it would
83
+ * leave nothing to report and hide its siblings' paths.
84
+ */
85
+ type OwnUnsafeKeys<T> = {
86
+ [K in keyof T]-?: IsAny<T[K]> extends true ? never : IsUnknown<T[K]> extends true ? K : [UnsafeReason<T[K]>] extends [never] ? never : K;
87
+ }[keyof T];
88
+ type LocateFields<T, P extends string, D extends unknown[], Own extends keyof T> = [Own] extends [never] ? {
89
+ [K in keyof T]-?: LocateChild<T[K], ChildPath<P, K>, D>;
90
+ }[keyof T] : {
91
+ [K in Own]-?: LocateChild<T[K], ChildPath<P, K>, D>;
92
+ }[Own];
93
+ type LocateChild<T, P extends string, D extends unknown[]> = [T] extends [
94
+ LocationStateSafe<T>
95
+ ] ? never : LocateUnsafe<T, P, [...D, unknown]>;
96
+ type GenericReason = "location state must be serializable: React/RSC content, functions, and symbols cannot be stored — pass plain data and render it on arrival";
48
97
  /**
49
98
  * `unknown` (a no-op) when `T` is safe to store in location state, otherwise a
50
- * branded {@link LocationStateUnsafe} object. Intersected into the value
51
- * parameter of a definition's call and `write()` so POSTING RSC content (or any
52
- * non-serializable value) is a compile error whose text carries the reason —
53
- * without a `TState extends ...` self-constraint, which TypeScript rejects as
54
- * circular (TS2313). For safe `T`, `value & unknown` collapses back to `value`,
55
- * so valid usage is unchanged.
99
+ * branded {@link LocationStateUnsafe} object naming the reason and the path of
100
+ * each offending field, e.g. `LocationStateUnsafe<"...", "items[].info.values">`.
101
+ * Intersected into the value parameter of a definition's call and `write()` so
102
+ * POSTING RSC content (or any non-serializable value) is a compile error whose
103
+ * text carries the reason and the field — without a `TState extends ...`
104
+ * self-constraint, which TypeScript rejects as circular (TS2313). For safe `T`,
105
+ * `value & unknown` collapses back to `value`, so valid usage is unchanged.
56
106
  */
57
- export type ValidateLocationState<T> = [T] extends [LocationStateSafe<T>] ? unknown : LocationStateUnsafe<"location state must be serializable: React/RSC content, functions, and symbols cannot be stored — pass plain data and render it on arrival">;
107
+ export type ValidateLocationState<T> = [T] extends [LocationStateSafe<T>] ? unknown : OrGenericUnsafe<LocateUnsafe<T, "", []>>;
108
+ type OrGenericUnsafe<U> = [U] extends [never] ? LocationStateUnsafe<GenericReason> : U;
58
109
  /**
59
110
  * Type-safe location state definition
60
111
  *
@@ -63,7 +114,10 @@ export type ValidateLocationState<T> = [T] extends [LocationStateSafe<T>] ? unkn
63
114
  */
64
115
  export interface LocationStateDefinition<TArgs extends unknown[], TState> {
65
116
  (...args: TArgs): LocationStateEntry;
66
- /** Injected by Vite plugin - do not set manually */
117
+ /**
118
+ * Injected by Vite plugin - do not set manually. Unit tests without the
119
+ * plugin use withLocationStateKey() from @rangojs/router/testing.
120
+ */
67
121
  __rsc_ls_key: string;
68
122
  /** Whether this state auto-clears after first read */
69
123
  readonly __rsc_ls_flash: boolean;
@@ -79,6 +133,15 @@ export interface LocationStateDefinition<TArgs extends unknown[], TState> {
79
133
  * want React to manage subscription/hydration for you.
80
134
  */
81
135
  read(): TState | undefined;
136
+ /**
137
+ * Read this definition's slot from a location snapshot instead of the
138
+ * current entry: a transition({ when }) context's `from` / `to`, or any
139
+ * `{ state }` holding a `history.state` object. A snapshot read never clears
140
+ * flash state.
141
+ */
142
+ read(location: {
143
+ readonly state: unknown;
144
+ }): TState | undefined;
82
145
  /**
83
146
  * Statically write the value into the current history entry under this
84
147
  * definition's key, preserving any other keys already on history.state
@@ -106,51 +169,27 @@ export interface LocationStateDefinition<TArgs extends unknown[], TState> {
106
169
  delete(): void;
107
170
  }
108
171
  /**
109
- * Create a type-safe location state definition
110
- *
111
- * The key is auto-injected by the Vite exposeInternalIds plugin as a property
112
- * based on file path and export name. No manual key required.
113
- *
114
- * @param options Optional configuration
115
- * @returns A typed state definition for use with Link and useLocationState
116
- *
117
- * @example
118
- * ```typescript
119
- * // Persistent state (survives back/forward)
120
- * export const ProductState = createLocationState<{ name: string; price: number }>();
121
- *
122
- * // Flash state (cleared after first read)
123
- * export const FlashMessage = createLocationState<{ text: string }>({ flash: true });
124
- *
125
- * // Use in Link
126
- * <Link to="/product/123" state={[ProductState({ name: "Widget", price: 9.99 })]}>
127
- *
128
- * // Just-in-time typed state (getter called at click time, not render time).
129
- * // Must be in a client component — the getter function can't cross the RSC boundary.
130
- * <Link
131
- * to="/product/123"
132
- * state={[ProductState(() => ({ name: product.name, price: product.price }))]}
133
- * >
134
- *
135
- * // Read with hook (reactive)
136
- * const product = useLocationState(ProductState);
137
- *
138
- * // Read without hook (snapshot, client-side only)
139
- * const snap = ProductState.read();
140
- *
141
- * // Static write to current history entry (non-reactive, client-side only).
142
- * // Survives back/forward and hard refresh; useLocationState() readers will
143
- * // NOT see the new value until the next navigation. Pair with .read() or a
144
- * // fresh mount.
145
- * ProductState.write({ name: "Widget", price: 9.99 });
146
- *
147
- * // Manually clear the slot (non-reactive, client-side only).
148
- * ProductState.delete();
149
- * ```
172
+ * @internal The navigation store registers here (navigation-store.ts) to
173
+ * refresh its per-entry memory: back/forward reads the entry being left from
174
+ * that memory (at popstate history.state already belongs to the destination),
175
+ * so a write after the entry's commit must reach it.
150
176
  */
177
+ export declare function setCurrentHistoryStateListener(listener: (() => void) | undefined): void;
178
+ /** Replace the current entry's history.state in place (same URL). */
179
+ export declare function replaceCurrentHistoryState(state: unknown): void;
151
180
  export declare function createLocationState<TState>(options?: LocationStateOptions): LocationStateDefinition<[
152
181
  (TState | (() => TState)) & ValidateLocationState<TState>
153
182
  ], TState>;
183
+ /**
184
+ * A definition's key, or undefined when none is set, without the missing-key
185
+ * throw of the `__rsc_ls_key` getter. For dev checks and test helpers.
186
+ */
187
+ export declare function peekLocationStateKey(definition: object): string | undefined;
188
+ /**
189
+ * Check if a value is a LocationStateDefinition (a createLocationState()
190
+ * result, callable, not an entry).
191
+ */
192
+ export declare function isLocationStateDefinition(value: unknown): value is LocationStateDefinition<unknown[], unknown>;
154
193
  /**
155
194
  * Check if a value is a LocationStateEntry
156
195
  */
@@ -1,4 +1,4 @@
1
- import type { LocationStateDefinition } from "./location-state-shared.js";
1
+ import { type LocationStateDefinition } from "./location-state-shared.js";
2
2
  export { createLocationState, isLocationStateEntry, resolveLocationStateEntries, type LocationStateEntry, type LocationStateDefinition, type LocationStateOptions, } from "./location-state-shared.js";
3
3
  /**
4
4
  * Hook to read location state from history.state
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Browser-side transition({ when }) decisions.
3
+ *
4
+ * A navigation is decided ONCE, at the first commit that presents its
5
+ * destination: the canonical commit for a server route, the optimistic swap
6
+ * for a clientUrls() cross-route navigation (the canonical commit then reuses
7
+ * the decision carried on its transaction), the history-cache restore or
8
+ * refetch commit for back/forward, the action commit (error lane included).
9
+ *
10
+ * The decision is navigation-wide: every distinct predicate (by function
11
+ * identity) declared by a committed segment, kept or re-sent (#989), runs, and
12
+ * any false gates the whole navigation off. It never changes the rendered
13
+ * tree's shape: every segment keeps its key and its <ViewTransition> element
14
+ * (segment-system.tsx renders each class "none"), and the commit is urgent
15
+ * instead of held (partial-update.ts). That is what keeps component state
16
+ * across when true -> false -> true (#995). A response carries no decision,
17
+ * so a reused prefetch is decided against the real source.
18
+ */
19
+ import type { ResolvedSegment, TransitionWhenContext, TransitionWhenFn, TransitionWhenKind } from "../types/segments.js";
20
+ import type { NavigationStore } from "./types.js";
21
+ /** One side of the navigation before it is frozen into a RouteLocation. */
22
+ export interface RouteLocationInput {
23
+ url: string | URL;
24
+ params?: Readonly<Record<string, string>>;
25
+ routeName?: string;
26
+ state?: unknown;
27
+ }
28
+ /** The action fields of a `kind: "action"` context. */
29
+ export interface TransitionWhenActionInput {
30
+ id?: string;
31
+ formData?: FormData;
32
+ result?: unknown;
33
+ error?: unknown;
34
+ }
35
+ export interface TransitionDecisionInput {
36
+ kind: TransitionWhenKind;
37
+ from: RouteLocationInput;
38
+ /** The destination; ignored for "action" and "revalidate" (`to` is `from`). */
39
+ to?: RouteLocationInput;
40
+ action?: TransitionWhenActionInput;
41
+ }
42
+ export declare function createTransitionWhenContext(input: TransitionDecisionInput): TransitionWhenContext;
43
+ /**
44
+ * Run one predicate. A throw counts as false (the navigation does not hold)
45
+ * and is logged; it never fails the navigation.
46
+ */
47
+ export declare function evaluateTransitionWhen(fn: TransitionWhenFn, ctx: TransitionWhenContext): boolean;
48
+ /** Distinct predicates of the segments that declare one, in segment order. */
49
+ export declare function collectTransitionWhens(segments: readonly ResolvedSegment[]): Set<TransitionWhenFn>;
50
+ /**
51
+ * Decide a commit: true when the navigation is gated off (some predicate
52
+ * returned false or threw). The context is built only when a predicate runs.
53
+ * An intercept commit is not evaluated: it never holds or animates
54
+ * (partial-update.ts shouldStartViewTransition). `extra` is a predicate not on
55
+ * a segment yet (the optimistic clientUrls() destination).
56
+ */
57
+ export declare function decideTransitionGatedOff(segments: readonly ResolvedSegment[], input: () => TransitionDecisionInput, extra?: TransitionWhenFn): boolean;
58
+ /** Merge route params the way popstate restores them (per-segment params). */
59
+ export declare function mergeSegmentParams(segments: readonly ResolvedSegment[]): Record<string, string>;
60
+ export interface CommitDecision {
61
+ /** Newer params / route name for the committed location (an action response's). */
62
+ from?: {
63
+ params?: Record<string, string>;
64
+ routeName?: string;
65
+ };
66
+ /** The destination, from the committed location; omit for "action" / "revalidate". */
67
+ to?: (from: RouteLocationInput) => RouteLocationInput;
68
+ action?: TransitionWhenActionInput;
69
+ extra?: TransitionWhenFn;
70
+ }
71
+ /**
72
+ * decideTransitionGatedOff with `from` read from the store: call it before
73
+ * the commit moves the store. `state` is the live history.state, except for
74
+ * back/forward: at popstate history.state already belongs to the destination,
75
+ * so the entry being left comes from the store's per-entry memory (undefined
76
+ * after a reload, never guessed).
77
+ */
78
+ export declare function decideCommitGatedOff(store: NavigationStore, segments: readonly ResolvedSegment[], kind: TransitionWhenKind, decision?: CommitDecision): boolean;
79
+ /** The FormData argument a server action was called with, if any. */
80
+ export declare function findActionFormData(args: readonly unknown[]): FormData | undefined;