@depup/tanstack__react-router 1.166.4-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/cjs/Asset.cjs +177 -0
- package/dist/cjs/Asset.cjs.map +1 -0
- package/dist/cjs/Asset.d.cts +5 -0
- package/dist/cjs/CatchBoundary.cjs +114 -0
- package/dist/cjs/CatchBoundary.cjs.map +1 -0
- package/dist/cjs/CatchBoundary.d.cts +12 -0
- package/dist/cjs/ClientOnly.cjs +21 -0
- package/dist/cjs/ClientOnly.cjs.map +1 -0
- package/dist/cjs/ClientOnly.d.cts +49 -0
- package/dist/cjs/HeadContent.cjs +15 -0
- package/dist/cjs/HeadContent.cjs.map +1 -0
- package/dist/cjs/HeadContent.d.cts +6 -0
- package/dist/cjs/HeadContent.dev.cjs +41 -0
- package/dist/cjs/HeadContent.dev.cjs.map +1 -0
- package/dist/cjs/HeadContent.dev.d.cts +10 -0
- package/dist/cjs/Match.cjs +255 -0
- package/dist/cjs/Match.cjs.map +1 -0
- package/dist/cjs/Match.d.cts +14 -0
- package/dist/cjs/Matches.cjs +138 -0
- package/dist/cjs/Matches.cjs.map +1 -0
- package/dist/cjs/Matches.d.cts +68 -0
- package/dist/cjs/RouterProvider.cjs +32 -0
- package/dist/cjs/RouterProvider.cjs.map +1 -0
- package/dist/cjs/RouterProvider.d.cts +23 -0
- package/dist/cjs/SafeFragment.cjs +8 -0
- package/dist/cjs/SafeFragment.cjs.map +1 -0
- package/dist/cjs/SafeFragment.d.cts +1 -0
- package/dist/cjs/ScriptOnce.cjs +22 -0
- package/dist/cjs/ScriptOnce.cjs.map +1 -0
- package/dist/cjs/ScriptOnce.d.cts +6 -0
- package/dist/cjs/Scripts.cjs +56 -0
- package/dist/cjs/Scripts.cjs.map +1 -0
- package/dist/cjs/Scripts.d.cts +5 -0
- package/dist/cjs/ScrollRestoration.cjs +38 -0
- package/dist/cjs/ScrollRestoration.cjs.map +1 -0
- package/dist/cjs/ScrollRestoration.d.cts +14 -0
- package/dist/cjs/Transitioner.cjs +119 -0
- package/dist/cjs/Transitioner.cjs.map +1 -0
- package/dist/cjs/Transitioner.d.cts +1 -0
- package/dist/cjs/awaited.cjs +51 -0
- package/dist/cjs/awaited.cjs.map +1 -0
- package/dist/cjs/awaited.d.cts +14 -0
- package/dist/cjs/fileRoute.cjs +109 -0
- package/dist/cjs/fileRoute.cjs.map +1 -0
- package/dist/cjs/fileRoute.d.cts +87 -0
- package/dist/cjs/headContentUtils.cjs +185 -0
- package/dist/cjs/headContentUtils.cjs.map +1 -0
- package/dist/cjs/headContentUtils.d.cts +7 -0
- package/dist/cjs/history.d.cts +8 -0
- package/dist/cjs/index.cjs +241 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +51 -0
- package/dist/cjs/index.dev.cjs +241 -0
- package/dist/cjs/index.dev.cjs.map +1 -0
- package/dist/cjs/index.dev.d.cts +2 -0
- package/dist/cjs/lazyRouteComponent.cjs +70 -0
- package/dist/cjs/lazyRouteComponent.cjs.map +1 -0
- package/dist/cjs/lazyRouteComponent.d.cts +11 -0
- package/dist/cjs/link.cjs +573 -0
- package/dist/cjs/link.cjs.map +1 -0
- package/dist/cjs/link.d.cts +98 -0
- package/dist/cjs/matchContext.cjs +27 -0
- package/dist/cjs/matchContext.cjs.map +1 -0
- package/dist/cjs/matchContext.d.cts +3 -0
- package/dist/cjs/not-found.cjs +38 -0
- package/dist/cjs/not-found.cjs.map +1 -0
- package/dist/cjs/not-found.d.cts +9 -0
- package/dist/cjs/renderRouteNotFound.cjs +22 -0
- package/dist/cjs/renderRouteNotFound.cjs.map +1 -0
- package/dist/cjs/renderRouteNotFound.d.cts +10 -0
- package/dist/cjs/route.cjs +198 -0
- package/dist/cjs/route.cjs.map +1 -0
- package/dist/cjs/route.d.cts +142 -0
- package/dist/cjs/router.cjs +22 -0
- package/dist/cjs/router.cjs.map +1 -0
- package/dist/cjs/router.d.cts +83 -0
- package/dist/cjs/routerContext.cjs +23 -0
- package/dist/cjs/routerContext.cjs.map +1 -0
- package/dist/cjs/routerContext.d.cts +3 -0
- package/dist/cjs/scroll-restoration.cjs +39 -0
- package/dist/cjs/scroll-restoration.cjs.map +1 -0
- package/dist/cjs/scroll-restoration.d.cts +1 -0
- package/dist/cjs/ssr/RouterClient.cjs +25 -0
- package/dist/cjs/ssr/RouterClient.cjs.map +1 -0
- package/dist/cjs/ssr/RouterClient.d.cts +4 -0
- package/dist/cjs/ssr/RouterServer.cjs +9 -0
- package/dist/cjs/ssr/RouterServer.cjs.map +1 -0
- package/dist/cjs/ssr/RouterServer.d.cts +4 -0
- package/dist/cjs/ssr/client.cjs +12 -0
- package/dist/cjs/ssr/client.cjs.map +1 -0
- package/dist/cjs/ssr/client.d.cts +2 -0
- package/dist/cjs/ssr/defaultRenderHandler.cjs +15 -0
- package/dist/cjs/ssr/defaultRenderHandler.cjs.map +1 -0
- package/dist/cjs/ssr/defaultRenderHandler.d.cts +1 -0
- package/dist/cjs/ssr/defaultStreamHandler.cjs +16 -0
- package/dist/cjs/ssr/defaultStreamHandler.cjs.map +1 -0
- package/dist/cjs/ssr/defaultStreamHandler.d.cts +1 -0
- package/dist/cjs/ssr/renderRouterToStream.cjs +73 -0
- package/dist/cjs/ssr/renderRouterToStream.cjs.map +1 -0
- package/dist/cjs/ssr/renderRouterToStream.d.cts +8 -0
- package/dist/cjs/ssr/renderRouterToString.cjs +31 -0
- package/dist/cjs/ssr/renderRouterToString.cjs.map +1 -0
- package/dist/cjs/ssr/renderRouterToString.d.cts +7 -0
- package/dist/cjs/ssr/serializer.d.cts +6 -0
- package/dist/cjs/ssr/server.cjs +20 -0
- package/dist/cjs/ssr/server.cjs.map +1 -0
- package/dist/cjs/ssr/server.d.cts +6 -0
- package/dist/cjs/structuralSharing.d.cts +8 -0
- package/dist/cjs/typePrimitives.d.cts +16 -0
- package/dist/cjs/useBlocker.cjs +171 -0
- package/dist/cjs/useBlocker.cjs.map +1 -0
- package/dist/cjs/useBlocker.d.cts +66 -0
- package/dist/cjs/useCanGoBack.cjs +8 -0
- package/dist/cjs/useCanGoBack.cjs.map +1 -0
- package/dist/cjs/useCanGoBack.d.cts +1 -0
- package/dist/cjs/useLoaderData.cjs +15 -0
- package/dist/cjs/useLoaderData.cjs.map +1 -0
- package/dist/cjs/useLoaderData.d.cts +19 -0
- package/dist/cjs/useLoaderDeps.cjs +14 -0
- package/dist/cjs/useLoaderDeps.cjs.map +1 -0
- package/dist/cjs/useLoaderDeps.d.cts +19 -0
- package/dist/cjs/useLocation.cjs +10 -0
- package/dist/cjs/useLocation.cjs.map +1 -0
- package/dist/cjs/useLocation.d.cts +18 -0
- package/dist/cjs/useMatch.cjs +47 -0
- package/dist/cjs/useMatch.cjs.map +1 -0
- package/dist/cjs/useMatch.d.cts +14 -0
- package/dist/cjs/useNavigate.cjs +49 -0
- package/dist/cjs/useNavigate.cjs.map +1 -0
- package/dist/cjs/useNavigate.d.cts +28 -0
- package/dist/cjs/useParams.cjs +17 -0
- package/dist/cjs/useParams.cjs.map +1 -0
- package/dist/cjs/useParams.d.cts +21 -0
- package/dist/cjs/useRouteContext.cjs +11 -0
- package/dist/cjs/useRouteContext.cjs.map +1 -0
- package/dist/cjs/useRouteContext.d.cts +3 -0
- package/dist/cjs/useRouter.cjs +32 -0
- package/dist/cjs/useRouter.cjs.map +1 -0
- package/dist/cjs/useRouter.d.cts +14 -0
- package/dist/cjs/useRouterState.cjs +38 -0
- package/dist/cjs/useRouterState.cjs.map +1 -0
- package/dist/cjs/useRouterState.d.cts +20 -0
- package/dist/cjs/useSearch.cjs +16 -0
- package/dist/cjs/useSearch.cjs.map +1 -0
- package/dist/cjs/useSearch.d.cts +21 -0
- package/dist/cjs/utils.cjs +62 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +54 -0
- package/dist/esm/Asset.d.ts +5 -0
- package/dist/esm/Asset.js +160 -0
- package/dist/esm/Asset.js.map +1 -0
- package/dist/esm/CatchBoundary.d.ts +12 -0
- package/dist/esm/CatchBoundary.js +97 -0
- package/dist/esm/CatchBoundary.js.map +1 -0
- package/dist/esm/ClientOnly.d.ts +49 -0
- package/dist/esm/ClientOnly.js +21 -0
- package/dist/esm/ClientOnly.js.map +1 -0
- package/dist/esm/HeadContent.d.ts +6 -0
- package/dist/esm/HeadContent.dev.d.ts +10 -0
- package/dist/esm/HeadContent.dev.js +25 -0
- package/dist/esm/HeadContent.dev.js.map +1 -0
- package/dist/esm/HeadContent.js +15 -0
- package/dist/esm/HeadContent.js.map +1 -0
- package/dist/esm/Match.d.ts +14 -0
- package/dist/esm/Match.js +238 -0
- package/dist/esm/Match.js.map +1 -0
- package/dist/esm/Matches.d.ts +68 -0
- package/dist/esm/Matches.js +121 -0
- package/dist/esm/Matches.js.map +1 -0
- package/dist/esm/RouterProvider.d.ts +23 -0
- package/dist/esm/RouterProvider.js +32 -0
- package/dist/esm/RouterProvider.js.map +1 -0
- package/dist/esm/SafeFragment.d.ts +1 -0
- package/dist/esm/SafeFragment.js +8 -0
- package/dist/esm/SafeFragment.js.map +1 -0
- package/dist/esm/ScriptOnce.d.ts +6 -0
- package/dist/esm/ScriptOnce.js +22 -0
- package/dist/esm/ScriptOnce.js.map +1 -0
- package/dist/esm/Scripts.d.ts +5 -0
- package/dist/esm/Scripts.js +56 -0
- package/dist/esm/Scripts.js.map +1 -0
- package/dist/esm/ScrollRestoration.d.ts +14 -0
- package/dist/esm/ScrollRestoration.js +38 -0
- package/dist/esm/ScrollRestoration.js.map +1 -0
- package/dist/esm/Transitioner.d.ts +1 -0
- package/dist/esm/Transitioner.js +102 -0
- package/dist/esm/Transitioner.js.map +1 -0
- package/dist/esm/awaited.d.ts +14 -0
- package/dist/esm/awaited.js +34 -0
- package/dist/esm/awaited.js.map +1 -0
- package/dist/esm/fileRoute.d.ts +87 -0
- package/dist/esm/fileRoute.js +109 -0
- package/dist/esm/fileRoute.js.map +1 -0
- package/dist/esm/headContentUtils.d.ts +7 -0
- package/dist/esm/headContentUtils.js +168 -0
- package/dist/esm/headContentUtils.js.map +1 -0
- package/dist/esm/history.d.ts +8 -0
- package/dist/esm/index.d.ts +51 -0
- package/dist/esm/index.dev.d.ts +2 -0
- package/dist/esm/index.dev.js +133 -0
- package/dist/esm/index.dev.js.map +1 -0
- package/dist/esm/index.js +133 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lazyRouteComponent.d.ts +11 -0
- package/dist/esm/lazyRouteComponent.js +53 -0
- package/dist/esm/lazyRouteComponent.js.map +1 -0
- package/dist/esm/link.d.ts +98 -0
- package/dist/esm/link.js +556 -0
- package/dist/esm/link.js.map +1 -0
- package/dist/esm/matchContext.d.ts +3 -0
- package/dist/esm/matchContext.js +10 -0
- package/dist/esm/matchContext.js.map +1 -0
- package/dist/esm/not-found.d.ts +9 -0
- package/dist/esm/not-found.js +38 -0
- package/dist/esm/not-found.js.map +1 -0
- package/dist/esm/renderRouteNotFound.d.ts +10 -0
- package/dist/esm/renderRouteNotFound.js +22 -0
- package/dist/esm/renderRouteNotFound.js.map +1 -0
- package/dist/esm/route.d.ts +142 -0
- package/dist/esm/route.js +198 -0
- package/dist/esm/route.js.map +1 -0
- package/dist/esm/router.d.ts +83 -0
- package/dist/esm/router.js +22 -0
- package/dist/esm/router.js.map +1 -0
- package/dist/esm/routerContext.d.ts +3 -0
- package/dist/esm/routerContext.js +6 -0
- package/dist/esm/routerContext.js.map +1 -0
- package/dist/esm/scroll-restoration.d.ts +1 -0
- package/dist/esm/scroll-restoration.js +39 -0
- package/dist/esm/scroll-restoration.js.map +1 -0
- package/dist/esm/ssr/RouterClient.d.ts +4 -0
- package/dist/esm/ssr/RouterClient.js +25 -0
- package/dist/esm/ssr/RouterClient.js.map +1 -0
- package/dist/esm/ssr/RouterServer.d.ts +4 -0
- package/dist/esm/ssr/RouterServer.js +9 -0
- package/dist/esm/ssr/RouterServer.js.map +1 -0
- package/dist/esm/ssr/client.d.ts +2 -0
- package/dist/esm/ssr/client.js +6 -0
- package/dist/esm/ssr/client.js.map +1 -0
- package/dist/esm/ssr/defaultRenderHandler.d.ts +1 -0
- package/dist/esm/ssr/defaultRenderHandler.js +15 -0
- package/dist/esm/ssr/defaultRenderHandler.js.map +1 -0
- package/dist/esm/ssr/defaultStreamHandler.d.ts +1 -0
- package/dist/esm/ssr/defaultStreamHandler.js +16 -0
- package/dist/esm/ssr/defaultStreamHandler.js.map +1 -0
- package/dist/esm/ssr/renderRouterToStream.d.ts +8 -0
- package/dist/esm/ssr/renderRouterToStream.js +73 -0
- package/dist/esm/ssr/renderRouterToStream.js.map +1 -0
- package/dist/esm/ssr/renderRouterToString.d.ts +7 -0
- package/dist/esm/ssr/renderRouterToString.js +31 -0
- package/dist/esm/ssr/renderRouterToString.js.map +1 -0
- package/dist/esm/ssr/serializer.d.ts +6 -0
- package/dist/esm/ssr/server.d.ts +6 -0
- package/dist/esm/ssr/server.js +14 -0
- package/dist/esm/ssr/server.js.map +1 -0
- package/dist/esm/structuralSharing.d.ts +8 -0
- package/dist/esm/typePrimitives.d.ts +16 -0
- package/dist/esm/useBlocker.d.ts +66 -0
- package/dist/esm/useBlocker.js +154 -0
- package/dist/esm/useBlocker.js.map +1 -0
- package/dist/esm/useCanGoBack.d.ts +1 -0
- package/dist/esm/useCanGoBack.js +8 -0
- package/dist/esm/useCanGoBack.js.map +1 -0
- package/dist/esm/useLoaderData.d.ts +19 -0
- package/dist/esm/useLoaderData.js +15 -0
- package/dist/esm/useLoaderData.js.map +1 -0
- package/dist/esm/useLoaderDeps.d.ts +19 -0
- package/dist/esm/useLoaderDeps.js +14 -0
- package/dist/esm/useLoaderDeps.js.map +1 -0
- package/dist/esm/useLocation.d.ts +18 -0
- package/dist/esm/useLocation.js +10 -0
- package/dist/esm/useLocation.js.map +1 -0
- package/dist/esm/useMatch.d.ts +14 -0
- package/dist/esm/useMatch.js +30 -0
- package/dist/esm/useMatch.js.map +1 -0
- package/dist/esm/useNavigate.d.ts +28 -0
- package/dist/esm/useNavigate.js +32 -0
- package/dist/esm/useNavigate.js.map +1 -0
- package/dist/esm/useParams.d.ts +21 -0
- package/dist/esm/useParams.js +17 -0
- package/dist/esm/useParams.js.map +1 -0
- package/dist/esm/useRouteContext.d.ts +3 -0
- package/dist/esm/useRouteContext.js +11 -0
- package/dist/esm/useRouteContext.js.map +1 -0
- package/dist/esm/useRouter.d.ts +14 -0
- package/dist/esm/useRouter.js +15 -0
- package/dist/esm/useRouter.js.map +1 -0
- package/dist/esm/useRouterState.d.ts +20 -0
- package/dist/esm/useRouterState.js +38 -0
- package/dist/esm/useRouterState.js.map +1 -0
- package/dist/esm/useSearch.d.ts +21 -0
- package/dist/esm/useSearch.js +16 -0
- package/dist/esm/useSearch.js.map +1 -0
- package/dist/esm/utils.d.ts +54 -0
- package/dist/esm/utils.js +45 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/llms/index.d.ts +3 -0
- package/dist/llms/index.js +43 -0
- package/dist/llms/rules/api.d.ts +2 -0
- package/dist/llms/rules/api.js +4612 -0
- package/dist/llms/rules/guide.d.ts +2 -0
- package/dist/llms/rules/guide.js +10690 -0
- package/dist/llms/rules/installation.d.ts +2 -0
- package/dist/llms/rules/installation.js +1285 -0
- package/dist/llms/rules/routing.d.ts +2 -0
- package/dist/llms/rules/routing.js +1984 -0
- package/dist/llms/rules/setup-and-architecture.d.ts +2 -0
- package/dist/llms/rules/setup-and-architecture.js +920 -0
- package/package.json +142 -0
- package/src/Asset.tsx +219 -0
- package/src/CatchBoundary.tsx +120 -0
- package/src/ClientOnly.tsx +68 -0
- package/src/HeadContent.dev.tsx +46 -0
- package/src/HeadContent.tsx +22 -0
- package/src/Match.tsx +360 -0
- package/src/Matches.tsx +313 -0
- package/src/RouterProvider.tsx +92 -0
- package/src/SafeFragment.tsx +5 -0
- package/src/ScriptOnce.tsx +21 -0
- package/src/Scripts.tsx +80 -0
- package/src/ScrollRestoration.tsx +69 -0
- package/src/Transitioner.tsx +134 -0
- package/src/awaited.tsx +55 -0
- package/src/fileRoute.ts +313 -0
- package/src/headContentUtils.tsx +217 -0
- package/src/history.ts +9 -0
- package/src/index.dev.tsx +6 -0
- package/src/index.tsx +341 -0
- package/src/lazyRouteComponent.tsx +96 -0
- package/src/link.tsx +984 -0
- package/src/matchContext.tsx +8 -0
- package/src/not-found.tsx +43 -0
- package/src/renderRouteNotFound.tsx +35 -0
- package/src/route.tsx +740 -0
- package/src/router.ts +127 -0
- package/src/routerContext.tsx +4 -0
- package/src/scroll-restoration.tsx +45 -0
- package/src/ssr/RouterClient.tsx +22 -0
- package/src/ssr/RouterServer.tsx +9 -0
- package/src/ssr/client.ts +2 -0
- package/src/ssr/defaultRenderHandler.tsx +12 -0
- package/src/ssr/defaultStreamHandler.tsx +13 -0
- package/src/ssr/renderRouterToStream.tsx +90 -0
- package/src/ssr/renderRouterToString.tsx +36 -0
- package/src/ssr/serializer.ts +7 -0
- package/src/ssr/server.ts +6 -0
- package/src/structuralSharing.ts +47 -0
- package/src/typePrimitives.ts +84 -0
- package/src/useBlocker.tsx +320 -0
- package/src/useCanGoBack.ts +5 -0
- package/src/useLoaderData.tsx +91 -0
- package/src/useLoaderDeps.tsx +69 -0
- package/src/useLocation.tsx +52 -0
- package/src/useMatch.tsx +123 -0
- package/src/useNavigate.tsx +78 -0
- package/src/useParams.tsx +107 -0
- package/src/useRouteContext.ts +30 -0
- package/src/useRouter.tsx +25 -0
- package/src/useRouterState.tsx +86 -0
- package/src/useSearch.tsx +105 -0
- package/src/utils.ts +125 -0
package/src/link.tsx
ADDED
|
@@ -0,0 +1,984 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { flushSync } from 'react-dom'
|
|
3
|
+
import {
|
|
4
|
+
deepEqual,
|
|
5
|
+
exactPathTest,
|
|
6
|
+
functionalUpdate,
|
|
7
|
+
isDangerousProtocol,
|
|
8
|
+
preloadWarning,
|
|
9
|
+
removeTrailingSlash,
|
|
10
|
+
} from '@tanstack/router-core'
|
|
11
|
+
import { isServer } from '@tanstack/router-core/isServer'
|
|
12
|
+
import { useRouterState } from './useRouterState'
|
|
13
|
+
import { useRouter } from './useRouter'
|
|
14
|
+
|
|
15
|
+
import { useForwardedRef, useIntersectionObserver } from './utils'
|
|
16
|
+
|
|
17
|
+
import { useHydrated } from './ClientOnly'
|
|
18
|
+
import type {
|
|
19
|
+
AnyRouter,
|
|
20
|
+
Constrain,
|
|
21
|
+
LinkOptions,
|
|
22
|
+
RegisteredRouter,
|
|
23
|
+
RoutePaths,
|
|
24
|
+
} from '@tanstack/router-core'
|
|
25
|
+
import type { ReactNode } from 'react'
|
|
26
|
+
import type {
|
|
27
|
+
ValidateLinkOptions,
|
|
28
|
+
ValidateLinkOptionsArray,
|
|
29
|
+
} from './typePrimitives'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build anchor-like props for declarative navigation and preloading.
|
|
33
|
+
*
|
|
34
|
+
* Returns stable `href`, event handlers and accessibility props derived from
|
|
35
|
+
* router options and active state. Used internally by `Link` and custom links.
|
|
36
|
+
*
|
|
37
|
+
* Options cover `to`, `params`, `search`, `hash`, `state`, `preload`,
|
|
38
|
+
* `activeProps`, `inactiveProps`, and more.
|
|
39
|
+
*
|
|
40
|
+
* @returns React anchor props suitable for `<a>` or custom components.
|
|
41
|
+
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/useLinkPropsHook
|
|
42
|
+
*/
|
|
43
|
+
export function useLinkProps<
|
|
44
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
45
|
+
const TFrom extends string = string,
|
|
46
|
+
const TTo extends string | undefined = undefined,
|
|
47
|
+
const TMaskFrom extends string = TFrom,
|
|
48
|
+
const TMaskTo extends string = '',
|
|
49
|
+
>(
|
|
50
|
+
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
51
|
+
forwardedRef?: React.ForwardedRef<Element>,
|
|
52
|
+
): React.ComponentPropsWithRef<'a'> {
|
|
53
|
+
const router = useRouter()
|
|
54
|
+
const innerRef = useForwardedRef(forwardedRef)
|
|
55
|
+
|
|
56
|
+
// Determine if we're on the server - used for tree-shaking client-only code
|
|
57
|
+
const _isServer = isServer ?? router.isServer
|
|
58
|
+
|
|
59
|
+
const {
|
|
60
|
+
// custom props
|
|
61
|
+
activeProps,
|
|
62
|
+
inactiveProps,
|
|
63
|
+
activeOptions,
|
|
64
|
+
to,
|
|
65
|
+
preload: userPreload,
|
|
66
|
+
preloadDelay: userPreloadDelay,
|
|
67
|
+
hashScrollIntoView,
|
|
68
|
+
replace,
|
|
69
|
+
startTransition,
|
|
70
|
+
resetScroll,
|
|
71
|
+
viewTransition,
|
|
72
|
+
// element props
|
|
73
|
+
children,
|
|
74
|
+
target,
|
|
75
|
+
disabled,
|
|
76
|
+
style,
|
|
77
|
+
className,
|
|
78
|
+
onClick,
|
|
79
|
+
onBlur,
|
|
80
|
+
onFocus,
|
|
81
|
+
onMouseEnter,
|
|
82
|
+
onMouseLeave,
|
|
83
|
+
onTouchStart,
|
|
84
|
+
ignoreBlocker,
|
|
85
|
+
// prevent these from being returned
|
|
86
|
+
params: _params,
|
|
87
|
+
search: _search,
|
|
88
|
+
hash: _hash,
|
|
89
|
+
state: _state,
|
|
90
|
+
mask: _mask,
|
|
91
|
+
reloadDocument: _reloadDocument,
|
|
92
|
+
unsafeRelative: _unsafeRelative,
|
|
93
|
+
from: _from,
|
|
94
|
+
_fromLocation,
|
|
95
|
+
...propsSafeToSpread
|
|
96
|
+
} = options
|
|
97
|
+
|
|
98
|
+
// ==========================================================================
|
|
99
|
+
// SERVER EARLY RETURN
|
|
100
|
+
// On the server, we return static props without any event handlers,
|
|
101
|
+
// effects, or client-side interactivity.
|
|
102
|
+
//
|
|
103
|
+
// For SSR parity (to avoid hydration errors), we still compute the link's
|
|
104
|
+
// active status on the server, but we avoid creating any router-state
|
|
105
|
+
// subscriptions by reading from `router.state` directly.
|
|
106
|
+
//
|
|
107
|
+
// Note: `location.hash` is not available on the server.
|
|
108
|
+
// ==========================================================================
|
|
109
|
+
if (_isServer) {
|
|
110
|
+
const safeInternal = isSafeInternal(to)
|
|
111
|
+
|
|
112
|
+
// If `to` is obviously an absolute URL, treat as external and avoid
|
|
113
|
+
// computing the internal location via `buildLocation`.
|
|
114
|
+
if (
|
|
115
|
+
typeof to === 'string' &&
|
|
116
|
+
!safeInternal &&
|
|
117
|
+
// Quick checks to avoid `new URL` in common internal-like cases
|
|
118
|
+
to.indexOf(':') > -1
|
|
119
|
+
) {
|
|
120
|
+
try {
|
|
121
|
+
new URL(to)
|
|
122
|
+
if (isDangerousProtocol(to, router.protocolAllowlist)) {
|
|
123
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
124
|
+
console.warn(`Blocked Link with dangerous protocol: ${to}`)
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
...propsSafeToSpread,
|
|
128
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
129
|
+
href: undefined,
|
|
130
|
+
...(children && { children }),
|
|
131
|
+
...(target && { target }),
|
|
132
|
+
...(disabled && { disabled }),
|
|
133
|
+
...(style && { style }),
|
|
134
|
+
...(className && { className }),
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
...propsSafeToSpread,
|
|
140
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
141
|
+
href: to,
|
|
142
|
+
...(children && { children }),
|
|
143
|
+
...(target && { target }),
|
|
144
|
+
...(disabled && { disabled }),
|
|
145
|
+
...(style && { style }),
|
|
146
|
+
...(className && { className }),
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// Not an absolute URL
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const next = router.buildLocation({ ...options, from: options.from } as any)
|
|
154
|
+
|
|
155
|
+
// Use publicHref - it contains the correct href for display
|
|
156
|
+
// When a rewrite changes the origin, publicHref is the full URL
|
|
157
|
+
// Otherwise it's the origin-stripped path
|
|
158
|
+
// This avoids constructing URL objects in the hot path
|
|
159
|
+
const hrefOptionPublicHref = next.maskedLocation
|
|
160
|
+
? next.maskedLocation.publicHref
|
|
161
|
+
: next.publicHref
|
|
162
|
+
const hrefOptionExternal = next.maskedLocation
|
|
163
|
+
? next.maskedLocation.external
|
|
164
|
+
: next.external
|
|
165
|
+
const hrefOption = getHrefOption(
|
|
166
|
+
hrefOptionPublicHref,
|
|
167
|
+
hrefOptionExternal,
|
|
168
|
+
router.history,
|
|
169
|
+
disabled,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
const externalLink = (() => {
|
|
173
|
+
if (hrefOption?.external) {
|
|
174
|
+
if (isDangerousProtocol(hrefOption.href, router.protocolAllowlist)) {
|
|
175
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
176
|
+
console.warn(
|
|
177
|
+
`Blocked Link with dangerous protocol: ${hrefOption.href}`,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
return undefined
|
|
181
|
+
}
|
|
182
|
+
return hrefOption.href
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (safeInternal) return undefined
|
|
186
|
+
|
|
187
|
+
// Only attempt URL parsing when it looks like an absolute URL.
|
|
188
|
+
if (typeof to === 'string' && to.indexOf(':') > -1) {
|
|
189
|
+
try {
|
|
190
|
+
new URL(to)
|
|
191
|
+
if (isDangerousProtocol(to, router.protocolAllowlist)) {
|
|
192
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
193
|
+
console.warn(`Blocked Link with dangerous protocol: ${to}`)
|
|
194
|
+
}
|
|
195
|
+
return undefined
|
|
196
|
+
}
|
|
197
|
+
return to
|
|
198
|
+
} catch {}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return undefined
|
|
202
|
+
})()
|
|
203
|
+
|
|
204
|
+
const isActive = (() => {
|
|
205
|
+
if (externalLink) return false
|
|
206
|
+
|
|
207
|
+
const currentLocation = router.state.location
|
|
208
|
+
|
|
209
|
+
const exact = activeOptions?.exact ?? false
|
|
210
|
+
|
|
211
|
+
if (exact) {
|
|
212
|
+
const testExact = exactPathTest(
|
|
213
|
+
currentLocation.pathname,
|
|
214
|
+
next.pathname,
|
|
215
|
+
router.basepath,
|
|
216
|
+
)
|
|
217
|
+
if (!testExact) {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
const currentPathSplit = removeTrailingSlash(
|
|
222
|
+
currentLocation.pathname,
|
|
223
|
+
router.basepath,
|
|
224
|
+
)
|
|
225
|
+
const nextPathSplit = removeTrailingSlash(
|
|
226
|
+
next.pathname,
|
|
227
|
+
router.basepath,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
const pathIsFuzzyEqual =
|
|
231
|
+
currentPathSplit.startsWith(nextPathSplit) &&
|
|
232
|
+
(currentPathSplit.length === nextPathSplit.length ||
|
|
233
|
+
currentPathSplit[nextPathSplit.length] === '/')
|
|
234
|
+
|
|
235
|
+
if (!pathIsFuzzyEqual) {
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const includeSearch = activeOptions?.includeSearch ?? true
|
|
241
|
+
if (includeSearch) {
|
|
242
|
+
if (currentLocation.search !== next.search) {
|
|
243
|
+
const currentSearchEmpty =
|
|
244
|
+
!currentLocation.search ||
|
|
245
|
+
(typeof currentLocation.search === 'object' &&
|
|
246
|
+
Object.keys(currentLocation.search).length === 0)
|
|
247
|
+
const nextSearchEmpty =
|
|
248
|
+
!next.search ||
|
|
249
|
+
(typeof next.search === 'object' &&
|
|
250
|
+
Object.keys(next.search).length === 0)
|
|
251
|
+
|
|
252
|
+
if (!(currentSearchEmpty && nextSearchEmpty)) {
|
|
253
|
+
const searchTest = deepEqual(currentLocation.search, next.search, {
|
|
254
|
+
partial: !exact,
|
|
255
|
+
ignoreUndefined: !activeOptions?.explicitUndefined,
|
|
256
|
+
})
|
|
257
|
+
if (!searchTest) {
|
|
258
|
+
return false
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Hash is not available on the server
|
|
265
|
+
if (activeOptions?.includeHash) {
|
|
266
|
+
return false
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return true
|
|
270
|
+
})()
|
|
271
|
+
|
|
272
|
+
if (externalLink) {
|
|
273
|
+
return {
|
|
274
|
+
...propsSafeToSpread,
|
|
275
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
276
|
+
href: externalLink,
|
|
277
|
+
...(children && { children }),
|
|
278
|
+
...(target && { target }),
|
|
279
|
+
...(disabled && { disabled }),
|
|
280
|
+
...(style && { style }),
|
|
281
|
+
...(className && { className }),
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
286
|
+
isActive
|
|
287
|
+
? (functionalUpdate(activeProps as any, {}) ?? STATIC_ACTIVE_OBJECT)
|
|
288
|
+
: STATIC_EMPTY_OBJECT
|
|
289
|
+
|
|
290
|
+
const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
291
|
+
isActive
|
|
292
|
+
? STATIC_EMPTY_OBJECT
|
|
293
|
+
: (functionalUpdate(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT)
|
|
294
|
+
|
|
295
|
+
const resolvedStyle = (() => {
|
|
296
|
+
const baseStyle = style
|
|
297
|
+
const activeStyle = resolvedActiveProps.style
|
|
298
|
+
const inactiveStyle = resolvedInactiveProps.style
|
|
299
|
+
|
|
300
|
+
if (!baseStyle && !activeStyle && !inactiveStyle) {
|
|
301
|
+
return undefined
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (baseStyle && !activeStyle && !inactiveStyle) {
|
|
305
|
+
return baseStyle
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!baseStyle && activeStyle && !inactiveStyle) {
|
|
309
|
+
return activeStyle
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!baseStyle && !activeStyle && inactiveStyle) {
|
|
313
|
+
return inactiveStyle
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
...baseStyle,
|
|
318
|
+
...activeStyle,
|
|
319
|
+
...inactiveStyle,
|
|
320
|
+
}
|
|
321
|
+
})()
|
|
322
|
+
|
|
323
|
+
const resolvedClassName = (() => {
|
|
324
|
+
const baseClassName = className
|
|
325
|
+
const activeClassName = resolvedActiveProps.className
|
|
326
|
+
const inactiveClassName = resolvedInactiveProps.className
|
|
327
|
+
|
|
328
|
+
if (!baseClassName && !activeClassName && !inactiveClassName) {
|
|
329
|
+
return ''
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let out = ''
|
|
333
|
+
|
|
334
|
+
if (baseClassName) {
|
|
335
|
+
out = baseClassName
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (activeClassName) {
|
|
339
|
+
out = out ? `${out} ${activeClassName}` : activeClassName
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (inactiveClassName) {
|
|
343
|
+
out = out ? `${out} ${inactiveClassName}` : inactiveClassName
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return out
|
|
347
|
+
})()
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
...propsSafeToSpread,
|
|
351
|
+
...resolvedActiveProps,
|
|
352
|
+
...resolvedInactiveProps,
|
|
353
|
+
href: hrefOption?.href,
|
|
354
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
355
|
+
disabled: !!disabled,
|
|
356
|
+
target,
|
|
357
|
+
...(resolvedStyle && { style: resolvedStyle }),
|
|
358
|
+
...(resolvedClassName && { className: resolvedClassName }),
|
|
359
|
+
...(disabled && STATIC_DISABLED_PROPS),
|
|
360
|
+
...(isActive && STATIC_ACTIVE_PROPS),
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ==========================================================================
|
|
365
|
+
// CLIENT-ONLY CODE
|
|
366
|
+
// Everything below this point only runs on the client. The `isServer` check
|
|
367
|
+
// above is a compile-time constant that bundlers use for dead code elimination,
|
|
368
|
+
// so this entire section is removed from server bundles.
|
|
369
|
+
//
|
|
370
|
+
// We disable the rules-of-hooks lint rule because these hooks appear after
|
|
371
|
+
// an early return. This is safe because:
|
|
372
|
+
// 1. `isServer` is a compile-time constant from conditional exports
|
|
373
|
+
// 2. In server bundles, this code is completely eliminated by the bundler
|
|
374
|
+
// 3. In client bundles, `isServer` is `false`, so the early return never executes
|
|
375
|
+
// ==========================================================================
|
|
376
|
+
|
|
377
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
378
|
+
const isHydrated = useHydrated()
|
|
379
|
+
|
|
380
|
+
// subscribe to path/search/hash/params to re-build location when they change
|
|
381
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
382
|
+
const currentLocationState = useRouterState({
|
|
383
|
+
select: (s) => {
|
|
384
|
+
const leaf = s.matches[s.matches.length - 1]
|
|
385
|
+
return {
|
|
386
|
+
search: leaf?.search,
|
|
387
|
+
hash: s.location.hash,
|
|
388
|
+
path: leaf?.pathname, // path + params
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
structuralSharing: true as any,
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
const from = options.from
|
|
395
|
+
|
|
396
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
397
|
+
const _options = React.useMemo(
|
|
398
|
+
() => {
|
|
399
|
+
return { ...options, from }
|
|
400
|
+
},
|
|
401
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
402
|
+
[
|
|
403
|
+
router,
|
|
404
|
+
currentLocationState,
|
|
405
|
+
from,
|
|
406
|
+
options._fromLocation,
|
|
407
|
+
options.hash,
|
|
408
|
+
options.to,
|
|
409
|
+
options.search,
|
|
410
|
+
options.params,
|
|
411
|
+
options.state,
|
|
412
|
+
options.mask,
|
|
413
|
+
options.unsafeRelative,
|
|
414
|
+
],
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
418
|
+
const next = React.useMemo(
|
|
419
|
+
() => router.buildLocation({ ..._options } as any),
|
|
420
|
+
[router, _options],
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
// Use publicHref - it contains the correct href for display
|
|
424
|
+
// When a rewrite changes the origin, publicHref is the full URL
|
|
425
|
+
// Otherwise it's the origin-stripped path
|
|
426
|
+
// This avoids constructing URL objects in the hot path
|
|
427
|
+
const hrefOptionPublicHref = next.maskedLocation
|
|
428
|
+
? next.maskedLocation.publicHref
|
|
429
|
+
: next.publicHref
|
|
430
|
+
const hrefOptionExternal = next.maskedLocation
|
|
431
|
+
? next.maskedLocation.external
|
|
432
|
+
: next.external
|
|
433
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
434
|
+
const hrefOption = React.useMemo(
|
|
435
|
+
() =>
|
|
436
|
+
getHrefOption(
|
|
437
|
+
hrefOptionPublicHref,
|
|
438
|
+
hrefOptionExternal,
|
|
439
|
+
router.history,
|
|
440
|
+
disabled,
|
|
441
|
+
),
|
|
442
|
+
[disabled, hrefOptionExternal, hrefOptionPublicHref, router.history],
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
446
|
+
const externalLink = React.useMemo(() => {
|
|
447
|
+
if (hrefOption?.external) {
|
|
448
|
+
// Block dangerous protocols for external links
|
|
449
|
+
if (isDangerousProtocol(hrefOption.href, router.protocolAllowlist)) {
|
|
450
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
451
|
+
console.warn(
|
|
452
|
+
`Blocked Link with dangerous protocol: ${hrefOption.href}`,
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
return undefined
|
|
456
|
+
}
|
|
457
|
+
return hrefOption.href
|
|
458
|
+
}
|
|
459
|
+
const safeInternal = isSafeInternal(to)
|
|
460
|
+
if (safeInternal) return undefined
|
|
461
|
+
if (typeof to !== 'string' || to.indexOf(':') === -1) return undefined
|
|
462
|
+
try {
|
|
463
|
+
new URL(to as any)
|
|
464
|
+
// Block dangerous protocols like javascript:, blob:, data:
|
|
465
|
+
if (isDangerousProtocol(to, router.protocolAllowlist)) {
|
|
466
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
467
|
+
console.warn(`Blocked Link with dangerous protocol: ${to}`)
|
|
468
|
+
}
|
|
469
|
+
return undefined
|
|
470
|
+
}
|
|
471
|
+
return to
|
|
472
|
+
} catch {}
|
|
473
|
+
return undefined
|
|
474
|
+
}, [to, hrefOption, router.protocolAllowlist])
|
|
475
|
+
|
|
476
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
477
|
+
const isActive = useRouterState({
|
|
478
|
+
select: (s) => {
|
|
479
|
+
if (externalLink) return false
|
|
480
|
+
if (activeOptions?.exact) {
|
|
481
|
+
const testExact = exactPathTest(
|
|
482
|
+
s.location.pathname,
|
|
483
|
+
next.pathname,
|
|
484
|
+
router.basepath,
|
|
485
|
+
)
|
|
486
|
+
if (!testExact) {
|
|
487
|
+
return false
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
const currentPathSplit = removeTrailingSlash(
|
|
491
|
+
s.location.pathname,
|
|
492
|
+
router.basepath,
|
|
493
|
+
)
|
|
494
|
+
const nextPathSplit = removeTrailingSlash(
|
|
495
|
+
next.pathname,
|
|
496
|
+
router.basepath,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
const pathIsFuzzyEqual =
|
|
500
|
+
currentPathSplit.startsWith(nextPathSplit) &&
|
|
501
|
+
(currentPathSplit.length === nextPathSplit.length ||
|
|
502
|
+
currentPathSplit[nextPathSplit.length] === '/')
|
|
503
|
+
|
|
504
|
+
if (!pathIsFuzzyEqual) {
|
|
505
|
+
return false
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (activeOptions?.includeSearch ?? true) {
|
|
510
|
+
const searchTest = deepEqual(s.location.search, next.search, {
|
|
511
|
+
partial: !activeOptions?.exact,
|
|
512
|
+
ignoreUndefined: !activeOptions?.explicitUndefined,
|
|
513
|
+
})
|
|
514
|
+
if (!searchTest) {
|
|
515
|
+
return false
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (activeOptions?.includeHash) {
|
|
520
|
+
return isHydrated && s.location.hash === next.hash
|
|
521
|
+
}
|
|
522
|
+
return true
|
|
523
|
+
},
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
// Get the active props
|
|
527
|
+
const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
|
|
528
|
+
? (functionalUpdate(activeProps as any, {}) ?? STATIC_ACTIVE_OBJECT)
|
|
529
|
+
: STATIC_EMPTY_OBJECT
|
|
530
|
+
|
|
531
|
+
// Get the inactive props
|
|
532
|
+
const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
533
|
+
isActive
|
|
534
|
+
? STATIC_EMPTY_OBJECT
|
|
535
|
+
: (functionalUpdate(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT)
|
|
536
|
+
|
|
537
|
+
const resolvedClassName = [
|
|
538
|
+
className,
|
|
539
|
+
resolvedActiveProps.className,
|
|
540
|
+
resolvedInactiveProps.className,
|
|
541
|
+
]
|
|
542
|
+
.filter(Boolean)
|
|
543
|
+
.join(' ')
|
|
544
|
+
|
|
545
|
+
const resolvedStyle = (style ||
|
|
546
|
+
resolvedActiveProps.style ||
|
|
547
|
+
resolvedInactiveProps.style) && {
|
|
548
|
+
...style,
|
|
549
|
+
...resolvedActiveProps.style,
|
|
550
|
+
...resolvedInactiveProps.style,
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
554
|
+
const [isTransitioning, setIsTransitioning] = React.useState(false)
|
|
555
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
556
|
+
const hasRenderFetched = React.useRef(false)
|
|
557
|
+
|
|
558
|
+
const preload =
|
|
559
|
+
options.reloadDocument || externalLink
|
|
560
|
+
? false
|
|
561
|
+
: (userPreload ?? router.options.defaultPreload)
|
|
562
|
+
const preloadDelay =
|
|
563
|
+
userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
|
|
564
|
+
|
|
565
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
566
|
+
const doPreload = React.useCallback(() => {
|
|
567
|
+
router
|
|
568
|
+
.preloadRoute({ ..._options, _builtLocation: next } as any)
|
|
569
|
+
.catch((err) => {
|
|
570
|
+
console.warn(err)
|
|
571
|
+
console.warn(preloadWarning)
|
|
572
|
+
})
|
|
573
|
+
}, [router, _options, next])
|
|
574
|
+
|
|
575
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
576
|
+
const preloadViewportIoCallback = React.useCallback(
|
|
577
|
+
(entry: IntersectionObserverEntry | undefined) => {
|
|
578
|
+
if (entry?.isIntersecting) {
|
|
579
|
+
doPreload()
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
[doPreload],
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
586
|
+
useIntersectionObserver(
|
|
587
|
+
innerRef,
|
|
588
|
+
preloadViewportIoCallback,
|
|
589
|
+
intersectionObserverOptions,
|
|
590
|
+
{ disabled: !!disabled || !(preload === 'viewport') },
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
594
|
+
React.useEffect(() => {
|
|
595
|
+
if (hasRenderFetched.current) {
|
|
596
|
+
return
|
|
597
|
+
}
|
|
598
|
+
if (!disabled && preload === 'render') {
|
|
599
|
+
doPreload()
|
|
600
|
+
hasRenderFetched.current = true
|
|
601
|
+
}
|
|
602
|
+
}, [disabled, doPreload, preload])
|
|
603
|
+
|
|
604
|
+
// The click handler
|
|
605
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
606
|
+
// Check actual element's target attribute as fallback
|
|
607
|
+
const elementTarget = (
|
|
608
|
+
e.currentTarget as HTMLAnchorElement | SVGAElement
|
|
609
|
+
).getAttribute('target')
|
|
610
|
+
const effectiveTarget = target !== undefined ? target : elementTarget
|
|
611
|
+
|
|
612
|
+
if (
|
|
613
|
+
!disabled &&
|
|
614
|
+
!isCtrlEvent(e) &&
|
|
615
|
+
!e.defaultPrevented &&
|
|
616
|
+
(!effectiveTarget || effectiveTarget === '_self') &&
|
|
617
|
+
e.button === 0
|
|
618
|
+
) {
|
|
619
|
+
e.preventDefault()
|
|
620
|
+
|
|
621
|
+
flushSync(() => {
|
|
622
|
+
setIsTransitioning(true)
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
const unsub = router.subscribe('onResolved', () => {
|
|
626
|
+
unsub()
|
|
627
|
+
setIsTransitioning(false)
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
// All is well? Navigate!
|
|
631
|
+
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
|
|
632
|
+
router.navigate({
|
|
633
|
+
..._options,
|
|
634
|
+
replace,
|
|
635
|
+
resetScroll,
|
|
636
|
+
hashScrollIntoView,
|
|
637
|
+
startTransition,
|
|
638
|
+
viewTransition,
|
|
639
|
+
ignoreBlocker,
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (externalLink) {
|
|
645
|
+
return {
|
|
646
|
+
...propsSafeToSpread,
|
|
647
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
648
|
+
href: externalLink,
|
|
649
|
+
...(children && { children }),
|
|
650
|
+
...(target && { target }),
|
|
651
|
+
...(disabled && { disabled }),
|
|
652
|
+
...(style && { style }),
|
|
653
|
+
...(className && { className }),
|
|
654
|
+
...(onClick && { onClick }),
|
|
655
|
+
...(onBlur && { onBlur }),
|
|
656
|
+
...(onFocus && { onFocus }),
|
|
657
|
+
...(onMouseEnter && { onMouseEnter }),
|
|
658
|
+
...(onMouseLeave && { onMouseLeave }),
|
|
659
|
+
...(onTouchStart && { onTouchStart }),
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const enqueueIntentPreload = (e: React.MouseEvent | React.FocusEvent) => {
|
|
664
|
+
if (disabled || preload !== 'intent') return
|
|
665
|
+
|
|
666
|
+
if (!preloadDelay) {
|
|
667
|
+
doPreload()
|
|
668
|
+
return
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const eventTarget = e.currentTarget
|
|
672
|
+
|
|
673
|
+
if (timeoutMap.has(eventTarget)) {
|
|
674
|
+
return
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const id = setTimeout(() => {
|
|
678
|
+
timeoutMap.delete(eventTarget)
|
|
679
|
+
doPreload()
|
|
680
|
+
}, preloadDelay)
|
|
681
|
+
timeoutMap.set(eventTarget, id)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const handleTouchStart = (_: React.TouchEvent) => {
|
|
685
|
+
if (disabled || preload !== 'intent') return
|
|
686
|
+
doPreload()
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const handleLeave = (e: React.MouseEvent | React.FocusEvent) => {
|
|
690
|
+
if (disabled || !preload || !preloadDelay) return
|
|
691
|
+
const eventTarget = e.currentTarget
|
|
692
|
+
const id = timeoutMap.get(eventTarget)
|
|
693
|
+
if (id) {
|
|
694
|
+
clearTimeout(id)
|
|
695
|
+
timeoutMap.delete(eventTarget)
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
...propsSafeToSpread,
|
|
701
|
+
...resolvedActiveProps,
|
|
702
|
+
...resolvedInactiveProps,
|
|
703
|
+
href: hrefOption?.href,
|
|
704
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
705
|
+
onClick: composeHandlers([onClick, handleClick]),
|
|
706
|
+
onBlur: composeHandlers([onBlur, handleLeave]),
|
|
707
|
+
onFocus: composeHandlers([onFocus, enqueueIntentPreload]),
|
|
708
|
+
onMouseEnter: composeHandlers([onMouseEnter, enqueueIntentPreload]),
|
|
709
|
+
onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
|
|
710
|
+
onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
|
|
711
|
+
disabled: !!disabled,
|
|
712
|
+
target,
|
|
713
|
+
...(resolvedStyle && { style: resolvedStyle }),
|
|
714
|
+
...(resolvedClassName && { className: resolvedClassName }),
|
|
715
|
+
...(disabled && STATIC_DISABLED_PROPS),
|
|
716
|
+
...(isActive && STATIC_ACTIVE_PROPS),
|
|
717
|
+
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const STATIC_EMPTY_OBJECT = {}
|
|
722
|
+
const STATIC_ACTIVE_OBJECT = { className: 'active' }
|
|
723
|
+
const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
|
|
724
|
+
const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
|
|
725
|
+
const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }
|
|
726
|
+
|
|
727
|
+
const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()
|
|
728
|
+
|
|
729
|
+
const intersectionObserverOptions: IntersectionObserverInit = {
|
|
730
|
+
rootMargin: '100px',
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const composeHandlers =
|
|
734
|
+
(handlers: Array<undefined | React.EventHandler<any>>) =>
|
|
735
|
+
(e: React.SyntheticEvent) => {
|
|
736
|
+
for (const handler of handlers) {
|
|
737
|
+
if (!handler) continue
|
|
738
|
+
if (e.defaultPrevented) return
|
|
739
|
+
handler(e)
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function getHrefOption(
|
|
744
|
+
publicHref: string,
|
|
745
|
+
external: boolean,
|
|
746
|
+
history: AnyRouter['history'],
|
|
747
|
+
disabled: boolean | undefined,
|
|
748
|
+
) {
|
|
749
|
+
if (disabled) return undefined
|
|
750
|
+
// Full URL means rewrite changed the origin - treat as external-like
|
|
751
|
+
if (external) {
|
|
752
|
+
return { href: publicHref, external: true }
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
href: history.createHref(publicHref) || '/',
|
|
756
|
+
external: false,
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function isSafeInternal(to: unknown) {
|
|
761
|
+
if (typeof to !== 'string') return false
|
|
762
|
+
const zero = to.charCodeAt(0)
|
|
763
|
+
if (zero === 47) return to.charCodeAt(1) !== 47 // '/' but not '//'
|
|
764
|
+
return zero === 46 // '.', '..', './', '../'
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
type UseLinkReactProps<TComp> = TComp extends keyof React.JSX.IntrinsicElements
|
|
768
|
+
? React.JSX.IntrinsicElements[TComp]
|
|
769
|
+
: TComp extends React.ComponentType<any>
|
|
770
|
+
? React.ComponentPropsWithoutRef<TComp> &
|
|
771
|
+
React.RefAttributes<React.ComponentRef<TComp>>
|
|
772
|
+
: never
|
|
773
|
+
|
|
774
|
+
export type UseLinkPropsOptions<
|
|
775
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
776
|
+
TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
|
|
777
|
+
TTo extends string | undefined = '.',
|
|
778
|
+
TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
|
|
779
|
+
TMaskTo extends string = '.',
|
|
780
|
+
> = ActiveLinkOptions<'a', TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
781
|
+
UseLinkReactProps<'a'>
|
|
782
|
+
|
|
783
|
+
export type ActiveLinkOptions<
|
|
784
|
+
TComp = 'a',
|
|
785
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
786
|
+
TFrom extends string = string,
|
|
787
|
+
TTo extends string | undefined = '.',
|
|
788
|
+
TMaskFrom extends string = TFrom,
|
|
789
|
+
TMaskTo extends string = '.',
|
|
790
|
+
> = LinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
791
|
+
ActiveLinkOptionProps<TComp>
|
|
792
|
+
|
|
793
|
+
type ActiveLinkProps<TComp> = Partial<
|
|
794
|
+
LinkComponentReactProps<TComp> & {
|
|
795
|
+
[key: `data-${string}`]: unknown
|
|
796
|
+
}
|
|
797
|
+
>
|
|
798
|
+
|
|
799
|
+
export interface ActiveLinkOptionProps<TComp = 'a'> {
|
|
800
|
+
/**
|
|
801
|
+
* A function that returns additional props for the `active` state of this link.
|
|
802
|
+
* These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
|
|
803
|
+
*/
|
|
804
|
+
activeProps?: ActiveLinkProps<TComp> | (() => ActiveLinkProps<TComp>)
|
|
805
|
+
/**
|
|
806
|
+
* A function that returns additional props for the `inactive` state of this link.
|
|
807
|
+
* These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
|
|
808
|
+
*/
|
|
809
|
+
inactiveProps?: ActiveLinkProps<TComp> | (() => ActiveLinkProps<TComp>)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
export type LinkProps<
|
|
813
|
+
TComp = 'a',
|
|
814
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
815
|
+
TFrom extends string = string,
|
|
816
|
+
TTo extends string | undefined = '.',
|
|
817
|
+
TMaskFrom extends string = TFrom,
|
|
818
|
+
TMaskTo extends string = '.',
|
|
819
|
+
> = ActiveLinkOptions<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
820
|
+
LinkPropsChildren
|
|
821
|
+
|
|
822
|
+
export interface LinkPropsChildren {
|
|
823
|
+
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
|
|
824
|
+
children?:
|
|
825
|
+
| React.ReactNode
|
|
826
|
+
| ((state: {
|
|
827
|
+
isActive: boolean
|
|
828
|
+
isTransitioning: boolean
|
|
829
|
+
}) => React.ReactNode)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
type LinkComponentReactProps<TComp> = Omit<
|
|
833
|
+
UseLinkReactProps<TComp>,
|
|
834
|
+
keyof CreateLinkProps
|
|
835
|
+
>
|
|
836
|
+
|
|
837
|
+
export type LinkComponentProps<
|
|
838
|
+
TComp = 'a',
|
|
839
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
840
|
+
TFrom extends string = string,
|
|
841
|
+
TTo extends string | undefined = '.',
|
|
842
|
+
TMaskFrom extends string = TFrom,
|
|
843
|
+
TMaskTo extends string = '.',
|
|
844
|
+
> = LinkComponentReactProps<TComp> &
|
|
845
|
+
LinkProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
|
|
846
|
+
|
|
847
|
+
export type CreateLinkProps = LinkProps<
|
|
848
|
+
any,
|
|
849
|
+
any,
|
|
850
|
+
string,
|
|
851
|
+
string,
|
|
852
|
+
string,
|
|
853
|
+
string
|
|
854
|
+
>
|
|
855
|
+
|
|
856
|
+
export type LinkComponent<
|
|
857
|
+
in out TComp,
|
|
858
|
+
in out TDefaultFrom extends string = string,
|
|
859
|
+
> = <
|
|
860
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
861
|
+
const TFrom extends string = TDefaultFrom,
|
|
862
|
+
const TTo extends string | undefined = undefined,
|
|
863
|
+
const TMaskFrom extends string = TFrom,
|
|
864
|
+
const TMaskTo extends string = '',
|
|
865
|
+
>(
|
|
866
|
+
props: LinkComponentProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
867
|
+
) => React.ReactElement
|
|
868
|
+
|
|
869
|
+
export interface LinkComponentRoute<
|
|
870
|
+
in out TDefaultFrom extends string = string,
|
|
871
|
+
> {
|
|
872
|
+
defaultFrom: TDefaultFrom;
|
|
873
|
+
<
|
|
874
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
875
|
+
const TTo extends string | undefined = undefined,
|
|
876
|
+
const TMaskTo extends string = '',
|
|
877
|
+
>(
|
|
878
|
+
props: LinkComponentProps<
|
|
879
|
+
'a',
|
|
880
|
+
TRouter,
|
|
881
|
+
this['defaultFrom'],
|
|
882
|
+
TTo,
|
|
883
|
+
this['defaultFrom'],
|
|
884
|
+
TMaskTo
|
|
885
|
+
>,
|
|
886
|
+
): React.ReactElement
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Creates a typed Link-like component that preserves TanStack Router's
|
|
891
|
+
* navigation semantics and type-safety while delegating rendering to the
|
|
892
|
+
* provided host component.
|
|
893
|
+
*
|
|
894
|
+
* Useful for integrating design system anchors/buttons while keeping
|
|
895
|
+
* router-aware props (eg. `to`, `params`, `search`, `preload`).
|
|
896
|
+
*
|
|
897
|
+
* @param Comp The host component to render (eg. a design-system Link/Button)
|
|
898
|
+
* @returns A router-aware component with the same API as `Link`.
|
|
899
|
+
* @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-link
|
|
900
|
+
*/
|
|
901
|
+
export function createLink<const TComp>(
|
|
902
|
+
Comp: Constrain<TComp, any, (props: CreateLinkProps) => ReactNode>,
|
|
903
|
+
): LinkComponent<TComp> {
|
|
904
|
+
return React.forwardRef(function CreatedLink(props, ref) {
|
|
905
|
+
return <Link {...(props as any)} _asChild={Comp} ref={ref} />
|
|
906
|
+
}) as any
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* A strongly-typed anchor component for declarative navigation.
|
|
911
|
+
* Handles path, search, hash and state updates with optional route preloading
|
|
912
|
+
* and active-state styling.
|
|
913
|
+
*
|
|
914
|
+
* Props:
|
|
915
|
+
* - `preload`: Controls route preloading (eg. 'intent', 'render', 'viewport', true/false)
|
|
916
|
+
* - `preloadDelay`: Delay in ms before preloading on hover
|
|
917
|
+
* - `activeProps`/`inactiveProps`: Additional props merged when link is active/inactive
|
|
918
|
+
* - `resetScroll`/`hashScrollIntoView`: Control scroll behavior on navigation
|
|
919
|
+
* - `viewTransition`/`startTransition`: Use View Transitions/React transitions for navigation
|
|
920
|
+
* - `ignoreBlocker`: Bypass registered blockers
|
|
921
|
+
*
|
|
922
|
+
* @returns An anchor-like element that navigates without full page reloads.
|
|
923
|
+
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/linkComponent
|
|
924
|
+
*/
|
|
925
|
+
export const Link: LinkComponent<'a'> = React.forwardRef<Element, any>(
|
|
926
|
+
(props, ref) => {
|
|
927
|
+
const { _asChild, ...rest } = props
|
|
928
|
+
const { type: _type, ...linkProps } = useLinkProps(rest as any, ref)
|
|
929
|
+
|
|
930
|
+
const children =
|
|
931
|
+
typeof rest.children === 'function'
|
|
932
|
+
? rest.children({
|
|
933
|
+
isActive: (linkProps as any)['data-status'] === 'active',
|
|
934
|
+
})
|
|
935
|
+
: rest.children
|
|
936
|
+
|
|
937
|
+
if (!_asChild) {
|
|
938
|
+
// the ReturnType of useLinkProps returns the correct type for a <a> element, not a general component that has a disabled prop
|
|
939
|
+
// @ts-expect-error
|
|
940
|
+
const { disabled: _, ...rest } = linkProps
|
|
941
|
+
return React.createElement('a', rest, children)
|
|
942
|
+
}
|
|
943
|
+
return React.createElement(_asChild, linkProps, children)
|
|
944
|
+
},
|
|
945
|
+
) as any
|
|
946
|
+
|
|
947
|
+
function isCtrlEvent(e: React.MouseEvent) {
|
|
948
|
+
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export type LinkOptionsFnOptions<
|
|
952
|
+
TOptions,
|
|
953
|
+
TComp,
|
|
954
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
955
|
+
> =
|
|
956
|
+
TOptions extends ReadonlyArray<any>
|
|
957
|
+
? ValidateLinkOptionsArray<TRouter, TOptions, string, TComp>
|
|
958
|
+
: ValidateLinkOptions<TRouter, TOptions, string, TComp>
|
|
959
|
+
|
|
960
|
+
export type LinkOptionsFn<TComp> = <
|
|
961
|
+
const TOptions,
|
|
962
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
963
|
+
>(
|
|
964
|
+
options: LinkOptionsFnOptions<TOptions, TComp, TRouter>,
|
|
965
|
+
) => TOptions
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Validate and reuse navigation options for `Link`, `navigate` or `redirect`.
|
|
969
|
+
* Accepts a literal options object and returns it typed for later spreading.
|
|
970
|
+
* @example
|
|
971
|
+
* const opts = linkOptions({ to: '/dashboard', search: { tab: 'home' } })
|
|
972
|
+
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/linkOptions
|
|
973
|
+
*/
|
|
974
|
+
export const linkOptions: LinkOptionsFn<'a'> = (options) => {
|
|
975
|
+
return options as any
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Type-check a literal object for use with `Link`, `navigate` or `redirect`.
|
|
980
|
+
* Use to validate and reuse navigation options across your app.
|
|
981
|
+
* @example
|
|
982
|
+
* const opts = linkOptions({ to: '/dashboard', search: { tab: 'home' } })
|
|
983
|
+
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/linkOptions
|
|
984
|
+
*/
|