@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.30
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 +5 -0
- package/README.md +883 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4655 -747
- package/package.json +78 -50
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +54 -25
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +23 -21
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +390 -63
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +133 -10
- package/skills/layout/SKILL.md +102 -5
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +366 -29
- package/skills/middleware/SKILL.md +173 -36
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +80 -3
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +86 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +227 -14
- package/skills/router-setup/SKILL.md +225 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +12 -11
- package/skills/typesafety/SKILL.md +401 -75
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +10 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +20 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +201 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +267 -317
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +173 -73
- package/src/browser/react/NavigationProvider.tsx +138 -27
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +37 -0
- 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 +49 -65
- package/src/browser/react/use-href.tsx +20 -188
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +27 -78
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +111 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +504 -584
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +92 -57
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +469 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -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 +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +10 -15
- package/src/client.tsx +114 -135
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +34 -19
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +135 -49
- package/src/index.rsc.ts +182 -17
- package/src/index.ts +238 -24
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +27 -142
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +9 -11
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1388
- package/src/route-map-builder.ts +241 -112
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +70 -9
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +371 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +155 -32
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -29
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +321 -30
- package/src/router/prerender-match.ts +400 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1241 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +77 -3
- package/src/router.ts +688 -3656
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +786 -760
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +5 -25
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +40 -14
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +57 -61
- package/src/server/context.ts +202 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +422 -70
- package/src/server.ts +36 -120
- package/src/ssr/index.tsx +157 -26
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1577
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -726
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -782
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -3
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -357
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -1,69 +1,71 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
3
|
+
import React, {
|
|
4
|
+
forwardRef,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
type ForwardRefExoticComponent,
|
|
10
|
+
type RefAttributes,
|
|
11
|
+
} from "react";
|
|
4
12
|
import { NavigationStoreContext } from "./context.js";
|
|
5
13
|
import { LinkContext } from "./use-link-status.js";
|
|
6
14
|
import type { NavigateOptions } from "../types.js";
|
|
15
|
+
import { isHashOnlyNavigation } from "../link-interceptor.js";
|
|
7
16
|
import {
|
|
8
|
-
type LocationStateEntry,
|
|
9
17
|
isLocationStateEntry,
|
|
18
|
+
type LocationStateEntry,
|
|
10
19
|
resolveLocationStateEntries,
|
|
11
20
|
} from "./location-state.js";
|
|
12
21
|
|
|
13
22
|
/**
|
|
14
|
-
* State
|
|
23
|
+
* State prop type for Link component.
|
|
24
|
+
* - LocationStateEntry[]: Type-safe state entries via createLocationState()
|
|
25
|
+
* - StateOrGetter: Plain state object or click-time getter function
|
|
26
|
+
* - Record<string, unknown>: Plain state object passed to history.pushState
|
|
15
27
|
*/
|
|
16
28
|
export type StateOrGetter<T = unknown> = T | (() => T);
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* - StateOrGetter: Legacy format for backwards compatibility
|
|
22
|
-
*/
|
|
23
|
-
export type LinkState = LocationStateEntry[] | StateOrGetter;
|
|
24
|
-
|
|
25
|
-
// Track prefetched URLs to avoid duplicate <link> elements
|
|
26
|
-
const prefetchedUrls = new Set<string>();
|
|
30
|
+
export type LinkState =
|
|
31
|
+
| LocationStateEntry[]
|
|
32
|
+
| StateOrGetter<Record<string, unknown>>;
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (prefetchedUrls.has(url)) return;
|
|
34
|
-
prefetchedUrls.add(url);
|
|
35
|
-
|
|
36
|
-
// Build RSC partial URL with segment IDs
|
|
37
|
-
const targetUrl = new URL(url, window.location.origin);
|
|
38
|
-
targetUrl.searchParams.set("_rsc_partial", "true");
|
|
39
|
-
if (segmentIds.length > 0) {
|
|
40
|
-
targetUrl.searchParams.set("_rsc_segments", segmentIds.join(","));
|
|
41
|
-
}
|
|
34
|
+
import { prefetchDirect, prefetchQueued } from "../prefetch/fetch.js";
|
|
35
|
+
import {
|
|
36
|
+
observeForPrefetch,
|
|
37
|
+
unobserveForPrefetch,
|
|
38
|
+
} from "../prefetch/observer.js";
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
link.as = "fetch";
|
|
48
|
-
document.head.appendChild(link);
|
|
49
|
-
}
|
|
40
|
+
// Touch device detection for adaptive strategy.
|
|
41
|
+
// Checked once at module load (Link.tsx is "use client", runs only in browser).
|
|
42
|
+
const isTouchDevice =
|
|
43
|
+
typeof window !== "undefined" && window.matchMedia("(hover: none)").matches;
|
|
50
44
|
|
|
51
45
|
/**
|
|
52
46
|
* Prefetch strategy for the Link component
|
|
53
|
-
* - "hover": Prefetch on mouse enter (
|
|
54
|
-
* - "viewport": Prefetch when link enters viewport (
|
|
55
|
-
* - "
|
|
47
|
+
* - "hover": Prefetch on mouse enter (direct, no queue)
|
|
48
|
+
* - "viewport": Prefetch when link enters viewport (queued, waits for idle)
|
|
49
|
+
* - "render": Prefetch on component mount regardless of visibility (queued, waits for idle)
|
|
50
|
+
* - "adaptive": Hover on pointer devices, viewport on touch devices
|
|
56
51
|
* - "none": No prefetching (default)
|
|
57
52
|
*/
|
|
58
|
-
export type PrefetchStrategy =
|
|
53
|
+
export type PrefetchStrategy =
|
|
54
|
+
| "hover"
|
|
55
|
+
| "viewport"
|
|
56
|
+
| "render"
|
|
57
|
+
| "adaptive"
|
|
58
|
+
| "none";
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Link component props
|
|
62
62
|
*/
|
|
63
|
-
export interface LinkProps
|
|
64
|
-
|
|
63
|
+
export interface LinkProps extends Omit<
|
|
64
|
+
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
65
|
+
"href"
|
|
66
|
+
> {
|
|
65
67
|
/**
|
|
66
|
-
* The URL to navigate to (typically from router.
|
|
68
|
+
* The URL to navigate to (typically from router.reverse())
|
|
67
69
|
*/
|
|
68
70
|
to: string;
|
|
69
71
|
/**
|
|
@@ -90,16 +92,29 @@ export interface LinkProps
|
|
|
90
92
|
* @example
|
|
91
93
|
* ```tsx
|
|
92
94
|
* // Type-safe state with createLocationState (recommended)
|
|
93
|
-
* const ProductState = createLocationState
|
|
94
|
-
* <Link to="/product" state={[ProductState(product)]}>
|
|
95
|
+
* const ProductState = createLocationState<{ name: string; price: number }>();
|
|
96
|
+
* <Link to="/product" state={[ProductState({ name: product.name, price: product.price })]}>
|
|
97
|
+
* View
|
|
98
|
+
* </Link>
|
|
99
|
+
*
|
|
100
|
+
* // Type-safe just-in-time state (getter called at click time, not render time).
|
|
101
|
+
* // Must be in a client component -- getter can't cross the RSC boundary.
|
|
102
|
+
* <Link
|
|
103
|
+
* to="/product"
|
|
104
|
+
* state={[ProductState(() => ({ name: product.name, price: product.price }))]}
|
|
105
|
+
* >
|
|
106
|
+
* View
|
|
107
|
+
* </Link>
|
|
95
108
|
*
|
|
96
109
|
* // Multiple typed states
|
|
97
|
-
* <Link to="/checkout" state={[ProductState(p), CartState(c)]}>
|
|
110
|
+
* <Link to="/checkout" state={[ProductState({ name: p.name, price: p.price }), CartState(c)]}>
|
|
111
|
+
* Checkout
|
|
112
|
+
* </Link>
|
|
98
113
|
*
|
|
99
|
-
* //
|
|
114
|
+
* // Plain static state
|
|
100
115
|
* <Link to="/product" state={{ from: "list" }}>View</Link>
|
|
101
116
|
*
|
|
102
|
-
* //
|
|
117
|
+
* // Plain just-in-time state (called at click time, requires client component)
|
|
103
118
|
* <Link to="/product" state={() => ({ scrollY: window.scrollY })}>View</Link>
|
|
104
119
|
* ```
|
|
105
120
|
*/
|
|
@@ -134,9 +149,9 @@ function isExternalUrl(href: string): boolean {
|
|
|
134
149
|
/**
|
|
135
150
|
* Type-safe Link component for SPA navigation
|
|
136
151
|
*
|
|
137
|
-
* Works with router.
|
|
152
|
+
* Works with router.reverse() for type-safe URLs:
|
|
138
153
|
* ```tsx
|
|
139
|
-
* <Link to={router.
|
|
154
|
+
* <Link to={router.reverse("shop.products.detail", { slug: "my-product" })}>
|
|
140
155
|
* View Product
|
|
141
156
|
* </Link>
|
|
142
157
|
* ```
|
|
@@ -147,7 +162,9 @@ function isExternalUrl(href: string): boolean {
|
|
|
147
162
|
* <Link to="https://example.com">External</Link>
|
|
148
163
|
* ```
|
|
149
164
|
*/
|
|
150
|
-
export const Link: ForwardRefExoticComponent<
|
|
165
|
+
export const Link: ForwardRefExoticComponent<
|
|
166
|
+
LinkProps & RefAttributes<HTMLAnchorElement>
|
|
167
|
+
> = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
|
|
151
168
|
{
|
|
152
169
|
to,
|
|
153
170
|
replace = false,
|
|
@@ -159,11 +176,30 @@ export const Link: ForwardRefExoticComponent<LinkProps & RefAttributes<HTMLAncho
|
|
|
159
176
|
onClick,
|
|
160
177
|
...props
|
|
161
178
|
},
|
|
162
|
-
ref
|
|
179
|
+
ref,
|
|
163
180
|
) {
|
|
164
181
|
const ctx = useContext(NavigationStoreContext);
|
|
165
182
|
const isExternal = isExternalUrl(to);
|
|
166
183
|
|
|
184
|
+
// Resolve adaptive: viewport on touch devices, hover on pointer devices
|
|
185
|
+
const resolvedStrategy =
|
|
186
|
+
prefetch === "adaptive" ? (isTouchDevice ? "viewport" : "hover") : prefetch;
|
|
187
|
+
|
|
188
|
+
// Internal ref for viewport observation; merge with forwarded ref
|
|
189
|
+
const internalRef = useRef<HTMLAnchorElement | null>(null);
|
|
190
|
+
const setRef = useCallback(
|
|
191
|
+
(node: HTMLAnchorElement | null) => {
|
|
192
|
+
internalRef.current = node;
|
|
193
|
+
if (typeof ref === "function") {
|
|
194
|
+
ref(node);
|
|
195
|
+
} else if (ref) {
|
|
196
|
+
(ref as React.MutableRefObject<HTMLAnchorElement | null>).current =
|
|
197
|
+
node;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
[ref],
|
|
201
|
+
);
|
|
202
|
+
|
|
167
203
|
// Use ref to always get the latest state/getter without adding to useCallback deps
|
|
168
204
|
// This enables just-in-time state resolution without causing re-renders
|
|
169
205
|
const stateRef = useRef(state);
|
|
@@ -194,43 +230,109 @@ export const Link: ForwardRefExoticComponent<LinkProps & RefAttributes<HTMLAncho
|
|
|
194
230
|
const target = (e.currentTarget as HTMLAnchorElement).target;
|
|
195
231
|
if (target && target !== "_self") return;
|
|
196
232
|
|
|
233
|
+
// Hash-only navigation: let the browser handle anchor scrolling natively.
|
|
234
|
+
if (isHashOnlyNavigation(e.currentTarget as HTMLAnchorElement)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// No navigation context (outside provider): fall back to native navigation.
|
|
239
|
+
if (!ctx?.navigate) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
197
243
|
// Prevent default and use SPA navigation
|
|
198
244
|
e.preventDefault();
|
|
199
245
|
// Stop propagation to prevent link-interceptor from also handling this
|
|
200
246
|
e.stopPropagation();
|
|
201
247
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
let resolvedState: unknown;
|
|
205
|
-
const currentState = stateRef.current;
|
|
206
|
-
|
|
207
|
-
if (Array.isArray(currentState) && currentState.length > 0 && isLocationStateEntry(currentState[0])) {
|
|
208
|
-
// Type-safe LocationStateEntry[] - resolve each entry into keyed object
|
|
209
|
-
resolvedState = resolveLocationStateEntries(currentState as LocationStateEntry[]);
|
|
210
|
-
} else if (typeof currentState === "function") {
|
|
211
|
-
// Legacy getter function
|
|
212
|
-
resolvedState = currentState();
|
|
213
|
-
} else {
|
|
214
|
-
// Legacy static value
|
|
215
|
-
resolvedState = currentState;
|
|
216
|
-
}
|
|
248
|
+
const currentState = stateRef.current;
|
|
249
|
+
let resolvedState: unknown;
|
|
217
250
|
|
|
218
|
-
|
|
251
|
+
if (
|
|
252
|
+
Array.isArray(currentState) &&
|
|
253
|
+
currentState.length > 0 &&
|
|
254
|
+
isLocationStateEntry(currentState[0])
|
|
255
|
+
) {
|
|
256
|
+
resolvedState = resolveLocationStateEntries(
|
|
257
|
+
currentState as LocationStateEntry[],
|
|
258
|
+
);
|
|
259
|
+
} else if (typeof currentState === "function") {
|
|
260
|
+
resolvedState = currentState();
|
|
261
|
+
} else if (currentState != null) {
|
|
262
|
+
resolvedState = currentState;
|
|
219
263
|
}
|
|
264
|
+
|
|
265
|
+
ctx.navigate(to, { replace, scroll, state: resolvedState });
|
|
220
266
|
},
|
|
221
|
-
[to, isExternal, reloadDocument, replace, scroll, ctx, onClick]
|
|
267
|
+
[to, isExternal, reloadDocument, replace, scroll, ctx, onClick],
|
|
222
268
|
);
|
|
223
269
|
|
|
224
270
|
const handleMouseEnter = useCallback(() => {
|
|
225
|
-
if (
|
|
271
|
+
if (resolvedStrategy === "hover" && !isExternal && ctx?.store) {
|
|
272
|
+
const segmentState = ctx.store.getSegmentState();
|
|
273
|
+
prefetchDirect(to, segmentState.currentSegmentIds, ctx.version);
|
|
274
|
+
}
|
|
275
|
+
}, [resolvedStrategy, to, isExternal, ctx]);
|
|
276
|
+
|
|
277
|
+
// Viewport/render prefetch: waits for idle before starting,
|
|
278
|
+
// uses concurrency-limited queue to avoid flooding.
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
if (isExternal || !ctx?.store) return;
|
|
281
|
+
const isViewport = resolvedStrategy === "viewport";
|
|
282
|
+
const isRender = resolvedStrategy === "render";
|
|
283
|
+
if (!isViewport && !isRender) return;
|
|
284
|
+
|
|
285
|
+
let cancelled = false;
|
|
286
|
+
let unsubIdle: (() => void) | undefined;
|
|
287
|
+
let observedElement: Element | null = null;
|
|
288
|
+
|
|
289
|
+
const triggerPrefetch = () => {
|
|
290
|
+
if (cancelled) return;
|
|
226
291
|
const segmentState = ctx.store.getSegmentState();
|
|
227
|
-
|
|
292
|
+
prefetchQueued(to, segmentState.currentSegmentIds, ctx.version);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// Schedule prefetch only when the app is idle (no navigation/streaming).
|
|
296
|
+
// This avoids competing with hydration and active navigation fetches.
|
|
297
|
+
const scheduleWhenIdle = (callback: () => void) => {
|
|
298
|
+
const state = ctx.eventController.getState();
|
|
299
|
+
if (state.state === "idle" && !state.isStreaming) {
|
|
300
|
+
callback();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const unsub = ctx.eventController.subscribe(() => {
|
|
304
|
+
const s = ctx.eventController.getState();
|
|
305
|
+
if (s.state === "idle" && !s.isStreaming) {
|
|
306
|
+
unsub();
|
|
307
|
+
callback();
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
unsubIdle = unsub;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (isRender) {
|
|
314
|
+
scheduleWhenIdle(triggerPrefetch);
|
|
315
|
+
} else if (isViewport) {
|
|
316
|
+
const element = internalRef.current;
|
|
317
|
+
if (!element) return;
|
|
318
|
+
observedElement = element;
|
|
319
|
+
observeForPrefetch(element, () => {
|
|
320
|
+
scheduleWhenIdle(triggerPrefetch);
|
|
321
|
+
});
|
|
228
322
|
}
|
|
229
|
-
|
|
323
|
+
|
|
324
|
+
return () => {
|
|
325
|
+
cancelled = true;
|
|
326
|
+
unsubIdle?.();
|
|
327
|
+
if (isViewport && observedElement) {
|
|
328
|
+
unobserveForPrefetch(observedElement);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}, [resolvedStrategy, to, isExternal, ctx]);
|
|
230
332
|
|
|
231
333
|
return (
|
|
232
334
|
<a
|
|
233
|
-
ref={
|
|
335
|
+
ref={setRef}
|
|
234
336
|
href={to}
|
|
235
337
|
onClick={handleClick}
|
|
236
338
|
onMouseEnter={handleMouseEnter}
|
|
@@ -240,9 +342,7 @@ export const Link: ForwardRefExoticComponent<LinkProps & RefAttributes<HTMLAncho
|
|
|
240
342
|
data-replace={replace ? "true" : undefined}
|
|
241
343
|
{...props}
|
|
242
344
|
>
|
|
243
|
-
<LinkContext.Provider value={to}>
|
|
244
|
-
{children}
|
|
245
|
-
</LinkContext.Provider>
|
|
345
|
+
<LinkContext.Provider value={to}>{children}</LinkContext.Provider>
|
|
246
346
|
</a>
|
|
247
347
|
);
|
|
248
348
|
});
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./context.js";
|
|
15
15
|
import type {
|
|
16
16
|
NavigationStore,
|
|
17
|
-
|
|
17
|
+
NavigationUpdate,
|
|
18
18
|
NavigateOptions,
|
|
19
19
|
NavigationBridge,
|
|
20
20
|
} from "../types.js";
|
|
@@ -22,8 +22,9 @@ import type { EventController } from "../event-controller.js";
|
|
|
22
22
|
import { RootErrorBoundary } from "../../root-error-boundary.js";
|
|
23
23
|
import type { HandleData } from "../types.js";
|
|
24
24
|
import { ThemeProvider } from "../../theme/ThemeProvider.js";
|
|
25
|
+
import { NonceContext } from "./nonce-context.js";
|
|
25
26
|
import type { ResolvedThemeConfig, Theme } from "../../theme/types.js";
|
|
26
|
-
import {
|
|
27
|
+
import { cancelAllPrefetches } from "../prefetch/queue.js";
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Process handles from an async generator, updating the event controller
|
|
@@ -43,7 +44,7 @@ async function processHandles(
|
|
|
43
44
|
matched?: string[];
|
|
44
45
|
isPartial?: boolean;
|
|
45
46
|
historyKey: string;
|
|
46
|
-
}
|
|
47
|
+
},
|
|
47
48
|
): Promise<void> {
|
|
48
49
|
const { eventController, store, matched, isPartial, historyKey } = opts;
|
|
49
50
|
|
|
@@ -54,7 +55,7 @@ async function processHandles(
|
|
|
54
55
|
// the current route's breadcrumbs (e.g., quick popstate after clicking a link).
|
|
55
56
|
if (historyKey !== store.getHistoryKey()) {
|
|
56
57
|
console.log(
|
|
57
|
-
"[NavigationProvider] Stopping handle processing - user navigated away"
|
|
58
|
+
"[NavigationProvider] Stopping handle processing - user navigated away",
|
|
58
59
|
);
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
@@ -101,9 +102,9 @@ export interface NavigationProviderProps {
|
|
|
101
102
|
eventController: EventController;
|
|
102
103
|
|
|
103
104
|
/**
|
|
104
|
-
* Initial
|
|
105
|
+
* Initial rendered tree + metadata from server payload
|
|
105
106
|
*/
|
|
106
|
-
initialPayload:
|
|
107
|
+
initialPayload: NavigationUpdate;
|
|
107
108
|
|
|
108
109
|
/**
|
|
109
110
|
* Navigation bridge for handling navigation
|
|
@@ -121,6 +122,18 @@ export interface NavigationProviderProps {
|
|
|
121
122
|
* Only used when themeConfig is provided
|
|
122
123
|
*/
|
|
123
124
|
initialTheme?: Theme;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Whether connection warmup is enabled.
|
|
128
|
+
* When true, keeps TLS alive by sending HEAD requests after idle periods.
|
|
129
|
+
*/
|
|
130
|
+
warmupEnabled?: boolean;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* App version from server payload (stable, immutable).
|
|
134
|
+
* Forwarded to prefetch requests for version mismatch detection.
|
|
135
|
+
*/
|
|
136
|
+
version?: string;
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
/**
|
|
@@ -151,6 +164,8 @@ export function NavigationProvider({
|
|
|
151
164
|
bridge,
|
|
152
165
|
themeConfig,
|
|
153
166
|
initialTheme,
|
|
167
|
+
warmupEnabled,
|
|
168
|
+
version,
|
|
154
169
|
}: NavigationProviderProps): ReactNode {
|
|
155
170
|
// Track current payload for rendering (this triggers re-renders)
|
|
156
171
|
const [payload, setPayload] = useState(initialPayload);
|
|
@@ -162,7 +177,7 @@ export function NavigationProvider({
|
|
|
162
177
|
async (url: string, options?: NavigateOptions): Promise<void> => {
|
|
163
178
|
await bridge.navigate(url, options);
|
|
164
179
|
},
|
|
165
|
-
[]
|
|
180
|
+
[],
|
|
166
181
|
);
|
|
167
182
|
|
|
168
183
|
/**
|
|
@@ -179,10 +194,113 @@ export function NavigationProvider({
|
|
|
179
194
|
eventController,
|
|
180
195
|
navigate,
|
|
181
196
|
refresh,
|
|
197
|
+
version,
|
|
182
198
|
}),
|
|
183
|
-
[]
|
|
199
|
+
[],
|
|
184
200
|
);
|
|
185
201
|
|
|
202
|
+
// Connection warmup: keep TLS alive after idle periods.
|
|
203
|
+
// After 60s of no user interaction, marks connection as "cold".
|
|
204
|
+
// On next interaction or visibility change, sends a HEAD request to warm TLS
|
|
205
|
+
// before the user actually clicks a link.
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (!warmupEnabled) return;
|
|
208
|
+
|
|
209
|
+
const IDLE_TIMEOUT = 60_000;
|
|
210
|
+
const DEBOUNCE_DELAY = 150;
|
|
211
|
+
|
|
212
|
+
let idleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
213
|
+
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
214
|
+
let isCold = false;
|
|
215
|
+
let warmupListenersAttached = false;
|
|
216
|
+
|
|
217
|
+
function sendWarmup() {
|
|
218
|
+
isCold = false;
|
|
219
|
+
fetch("/?_rsc_warmup", { method: "HEAD" }).catch(() => {});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function triggerWarmup() {
|
|
223
|
+
if (!isCold) return;
|
|
224
|
+
clearTimeout(debounceTimer);
|
|
225
|
+
debounceTimer = setTimeout(() => {
|
|
226
|
+
sendWarmup();
|
|
227
|
+
detachWarmupListeners();
|
|
228
|
+
resetIdleTimer();
|
|
229
|
+
}, DEBOUNCE_DELAY);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function onVisibilityChange() {
|
|
233
|
+
if (document.visibilityState === "visible" && isCold) {
|
|
234
|
+
triggerWarmup();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function attachWarmupListeners() {
|
|
239
|
+
if (warmupListenersAttached) return;
|
|
240
|
+
warmupListenersAttached = true;
|
|
241
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
242
|
+
document.addEventListener("mousemove", triggerWarmup, { once: true });
|
|
243
|
+
document.addEventListener("touchstart", triggerWarmup, { once: true });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function detachWarmupListeners() {
|
|
247
|
+
warmupListenersAttached = false;
|
|
248
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
249
|
+
document.removeEventListener("mousemove", triggerWarmup);
|
|
250
|
+
document.removeEventListener("touchstart", triggerWarmup);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function markCold() {
|
|
254
|
+
isCold = true;
|
|
255
|
+
attachWarmupListeners();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function resetIdleTimer() {
|
|
259
|
+
clearTimeout(idleTimer);
|
|
260
|
+
isCold = false;
|
|
261
|
+
idleTimer = setTimeout(markCold, IDLE_TIMEOUT);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Activity events that reset the idle timer
|
|
265
|
+
const activityEvents = [
|
|
266
|
+
"mousemove",
|
|
267
|
+
"keydown",
|
|
268
|
+
"touchstart",
|
|
269
|
+
"scroll",
|
|
270
|
+
] as const;
|
|
271
|
+
const activityOptions: AddEventListenerOptions = { passive: true };
|
|
272
|
+
|
|
273
|
+
for (const event of activityEvents) {
|
|
274
|
+
document.addEventListener(event, resetIdleTimer, activityOptions);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
resetIdleTimer();
|
|
278
|
+
|
|
279
|
+
return () => {
|
|
280
|
+
clearTimeout(idleTimer);
|
|
281
|
+
clearTimeout(debounceTimer);
|
|
282
|
+
detachWarmupListeners();
|
|
283
|
+
for (const event of activityEvents) {
|
|
284
|
+
document.removeEventListener(event, resetIdleTimer);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}, [warmupEnabled]);
|
|
288
|
+
|
|
289
|
+
// Cancel speculative prefetches when navigation starts.
|
|
290
|
+
// Viewport/render prefetches should not compete with navigation fetches.
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
let wasIdle = true;
|
|
293
|
+
const unsub = eventController.subscribe(() => {
|
|
294
|
+
const state = eventController.getState();
|
|
295
|
+
const isIdle = state.state === "idle" && !state.isStreaming;
|
|
296
|
+
if (wasIdle && !isIdle) {
|
|
297
|
+
cancelAllPrefetches();
|
|
298
|
+
}
|
|
299
|
+
wasIdle = isIdle;
|
|
300
|
+
});
|
|
301
|
+
return unsub;
|
|
302
|
+
}, [eventController]);
|
|
303
|
+
|
|
186
304
|
// Subscribe to UI updates (for re-rendering the tree)
|
|
187
305
|
useEffect(() => {
|
|
188
306
|
const unsubscribe = store.onUpdate((update) => {
|
|
@@ -191,6 +309,9 @@ export function NavigationProvider({
|
|
|
191
309
|
metadata: update.metadata,
|
|
192
310
|
});
|
|
193
311
|
|
|
312
|
+
// Update route params
|
|
313
|
+
eventController.setParams(update.metadata.params ?? {});
|
|
314
|
+
|
|
194
315
|
// Update handle data progressively as it streams in
|
|
195
316
|
if (update.metadata.handles) {
|
|
196
317
|
// Capture historyKey now - by the time async processing completes,
|
|
@@ -204,7 +325,7 @@ export function NavigationProvider({
|
|
|
204
325
|
isPartial: update.metadata.isPartial,
|
|
205
326
|
historyKey,
|
|
206
327
|
}).catch((err) =>
|
|
207
|
-
console.error("[NavigationProvider] Error consuming handles:", err)
|
|
328
|
+
console.error("[NavigationProvider] Error consuming handles:", err),
|
|
208
329
|
);
|
|
209
330
|
} else if (update.metadata.cachedHandleData) {
|
|
210
331
|
// For back/forward navigation from cache, restore the cached handleData
|
|
@@ -212,14 +333,14 @@ export function NavigationProvider({
|
|
|
212
333
|
eventController.setHandleData(
|
|
213
334
|
update.metadata.cachedHandleData,
|
|
214
335
|
update.metadata.matched,
|
|
215
|
-
false // full replace - restore entire cached state
|
|
336
|
+
false, // full replace - restore entire cached state
|
|
216
337
|
);
|
|
217
338
|
} else if (update.metadata.matched) {
|
|
218
339
|
// For cached navigations without handleData, update segmentOrder to clean up stale data
|
|
219
340
|
eventController.setHandleData(
|
|
220
341
|
{}, // Empty data - all existing data not in matched will be cleaned up
|
|
221
342
|
update.metadata.matched,
|
|
222
|
-
true // partial update - will clean up segments not in matched
|
|
343
|
+
true, // partial update - will clean up segments not in matched
|
|
223
344
|
);
|
|
224
345
|
}
|
|
225
346
|
});
|
|
@@ -250,22 +371,12 @@ export function NavigationProvider({
|
|
|
250
371
|
);
|
|
251
372
|
}
|
|
252
373
|
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}, [payload.metadata?.routeMap, payload.metadata?.routeName]);
|
|
260
|
-
|
|
261
|
-
// Wrap with HrefContext when routeMap is available
|
|
262
|
-
if (hrefContextValue) {
|
|
263
|
-
content = (
|
|
264
|
-
<HrefContext.Provider value={hrefContextValue}>
|
|
265
|
-
{content}
|
|
266
|
-
</HrefContext.Provider>
|
|
267
|
-
);
|
|
268
|
-
}
|
|
374
|
+
// Match SSR tree shape: NonceContext.Provider is always present so
|
|
375
|
+
// hydration sees the same component tree. Value is undefined on the
|
|
376
|
+
// client — CSP nonces are a server-side HTML concern.
|
|
377
|
+
content = (
|
|
378
|
+
<NonceContext.Provider value={undefined}>{content}</NonceContext.Provider>
|
|
379
|
+
);
|
|
269
380
|
|
|
270
381
|
return (
|
|
271
382
|
<NavigationStoreContext.Provider value={contextValue}>
|
|
@@ -41,6 +41,12 @@ export interface NavigationStoreContextValue {
|
|
|
41
41
|
* @returns Promise that resolves when refresh is complete
|
|
42
42
|
*/
|
|
43
43
|
refresh: () => Promise<void>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* App version from server payload (stable, immutable).
|
|
47
|
+
* Used in prefetch requests for version mismatch detection.
|
|
48
|
+
*/
|
|
49
|
+
version: string | undefined;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter segment IDs to only include routes and layouts.
|
|
3
|
+
* Excludes parallels (contain .@) and loaders (contain D followed by digit).
|
|
4
|
+
*/
|
|
5
|
+
export function filterSegmentOrder(matched: string[]): string[] {
|
|
6
|
+
return matched.filter((id) => {
|
|
7
|
+
if (id.includes(".@")) return false;
|
|
8
|
+
if (/D\d+\./.test(id)) return false;
|
|
9
|
+
return true;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
// React exports for browser navigation
|
|
2
2
|
|
|
3
3
|
// Hook with Zustand-style selectors
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export { useNavigation } from "./use-navigation.js";
|
|
5
|
+
|
|
6
|
+
// Router actions hook (stable reference, no re-renders)
|
|
7
|
+
export { useRouter } from "./use-router.js";
|
|
8
|
+
|
|
9
|
+
// URL hooks
|
|
10
|
+
export { usePathname } from "./use-pathname.js";
|
|
11
|
+
export { useSearchParams } from "./use-search-params.js";
|
|
12
|
+
export { useParams } from "./use-params.js";
|
|
9
13
|
|
|
10
14
|
// Action state tracking hook
|
|
11
15
|
export { useAction, type TrackedActionState } from "./use-action.js";
|
|
12
16
|
|
|
13
17
|
// Segments state hook
|
|
14
|
-
export { useSegments,
|
|
18
|
+
export { useSegments, type SegmentsState } from "./use-segments.js";
|
|
15
19
|
|
|
16
20
|
// Handle data hook
|
|
17
|
-
export { useHandle
|
|
21
|
+
export { useHandle } from "./use-handle.js";
|
|
18
22
|
|
|
19
23
|
// Client cache controls hook
|
|
20
24
|
export {
|
|
@@ -35,11 +39,7 @@ export {
|
|
|
35
39
|
} from "./context.js";
|
|
36
40
|
|
|
37
41
|
// Link component
|
|
38
|
-
export {
|
|
39
|
-
Link,
|
|
40
|
-
type LinkProps,
|
|
41
|
-
type PrefetchStrategy,
|
|
42
|
-
} from "./Link.js";
|
|
42
|
+
export { Link, type LinkProps, type PrefetchStrategy } from "./Link.js";
|
|
43
43
|
|
|
44
44
|
// Link status hook
|
|
45
45
|
export { useLinkStatus, type LinkStatus } from "./use-link-status.js";
|