@tanstack/react-router 1.20.1 → 1.20.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/cjs/Asset.cjs +41 -0
- package/dist/cjs/Asset.cjs.map +1 -0
- package/dist/cjs/Asset.d.cts +2 -0
- package/dist/cjs/CatchBoundary.cjs +16 -12
- package/dist/cjs/CatchBoundary.cjs.map +1 -1
- package/dist/cjs/CatchBoundary.d.cts +8 -32
- package/dist/cjs/ClientOnly.cjs +20 -0
- package/dist/cjs/ClientOnly.cjs.map +1 -0
- package/dist/cjs/ClientOnly.d.cts +29 -0
- package/dist/cjs/HeadContent.cjs +155 -0
- package/dist/cjs/HeadContent.cjs.map +1 -0
- package/dist/cjs/HeadContent.d.cts +7 -0
- package/dist/cjs/Match.cjs +252 -0
- package/dist/cjs/Match.cjs.map +1 -0
- package/dist/cjs/Match.d.cts +8 -0
- package/dist/cjs/Matches.cjs +39 -287
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +23 -83
- package/dist/cjs/RouterProvider.cjs +17 -140
- package/dist/cjs/RouterProvider.cjs.map +1 -1
- package/dist/cjs/RouterProvider.d.cts +8 -27
- 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 +28 -0
- package/dist/cjs/ScriptOnce.cjs.map +1 -0
- package/dist/cjs/ScriptOnce.d.cts +5 -0
- package/dist/cjs/Scripts.cjs +51 -0
- package/dist/cjs/Scripts.cjs.map +1 -0
- package/dist/cjs/Scripts.d.cts +1 -0
- package/dist/cjs/ScrollRestoration.cjs +39 -0
- package/dist/cjs/ScrollRestoration.cjs.map +1 -0
- package/dist/cjs/ScrollRestoration.d.cts +14 -0
- package/dist/cjs/Transitioner.cjs +115 -0
- package/dist/cjs/Transitioner.cjs.map +1 -0
- package/dist/cjs/Transitioner.d.cts +1 -0
- package/dist/cjs/awaited.cjs +12 -65
- package/dist/cjs/awaited.cjs.map +1 -1
- package/dist/cjs/awaited.d.cts +4 -4
- package/dist/cjs/fileRoute.cjs +41 -15
- package/dist/cjs/fileRoute.cjs.map +1 -1
- package/dist/cjs/fileRoute.d.cts +33 -108
- package/dist/cjs/history.d.cts +1 -0
- package/dist/cjs/index.cjs +216 -73
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +52 -29
- package/dist/cjs/lazyRouteComponent.cjs +40 -29
- package/dist/cjs/lazyRouteComponent.cjs.map +1 -1
- package/dist/cjs/lazyRouteComponent.d.cts +1 -1
- package/dist/cjs/link.cjs +212 -106
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/link.d.cts +41 -86
- 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 +9 -15
- package/dist/cjs/not-found.cjs.map +1 -1
- package/dist/cjs/not-found.d.cts +5 -22
- package/dist/cjs/renderRouteNotFound.cjs +22 -0
- package/dist/cjs/renderRouteNotFound.cjs.map +1 -0
- package/dist/cjs/renderRouteNotFound.d.cts +2 -0
- package/dist/cjs/route.cjs +110 -79
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +64 -361
- package/dist/cjs/router.cjs +12 -1237
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +69 -237
- package/dist/cjs/routerContext.cjs +1 -1
- package/dist/cjs/routerContext.cjs.map +1 -1
- package/dist/cjs/routerContext.d.cts +7 -2
- package/dist/cjs/scroll-restoration.cjs +16 -177
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.d.cts +1 -18
- package/dist/cjs/serializer.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 +138 -9
- package/dist/cjs/useBlocker.cjs.map +1 -1
- package/dist/cjs/useBlocker.d.cts +64 -7
- 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 +8 -0
- package/dist/cjs/useLoaderDeps.cjs +14 -0
- package/dist/cjs/useLoaderDeps.cjs.map +1 -0
- package/dist/cjs/useLoaderDeps.d.cts +8 -0
- package/dist/cjs/useLocation.cjs +10 -0
- package/dist/cjs/useLocation.cjs.map +1 -0
- package/dist/cjs/useLocation.d.cts +7 -0
- package/dist/cjs/useMatch.cjs +47 -0
- package/dist/cjs/useMatch.cjs.map +1 -0
- package/dist/cjs/useMatch.d.cts +10 -0
- package/dist/cjs/useNavigate.cjs +18 -19
- package/dist/cjs/useNavigate.cjs.map +1 -1
- package/dist/cjs/useNavigate.d.cts +4 -8
- package/dist/cjs/useParams.cjs +8 -8
- package/dist/cjs/useParams.cjs.map +1 -1
- package/dist/cjs/useParams.d.cts +9 -8
- package/dist/cjs/useRouteContext.cjs +3 -3
- package/dist/cjs/useRouteContext.cjs.map +1 -1
- package/dist/cjs/useRouteContext.d.cts +3 -7
- package/dist/cjs/useRouter.cjs.map +1 -1
- package/dist/cjs/useRouter.d.cts +3 -4
- package/dist/cjs/useRouterState.cjs +18 -1
- package/dist/cjs/useRouterState.cjs.map +1 -1
- package/dist/cjs/useRouterState.d.cts +8 -6
- package/dist/cjs/useSearch.cjs +7 -4
- package/dist/cjs/useSearch.cjs.map +1 -1
- package/dist/cjs/useSearch.d.cts +9 -7
- package/dist/cjs/utils.cjs +40 -122
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +46 -50
- package/dist/esm/Asset.d.ts +2 -0
- package/dist/esm/Asset.js +41 -0
- package/dist/esm/Asset.js.map +1 -0
- package/dist/esm/CatchBoundary.d.ts +8 -32
- package/dist/esm/CatchBoundary.js +16 -12
- package/dist/esm/CatchBoundary.js.map +1 -1
- package/dist/esm/ClientOnly.d.ts +29 -0
- package/dist/esm/ClientOnly.js +20 -0
- package/dist/esm/ClientOnly.js.map +1 -0
- package/dist/esm/HeadContent.d.ts +7 -0
- package/dist/esm/HeadContent.js +139 -0
- package/dist/esm/HeadContent.js.map +1 -0
- package/dist/esm/Match.d.ts +8 -0
- package/dist/esm/Match.js +235 -0
- package/dist/esm/Match.js.map +1 -0
- package/dist/esm/Matches.d.ts +23 -83
- package/dist/esm/Matches.js +36 -284
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/RouterProvider.d.ts +8 -27
- package/dist/esm/RouterProvider.js +20 -126
- package/dist/esm/RouterProvider.js.map +1 -1
- 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 +5 -0
- package/dist/esm/ScriptOnce.js +28 -0
- package/dist/esm/ScriptOnce.js.map +1 -0
- package/dist/esm/Scripts.d.ts +1 -0
- package/dist/esm/Scripts.js +51 -0
- package/dist/esm/Scripts.js.map +1 -0
- package/dist/esm/ScrollRestoration.d.ts +14 -0
- package/dist/esm/ScrollRestoration.js +39 -0
- package/dist/esm/ScrollRestoration.js.map +1 -0
- package/dist/esm/Transitioner.d.ts +1 -0
- package/dist/esm/Transitioner.js +98 -0
- package/dist/esm/Transitioner.js.map +1 -0
- package/dist/esm/awaited.d.ts +4 -4
- package/dist/esm/awaited.js +12 -65
- package/dist/esm/awaited.js.map +1 -1
- package/dist/esm/fileRoute.d.ts +33 -108
- package/dist/esm/fileRoute.js +38 -12
- package/dist/esm/fileRoute.js.map +1 -1
- package/dist/esm/history.d.ts +1 -0
- package/dist/esm/index.d.ts +52 -29
- package/dist/esm/index.js +41 -29
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lazyRouteComponent.d.ts +1 -1
- package/dist/esm/lazyRouteComponent.js +40 -29
- package/dist/esm/lazyRouteComponent.js.map +1 -1
- package/dist/esm/link.d.ts +41 -86
- package/dist/esm/link.js +212 -106
- package/dist/esm/link.js.map +1 -1
- 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 +5 -22
- package/dist/esm/not-found.js +9 -15
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/renderRouteNotFound.d.ts +2 -0
- package/dist/esm/renderRouteNotFound.js +22 -0
- package/dist/esm/renderRouteNotFound.js.map +1 -0
- package/dist/esm/route.d.ts +64 -361
- package/dist/esm/route.js +103 -72
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +69 -237
- package/dist/esm/router.js +13 -1238
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/routerContext.d.ts +7 -2
- package/dist/esm/routerContext.js +1 -1
- package/dist/esm/routerContext.js.map +1 -1
- package/dist/esm/scroll-restoration.d.ts +1 -18
- package/dist/esm/scroll-restoration.js +17 -161
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/serializer.d.ts +6 -0
- package/dist/esm/structuralSharing.d.ts +8 -0
- package/dist/esm/typePrimitives.d.ts +16 -0
- package/dist/esm/useBlocker.d.ts +64 -7
- package/dist/esm/useBlocker.js +138 -9
- package/dist/esm/useBlocker.js.map +1 -1
- 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 +8 -0
- package/dist/esm/useLoaderData.js +15 -0
- package/dist/esm/useLoaderData.js.map +1 -0
- package/dist/esm/useLoaderDeps.d.ts +8 -0
- package/dist/esm/useLoaderDeps.js +14 -0
- package/dist/esm/useLoaderDeps.js.map +1 -0
- package/dist/esm/useLocation.d.ts +7 -0
- package/dist/esm/useLocation.js +10 -0
- package/dist/esm/useLocation.js.map +1 -0
- package/dist/esm/useMatch.d.ts +10 -0
- package/dist/esm/useMatch.js +30 -0
- package/dist/esm/useMatch.js.map +1 -0
- package/dist/esm/useNavigate.d.ts +4 -8
- package/dist/esm/useNavigate.js +18 -19
- package/dist/esm/useNavigate.js.map +1 -1
- package/dist/esm/useParams.d.ts +9 -8
- package/dist/esm/useParams.js +8 -8
- package/dist/esm/useParams.js.map +1 -1
- package/dist/esm/useRouteContext.d.ts +3 -7
- package/dist/esm/useRouteContext.js +2 -2
- package/dist/esm/useRouteContext.js.map +1 -1
- package/dist/esm/useRouter.d.ts +3 -4
- package/dist/esm/useRouter.js.map +1 -1
- package/dist/esm/useRouterState.d.ts +8 -6
- package/dist/esm/useRouterState.js +18 -1
- package/dist/esm/useRouterState.js.map +1 -1
- package/dist/esm/useSearch.d.ts +9 -7
- package/dist/esm/useSearch.js +6 -3
- package/dist/esm/useSearch.js.map +1 -1
- package/dist/esm/utils.d.ts +46 -50
- package/dist/esm/utils.js +41 -123
- package/dist/esm/utils.js.map +1 -1
- package/package.json +30 -31
- package/src/Asset.tsx +40 -0
- package/src/CatchBoundary.tsx +35 -19
- package/src/ClientOnly.tsx +68 -0
- package/src/HeadContent.tsx +174 -0
- package/src/Match.tsx +330 -0
- package/src/Matches.tsx +149 -558
- package/src/RouterProvider.tsx +58 -212
- package/src/SafeFragment.tsx +5 -0
- package/src/ScriptOnce.tsx +32 -0
- package/src/Scripts.tsx +65 -0
- package/src/ScrollRestoration.tsx +69 -0
- package/src/Transitioner.tsx +130 -0
- package/src/awaited.tsx +16 -87
- package/src/fileRoute.ts +145 -248
- package/src/history.ts +2 -1
- package/src/index.tsx +368 -30
- package/src/lazyRouteComponent.tsx +68 -54
- package/src/link.tsx +397 -522
- package/src/matchContext.tsx +8 -0
- package/src/not-found.tsx +13 -34
- package/src/renderRouteNotFound.tsx +27 -0
- package/src/route.tsx +572 -0
- package/src/router.ts +99 -2067
- package/src/routerContext.tsx +8 -2
- package/src/scroll-restoration.tsx +23 -224
- package/src/serializer.ts +7 -0
- package/src/structuralSharing.ts +47 -0
- package/src/typePrimitives.ts +84 -0
- package/src/useBlocker.tsx +297 -15
- package/src/useCanGoBack.ts +5 -0
- package/src/useLoaderData.tsx +80 -0
- package/src/useLoaderDeps.tsx +58 -0
- package/src/useLocation.tsx +41 -0
- package/src/useMatch.tsx +119 -0
- package/src/useNavigate.tsx +41 -61
- package/src/useParams.tsx +88 -23
- package/src/useRouteContext.ts +24 -18
- package/src/useRouter.tsx +4 -5
- package/src/useRouterState.tsx +52 -10
- package/src/useSearch.tsx +87 -24
- package/src/utils.ts +97 -312
- package/dist/cjs/createServerFn.cjs +0 -40
- package/dist/cjs/createServerFn.cjs.map +0 -1
- package/dist/cjs/createServerFn.d.cts +0 -44
- package/dist/cjs/defer.cjs +0 -30
- package/dist/cjs/defer.cjs.map +0 -1
- package/dist/cjs/defer.d.cts +0 -25
- package/dist/cjs/location.d.cts +0 -12
- package/dist/cjs/path.cjs +0 -213
- package/dist/cjs/path.cjs.map +0 -1
- package/dist/cjs/path.d.cts +0 -24
- package/dist/cjs/qss.cjs +0 -45
- package/dist/cjs/qss.cjs.map +0 -1
- package/dist/cjs/qss.d.cts +0 -2
- package/dist/cjs/redirects.cjs +0 -16
- package/dist/cjs/redirects.cjs.map +0 -1
- package/dist/cjs/redirects.d.cts +0 -18
- package/dist/cjs/routeInfo.d.cts +0 -31
- package/dist/cjs/searchParams.cjs +0 -63
- package/dist/cjs/searchParams.cjs.map +0 -1
- package/dist/cjs/searchParams.d.cts +0 -7
- package/dist/esm/createServerFn.d.ts +0 -44
- package/dist/esm/createServerFn.js +0 -40
- package/dist/esm/createServerFn.js.map +0 -1
- package/dist/esm/defer.d.ts +0 -25
- package/dist/esm/defer.js +0 -30
- package/dist/esm/defer.js.map +0 -1
- package/dist/esm/location.d.ts +0 -12
- package/dist/esm/path.d.ts +0 -24
- package/dist/esm/path.js +0 -213
- package/dist/esm/path.js.map +0 -1
- package/dist/esm/qss.d.ts +0 -2
- package/dist/esm/qss.js +0 -45
- package/dist/esm/qss.js.map +0 -1
- package/dist/esm/redirects.d.ts +0 -18
- package/dist/esm/redirects.js +0 -16
- package/dist/esm/redirects.js.map +0 -1
- package/dist/esm/routeInfo.d.ts +0 -31
- package/dist/esm/searchParams.d.ts +0 -7
- package/dist/esm/searchParams.js +0 -63
- package/dist/esm/searchParams.js.map +0 -1
- package/src/createServerFn.ts +0 -107
- package/src/defer.ts +0 -70
- package/src/location.ts +0 -13
- package/src/path.ts +0 -280
- package/src/qss.ts +0 -53
- package/src/redirects.ts +0 -56
- package/src/route.ts +0 -1356
- package/src/routeInfo.ts +0 -63
- package/src/searchParams.ts +0 -79
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
export interface ClientOnlyProps {
|
|
4
|
+
/**
|
|
5
|
+
* The children to render if the JS is loaded.
|
|
6
|
+
*/
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
/**
|
|
9
|
+
* The fallback component to render if the JS is not yet loaded.
|
|
10
|
+
*/
|
|
11
|
+
fallback?: React.ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Render the children only after the JS has loaded client-side. Use an optional
|
|
16
|
+
* fallback component if the JS is not yet loaded.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* Render a Chart component if JS loads, renders a simple FakeChart
|
|
20
|
+
* component server-side or if there is no JS. The FakeChart can have only the
|
|
21
|
+
* UI without the behavior or be a loading spinner or skeleton.
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* return (
|
|
25
|
+
* <ClientOnly fallback={<FakeChart />}>
|
|
26
|
+
* <Chart />
|
|
27
|
+
* </ClientOnly>
|
|
28
|
+
* )
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function ClientOnly({ children, fallback = null }: ClientOnlyProps) {
|
|
32
|
+
return useHydrated() ? (
|
|
33
|
+
<React.Fragment>{children}</React.Fragment>
|
|
34
|
+
) : (
|
|
35
|
+
<React.Fragment>{fallback}</React.Fragment>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Return a boolean indicating if the JS has been hydrated already.
|
|
41
|
+
* When doing Server-Side Rendering, the result will always be false.
|
|
42
|
+
* When doing Client-Side Rendering, the result will always be false on the
|
|
43
|
+
* first render and true from then on. Even if a new component renders it will
|
|
44
|
+
* always start with true.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* // Disable a button that needs JS to work.
|
|
49
|
+
* let hydrated = useHydrated()
|
|
50
|
+
* return (
|
|
51
|
+
* <button type="button" disabled={!hydrated} onClick={doSomethingCustom}>
|
|
52
|
+
* Click me
|
|
53
|
+
* </button>
|
|
54
|
+
* )
|
|
55
|
+
* ```
|
|
56
|
+
* @returns True if the JS has been hydrated already, false otherwise.
|
|
57
|
+
*/
|
|
58
|
+
function useHydrated(): boolean {
|
|
59
|
+
return React.useSyncExternalStore(
|
|
60
|
+
subscribe,
|
|
61
|
+
() => true,
|
|
62
|
+
() => false,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function subscribe() {
|
|
67
|
+
return () => {}
|
|
68
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { Asset } from './Asset'
|
|
3
|
+
import { useRouter } from './useRouter'
|
|
4
|
+
import { useRouterState } from './useRouterState'
|
|
5
|
+
import type { RouterManagedTag } from '@tanstack/router-core'
|
|
6
|
+
|
|
7
|
+
export const useTags = () => {
|
|
8
|
+
const router = useRouter()
|
|
9
|
+
|
|
10
|
+
const routeMeta = useRouterState({
|
|
11
|
+
select: (state) => {
|
|
12
|
+
return state.matches.map((match) => match.meta!).filter(Boolean)
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const meta: Array<RouterManagedTag> = React.useMemo(() => {
|
|
17
|
+
const resultMeta: Array<RouterManagedTag> = []
|
|
18
|
+
const metaByAttribute: Record<string, true> = {}
|
|
19
|
+
let title: RouterManagedTag | undefined
|
|
20
|
+
;[...routeMeta].reverse().forEach((metas) => {
|
|
21
|
+
;[...metas].reverse().forEach((m) => {
|
|
22
|
+
if (!m) return
|
|
23
|
+
|
|
24
|
+
if (m.title) {
|
|
25
|
+
if (!title) {
|
|
26
|
+
title = {
|
|
27
|
+
tag: 'title',
|
|
28
|
+
children: m.title,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
const attribute = m.name ?? m.property
|
|
33
|
+
if (attribute) {
|
|
34
|
+
if (metaByAttribute[attribute]) {
|
|
35
|
+
return
|
|
36
|
+
} else {
|
|
37
|
+
metaByAttribute[attribute] = true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
resultMeta.push({
|
|
42
|
+
tag: 'meta',
|
|
43
|
+
attrs: {
|
|
44
|
+
...m,
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
if (title) {
|
|
52
|
+
resultMeta.push(title)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
resultMeta.reverse()
|
|
56
|
+
|
|
57
|
+
return resultMeta
|
|
58
|
+
}, [routeMeta])
|
|
59
|
+
|
|
60
|
+
const links = useRouterState({
|
|
61
|
+
select: (state) => {
|
|
62
|
+
const constructed = state.matches
|
|
63
|
+
.map((match) => match.links!)
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.flat(1)
|
|
66
|
+
.map((link) => ({
|
|
67
|
+
tag: 'link',
|
|
68
|
+
attrs: {
|
|
69
|
+
...link,
|
|
70
|
+
},
|
|
71
|
+
})) satisfies Array<RouterManagedTag>
|
|
72
|
+
|
|
73
|
+
const manifest = router.ssr?.manifest
|
|
74
|
+
|
|
75
|
+
// These are the assets extracted from the ViteManifest
|
|
76
|
+
// using the `startManifestPlugin`
|
|
77
|
+
const assets = state.matches
|
|
78
|
+
.map((match) => manifest?.routes[match.routeId]?.assets ?? [])
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.flat(1)
|
|
81
|
+
.filter((asset) => asset.tag === 'link')
|
|
82
|
+
.map(
|
|
83
|
+
(asset) =>
|
|
84
|
+
({
|
|
85
|
+
tag: 'link',
|
|
86
|
+
attrs: {
|
|
87
|
+
...asset.attrs,
|
|
88
|
+
suppressHydrationWarning: true,
|
|
89
|
+
},
|
|
90
|
+
}) satisfies RouterManagedTag,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return [...constructed, ...assets]
|
|
94
|
+
},
|
|
95
|
+
structuralSharing: true as any,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const preloadMeta = useRouterState({
|
|
99
|
+
select: (state) => {
|
|
100
|
+
const preloadMeta: Array<RouterManagedTag> = []
|
|
101
|
+
|
|
102
|
+
state.matches
|
|
103
|
+
.map((match) => router.looseRoutesById[match.routeId]!)
|
|
104
|
+
.forEach((route) =>
|
|
105
|
+
router.ssr?.manifest?.routes[route.id]?.preloads
|
|
106
|
+
?.filter(Boolean)
|
|
107
|
+
.forEach((preload) => {
|
|
108
|
+
preloadMeta.push({
|
|
109
|
+
tag: 'link',
|
|
110
|
+
attrs: {
|
|
111
|
+
rel: 'modulepreload',
|
|
112
|
+
href: preload,
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
}),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return preloadMeta
|
|
119
|
+
},
|
|
120
|
+
structuralSharing: true as any,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const headScripts = useRouterState({
|
|
124
|
+
select: (state) =>
|
|
125
|
+
(
|
|
126
|
+
state.matches
|
|
127
|
+
.map((match) => match.headScripts!)
|
|
128
|
+
.flat(1)
|
|
129
|
+
.filter(Boolean) as Array<RouterManagedTag>
|
|
130
|
+
).map(({ children, ...script }) => ({
|
|
131
|
+
tag: 'script',
|
|
132
|
+
attrs: {
|
|
133
|
+
...script,
|
|
134
|
+
},
|
|
135
|
+
children,
|
|
136
|
+
})),
|
|
137
|
+
structuralSharing: true as any,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return uniqBy(
|
|
141
|
+
[
|
|
142
|
+
...meta,
|
|
143
|
+
...preloadMeta,
|
|
144
|
+
...links,
|
|
145
|
+
...headScripts,
|
|
146
|
+
] as Array<RouterManagedTag>,
|
|
147
|
+
(d) => {
|
|
148
|
+
return JSON.stringify(d)
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @description The `HeadContent` component is used to render meta tags, links, and scripts for the current route.
|
|
155
|
+
* It should be rendered in the `<head>` of your document.
|
|
156
|
+
*/
|
|
157
|
+
export function HeadContent() {
|
|
158
|
+
const tags = useTags()
|
|
159
|
+
return tags.map((tag) => (
|
|
160
|
+
<Asset {...tag} key={`tsr-meta-${JSON.stringify(tag)}`} />
|
|
161
|
+
))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {
|
|
165
|
+
const seen = new Set<string>()
|
|
166
|
+
return arr.filter((item) => {
|
|
167
|
+
const key = fn(item)
|
|
168
|
+
if (seen.has(key)) {
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
seen.add(key)
|
|
172
|
+
return true
|
|
173
|
+
})
|
|
174
|
+
}
|
package/src/Match.tsx
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import invariant from 'tiny-invariant'
|
|
3
|
+
import warning from 'tiny-warning'
|
|
4
|
+
import {
|
|
5
|
+
createControlledPromise,
|
|
6
|
+
getLocationChangeInfo,
|
|
7
|
+
isNotFound,
|
|
8
|
+
isRedirect,
|
|
9
|
+
pick,
|
|
10
|
+
rootRouteId,
|
|
11
|
+
} from '@tanstack/router-core'
|
|
12
|
+
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
|
|
13
|
+
import { useRouterState } from './useRouterState'
|
|
14
|
+
import { useRouter } from './useRouter'
|
|
15
|
+
import { CatchNotFound } from './not-found'
|
|
16
|
+
import { matchContext } from './matchContext'
|
|
17
|
+
import { SafeFragment } from './SafeFragment'
|
|
18
|
+
import { renderRouteNotFound } from './renderRouteNotFound'
|
|
19
|
+
import { ScrollRestoration } from './scroll-restoration'
|
|
20
|
+
import type { AnyRoute, ParsedLocation } from '@tanstack/router-core'
|
|
21
|
+
|
|
22
|
+
export const Match = React.memo(function MatchImpl({
|
|
23
|
+
matchId,
|
|
24
|
+
}: {
|
|
25
|
+
matchId: string
|
|
26
|
+
}) {
|
|
27
|
+
const router = useRouter()
|
|
28
|
+
const routeId = useRouterState({
|
|
29
|
+
select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
invariant(
|
|
33
|
+
routeId,
|
|
34
|
+
`Could not find routeId for matchId "${matchId}". Please file an issue!`,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const route: AnyRoute = router.routesById[routeId]
|
|
38
|
+
|
|
39
|
+
const PendingComponent =
|
|
40
|
+
route.options.pendingComponent ?? router.options.defaultPendingComponent
|
|
41
|
+
|
|
42
|
+
const pendingElement = PendingComponent ? <PendingComponent /> : null
|
|
43
|
+
|
|
44
|
+
const routeErrorComponent =
|
|
45
|
+
route.options.errorComponent ?? router.options.defaultErrorComponent
|
|
46
|
+
|
|
47
|
+
const routeOnCatch = route.options.onCatch ?? router.options.defaultOnCatch
|
|
48
|
+
|
|
49
|
+
const routeNotFoundComponent = route.isRoot
|
|
50
|
+
? // If it's the root route, use the globalNotFound option, with fallback to the notFoundRoute's component
|
|
51
|
+
(route.options.notFoundComponent ??
|
|
52
|
+
router.options.notFoundRoute?.options.component)
|
|
53
|
+
: route.options.notFoundComponent
|
|
54
|
+
|
|
55
|
+
const ResolvedSuspenseBoundary =
|
|
56
|
+
// If we're on the root route, allow forcefully wrapping in suspense
|
|
57
|
+
(!route.isRoot || route.options.wrapInSuspense) &&
|
|
58
|
+
(route.options.wrapInSuspense ??
|
|
59
|
+
PendingComponent ??
|
|
60
|
+
(route.options.errorComponent as any)?.preload)
|
|
61
|
+
? React.Suspense
|
|
62
|
+
: SafeFragment
|
|
63
|
+
|
|
64
|
+
const ResolvedCatchBoundary = routeErrorComponent
|
|
65
|
+
? CatchBoundary
|
|
66
|
+
: SafeFragment
|
|
67
|
+
|
|
68
|
+
const ResolvedNotFoundBoundary = routeNotFoundComponent
|
|
69
|
+
? CatchNotFound
|
|
70
|
+
: SafeFragment
|
|
71
|
+
|
|
72
|
+
const resetKey = useRouterState({
|
|
73
|
+
select: (s) => s.loadedAt,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const parentRouteId = useRouterState({
|
|
77
|
+
select: (s) => {
|
|
78
|
+
const index = s.matches.findIndex((d) => d.id === matchId)
|
|
79
|
+
return s.matches[index - 1]?.routeId as string
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<>
|
|
85
|
+
<matchContext.Provider value={matchId}>
|
|
86
|
+
<ResolvedSuspenseBoundary fallback={pendingElement}>
|
|
87
|
+
<ResolvedCatchBoundary
|
|
88
|
+
getResetKey={() => resetKey}
|
|
89
|
+
errorComponent={routeErrorComponent || ErrorComponent}
|
|
90
|
+
onCatch={(error, errorInfo) => {
|
|
91
|
+
// Forward not found errors (we don't want to show the error component for these)
|
|
92
|
+
if (isNotFound(error)) throw error
|
|
93
|
+
warning(false, `Error in route match: ${matchId}`)
|
|
94
|
+
routeOnCatch?.(error, errorInfo)
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<ResolvedNotFoundBoundary
|
|
98
|
+
fallback={(error) => {
|
|
99
|
+
// If the current not found handler doesn't exist or it has a
|
|
100
|
+
// route ID which doesn't match the current route, rethrow the error
|
|
101
|
+
if (
|
|
102
|
+
!routeNotFoundComponent ||
|
|
103
|
+
(error.routeId && error.routeId !== routeId) ||
|
|
104
|
+
(!error.routeId && !route.isRoot)
|
|
105
|
+
)
|
|
106
|
+
throw error
|
|
107
|
+
|
|
108
|
+
return React.createElement(routeNotFoundComponent, error as any)
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<MatchInner matchId={matchId} />
|
|
112
|
+
</ResolvedNotFoundBoundary>
|
|
113
|
+
</ResolvedCatchBoundary>
|
|
114
|
+
</ResolvedSuspenseBoundary>
|
|
115
|
+
</matchContext.Provider>
|
|
116
|
+
{parentRouteId === rootRouteId && router.options.scrollRestoration ? (
|
|
117
|
+
<>
|
|
118
|
+
<OnRendered />
|
|
119
|
+
<ScrollRestoration />
|
|
120
|
+
</>
|
|
121
|
+
) : null}
|
|
122
|
+
</>
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// On Rendered can't happen above the root layout because it actually
|
|
127
|
+
// renders a dummy dom element to track the rendered state of the app.
|
|
128
|
+
// We render a script tag with a key that changes based on the current
|
|
129
|
+
// location state.key. Also, because it's below the root layout, it
|
|
130
|
+
// allows us to fire onRendered events even after a hydration mismatch
|
|
131
|
+
// error that occurred above the root layout (like bad head/link tags,
|
|
132
|
+
// which is common).
|
|
133
|
+
function OnRendered() {
|
|
134
|
+
const router = useRouter()
|
|
135
|
+
|
|
136
|
+
const prevLocationRef = React.useRef<undefined | ParsedLocation<{}>>(
|
|
137
|
+
undefined,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<script
|
|
142
|
+
key={router.latestLocation.state.key}
|
|
143
|
+
suppressHydrationWarning
|
|
144
|
+
ref={(el) => {
|
|
145
|
+
if (
|
|
146
|
+
el &&
|
|
147
|
+
(prevLocationRef.current === undefined ||
|
|
148
|
+
prevLocationRef.current.href !== router.latestLocation.href)
|
|
149
|
+
) {
|
|
150
|
+
router.emit({
|
|
151
|
+
type: 'onRendered',
|
|
152
|
+
...getLocationChangeInfo(router.state),
|
|
153
|
+
})
|
|
154
|
+
prevLocationRef.current = router.latestLocation
|
|
155
|
+
}
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const MatchInner = React.memo(function MatchInnerImpl({
|
|
162
|
+
matchId,
|
|
163
|
+
}: {
|
|
164
|
+
matchId: string
|
|
165
|
+
}): any {
|
|
166
|
+
const router = useRouter()
|
|
167
|
+
|
|
168
|
+
const { match, key, routeId } = useRouterState({
|
|
169
|
+
select: (s) => {
|
|
170
|
+
const matchIndex = s.matches.findIndex((d) => d.id === matchId)
|
|
171
|
+
const match = s.matches[matchIndex]!
|
|
172
|
+
const routeId = match.routeId as string
|
|
173
|
+
|
|
174
|
+
const remountFn =
|
|
175
|
+
(router.routesById[routeId] as AnyRoute).options.remountDeps ??
|
|
176
|
+
router.options.defaultRemountDeps
|
|
177
|
+
const remountDeps = remountFn?.({
|
|
178
|
+
routeId,
|
|
179
|
+
loaderDeps: match.loaderDeps,
|
|
180
|
+
params: match._strictParams,
|
|
181
|
+
search: match._strictSearch,
|
|
182
|
+
})
|
|
183
|
+
const key = remountDeps ? JSON.stringify(remountDeps) : undefined
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
key,
|
|
187
|
+
routeId,
|
|
188
|
+
match: pick(match, ['id', 'status', 'error']),
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
structuralSharing: true as any,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const route = router.routesById[routeId] as AnyRoute
|
|
195
|
+
|
|
196
|
+
const out = React.useMemo(() => {
|
|
197
|
+
const Comp = route.options.component ?? router.options.defaultComponent
|
|
198
|
+
if (Comp) {
|
|
199
|
+
return <Comp key={key} />
|
|
200
|
+
}
|
|
201
|
+
return <Outlet />
|
|
202
|
+
}, [key, route.options.component, router.options.defaultComponent])
|
|
203
|
+
|
|
204
|
+
const RouteErrorComponent =
|
|
205
|
+
(route.options.errorComponent ?? router.options.defaultErrorComponent) ||
|
|
206
|
+
ErrorComponent
|
|
207
|
+
|
|
208
|
+
if (match.status === 'notFound') {
|
|
209
|
+
invariant(isNotFound(match.error), 'Expected a notFound error')
|
|
210
|
+
return renderRouteNotFound(router, route, match.error)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (match.status === 'redirected') {
|
|
214
|
+
// Redirects should be handled by the router transition. If we happen to
|
|
215
|
+
// encounter a redirect here, it's a bug. Let's warn, but render nothing.
|
|
216
|
+
invariant(isRedirect(match.error), 'Expected a redirect error')
|
|
217
|
+
|
|
218
|
+
// warning(
|
|
219
|
+
// false,
|
|
220
|
+
// 'Tried to render a redirected route match! This is a weird circumstance, please file an issue!',
|
|
221
|
+
// )
|
|
222
|
+
throw router.getMatch(match.id)?.loadPromise
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (match.status === 'error') {
|
|
226
|
+
// If we're on the server, we need to use React's new and super
|
|
227
|
+
// wonky api for throwing errors from a server side render inside
|
|
228
|
+
// of a suspense boundary. This is the only way to get
|
|
229
|
+
// renderToPipeableStream to not hang indefinitely.
|
|
230
|
+
// We'll serialize the error and rethrow it on the client.
|
|
231
|
+
if (router.isServer) {
|
|
232
|
+
return (
|
|
233
|
+
<RouteErrorComponent
|
|
234
|
+
error={match.error as any}
|
|
235
|
+
reset={undefined as any}
|
|
236
|
+
info={{
|
|
237
|
+
componentStack: '',
|
|
238
|
+
}}
|
|
239
|
+
/>
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
throw match.error
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (match.status === 'pending') {
|
|
247
|
+
// We're pending, and if we have a minPendingMs, we need to wait for it
|
|
248
|
+
const pendingMinMs =
|
|
249
|
+
route.options.pendingMinMs ?? router.options.defaultPendingMinMs
|
|
250
|
+
|
|
251
|
+
if (pendingMinMs && !router.getMatch(match.id)?.minPendingPromise) {
|
|
252
|
+
// Create a promise that will resolve after the minPendingMs
|
|
253
|
+
if (!router.isServer) {
|
|
254
|
+
const minPendingPromise = createControlledPromise<void>()
|
|
255
|
+
|
|
256
|
+
Promise.resolve().then(() => {
|
|
257
|
+
router.updateMatch(match.id, (prev) => ({
|
|
258
|
+
...prev,
|
|
259
|
+
minPendingPromise,
|
|
260
|
+
}))
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
minPendingPromise.resolve()
|
|
265
|
+
|
|
266
|
+
// We've handled the minPendingPromise, so we can delete it
|
|
267
|
+
router.updateMatch(match.id, (prev) => ({
|
|
268
|
+
...prev,
|
|
269
|
+
minPendingPromise: undefined,
|
|
270
|
+
}))
|
|
271
|
+
}, pendingMinMs)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
throw router.getMatch(match.id)?.loadPromise
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return out
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
export const Outlet = React.memo(function OutletImpl() {
|
|
281
|
+
const router = useRouter()
|
|
282
|
+
const matchId = React.useContext(matchContext)
|
|
283
|
+
const routeId = useRouterState({
|
|
284
|
+
select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
const route = router.routesById[routeId]!
|
|
288
|
+
|
|
289
|
+
const parentGlobalNotFound = useRouterState({
|
|
290
|
+
select: (s) => {
|
|
291
|
+
const matches = s.matches
|
|
292
|
+
const parentMatch = matches.find((d) => d.id === matchId)
|
|
293
|
+
invariant(
|
|
294
|
+
parentMatch,
|
|
295
|
+
`Could not find parent match for matchId "${matchId}"`,
|
|
296
|
+
)
|
|
297
|
+
return parentMatch.globalNotFound
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const childMatchId = useRouterState({
|
|
302
|
+
select: (s) => {
|
|
303
|
+
const matches = s.matches
|
|
304
|
+
const index = matches.findIndex((d) => d.id === matchId)
|
|
305
|
+
return matches[index + 1]?.id
|
|
306
|
+
},
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
if (parentGlobalNotFound) {
|
|
310
|
+
return renderRouteNotFound(router, route, undefined)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!childMatchId) {
|
|
314
|
+
return null
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const nextMatch = <Match matchId={childMatchId} />
|
|
318
|
+
|
|
319
|
+
const pendingElement = router.options.defaultPendingComponent ? (
|
|
320
|
+
<router.options.defaultPendingComponent />
|
|
321
|
+
) : null
|
|
322
|
+
|
|
323
|
+
if (matchId === rootRouteId) {
|
|
324
|
+
return (
|
|
325
|
+
<React.Suspense fallback={pendingElement}>{nextMatch}</React.Suspense>
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return nextMatch
|
|
330
|
+
})
|