@tanstack/router-core 0.0.1-beta.8 → 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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/cjs/Matches.cjs +13 -0
- package/dist/cjs/Matches.cjs.map +1 -0
- package/dist/cjs/Matches.d.cts +109 -0
- package/dist/cjs/RouterProvider.d.cts +26 -0
- package/dist/cjs/defer.cjs +25 -0
- package/dist/cjs/defer.cjs.map +1 -0
- package/dist/cjs/defer.d.cts +20 -0
- package/dist/cjs/fileRoute.d.cts +23 -0
- package/dist/cjs/history.d.cts +8 -0
- package/dist/cjs/index.cjs +80 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +41 -0
- package/dist/cjs/link.cjs +5 -0
- package/dist/cjs/link.cjs.map +1 -0
- package/dist/cjs/link.d.cts +200 -0
- package/dist/cjs/location.d.cts +12 -0
- package/dist/cjs/manifest.d.cts +24 -0
- package/dist/cjs/not-found.cjs +13 -0
- package/dist/cjs/not-found.cjs.map +1 -0
- package/dist/cjs/not-found.d.cts +20 -0
- package/dist/cjs/path.cjs +412 -0
- package/dist/cjs/path.cjs.map +1 -0
- package/dist/cjs/path.d.cts +56 -0
- package/dist/cjs/qss.cjs +38 -0
- package/dist/cjs/qss.cjs.map +1 -0
- package/dist/cjs/qss.d.cts +22 -0
- package/dist/cjs/redirect.cjs +34 -0
- package/dist/cjs/redirect.cjs.map +1 -0
- package/dist/cjs/redirect.d.cts +38 -0
- package/dist/cjs/root.cjs +5 -0
- package/dist/cjs/root.cjs.map +1 -0
- package/dist/cjs/root.d.cts +2 -0
- package/dist/cjs/route.cjs +119 -0
- package/dist/cjs/route.cjs.map +1 -0
- package/dist/cjs/route.d.cts +422 -0
- package/dist/cjs/routeInfo.d.cts +54 -0
- package/dist/cjs/router.cjs +1800 -0
- package/dist/cjs/router.cjs.map +1 -0
- package/dist/cjs/router.d.cts +630 -0
- package/dist/cjs/scroll-restoration.cjs +196 -0
- package/dist/cjs/scroll-restoration.cjs.map +1 -0
- package/dist/cjs/scroll-restoration.d.cts +38 -0
- package/dist/cjs/searchMiddleware.cjs +42 -0
- package/dist/cjs/searchMiddleware.cjs.map +1 -0
- package/dist/cjs/searchMiddleware.d.cts +5 -0
- package/dist/cjs/searchParams.cjs +61 -0
- package/dist/cjs/searchParams.cjs.map +1 -0
- package/dist/cjs/searchParams.d.cts +7 -0
- package/dist/cjs/serializer.d.cts +22 -0
- package/dist/cjs/structuralSharing.d.cts +4 -0
- package/dist/cjs/typePrimitives.d.cts +65 -0
- package/dist/cjs/useLoaderData.d.cts +5 -0
- package/dist/cjs/useLoaderDeps.d.cts +5 -0
- package/dist/cjs/useNavigate.d.cts +3 -0
- package/dist/cjs/useParams.d.cts +5 -0
- package/dist/cjs/useRouteContext.d.cts +9 -0
- package/dist/cjs/useSearch.d.cts +5 -0
- package/dist/cjs/utils.cjs +160 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +105 -0
- package/dist/cjs/validators.d.cts +51 -0
- package/dist/esm/Matches.d.ts +109 -0
- package/dist/esm/Matches.js +13 -0
- package/dist/esm/Matches.js.map +1 -0
- package/dist/esm/RouterProvider.d.ts +26 -0
- package/dist/esm/defer.d.ts +20 -0
- package/dist/esm/defer.js +25 -0
- package/dist/esm/defer.js.map +1 -0
- package/dist/esm/fileRoute.d.ts +23 -0
- package/dist/esm/history.d.ts +8 -0
- package/dist/esm/index.d.ts +41 -0
- package/dist/esm/index.js +80 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/link.d.ts +200 -0
- package/dist/esm/link.js +5 -0
- package/dist/esm/link.js.map +1 -0
- package/dist/esm/location.d.ts +12 -0
- package/dist/esm/manifest.d.ts +24 -0
- package/dist/esm/not-found.d.ts +20 -0
- package/dist/esm/not-found.js +13 -0
- package/dist/esm/not-found.js.map +1 -0
- package/dist/esm/path.d.ts +56 -0
- package/dist/esm/path.js +412 -0
- package/dist/esm/path.js.map +1 -0
- package/dist/esm/qss.d.ts +22 -0
- package/dist/esm/qss.js +38 -0
- package/dist/esm/qss.js.map +1 -0
- package/dist/esm/redirect.d.ts +38 -0
- package/dist/esm/redirect.js +34 -0
- package/dist/esm/redirect.js.map +1 -0
- package/dist/esm/root.d.ts +2 -0
- package/dist/esm/root.js +5 -0
- package/dist/esm/root.js.map +1 -0
- package/dist/esm/route.d.ts +422 -0
- package/dist/esm/route.js +119 -0
- package/dist/esm/route.js.map +1 -0
- package/dist/esm/routeInfo.d.ts +54 -0
- package/dist/esm/router.d.ts +630 -0
- package/dist/esm/router.js +1800 -0
- package/dist/esm/router.js.map +1 -0
- package/dist/esm/scroll-restoration.d.ts +38 -0
- package/dist/esm/scroll-restoration.js +196 -0
- package/dist/esm/scroll-restoration.js.map +1 -0
- package/dist/esm/searchMiddleware.d.ts +5 -0
- package/dist/esm/searchMiddleware.js +42 -0
- package/dist/esm/searchMiddleware.js.map +1 -0
- package/dist/esm/searchParams.d.ts +7 -0
- package/dist/esm/searchParams.js +61 -0
- package/dist/esm/searchParams.js.map +1 -0
- package/dist/esm/serializer.d.ts +22 -0
- package/dist/esm/structuralSharing.d.ts +4 -0
- package/dist/esm/typePrimitives.d.ts +65 -0
- package/dist/esm/useLoaderData.d.ts +5 -0
- package/dist/esm/useLoaderDeps.d.ts +5 -0
- package/dist/esm/useNavigate.d.ts +3 -0
- package/dist/esm/useParams.d.ts +5 -0
- package/dist/esm/useRouteContext.d.ts +9 -0
- package/dist/esm/useSearch.d.ts +5 -0
- package/dist/esm/utils.d.ts +105 -0
- package/dist/esm/utils.js +160 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/validators.d.ts +51 -0
- package/package.json +36 -32
- package/src/Matches.ts +239 -0
- package/src/RouterProvider.ts +50 -0
- package/src/defer.ts +52 -0
- package/src/fileRoute.ts +140 -0
- package/src/history.ts +9 -0
- package/src/index.ts +421 -19
- package/src/link.ts +580 -286
- package/src/location.ts +13 -0
- package/src/manifest.ts +32 -0
- package/src/not-found.ts +29 -0
- package/src/path.ts +425 -49
- package/src/qss.ts +70 -41
- package/src/redirect.ts +100 -0
- package/src/root.ts +2 -0
- package/src/route.ts +1679 -228
- package/src/routeInfo.ts +224 -217
- package/src/router.ts +3073 -1033
- package/src/scroll-restoration.ts +340 -0
- package/src/searchMiddleware.ts +54 -0
- package/src/searchParams.ts +43 -20
- package/src/serializer.ts +32 -0
- package/src/structuralSharing.ts +7 -0
- package/src/typePrimitives.ts +181 -0
- package/src/useLoaderData.ts +20 -0
- package/src/useLoaderDeps.ts +13 -0
- package/src/useNavigate.ts +13 -0
- package/src/useParams.ts +20 -0
- package/src/useRouteContext.ts +39 -0
- package/src/useSearch.ts +20 -0
- package/src/utils.ts +369 -75
- package/src/validators.ts +121 -0
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -33
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +0 -1
- package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js +0 -33
- package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js.map +0 -1
- package/build/cjs/node_modules/history/index.js +0 -815
- package/build/cjs/node_modules/history/index.js.map +0 -1
- package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js +0 -30
- package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +0 -1
- package/build/cjs/packages/router-core/src/index.js +0 -59
- package/build/cjs/packages/router-core/src/index.js.map +0 -1
- package/build/cjs/packages/router-core/src/path.js +0 -222
- package/build/cjs/packages/router-core/src/path.js.map +0 -1
- package/build/cjs/packages/router-core/src/qss.js +0 -71
- package/build/cjs/packages/router-core/src/qss.js.map +0 -1
- package/build/cjs/packages/router-core/src/route.js +0 -161
- package/build/cjs/packages/router-core/src/route.js.map +0 -1
- package/build/cjs/packages/router-core/src/routeConfig.js +0 -69
- package/build/cjs/packages/router-core/src/routeConfig.js.map +0 -1
- package/build/cjs/packages/router-core/src/routeMatch.js +0 -266
- package/build/cjs/packages/router-core/src/routeMatch.js.map +0 -1
- package/build/cjs/packages/router-core/src/router.js +0 -812
- package/build/cjs/packages/router-core/src/router.js.map +0 -1
- package/build/cjs/packages/router-core/src/searchParams.js +0 -70
- package/build/cjs/packages/router-core/src/searchParams.js.map +0 -1
- package/build/cjs/packages/router-core/src/utils.js +0 -125
- package/build/cjs/packages/router-core/src/utils.js.map +0 -1
- package/build/esm/index.js +0 -2480
- package/build/esm/index.js.map +0 -1
- package/build/stats-html.html +0 -4034
- package/build/stats-react.json +0 -499
- package/build/types/index.d.ts +0 -619
- package/build/umd/index.development.js +0 -2514
- package/build/umd/index.development.js.map +0 -1
- package/build/umd/index.production.js +0 -12
- package/build/umd/index.production.js.map +0 -1
- package/src/frameworks.ts +0 -12
- package/src/routeConfig.ts +0 -495
- package/src/routeMatch.ts +0 -374
package/src/router.ts
CHANGED
|
@@ -1,1284 +1,3324 @@
|
|
|
1
|
+
import { Store, batch } from '@tanstack/store'
|
|
1
2
|
import {
|
|
2
|
-
BrowserHistory,
|
|
3
3
|
createBrowserHistory,
|
|
4
4
|
createMemoryHistory,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
MemoryHistory,
|
|
8
|
-
} from 'history'
|
|
5
|
+
parseHref,
|
|
6
|
+
} from '@tanstack/history'
|
|
9
7
|
import invariant from 'tiny-invariant'
|
|
10
|
-
import { GetFrameworkGeneric } from './frameworks'
|
|
11
|
-
|
|
12
8
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
createControlledPromise,
|
|
10
|
+
deepEqual,
|
|
11
|
+
functionalUpdate,
|
|
12
|
+
last,
|
|
13
|
+
pick,
|
|
14
|
+
replaceEqualDeep,
|
|
15
|
+
} from './utils'
|
|
19
16
|
import {
|
|
20
17
|
cleanPath,
|
|
21
18
|
interpolatePath,
|
|
22
19
|
joinPaths,
|
|
23
20
|
matchPathname,
|
|
21
|
+
parsePathname,
|
|
24
22
|
resolvePath,
|
|
23
|
+
trimPath,
|
|
24
|
+
trimPathLeft,
|
|
25
|
+
trimPathRight,
|
|
25
26
|
} from './path'
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
AnyLoaderData,
|
|
29
|
-
AnyPathParams,
|
|
30
|
-
AnyRouteConfig,
|
|
31
|
-
AnySearchSchema,
|
|
32
|
-
LoaderContext,
|
|
33
|
-
RouteConfig,
|
|
34
|
-
SearchFilter,
|
|
35
|
-
} from './routeConfig'
|
|
36
|
-
import {
|
|
37
|
-
AllRouteInfo,
|
|
38
|
-
AnyAllRouteInfo,
|
|
39
|
-
AnyRouteInfo,
|
|
40
|
-
RouteInfo,
|
|
41
|
-
RoutesById,
|
|
42
|
-
} from './routeInfo'
|
|
43
|
-
import { createRouteMatch, RouteMatch } from './routeMatch'
|
|
27
|
+
import { isNotFound } from './not-found'
|
|
28
|
+
import { setupScrollRestoration } from './scroll-restoration'
|
|
44
29
|
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
import { rootRouteId } from './root'
|
|
31
|
+
import { isRedirect } from './redirect'
|
|
32
|
+
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
33
|
+
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
34
|
+
import type {
|
|
35
|
+
HistoryLocation,
|
|
36
|
+
HistoryState,
|
|
37
|
+
ParsedHistoryState,
|
|
38
|
+
RouterHistory,
|
|
39
|
+
} from '@tanstack/history'
|
|
40
|
+
import type {
|
|
41
|
+
ControlledPromise,
|
|
42
|
+
NoInfer,
|
|
43
|
+
NonNullableUpdater,
|
|
49
44
|
PickAsRequired,
|
|
50
|
-
PickRequired,
|
|
51
|
-
replaceEqualDeep,
|
|
52
|
-
Timeout,
|
|
53
45
|
Updater,
|
|
54
46
|
} from './utils'
|
|
47
|
+
import type { ParsedLocation } from './location'
|
|
48
|
+
import type { DeferredPromiseState } from './defer'
|
|
49
|
+
import type {
|
|
50
|
+
AnyContext,
|
|
51
|
+
AnyRoute,
|
|
52
|
+
AnyRouteWithContext,
|
|
53
|
+
BeforeLoadContextOptions,
|
|
54
|
+
LoaderFnContext,
|
|
55
|
+
MakeRemountDepsOptionsUnion,
|
|
56
|
+
RouteContextOptions,
|
|
57
|
+
RouteMask,
|
|
58
|
+
SearchMiddleware,
|
|
59
|
+
} from './route'
|
|
60
|
+
import type {
|
|
61
|
+
FullSearchSchema,
|
|
62
|
+
RouteById,
|
|
63
|
+
RoutePaths,
|
|
64
|
+
RoutesById,
|
|
65
|
+
RoutesByPath,
|
|
66
|
+
} from './routeInfo'
|
|
67
|
+
import type {
|
|
68
|
+
AnyRouteMatch,
|
|
69
|
+
MakeRouteMatch,
|
|
70
|
+
MakeRouteMatchUnion,
|
|
71
|
+
MatchRouteOptions,
|
|
72
|
+
} from './Matches'
|
|
73
|
+
import type {
|
|
74
|
+
BuildLocationFn,
|
|
75
|
+
CommitLocationOptions,
|
|
76
|
+
NavigateFn,
|
|
77
|
+
} from './RouterProvider'
|
|
78
|
+
import type { Manifest } from './manifest'
|
|
79
|
+
import type { StartSerializer } from './serializer'
|
|
80
|
+
import type { AnySchema, AnyValidator } from './validators'
|
|
81
|
+
import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
|
|
82
|
+
import type { NotFoundError } from './not-found'
|
|
83
|
+
|
|
84
|
+
declare global {
|
|
85
|
+
interface Window {
|
|
86
|
+
__TSR_ROUTER__?: AnyRouter
|
|
87
|
+
}
|
|
88
|
+
}
|
|
55
89
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
TSearchObj extends AnySearchSchema = {},
|
|
60
|
-
TState extends LocationState = LocationState,
|
|
61
|
-
> {
|
|
62
|
-
href: string
|
|
63
|
-
pathname: string
|
|
64
|
-
search: TSearchObj
|
|
65
|
-
searchStr: string
|
|
66
|
-
state: TState
|
|
67
|
-
hash: string
|
|
68
|
-
key?: string
|
|
90
|
+
export type ControllablePromise<T = any> = Promise<T> & {
|
|
91
|
+
resolve: (value: T) => void
|
|
92
|
+
reject: (value?: any) => void
|
|
69
93
|
}
|
|
70
94
|
|
|
71
|
-
export
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
hash?: string
|
|
95
|
+
export type InjectedHtmlEntry = Promise<string>
|
|
96
|
+
|
|
97
|
+
export interface DefaultRegister {
|
|
98
|
+
router: AnyRouter
|
|
76
99
|
}
|
|
77
100
|
|
|
78
|
-
export
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
routeConfigs: TRoute[],
|
|
82
|
-
) => TRoute[]
|
|
101
|
+
export interface Register extends DefaultRegister {
|
|
102
|
+
// router: Router
|
|
103
|
+
}
|
|
83
104
|
|
|
84
|
-
export
|
|
85
|
-
|
|
105
|
+
export type RegisteredRouter = Register['router']
|
|
106
|
+
|
|
107
|
+
export type DefaultRemountDepsFn<TRouteTree extends AnyRoute> = (
|
|
108
|
+
opts: MakeRemountDepsOptionsUnion<TRouteTree>,
|
|
109
|
+
) => any
|
|
110
|
+
|
|
111
|
+
export interface DefaultRouterOptionsExtensions {}
|
|
112
|
+
|
|
113
|
+
export interface RouterOptionsExtensions
|
|
114
|
+
extends DefaultRouterOptionsExtensions {}
|
|
115
|
+
|
|
116
|
+
export interface RouterOptions<
|
|
117
|
+
TRouteTree extends AnyRoute,
|
|
118
|
+
TTrailingSlashOption extends TrailingSlashOption,
|
|
119
|
+
TDefaultStructuralSharingOption extends boolean = false,
|
|
120
|
+
TRouterHistory extends RouterHistory = RouterHistory,
|
|
121
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
122
|
+
> extends RouterOptionsExtensions {
|
|
123
|
+
/**
|
|
124
|
+
* The history object that will be used to manage the browser history.
|
|
125
|
+
*
|
|
126
|
+
* If not provided, a new createBrowserHistory instance will be created and used.
|
|
127
|
+
*
|
|
128
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#history-property)
|
|
129
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/history-types)
|
|
130
|
+
*/
|
|
131
|
+
history?: TRouterHistory
|
|
132
|
+
/**
|
|
133
|
+
* A function that will be used to stringify search params when generating links.
|
|
134
|
+
*
|
|
135
|
+
* @default defaultStringifySearch
|
|
136
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#stringifysearch-method)
|
|
137
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization)
|
|
138
|
+
*/
|
|
86
139
|
stringifySearch?: SearchSerializer
|
|
140
|
+
/**
|
|
141
|
+
* A function that will be used to parse search params when parsing the current location.
|
|
142
|
+
*
|
|
143
|
+
* @default defaultParseSearch
|
|
144
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#parsesearch-method)
|
|
145
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization)
|
|
146
|
+
*/
|
|
87
147
|
parseSearch?: SearchParser
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
148
|
+
/**
|
|
149
|
+
* If `false`, routes will not be preloaded by default in any way.
|
|
150
|
+
*
|
|
151
|
+
* If `'intent'`, routes will be preloaded by default when the user hovers over a link or a `touchstart` event is detected on a `<Link>`.
|
|
152
|
+
*
|
|
153
|
+
* If `'viewport'`, routes will be preloaded by default when they are within the viewport.
|
|
154
|
+
*
|
|
155
|
+
* @default false
|
|
156
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreload-property)
|
|
157
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
|
|
158
|
+
*/
|
|
159
|
+
defaultPreload?: false | 'intent' | 'viewport' | 'render'
|
|
160
|
+
/**
|
|
161
|
+
* The delay in milliseconds that a route must be hovered over or touched before it is preloaded.
|
|
162
|
+
*
|
|
163
|
+
* @default 50
|
|
164
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloaddelay-property)
|
|
165
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading#preload-delay)
|
|
166
|
+
*/
|
|
92
167
|
defaultPreloadDelay?: number
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
168
|
+
/**
|
|
169
|
+
* The default `preloadIntentProximity` a route should use if no preloadIntentProximity is provided.
|
|
170
|
+
*
|
|
171
|
+
* @default 0
|
|
172
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadintentproximity-property)
|
|
173
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading#preload-intent-proximity)
|
|
174
|
+
*/
|
|
175
|
+
defaultPreloadIntentProximity?: number
|
|
176
|
+
/**
|
|
177
|
+
* The default `pendingMs` a route should use if no pendingMs is provided.
|
|
178
|
+
*
|
|
179
|
+
* @default 1000
|
|
180
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpendingms-property)
|
|
181
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#avoiding-pending-component-flash)
|
|
182
|
+
*/
|
|
98
183
|
defaultPendingMs?: number
|
|
184
|
+
/**
|
|
185
|
+
* The default `pendingMinMs` a route should use if no pendingMinMs is provided.
|
|
186
|
+
*
|
|
187
|
+
* @default 500
|
|
188
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpendingminms-property)
|
|
189
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#avoiding-pending-component-flash)
|
|
190
|
+
*/
|
|
99
191
|
defaultPendingMinMs?: number
|
|
100
|
-
|
|
101
|
-
|
|
192
|
+
/**
|
|
193
|
+
* The default `staleTime` a route should use if no staleTime is provided. This is the time in milliseconds that a route will be considered fresh.
|
|
194
|
+
*
|
|
195
|
+
* @default 0
|
|
196
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultstaletime-property)
|
|
197
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
|
|
198
|
+
*/
|
|
199
|
+
defaultStaleTime?: number
|
|
200
|
+
/**
|
|
201
|
+
* The default `preloadStaleTime` a route should use if no preloadStaleTime is provided.
|
|
202
|
+
*
|
|
203
|
+
* @default 30_000 `(30 seconds)`
|
|
204
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadstaletime-property)
|
|
205
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
|
|
206
|
+
*/
|
|
207
|
+
defaultPreloadStaleTime?: number
|
|
208
|
+
/**
|
|
209
|
+
* The default `defaultPreloadGcTime` a route should use if no preloadGcTime is provided.
|
|
210
|
+
*
|
|
211
|
+
* @default 1_800_000 `(30 minutes)`
|
|
212
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadgctime-property)
|
|
213
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
|
|
214
|
+
*/
|
|
215
|
+
defaultPreloadGcTime?: number
|
|
216
|
+
/**
|
|
217
|
+
* If `true`, route navigations will called using `document.startViewTransition()`.
|
|
218
|
+
*
|
|
219
|
+
* If the browser does not support this api, this option will be ignored.
|
|
220
|
+
*
|
|
221
|
+
* See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) for more information on how this function works.
|
|
222
|
+
*
|
|
223
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultviewtransition-property)
|
|
224
|
+
*/
|
|
225
|
+
defaultViewTransition?: boolean | ViewTransitionOptions
|
|
226
|
+
/**
|
|
227
|
+
* The default `hashScrollIntoView` a route should use if no hashScrollIntoView is provided while navigating
|
|
228
|
+
*
|
|
229
|
+
* See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) for more information on `ScrollIntoViewOptions`.
|
|
230
|
+
*
|
|
231
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaulthashscrollintoview-property)
|
|
232
|
+
*/
|
|
233
|
+
defaultHashScrollIntoView?: boolean | ScrollIntoViewOptions
|
|
234
|
+
/**
|
|
235
|
+
* @default 'fuzzy'
|
|
236
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#notfoundmode-property)
|
|
237
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/not-found-errors#the-notfoundmode-option)
|
|
238
|
+
*/
|
|
239
|
+
notFoundMode?: 'root' | 'fuzzy'
|
|
240
|
+
/**
|
|
241
|
+
* The default `gcTime` a route should use if no gcTime is provided.
|
|
242
|
+
*
|
|
243
|
+
* @default 1_800_000 `(30 minutes)`
|
|
244
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultgctime-property)
|
|
245
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
|
|
246
|
+
*/
|
|
247
|
+
defaultGcTime?: number
|
|
248
|
+
/**
|
|
249
|
+
* If `true`, all routes will be matched as case-sensitive.
|
|
250
|
+
*
|
|
251
|
+
* @default false
|
|
252
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#casesensitive-property)
|
|
253
|
+
*/
|
|
102
254
|
caseSensitive?: boolean
|
|
103
|
-
|
|
255
|
+
/**
|
|
256
|
+
*
|
|
257
|
+
* The route tree that will be used to configure the router instance.
|
|
258
|
+
*
|
|
259
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#routetree-property)
|
|
260
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/routing/route-trees)
|
|
261
|
+
*/
|
|
262
|
+
routeTree?: TRouteTree
|
|
263
|
+
/**
|
|
264
|
+
* The basepath for then entire router. This is useful for mounting a router instance at a subpath.
|
|
265
|
+
*
|
|
266
|
+
* @default '/'
|
|
267
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#basepath-property)
|
|
268
|
+
*/
|
|
104
269
|
basepath?: string
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
270
|
+
/**
|
|
271
|
+
* The root context that will be provided to all routes in the route tree.
|
|
272
|
+
*
|
|
273
|
+
* This can be used to provide a context to all routes in the tree without having to provide it to each route individually.
|
|
274
|
+
*
|
|
275
|
+
* Optional or required if the root route was created with [`createRootRouteWithContext()`](https://tanstack.com/router/latest/docs/framework/react/api/router/createRootRouteWithContextFunction).
|
|
276
|
+
*
|
|
277
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#context-property)
|
|
278
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/router-context)
|
|
279
|
+
*/
|
|
280
|
+
context?: InferRouterContext<TRouteTree>
|
|
281
|
+
/**
|
|
282
|
+
* A function that will be called when the router is dehydrated.
|
|
283
|
+
*
|
|
284
|
+
* The return value of this function will be serialized and stored in the router's dehydrated state.
|
|
285
|
+
*
|
|
286
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#dehydrate-method)
|
|
287
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
|
|
288
|
+
*/
|
|
289
|
+
dehydrate?: () => TDehydrated
|
|
290
|
+
/**
|
|
291
|
+
* A function that will be called when the router is hydrated.
|
|
292
|
+
*
|
|
293
|
+
* The return value of this function will be serialized and stored in the router's dehydrated state.
|
|
294
|
+
*
|
|
295
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#hydrate-method)
|
|
296
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
|
|
297
|
+
*/
|
|
298
|
+
hydrate?: (dehydrated: TDehydrated) => void
|
|
299
|
+
/**
|
|
300
|
+
* An array of route masks that will be used to mask routes in the route tree.
|
|
301
|
+
*
|
|
302
|
+
* Route masking is when you display a route at a different path than the one it is configured to match, like a modal popup that when shared will unmask to the modal's content instead of the modal's context.
|
|
303
|
+
*
|
|
304
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#routemasks-property)
|
|
305
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/route-masking)
|
|
306
|
+
*/
|
|
307
|
+
routeMasks?: Array<RouteMask<TRouteTree>>
|
|
308
|
+
/**
|
|
309
|
+
* If `true`, route masks will, by default, be removed when the page is reloaded.
|
|
310
|
+
*
|
|
311
|
+
* This can be overridden on a per-mask basis by setting the `unmaskOnReload` option on the mask, or on a per-navigation basis by setting the `unmaskOnReload` option in the `Navigate` options.
|
|
312
|
+
*
|
|
313
|
+
* @default false
|
|
314
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#unmaskonreload-property)
|
|
315
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/route-masking#unmasking-on-page-reload)
|
|
316
|
+
*/
|
|
317
|
+
unmaskOnReload?: boolean
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Use `notFoundComponent` instead.
|
|
321
|
+
*
|
|
322
|
+
* @deprecated
|
|
323
|
+
* See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info.
|
|
324
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#notfoundroute-property)
|
|
325
|
+
*/
|
|
326
|
+
notFoundRoute?: AnyRoute
|
|
327
|
+
/**
|
|
328
|
+
* Configures how trailing slashes are treated.
|
|
329
|
+
*
|
|
330
|
+
* - `'always'` will add a trailing slash if not present
|
|
331
|
+
* - `'never'` will remove the trailing slash if present
|
|
332
|
+
* - `'preserve'` will not modify the trailing slash.
|
|
333
|
+
*
|
|
334
|
+
* @default 'never'
|
|
335
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#trailingslash-property)
|
|
336
|
+
*/
|
|
337
|
+
trailingSlash?: TTrailingSlashOption
|
|
338
|
+
/**
|
|
339
|
+
* While usually automatic, sometimes it can be useful to force the router into a server-side state, e.g. when using the router in a non-browser environment that has access to a global.document object.
|
|
340
|
+
*
|
|
341
|
+
* @default typeof document !== 'undefined'
|
|
342
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#isserver-property)
|
|
343
|
+
*/
|
|
344
|
+
isServer?: boolean
|
|
345
|
+
|
|
346
|
+
defaultSsr?: boolean
|
|
347
|
+
|
|
348
|
+
search?: {
|
|
349
|
+
/**
|
|
350
|
+
* Configures how unknown search params (= not returned by any `validateSearch`) are treated.
|
|
351
|
+
*
|
|
352
|
+
* @default false
|
|
353
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#search.strict-property)
|
|
354
|
+
*/
|
|
355
|
+
strict?: boolean
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Configures whether structural sharing is enabled by default for fine-grained selectors.
|
|
360
|
+
*
|
|
361
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultstructuralsharing-property)
|
|
362
|
+
*/
|
|
363
|
+
defaultStructuralSharing?: TDefaultStructuralSharingOption
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Configures which URI characters are allowed in path params that would ordinarily be escaped by encodeURIComponent.
|
|
367
|
+
*
|
|
368
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#pathparamsallowedcharacters-property)
|
|
369
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/path-params#allowed-characters)
|
|
370
|
+
*/
|
|
371
|
+
pathParamsAllowedCharacters?: Array<
|
|
372
|
+
';' | ':' | '@' | '&' | '=' | '+' | '$' | ','
|
|
373
|
+
>
|
|
374
|
+
|
|
375
|
+
defaultRemountDeps?: DefaultRemountDepsFn<TRouteTree>
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* If `true`, scroll restoration will be enabled
|
|
379
|
+
*
|
|
380
|
+
* @default false
|
|
381
|
+
*/
|
|
382
|
+
scrollRestoration?: boolean
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* A function that will be called to get the key for the scroll restoration cache.
|
|
386
|
+
*
|
|
387
|
+
* @default (location) => location.href
|
|
388
|
+
*/
|
|
389
|
+
getScrollRestorationKey?: (location: ParsedLocation) => string
|
|
390
|
+
/**
|
|
391
|
+
* The default behavior for scroll restoration.
|
|
392
|
+
*
|
|
393
|
+
* @default 'auto'
|
|
394
|
+
*/
|
|
395
|
+
scrollRestorationBehavior?: ScrollBehavior
|
|
396
|
+
/**
|
|
397
|
+
* An array of selectors that will be used to scroll to the top of the page in addition to `window`
|
|
398
|
+
*
|
|
399
|
+
* @default ['window']
|
|
400
|
+
*/
|
|
401
|
+
scrollToTopSelectors?: Array<string>
|
|
110
402
|
}
|
|
111
403
|
|
|
112
|
-
export interface
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// TError = unknown,
|
|
404
|
+
export interface RouterState<
|
|
405
|
+
in out TRouteTree extends AnyRoute = AnyRoute,
|
|
406
|
+
in out TRouteMatch = MakeRouteMatchUnion,
|
|
116
407
|
> {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
408
|
+
status: 'pending' | 'idle'
|
|
409
|
+
loadedAt: number
|
|
410
|
+
isLoading: boolean
|
|
411
|
+
isTransitioning: boolean
|
|
412
|
+
matches: Array<TRouteMatch>
|
|
413
|
+
pendingMatches?: Array<TRouteMatch>
|
|
414
|
+
cachedMatches: Array<TRouteMatch>
|
|
415
|
+
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
416
|
+
resolvedLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
417
|
+
statusCode: number
|
|
418
|
+
redirect?: AnyRedirect
|
|
121
419
|
}
|
|
122
420
|
|
|
123
|
-
export interface
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
421
|
+
export interface BuildNextOptions {
|
|
422
|
+
to?: string | number | null
|
|
423
|
+
params?: true | Updater<unknown>
|
|
424
|
+
search?: true | Updater<unknown>
|
|
425
|
+
hash?: true | Updater<string>
|
|
426
|
+
state?: true | NonNullableUpdater<ParsedHistoryState, HistoryState>
|
|
427
|
+
mask?: {
|
|
428
|
+
to?: string | number | null
|
|
429
|
+
params?: true | Updater<unknown>
|
|
430
|
+
search?: true | Updater<unknown>
|
|
431
|
+
hash?: true | Updater<string>
|
|
432
|
+
state?: true | NonNullableUpdater<ParsedHistoryState, HistoryState>
|
|
433
|
+
unmaskOnReload?: boolean
|
|
434
|
+
}
|
|
435
|
+
from?: string
|
|
436
|
+
_fromLocation?: ParsedLocation
|
|
437
|
+
href?: string
|
|
133
438
|
}
|
|
134
439
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
? keyof TAllParams extends never
|
|
142
|
-
? (loaderContext: { signal?: AbortSignal }) => Promise<TRouteLoaderData>
|
|
143
|
-
: (loaderContext: {
|
|
144
|
-
params: TAllParams
|
|
145
|
-
search?: TFullSearchSchema
|
|
146
|
-
signal?: AbortSignal
|
|
147
|
-
}) => Promise<TRouteLoaderData>
|
|
148
|
-
: keyof TAllParams extends never
|
|
149
|
-
? (loaderContext: {
|
|
150
|
-
search: TFullSearchSchema
|
|
151
|
-
params: TAllParams
|
|
152
|
-
signal?: AbortSignal
|
|
153
|
-
}) => Promise<TRouteLoaderData>
|
|
154
|
-
: (loaderContext: {
|
|
155
|
-
search: TFullSearchSchema
|
|
156
|
-
signal?: AbortSignal
|
|
157
|
-
}) => Promise<TRouteLoaderData>
|
|
158
|
-
current?: LoaderState<TFullSearchSchema, TAllParams>
|
|
159
|
-
latest?: LoaderState<TFullSearchSchema, TAllParams>
|
|
160
|
-
pending: LoaderState<TFullSearchSchema, TAllParams>[]
|
|
440
|
+
type NavigationEventInfo = {
|
|
441
|
+
fromLocation?: ParsedLocation
|
|
442
|
+
toLocation: ParsedLocation
|
|
443
|
+
pathChanged: boolean
|
|
444
|
+
hrefChanged: boolean
|
|
445
|
+
hashChanged: boolean
|
|
161
446
|
}
|
|
162
447
|
|
|
163
|
-
export
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
448
|
+
export type RouterEvents = {
|
|
449
|
+
onBeforeNavigate: {
|
|
450
|
+
type: 'onBeforeNavigate'
|
|
451
|
+
} & NavigationEventInfo
|
|
452
|
+
onBeforeLoad: {
|
|
453
|
+
type: 'onBeforeLoad'
|
|
454
|
+
} & NavigationEventInfo
|
|
455
|
+
onLoad: {
|
|
456
|
+
type: 'onLoad'
|
|
457
|
+
} & NavigationEventInfo
|
|
458
|
+
onResolved: {
|
|
459
|
+
type: 'onResolved'
|
|
460
|
+
} & NavigationEventInfo
|
|
461
|
+
onBeforeRouteMount: {
|
|
462
|
+
type: 'onBeforeRouteMount'
|
|
463
|
+
} & NavigationEventInfo
|
|
464
|
+
onInjectedHtml: {
|
|
465
|
+
type: 'onInjectedHtml'
|
|
466
|
+
promise: Promise<string>
|
|
467
|
+
}
|
|
468
|
+
onRendered: {
|
|
469
|
+
type: 'onRendered'
|
|
470
|
+
} & NavigationEventInfo
|
|
169
471
|
}
|
|
170
472
|
|
|
171
|
-
export
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
actions: Record<string, Action>
|
|
179
|
-
loaders: Record<string, Loader>
|
|
180
|
-
pending?: PendingState
|
|
181
|
-
isFetching: boolean
|
|
182
|
-
isPreloading: boolean
|
|
473
|
+
export type RouterEvent = RouterEvents[keyof RouterEvents]
|
|
474
|
+
|
|
475
|
+
export type ListenerFn<TEvent extends RouterEvent> = (event: TEvent) => void
|
|
476
|
+
|
|
477
|
+
export type RouterListener<TRouterEvent extends RouterEvent> = {
|
|
478
|
+
eventType: TRouterEvent['type']
|
|
479
|
+
fn: ListenerFn<TRouterEvent>
|
|
183
480
|
}
|
|
184
481
|
|
|
185
|
-
export interface
|
|
186
|
-
|
|
187
|
-
|
|
482
|
+
export interface MatchRoutesOpts {
|
|
483
|
+
preload?: boolean
|
|
484
|
+
throwOnError?: boolean
|
|
485
|
+
_buildLocation?: boolean
|
|
486
|
+
dest?: BuildNextOptions
|
|
188
487
|
}
|
|
189
488
|
|
|
190
|
-
type
|
|
489
|
+
export type InferRouterContext<TRouteTree extends AnyRoute> =
|
|
490
|
+
TRouteTree['types']['routerContext']
|
|
191
491
|
|
|
192
|
-
export type
|
|
492
|
+
export type RouterContextOptions<TRouteTree extends AnyRoute> =
|
|
493
|
+
AnyContext extends InferRouterContext<TRouteTree>
|
|
494
|
+
? {
|
|
495
|
+
context?: InferRouterContext<TRouteTree>
|
|
496
|
+
}
|
|
497
|
+
: {
|
|
498
|
+
context: InferRouterContext<TRouteTree>
|
|
499
|
+
}
|
|
193
500
|
|
|
194
|
-
export
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
501
|
+
export type RouterConstructorOptions<
|
|
502
|
+
TRouteTree extends AnyRoute,
|
|
503
|
+
TTrailingSlashOption extends TrailingSlashOption,
|
|
504
|
+
TDefaultStructuralSharingOption extends boolean,
|
|
505
|
+
TRouterHistory extends RouterHistory,
|
|
506
|
+
TDehydrated extends Record<string, any>,
|
|
507
|
+
> = Omit<
|
|
508
|
+
RouterOptions<
|
|
509
|
+
TRouteTree,
|
|
510
|
+
TTrailingSlashOption,
|
|
511
|
+
TDefaultStructuralSharingOption,
|
|
512
|
+
TRouterHistory,
|
|
513
|
+
TDehydrated
|
|
514
|
+
>,
|
|
515
|
+
'context'
|
|
516
|
+
> &
|
|
517
|
+
RouterContextOptions<TRouteTree>
|
|
518
|
+
|
|
519
|
+
export interface RouterErrorSerializer<TSerializedError> {
|
|
520
|
+
serialize: (err: unknown) => TSerializedError
|
|
521
|
+
deserialize: (err: TSerializedError) => unknown
|
|
204
522
|
}
|
|
205
523
|
|
|
206
|
-
export
|
|
207
|
-
|
|
208
|
-
|
|
524
|
+
export interface MatchedRoutesResult {
|
|
525
|
+
matchedRoutes: Array<AnyRoute>
|
|
526
|
+
routeParams: Record<string, string>
|
|
209
527
|
}
|
|
210
528
|
|
|
211
|
-
export
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
529
|
+
export type PreloadRouteFn<
|
|
530
|
+
TRouteTree extends AnyRoute,
|
|
531
|
+
TTrailingSlashOption extends TrailingSlashOption,
|
|
532
|
+
TDefaultStructuralSharingOption extends boolean,
|
|
533
|
+
TRouterHistory extends RouterHistory,
|
|
534
|
+
> = <
|
|
535
|
+
TFrom extends RoutePaths<TRouteTree> | string = string,
|
|
536
|
+
TTo extends string | undefined = undefined,
|
|
537
|
+
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
|
|
538
|
+
TMaskTo extends string = '',
|
|
539
|
+
>(
|
|
540
|
+
opts: NavigateOptions<
|
|
541
|
+
RouterCore<
|
|
542
|
+
TRouteTree,
|
|
543
|
+
TTrailingSlashOption,
|
|
544
|
+
TDefaultStructuralSharingOption,
|
|
545
|
+
TRouterHistory
|
|
546
|
+
>,
|
|
547
|
+
TFrom,
|
|
548
|
+
TTo,
|
|
549
|
+
TMaskFrom,
|
|
550
|
+
TMaskTo
|
|
551
|
+
>,
|
|
552
|
+
) => Promise<Array<AnyRouteMatch> | undefined>
|
|
553
|
+
|
|
554
|
+
export type MatchRouteFn<
|
|
555
|
+
TRouteTree extends AnyRoute,
|
|
556
|
+
TTrailingSlashOption extends TrailingSlashOption,
|
|
557
|
+
TDefaultStructuralSharingOption extends boolean,
|
|
558
|
+
TRouterHistory extends RouterHistory,
|
|
559
|
+
> = <
|
|
560
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
561
|
+
TTo extends string | undefined = undefined,
|
|
562
|
+
TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
|
|
563
|
+
>(
|
|
564
|
+
location: ToOptions<
|
|
565
|
+
RouterCore<
|
|
566
|
+
TRouteTree,
|
|
567
|
+
TTrailingSlashOption,
|
|
568
|
+
TDefaultStructuralSharingOption,
|
|
569
|
+
TRouterHistory
|
|
570
|
+
>,
|
|
571
|
+
TFrom,
|
|
572
|
+
TTo
|
|
573
|
+
>,
|
|
574
|
+
opts?: MatchRouteOptions,
|
|
575
|
+
) => false | RouteById<TRouteTree, TResolved>['types']['allParams']
|
|
576
|
+
|
|
577
|
+
export type UpdateFn<
|
|
578
|
+
TRouteTree extends AnyRoute,
|
|
579
|
+
TTrailingSlashOption extends TrailingSlashOption,
|
|
580
|
+
TDefaultStructuralSharingOption extends boolean,
|
|
581
|
+
TRouterHistory extends RouterHistory,
|
|
582
|
+
TDehydrated extends Record<string, any>,
|
|
583
|
+
> = (
|
|
584
|
+
newOptions: RouterConstructorOptions<
|
|
585
|
+
TRouteTree,
|
|
586
|
+
TTrailingSlashOption,
|
|
587
|
+
TDefaultStructuralSharingOption,
|
|
588
|
+
TRouterHistory,
|
|
589
|
+
TDehydrated
|
|
590
|
+
>,
|
|
591
|
+
) => void
|
|
592
|
+
|
|
593
|
+
export type InvalidateFn<TRouter extends AnyRouter> = (opts?: {
|
|
594
|
+
filter?: (d: MakeRouteMatchUnion<TRouter>) => boolean
|
|
595
|
+
sync?: boolean
|
|
596
|
+
}) => Promise<void>
|
|
597
|
+
|
|
598
|
+
export type ParseLocationFn<TRouteTree extends AnyRoute> = (
|
|
599
|
+
previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>,
|
|
600
|
+
locationToParse?: HistoryLocation,
|
|
601
|
+
) => ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
602
|
+
|
|
603
|
+
export type GetMatchRoutesFn = (
|
|
604
|
+
pathname: string,
|
|
605
|
+
routePathname: string | undefined,
|
|
606
|
+
) => {
|
|
607
|
+
matchedRoutes: Array<AnyRoute>
|
|
608
|
+
routeParams: Record<string, string>
|
|
609
|
+
foundRoute: AnyRoute | undefined
|
|
217
610
|
}
|
|
218
611
|
|
|
219
|
-
export
|
|
220
|
-
|
|
221
|
-
|
|
612
|
+
export type EmitFn = (routerEvent: RouterEvent) => void
|
|
613
|
+
|
|
614
|
+
export type LoadFn = (opts?: { sync?: boolean }) => Promise<void>
|
|
615
|
+
|
|
616
|
+
export type CommitLocationFn = ({
|
|
617
|
+
viewTransition,
|
|
618
|
+
ignoreBlocker,
|
|
619
|
+
...next
|
|
620
|
+
}: ParsedLocation & CommitLocationOptions) => Promise<void>
|
|
621
|
+
|
|
622
|
+
export type StartTransitionFn = (fn: () => void) => void
|
|
623
|
+
|
|
624
|
+
export type SubscribeFn = <TType extends keyof RouterEvents>(
|
|
625
|
+
eventType: TType,
|
|
626
|
+
fn: ListenerFn<RouterEvents[TType]>,
|
|
627
|
+
) => () => void
|
|
628
|
+
|
|
629
|
+
export interface MatchRoutesFn {
|
|
630
|
+
(
|
|
631
|
+
pathname: string,
|
|
632
|
+
locationSearch: AnySchema,
|
|
633
|
+
opts?: MatchRoutesOpts,
|
|
634
|
+
): Array<AnyRouteMatch>
|
|
635
|
+
(next: ParsedLocation, opts?: MatchRoutesOpts): Array<AnyRouteMatch>
|
|
636
|
+
(
|
|
637
|
+
pathnameOrNext: string | ParsedLocation,
|
|
638
|
+
locationSearchOrOpts?: AnySchema | MatchRoutesOpts,
|
|
639
|
+
opts?: MatchRoutesOpts,
|
|
640
|
+
): Array<AnyRouteMatch>
|
|
222
641
|
}
|
|
223
642
|
|
|
224
|
-
type
|
|
225
|
-
|
|
643
|
+
export type GetMatchFn = (matchId: string) => AnyRouteMatch | undefined
|
|
644
|
+
|
|
645
|
+
export type UpdateMatchFn = (
|
|
646
|
+
id: string,
|
|
647
|
+
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
648
|
+
) => AnyRouteMatch
|
|
649
|
+
|
|
650
|
+
export type LoadRouteChunkFn = (route: AnyRoute) => Promise<Array<void>>
|
|
651
|
+
|
|
652
|
+
export type ResolveRedirect = (err: AnyRedirect) => ResolvedRedirect
|
|
653
|
+
|
|
654
|
+
export type ClearCacheFn<TRouter extends AnyRouter> = (opts?: {
|
|
655
|
+
filter?: (d: MakeRouteMatchUnion<TRouter>) => boolean
|
|
656
|
+
}) => void
|
|
657
|
+
|
|
658
|
+
export interface ServerSrr {
|
|
659
|
+
injectedHtml: Array<InjectedHtmlEntry>
|
|
660
|
+
injectHtml: (getHtml: () => string | Promise<string>) => Promise<void>
|
|
661
|
+
injectScript: (
|
|
662
|
+
getScript: () => string | Promise<string>,
|
|
663
|
+
opts?: { logScript?: boolean },
|
|
664
|
+
) => Promise<void>
|
|
665
|
+
streamValue: (key: string, value: any) => void
|
|
666
|
+
streamedKeys: Set<string>
|
|
667
|
+
onMatchSettled: (opts: { router: AnyRouter; match: AnyRouteMatch }) => any
|
|
226
668
|
}
|
|
227
669
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
670
|
+
export type AnyRouterWithContext<TContext> = RouterCore<
|
|
671
|
+
AnyRouteWithContext<TContext>,
|
|
672
|
+
any,
|
|
673
|
+
any,
|
|
674
|
+
any,
|
|
675
|
+
any
|
|
676
|
+
>
|
|
677
|
+
|
|
678
|
+
export type AnyRouter = RouterCore<any, any, any, any, any>
|
|
679
|
+
|
|
680
|
+
export interface ViewTransitionOptions {
|
|
681
|
+
types:
|
|
682
|
+
| Array<string>
|
|
683
|
+
| ((locationChangeInfo: {
|
|
684
|
+
fromLocation?: ParsedLocation
|
|
685
|
+
toLocation: ParsedLocation
|
|
686
|
+
pathChanged: boolean
|
|
687
|
+
hrefChanged: boolean
|
|
688
|
+
hashChanged: boolean
|
|
689
|
+
}) => Array<string>)
|
|
231
690
|
}
|
|
232
691
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
TRouteConfig extends AnyRouteConfig = RouteConfig,
|
|
246
|
-
TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
|
|
247
|
-
> {
|
|
248
|
-
history: BrowserHistory | MemoryHistory | HashHistory
|
|
249
|
-
options: PickAsRequired<
|
|
250
|
-
RouterOptions<TRouteConfig>,
|
|
251
|
-
'stringifySearch' | 'parseSearch'
|
|
252
|
-
>
|
|
253
|
-
// Computed in this.update()
|
|
254
|
-
basepath: string
|
|
255
|
-
// Internal:
|
|
256
|
-
allRouteInfo: TAllRouteInfo
|
|
257
|
-
listeners: Listener[]
|
|
258
|
-
location: Location
|
|
259
|
-
navigateTimeout?: Timeout
|
|
260
|
-
nextAction?: 'push' | 'replace'
|
|
261
|
-
state: RouterState
|
|
262
|
-
routeTree: Route<TAllRouteInfo, RouteInfo>
|
|
263
|
-
routesById: RoutesById<TAllRouteInfo>
|
|
264
|
-
navigationPromise: Promise<void>
|
|
265
|
-
removeActionQueue: { action: Action; actionState: ActionState }[]
|
|
266
|
-
startedLoadingAt: number
|
|
267
|
-
resolveNavigation: () => void
|
|
268
|
-
subscribe: (listener: Listener) => () => void
|
|
269
|
-
notify: () => void
|
|
270
|
-
mount: () => () => void
|
|
271
|
-
onFocus: () => void
|
|
272
|
-
update: <TRouteConfig extends RouteConfig = RouteConfig>(
|
|
273
|
-
opts?: RouterOptions<TRouteConfig>,
|
|
274
|
-
) => Router<TRouteConfig>
|
|
275
|
-
|
|
276
|
-
buildNext: (opts: BuildNextOptions) => Location
|
|
277
|
-
cancelMatches: () => void
|
|
278
|
-
loadLocation: (next?: Location) => Promise<void>
|
|
279
|
-
matchCache: Record<string, MatchCacheEntry>
|
|
280
|
-
cleanMatchCache: () => void
|
|
281
|
-
getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
282
|
-
id: TId,
|
|
283
|
-
) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
284
|
-
loadRoute: (navigateOpts: BuildNextOptions) => Promise<RouteMatch[]>
|
|
285
|
-
preloadRoute: (
|
|
286
|
-
navigateOpts: BuildNextOptions,
|
|
287
|
-
loaderOpts: { maxAge?: number; gcMaxAge?: number },
|
|
288
|
-
) => Promise<RouteMatch[]>
|
|
289
|
-
matchRoutes: (
|
|
290
|
-
pathname: string,
|
|
291
|
-
opts?: { strictParseParams?: boolean },
|
|
292
|
-
) => RouteMatch[]
|
|
293
|
-
loadMatches: (
|
|
294
|
-
resolvedMatches: RouteMatch[],
|
|
295
|
-
loaderOpts?: { withPending?: boolean } & (
|
|
296
|
-
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
297
|
-
| { preload?: false; maxAge?: never; gcMaxAge?: never }
|
|
298
|
-
),
|
|
299
|
-
) => Promise<void>
|
|
300
|
-
invalidateRoute: (opts: MatchLocation) => void
|
|
301
|
-
reload: () => Promise<void>
|
|
302
|
-
resolvePath: (from: string, path: string) => string
|
|
303
|
-
navigate: <
|
|
304
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
305
|
-
TTo extends string = '.',
|
|
306
|
-
>(
|
|
307
|
-
opts: NavigateOptionsAbsolute<TAllRouteInfo, TFrom, TTo>,
|
|
308
|
-
) => Promise<void>
|
|
309
|
-
matchRoute: <
|
|
310
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
311
|
-
TTo extends string = '.',
|
|
312
|
-
>(
|
|
313
|
-
matchLocation: ToOptions<TAllRouteInfo, TFrom, TTo>,
|
|
314
|
-
opts?: MatchRouteOptions,
|
|
315
|
-
) => boolean
|
|
316
|
-
buildLink: <
|
|
317
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
318
|
-
TTo extends string = '.',
|
|
319
|
-
>(
|
|
320
|
-
opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
|
|
321
|
-
) => LinkInfo
|
|
322
|
-
dehydrateState: () => DehydratedRouterState
|
|
323
|
-
hydrateState: (state: DehydratedRouterState) => void
|
|
324
|
-
__: {
|
|
325
|
-
buildRouteTree: (
|
|
326
|
-
routeConfig: RouteConfig,
|
|
327
|
-
) => Route<TAllRouteInfo, AnyRouteInfo>
|
|
328
|
-
parseLocation: (
|
|
329
|
-
location: History['location'],
|
|
330
|
-
previousLocation?: Location,
|
|
331
|
-
) => Location
|
|
332
|
-
buildLocation: (dest: BuildNextOptions) => Location
|
|
333
|
-
commitLocation: (next: Location, replace?: boolean) => Promise<void>
|
|
334
|
-
navigate: (
|
|
335
|
-
location: BuildNextOptions & { replace?: boolean },
|
|
336
|
-
) => Promise<void>
|
|
692
|
+
export function defaultSerializeError(err: unknown) {
|
|
693
|
+
if (err instanceof Error) {
|
|
694
|
+
const obj = {
|
|
695
|
+
name: err.name,
|
|
696
|
+
message: err.message,
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (process.env.NODE_ENV === 'development') {
|
|
700
|
+
;(obj as any).stack = err.stack
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return obj
|
|
337
704
|
}
|
|
705
|
+
|
|
706
|
+
return {
|
|
707
|
+
data: err,
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
export interface ExtractedBaseEntry {
|
|
711
|
+
dataType: '__beforeLoadContext' | 'loaderData'
|
|
712
|
+
type: string
|
|
713
|
+
path: Array<string>
|
|
714
|
+
id: number
|
|
715
|
+
matchIndex: number
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export interface ExtractedStream extends ExtractedBaseEntry {
|
|
719
|
+
type: 'stream'
|
|
720
|
+
streamState: StreamState
|
|
338
721
|
}
|
|
339
722
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
723
|
+
export interface ExtractedPromise extends ExtractedBaseEntry {
|
|
724
|
+
type: 'promise'
|
|
725
|
+
promiseState: DeferredPromiseState<any>
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export type ExtractedEntry = ExtractedStream | ExtractedPromise
|
|
343
729
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
730
|
+
export type StreamState = {
|
|
731
|
+
promises: Array<ControlledPromise<string | null>>
|
|
732
|
+
}
|
|
347
733
|
|
|
348
|
-
export
|
|
349
|
-
|
|
350
|
-
|
|
734
|
+
export type TrailingSlashOption = 'always' | 'never' | 'preserve'
|
|
735
|
+
|
|
736
|
+
export function getLocationChangeInfo(routerState: {
|
|
737
|
+
resolvedLocation?: ParsedLocation
|
|
738
|
+
location: ParsedLocation
|
|
739
|
+
}) {
|
|
740
|
+
const fromLocation = routerState.resolvedLocation
|
|
741
|
+
const toLocation = routerState.location
|
|
742
|
+
const pathChanged = fromLocation?.pathname !== toLocation.pathname
|
|
743
|
+
const hrefChanged = fromLocation?.href !== toLocation.href
|
|
744
|
+
const hashChanged = fromLocation?.hash !== toLocation.hash
|
|
745
|
+
return { fromLocation, toLocation, pathChanged, hrefChanged, hashChanged }
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
export type CreateRouterFn = <
|
|
749
|
+
TRouteTree extends AnyRoute,
|
|
750
|
+
TTrailingSlashOption extends TrailingSlashOption = 'never',
|
|
751
|
+
TDefaultStructuralSharingOption extends boolean = false,
|
|
752
|
+
TRouterHistory extends RouterHistory = RouterHistory,
|
|
753
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
351
754
|
>(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
755
|
+
options: undefined extends number
|
|
756
|
+
? 'strictNullChecks must be enabled in tsconfig.json'
|
|
757
|
+
: RouterConstructorOptions<
|
|
758
|
+
TRouteTree,
|
|
759
|
+
TTrailingSlashOption,
|
|
760
|
+
TDefaultStructuralSharingOption,
|
|
761
|
+
TRouterHistory,
|
|
762
|
+
TDehydrated
|
|
763
|
+
>,
|
|
764
|
+
) => RouterCore<
|
|
765
|
+
TRouteTree,
|
|
766
|
+
TTrailingSlashOption,
|
|
767
|
+
TDefaultStructuralSharingOption,
|
|
768
|
+
TRouterHistory,
|
|
769
|
+
TDehydrated
|
|
770
|
+
>
|
|
771
|
+
|
|
772
|
+
export class RouterCore<
|
|
773
|
+
in out TRouteTree extends AnyRoute,
|
|
774
|
+
in out TTrailingSlashOption extends TrailingSlashOption,
|
|
775
|
+
in out TDefaultStructuralSharingOption extends boolean,
|
|
776
|
+
in out TRouterHistory extends RouterHistory = RouterHistory,
|
|
777
|
+
in out TDehydrated extends Record<string, any> = Record<string, any>,
|
|
778
|
+
> {
|
|
779
|
+
// Option-independent properties
|
|
780
|
+
tempLocationKey: string | undefined = `${Math.round(
|
|
781
|
+
Math.random() * 10000000,
|
|
782
|
+
)}`
|
|
783
|
+
resetNextScroll = true
|
|
784
|
+
shouldViewTransition?: boolean | ViewTransitionOptions = undefined
|
|
785
|
+
isViewTransitionTypesSupported?: boolean = undefined
|
|
786
|
+
subscribers = new Set<RouterListener<RouterEvent>>()
|
|
787
|
+
viewTransitionPromise?: ControlledPromise<true>
|
|
788
|
+
isScrollRestoring = false
|
|
789
|
+
isScrollRestorationSetup = false
|
|
790
|
+
|
|
791
|
+
// Must build in constructor
|
|
792
|
+
__store!: Store<RouterState<TRouteTree>>
|
|
793
|
+
options!: PickAsRequired<
|
|
794
|
+
RouterOptions<
|
|
795
|
+
TRouteTree,
|
|
796
|
+
TTrailingSlashOption,
|
|
797
|
+
TDefaultStructuralSharingOption,
|
|
798
|
+
TRouterHistory,
|
|
799
|
+
TDehydrated
|
|
800
|
+
>,
|
|
801
|
+
'stringifySearch' | 'parseSearch' | 'context'
|
|
802
|
+
>
|
|
803
|
+
history!: TRouterHistory
|
|
804
|
+
latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
805
|
+
basepath!: string
|
|
806
|
+
routeTree!: TRouteTree
|
|
807
|
+
routesById!: RoutesById<TRouteTree>
|
|
808
|
+
routesByPath!: RoutesByPath<TRouteTree>
|
|
809
|
+
flatRoutes!: Array<AnyRoute>
|
|
810
|
+
isServer!: boolean
|
|
811
|
+
pathParamsDecodeCharMap?: Map<string, string>
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* @deprecated Use the `createRouter` function instead
|
|
815
|
+
*/
|
|
816
|
+
constructor(
|
|
817
|
+
options: RouterConstructorOptions<
|
|
818
|
+
TRouteTree,
|
|
819
|
+
TTrailingSlashOption,
|
|
820
|
+
TDefaultStructuralSharingOption,
|
|
821
|
+
TRouterHistory,
|
|
822
|
+
TDehydrated
|
|
823
|
+
>,
|
|
824
|
+
) {
|
|
825
|
+
this.update({
|
|
826
|
+
defaultPreloadDelay: 50,
|
|
827
|
+
defaultPendingMs: 1000,
|
|
828
|
+
defaultPendingMinMs: 500,
|
|
829
|
+
context: undefined!,
|
|
830
|
+
...options,
|
|
831
|
+
caseSensitive: options.caseSensitive ?? false,
|
|
832
|
+
notFoundMode: options.notFoundMode ?? 'fuzzy',
|
|
833
|
+
stringifySearch: options.stringifySearch ?? defaultStringifySearch,
|
|
834
|
+
parseSearch: options.parseSearch ?? defaultParseSearch,
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
if (typeof document !== 'undefined') {
|
|
838
|
+
;(window as any).__TSR_ROUTER__ = this
|
|
839
|
+
}
|
|
364
840
|
}
|
|
365
841
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
location: null!,
|
|
384
|
-
matches: [],
|
|
385
|
-
actions: {},
|
|
386
|
-
loaders: {},
|
|
387
|
-
lastUpdated: Date.now(),
|
|
388
|
-
isFetching: false,
|
|
389
|
-
isPreloading: false,
|
|
390
|
-
},
|
|
391
|
-
startedLoadingAt: Date.now(),
|
|
392
|
-
subscribe: (listener: Listener): (() => void) => {
|
|
393
|
-
router.listeners.push(listener as Listener)
|
|
394
|
-
return () => {
|
|
395
|
-
router.listeners = router.listeners.filter((x) => x !== listener)
|
|
396
|
-
}
|
|
397
|
-
},
|
|
398
|
-
getRoute: (id) => {
|
|
399
|
-
return router.routesById[id]
|
|
400
|
-
},
|
|
401
|
-
notify: (): void => {
|
|
402
|
-
router.state = {
|
|
403
|
-
...router.state,
|
|
404
|
-
isFetching:
|
|
405
|
-
router.state.status === 'loading' ||
|
|
406
|
-
router.state.matches.some((d) => d.isFetching),
|
|
407
|
-
isPreloading: Object.values(router.matchCache).some(
|
|
408
|
-
(d) =>
|
|
409
|
-
d.match.isFetching &&
|
|
410
|
-
!router.state.matches.find((dd) => dd.matchId === d.match.matchId),
|
|
411
|
-
),
|
|
412
|
-
}
|
|
842
|
+
// These are default implementations that can optionally be overridden
|
|
843
|
+
// by the router provider once rendered. We provide these so that the
|
|
844
|
+
// router can be used in a non-react environment if necessary
|
|
845
|
+
startTransition: StartTransitionFn = (fn) => fn()
|
|
846
|
+
|
|
847
|
+
update: UpdateFn<
|
|
848
|
+
TRouteTree,
|
|
849
|
+
TTrailingSlashOption,
|
|
850
|
+
TDefaultStructuralSharingOption,
|
|
851
|
+
TRouterHistory,
|
|
852
|
+
TDehydrated
|
|
853
|
+
> = (newOptions) => {
|
|
854
|
+
if (newOptions.notFoundRoute) {
|
|
855
|
+
console.warn(
|
|
856
|
+
'The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/framework/react/guide/not-found-errors#migrating-from-notfoundroute for more info.',
|
|
857
|
+
)
|
|
858
|
+
}
|
|
413
859
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
860
|
+
const previousOptions = this.options
|
|
861
|
+
this.options = {
|
|
862
|
+
...this.options,
|
|
863
|
+
...newOptions,
|
|
864
|
+
}
|
|
417
865
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
'routeLoaderData',
|
|
426
|
-
'loaderData',
|
|
427
|
-
'isInvalid',
|
|
428
|
-
'invalidAt',
|
|
866
|
+
this.isServer = this.options.isServer ?? typeof document === 'undefined'
|
|
867
|
+
|
|
868
|
+
this.pathParamsDecodeCharMap = this.options.pathParamsAllowedCharacters
|
|
869
|
+
? new Map(
|
|
870
|
+
this.options.pathParamsAllowedCharacters.map((char) => [
|
|
871
|
+
encodeURIComponent(char),
|
|
872
|
+
char,
|
|
429
873
|
]),
|
|
430
|
-
)
|
|
874
|
+
)
|
|
875
|
+
: undefined
|
|
876
|
+
|
|
877
|
+
if (
|
|
878
|
+
!this.basepath ||
|
|
879
|
+
(newOptions.basepath && newOptions.basepath !== previousOptions.basepath)
|
|
880
|
+
) {
|
|
881
|
+
if (
|
|
882
|
+
newOptions.basepath === undefined ||
|
|
883
|
+
newOptions.basepath === '' ||
|
|
884
|
+
newOptions.basepath === '/'
|
|
885
|
+
) {
|
|
886
|
+
this.basepath = '/'
|
|
887
|
+
} else {
|
|
888
|
+
this.basepath = `/${trimPath(newOptions.basepath)}`
|
|
431
889
|
}
|
|
432
|
-
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (
|
|
893
|
+
!this.history ||
|
|
894
|
+
(this.options.history && this.options.history !== this.history)
|
|
895
|
+
) {
|
|
896
|
+
this.history =
|
|
897
|
+
this.options.history ??
|
|
898
|
+
((this.isServer
|
|
899
|
+
? createMemoryHistory({
|
|
900
|
+
initialEntries: [this.basepath || '/'],
|
|
901
|
+
})
|
|
902
|
+
: createBrowserHistory()) as TRouterHistory)
|
|
903
|
+
this.latestLocation = this.parseLocation()
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (this.options.routeTree !== this.routeTree) {
|
|
907
|
+
this.routeTree = this.options.routeTree as TRouteTree
|
|
908
|
+
this.buildRouteTree()
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (!this.__store) {
|
|
912
|
+
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
913
|
+
onUpdate: () => {
|
|
914
|
+
this.__store.state = {
|
|
915
|
+
...this.state,
|
|
916
|
+
cachedMatches: this.state.cachedMatches.filter(
|
|
917
|
+
(d) => !['redirected'].includes(d.status),
|
|
918
|
+
),
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
setupScrollRestoration(this)
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (
|
|
927
|
+
typeof window !== 'undefined' &&
|
|
928
|
+
'CSS' in window &&
|
|
929
|
+
typeof window.CSS?.supports === 'function'
|
|
930
|
+
) {
|
|
931
|
+
this.isViewTransitionTypesSupported = window.CSS.supports(
|
|
932
|
+
'selector(:active-view-transition-type(a)',
|
|
933
|
+
)
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
get state() {
|
|
938
|
+
return this.__store.state
|
|
939
|
+
}
|
|
433
940
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
941
|
+
buildRouteTree = () => {
|
|
942
|
+
const { routesById, routesByPath, flatRoutes } = processRouteTree({
|
|
943
|
+
routeTree: this.routeTree,
|
|
944
|
+
initRoute: (route, i) => {
|
|
945
|
+
route.init({
|
|
946
|
+
originalIndex: i,
|
|
947
|
+
defaultSsr: this.options.defaultSsr,
|
|
948
|
+
})
|
|
949
|
+
},
|
|
950
|
+
})
|
|
951
|
+
|
|
952
|
+
this.routesById = routesById as RoutesById<TRouteTree>
|
|
953
|
+
this.routesByPath = routesByPath as RoutesByPath<TRouteTree>
|
|
954
|
+
this.flatRoutes = flatRoutes as Array<AnyRoute>
|
|
955
|
+
|
|
956
|
+
const notFoundRoute = this.options.notFoundRoute
|
|
957
|
+
|
|
958
|
+
if (notFoundRoute) {
|
|
959
|
+
notFoundRoute.init({
|
|
960
|
+
originalIndex: 99999999999,
|
|
961
|
+
defaultSsr: this.options.defaultSsr,
|
|
438
962
|
})
|
|
963
|
+
this.routesById[notFoundRoute.id] = notFoundRoute
|
|
964
|
+
}
|
|
965
|
+
}
|
|
439
966
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
967
|
+
subscribe: SubscribeFn = (eventType, fn) => {
|
|
968
|
+
const listener: RouterListener<any> = {
|
|
969
|
+
eventType,
|
|
970
|
+
fn,
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
this.subscribers.add(listener)
|
|
974
|
+
|
|
975
|
+
return () => {
|
|
976
|
+
this.subscribers.delete(listener)
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
emit: EmitFn = (routerEvent) => {
|
|
981
|
+
this.subscribers.forEach((listener) => {
|
|
982
|
+
if (listener.eventType === routerEvent.type) {
|
|
983
|
+
listener.fn(routerEvent)
|
|
454
984
|
}
|
|
455
|
-
}
|
|
985
|
+
})
|
|
986
|
+
}
|
|
456
987
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
988
|
+
parseLocation: ParseLocationFn<TRouteTree> = (
|
|
989
|
+
previousLocation,
|
|
990
|
+
locationToParse,
|
|
991
|
+
) => {
|
|
992
|
+
const parse = ({
|
|
993
|
+
pathname,
|
|
994
|
+
search,
|
|
995
|
+
hash,
|
|
996
|
+
state,
|
|
997
|
+
}: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
|
|
998
|
+
const parsedSearch = this.options.parseSearch(search)
|
|
999
|
+
const searchStr = this.options.stringifySearch(parsedSearch)
|
|
463
1000
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
1001
|
+
return {
|
|
1002
|
+
pathname,
|
|
1003
|
+
searchStr,
|
|
1004
|
+
search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
|
|
1005
|
+
hash: hash.split('#').reverse()[0] ?? '',
|
|
1006
|
+
href: `${pathname}${searchStr}${hash}`,
|
|
1007
|
+
state: replaceEqualDeep(previousLocation?.state, state),
|
|
468
1008
|
}
|
|
1009
|
+
}
|
|
469
1010
|
|
|
470
|
-
|
|
1011
|
+
const location = parse(locationToParse ?? this.history.location)
|
|
471
1012
|
|
|
472
|
-
|
|
473
|
-
console.log(event.location)
|
|
474
|
-
router.loadLocation(
|
|
475
|
-
router.__.parseLocation(event.location, router.location),
|
|
476
|
-
)
|
|
477
|
-
})
|
|
1013
|
+
const { __tempLocation, __tempKey } = location.state
|
|
478
1014
|
|
|
479
|
-
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
1015
|
+
if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
|
|
1016
|
+
// Sync up the location keys
|
|
1017
|
+
const parsedTempLocation = parse(__tempLocation) as any
|
|
1018
|
+
parsedTempLocation.state.key = location.state.key
|
|
1019
|
+
|
|
1020
|
+
delete parsedTempLocation.state.__tempLocation
|
|
1021
|
+
|
|
1022
|
+
return {
|
|
1023
|
+
...parsedTempLocation,
|
|
1024
|
+
maskedLocation: location,
|
|
485
1025
|
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
return location
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
resolvePathWithBase = (from: string, path: string) => {
|
|
1032
|
+
const resolvedPath = resolvePath({
|
|
1033
|
+
basepath: this.basepath,
|
|
1034
|
+
base: from,
|
|
1035
|
+
to: cleanPath(path),
|
|
1036
|
+
trailingSlash: this.options.trailingSlash,
|
|
1037
|
+
caseSensitive: this.options.caseSensitive,
|
|
1038
|
+
})
|
|
1039
|
+
return resolvedPath
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
get looseRoutesById() {
|
|
1043
|
+
return this.routesById as Record<string, AnyRoute>
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
@deprecated use the following signature instead
|
|
1048
|
+
```ts
|
|
1049
|
+
matchRoutes (
|
|
1050
|
+
next: ParsedLocation,
|
|
1051
|
+
opts?: { preload?: boolean; throwOnError?: boolean },
|
|
1052
|
+
): Array<AnyRouteMatch>;
|
|
1053
|
+
```
|
|
1054
|
+
*/
|
|
1055
|
+
matchRoutes: MatchRoutesFn = (
|
|
1056
|
+
pathnameOrNext: string | ParsedLocation,
|
|
1057
|
+
locationSearchOrOpts?: AnySchema | MatchRoutesOpts,
|
|
1058
|
+
opts?: MatchRoutesOpts,
|
|
1059
|
+
) => {
|
|
1060
|
+
if (typeof pathnameOrNext === 'string') {
|
|
1061
|
+
return this.matchRoutesInternal(
|
|
1062
|
+
{
|
|
1063
|
+
pathname: pathnameOrNext,
|
|
1064
|
+
search: locationSearchOrOpts,
|
|
1065
|
+
} as ParsedLocation,
|
|
1066
|
+
opts,
|
|
1067
|
+
)
|
|
1068
|
+
} else {
|
|
1069
|
+
return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
486
1072
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
1073
|
+
private matchRoutesInternal(
|
|
1074
|
+
next: ParsedLocation,
|
|
1075
|
+
opts?: MatchRoutesOpts,
|
|
1076
|
+
): Array<AnyRouteMatch> {
|
|
1077
|
+
const { foundRoute, matchedRoutes, routeParams } = this.getMatchedRoutes(
|
|
1078
|
+
next.pathname,
|
|
1079
|
+
opts?.dest?.to as string,
|
|
1080
|
+
)
|
|
1081
|
+
let isGlobalNotFound = false
|
|
1082
|
+
|
|
1083
|
+
// Check to see if the route needs a 404 entry
|
|
1084
|
+
if (
|
|
1085
|
+
// If we found a route, and it's not an index route and we have left over path
|
|
1086
|
+
foundRoute
|
|
1087
|
+
? foundRoute.path !== '/' && routeParams['**']
|
|
1088
|
+
: // Or if we didn't find a route and we have left over path
|
|
1089
|
+
trimPathRight(next.pathname)
|
|
1090
|
+
) {
|
|
1091
|
+
// If the user has defined an (old) 404 route, use it
|
|
1092
|
+
if (this.options.notFoundRoute) {
|
|
1093
|
+
matchedRoutes.push(this.options.notFoundRoute)
|
|
1094
|
+
} else {
|
|
1095
|
+
// If there is no routes found during path matching
|
|
1096
|
+
isGlobalNotFound = true
|
|
492
1097
|
}
|
|
493
|
-
}
|
|
1098
|
+
}
|
|
494
1099
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
1100
|
+
const globalNotFoundRouteId = (() => {
|
|
1101
|
+
if (!isGlobalNotFound) {
|
|
1102
|
+
return undefined
|
|
1103
|
+
}
|
|
498
1104
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
1105
|
+
if (this.options.notFoundMode !== 'root') {
|
|
1106
|
+
for (let i = matchedRoutes.length - 1; i >= 0; i--) {
|
|
1107
|
+
const route = matchedRoutes[i]!
|
|
1108
|
+
if (route.children) {
|
|
1109
|
+
return route.id
|
|
1110
|
+
}
|
|
504
1111
|
}
|
|
505
|
-
router.location = router.__.parseLocation(router.history.location)
|
|
506
|
-
router.state.location = router.location
|
|
507
1112
|
}
|
|
508
1113
|
|
|
509
|
-
|
|
1114
|
+
return rootRouteId
|
|
1115
|
+
})()
|
|
1116
|
+
|
|
1117
|
+
const parseErrors = matchedRoutes.map((route) => {
|
|
1118
|
+
let parsedParamsError
|
|
1119
|
+
|
|
1120
|
+
const parseParams =
|
|
1121
|
+
route.options.params?.parse ?? route.options.parseParams
|
|
510
1122
|
|
|
511
|
-
|
|
1123
|
+
if (parseParams) {
|
|
1124
|
+
try {
|
|
1125
|
+
const parsedParams = parseParams(routeParams)
|
|
1126
|
+
// Add the parsed params to the accumulated params bag
|
|
1127
|
+
Object.assign(routeParams, parsedParams)
|
|
1128
|
+
} catch (err: any) {
|
|
1129
|
+
parsedParamsError = new PathParamError(err.message, {
|
|
1130
|
+
cause: err,
|
|
1131
|
+
})
|
|
512
1132
|
|
|
513
|
-
|
|
1133
|
+
if (opts?.throwOnError) {
|
|
1134
|
+
throw parsedParamsError
|
|
1135
|
+
}
|
|
514
1136
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
router.routeTree = router.__.buildRouteTree(routeConfig)
|
|
1137
|
+
return parsedParamsError
|
|
1138
|
+
}
|
|
518
1139
|
}
|
|
519
1140
|
|
|
520
|
-
return
|
|
521
|
-
}
|
|
1141
|
+
return
|
|
1142
|
+
})
|
|
522
1143
|
|
|
523
|
-
|
|
524
|
-
;[
|
|
525
|
-
...router.state.matches,
|
|
526
|
-
...(router.state.pending?.matches ?? []),
|
|
527
|
-
].forEach((match) => {
|
|
528
|
-
match.cancel()
|
|
529
|
-
})
|
|
530
|
-
},
|
|
1144
|
+
const matches: Array<AnyRouteMatch> = []
|
|
531
1145
|
|
|
532
|
-
|
|
533
|
-
const
|
|
534
|
-
router.startedLoadingAt = id
|
|
1146
|
+
const getParentContext = (parentMatch?: AnyRouteMatch) => {
|
|
1147
|
+
const parentMatchId = parentMatch?.id
|
|
535
1148
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
1149
|
+
const parentContext = !parentMatchId
|
|
1150
|
+
? ((this.options.context as any) ?? {})
|
|
1151
|
+
: (parentMatch.context ?? this.options.context ?? {})
|
|
540
1152
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1153
|
+
return parentContext
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
matchedRoutes.forEach((route, index) => {
|
|
1157
|
+
// Take each matched route and resolve + validate its search params
|
|
1158
|
+
// This has to happen serially because each route's search params
|
|
1159
|
+
// can depend on the parent route's search params
|
|
1160
|
+
// It must also happen before we create the match so that we can
|
|
1161
|
+
// pass the search params to the route's potential key function
|
|
1162
|
+
// which is used to uniquely identify the route match in state
|
|
1163
|
+
|
|
1164
|
+
const parentMatch = matches[index - 1]
|
|
1165
|
+
|
|
1166
|
+
const [preMatchSearch, strictMatchSearch, searchError]: [
|
|
1167
|
+
Record<string, any>,
|
|
1168
|
+
Record<string, any>,
|
|
1169
|
+
any,
|
|
1170
|
+
] = (() => {
|
|
1171
|
+
// Validate the search params and stabilize them
|
|
1172
|
+
const parentSearch = parentMatch?.search ?? next.search
|
|
1173
|
+
const parentStrictSearch = parentMatch?._strictSearch ?? {}
|
|
1174
|
+
|
|
1175
|
+
try {
|
|
1176
|
+
const strictSearch =
|
|
1177
|
+
validateSearch(route.options.validateSearch, { ...parentSearch }) ??
|
|
1178
|
+
{}
|
|
1179
|
+
|
|
1180
|
+
return [
|
|
1181
|
+
{
|
|
1182
|
+
...parentSearch,
|
|
1183
|
+
...strictSearch,
|
|
1184
|
+
},
|
|
1185
|
+
{ ...parentStrictSearch, ...strictSearch },
|
|
1186
|
+
undefined,
|
|
1187
|
+
]
|
|
1188
|
+
} catch (err: any) {
|
|
1189
|
+
let searchParamError = err
|
|
1190
|
+
if (!(err instanceof SearchParamError)) {
|
|
1191
|
+
searchParamError = new SearchParamError(err.message, {
|
|
1192
|
+
cause: err,
|
|
1193
|
+
})
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (opts?.throwOnError) {
|
|
1197
|
+
throw searchParamError
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
return [parentSearch, {}, searchParamError]
|
|
548
1201
|
}
|
|
549
|
-
})
|
|
550
|
-
router.removeActionQueue = []
|
|
1202
|
+
})()
|
|
551
1203
|
|
|
552
|
-
//
|
|
553
|
-
|
|
1204
|
+
// This is where we need to call route.options.loaderDeps() to get any additional
|
|
1205
|
+
// deps that the route's loader function might need to run. We need to do this
|
|
1206
|
+
// before we create the match so that we can pass the deps to the route's
|
|
1207
|
+
// potential key function which is used to uniquely identify the route match in state
|
|
554
1208
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1209
|
+
const loaderDeps =
|
|
1210
|
+
route.options.loaderDeps?.({
|
|
1211
|
+
search: preMatchSearch,
|
|
1212
|
+
}) ?? ''
|
|
1213
|
+
|
|
1214
|
+
const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : ''
|
|
1215
|
+
|
|
1216
|
+
const { usedParams, interpolatedPath } = interpolatePath({
|
|
1217
|
+
path: route.fullPath,
|
|
1218
|
+
params: routeParams,
|
|
1219
|
+
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
558
1220
|
})
|
|
559
1221
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
1222
|
+
const matchId =
|
|
1223
|
+
interpolatePath({
|
|
1224
|
+
path: route.id,
|
|
1225
|
+
params: routeParams,
|
|
1226
|
+
leaveWildcards: true,
|
|
1227
|
+
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
1228
|
+
}).interpolatedPath + loaderDepsHash
|
|
568
1229
|
|
|
569
|
-
|
|
1230
|
+
// Waste not, want not. If we already have a match for this route,
|
|
1231
|
+
// reuse it. This is important for layout routes, which might stick
|
|
1232
|
+
// around between navigation actions that only change leaf routes.
|
|
570
1233
|
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
1234
|
+
// Existing matches are matches that are already loaded along with
|
|
1235
|
+
// pending matches that are still loading
|
|
1236
|
+
const existingMatch = this.getMatch(matchId)
|
|
1237
|
+
|
|
1238
|
+
const previousMatch = this.state.matches.find(
|
|
1239
|
+
(d) => d.routeId === route.id,
|
|
1240
|
+
)
|
|
575
1241
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
1242
|
+
const cause = previousMatch ? 'stay' : 'enter'
|
|
1243
|
+
|
|
1244
|
+
let match: AnyRouteMatch
|
|
1245
|
+
|
|
1246
|
+
if (existingMatch) {
|
|
1247
|
+
match = {
|
|
1248
|
+
...existingMatch,
|
|
1249
|
+
cause,
|
|
1250
|
+
params: previousMatch
|
|
1251
|
+
? replaceEqualDeep(previousMatch.params, routeParams)
|
|
1252
|
+
: routeParams,
|
|
1253
|
+
_strictParams: usedParams,
|
|
1254
|
+
search: previousMatch
|
|
1255
|
+
? replaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1256
|
+
: replaceEqualDeep(existingMatch.search, preMatchSearch),
|
|
1257
|
+
_strictSearch: strictMatchSearch,
|
|
1258
|
+
}
|
|
1259
|
+
} else {
|
|
1260
|
+
const status =
|
|
1261
|
+
route.options.loader ||
|
|
1262
|
+
route.options.beforeLoad ||
|
|
1263
|
+
route.lazyFn ||
|
|
1264
|
+
routeNeedsPreload(route)
|
|
1265
|
+
? 'pending'
|
|
1266
|
+
: 'success'
|
|
1267
|
+
|
|
1268
|
+
match = {
|
|
1269
|
+
id: matchId,
|
|
1270
|
+
index,
|
|
1271
|
+
routeId: route.id,
|
|
1272
|
+
params: previousMatch
|
|
1273
|
+
? replaceEqualDeep(previousMatch.params, routeParams)
|
|
1274
|
+
: routeParams,
|
|
1275
|
+
_strictParams: usedParams,
|
|
1276
|
+
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
1277
|
+
updatedAt: Date.now(),
|
|
1278
|
+
search: previousMatch
|
|
1279
|
+
? replaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1280
|
+
: preMatchSearch,
|
|
1281
|
+
_strictSearch: strictMatchSearch,
|
|
1282
|
+
searchError: undefined,
|
|
1283
|
+
status,
|
|
1284
|
+
isFetching: false,
|
|
1285
|
+
error: undefined,
|
|
1286
|
+
paramsError: parseErrors[index],
|
|
1287
|
+
__routeContext: {},
|
|
1288
|
+
__beforeLoadContext: {},
|
|
1289
|
+
context: {},
|
|
1290
|
+
abortController: new AbortController(),
|
|
1291
|
+
fetchCount: 0,
|
|
1292
|
+
cause,
|
|
1293
|
+
loaderDeps: previousMatch
|
|
1294
|
+
? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps)
|
|
1295
|
+
: loaderDeps,
|
|
1296
|
+
invalid: false,
|
|
1297
|
+
preload: false,
|
|
1298
|
+
links: undefined,
|
|
1299
|
+
scripts: undefined,
|
|
1300
|
+
headScripts: undefined,
|
|
1301
|
+
meta: undefined,
|
|
1302
|
+
staticData: route.options.staticData || {},
|
|
1303
|
+
loadPromise: createControlledPromise(),
|
|
1304
|
+
fullPath: route.fullPath,
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
if (!opts?.preload) {
|
|
1309
|
+
// If we have a global not found, mark the right match as global not found
|
|
1310
|
+
match.globalNotFound = globalNotFoundRouteId === route.id
|
|
579
1311
|
}
|
|
580
1312
|
|
|
581
|
-
|
|
1313
|
+
// update the searchError if there is one
|
|
1314
|
+
match.searchError = searchError
|
|
1315
|
+
|
|
1316
|
+
const parentContext = getParentContext(parentMatch)
|
|
582
1317
|
|
|
583
|
-
|
|
584
|
-
|
|
1318
|
+
match.context = {
|
|
1319
|
+
...parentContext,
|
|
1320
|
+
...match.__routeContext,
|
|
1321
|
+
...match.__beforeLoadContext,
|
|
1322
|
+
}
|
|
585
1323
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
1324
|
+
matches.push(match)
|
|
1325
|
+
})
|
|
1326
|
+
|
|
1327
|
+
matches.forEach((match, index) => {
|
|
1328
|
+
const route = this.looseRoutesById[match.routeId]!
|
|
1329
|
+
const existingMatch = this.getMatch(match.id)
|
|
1330
|
+
|
|
1331
|
+
// only execute `context` if we are not just building a location
|
|
1332
|
+
if (!existingMatch && opts?._buildLocation !== true) {
|
|
1333
|
+
const parentMatch = matches[index - 1]
|
|
1334
|
+
const parentContext = getParentContext(parentMatch)
|
|
1335
|
+
|
|
1336
|
+
// Update the match's context
|
|
1337
|
+
const contextFnContext: RouteContextOptions<any, any, any, any> = {
|
|
1338
|
+
deps: match.loaderDeps,
|
|
1339
|
+
params: match.params,
|
|
1340
|
+
context: parentContext,
|
|
1341
|
+
location: next,
|
|
1342
|
+
navigate: (opts: any) =>
|
|
1343
|
+
this.navigate({ ...opts, _fromLocation: next }),
|
|
1344
|
+
buildLocation: this.buildLocation,
|
|
1345
|
+
cause: match.cause,
|
|
1346
|
+
abortController: match.abortController,
|
|
1347
|
+
preload: !!match.preload,
|
|
1348
|
+
matches,
|
|
591
1349
|
}
|
|
592
|
-
})
|
|
593
1350
|
|
|
594
|
-
|
|
1351
|
+
// Get the route context
|
|
1352
|
+
match.__routeContext = route.options.context?.(contextFnContext) ?? {}
|
|
595
1353
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
1354
|
+
match.context = {
|
|
1355
|
+
...parentContext,
|
|
1356
|
+
...match.__routeContext,
|
|
1357
|
+
...match.__beforeLoadContext,
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// If it's already a success, update headers and head content
|
|
1362
|
+
// These may get updated again if the match is refreshed
|
|
1363
|
+
// due to being stale
|
|
1364
|
+
if (match.status === 'success') {
|
|
1365
|
+
match.headers = route.options.headers?.({
|
|
1366
|
+
loaderData: match.loaderData,
|
|
600
1367
|
})
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1368
|
+
const assetContext = {
|
|
1369
|
+
matches,
|
|
1370
|
+
match,
|
|
1371
|
+
params: match.params,
|
|
1372
|
+
loaderData: match.loaderData,
|
|
605
1373
|
}
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
1374
|
+
const headFnContent = route.options.head?.(assetContext)
|
|
1375
|
+
match.links = headFnContent?.links
|
|
1376
|
+
match.headScripts = headFnContent?.scripts
|
|
1377
|
+
match.meta = headFnContent?.meta
|
|
1378
|
+
match.scripts = route.options.scripts?.(assetContext)
|
|
1379
|
+
}
|
|
1380
|
+
})
|
|
1381
|
+
|
|
1382
|
+
return matches
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
getMatchedRoutes: GetMatchRoutesFn = (
|
|
1386
|
+
pathname: string,
|
|
1387
|
+
routePathname: string | undefined,
|
|
1388
|
+
) => {
|
|
1389
|
+
return getMatchedRoutes({
|
|
1390
|
+
pathname,
|
|
1391
|
+
routePathname,
|
|
1392
|
+
basepath: this.basepath,
|
|
1393
|
+
caseSensitive: this.options.caseSensitive,
|
|
1394
|
+
routesByPath: this.routesByPath,
|
|
1395
|
+
routesById: this.routesById,
|
|
1396
|
+
flatRoutes: this.flatRoutes,
|
|
1397
|
+
})
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
cancelMatch = (id: string) => {
|
|
1401
|
+
const match = this.getMatch(id)
|
|
1402
|
+
|
|
1403
|
+
if (!match) return
|
|
1404
|
+
|
|
1405
|
+
match.abortController.abort()
|
|
1406
|
+
clearTimeout(match.pendingTimeout)
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
cancelMatches = () => {
|
|
1410
|
+
this.state.pendingMatches?.forEach((match) => {
|
|
1411
|
+
this.cancelMatch(match.id)
|
|
1412
|
+
})
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
buildLocation: BuildLocationFn = (opts) => {
|
|
1416
|
+
const build = (
|
|
1417
|
+
dest: BuildNextOptions & {
|
|
1418
|
+
unmaskOnReload?: boolean
|
|
1419
|
+
} = {},
|
|
1420
|
+
matchedRoutesResult?: MatchedRoutesResult,
|
|
1421
|
+
): ParsedLocation => {
|
|
1422
|
+
const fromMatches = dest._fromLocation
|
|
1423
|
+
? this.matchRoutes(dest._fromLocation, { _buildLocation: true })
|
|
1424
|
+
: this.state.matches
|
|
1425
|
+
|
|
1426
|
+
const fromMatch =
|
|
1427
|
+
dest.from != null
|
|
1428
|
+
? fromMatches.find((d) =>
|
|
1429
|
+
matchPathname(this.basepath, trimPathRight(d.pathname), {
|
|
1430
|
+
to: dest.from,
|
|
1431
|
+
caseSensitive: false,
|
|
1432
|
+
fuzzy: false,
|
|
1433
|
+
}),
|
|
1434
|
+
)
|
|
1435
|
+
: undefined
|
|
1436
|
+
|
|
1437
|
+
const fromPath = fromMatch?.pathname || this.latestLocation.pathname
|
|
1438
|
+
|
|
1439
|
+
invariant(
|
|
1440
|
+
dest.from == null || fromMatch != null,
|
|
1441
|
+
'Could not find match for from: ' + dest.from,
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
const fromSearch = this.state.pendingMatches?.length
|
|
1445
|
+
? last(this.state.pendingMatches)?.search
|
|
1446
|
+
: last(fromMatches)?.search || this.latestLocation.search
|
|
1447
|
+
|
|
1448
|
+
const stayingMatches = matchedRoutesResult?.matchedRoutes.filter((d) =>
|
|
1449
|
+
fromMatches.find((e) => e.routeId === d.id),
|
|
1450
|
+
)
|
|
1451
|
+
let pathname: string
|
|
1452
|
+
if (dest.to) {
|
|
1453
|
+
const resolvePathTo =
|
|
1454
|
+
fromMatch?.fullPath ||
|
|
1455
|
+
last(fromMatches)?.fullPath ||
|
|
1456
|
+
this.latestLocation.pathname
|
|
1457
|
+
pathname = this.resolvePathWithBase(resolvePathTo, `${dest.to}`)
|
|
1458
|
+
} else {
|
|
1459
|
+
const fromRouteByFromPathRouteId =
|
|
1460
|
+
this.routesById[
|
|
1461
|
+
stayingMatches?.find((route) => {
|
|
1462
|
+
const interpolatedPath = interpolatePath({
|
|
1463
|
+
path: route.fullPath,
|
|
1464
|
+
params: matchedRoutesResult?.routeParams ?? {},
|
|
1465
|
+
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
1466
|
+
}).interpolatedPath
|
|
1467
|
+
const pathname = joinPaths([this.basepath, interpolatedPath])
|
|
1468
|
+
return pathname === fromPath
|
|
1469
|
+
})?.id as keyof this['routesById']
|
|
1470
|
+
]
|
|
1471
|
+
pathname = this.resolvePathWithBase(
|
|
1472
|
+
fromPath,
|
|
1473
|
+
fromRouteByFromPathRouteId?.to ?? fromPath,
|
|
609
1474
|
)
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
const prevParams = { ...last(fromMatches)?.params }
|
|
1478
|
+
|
|
1479
|
+
let nextParams =
|
|
1480
|
+
(dest.params ?? true) === true
|
|
1481
|
+
? prevParams
|
|
1482
|
+
: {
|
|
1483
|
+
...prevParams,
|
|
1484
|
+
...functionalUpdate(dest.params as any, prevParams),
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if (Object.keys(nextParams).length > 0) {
|
|
1488
|
+
matchedRoutesResult?.matchedRoutes
|
|
1489
|
+
.map((route) => {
|
|
1490
|
+
return (
|
|
1491
|
+
route.options.params?.stringify ?? route.options.stringifyParams
|
|
1492
|
+
)
|
|
1493
|
+
})
|
|
1494
|
+
.filter(Boolean)
|
|
1495
|
+
.forEach((fn) => {
|
|
1496
|
+
nextParams = { ...nextParams!, ...fn!(nextParams) }
|
|
1497
|
+
})
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
pathname = interpolatePath({
|
|
1501
|
+
path: pathname,
|
|
1502
|
+
params: nextParams ?? {},
|
|
1503
|
+
leaveWildcards: false,
|
|
1504
|
+
leaveParams: opts.leaveParams,
|
|
1505
|
+
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
1506
|
+
}).interpolatedPath
|
|
1507
|
+
|
|
1508
|
+
let search = fromSearch
|
|
1509
|
+
if (opts._includeValidateSearch && this.options.search?.strict) {
|
|
1510
|
+
let validatedSearch = {}
|
|
1511
|
+
matchedRoutesResult?.matchedRoutes.forEach((route) => {
|
|
1512
|
+
try {
|
|
1513
|
+
if (route.options.validateSearch) {
|
|
1514
|
+
validatedSearch = {
|
|
1515
|
+
...validatedSearch,
|
|
1516
|
+
...(validateSearch(route.options.validateSearch, {
|
|
1517
|
+
...validatedSearch,
|
|
1518
|
+
...search,
|
|
1519
|
+
}) ?? {}),
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
} catch {
|
|
1523
|
+
// ignore errors here because they are already handled in matchRoutes
|
|
614
1524
|
}
|
|
1525
|
+
})
|
|
1526
|
+
search = validatedSearch
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
const applyMiddlewares = (search: any) => {
|
|
1530
|
+
const allMiddlewares =
|
|
1531
|
+
matchedRoutesResult?.matchedRoutes.reduce(
|
|
1532
|
+
(acc, route) => {
|
|
1533
|
+
const middlewares: Array<SearchMiddleware<any>> = []
|
|
1534
|
+
if ('search' in route.options) {
|
|
1535
|
+
if (route.options.search?.middlewares) {
|
|
1536
|
+
middlewares.push(...route.options.search.middlewares)
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
// TODO remove preSearchFilters and postSearchFilters in v2
|
|
1540
|
+
else if (
|
|
1541
|
+
route.options.preSearchFilters ||
|
|
1542
|
+
route.options.postSearchFilters
|
|
1543
|
+
) {
|
|
1544
|
+
const legacyMiddleware: SearchMiddleware<any> = ({
|
|
1545
|
+
search,
|
|
1546
|
+
next,
|
|
1547
|
+
}) => {
|
|
1548
|
+
let nextSearch = search
|
|
1549
|
+
if (
|
|
1550
|
+
'preSearchFilters' in route.options &&
|
|
1551
|
+
route.options.preSearchFilters
|
|
1552
|
+
) {
|
|
1553
|
+
nextSearch = route.options.preSearchFilters.reduce(
|
|
1554
|
+
(prev, next) => next(prev),
|
|
1555
|
+
search,
|
|
1556
|
+
)
|
|
1557
|
+
}
|
|
1558
|
+
const result = next(nextSearch)
|
|
1559
|
+
if (
|
|
1560
|
+
'postSearchFilters' in route.options &&
|
|
1561
|
+
route.options.postSearchFilters
|
|
1562
|
+
) {
|
|
1563
|
+
return route.options.postSearchFilters.reduce(
|
|
1564
|
+
(prev, next) => next(prev),
|
|
1565
|
+
result,
|
|
1566
|
+
)
|
|
1567
|
+
}
|
|
1568
|
+
return result
|
|
1569
|
+
}
|
|
1570
|
+
middlewares.push(legacyMiddleware)
|
|
1571
|
+
}
|
|
1572
|
+
if (opts._includeValidateSearch && route.options.validateSearch) {
|
|
1573
|
+
const validate: SearchMiddleware<any> = ({ search, next }) => {
|
|
1574
|
+
const result = next(search)
|
|
1575
|
+
try {
|
|
1576
|
+
const validatedSearch = {
|
|
1577
|
+
...result,
|
|
1578
|
+
...(validateSearch(
|
|
1579
|
+
route.options.validateSearch,
|
|
1580
|
+
result,
|
|
1581
|
+
) ?? {}),
|
|
1582
|
+
}
|
|
1583
|
+
return validatedSearch
|
|
1584
|
+
} catch {
|
|
1585
|
+
// ignore errors here because they are already handled in matchRoutes
|
|
1586
|
+
return result
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
middlewares.push(validate)
|
|
1590
|
+
}
|
|
1591
|
+
return acc.concat(middlewares)
|
|
1592
|
+
},
|
|
1593
|
+
[] as Array<SearchMiddleware<any>>,
|
|
1594
|
+
) ?? []
|
|
1595
|
+
|
|
1596
|
+
// the chain ends here since `next` is not called
|
|
1597
|
+
const final: SearchMiddleware<any> = ({ search }) => {
|
|
1598
|
+
if (!dest.search) {
|
|
1599
|
+
return {}
|
|
1600
|
+
}
|
|
1601
|
+
if (dest.search === true) {
|
|
1602
|
+
return search
|
|
1603
|
+
}
|
|
1604
|
+
return functionalUpdate(dest.search, search)
|
|
615
1605
|
}
|
|
616
|
-
|
|
1606
|
+
allMiddlewares.push(final)
|
|
1607
|
+
|
|
1608
|
+
const applyNext = (index: number, currentSearch: any): any => {
|
|
1609
|
+
// no more middlewares left, return the current search
|
|
1610
|
+
if (index >= allMiddlewares.length) {
|
|
1611
|
+
return currentSearch
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
const middleware = allMiddlewares[index]!
|
|
1615
|
+
|
|
1616
|
+
const next = (newSearch: any): any => {
|
|
1617
|
+
return applyNext(index + 1, newSearch)
|
|
1618
|
+
}
|
|
617
1619
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
1620
|
+
return middleware({ search: currentSearch, next })
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// Start applying middlewares
|
|
1624
|
+
return applyNext(0, search)
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
search = applyMiddlewares(search)
|
|
1628
|
+
|
|
1629
|
+
search = replaceEqualDeep(fromSearch, search)
|
|
1630
|
+
const searchStr = this.options.stringifySearch(search)
|
|
1631
|
+
|
|
1632
|
+
const hash =
|
|
1633
|
+
dest.hash === true
|
|
1634
|
+
? this.latestLocation.hash
|
|
1635
|
+
: dest.hash
|
|
1636
|
+
? functionalUpdate(dest.hash, this.latestLocation.hash)
|
|
1637
|
+
: undefined
|
|
1638
|
+
|
|
1639
|
+
const hashStr = hash ? `#${hash}` : ''
|
|
1640
|
+
|
|
1641
|
+
let nextState =
|
|
1642
|
+
dest.state === true
|
|
1643
|
+
? this.latestLocation.state
|
|
1644
|
+
: dest.state
|
|
1645
|
+
? functionalUpdate(dest.state, this.latestLocation.state)
|
|
1646
|
+
: {}
|
|
1647
|
+
|
|
1648
|
+
nextState = replaceEqualDeep(this.latestLocation.state, nextState)
|
|
1649
|
+
|
|
1650
|
+
return {
|
|
1651
|
+
pathname,
|
|
1652
|
+
search,
|
|
1653
|
+
searchStr,
|
|
1654
|
+
state: nextState as any,
|
|
1655
|
+
hash: hash ?? '',
|
|
1656
|
+
href: `${pathname}${searchStr}${hashStr}`,
|
|
1657
|
+
unmaskOnReload: dest.unmaskOnReload,
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
const buildWithMatches = (
|
|
1662
|
+
dest: BuildNextOptions = {},
|
|
1663
|
+
maskedDest?: BuildNextOptions,
|
|
1664
|
+
) => {
|
|
1665
|
+
const next = build(dest)
|
|
1666
|
+
let maskedNext = maskedDest ? build(maskedDest) : undefined
|
|
1667
|
+
|
|
1668
|
+
if (!maskedNext) {
|
|
1669
|
+
let params = {}
|
|
1670
|
+
|
|
1671
|
+
const foundMask = this.options.routeMasks?.find((d) => {
|
|
1672
|
+
const match = matchPathname(this.basepath, next.pathname, {
|
|
1673
|
+
to: d.from,
|
|
1674
|
+
caseSensitive: false,
|
|
1675
|
+
fuzzy: false,
|
|
1676
|
+
})
|
|
1677
|
+
|
|
1678
|
+
if (match) {
|
|
1679
|
+
params = match
|
|
1680
|
+
return true
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
return false
|
|
622
1684
|
})
|
|
1685
|
+
|
|
1686
|
+
if (foundMask) {
|
|
1687
|
+
const { from: _from, ...maskProps } = foundMask
|
|
1688
|
+
maskedDest = {
|
|
1689
|
+
...pick(opts, ['from']),
|
|
1690
|
+
...maskProps,
|
|
1691
|
+
params,
|
|
1692
|
+
}
|
|
1693
|
+
maskedNext = build(maskedDest)
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
const nextMatches = this.getMatchedRoutes(
|
|
1698
|
+
next.pathname,
|
|
1699
|
+
dest.to as string,
|
|
1700
|
+
)
|
|
1701
|
+
const final = build(dest, nextMatches)
|
|
1702
|
+
|
|
1703
|
+
if (maskedNext) {
|
|
1704
|
+
const maskedMatches = this.getMatchedRoutes(
|
|
1705
|
+
maskedNext.pathname,
|
|
1706
|
+
maskedDest?.to as string,
|
|
1707
|
+
)
|
|
1708
|
+
const maskedFinal = build(maskedDest, maskedMatches)
|
|
1709
|
+
final.maskedLocation = maskedFinal
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
return final
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
if (opts.mask) {
|
|
1716
|
+
return buildWithMatches(opts, {
|
|
1717
|
+
...pick(opts, ['from']),
|
|
1718
|
+
...opts.mask,
|
|
623
1719
|
})
|
|
1720
|
+
}
|
|
624
1721
|
|
|
625
|
-
|
|
626
|
-
|
|
1722
|
+
return buildWithMatches(opts)
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
commitLocationPromise: undefined | ControlledPromise<void>
|
|
1726
|
+
|
|
1727
|
+
commitLocation: CommitLocationFn = ({
|
|
1728
|
+
viewTransition,
|
|
1729
|
+
ignoreBlocker,
|
|
1730
|
+
...next
|
|
1731
|
+
}) => {
|
|
1732
|
+
const isSameState = () => {
|
|
1733
|
+
// the following props are ignored but may still be provided when navigating,
|
|
1734
|
+
// temporarily add the previous values to the next state so they don't affect
|
|
1735
|
+
// the comparison
|
|
1736
|
+
const ignoredProps = [
|
|
1737
|
+
'key',
|
|
1738
|
+
'__TSR_index',
|
|
1739
|
+
'__hashScrollIntoViewOptions',
|
|
1740
|
+
] as const
|
|
1741
|
+
ignoredProps.forEach((prop) => {
|
|
1742
|
+
;(next.state as any)[prop] = this.latestLocation.state[prop]
|
|
1743
|
+
})
|
|
1744
|
+
const isEqual = deepEqual(next.state, this.latestLocation.state)
|
|
1745
|
+
ignoredProps.forEach((prop) => {
|
|
1746
|
+
delete next.state[prop]
|
|
627
1747
|
})
|
|
1748
|
+
return isEqual
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
const isSameUrl = this.latestLocation.href === next.href
|
|
1752
|
+
|
|
1753
|
+
const previousCommitPromise = this.commitLocationPromise
|
|
1754
|
+
this.commitLocationPromise = createControlledPromise<void>(() => {
|
|
1755
|
+
previousCommitPromise?.resolve()
|
|
1756
|
+
})
|
|
1757
|
+
|
|
1758
|
+
// Don't commit to history if nothing changed
|
|
1759
|
+
if (isSameUrl && isSameState()) {
|
|
1760
|
+
this.load()
|
|
1761
|
+
} else {
|
|
1762
|
+
// eslint-disable-next-line prefer-const
|
|
1763
|
+
let { maskedLocation, hashScrollIntoView, ...nextHistory } = next
|
|
1764
|
+
|
|
1765
|
+
if (maskedLocation) {
|
|
1766
|
+
nextHistory = {
|
|
1767
|
+
...maskedLocation,
|
|
1768
|
+
state: {
|
|
1769
|
+
...maskedLocation.state,
|
|
1770
|
+
__tempKey: undefined,
|
|
1771
|
+
__tempLocation: {
|
|
1772
|
+
...nextHistory,
|
|
1773
|
+
search: nextHistory.searchStr,
|
|
1774
|
+
state: {
|
|
1775
|
+
...nextHistory.state,
|
|
1776
|
+
__tempKey: undefined!,
|
|
1777
|
+
__tempLocation: undefined!,
|
|
1778
|
+
key: undefined!,
|
|
1779
|
+
},
|
|
1780
|
+
},
|
|
1781
|
+
},
|
|
1782
|
+
}
|
|
628
1783
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1784
|
+
if (
|
|
1785
|
+
nextHistory.unmaskOnReload ??
|
|
1786
|
+
this.options.unmaskOnReload ??
|
|
1787
|
+
false
|
|
1788
|
+
) {
|
|
1789
|
+
nextHistory.state.__tempKey = this.tempLocationKey
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
nextHistory.state.__hashScrollIntoViewOptions =
|
|
1794
|
+
hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true
|
|
1795
|
+
|
|
1796
|
+
this.shouldViewTransition = viewTransition
|
|
1797
|
+
|
|
1798
|
+
this.history[next.replace ? 'replace' : 'push'](
|
|
1799
|
+
nextHistory.href,
|
|
1800
|
+
nextHistory.state,
|
|
1801
|
+
{ ignoreBlocker },
|
|
1802
|
+
)
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
this.resetNextScroll = next.resetScroll ?? true
|
|
1806
|
+
|
|
1807
|
+
if (!this.history.subscribers.size) {
|
|
1808
|
+
this.load()
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
return this.commitLocationPromise
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
buildAndCommitLocation = ({
|
|
1815
|
+
replace,
|
|
1816
|
+
resetScroll,
|
|
1817
|
+
hashScrollIntoView,
|
|
1818
|
+
viewTransition,
|
|
1819
|
+
ignoreBlocker,
|
|
1820
|
+
href,
|
|
1821
|
+
...rest
|
|
1822
|
+
}: BuildNextOptions & CommitLocationOptions = {}) => {
|
|
1823
|
+
if (href) {
|
|
1824
|
+
const currentIndex = this.history.location.state.__TSR_index
|
|
1825
|
+
const parsed = parseHref(href, {
|
|
1826
|
+
__TSR_index: replace ? currentIndex : currentIndex + 1,
|
|
635
1827
|
})
|
|
1828
|
+
rest.to = parsed.pathname
|
|
1829
|
+
rest.search = this.options.parseSearch(parsed.search)
|
|
1830
|
+
// remove the leading `#` from the hash
|
|
1831
|
+
rest.hash = parsed.hash.slice(1)
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const location = this.buildLocation({
|
|
1835
|
+
...(rest as any),
|
|
1836
|
+
_includeValidateSearch: true,
|
|
1837
|
+
})
|
|
1838
|
+
return this.commitLocation({
|
|
1839
|
+
...location,
|
|
1840
|
+
viewTransition,
|
|
1841
|
+
replace,
|
|
1842
|
+
resetScroll,
|
|
1843
|
+
hashScrollIntoView,
|
|
1844
|
+
ignoreBlocker,
|
|
1845
|
+
})
|
|
1846
|
+
}
|
|
636
1847
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1848
|
+
navigate: NavigateFn = ({ to, reloadDocument, href, ...rest }) => {
|
|
1849
|
+
if (!reloadDocument && href) {
|
|
1850
|
+
try {
|
|
1851
|
+
new URL(`${href}`)
|
|
1852
|
+
reloadDocument = true
|
|
1853
|
+
} catch {}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
if (reloadDocument) {
|
|
1857
|
+
if (!href) {
|
|
1858
|
+
const location = this.buildLocation({ to, ...rest } as any)
|
|
1859
|
+
href = this.history.createHref(location.href)
|
|
642
1860
|
}
|
|
643
|
-
if (
|
|
644
|
-
|
|
645
|
-
|
|
1861
|
+
if (rest.replace) {
|
|
1862
|
+
window.location.replace(href)
|
|
1863
|
+
} else {
|
|
1864
|
+
window.location.href = href
|
|
646
1865
|
}
|
|
1866
|
+
return
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
return this.buildAndCommitLocation({
|
|
1870
|
+
...rest,
|
|
1871
|
+
href,
|
|
1872
|
+
to: to as string,
|
|
1873
|
+
})
|
|
1874
|
+
}
|
|
647
1875
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
1876
|
+
latestLoadPromise: undefined | Promise<void>
|
|
1877
|
+
|
|
1878
|
+
beforeLoad = () => {
|
|
1879
|
+
// Cancel any pending matches
|
|
1880
|
+
this.cancelMatches()
|
|
1881
|
+
this.latestLocation = this.parseLocation(this.latestLocation)
|
|
1882
|
+
|
|
1883
|
+
// Match the routes
|
|
1884
|
+
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
1885
|
+
|
|
1886
|
+
// Ingest the new matches
|
|
1887
|
+
this.__store.setState((s) => ({
|
|
1888
|
+
...s,
|
|
1889
|
+
status: 'pending',
|
|
1890
|
+
isLoading: true,
|
|
1891
|
+
location: this.latestLocation,
|
|
1892
|
+
pendingMatches,
|
|
1893
|
+
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1894
|
+
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1895
|
+
return !pendingMatches.find((e) => e.id === d.id)
|
|
1896
|
+
}),
|
|
1897
|
+
}))
|
|
1898
|
+
}
|
|
655
1899
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1900
|
+
load: LoadFn = async (opts?: { sync?: boolean }): Promise<void> => {
|
|
1901
|
+
let redirect: AnyRedirect | undefined
|
|
1902
|
+
let notFound: NotFoundError | undefined
|
|
1903
|
+
|
|
1904
|
+
let loadPromise: Promise<void>
|
|
1905
|
+
|
|
1906
|
+
// eslint-disable-next-line prefer-const
|
|
1907
|
+
loadPromise = new Promise<void>((resolve) => {
|
|
1908
|
+
this.startTransition(async () => {
|
|
1909
|
+
try {
|
|
1910
|
+
this.beforeLoad()
|
|
1911
|
+
const next = this.latestLocation
|
|
1912
|
+
const prevLocation = this.state.resolvedLocation
|
|
1913
|
+
|
|
1914
|
+
if (!this.state.redirect) {
|
|
1915
|
+
this.emit({
|
|
1916
|
+
type: 'onBeforeNavigate',
|
|
1917
|
+
...getLocationChangeInfo({
|
|
1918
|
+
resolvedLocation: prevLocation,
|
|
1919
|
+
location: next,
|
|
1920
|
+
}),
|
|
1921
|
+
})
|
|
1922
|
+
}
|
|
659
1923
|
|
|
660
|
-
|
|
661
|
-
|
|
1924
|
+
this.emit({
|
|
1925
|
+
type: 'onBeforeLoad',
|
|
1926
|
+
...getLocationChangeInfo({
|
|
1927
|
+
resolvedLocation: prevLocation,
|
|
1928
|
+
location: next,
|
|
1929
|
+
}),
|
|
1930
|
+
})
|
|
662
1931
|
|
|
663
|
-
|
|
664
|
-
|
|
1932
|
+
await this.loadMatches({
|
|
1933
|
+
sync: opts?.sync,
|
|
1934
|
+
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
1935
|
+
location: next,
|
|
1936
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1937
|
+
onReady: async () => {
|
|
1938
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1939
|
+
this.startViewTransition(async () => {
|
|
1940
|
+
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
1941
|
+
|
|
1942
|
+
// Commit the pending matches. If a previous match was
|
|
1943
|
+
// removed, place it in the cachedMatches
|
|
1944
|
+
let exitingMatches!: Array<AnyRouteMatch>
|
|
1945
|
+
let enteringMatches!: Array<AnyRouteMatch>
|
|
1946
|
+
let stayingMatches!: Array<AnyRouteMatch>
|
|
1947
|
+
|
|
1948
|
+
batch(() => {
|
|
1949
|
+
this.__store.setState((s) => {
|
|
1950
|
+
const previousMatches = s.matches
|
|
1951
|
+
const newMatches = s.pendingMatches || s.matches
|
|
1952
|
+
|
|
1953
|
+
exitingMatches = previousMatches.filter(
|
|
1954
|
+
(match) => !newMatches.find((d) => d.id === match.id),
|
|
1955
|
+
)
|
|
1956
|
+
enteringMatches = newMatches.filter(
|
|
1957
|
+
(match) =>
|
|
1958
|
+
!previousMatches.find((d) => d.id === match.id),
|
|
1959
|
+
)
|
|
1960
|
+
stayingMatches = previousMatches.filter((match) =>
|
|
1961
|
+
newMatches.find((d) => d.id === match.id),
|
|
1962
|
+
)
|
|
1963
|
+
|
|
1964
|
+
return {
|
|
1965
|
+
...s,
|
|
1966
|
+
isLoading: false,
|
|
1967
|
+
loadedAt: Date.now(),
|
|
1968
|
+
matches: newMatches,
|
|
1969
|
+
pendingMatches: undefined,
|
|
1970
|
+
cachedMatches: [
|
|
1971
|
+
...s.cachedMatches,
|
|
1972
|
+
...exitingMatches.filter((d) => d.status !== 'error'),
|
|
1973
|
+
],
|
|
1974
|
+
}
|
|
1975
|
+
})
|
|
1976
|
+
this.clearExpiredCache()
|
|
1977
|
+
})
|
|
1978
|
+
|
|
1979
|
+
//
|
|
1980
|
+
;(
|
|
1981
|
+
[
|
|
1982
|
+
[exitingMatches, 'onLeave'],
|
|
1983
|
+
[enteringMatches, 'onEnter'],
|
|
1984
|
+
[stayingMatches, 'onStay'],
|
|
1985
|
+
] as const
|
|
1986
|
+
).forEach(([matches, hook]) => {
|
|
1987
|
+
matches.forEach((match) => {
|
|
1988
|
+
this.looseRoutesById[match.routeId]!.options[hook]?.(match)
|
|
1989
|
+
})
|
|
1990
|
+
})
|
|
1991
|
+
})
|
|
1992
|
+
},
|
|
1993
|
+
})
|
|
1994
|
+
} catch (err) {
|
|
1995
|
+
if (isRedirect(err)) {
|
|
1996
|
+
redirect = err
|
|
1997
|
+
if (!this.isServer) {
|
|
1998
|
+
this.navigate({
|
|
1999
|
+
...redirect.options,
|
|
2000
|
+
replace: true,
|
|
2001
|
+
ignoreBlocker: true,
|
|
2002
|
+
})
|
|
2003
|
+
}
|
|
2004
|
+
} else if (isNotFound(err)) {
|
|
2005
|
+
notFound = err
|
|
2006
|
+
}
|
|
665
2007
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
2008
|
+
this.__store.setState((s) => ({
|
|
2009
|
+
...s,
|
|
2010
|
+
statusCode: redirect
|
|
2011
|
+
? redirect.status
|
|
2012
|
+
: notFound
|
|
2013
|
+
? 404
|
|
2014
|
+
: s.matches.some((d) => d.status === 'error')
|
|
2015
|
+
? 500
|
|
2016
|
+
: 200,
|
|
2017
|
+
redirect,
|
|
2018
|
+
}))
|
|
669
2019
|
}
|
|
670
2020
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
2021
|
+
if (this.latestLoadPromise === loadPromise) {
|
|
2022
|
+
this.commitLocationPromise?.resolve()
|
|
2023
|
+
this.latestLoadPromise = undefined
|
|
2024
|
+
this.commitLocationPromise = undefined
|
|
674
2025
|
}
|
|
675
|
-
|
|
676
|
-
// Everything else gets removed
|
|
677
|
-
delete router.matchCache[matchId]
|
|
2026
|
+
resolve()
|
|
678
2027
|
})
|
|
679
|
-
}
|
|
2028
|
+
})
|
|
680
2029
|
|
|
681
|
-
|
|
682
|
-
const next = router.buildNext(navigateOpts)
|
|
683
|
-
const matches = router.matchRoutes(next.pathname, {
|
|
684
|
-
strictParseParams: true,
|
|
685
|
-
})
|
|
686
|
-
await router.loadMatches(matches)
|
|
687
|
-
return matches
|
|
688
|
-
},
|
|
2030
|
+
this.latestLoadPromise = loadPromise
|
|
689
2031
|
|
|
690
|
-
|
|
691
|
-
const next = router.buildNext(navigateOpts)
|
|
692
|
-
const matches = router.matchRoutes(next.pathname, {
|
|
693
|
-
strictParseParams: true,
|
|
694
|
-
})
|
|
695
|
-
await router.loadMatches(matches, {
|
|
696
|
-
preload: true,
|
|
697
|
-
maxAge:
|
|
698
|
-
loaderOpts.maxAge ??
|
|
699
|
-
router.options.defaultPreloadMaxAge ??
|
|
700
|
-
router.options.defaultLoaderMaxAge ??
|
|
701
|
-
0,
|
|
702
|
-
gcMaxAge:
|
|
703
|
-
loaderOpts.gcMaxAge ??
|
|
704
|
-
router.options.defaultPreloadGcMaxAge ??
|
|
705
|
-
router.options.defaultLoaderGcMaxAge ??
|
|
706
|
-
0,
|
|
707
|
-
})
|
|
708
|
-
return matches
|
|
709
|
-
},
|
|
2032
|
+
await loadPromise
|
|
710
2033
|
|
|
711
|
-
|
|
712
|
-
|
|
2034
|
+
while (
|
|
2035
|
+
(this.latestLoadPromise as any) &&
|
|
2036
|
+
loadPromise !== this.latestLoadPromise
|
|
2037
|
+
) {
|
|
2038
|
+
await this.latestLoadPromise
|
|
2039
|
+
}
|
|
713
2040
|
|
|
714
|
-
|
|
2041
|
+
if (this.hasNotFoundMatch()) {
|
|
2042
|
+
this.__store.setState((s) => ({
|
|
2043
|
+
...s,
|
|
2044
|
+
statusCode: 404,
|
|
2045
|
+
}))
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
715
2048
|
|
|
716
|
-
|
|
717
|
-
|
|
2049
|
+
startViewTransition = (fn: () => Promise<void>) => {
|
|
2050
|
+
// Determine if we should start a view transition from the navigation
|
|
2051
|
+
// or from the router default
|
|
2052
|
+
const shouldViewTransition =
|
|
2053
|
+
this.shouldViewTransition ?? this.options.defaultViewTransition
|
|
2054
|
+
|
|
2055
|
+
// Reset the view transition flag
|
|
2056
|
+
delete this.shouldViewTransition
|
|
2057
|
+
// Attempt to start a view transition (or just apply the changes if we can't)
|
|
2058
|
+
if (
|
|
2059
|
+
shouldViewTransition &&
|
|
2060
|
+
typeof document !== 'undefined' &&
|
|
2061
|
+
'startViewTransition' in document &&
|
|
2062
|
+
typeof document.startViewTransition === 'function'
|
|
2063
|
+
) {
|
|
2064
|
+
// lib.dom.ts doesn't support viewTransition types variant yet.
|
|
2065
|
+
// TODO: Fix this when dom types are updated
|
|
2066
|
+
let startViewTransitionParams: any
|
|
2067
|
+
|
|
2068
|
+
if (
|
|
2069
|
+
typeof shouldViewTransition === 'object' &&
|
|
2070
|
+
this.isViewTransitionTypesSupported
|
|
2071
|
+
) {
|
|
2072
|
+
const next = this.latestLocation
|
|
2073
|
+
const prevLocation = this.state.resolvedLocation
|
|
2074
|
+
|
|
2075
|
+
const resolvedViewTransitionTypes =
|
|
2076
|
+
typeof shouldViewTransition.types === 'function'
|
|
2077
|
+
? shouldViewTransition.types(
|
|
2078
|
+
getLocationChangeInfo({
|
|
2079
|
+
resolvedLocation: prevLocation,
|
|
2080
|
+
location: next,
|
|
2081
|
+
}),
|
|
2082
|
+
)
|
|
2083
|
+
: shouldViewTransition.types
|
|
2084
|
+
|
|
2085
|
+
startViewTransitionParams = {
|
|
2086
|
+
update: fn,
|
|
2087
|
+
types: resolvedViewTransitionTypes,
|
|
2088
|
+
}
|
|
2089
|
+
} else {
|
|
2090
|
+
startViewTransitionParams = fn
|
|
718
2091
|
}
|
|
719
2092
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
2093
|
+
document.startViewTransition(startViewTransitionParams)
|
|
2094
|
+
} else {
|
|
2095
|
+
fn()
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
724
2098
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
2099
|
+
updateMatch: UpdateMatchFn = (id, updater) => {
|
|
2100
|
+
let updated!: AnyRouteMatch
|
|
2101
|
+
const isPending = this.state.pendingMatches?.find((d) => d.id === id)
|
|
2102
|
+
const isMatched = this.state.matches.find((d) => d.id === id)
|
|
2103
|
+
const isCached = this.state.cachedMatches.find((d) => d.id === id)
|
|
2104
|
+
|
|
2105
|
+
const matchesKey = isPending
|
|
2106
|
+
? 'pendingMatches'
|
|
2107
|
+
: isMatched
|
|
2108
|
+
? 'matches'
|
|
2109
|
+
: isCached
|
|
2110
|
+
? 'cachedMatches'
|
|
2111
|
+
: ''
|
|
2112
|
+
|
|
2113
|
+
if (matchesKey) {
|
|
2114
|
+
this.__store.setState((s) => ({
|
|
2115
|
+
...s,
|
|
2116
|
+
[matchesKey]: s[matchesKey]?.map((d) =>
|
|
2117
|
+
d.id === id ? (updated = updater(d)) : d,
|
|
2118
|
+
),
|
|
2119
|
+
}))
|
|
2120
|
+
}
|
|
728
2121
|
|
|
729
|
-
|
|
2122
|
+
return updated
|
|
2123
|
+
}
|
|
730
2124
|
|
|
731
|
-
|
|
2125
|
+
getMatch: GetMatchFn = (matchId: string) => {
|
|
2126
|
+
return [
|
|
2127
|
+
...this.state.cachedMatches,
|
|
2128
|
+
...(this.state.pendingMatches ?? []),
|
|
2129
|
+
...this.state.matches,
|
|
2130
|
+
].find((d) => d.id === matchId)
|
|
2131
|
+
}
|
|
732
2132
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
2133
|
+
loadMatches = async ({
|
|
2134
|
+
location,
|
|
2135
|
+
matches,
|
|
2136
|
+
preload: allPreload,
|
|
2137
|
+
onReady,
|
|
2138
|
+
updateMatch = this.updateMatch,
|
|
2139
|
+
sync,
|
|
2140
|
+
}: {
|
|
2141
|
+
location: ParsedLocation
|
|
2142
|
+
matches: Array<AnyRouteMatch>
|
|
2143
|
+
preload?: boolean
|
|
2144
|
+
onReady?: () => Promise<void>
|
|
2145
|
+
updateMatch?: (
|
|
2146
|
+
id: string,
|
|
2147
|
+
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
2148
|
+
) => void
|
|
2149
|
+
getMatch?: (matchId: string) => AnyRouteMatch | undefined
|
|
2150
|
+
sync?: boolean
|
|
2151
|
+
}): Promise<Array<MakeRouteMatch>> => {
|
|
2152
|
+
let firstBadMatchIndex: number | undefined
|
|
2153
|
+
let rendered = false
|
|
2154
|
+
|
|
2155
|
+
const triggerOnReady = async () => {
|
|
2156
|
+
if (!rendered) {
|
|
2157
|
+
rendered = true
|
|
2158
|
+
await onReady?.()
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
const resolvePreload = (matchId: string) => {
|
|
2163
|
+
return !!(allPreload && !this.state.matches.find((d) => d.id === matchId))
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
2167
|
+
if (isRedirect(err) || isNotFound(err)) {
|
|
2168
|
+
if (isRedirect(err)) {
|
|
2169
|
+
if (err.redirectHandled) {
|
|
2170
|
+
if (!err.options.reloadDocument) {
|
|
2171
|
+
throw err
|
|
740
2172
|
}
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
741
2175
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
2176
|
+
updateMatch(match.id, (prev) => ({
|
|
2177
|
+
...prev,
|
|
2178
|
+
status: isRedirect(err)
|
|
2179
|
+
? 'redirected'
|
|
2180
|
+
: isNotFound(err)
|
|
2181
|
+
? 'notFound'
|
|
2182
|
+
: 'error',
|
|
2183
|
+
isFetching: false,
|
|
2184
|
+
error: err,
|
|
2185
|
+
beforeLoadPromise: undefined,
|
|
2186
|
+
loaderPromise: undefined,
|
|
2187
|
+
}))
|
|
2188
|
+
|
|
2189
|
+
if (!(err as any).routeId) {
|
|
2190
|
+
;(err as any).routeId = match.routeId
|
|
2191
|
+
}
|
|
745
2192
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
2193
|
+
match.beforeLoadPromise?.resolve()
|
|
2194
|
+
match.loaderPromise?.resolve()
|
|
2195
|
+
match.loadPromise?.resolve()
|
|
2196
|
+
|
|
2197
|
+
if (isRedirect(err)) {
|
|
2198
|
+
rendered = true
|
|
2199
|
+
err.options._fromLocation = location
|
|
2200
|
+
err.redirectHandled = true
|
|
2201
|
+
err = this.resolveRedirect(err)
|
|
2202
|
+
throw err
|
|
2203
|
+
} else if (isNotFound(err)) {
|
|
2204
|
+
this._handleNotFound(matches, err, {
|
|
2205
|
+
updateMatch,
|
|
2206
|
+
})
|
|
2207
|
+
this.serverSsr?.onMatchSettled({
|
|
2208
|
+
router: this,
|
|
2209
|
+
match: this.getMatch(match.id)!,
|
|
2210
|
+
})
|
|
2211
|
+
throw err
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
try {
|
|
2217
|
+
await new Promise<void>((resolveAll, rejectAll) => {
|
|
2218
|
+
;(async () => {
|
|
2219
|
+
try {
|
|
2220
|
+
const handleSerialError = (
|
|
2221
|
+
index: number,
|
|
2222
|
+
err: any,
|
|
2223
|
+
routerCode: string,
|
|
2224
|
+
) => {
|
|
2225
|
+
const { id: matchId, routeId } = matches[index]!
|
|
2226
|
+
const route = this.looseRoutesById[routeId]!
|
|
2227
|
+
|
|
2228
|
+
// Much like suspense, we use a promise here to know if
|
|
2229
|
+
// we've been outdated by a new loadMatches call and
|
|
2230
|
+
// should abort the current async operation
|
|
2231
|
+
if (err instanceof Promise) {
|
|
2232
|
+
throw err
|
|
2233
|
+
}
|
|
752
2234
|
|
|
753
|
-
|
|
754
|
-
|
|
2235
|
+
err.routerCode = routerCode
|
|
2236
|
+
firstBadMatchIndex = firstBadMatchIndex ?? index
|
|
2237
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
755
2238
|
|
|
756
2239
|
try {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
throw err
|
|
762
|
-
}
|
|
2240
|
+
route.options.onError?.(err)
|
|
2241
|
+
} catch (errorHandlerErr) {
|
|
2242
|
+
err = errorHandlerErr
|
|
2243
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
763
2244
|
}
|
|
764
2245
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
2246
|
+
updateMatch(matchId, (prev) => {
|
|
2247
|
+
prev.beforeLoadPromise?.resolve()
|
|
2248
|
+
prev.loadPromise?.resolve()
|
|
2249
|
+
|
|
2250
|
+
return {
|
|
2251
|
+
...prev,
|
|
2252
|
+
error: err,
|
|
2253
|
+
status: 'error',
|
|
2254
|
+
isFetching: false,
|
|
2255
|
+
updatedAt: Date.now(),
|
|
2256
|
+
abortController: new AbortController(),
|
|
2257
|
+
beforeLoadPromise: undefined,
|
|
2258
|
+
}
|
|
2259
|
+
})
|
|
769
2260
|
}
|
|
770
2261
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
2262
|
+
for (const [index, { id: matchId, routeId }] of matches.entries()) {
|
|
2263
|
+
const existingMatch = this.getMatch(matchId)!
|
|
2264
|
+
const parentMatchId = matches[index - 1]?.id
|
|
2265
|
+
|
|
2266
|
+
const route = this.looseRoutesById[routeId]!
|
|
2267
|
+
|
|
2268
|
+
const pendingMs =
|
|
2269
|
+
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
2270
|
+
|
|
2271
|
+
const shouldPending = !!(
|
|
2272
|
+
onReady &&
|
|
2273
|
+
!this.isServer &&
|
|
2274
|
+
!resolvePreload(matchId) &&
|
|
2275
|
+
(route.options.loader ||
|
|
2276
|
+
route.options.beforeLoad ||
|
|
2277
|
+
routeNeedsPreload(route)) &&
|
|
2278
|
+
typeof pendingMs === 'number' &&
|
|
2279
|
+
pendingMs !== Infinity &&
|
|
2280
|
+
(route.options.pendingComponent ??
|
|
2281
|
+
(this.options as any)?.defaultPendingComponent)
|
|
2282
|
+
)
|
|
774
2283
|
|
|
775
|
-
|
|
776
|
-
|
|
2284
|
+
let executeBeforeLoad = true
|
|
2285
|
+
if (
|
|
2286
|
+
// If we are in the middle of a load, either of these will be present
|
|
2287
|
+
// (not to be confused with `loadPromise`, which is always defined)
|
|
2288
|
+
existingMatch.beforeLoadPromise ||
|
|
2289
|
+
existingMatch.loaderPromise
|
|
2290
|
+
) {
|
|
2291
|
+
if (shouldPending) {
|
|
2292
|
+
setTimeout(() => {
|
|
2293
|
+
try {
|
|
2294
|
+
// Update the match and prematurely resolve the loadMatches promise so that
|
|
2295
|
+
// the pending component can start rendering
|
|
2296
|
+
triggerOnReady()
|
|
2297
|
+
} catch {}
|
|
2298
|
+
}, pendingMs)
|
|
2299
|
+
}
|
|
777
2300
|
|
|
778
|
-
|
|
779
|
-
|
|
2301
|
+
// Wait for the beforeLoad to resolve before we continue
|
|
2302
|
+
await existingMatch.beforeLoadPromise
|
|
2303
|
+
executeBeforeLoad = this.getMatch(matchId)!.status !== 'success'
|
|
2304
|
+
}
|
|
2305
|
+
if (executeBeforeLoad) {
|
|
2306
|
+
// If we are not in the middle of a load OR the previous load failed, start it
|
|
2307
|
+
try {
|
|
2308
|
+
updateMatch(matchId, (prev) => {
|
|
2309
|
+
// explicitly capture the previous loadPromise
|
|
2310
|
+
const prevLoadPromise = prev.loadPromise
|
|
2311
|
+
return {
|
|
2312
|
+
...prev,
|
|
2313
|
+
loadPromise: createControlledPromise<void>(() => {
|
|
2314
|
+
prevLoadPromise?.resolve()
|
|
2315
|
+
}),
|
|
2316
|
+
beforeLoadPromise: createControlledPromise<void>(),
|
|
2317
|
+
}
|
|
2318
|
+
})
|
|
2319
|
+
const abortController = new AbortController()
|
|
2320
|
+
|
|
2321
|
+
let pendingTimeout: ReturnType<typeof setTimeout>
|
|
2322
|
+
|
|
2323
|
+
if (shouldPending) {
|
|
2324
|
+
// If we might show a pending component, we need to wait for the
|
|
2325
|
+
// pending promise to resolve before we start showing that state
|
|
2326
|
+
pendingTimeout = setTimeout(() => {
|
|
2327
|
+
try {
|
|
2328
|
+
// Update the match and prematurely resolve the loadMatches promise so that
|
|
2329
|
+
// the pending component can start rendering
|
|
2330
|
+
triggerOnReady()
|
|
2331
|
+
} catch {}
|
|
2332
|
+
}, pendingMs)
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
const { paramsError, searchError } = this.getMatch(matchId)!
|
|
2336
|
+
|
|
2337
|
+
if (paramsError) {
|
|
2338
|
+
handleSerialError(index, paramsError, 'PARSE_PARAMS')
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
if (searchError) {
|
|
2342
|
+
handleSerialError(index, searchError, 'VALIDATE_SEARCH')
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
const getParentMatchContext = () =>
|
|
2346
|
+
parentMatchId
|
|
2347
|
+
? this.getMatch(parentMatchId)!.context
|
|
2348
|
+
: (this.options.context ?? {})
|
|
2349
|
+
|
|
2350
|
+
updateMatch(matchId, (prev) => ({
|
|
2351
|
+
...prev,
|
|
2352
|
+
isFetching: 'beforeLoad',
|
|
2353
|
+
fetchCount: prev.fetchCount + 1,
|
|
2354
|
+
abortController,
|
|
2355
|
+
pendingTimeout,
|
|
2356
|
+
context: {
|
|
2357
|
+
...getParentMatchContext(),
|
|
2358
|
+
...prev.__routeContext,
|
|
2359
|
+
},
|
|
2360
|
+
}))
|
|
2361
|
+
|
|
2362
|
+
const { search, params, context, cause } =
|
|
2363
|
+
this.getMatch(matchId)!
|
|
2364
|
+
|
|
2365
|
+
const preload = resolvePreload(matchId)
|
|
2366
|
+
|
|
2367
|
+
const beforeLoadFnContext: BeforeLoadContextOptions<
|
|
2368
|
+
any,
|
|
2369
|
+
any,
|
|
2370
|
+
any,
|
|
2371
|
+
any,
|
|
2372
|
+
any
|
|
2373
|
+
> = {
|
|
2374
|
+
search,
|
|
2375
|
+
abortController,
|
|
2376
|
+
params,
|
|
2377
|
+
preload,
|
|
2378
|
+
context,
|
|
2379
|
+
location,
|
|
2380
|
+
navigate: (opts: any) =>
|
|
2381
|
+
this.navigate({ ...opts, _fromLocation: location }),
|
|
2382
|
+
buildLocation: this.buildLocation,
|
|
2383
|
+
cause: preload ? 'preload' : cause,
|
|
2384
|
+
matches,
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
const beforeLoadContext =
|
|
2388
|
+
(await route.options.beforeLoad?.(beforeLoadFnContext)) ??
|
|
2389
|
+
{}
|
|
2390
|
+
|
|
2391
|
+
if (
|
|
2392
|
+
isRedirect(beforeLoadContext) ||
|
|
2393
|
+
isNotFound(beforeLoadContext)
|
|
2394
|
+
) {
|
|
2395
|
+
handleSerialError(index, beforeLoadContext, 'BEFORE_LOAD')
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
updateMatch(matchId, (prev) => {
|
|
2399
|
+
return {
|
|
2400
|
+
...prev,
|
|
2401
|
+
__beforeLoadContext: beforeLoadContext,
|
|
2402
|
+
context: {
|
|
2403
|
+
...getParentMatchContext(),
|
|
2404
|
+
...prev.__routeContext,
|
|
2405
|
+
...beforeLoadContext,
|
|
2406
|
+
},
|
|
2407
|
+
abortController,
|
|
2408
|
+
}
|
|
2409
|
+
})
|
|
2410
|
+
} catch (err) {
|
|
2411
|
+
handleSerialError(index, err, 'BEFORE_LOAD')
|
|
2412
|
+
}
|
|
780
2413
|
|
|
781
|
-
|
|
2414
|
+
updateMatch(matchId, (prev) => {
|
|
2415
|
+
prev.beforeLoadPromise?.resolve()
|
|
782
2416
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
2417
|
+
return {
|
|
2418
|
+
...prev,
|
|
2419
|
+
beforeLoadPromise: undefined,
|
|
2420
|
+
isFetching: false,
|
|
2421
|
+
}
|
|
2422
|
+
})
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
786
2425
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
2426
|
+
const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
|
|
2427
|
+
const matchPromises: Array<Promise<AnyRouteMatch>> = []
|
|
2428
|
+
|
|
2429
|
+
validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
|
|
2430
|
+
matchPromises.push(
|
|
2431
|
+
(async () => {
|
|
2432
|
+
const { loaderPromise: prevLoaderPromise } =
|
|
2433
|
+
this.getMatch(matchId)!
|
|
2434
|
+
|
|
2435
|
+
let loaderShouldRunAsync = false
|
|
2436
|
+
let loaderIsRunningAsync = false
|
|
2437
|
+
|
|
2438
|
+
if (prevLoaderPromise) {
|
|
2439
|
+
await prevLoaderPromise
|
|
2440
|
+
const match = this.getMatch(matchId)!
|
|
2441
|
+
if (match.error) {
|
|
2442
|
+
handleRedirectAndNotFound(match, match.error)
|
|
2443
|
+
}
|
|
2444
|
+
} else {
|
|
2445
|
+
const parentMatchPromise = matchPromises[index - 1] as any
|
|
2446
|
+
const route = this.looseRoutesById[routeId]!
|
|
2447
|
+
|
|
2448
|
+
const getLoaderContext = (): LoaderFnContext => {
|
|
2449
|
+
const {
|
|
2450
|
+
params,
|
|
2451
|
+
loaderDeps,
|
|
2452
|
+
abortController,
|
|
2453
|
+
context,
|
|
2454
|
+
cause,
|
|
2455
|
+
} = this.getMatch(matchId)!
|
|
2456
|
+
|
|
2457
|
+
const preload = resolvePreload(matchId)
|
|
2458
|
+
|
|
2459
|
+
return {
|
|
2460
|
+
params,
|
|
2461
|
+
deps: loaderDeps,
|
|
2462
|
+
preload: !!preload,
|
|
2463
|
+
parentMatchPromise,
|
|
2464
|
+
abortController: abortController,
|
|
2465
|
+
context,
|
|
2466
|
+
location,
|
|
2467
|
+
navigate: (opts) =>
|
|
2468
|
+
this.navigate({ ...opts, _fromLocation: location }),
|
|
2469
|
+
cause: preload ? 'preload' : cause,
|
|
2470
|
+
route,
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// This is where all of the stale-while-revalidate magic happens
|
|
2475
|
+
const age = Date.now() - this.getMatch(matchId)!.updatedAt
|
|
2476
|
+
|
|
2477
|
+
const preload = resolvePreload(matchId)
|
|
2478
|
+
|
|
2479
|
+
const staleAge = preload
|
|
2480
|
+
? (route.options.preloadStaleTime ??
|
|
2481
|
+
this.options.defaultPreloadStaleTime ??
|
|
2482
|
+
30_000) // 30 seconds for preloads by default
|
|
2483
|
+
: (route.options.staleTime ??
|
|
2484
|
+
this.options.defaultStaleTime ??
|
|
2485
|
+
0)
|
|
2486
|
+
|
|
2487
|
+
const shouldReloadOption = route.options.shouldReload
|
|
2488
|
+
|
|
2489
|
+
// Default to reloading the route all the time
|
|
2490
|
+
// Allow shouldReload to get the last say,
|
|
2491
|
+
// if provided.
|
|
2492
|
+
const shouldReload =
|
|
2493
|
+
typeof shouldReloadOption === 'function'
|
|
2494
|
+
? shouldReloadOption(getLoaderContext())
|
|
2495
|
+
: shouldReloadOption
|
|
2496
|
+
|
|
2497
|
+
updateMatch(matchId, (prev) => ({
|
|
2498
|
+
...prev,
|
|
2499
|
+
loaderPromise: createControlledPromise<void>(),
|
|
2500
|
+
preload:
|
|
2501
|
+
!!preload &&
|
|
2502
|
+
!this.state.matches.find((d) => d.id === matchId),
|
|
2503
|
+
}))
|
|
2504
|
+
|
|
2505
|
+
const runLoader = async () => {
|
|
2506
|
+
try {
|
|
2507
|
+
// If the Matches component rendered
|
|
2508
|
+
// the pending component and needs to show it for
|
|
2509
|
+
// a minimum duration, we''ll wait for it to resolve
|
|
2510
|
+
// before committing to the match and resolving
|
|
2511
|
+
// the loadPromise
|
|
2512
|
+
const potentialPendingMinPromise = async () => {
|
|
2513
|
+
const latestMatch = this.getMatch(matchId)!
|
|
2514
|
+
|
|
2515
|
+
if (latestMatch.minPendingPromise) {
|
|
2516
|
+
await latestMatch.minPendingPromise
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
// Actually run the loader and handle the result
|
|
2521
|
+
try {
|
|
2522
|
+
this.loadRouteChunk(route)
|
|
2523
|
+
|
|
2524
|
+
updateMatch(matchId, (prev) => ({
|
|
2525
|
+
...prev,
|
|
2526
|
+
isFetching: 'loader',
|
|
2527
|
+
}))
|
|
2528
|
+
|
|
2529
|
+
// Kick off the loader!
|
|
2530
|
+
const loaderData =
|
|
2531
|
+
await route.options.loader?.(getLoaderContext())
|
|
2532
|
+
|
|
2533
|
+
handleRedirectAndNotFound(
|
|
2534
|
+
this.getMatch(matchId)!,
|
|
2535
|
+
loaderData,
|
|
2536
|
+
)
|
|
2537
|
+
|
|
2538
|
+
// Lazy option can modify the route options,
|
|
2539
|
+
// so we need to wait for it to resolve before
|
|
2540
|
+
// we can use the options
|
|
2541
|
+
await route._lazyPromise
|
|
2542
|
+
|
|
2543
|
+
await potentialPendingMinPromise()
|
|
2544
|
+
|
|
2545
|
+
const assetContext = {
|
|
2546
|
+
matches,
|
|
2547
|
+
match: this.getMatch(matchId)!,
|
|
2548
|
+
params: this.getMatch(matchId)!.params,
|
|
2549
|
+
loaderData,
|
|
2550
|
+
}
|
|
2551
|
+
const headFnContent =
|
|
2552
|
+
route.options.head?.(assetContext)
|
|
2553
|
+
const meta = headFnContent?.meta
|
|
2554
|
+
const links = headFnContent?.links
|
|
2555
|
+
const headScripts = headFnContent?.scripts
|
|
2556
|
+
|
|
2557
|
+
const scripts = route.options.scripts?.(assetContext)
|
|
2558
|
+
const headers = route.options.headers?.({
|
|
2559
|
+
loaderData,
|
|
2560
|
+
})
|
|
2561
|
+
|
|
2562
|
+
// Last but not least, wait for the the components
|
|
2563
|
+
// to be preloaded before we resolve the match
|
|
2564
|
+
await route._componentsPromise
|
|
2565
|
+
|
|
2566
|
+
updateMatch(matchId, (prev) => ({
|
|
2567
|
+
...prev,
|
|
2568
|
+
error: undefined,
|
|
2569
|
+
status: 'success',
|
|
2570
|
+
isFetching: false,
|
|
2571
|
+
updatedAt: Date.now(),
|
|
2572
|
+
loaderData,
|
|
2573
|
+
meta,
|
|
2574
|
+
links,
|
|
2575
|
+
headScripts,
|
|
2576
|
+
headers,
|
|
2577
|
+
scripts,
|
|
2578
|
+
}))
|
|
2579
|
+
} catch (e) {
|
|
2580
|
+
let error = e
|
|
2581
|
+
|
|
2582
|
+
await potentialPendingMinPromise()
|
|
2583
|
+
|
|
2584
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, e)
|
|
2585
|
+
|
|
2586
|
+
try {
|
|
2587
|
+
route.options.onError?.(e)
|
|
2588
|
+
} catch (onErrorError) {
|
|
2589
|
+
error = onErrorError
|
|
2590
|
+
handleRedirectAndNotFound(
|
|
2591
|
+
this.getMatch(matchId)!,
|
|
2592
|
+
onErrorError,
|
|
2593
|
+
)
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
updateMatch(matchId, (prev) => ({
|
|
2597
|
+
...prev,
|
|
2598
|
+
error,
|
|
2599
|
+
status: 'error',
|
|
2600
|
+
isFetching: false,
|
|
2601
|
+
}))
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
this.serverSsr?.onMatchSettled({
|
|
2605
|
+
router: this,
|
|
2606
|
+
match: this.getMatch(matchId)!,
|
|
2607
|
+
})
|
|
2608
|
+
} catch (err) {
|
|
2609
|
+
updateMatch(matchId, (prev) => ({
|
|
2610
|
+
...prev,
|
|
2611
|
+
loaderPromise: undefined,
|
|
2612
|
+
}))
|
|
2613
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
// If the route is successful and still fresh, just resolve
|
|
2618
|
+
const { status, invalid } = this.getMatch(matchId)!
|
|
2619
|
+
loaderShouldRunAsync =
|
|
2620
|
+
status === 'success' &&
|
|
2621
|
+
(invalid || (shouldReload ?? age > staleAge))
|
|
2622
|
+
if (preload && route.options.preload === false) {
|
|
2623
|
+
// Do nothing
|
|
2624
|
+
} else if (loaderShouldRunAsync && !sync) {
|
|
2625
|
+
loaderIsRunningAsync = true
|
|
2626
|
+
;(async () => {
|
|
2627
|
+
try {
|
|
2628
|
+
await runLoader()
|
|
2629
|
+
const { loaderPromise, loadPromise } =
|
|
2630
|
+
this.getMatch(matchId)!
|
|
2631
|
+
loaderPromise?.resolve()
|
|
2632
|
+
loadPromise?.resolve()
|
|
2633
|
+
updateMatch(matchId, (prev) => ({
|
|
2634
|
+
...prev,
|
|
2635
|
+
loaderPromise: undefined,
|
|
2636
|
+
}))
|
|
2637
|
+
} catch (err) {
|
|
2638
|
+
if (isRedirect(err)) {
|
|
2639
|
+
await this.navigate(err.options)
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
})()
|
|
2643
|
+
} else if (
|
|
2644
|
+
status !== 'success' ||
|
|
2645
|
+
(loaderShouldRunAsync && sync)
|
|
2646
|
+
) {
|
|
2647
|
+
await runLoader()
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
if (!loaderIsRunningAsync) {
|
|
2651
|
+
const { loaderPromise, loadPromise } =
|
|
2652
|
+
this.getMatch(matchId)!
|
|
2653
|
+
loaderPromise?.resolve()
|
|
2654
|
+
loadPromise?.resolve()
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
updateMatch(matchId, (prev) => ({
|
|
2658
|
+
...prev,
|
|
2659
|
+
isFetching: loaderIsRunningAsync ? prev.isFetching : false,
|
|
2660
|
+
loaderPromise: loaderIsRunningAsync
|
|
2661
|
+
? prev.loaderPromise
|
|
2662
|
+
: undefined,
|
|
2663
|
+
invalid: false,
|
|
2664
|
+
}))
|
|
2665
|
+
return this.getMatch(matchId)!
|
|
2666
|
+
})(),
|
|
2667
|
+
)
|
|
798
2668
|
})
|
|
799
2669
|
|
|
800
|
-
|
|
801
|
-
})
|
|
2670
|
+
await Promise.all(matchPromises)
|
|
802
2671
|
|
|
803
|
-
|
|
2672
|
+
resolveAll()
|
|
2673
|
+
} catch (err) {
|
|
2674
|
+
rejectAll(err)
|
|
2675
|
+
}
|
|
2676
|
+
})()
|
|
2677
|
+
})
|
|
2678
|
+
await triggerOnReady()
|
|
2679
|
+
} catch (err) {
|
|
2680
|
+
if (isRedirect(err) || isNotFound(err)) {
|
|
2681
|
+
if (isNotFound(err) && !allPreload) {
|
|
2682
|
+
await triggerOnReady()
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
throw err
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
804
2688
|
|
|
805
|
-
|
|
806
|
-
|
|
2689
|
+
return matches
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
invalidate: InvalidateFn<
|
|
2693
|
+
RouterCore<
|
|
2694
|
+
TRouteTree,
|
|
2695
|
+
TTrailingSlashOption,
|
|
2696
|
+
TDefaultStructuralSharingOption,
|
|
2697
|
+
TRouterHistory,
|
|
2698
|
+
TDehydrated
|
|
2699
|
+
>
|
|
2700
|
+
> = (opts) => {
|
|
2701
|
+
const invalidate = (d: MakeRouteMatch<TRouteTree>) => {
|
|
2702
|
+
if (opts?.filter?.(d as MakeRouteMatchUnion<this>) ?? true) {
|
|
2703
|
+
return {
|
|
2704
|
+
...d,
|
|
2705
|
+
invalid: true,
|
|
2706
|
+
...(d.status === 'error'
|
|
2707
|
+
? ({ status: 'pending', error: undefined } as const)
|
|
2708
|
+
: {}),
|
|
807
2709
|
}
|
|
808
2710
|
}
|
|
2711
|
+
return d
|
|
2712
|
+
}
|
|
809
2713
|
|
|
810
|
-
|
|
2714
|
+
this.__store.setState((s) => ({
|
|
2715
|
+
...s,
|
|
2716
|
+
matches: s.matches.map(invalidate),
|
|
2717
|
+
cachedMatches: s.cachedMatches.map(invalidate),
|
|
2718
|
+
pendingMatches: s.pendingMatches?.map(invalidate),
|
|
2719
|
+
}))
|
|
811
2720
|
|
|
812
|
-
|
|
2721
|
+
return this.load({ sync: opts?.sync })
|
|
2722
|
+
}
|
|
813
2723
|
|
|
814
|
-
|
|
815
|
-
|
|
2724
|
+
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
|
|
2725
|
+
if (!redirect.options.href) {
|
|
2726
|
+
redirect.options.href = this.buildLocation(redirect.options).href
|
|
2727
|
+
redirect.headers.set('Location', redirect.options.href)
|
|
2728
|
+
}
|
|
816
2729
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
match.__.validate()
|
|
821
|
-
match.load(loaderOpts)
|
|
2730
|
+
if (!redirect.headers.get('Location')) {
|
|
2731
|
+
redirect.headers.set('Location', redirect.options.href)
|
|
2732
|
+
}
|
|
822
2733
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (loaderOpts?.withPending) match.__.startPending()
|
|
2734
|
+
return redirect
|
|
2735
|
+
}
|
|
826
2736
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
2737
|
+
clearCache: ClearCacheFn<this> = (opts) => {
|
|
2738
|
+
const filter = opts?.filter
|
|
2739
|
+
if (filter !== undefined) {
|
|
2740
|
+
this.__store.setState((s) => {
|
|
2741
|
+
return {
|
|
2742
|
+
...s,
|
|
2743
|
+
cachedMatches: s.cachedMatches.filter(
|
|
2744
|
+
(m) => !filter(m as MakeRouteMatchUnion<this>),
|
|
2745
|
+
),
|
|
830
2746
|
}
|
|
831
2747
|
})
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
invalidateRoute: (opts: MatchLocation) => {
|
|
839
|
-
const next = router.buildNext(opts)
|
|
840
|
-
const unloadedMatchIds = router
|
|
841
|
-
.matchRoutes(next.pathname)
|
|
842
|
-
.map((d) => d.matchId)
|
|
843
|
-
;[
|
|
844
|
-
...router.state.matches,
|
|
845
|
-
...(router.state.pending?.matches ?? []),
|
|
846
|
-
].forEach((match) => {
|
|
847
|
-
if (unloadedMatchIds.includes(match.matchId)) {
|
|
848
|
-
match.invalidate()
|
|
2748
|
+
} else {
|
|
2749
|
+
this.__store.setState((s) => {
|
|
2750
|
+
return {
|
|
2751
|
+
...s,
|
|
2752
|
+
cachedMatches: [],
|
|
849
2753
|
}
|
|
850
2754
|
})
|
|
851
|
-
}
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
852
2757
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
search: true,
|
|
858
|
-
}),
|
|
2758
|
+
clearExpiredCache = () => {
|
|
2759
|
+
// This is where all of the garbage collection magic happens
|
|
2760
|
+
const filter = (d: MakeRouteMatch<TRouteTree>) => {
|
|
2761
|
+
const route = this.looseRoutesById[d.routeId]!
|
|
859
2762
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
2763
|
+
if (!route.options.loader) {
|
|
2764
|
+
return true
|
|
2765
|
+
}
|
|
863
2766
|
|
|
864
|
-
|
|
865
|
-
//
|
|
2767
|
+
// If the route was preloaded, use the preloadGcTime
|
|
2768
|
+
// otherwise, use the gcTime
|
|
2769
|
+
const gcTime =
|
|
2770
|
+
(d.preload
|
|
2771
|
+
? (route.options.preloadGcTime ?? this.options.defaultPreloadGcTime)
|
|
2772
|
+
: (route.options.gcTime ?? this.options.defaultGcTime)) ??
|
|
2773
|
+
5 * 60 * 1000
|
|
2774
|
+
|
|
2775
|
+
return !(d.status !== 'error' && Date.now() - d.updatedAt < gcTime)
|
|
2776
|
+
}
|
|
2777
|
+
this.clearCache({ filter })
|
|
2778
|
+
}
|
|
866
2779
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
2780
|
+
loadRouteChunk = (route: AnyRoute) => {
|
|
2781
|
+
if (route._lazyPromise === undefined) {
|
|
2782
|
+
if (route.lazyFn) {
|
|
2783
|
+
route._lazyPromise = route.lazyFn().then((lazyRoute) => {
|
|
2784
|
+
// explicitly don't copy over the lazy route's id
|
|
2785
|
+
const { id: _id, ...options } = lazyRoute.options
|
|
2786
|
+
Object.assign(route.options, options)
|
|
2787
|
+
})
|
|
2788
|
+
} else {
|
|
2789
|
+
route._lazyPromise = Promise.resolve()
|
|
872
2790
|
}
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// If for some reason lazy resolves more lazy components...
|
|
2794
|
+
// We'll wait for that before pre attempt to preload any
|
|
2795
|
+
// components themselves.
|
|
2796
|
+
if (route._componentsPromise === undefined) {
|
|
2797
|
+
route._componentsPromise = route._lazyPromise.then(() =>
|
|
2798
|
+
Promise.all(
|
|
2799
|
+
componentTypes.map(async (type) => {
|
|
2800
|
+
const component = route.options[type]
|
|
2801
|
+
if ((component as any)?.preload) {
|
|
2802
|
+
await (component as any).preload()
|
|
2803
|
+
}
|
|
2804
|
+
}),
|
|
2805
|
+
),
|
|
2806
|
+
)
|
|
2807
|
+
}
|
|
2808
|
+
return route._componentsPromise
|
|
2809
|
+
}
|
|
873
2810
|
|
|
874
|
-
|
|
2811
|
+
preloadRoute: PreloadRouteFn<
|
|
2812
|
+
TRouteTree,
|
|
2813
|
+
TTrailingSlashOption,
|
|
2814
|
+
TDefaultStructuralSharingOption,
|
|
2815
|
+
TRouterHistory
|
|
2816
|
+
> = async (opts) => {
|
|
2817
|
+
const next = this.buildLocation(opts as any)
|
|
2818
|
+
|
|
2819
|
+
let matches = this.matchRoutes(next, {
|
|
2820
|
+
throwOnError: true,
|
|
2821
|
+
preload: true,
|
|
2822
|
+
dest: opts,
|
|
2823
|
+
})
|
|
2824
|
+
|
|
2825
|
+
const activeMatchIds = new Set(
|
|
2826
|
+
[...this.state.matches, ...(this.state.pendingMatches ?? [])].map(
|
|
2827
|
+
(d) => d.id,
|
|
2828
|
+
),
|
|
2829
|
+
)
|
|
2830
|
+
|
|
2831
|
+
const loadedMatchIds = new Set([
|
|
2832
|
+
...activeMatchIds,
|
|
2833
|
+
...this.state.cachedMatches.map((d) => d.id),
|
|
2834
|
+
])
|
|
2835
|
+
|
|
2836
|
+
// If the matches are already loaded, we need to add them to the cachedMatches
|
|
2837
|
+
batch(() => {
|
|
2838
|
+
matches.forEach((match) => {
|
|
2839
|
+
if (!loadedMatchIds.has(match.id)) {
|
|
2840
|
+
this.__store.setState((s) => ({
|
|
2841
|
+
...s,
|
|
2842
|
+
cachedMatches: [...(s.cachedMatches as any), match],
|
|
2843
|
+
}))
|
|
2844
|
+
}
|
|
2845
|
+
})
|
|
2846
|
+
})
|
|
875
2847
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
2848
|
+
try {
|
|
2849
|
+
matches = await this.loadMatches({
|
|
2850
|
+
matches,
|
|
2851
|
+
location: next,
|
|
2852
|
+
preload: true,
|
|
2853
|
+
updateMatch: (id, updater) => {
|
|
2854
|
+
// Don't update the match if it's currently loaded
|
|
2855
|
+
if (activeMatchIds.has(id)) {
|
|
2856
|
+
matches = matches.map((d) => (d.id === id ? updater(d) : d))
|
|
2857
|
+
} else {
|
|
2858
|
+
this.updateMatch(id, updater)
|
|
2859
|
+
}
|
|
2860
|
+
},
|
|
2861
|
+
})
|
|
2862
|
+
|
|
2863
|
+
return matches
|
|
2864
|
+
} catch (err) {
|
|
2865
|
+
if (isRedirect(err)) {
|
|
2866
|
+
if (err.options.reloadDocument) {
|
|
2867
|
+
return undefined
|
|
879
2868
|
}
|
|
880
|
-
return
|
|
881
|
-
...
|
|
882
|
-
|
|
2869
|
+
return await this.preloadRoute({
|
|
2870
|
+
...err.options,
|
|
2871
|
+
_fromLocation: next,
|
|
883
2872
|
})
|
|
884
2873
|
}
|
|
2874
|
+
if (!isNotFound(err)) {
|
|
2875
|
+
// Preload errors are not fatal, but we should still log them
|
|
2876
|
+
console.error(err)
|
|
2877
|
+
}
|
|
2878
|
+
return undefined
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
885
2881
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
2882
|
+
matchRoute: MatchRouteFn<
|
|
2883
|
+
TRouteTree,
|
|
2884
|
+
TTrailingSlashOption,
|
|
2885
|
+
TDefaultStructuralSharingOption,
|
|
2886
|
+
TRouterHistory
|
|
2887
|
+
> = (location, opts) => {
|
|
2888
|
+
const matchLocation = {
|
|
2889
|
+
...location,
|
|
2890
|
+
to: location.to
|
|
2891
|
+
? this.resolvePathWithBase(
|
|
2892
|
+
(location.from || '') as string,
|
|
2893
|
+
location.to as string,
|
|
2894
|
+
)
|
|
2895
|
+
: undefined,
|
|
2896
|
+
params: location.params || {},
|
|
2897
|
+
leaveParams: true,
|
|
2898
|
+
}
|
|
2899
|
+
const next = this.buildLocation(matchLocation as any)
|
|
2900
|
+
|
|
2901
|
+
if (opts?.pending && this.state.status !== 'pending') {
|
|
2902
|
+
return false
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
const pending =
|
|
2906
|
+
opts?.pending === undefined ? !this.state.isLoading : opts.pending
|
|
2907
|
+
|
|
2908
|
+
const baseLocation = pending
|
|
2909
|
+
? this.latestLocation
|
|
2910
|
+
: this.state.resolvedLocation || this.state.location
|
|
2911
|
+
|
|
2912
|
+
const match = matchPathname(this.basepath, baseLocation.pathname, {
|
|
2913
|
+
...opts,
|
|
2914
|
+
to: next.pathname,
|
|
2915
|
+
}) as any
|
|
2916
|
+
|
|
2917
|
+
if (!match) {
|
|
2918
|
+
return false
|
|
2919
|
+
}
|
|
2920
|
+
if (location.params) {
|
|
2921
|
+
if (!deepEqual(match, location.params, { partial: true })) {
|
|
2922
|
+
return false
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
891
2925
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
2926
|
+
if (match && (opts?.includeSearch ?? true)) {
|
|
2927
|
+
return deepEqual(baseLocation.search, next.search, { partial: true })
|
|
2928
|
+
? match
|
|
2929
|
+
: false
|
|
2930
|
+
}
|
|
895
2931
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
const toString = String(to)
|
|
899
|
-
const fromString = String(from)
|
|
2932
|
+
return match
|
|
2933
|
+
}
|
|
900
2934
|
|
|
901
|
-
|
|
2935
|
+
ssr?: {
|
|
2936
|
+
manifest: Manifest | undefined
|
|
2937
|
+
serializer: StartSerializer
|
|
2938
|
+
}
|
|
902
2939
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
2940
|
+
serverSsr?: {
|
|
2941
|
+
injectedHtml: Array<InjectedHtmlEntry>
|
|
2942
|
+
injectHtml: (getHtml: () => string | Promise<string>) => Promise<void>
|
|
2943
|
+
injectScript: (
|
|
2944
|
+
getScript: () => string | Promise<string>,
|
|
2945
|
+
opts?: { logScript?: boolean },
|
|
2946
|
+
) => Promise<void>
|
|
2947
|
+
streamValue: (key: string, value: any) => void
|
|
2948
|
+
streamedKeys: Set<string>
|
|
2949
|
+
onMatchSettled: (opts: { router: AnyRouter; match: AnyRouteMatch }) => any
|
|
2950
|
+
}
|
|
907
2951
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
)
|
|
2952
|
+
clientSsr?: {
|
|
2953
|
+
getStreamedValue: <T>(key: string) => T | undefined
|
|
2954
|
+
}
|
|
912
2955
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
2956
|
+
_handleNotFound = (
|
|
2957
|
+
matches: Array<AnyRouteMatch>,
|
|
2958
|
+
err: NotFoundError,
|
|
2959
|
+
{
|
|
2960
|
+
updateMatch = this.updateMatch,
|
|
2961
|
+
}: {
|
|
2962
|
+
updateMatch?: (
|
|
2963
|
+
id: string,
|
|
2964
|
+
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
2965
|
+
) => void
|
|
2966
|
+
} = {},
|
|
2967
|
+
) => {
|
|
2968
|
+
// Find the route that should handle the not found error
|
|
2969
|
+
// First check if a specific route is requested to show the error
|
|
2970
|
+
const routeCursor = this.routesById[err.routeId ?? ''] ?? this.routeTree
|
|
2971
|
+
const matchesByRouteId: Record<string, AnyRouteMatch> = {}
|
|
2972
|
+
|
|
2973
|
+
// Setup routesByRouteId object for quick access
|
|
2974
|
+
for (const match of matches) {
|
|
2975
|
+
matchesByRouteId[match.routeId] = match
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// Ensure a NotFoundComponent exists on the route
|
|
2979
|
+
if (
|
|
2980
|
+
!routeCursor.options.notFoundComponent &&
|
|
2981
|
+
(this.options as any)?.defaultNotFoundComponent
|
|
2982
|
+
) {
|
|
2983
|
+
routeCursor.options.notFoundComponent = (
|
|
2984
|
+
this.options as any
|
|
2985
|
+
).defaultNotFoundComponent
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
// Ensure we have a notFoundComponent
|
|
2989
|
+
invariant(
|
|
2990
|
+
routeCursor.options.notFoundComponent,
|
|
2991
|
+
'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
|
|
2992
|
+
)
|
|
2993
|
+
|
|
2994
|
+
// Find the match for this route
|
|
2995
|
+
const matchForRoute = matchesByRouteId[routeCursor.id]
|
|
2996
|
+
|
|
2997
|
+
invariant(
|
|
2998
|
+
matchForRoute,
|
|
2999
|
+
'Could not find match for route: ' + routeCursor.id,
|
|
3000
|
+
)
|
|
3001
|
+
|
|
3002
|
+
// Assign the error to the match - using non-null assertion since we've checked with invariant
|
|
3003
|
+
updateMatch(matchForRoute.id, (prev) => ({
|
|
3004
|
+
...prev,
|
|
3005
|
+
status: 'notFound',
|
|
3006
|
+
error: err,
|
|
3007
|
+
isFetching: false,
|
|
3008
|
+
}))
|
|
922
3009
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
replace,
|
|
931
|
-
activeOptions,
|
|
932
|
-
preload,
|
|
933
|
-
preloadMaxAge: userPreloadMaxAge,
|
|
934
|
-
preloadGcMaxAge: userPreloadGcMaxAge,
|
|
935
|
-
preloadDelay: userPreloadDelay,
|
|
936
|
-
disabled,
|
|
937
|
-
}) => {
|
|
938
|
-
// If this link simply reloads the current route,
|
|
939
|
-
// make sure it has a new key so it will trigger a data refresh
|
|
940
|
-
|
|
941
|
-
// If this `to` is a valid external URL, return
|
|
942
|
-
// null for LinkUtils
|
|
3010
|
+
if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
|
|
3011
|
+
err.routeId = routeCursor.parentRoute.id
|
|
3012
|
+
this._handleNotFound(matches, err, {
|
|
3013
|
+
updateMatch,
|
|
3014
|
+
})
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
943
3017
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
} catch (e) {}
|
|
3018
|
+
hasNotFoundMatch = () => {
|
|
3019
|
+
return this.__store.state.matches.some(
|
|
3020
|
+
(d) => d.status === 'notFound' || d.globalNotFound,
|
|
3021
|
+
)
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
951
3024
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
3025
|
+
export class SearchParamError extends Error {}
|
|
3026
|
+
|
|
3027
|
+
export class PathParamError extends Error {}
|
|
3028
|
+
|
|
3029
|
+
// A function that takes an import() argument which is a function and returns a new function that will
|
|
3030
|
+
// proxy arguments from the caller to the imported function, retaining all type
|
|
3031
|
+
// information along the way
|
|
3032
|
+
export function lazyFn<
|
|
3033
|
+
T extends Record<string, (...args: Array<any>) => any>,
|
|
3034
|
+
TKey extends keyof T = 'default',
|
|
3035
|
+
>(fn: () => Promise<T>, key?: TKey) {
|
|
3036
|
+
return async (
|
|
3037
|
+
...args: Parameters<T[TKey]>
|
|
3038
|
+
): Promise<Awaited<ReturnType<T[TKey]>>> => {
|
|
3039
|
+
const imported = await fn()
|
|
3040
|
+
return imported[key || 'default'](...args)
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
960
3043
|
|
|
961
|
-
|
|
3044
|
+
export function getInitialRouterState(
|
|
3045
|
+
location: ParsedLocation,
|
|
3046
|
+
): RouterState<any> {
|
|
3047
|
+
return {
|
|
3048
|
+
loadedAt: 0,
|
|
3049
|
+
isLoading: false,
|
|
3050
|
+
isTransitioning: false,
|
|
3051
|
+
status: 'idle',
|
|
3052
|
+
resolvedLocation: undefined,
|
|
3053
|
+
location,
|
|
3054
|
+
matches: [],
|
|
3055
|
+
pendingMatches: [],
|
|
3056
|
+
cachedMatches: [],
|
|
3057
|
+
statusCode: 200,
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
962
3060
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
|
|
3061
|
+
function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
3062
|
+
if (validateSearch == null) return {}
|
|
966
3063
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
const currentPathSplit = router.state.location.pathname.split('/')
|
|
970
|
-
const nextPathSplit = next.pathname.split('/')
|
|
971
|
-
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
972
|
-
(d, i) => d === currentPathSplit[i],
|
|
973
|
-
)
|
|
974
|
-
const hashIsEqual = router.state.location.hash === next.hash
|
|
975
|
-
// Combine the matches based on user options
|
|
976
|
-
const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual
|
|
977
|
-
const hashTest = activeOptions?.includeHash ? hashIsEqual : true
|
|
3064
|
+
if ('~standard' in validateSearch) {
|
|
3065
|
+
const result = validateSearch['~standard'].validate(input)
|
|
978
3066
|
|
|
979
|
-
|
|
980
|
-
|
|
3067
|
+
if (result instanceof Promise)
|
|
3068
|
+
throw new SearchParamError('Async validation not supported')
|
|
981
3069
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
!isCtrlEvent(e) &&
|
|
987
|
-
!e.defaultPrevented &&
|
|
988
|
-
(!target || target === '_self') &&
|
|
989
|
-
e.button === 0
|
|
990
|
-
) {
|
|
991
|
-
e.preventDefault()
|
|
992
|
-
if (pathIsEqual && !search && !hash) {
|
|
993
|
-
router.invalidateRoute(nextOpts)
|
|
994
|
-
}
|
|
3070
|
+
if (result.issues)
|
|
3071
|
+
throw new SearchParamError(JSON.stringify(result.issues, undefined, 2), {
|
|
3072
|
+
cause: result,
|
|
3073
|
+
})
|
|
995
3074
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
}
|
|
999
|
-
}
|
|
3075
|
+
return result.value
|
|
3076
|
+
}
|
|
1000
3077
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
router.preloadRoute(nextOpts, {
|
|
1005
|
-
maxAge: userPreloadMaxAge,
|
|
1006
|
-
gcMaxAge: userPreloadGcMaxAge,
|
|
1007
|
-
})
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
3078
|
+
if ('parse' in validateSearch) {
|
|
3079
|
+
return validateSearch.parse(input)
|
|
3080
|
+
}
|
|
1010
3081
|
|
|
1011
|
-
|
|
1012
|
-
|
|
3082
|
+
if (typeof validateSearch === 'function') {
|
|
3083
|
+
return validateSearch(input)
|
|
3084
|
+
}
|
|
1013
3085
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
return
|
|
1017
|
-
}
|
|
3086
|
+
return {}
|
|
3087
|
+
}
|
|
1018
3088
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
3089
|
+
export const componentTypes = [
|
|
3090
|
+
'component',
|
|
3091
|
+
'errorComponent',
|
|
3092
|
+
'pendingComponent',
|
|
3093
|
+
'notFoundComponent',
|
|
3094
|
+
] as const
|
|
3095
|
+
|
|
3096
|
+
function routeNeedsPreload(route: AnyRoute) {
|
|
3097
|
+
for (const componentType of componentTypes) {
|
|
3098
|
+
if ((route.options[componentType] as any)?.preload) {
|
|
3099
|
+
return true
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
return false
|
|
3103
|
+
}
|
|
1028
3104
|
|
|
1029
|
-
|
|
1030
|
-
|
|
3105
|
+
interface RouteLike {
|
|
3106
|
+
id: string
|
|
3107
|
+
isRoot?: boolean
|
|
3108
|
+
path?: string
|
|
3109
|
+
fullPath: string
|
|
3110
|
+
rank?: number
|
|
3111
|
+
parentRoute?: RouteLike
|
|
3112
|
+
children?: Array<RouteLike>
|
|
3113
|
+
options?: {
|
|
3114
|
+
caseSensitive?: boolean
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
1031
3117
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
3118
|
+
export function processRouteTree<TRouteLike extends RouteLike>({
|
|
3119
|
+
routeTree,
|
|
3120
|
+
initRoute,
|
|
3121
|
+
}: {
|
|
3122
|
+
routeTree: TRouteLike
|
|
3123
|
+
initRoute?: (route: TRouteLike, index: number) => void
|
|
3124
|
+
}) {
|
|
3125
|
+
const routesById = {} as Record<string, TRouteLike>
|
|
3126
|
+
const routesByPath = {} as Record<string, TRouteLike>
|
|
1037
3127
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
handleFocus,
|
|
1042
|
-
handleClick,
|
|
1043
|
-
handleEnter,
|
|
1044
|
-
handleLeave,
|
|
1045
|
-
isActive,
|
|
1046
|
-
disabled,
|
|
1047
|
-
}
|
|
1048
|
-
},
|
|
1049
|
-
buildNext: (opts: BuildNextOptions) => {
|
|
1050
|
-
const next = router.__.buildLocation(opts)
|
|
1051
|
-
|
|
1052
|
-
const matches = router.matchRoutes(next.pathname)
|
|
1053
|
-
|
|
1054
|
-
const __preSearchFilters = matches
|
|
1055
|
-
.map((match) => match.options.preSearchFilters ?? [])
|
|
1056
|
-
.flat()
|
|
1057
|
-
.filter(Boolean)
|
|
1058
|
-
|
|
1059
|
-
const __postSearchFilters = matches
|
|
1060
|
-
.map((match) => match.options.postSearchFilters ?? [])
|
|
1061
|
-
.flat()
|
|
1062
|
-
.filter(Boolean)
|
|
1063
|
-
|
|
1064
|
-
return router.__.buildLocation({
|
|
1065
|
-
...opts,
|
|
1066
|
-
__preSearchFilters,
|
|
1067
|
-
__postSearchFilters,
|
|
1068
|
-
})
|
|
1069
|
-
},
|
|
1070
|
-
|
|
1071
|
-
__: {
|
|
1072
|
-
buildRouteTree: (rootRouteConfig: RouteConfig) => {
|
|
1073
|
-
const recurseRoutes = (
|
|
1074
|
-
routeConfigs: RouteConfig[],
|
|
1075
|
-
parent?: Route<TAllRouteInfo, any>,
|
|
1076
|
-
): Route<TAllRouteInfo, any>[] => {
|
|
1077
|
-
return routeConfigs.map((routeConfig) => {
|
|
1078
|
-
const routeOptions = routeConfig.options
|
|
1079
|
-
const route = createRoute(routeConfig, routeOptions, parent, router)
|
|
1080
|
-
const existingRoute = (router.routesById as any)[route.routeId]
|
|
1081
|
-
|
|
1082
|
-
if (existingRoute) {
|
|
1083
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1084
|
-
console.warn(
|
|
1085
|
-
`Duplicate routes found with id: ${String(route.routeId)}`,
|
|
1086
|
-
router.routesById,
|
|
1087
|
-
route,
|
|
1088
|
-
)
|
|
1089
|
-
}
|
|
1090
|
-
throw new Error()
|
|
1091
|
-
}
|
|
3128
|
+
const recurseRoutes = (childRoutes: Array<TRouteLike>) => {
|
|
3129
|
+
childRoutes.forEach((childRoute, i) => {
|
|
3130
|
+
initRoute?.(childRoute, i)
|
|
1092
3131
|
|
|
1093
|
-
|
|
3132
|
+
const existingRoute = routesById[childRoute.id]
|
|
1094
3133
|
|
|
1095
|
-
|
|
3134
|
+
invariant(
|
|
3135
|
+
!existingRoute,
|
|
3136
|
+
`Duplicate routes found with id: ${String(childRoute.id)}`,
|
|
3137
|
+
)
|
|
1096
3138
|
|
|
1097
|
-
|
|
1098
|
-
? recurseRoutes(children, route)
|
|
1099
|
-
: undefined
|
|
3139
|
+
routesById[childRoute.id] = childRoute
|
|
1100
3140
|
|
|
1101
|
-
|
|
1102
|
-
|
|
3141
|
+
if (!childRoute.isRoot && childRoute.path) {
|
|
3142
|
+
const trimmedFullPath = trimPathRight(childRoute.fullPath)
|
|
3143
|
+
if (
|
|
3144
|
+
!routesByPath[trimmedFullPath] ||
|
|
3145
|
+
childRoute.fullPath.endsWith('/')
|
|
3146
|
+
) {
|
|
3147
|
+
routesByPath[trimmedFullPath] = childRoute
|
|
1103
3148
|
}
|
|
3149
|
+
}
|
|
1104
3150
|
|
|
1105
|
-
|
|
3151
|
+
const children = childRoute.children as Array<TRouteLike>
|
|
1106
3152
|
|
|
1107
|
-
|
|
1108
|
-
|
|
3153
|
+
if (children?.length) {
|
|
3154
|
+
recurseRoutes(children)
|
|
3155
|
+
}
|
|
3156
|
+
})
|
|
3157
|
+
}
|
|
1109
3158
|
|
|
1110
|
-
|
|
1111
|
-
location: History['location'],
|
|
1112
|
-
previousLocation?: Location,
|
|
1113
|
-
): Location => {
|
|
1114
|
-
const parsedSearch = router.options.parseSearch(location.search)
|
|
3159
|
+
recurseRoutes([routeTree])
|
|
1115
3160
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
key: location.key,
|
|
1124
|
-
}
|
|
1125
|
-
},
|
|
3161
|
+
const scoredRoutes: Array<{
|
|
3162
|
+
child: TRouteLike
|
|
3163
|
+
trimmed: string
|
|
3164
|
+
parsed: ReturnType<typeof parsePathname>
|
|
3165
|
+
index: number
|
|
3166
|
+
scores: Array<number>
|
|
3167
|
+
}> = []
|
|
1126
3168
|
|
|
1127
|
-
|
|
1128
|
-
const next = router.buildNext(location)
|
|
1129
|
-
return router.__.commitLocation(next, location.replace)
|
|
1130
|
-
},
|
|
3169
|
+
const routes: Array<TRouteLike> = Object.values(routesById)
|
|
1131
3170
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
? router.location.pathname
|
|
1137
|
-
: dest.from ?? router.location.pathname
|
|
1138
|
-
|
|
1139
|
-
let pathname = resolvePath(
|
|
1140
|
-
router.basepath ?? '/',
|
|
1141
|
-
fromPathname,
|
|
1142
|
-
`${dest.to ?? '.'}`,
|
|
1143
|
-
)
|
|
3171
|
+
routes.forEach((d, i) => {
|
|
3172
|
+
if (d.isRoot || !d.path) {
|
|
3173
|
+
return
|
|
3174
|
+
}
|
|
1144
3175
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
})
|
|
3176
|
+
const trimmed = trimPathLeft(d.fullPath)
|
|
3177
|
+
const parsed = parsePathname(trimmed)
|
|
1148
3178
|
|
|
1149
|
-
|
|
3179
|
+
// Removes the leading slash if it is not the only remaining segment
|
|
3180
|
+
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
3181
|
+
parsed.shift()
|
|
3182
|
+
}
|
|
1150
3183
|
|
|
1151
|
-
|
|
3184
|
+
const scores = parsed.map((segment) => {
|
|
3185
|
+
if (segment.value === '/') {
|
|
3186
|
+
return 0.75
|
|
3187
|
+
}
|
|
1152
3188
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
3189
|
+
if (
|
|
3190
|
+
segment.type === 'param' &&
|
|
3191
|
+
segment.prefixSegment &&
|
|
3192
|
+
segment.suffixSegment
|
|
3193
|
+
) {
|
|
3194
|
+
return 0.55
|
|
3195
|
+
}
|
|
1157
3196
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
.filter(Boolean)
|
|
1162
|
-
.forEach((fn) => {
|
|
1163
|
-
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
1164
|
-
})
|
|
1165
|
-
}
|
|
3197
|
+
if (segment.type === 'param' && segment.prefixSegment) {
|
|
3198
|
+
return 0.52
|
|
3199
|
+
}
|
|
1166
3200
|
|
|
1167
|
-
|
|
3201
|
+
if (segment.type === 'param' && segment.suffixSegment) {
|
|
3202
|
+
return 0.51
|
|
3203
|
+
}
|
|
1168
3204
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
(prev, next) => next(prev),
|
|
1173
|
-
router.location.search,
|
|
1174
|
-
)
|
|
1175
|
-
: router.location.search
|
|
1176
|
-
|
|
1177
|
-
// Then the link/navigate function
|
|
1178
|
-
const destSearch =
|
|
1179
|
-
dest.search === true
|
|
1180
|
-
? preFilteredSearch // Preserve resolvedFrom true
|
|
1181
|
-
: dest.search
|
|
1182
|
-
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1183
|
-
: dest.__preSearchFilters?.length
|
|
1184
|
-
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1185
|
-
: {}
|
|
3205
|
+
if (segment.type === 'param') {
|
|
3206
|
+
return 0.5
|
|
3207
|
+
}
|
|
1186
3208
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
3209
|
+
if (
|
|
3210
|
+
segment.type === 'wildcard' &&
|
|
3211
|
+
segment.prefixSegment &&
|
|
3212
|
+
segment.suffixSegment
|
|
3213
|
+
) {
|
|
3214
|
+
return 0.3
|
|
3215
|
+
}
|
|
1194
3216
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
)
|
|
3217
|
+
if (segment.type === 'wildcard' && segment.prefixSegment) {
|
|
3218
|
+
return 0.27
|
|
3219
|
+
}
|
|
1199
3220
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
? router.location.hash
|
|
1204
|
-
: functionalUpdate(dest.hash!, router.location.hash)
|
|
1205
|
-
hash = hash ? `#${hash}` : ''
|
|
3221
|
+
if (segment.type === 'wildcard' && segment.suffixSegment) {
|
|
3222
|
+
return 0.26
|
|
3223
|
+
}
|
|
1206
3224
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
searchStr,
|
|
1211
|
-
state: router.location.state,
|
|
1212
|
-
hash,
|
|
1213
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
1214
|
-
key: dest.key,
|
|
1215
|
-
}
|
|
1216
|
-
},
|
|
3225
|
+
if (segment.type === 'wildcard') {
|
|
3226
|
+
return 0.25
|
|
3227
|
+
}
|
|
1217
3228
|
|
|
1218
|
-
|
|
1219
|
-
|
|
3229
|
+
return 1
|
|
3230
|
+
})
|
|
1220
3231
|
|
|
1221
|
-
|
|
3232
|
+
scoredRoutes.push({ child: d, trimmed, parsed, index: i, scores })
|
|
3233
|
+
})
|
|
1222
3234
|
|
|
1223
|
-
|
|
3235
|
+
const flatRoutes = scoredRoutes
|
|
3236
|
+
.sort((a, b) => {
|
|
3237
|
+
const minLength = Math.min(a.scores.length, b.scores.length)
|
|
1224
3238
|
|
|
1225
|
-
|
|
1226
|
-
|
|
3239
|
+
// Sort by min available score
|
|
3240
|
+
for (let i = 0; i < minLength; i++) {
|
|
3241
|
+
if (a.scores[i] !== b.scores[i]) {
|
|
3242
|
+
return b.scores[i]! - a.scores[i]!
|
|
1227
3243
|
}
|
|
3244
|
+
}
|
|
1228
3245
|
|
|
1229
|
-
|
|
1230
|
-
|
|
3246
|
+
// Sort by length of score
|
|
3247
|
+
if (a.scores.length !== b.scores.length) {
|
|
3248
|
+
return b.scores.length - a.scores.length
|
|
3249
|
+
}
|
|
1231
3250
|
|
|
1232
|
-
|
|
1233
|
-
|
|
3251
|
+
// Sort by min available parsed value
|
|
3252
|
+
for (let i = 0; i < minLength; i++) {
|
|
3253
|
+
if (a.parsed[i]!.value !== b.parsed[i]!.value) {
|
|
3254
|
+
return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1
|
|
1234
3255
|
}
|
|
3256
|
+
}
|
|
1235
3257
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
{
|
|
1244
|
-
id,
|
|
1245
|
-
},
|
|
1246
|
-
)
|
|
1247
|
-
} else {
|
|
1248
|
-
history.push(
|
|
1249
|
-
{
|
|
1250
|
-
pathname: next.pathname,
|
|
1251
|
-
hash: next.hash,
|
|
1252
|
-
search: next.searchStr,
|
|
1253
|
-
},
|
|
1254
|
-
{
|
|
1255
|
-
id,
|
|
1256
|
-
},
|
|
1257
|
-
)
|
|
1258
|
-
}
|
|
3258
|
+
// Sort by original index
|
|
3259
|
+
return a.index - b.index
|
|
3260
|
+
})
|
|
3261
|
+
.map((d, i) => {
|
|
3262
|
+
d.child.rank = i
|
|
3263
|
+
return d.child
|
|
3264
|
+
})
|
|
1259
3265
|
|
|
1260
|
-
|
|
1261
|
-
|
|
3266
|
+
return { routesById, routesByPath, flatRoutes }
|
|
3267
|
+
}
|
|
1262
3268
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
3269
|
+
export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
3270
|
+
pathname,
|
|
3271
|
+
routePathname,
|
|
3272
|
+
basepath,
|
|
3273
|
+
caseSensitive,
|
|
3274
|
+
routesByPath,
|
|
3275
|
+
routesById,
|
|
3276
|
+
flatRoutes,
|
|
3277
|
+
}: {
|
|
3278
|
+
pathname: string
|
|
3279
|
+
routePathname?: string
|
|
3280
|
+
basepath: string
|
|
3281
|
+
caseSensitive?: boolean
|
|
3282
|
+
routesByPath: Record<string, TRouteLike>
|
|
3283
|
+
routesById: Record<string, TRouteLike>
|
|
3284
|
+
flatRoutes: Array<TRouteLike>
|
|
3285
|
+
}) {
|
|
3286
|
+
let routeParams: Record<string, string> = {}
|
|
3287
|
+
const trimmedPath = trimPathRight(pathname)
|
|
3288
|
+
const getMatchedParams = (route: TRouteLike) => {
|
|
3289
|
+
const result = matchPathname(basepath, trimmedPath, {
|
|
3290
|
+
to: route.fullPath,
|
|
3291
|
+
caseSensitive: route.options?.caseSensitive ?? caseSensitive,
|
|
3292
|
+
fuzzy: true,
|
|
3293
|
+
})
|
|
3294
|
+
return result
|
|
3295
|
+
}
|
|
1268
3296
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
3297
|
+
let foundRoute: TRouteLike | undefined =
|
|
3298
|
+
routePathname !== undefined ? routesByPath[routePathname] : undefined
|
|
3299
|
+
if (foundRoute) {
|
|
3300
|
+
routeParams = getMatchedParams(foundRoute)!
|
|
3301
|
+
} else {
|
|
3302
|
+
foundRoute = flatRoutes.find((route) => {
|
|
3303
|
+
const matchedParams = getMatchedParams(route)
|
|
3304
|
+
|
|
3305
|
+
if (matchedParams) {
|
|
3306
|
+
routeParams = matchedParams
|
|
3307
|
+
return true
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
return false
|
|
3311
|
+
})
|
|
1272
3312
|
}
|
|
1273
3313
|
|
|
1274
|
-
|
|
3314
|
+
let routeCursor: TRouteLike = foundRoute || routesById[rootRouteId]!
|
|
1275
3315
|
|
|
1276
|
-
|
|
1277
|
-
router.options.createRouter?.(router)
|
|
3316
|
+
const matchedRoutes: Array<TRouteLike> = [routeCursor]
|
|
1278
3317
|
|
|
1279
|
-
|
|
1280
|
-
|
|
3318
|
+
while (routeCursor.parentRoute) {
|
|
3319
|
+
routeCursor = routeCursor.parentRoute as TRouteLike
|
|
3320
|
+
matchedRoutes.unshift(routeCursor)
|
|
3321
|
+
}
|
|
1281
3322
|
|
|
1282
|
-
|
|
1283
|
-
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
|
|
3323
|
+
return { matchedRoutes, routeParams, foundRoute }
|
|
1284
3324
|
}
|