@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -0
- package/README.md +1037 -4
- package/dist/bin/rango.js +1619 -157
- package/dist/vite/index.js +5762 -2301
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +71 -63
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +367 -71
- package/skills/host-router/SKILL.md +218 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +176 -8
- package/skills/layout/SKILL.md +124 -3
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +474 -47
- package/skills/middleware/SKILL.md +207 -37
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +15 -11
- package/skills/parallel/SKILL.md +272 -1
- package/skills/prerender/SKILL.md +467 -65
- package/skills/rango/SKILL.md +89 -21
- package/skills/response-routes/SKILL.md +152 -91
- package/skills/route/SKILL.md +305 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/server-actions/SKILL.md +739 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +333 -86
- package/skills/use-cache/SKILL.md +324 -0
- package/skills/view-transitions/SKILL.md +212 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +136 -68
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +374 -561
- package/src/browser/navigation-client.ts +228 -70
- package/src/browser/navigation-store.ts +97 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +376 -315
- package/src/browser/prefetch/cache.ts +314 -0
- package/src/browser/prefetch/fetch.ts +282 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +191 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +152 -0
- package/src/browser/react/Link.tsx +255 -71
- package/src/browser/react/NavigationProvider.tsx +152 -24
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +55 -0
- package/src/browser/react/index.ts +15 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -120
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +78 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +99 -0
- package/src/browser/react/use-router.ts +83 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +85 -99
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +246 -64
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +158 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +84 -23
- package/src/build/generate-route-types.ts +39 -828
- package/src/build/index.ts +4 -5
- package/src/build/route-trie.ts +85 -32
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -307
- package/src/cache/cf/cf-cache-store.ts +573 -21
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +6 -1
- package/src/client.tsx +118 -302
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +55 -10
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +65 -45
- package/src/index.rsc.ts +138 -21
- package/src/index.ts +206 -51
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +25 -143
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +159 -13
- package/src/prerender.ts +397 -29
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +231 -121
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1134 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +483 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition.ts +1 -1431
- package/src/route-map-builder.ts +162 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +66 -9
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +418 -86
- package/src/router/intercept-resolution.ts +35 -20
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +359 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +98 -32
- package/src/router/match-api.ts +196 -261
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +441 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +415 -86
- package/src/router/match-middleware/cache-store.ts +91 -29
- package/src/router/match-middleware/intercept-resolution.ts +48 -21
- package/src/router/match-middleware/segment-resolution.ts +73 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +154 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +209 -0
- package/src/router/middleware.ts +373 -371
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +292 -52
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +152 -39
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +756 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1407 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1315
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +111 -39
- package/src/router/types.ts +17 -9
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +642 -2011
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +864 -1114
- package/src/rsc/helpers.ts +181 -19
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +395 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +360 -0
- package/src/rsc/rsc-rendering.ts +256 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +360 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +52 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +187 -38
- package/src/server/context.ts +333 -59
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +603 -109
- package/src/server.ts +35 -155
- package/src/ssr/index.tsx +107 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +764 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +209 -0
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +167 -0
- package/src/types.ts +1 -1757
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +108 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1282
- package/src/use-loader.tsx +161 -81
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +376 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +486 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -2063
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +98 -0
- package/src/vite/plugins/client-ref-dedup.ts +131 -0
- package/src/vite/plugins/client-ref-hashing.ts +117 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +816 -0
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +336 -0
- package/src/vite/plugins/version-injector.ts +109 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +497 -0
- package/src/vite/router-discovery.ts +1423 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +161 -0
- package/src/vite/utils/prerender-utils.ts +222 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/vite/expose-prerender-handler-id.ts +0 -429
- package/src/vite/package-resolution.ts +0 -125
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -8,6 +8,9 @@ argument-hint: [@slot-name] [route-to-intercept]
|
|
|
8
8
|
|
|
9
9
|
Intercept routes render a different component during soft navigation (client-side) while preserving the background route. Hard navigation (direct URL) shows the full page.
|
|
10
10
|
|
|
11
|
+
Canonical semantics reference:
|
|
12
|
+
[docs/execution-model.md](../../docs/internal/execution-model.md)
|
|
13
|
+
|
|
11
14
|
## Basic Intercept
|
|
12
15
|
|
|
13
16
|
```typescript
|
|
@@ -45,11 +48,11 @@ export const urlpatterns = urls(({ path, layout, intercept, loader }) => [
|
|
|
45
48
|
|
|
46
49
|
## Navigation Behavior
|
|
47
50
|
|
|
48
|
-
| Navigation Type
|
|
49
|
-
|
|
51
|
+
| Navigation Type | What Renders |
|
|
52
|
+
| ------------------------------ | ---------------------------------------------------- |
|
|
50
53
|
| Click link `/shop/product/abc` | `<ProductModal />` in `@modal`, background preserved |
|
|
51
|
-
| Direct URL `/shop/product/abc` | Full `<ProductPage />` page
|
|
52
|
-
| Browser back
|
|
54
|
+
| Direct URL `/shop/product/abc` | Full `<ProductPage />` page |
|
|
55
|
+
| Browser back | Close modal, restore previous state |
|
|
53
56
|
|
|
54
57
|
## Intercept with Layout
|
|
55
58
|
|
|
@@ -68,6 +71,78 @@ intercept(
|
|
|
68
71
|
)
|
|
69
72
|
```
|
|
70
73
|
|
|
74
|
+
## Intercept Middleware
|
|
75
|
+
|
|
76
|
+
Intercepts support their own middleware chain via the use callback. The full chain for an intercept request is:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
global mw (router.use) -> route mw (urls middleware()) -> intercept mw -> intercept handler -> intercept loaders
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
intercept(
|
|
84
|
+
"@modal",
|
|
85
|
+
"product",
|
|
86
|
+
<ProductModal />,
|
|
87
|
+
() => [
|
|
88
|
+
middleware(async (ctx, next) => {
|
|
89
|
+
// Runs only for this intercept, after global and route middleware
|
|
90
|
+
ctx.set("interceptSource", "modal");
|
|
91
|
+
await next();
|
|
92
|
+
}),
|
|
93
|
+
loader(ProductLoader),
|
|
94
|
+
]
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The intercept handler can read context variables set by all upstream middleware layers (global, route, and intercept-specific).
|
|
99
|
+
|
|
100
|
+
Handler/layout `ctx.set()` data follows the same rule as elsewhere:
|
|
101
|
+
intercepts see data produced in the current render pass, but partial
|
|
102
|
+
action revalidation only recomputes segments that actually revalidate.
|
|
103
|
+
If an intercept depends on data established by an outer layout/handler,
|
|
104
|
+
revalidate that outer segment too or reload/guard the data inside the
|
|
105
|
+
intercept.
|
|
106
|
+
|
|
107
|
+
### Revalidation Contracts for Intercept Dependencies
|
|
108
|
+
|
|
109
|
+
Use named revalidation contracts on both the outer producer and the intercept
|
|
110
|
+
consumer when they share `ctx.set()` data:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
export const revalidateProductShell = ({ actionId }) =>
|
|
114
|
+
actionId?.includes("src/actions/product.ts#") ?? false;
|
|
115
|
+
|
|
116
|
+
layout(ProductLayout, () => [
|
|
117
|
+
revalidate(revalidateProductShell), // producer reruns
|
|
118
|
+
intercept("@modal", "product", <ProductModal />, () => [
|
|
119
|
+
revalidate(revalidateProductShell), // consumer reruns
|
|
120
|
+
loader(ProductLoader),
|
|
121
|
+
]),
|
|
122
|
+
]);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Compose multiple contracts if the intercept depends on multiple upstream
|
|
126
|
+
domains.
|
|
127
|
+
|
|
128
|
+
Helper handoff style keeps intercept trees terse:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { revalidate } from "@rangojs/router";
|
|
132
|
+
|
|
133
|
+
export const revalidateProduct = () => [
|
|
134
|
+
revalidate(revalidateProductShell),
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
layout(ProductLayout, () => [
|
|
138
|
+
revalidateProduct(),
|
|
139
|
+
intercept("@modal", "product", <ProductModal />, () => [
|
|
140
|
+
revalidateProduct(),
|
|
141
|
+
loader(ProductLoader),
|
|
142
|
+
]),
|
|
143
|
+
]);
|
|
144
|
+
```
|
|
145
|
+
|
|
71
146
|
## Conditional Intercept with when()
|
|
72
147
|
|
|
73
148
|
Only intercept based on navigation context:
|
|
@@ -106,15 +181,15 @@ Use navigation to close:
|
|
|
106
181
|
|
|
107
182
|
```typescript
|
|
108
183
|
"use client";
|
|
109
|
-
import {
|
|
184
|
+
import { useRouter } from "@rangojs/router/client";
|
|
110
185
|
|
|
111
186
|
function ModalWrapper({ children }) {
|
|
112
|
-
const
|
|
187
|
+
const router = useRouter();
|
|
113
188
|
|
|
114
189
|
return (
|
|
115
|
-
<div className="modal-overlay" onClick={
|
|
190
|
+
<div className="modal-overlay" onClick={() => router.back()}>
|
|
116
191
|
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
|
117
|
-
<button onClick={
|
|
192
|
+
<button onClick={() => router.back()}>Close</button>
|
|
118
193
|
{children}
|
|
119
194
|
</div>
|
|
120
195
|
</div>
|
|
@@ -122,6 +197,79 @@ function ModalWrapper({ children }) {
|
|
|
122
197
|
}
|
|
123
198
|
```
|
|
124
199
|
|
|
200
|
+
## Interaction with View Transitions
|
|
201
|
+
|
|
202
|
+
A layout that owns the `@modal` slot can also configure `transition()` for page
|
|
203
|
+
fades — opening a modal does **not** fire the layout's view transition. Rango
|
|
204
|
+
narrows the layout's `<ViewTransition>` wrap to the layout's default outlet
|
|
205
|
+
content, so `<ParallelOutlet />` (the slot where the modal mounts) is a sibling
|
|
206
|
+
of the wrap, not inside its subtree. Form actions submitted from inside an open
|
|
207
|
+
modal also commit without firing the underlying layout's transition, and the
|
|
208
|
+
modal subtree identity is preserved across revalidation (no remount,
|
|
209
|
+
`useActionState` survives). Closing the modal restores the page without a
|
|
210
|
+
stray transition.
|
|
211
|
+
|
|
212
|
+
For a modal-only morph (e.g. when intercepted URLs change while the modal
|
|
213
|
+
stays open), use an element-level React `<ViewTransition>` inside the modal
|
|
214
|
+
component — `transition()` accepted on `intercept()` via the DSL is not
|
|
215
|
+
applied to slot rendering today.
|
|
216
|
+
|
|
217
|
+
Caveat: route-level `transition()` wraps the route component itself, so a
|
|
218
|
+
`<ParallelOutlet />` rendered directly inside that route component would still
|
|
219
|
+
be inside the route's VT subtree. Mount the slot in a layout instead when you
|
|
220
|
+
combine intercept modals with route-level transitions.
|
|
221
|
+
|
|
222
|
+
See [skills/view-transitions](../view-transitions/SKILL.md) for the full
|
|
223
|
+
contract and direction-aware examples.
|
|
224
|
+
|
|
225
|
+
## Interaction with Prerender
|
|
226
|
+
|
|
227
|
+
When the target route of an intercept uses `Prerender`, the intercept handler is
|
|
228
|
+
also resolved at build time and stored alongside the main pre-rendered segments.
|
|
229
|
+
This means intercept navigations to pre-rendered routes are served from the
|
|
230
|
+
prerender store without executing handler code at runtime.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// The detail route is pre-rendered
|
|
234
|
+
export const ProductDetail = Prerender(
|
|
235
|
+
async () => [{ slug: "shoes" }, { slug: "jacket" }],
|
|
236
|
+
async (ctx) => <ProductPage slug={ctx.params.slug} />,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// urls.tsx
|
|
240
|
+
layout(ShopLayout, () => [
|
|
241
|
+
path("/:slug", ProductDetail, { name: "detail" }, () => [
|
|
242
|
+
loader(ProductLoader),
|
|
243
|
+
]),
|
|
244
|
+
|
|
245
|
+
// This intercept is also pre-rendered at build time
|
|
246
|
+
intercept("@modal", ".detail", <ProductModal />, () => [
|
|
247
|
+
when(({ from }) => from.pathname.startsWith("/shop")),
|
|
248
|
+
loader(ProductLoader),
|
|
249
|
+
]),
|
|
250
|
+
])
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Build-time behavior:
|
|
254
|
+
|
|
255
|
+
- The intercept handler (`<ProductModal />`) is resolved with BuildContext
|
|
256
|
+
- Result is stored under the key `"detail/paramHash/i"` (intercept variant)
|
|
257
|
+
- `when()` conditions are skipped at build time (all intercepts pre-rendered unconditionally)
|
|
258
|
+
- `when()` is still evaluated at runtime by the intercept-resolution middleware
|
|
259
|
+
|
|
260
|
+
Runtime behavior:
|
|
261
|
+
|
|
262
|
+
- Intercept navigation: prerender store serves the `/i` variant (frozen handler + fresh loaders)
|
|
263
|
+
- Direct navigation: prerender store serves the main variant (full page)
|
|
264
|
+
- If no intercept prerender entry exists, falls through to live intercept resolution
|
|
265
|
+
|
|
266
|
+
Loaders inside the intercept always run fresh at request time, same as regular
|
|
267
|
+
pre-rendered routes.
|
|
268
|
+
|
|
269
|
+
During action-driven partial revalidation, this same partial rule applies:
|
|
270
|
+
refreshing the intercept does not implicitly rebuild non-revalidated outer
|
|
271
|
+
segments.
|
|
272
|
+
|
|
125
273
|
## Complete Example
|
|
126
274
|
|
|
127
275
|
```typescript
|
|
@@ -188,3 +336,23 @@ export const shopPatterns = urls(({
|
|
|
188
336
|
]),
|
|
189
337
|
]);
|
|
190
338
|
```
|
|
339
|
+
|
|
340
|
+
## Handler-attached `.use`
|
|
341
|
+
|
|
342
|
+
Intercept handlers can carry their own middleware, loaders, loading state, error/notFound boundaries, and even nested `layout`/`route`/`when` defaults via `.use` — useful for self-contained modal components that travel with their own data and chrome.
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
const QuickViewModal: Handler = async (ctx) => {
|
|
346
|
+
const product = await ctx.use(ProductLoader);
|
|
347
|
+
return <QuickView product={product} />;
|
|
348
|
+
};
|
|
349
|
+
QuickViewModal.use = () => [
|
|
350
|
+
loader(ProductLoader),
|
|
351
|
+
loading(<QuickViewSkeleton />),
|
|
352
|
+
layout(<ModalChrome />),
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
intercept("@modal", "product", QuickViewModal);
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Explicit `use()` at the mount site merges with `handler.use` (handler defaults first, explicit second). See [skills/handler-use](../handler-use/SKILL.md) for merge order and the per-mount-site allowed-types table.
|
package/skills/layout/SKILL.md
CHANGED
|
@@ -8,6 +8,9 @@ argument-hint: [component]
|
|
|
8
8
|
|
|
9
9
|
Layouts wrap child routes and persist during navigation within their scope.
|
|
10
10
|
|
|
11
|
+
Canonical semantics reference:
|
|
12
|
+
[docs/execution-model.md](../../docs/internal/execution-model.md)
|
|
13
|
+
|
|
11
14
|
## Basic Layout
|
|
12
15
|
|
|
13
16
|
```typescript
|
|
@@ -45,9 +48,7 @@ layout(<ShopLayout />, () => [
|
|
|
45
48
|
### Component Function
|
|
46
49
|
|
|
47
50
|
```typescript
|
|
48
|
-
layout(ShopLayout, () => [
|
|
49
|
-
path("/shop", ShopIndex, { name: "shop" }),
|
|
50
|
-
])
|
|
51
|
+
layout(ShopLayout, () => [path("/shop", ShopIndex, { name: "shop" })]);
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
### Handler with Context
|
|
@@ -117,6 +118,8 @@ function ShopLayout() {
|
|
|
117
118
|
}
|
|
118
119
|
```
|
|
119
120
|
|
|
121
|
+
A layout's `transition()` config wraps the content that flows through `<Outlet />` — not the layout chrome itself, and not sibling `<ParallelOutlet />` slots. Stacking transitions across nested layouts collapses around the deepest default outlet content. See [skills/view-transitions](../view-transitions/SKILL.md) for the full wrap rules and intercept-modal interaction.
|
|
122
|
+
|
|
120
123
|
## Named Outlets
|
|
121
124
|
|
|
122
125
|
For parallel routes, use named outlets:
|
|
@@ -141,6 +144,54 @@ function DashboardLayout() {
|
|
|
141
144
|
}
|
|
142
145
|
```
|
|
143
146
|
|
|
147
|
+
## Orphan Layout (inside route)
|
|
148
|
+
|
|
149
|
+
A layout as a child of `path()` wraps the route content and can read
|
|
150
|
+
data set by the route handler via `ctx.get()`. The handler always
|
|
151
|
+
executes before its children.
|
|
152
|
+
|
|
153
|
+
This handler-first guarantee applies to a single full render pass
|
|
154
|
+
(initial render, prerender, or full HTML re-render). During partial
|
|
155
|
+
action revalidation, only the segments that revalidate are recomputed.
|
|
156
|
+
If an orphan layout depends on data established by an outer handler or
|
|
157
|
+
layout, that outer segment must also revalidate, or the orphan must
|
|
158
|
+
guard/reload the data independently.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
162
|
+
|
|
163
|
+
urls(({ path, layout, parallel }) => [
|
|
164
|
+
path("/product/:slug", (ctx) => {
|
|
165
|
+
const product = await fetchProduct(ctx.params.slug);
|
|
166
|
+
ctx.set("product", product);
|
|
167
|
+
return <ProductPage product={product} />;
|
|
168
|
+
}, { name: "product" }, () => [
|
|
169
|
+
layout((ctx) => {
|
|
170
|
+
const product = ctx.get("product");
|
|
171
|
+
return (
|
|
172
|
+
<div>
|
|
173
|
+
<Breadcrumb name={product?.name} />
|
|
174
|
+
<Outlet />
|
|
175
|
+
<ParallelOutlet name="@related" />
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}, () => [
|
|
179
|
+
parallel({
|
|
180
|
+
"@related": (ctx) => {
|
|
181
|
+
const product = ctx.get("product");
|
|
182
|
+
return <RelatedProducts category={product?.category} />;
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
]),
|
|
186
|
+
]),
|
|
187
|
+
])
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Orphan layouts can call `ctx.get()` to read data set by their parent
|
|
191
|
+
handler. They can also call `ctx.set()`, though the primary pattern is
|
|
192
|
+
for route handlers and middleware to write context variables and for
|
|
193
|
+
orphan layouts to read them.
|
|
194
|
+
|
|
144
195
|
## Layout Revalidation
|
|
145
196
|
|
|
146
197
|
Layouts don't revalidate by default. Control with `revalidate()`:
|
|
@@ -161,6 +212,54 @@ layout(<CartLayout />, () => [
|
|
|
161
212
|
])
|
|
162
213
|
```
|
|
163
214
|
|
|
215
|
+
If child segments read data that was established by this layout or by a
|
|
216
|
+
route handler above them, revalidate the outer segment too. Partial
|
|
217
|
+
revalidation does not re-run non-revalidated ancestors just to rebuild
|
|
218
|
+
their `ctx.set()` state.
|
|
219
|
+
|
|
220
|
+
### Revalidation Contracts
|
|
221
|
+
|
|
222
|
+
For shared upstream data, define named revalidation functions and reuse
|
|
223
|
+
them on both producer and consumer segments:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// revalidation-contracts.ts
|
|
227
|
+
export const revalidateCartData = ({ actionId }) =>
|
|
228
|
+
actionId?.includes("src/actions/cart.ts#addToCart") ?? false;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
layout(<CartLayout />, () => [
|
|
233
|
+
revalidate(revalidateCartData), // producer
|
|
234
|
+
path("/cart", CartPage, { name: "cart" }, () => [
|
|
235
|
+
revalidate(revalidateCartData), // consumer
|
|
236
|
+
]),
|
|
237
|
+
]);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
If a segment depends on multiple upstream domains, compose multiple
|
|
241
|
+
contracts (`revalidateAuthData`, `revalidateCartData`, and so on).
|
|
242
|
+
|
|
243
|
+
You can also package them as importable handoff helpers:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// revalidation-contracts.ts
|
|
247
|
+
import { revalidate } from "@rangojs/router";
|
|
248
|
+
|
|
249
|
+
export const revalidateAuthData = ({ actionId }) =>
|
|
250
|
+
actionId?.includes("src/actions/auth.ts#") ?? false;
|
|
251
|
+
export const revalidateAuth = () => [revalidate(revalidateAuthData)];
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
layout(<ShellLayout />, () => [
|
|
256
|
+
revalidateAuth(),
|
|
257
|
+
path("/account", AccountPage, { name: "account" }, () => [
|
|
258
|
+
revalidateAuth(),
|
|
259
|
+
]),
|
|
260
|
+
]);
|
|
261
|
+
```
|
|
262
|
+
|
|
164
263
|
## Complete Example
|
|
165
264
|
|
|
166
265
|
```typescript
|
|
@@ -211,3 +310,25 @@ export const shopPatterns = urls(({ path, layout, parallel, loader, revalidate }
|
|
|
211
310
|
]),
|
|
212
311
|
]);
|
|
213
312
|
```
|
|
313
|
+
|
|
314
|
+
## Handler-attached `.use`
|
|
315
|
+
|
|
316
|
+
Layout handlers can carry their own middleware, default parallels, and includes via `.use` so a layout becomes a self-contained unit reusable across mount sites.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
const AdminLayout: Handler = (ctx) => {
|
|
320
|
+
const user = ctx.get(CurrentUser);
|
|
321
|
+
return <Admin user={user} />;
|
|
322
|
+
};
|
|
323
|
+
AdminLayout.use = () => [
|
|
324
|
+
middleware(requireAdmin),
|
|
325
|
+
parallel({ "@adminNotifs": AdminNotifsSlot }),
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
// Mount site declares structure only; defaults travel with the layout.
|
|
329
|
+
layout(AdminLayout, () => [
|
|
330
|
+
path("/admin", AdminIndex, { name: "admin.index" }),
|
|
331
|
+
]);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Allowed item types in a layout's `.use` mirror the layout `use()` callback (the broadest set). Explicit `use()` at the mount site merges with `handler.use` (handler defaults first, explicit second). See [skills/handler-use](../handler-use/SKILL.md) for merge order and per-mount-site allowed types.
|