@tanstack/router-core 0.0.1-beta.16 → 0.0.1-beta.160
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/build/cjs/fileRoute.js +29 -0
- package/build/cjs/fileRoute.js.map +1 -0
- package/build/cjs/history.js +226 -0
- package/build/cjs/history.js.map +1 -0
- package/build/cjs/index.js +78 -0
- package/build/cjs/{packages/router-core/src/index.js.map → index.js.map} +1 -1
- package/build/cjs/{packages/router-core/src/path.js → path.js} +45 -56
- package/build/cjs/path.js.map +1 -0
- package/build/cjs/{packages/router-core/src/qss.js → qss.js} +10 -16
- package/build/cjs/qss.js.map +1 -0
- package/build/cjs/route.js +101 -0
- package/build/cjs/route.js.map +1 -0
- package/build/cjs/router.js +1128 -0
- package/build/cjs/router.js.map +1 -0
- package/build/cjs/{packages/router-core/src/searchParams.js → searchParams.js} +34 -19
- package/build/cjs/searchParams.js.map +1 -0
- package/build/cjs/{packages/router-core/src/utils.js → utils.js} +54 -64
- package/build/cjs/utils.js.map +1 -0
- package/build/esm/index.js +1431 -2089
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +59 -49
- package/build/stats-react.json +203 -234
- package/build/types/index.d.ts +573 -431
- package/build/umd/index.development.js +1667 -2221
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +13 -2
- package/build/umd/index.production.js.map +1 -1
- package/package.json +11 -7
- package/src/fileRoute.ts +162 -0
- package/src/history.ts +292 -0
- package/src/index.ts +3 -10
- package/src/link.ts +121 -118
- package/src/path.ts +37 -17
- package/src/qss.ts +1 -2
- package/src/route.ts +874 -218
- package/src/routeInfo.ts +47 -211
- package/src/router.ts +1503 -1023
- package/src/searchParams.ts +33 -9
- package/src/utils.ts +80 -49
- 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 -58
- package/build/cjs/packages/router-core/src/path.js.map +0 -1
- package/build/cjs/packages/router-core/src/qss.js.map +0 -1
- package/build/cjs/packages/router-core/src/route.js +0 -147
- 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 -231
- package/build/cjs/packages/router-core/src/routeMatch.js.map +0 -1
- package/build/cjs/packages/router-core/src/router.js +0 -833
- package/build/cjs/packages/router-core/src/router.js.map +0 -1
- package/build/cjs/packages/router-core/src/searchParams.js.map +0 -1
- package/build/cjs/packages/router-core/src/utils.js.map +0 -1
- package/src/frameworks.ts +0 -11
- package/src/routeConfig.ts +0 -514
- package/src/routeMatch.ts +0 -319
package/src/router.ts
CHANGED
|
@@ -1,70 +1,92 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BrowserHistory,
|
|
3
|
-
createBrowserHistory,
|
|
4
|
-
createMemoryHistory,
|
|
5
|
-
HashHistory,
|
|
6
|
-
History,
|
|
7
|
-
MemoryHistory,
|
|
8
|
-
} from 'history'
|
|
9
|
-
import React from 'react'
|
|
1
|
+
import { Store } from '@tanstack/react-store'
|
|
10
2
|
import invariant from 'tiny-invariant'
|
|
11
|
-
|
|
3
|
+
|
|
4
|
+
//
|
|
12
5
|
|
|
13
6
|
import {
|
|
14
7
|
LinkInfo,
|
|
15
8
|
LinkOptions,
|
|
16
|
-
|
|
9
|
+
NavigateOptions,
|
|
17
10
|
ToOptions,
|
|
18
|
-
|
|
11
|
+
ResolveRelativePath,
|
|
19
12
|
} from './link'
|
|
20
13
|
import {
|
|
21
14
|
cleanPath,
|
|
22
15
|
interpolatePath,
|
|
23
16
|
joinPaths,
|
|
24
17
|
matchPathname,
|
|
18
|
+
parsePathname,
|
|
25
19
|
resolvePath,
|
|
20
|
+
trimPath,
|
|
21
|
+
trimPathRight,
|
|
26
22
|
} from './path'
|
|
27
|
-
import { AnyRoute, createRoute, Route } from './route'
|
|
28
23
|
import {
|
|
29
|
-
|
|
30
|
-
AnyPathParams,
|
|
31
|
-
AnyRouteConfig,
|
|
24
|
+
Route,
|
|
32
25
|
AnySearchSchema,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
AnyRoute,
|
|
27
|
+
AnyContext,
|
|
28
|
+
AnyPathParams,
|
|
29
|
+
RouteProps,
|
|
30
|
+
RegisteredRouteComponent,
|
|
31
|
+
RegisteredRouteErrorComponent,
|
|
32
|
+
} from './route'
|
|
37
33
|
import {
|
|
38
|
-
AllRouteInfo,
|
|
39
|
-
AnyAllRouteInfo,
|
|
40
|
-
AnyRouteInfo,
|
|
41
|
-
RouteInfo,
|
|
42
34
|
RoutesById,
|
|
35
|
+
RoutesByPath,
|
|
36
|
+
ParseRoute,
|
|
37
|
+
FullSearchSchema,
|
|
38
|
+
RouteById,
|
|
39
|
+
RoutePaths,
|
|
43
40
|
} from './routeInfo'
|
|
44
|
-
import { createRouteMatch, RouteMatch } from './routeMatch'
|
|
45
41
|
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
46
42
|
import {
|
|
47
43
|
functionalUpdate,
|
|
48
44
|
last,
|
|
45
|
+
NoInfer,
|
|
49
46
|
pick,
|
|
50
47
|
PickAsRequired,
|
|
51
|
-
PickRequired,
|
|
52
|
-
replaceEqualDeep,
|
|
53
48
|
Timeout,
|
|
54
49
|
Updater,
|
|
50
|
+
replaceEqualDeep,
|
|
51
|
+
partialDeepEqual,
|
|
55
52
|
} from './utils'
|
|
53
|
+
import {
|
|
54
|
+
createBrowserHistory,
|
|
55
|
+
createMemoryHistory,
|
|
56
|
+
RouterHistory,
|
|
57
|
+
} from './history'
|
|
58
|
+
|
|
59
|
+
//
|
|
60
|
+
|
|
61
|
+
declare global {
|
|
62
|
+
interface Window {
|
|
63
|
+
__TSR_DEHYDRATED__?: HydrationCtx
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface Register {
|
|
68
|
+
// router: Router
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type AnyRouter = Router<any, any>
|
|
72
|
+
|
|
73
|
+
export type RegisteredRouter = Register extends {
|
|
74
|
+
router: infer TRouter extends AnyRouter
|
|
75
|
+
}
|
|
76
|
+
? TRouter
|
|
77
|
+
: AnyRouter
|
|
56
78
|
|
|
57
79
|
export interface LocationState {}
|
|
58
80
|
|
|
59
|
-
export interface
|
|
81
|
+
export interface ParsedLocation<
|
|
60
82
|
TSearchObj extends AnySearchSchema = {},
|
|
61
|
-
TState extends LocationState = LocationState,
|
|
83
|
+
// TState extends LocationState = LocationState,
|
|
62
84
|
> {
|
|
63
85
|
href: string
|
|
64
86
|
pathname: string
|
|
65
87
|
search: TSearchObj
|
|
66
88
|
searchStr: string
|
|
67
|
-
state:
|
|
89
|
+
state: LocationState
|
|
68
90
|
hash: string
|
|
69
91
|
key?: string
|
|
70
92
|
}
|
|
@@ -78,138 +100,115 @@ export interface FromLocation {
|
|
|
78
100
|
|
|
79
101
|
export type SearchSerializer = (searchObj: Record<string, any>) => string
|
|
80
102
|
export type SearchParser = (searchStr: string) => Record<string, any>
|
|
81
|
-
export type FilterRoutesFn = <TRoute extends Route<any, RouteInfo>>(
|
|
82
|
-
routeConfigs: TRoute[],
|
|
83
|
-
) => TRoute[]
|
|
84
103
|
|
|
85
|
-
export
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
parseSearch?: SearchParser
|
|
89
|
-
filterRoutes?: FilterRoutesFn
|
|
90
|
-
defaultPreload?: false | 'intent'
|
|
91
|
-
defaultPreloadMaxAge?: number
|
|
92
|
-
defaultPreloadGcMaxAge?: number
|
|
93
|
-
defaultPreloadDelay?: number
|
|
94
|
-
useErrorBoundary?: boolean
|
|
95
|
-
defaultComponent?: GetFrameworkGeneric<'Component'>
|
|
96
|
-
defaultErrorComponent?: GetFrameworkGeneric<'Component'>
|
|
97
|
-
defaultPendingComponent?: GetFrameworkGeneric<'Component'>
|
|
98
|
-
defaultLoaderMaxAge?: number
|
|
99
|
-
defaultLoaderGcMaxAge?: number
|
|
100
|
-
caseSensitive?: boolean
|
|
101
|
-
routeConfig?: TRouteConfig
|
|
102
|
-
basepath?: string
|
|
103
|
-
createRouter?: (router: Router<any, any>) => void
|
|
104
|
-
createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
|
|
105
|
-
loadComponent?: (
|
|
106
|
-
component: GetFrameworkGeneric<'Component'>,
|
|
107
|
-
) => Promise<GetFrameworkGeneric<'Component'>>
|
|
108
|
-
// renderComponent?: (
|
|
109
|
-
// component: GetFrameworkGeneric<'Component'>,
|
|
110
|
-
// ) => GetFrameworkGeneric<'Element'>
|
|
104
|
+
export type HydrationCtx = {
|
|
105
|
+
router: DehydratedRouter
|
|
106
|
+
payload: Record<string, any>
|
|
111
107
|
}
|
|
112
108
|
|
|
113
|
-
export interface
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// TError = unknown,
|
|
109
|
+
export interface RouteMatch<
|
|
110
|
+
TRouteTree extends AnyRoute = AnyRoute,
|
|
111
|
+
TRoute extends AnyRoute = AnyRoute,
|
|
117
112
|
> {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
id: string
|
|
114
|
+
key?: string
|
|
115
|
+
routeId: string
|
|
116
|
+
pathname: string
|
|
117
|
+
params: TRoute['__types']['allParams']
|
|
118
|
+
status: 'pending' | 'success' | 'error'
|
|
119
|
+
isFetching: boolean
|
|
120
|
+
invalid: boolean
|
|
121
|
+
error: unknown
|
|
122
|
+
paramsError: unknown
|
|
123
|
+
searchError: unknown
|
|
124
|
+
updatedAt: number
|
|
125
|
+
invalidAt: number
|
|
126
|
+
preloadInvalidAt: number
|
|
127
|
+
loaderData: TRoute['__types']['loader']
|
|
128
|
+
loadPromise?: Promise<void>
|
|
129
|
+
__resolveLoadPromise?: () => void
|
|
130
|
+
routeContext: TRoute['__types']['routeContext']
|
|
131
|
+
context: TRoute['__types']['context']
|
|
132
|
+
routeSearch: TRoute['__types']['searchSchema']
|
|
133
|
+
search: FullSearchSchema<TRouteTree> & TRoute['__types']['fullSearchSchema']
|
|
134
|
+
fetchedAt: number
|
|
135
|
+
abortController: AbortController
|
|
125
136
|
}
|
|
126
137
|
|
|
127
|
-
export
|
|
128
|
-
TPayload = unknown,
|
|
129
|
-
TResponse = unknown,
|
|
130
|
-
// TError = unknown,
|
|
131
|
-
> {
|
|
132
|
-
submittedAt: number
|
|
133
|
-
status: 'idle' | 'pending' | 'success' | 'error'
|
|
134
|
-
submission: TPayload
|
|
135
|
-
isMulti: boolean
|
|
136
|
-
data?: TResponse
|
|
137
|
-
error?: unknown
|
|
138
|
-
}
|
|
138
|
+
export type AnyRouteMatch = RouteMatch<AnyRoute, AnyRoute>
|
|
139
139
|
|
|
140
|
-
export
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
: (loaderContext: {
|
|
149
|
-
params: TAllParams
|
|
150
|
-
search?: TFullSearchSchema
|
|
151
|
-
signal?: AbortSignal
|
|
152
|
-
}) => Promise<TRouteLoaderData>
|
|
153
|
-
: keyof TAllParams extends never
|
|
154
|
-
? (loaderContext: {
|
|
155
|
-
search: TFullSearchSchema
|
|
156
|
-
params: TAllParams
|
|
157
|
-
signal?: AbortSignal
|
|
158
|
-
}) => Promise<TRouteLoaderData>
|
|
159
|
-
: (loaderContext: {
|
|
160
|
-
search: TFullSearchSchema
|
|
161
|
-
signal?: AbortSignal
|
|
162
|
-
}) => Promise<TRouteLoaderData>
|
|
163
|
-
current?: LoaderState<TFullSearchSchema, TAllParams>
|
|
164
|
-
latest?: LoaderState<TFullSearchSchema, TAllParams>
|
|
165
|
-
pending: LoaderState<TFullSearchSchema, TAllParams>[]
|
|
166
|
-
}
|
|
140
|
+
export type RouterContextOptions<TRouteTree extends AnyRoute> =
|
|
141
|
+
AnyContext extends TRouteTree['__types']['routerContext']
|
|
142
|
+
? {
|
|
143
|
+
context?: TRouteTree['__types']['routerContext']
|
|
144
|
+
}
|
|
145
|
+
: {
|
|
146
|
+
context: TRouteTree['__types']['routerContext']
|
|
147
|
+
}
|
|
167
148
|
|
|
168
|
-
export interface
|
|
169
|
-
|
|
170
|
-
|
|
149
|
+
export interface RouterOptions<
|
|
150
|
+
TRouteTree extends AnyRoute,
|
|
151
|
+
TDehydrated extends Record<string, any>,
|
|
171
152
|
> {
|
|
172
|
-
|
|
173
|
-
|
|
153
|
+
history?: RouterHistory
|
|
154
|
+
stringifySearch?: SearchSerializer
|
|
155
|
+
parseSearch?: SearchParser
|
|
156
|
+
defaultPreload?: false | 'intent'
|
|
157
|
+
defaultPreloadDelay?: number
|
|
158
|
+
defaultComponent?: RegisteredRouteComponent<
|
|
159
|
+
RouteProps<unknown, AnySearchSchema, AnyPathParams, AnyContext, AnyContext>
|
|
160
|
+
>
|
|
161
|
+
defaultErrorComponent?: RegisteredRouteErrorComponent<
|
|
162
|
+
RouteProps<unknown, AnySearchSchema, AnyPathParams, AnyContext, AnyContext>
|
|
163
|
+
>
|
|
164
|
+
defaultPendingComponent?: RegisteredRouteComponent<
|
|
165
|
+
RouteProps<unknown, AnySearchSchema, AnyPathParams, AnyContext, AnyContext>
|
|
166
|
+
>
|
|
167
|
+
defaultMaxAge?: number
|
|
168
|
+
defaultGcMaxAge?: number
|
|
169
|
+
defaultPreloadMaxAge?: number
|
|
170
|
+
caseSensitive?: boolean
|
|
171
|
+
routeTree?: TRouteTree
|
|
172
|
+
basepath?: string
|
|
173
|
+
createRoute?: (opts: { route: AnyRoute; router: AnyRouter }) => void
|
|
174
|
+
onRouteChange?: () => void
|
|
175
|
+
context?: TRouteTree['__types']['routerContext']
|
|
176
|
+
Wrap?: React.ComponentType<{
|
|
177
|
+
children: React.ReactNode
|
|
178
|
+
dehydratedState?: TDehydrated
|
|
179
|
+
}>
|
|
180
|
+
dehydrate?: () => TDehydrated
|
|
181
|
+
hydrate?: (dehydrated: TDehydrated) => void
|
|
174
182
|
}
|
|
175
183
|
|
|
176
|
-
export interface RouterState
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
actions: Record<string, Action>
|
|
182
|
-
loaders: Record<string, Loader>
|
|
183
|
-
pending?: PendingState
|
|
184
|
+
export interface RouterState<
|
|
185
|
+
TRouteTree extends AnyRoute = AnyRoute,
|
|
186
|
+
// TState extends LocationState = LocationState,
|
|
187
|
+
> {
|
|
188
|
+
status: 'idle' | 'pending'
|
|
184
189
|
isFetching: boolean
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
matchesById: Record<string, RouteMatch<TRouteTree, AnyRoute>>
|
|
191
|
+
matchIds: string[]
|
|
192
|
+
pendingMatchIds: string[]
|
|
193
|
+
matches: RouteMatch<TRouteTree, AnyRoute>[]
|
|
194
|
+
pendingMatches: RouteMatch<TRouteTree, AnyRoute>[]
|
|
195
|
+
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
196
|
+
resolvedLocation: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
197
|
+
lastUpdated: number
|
|
191
198
|
}
|
|
192
199
|
|
|
193
|
-
type Listener = (router: Router<any, any>) => void
|
|
194
|
-
|
|
195
200
|
export type ListenerFn = () => void
|
|
196
201
|
|
|
197
202
|
export interface BuildNextOptions {
|
|
198
203
|
to?: string | number | null
|
|
199
|
-
params?: true | Updater<
|
|
204
|
+
params?: true | Updater<unknown>
|
|
200
205
|
search?: true | Updater<unknown>
|
|
201
206
|
hash?: true | Updater<string>
|
|
202
207
|
state?: LocationState
|
|
203
208
|
key?: string
|
|
204
209
|
from?: string
|
|
205
210
|
fromCurrent?: boolean
|
|
206
|
-
|
|
207
|
-
__postSearchFilters?: SearchFilter<any>[]
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export type MatchCacheEntry = {
|
|
211
|
-
gc: number
|
|
212
|
-
match: RouteMatch
|
|
211
|
+
__matches?: AnyRouteMatch[]
|
|
213
212
|
}
|
|
214
213
|
|
|
215
214
|
export interface MatchLocation {
|
|
@@ -221,1095 +220,1576 @@ export interface MatchLocation {
|
|
|
221
220
|
}
|
|
222
221
|
|
|
223
222
|
export interface MatchRouteOptions {
|
|
224
|
-
pending
|
|
223
|
+
pending?: boolean
|
|
225
224
|
caseSensitive?: boolean
|
|
225
|
+
includeSearch?: boolean
|
|
226
|
+
fuzzy?: boolean
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
type LinkCurrentTargetElement = {
|
|
229
230
|
preloadTimeout?: null | ReturnType<typeof setTimeout>
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
interface DehydratedRouterState
|
|
233
|
-
extends Pick<RouterState, 'status' | 'location' | 'lastUpdated'> {
|
|
234
|
-
|
|
233
|
+
export interface DehydratedRouterState
|
|
234
|
+
extends Pick<RouterState, 'status' | 'location' | 'lastUpdated'> {}
|
|
235
|
+
|
|
236
|
+
export interface DehydratedRouter {
|
|
237
|
+
state: DehydratedRouterState
|
|
235
238
|
}
|
|
236
239
|
|
|
237
|
-
|
|
238
|
-
extends
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
240
|
+
export type RouterConstructorOptions<
|
|
241
|
+
TRouteTree extends AnyRoute,
|
|
242
|
+
TDehydrated extends Record<string, any>,
|
|
243
|
+
> = Omit<RouterOptions<TRouteTree, TDehydrated>, 'context'> &
|
|
244
|
+
RouterContextOptions<TRouteTree>
|
|
245
|
+
|
|
246
|
+
export const componentTypes = [
|
|
247
|
+
'component',
|
|
248
|
+
'errorComponent',
|
|
249
|
+
'pendingComponent',
|
|
250
|
+
] as const
|
|
251
|
+
|
|
252
|
+
export class Router<
|
|
253
|
+
TRouteTree extends AnyRoute = AnyRoute,
|
|
254
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
251
255
|
> {
|
|
252
|
-
|
|
256
|
+
types!: {
|
|
257
|
+
RootRoute: TRouteTree
|
|
258
|
+
}
|
|
259
|
+
|
|
253
260
|
options: PickAsRequired<
|
|
254
|
-
RouterOptions<
|
|
255
|
-
'stringifySearch' | 'parseSearch'
|
|
261
|
+
RouterOptions<TRouteTree, TDehydrated>,
|
|
262
|
+
'stringifySearch' | 'parseSearch' | 'context'
|
|
256
263
|
>
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
cancelMatches: () => void
|
|
282
|
-
load: (next?: Location) => Promise<void>
|
|
283
|
-
matchCache: Record<string, MatchCacheEntry>
|
|
284
|
-
cleanMatchCache: () => void
|
|
285
|
-
getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
286
|
-
id: TId,
|
|
287
|
-
) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
288
|
-
loadRoute: (navigateOpts: BuildNextOptions) => Promise<RouteMatch[]>
|
|
289
|
-
preloadRoute: (
|
|
290
|
-
navigateOpts: BuildNextOptions,
|
|
291
|
-
loaderOpts: { maxAge?: number; gcMaxAge?: number },
|
|
292
|
-
) => Promise<RouteMatch[]>
|
|
293
|
-
matchRoutes: (
|
|
294
|
-
pathname: string,
|
|
295
|
-
opts?: { strictParseParams?: boolean },
|
|
296
|
-
) => RouteMatch[]
|
|
297
|
-
loadMatches: (
|
|
298
|
-
resolvedMatches: RouteMatch[],
|
|
299
|
-
loaderOpts?:
|
|
300
|
-
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
301
|
-
| { preload?: false; maxAge?: never; gcMaxAge?: never },
|
|
302
|
-
) => Promise<void>
|
|
303
|
-
invalidateRoute: (opts: MatchLocation) => void
|
|
304
|
-
reload: () => Promise<void>
|
|
305
|
-
resolvePath: (from: string, path: string) => string
|
|
306
|
-
navigate: <
|
|
307
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
308
|
-
TTo extends string = '.',
|
|
309
|
-
>(
|
|
310
|
-
opts: NavigateOptionsAbsolute<TAllRouteInfo, TFrom, TTo>,
|
|
311
|
-
) => Promise<void>
|
|
312
|
-
matchRoute: <
|
|
313
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
314
|
-
TTo extends string = '.',
|
|
315
|
-
>(
|
|
316
|
-
matchLocation: ToOptions<TAllRouteInfo, TFrom, TTo>,
|
|
317
|
-
opts?: MatchRouteOptions,
|
|
318
|
-
) => boolean
|
|
319
|
-
buildLink: <
|
|
320
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
321
|
-
TTo extends string = '.',
|
|
322
|
-
>(
|
|
323
|
-
opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
|
|
324
|
-
) => LinkInfo
|
|
325
|
-
dehydrateState: () => DehydratedRouterState
|
|
326
|
-
hydrateState: (state: DehydratedRouterState) => void
|
|
327
|
-
__: {
|
|
328
|
-
buildRouteTree: (
|
|
329
|
-
routeConfig: RouteConfig,
|
|
330
|
-
) => Route<TAllRouteInfo, AnyRouteInfo>
|
|
331
|
-
parseLocation: (
|
|
332
|
-
location: History['location'],
|
|
333
|
-
previousLocation?: Location,
|
|
334
|
-
) => Location
|
|
335
|
-
buildLocation: (dest: BuildNextOptions) => Location
|
|
336
|
-
commitLocation: (next: Location, replace?: boolean) => Promise<void>
|
|
337
|
-
navigate: (
|
|
338
|
-
location: BuildNextOptions & { replace?: boolean },
|
|
339
|
-
) => Promise<void>
|
|
340
|
-
}
|
|
341
|
-
}
|
|
264
|
+
history!: RouterHistory
|
|
265
|
+
#unsubHistory?: () => void
|
|
266
|
+
basepath!: string
|
|
267
|
+
routeTree!: TRouteTree
|
|
268
|
+
routesById!: RoutesById<TRouteTree>
|
|
269
|
+
routesByPath!: RoutesByPath<TRouteTree>
|
|
270
|
+
flatRoutes!: ParseRoute<TRouteTree>[]
|
|
271
|
+
navigateTimeout: undefined | Timeout
|
|
272
|
+
nextAction: undefined | 'push' | 'replace'
|
|
273
|
+
navigationPromise: undefined | Promise<void>
|
|
274
|
+
|
|
275
|
+
__store: Store<RouterState<TRouteTree>>
|
|
276
|
+
state: RouterState<TRouteTree>
|
|
277
|
+
dehydratedData?: TDehydrated
|
|
278
|
+
|
|
279
|
+
constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated>) {
|
|
280
|
+
this.options = {
|
|
281
|
+
defaultPreloadDelay: 50,
|
|
282
|
+
context: undefined!,
|
|
283
|
+
...options,
|
|
284
|
+
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
285
|
+
parseSearch: options?.parseSearch ?? defaultParseSearch,
|
|
286
|
+
// fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
|
|
287
|
+
}
|
|
342
288
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
289
|
+
this.__store = new Store<RouterState<TRouteTree>>(getInitialRouterState(), {
|
|
290
|
+
onUpdate: () => {
|
|
291
|
+
const prev = this.state
|
|
346
292
|
|
|
347
|
-
|
|
348
|
-
const createDefaultHistory = () =>
|
|
349
|
-
isServer ? createMemoryHistory() : createBrowserHistory()
|
|
293
|
+
const next = this.__store.state
|
|
350
294
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
location: null!,
|
|
355
|
-
matches: [],
|
|
356
|
-
actions: {},
|
|
357
|
-
loaders: {},
|
|
358
|
-
lastUpdated: Date.now(),
|
|
359
|
-
isFetching: false,
|
|
360
|
-
isPreloading: false,
|
|
361
|
-
}
|
|
362
|
-
}
|
|
295
|
+
console.log(
|
|
296
|
+
Object.values(next.matchesById).find((d) => d.status === 'error'),
|
|
297
|
+
)
|
|
363
298
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
>(
|
|
368
|
-
userOptions?: RouterOptions<TRouteConfig>,
|
|
369
|
-
): Router<TRouteConfig, TAllRouteInfo> {
|
|
370
|
-
const history = userOptions?.history || createDefaultHistory()
|
|
371
|
-
|
|
372
|
-
const originalOptions = {
|
|
373
|
-
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
374
|
-
defaultLoaderMaxAge: 0,
|
|
375
|
-
defaultPreloadMaxAge: 2000,
|
|
376
|
-
defaultPreloadDelay: 50,
|
|
377
|
-
...userOptions,
|
|
378
|
-
stringifySearch: userOptions?.stringifySearch ?? defaultStringifySearch,
|
|
379
|
-
parseSearch: userOptions?.parseSearch ?? defaultParseSearch,
|
|
380
|
-
}
|
|
299
|
+
const matchesByIdChanged = prev.matchesById !== next.matchesById
|
|
300
|
+
let matchesChanged
|
|
301
|
+
let pendingMatchesChanged
|
|
381
302
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
// Resolved after construction
|
|
387
|
-
basepath: '',
|
|
388
|
-
routeTree: undefined!,
|
|
389
|
-
routesById: {} as any,
|
|
390
|
-
location: undefined!,
|
|
391
|
-
allRouteInfo: undefined!,
|
|
392
|
-
//
|
|
393
|
-
navigationPromise: Promise.resolve(),
|
|
394
|
-
resolveNavigation: () => {},
|
|
395
|
-
matchCache: {},
|
|
396
|
-
state: getInitialRouterState(),
|
|
397
|
-
reset: () => {
|
|
398
|
-
router.state = getInitialRouterState()
|
|
399
|
-
router.notify()
|
|
400
|
-
},
|
|
401
|
-
startedLoadingAt: Date.now(),
|
|
402
|
-
subscribe: (listener: Listener): (() => void) => {
|
|
403
|
-
router.listeners.push(listener as Listener)
|
|
404
|
-
return () => {
|
|
405
|
-
router.listeners = router.listeners.filter((x) => x !== listener)
|
|
406
|
-
}
|
|
407
|
-
},
|
|
408
|
-
getRoute: (id) => {
|
|
409
|
-
return router.routesById[id]
|
|
410
|
-
},
|
|
411
|
-
notify: (): void => {
|
|
412
|
-
const isFetching =
|
|
413
|
-
router.state.status === 'loading' ||
|
|
414
|
-
router.state.matches.some((d) => d.isFetching)
|
|
415
|
-
|
|
416
|
-
const isPreloading = Object.values(router.matchCache).some(
|
|
417
|
-
(d) =>
|
|
418
|
-
d.match.isFetching &&
|
|
419
|
-
!router.state.matches.find((dd) => dd.matchId === d.match.matchId),
|
|
420
|
-
)
|
|
303
|
+
if (!matchesByIdChanged) {
|
|
304
|
+
matchesChanged =
|
|
305
|
+
prev.matchIds.length !== next.matchIds.length ||
|
|
306
|
+
prev.matchIds.some((d, i) => d !== next.matchIds[i])
|
|
421
307
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
) {
|
|
426
|
-
router.state = {
|
|
427
|
-
...router.state,
|
|
428
|
-
isFetching,
|
|
429
|
-
isPreloading,
|
|
308
|
+
pendingMatchesChanged =
|
|
309
|
+
prev.pendingMatchIds.length !== next.pendingMatchIds.length ||
|
|
310
|
+
prev.pendingMatchIds.some((d, i) => d !== next.pendingMatchIds[i])
|
|
430
311
|
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
cascadeLoaderData(router.state.matches)
|
|
434
|
-
router.listeners.forEach((listener) => listener(router))
|
|
435
|
-
},
|
|
436
312
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
'matchId',
|
|
443
|
-
'status',
|
|
444
|
-
'routeLoaderData',
|
|
445
|
-
'loaderData',
|
|
446
|
-
'isInvalid',
|
|
447
|
-
'invalidAt',
|
|
448
|
-
]),
|
|
449
|
-
),
|
|
450
|
-
}
|
|
451
|
-
},
|
|
313
|
+
if (matchesByIdChanged || matchesChanged) {
|
|
314
|
+
next.matches = next.matchIds.map((id) => {
|
|
315
|
+
return next.matchesById[id] as any
|
|
316
|
+
})
|
|
317
|
+
}
|
|
452
318
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
319
|
+
if (matchesByIdChanged || pendingMatchesChanged) {
|
|
320
|
+
next.pendingMatches = next.pendingMatchIds.map((id) => {
|
|
321
|
+
return next.matchesById[id] as any
|
|
322
|
+
})
|
|
323
|
+
}
|
|
458
324
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
invariant(
|
|
462
|
-
dehydratedMatch,
|
|
463
|
-
'Oh no! Dehydrated route matches did not match the active state of the router 😬',
|
|
325
|
+
next.isFetching = [...next.matches, ...next.pendingMatches].some(
|
|
326
|
+
(d) => d.isFetching,
|
|
464
327
|
)
|
|
465
|
-
Object.assign(match, dehydratedMatch)
|
|
466
|
-
})
|
|
467
328
|
|
|
468
|
-
|
|
329
|
+
this.state = next
|
|
330
|
+
},
|
|
331
|
+
defaultPriority: 'low',
|
|
332
|
+
})
|
|
469
333
|
|
|
470
|
-
|
|
471
|
-
...router.state,
|
|
472
|
-
...dehydratedState,
|
|
473
|
-
matches,
|
|
474
|
-
}
|
|
475
|
-
},
|
|
334
|
+
this.state = this.__store.state
|
|
476
335
|
|
|
477
|
-
|
|
478
|
-
const next = router.__.buildLocation({
|
|
479
|
-
to: '.',
|
|
480
|
-
search: true,
|
|
481
|
-
hash: true,
|
|
482
|
-
})
|
|
336
|
+
this.update(options)
|
|
483
337
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
338
|
+
const next = this.buildNext({
|
|
339
|
+
hash: true,
|
|
340
|
+
fromCurrent: true,
|
|
341
|
+
search: true,
|
|
342
|
+
state: true,
|
|
343
|
+
})
|
|
489
344
|
|
|
490
|
-
|
|
491
|
-
|
|
345
|
+
if (this.state.location.href !== next.href) {
|
|
346
|
+
this.#commitLocation({ ...next, replace: true })
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
reset = () => {
|
|
351
|
+
this.__store.setState((s) => Object.assign(s, getInitialRouterState()))
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
mount = () => {
|
|
355
|
+
// If the router matches are empty, start loading the matches
|
|
356
|
+
// if (!this.state.matches.length) {
|
|
357
|
+
this.safeLoad()
|
|
358
|
+
// }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
update = (opts?: RouterOptions<any, any>): this => {
|
|
362
|
+
this.options = {
|
|
363
|
+
...this.options,
|
|
364
|
+
...opts,
|
|
365
|
+
context: {
|
|
366
|
+
...this.options.context,
|
|
367
|
+
...opts?.context,
|
|
368
|
+
},
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (
|
|
372
|
+
!this.history ||
|
|
373
|
+
(this.options.history && this.options.history !== this.history)
|
|
374
|
+
) {
|
|
375
|
+
if (this.#unsubHistory) {
|
|
376
|
+
this.#unsubHistory()
|
|
492
377
|
}
|
|
493
378
|
|
|
494
|
-
|
|
495
|
-
|
|
379
|
+
this.history =
|
|
380
|
+
this.options.history ??
|
|
381
|
+
(isServer ? createMemoryHistory() : createBrowserHistory()!)
|
|
382
|
+
|
|
383
|
+
const parsedLocation = this.#parseLocation()
|
|
384
|
+
|
|
385
|
+
this.__store.setState((s) => ({
|
|
386
|
+
...s,
|
|
387
|
+
resolvedLocation: parsedLocation as any,
|
|
388
|
+
location: parsedLocation as any,
|
|
389
|
+
}))
|
|
390
|
+
|
|
391
|
+
this.#unsubHistory = this.history.listen(() => {
|
|
392
|
+
this.safeLoad({
|
|
393
|
+
next: this.#parseLocation(this.state.location),
|
|
394
|
+
})
|
|
496
395
|
})
|
|
396
|
+
}
|
|
497
397
|
|
|
498
|
-
|
|
499
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
500
|
-
if (!isServer && window.addEventListener) {
|
|
501
|
-
// Listen to visibillitychange and focus
|
|
502
|
-
window.addEventListener('visibilitychange', router.onFocus, false)
|
|
503
|
-
window.addEventListener('focus', router.onFocus, false)
|
|
504
|
-
}
|
|
398
|
+
const { basepath, routeTree } = this.options
|
|
505
399
|
|
|
506
|
-
|
|
507
|
-
unsub()
|
|
508
|
-
if (!isServer && window.removeEventListener) {
|
|
509
|
-
// Be sure to unsubscribe if a new handler is set
|
|
510
|
-
window.removeEventListener('visibilitychange', router.onFocus)
|
|
511
|
-
window.removeEventListener('focus', router.onFocus)
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
},
|
|
400
|
+
this.basepath = `/${trimPath(basepath ?? '') ?? ''}`
|
|
515
401
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
402
|
+
if (routeTree && routeTree !== this.routeTree) {
|
|
403
|
+
this.#buildRouteTree(routeTree)
|
|
404
|
+
}
|
|
519
405
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (!router.location || newHistory) {
|
|
523
|
-
if (opts?.history) {
|
|
524
|
-
router.history = opts.history
|
|
525
|
-
}
|
|
526
|
-
router.location = router.__.parseLocation(router.history.location)
|
|
527
|
-
router.state.location = router.location
|
|
528
|
-
}
|
|
406
|
+
return this
|
|
407
|
+
}
|
|
529
408
|
|
|
530
|
-
|
|
409
|
+
buildNext = (opts: BuildNextOptions): ParsedLocation => {
|
|
410
|
+
const next = this.#buildLocation(opts)
|
|
531
411
|
|
|
532
|
-
|
|
412
|
+
const __matches = this.matchRoutes(next.pathname, next.search)
|
|
533
413
|
|
|
534
|
-
|
|
414
|
+
return this.#buildLocation({
|
|
415
|
+
...opts,
|
|
416
|
+
__matches,
|
|
417
|
+
})
|
|
418
|
+
}
|
|
535
419
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
420
|
+
cancelMatches = () => {
|
|
421
|
+
this.state.matches.forEach((match) => {
|
|
422
|
+
this.cancelMatch(match.id)
|
|
423
|
+
})
|
|
424
|
+
}
|
|
540
425
|
|
|
541
|
-
|
|
542
|
-
|
|
426
|
+
cancelMatch = (id: string) => {
|
|
427
|
+
this.getRouteMatch(id)?.abortController?.abort()
|
|
428
|
+
}
|
|
543
429
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
},
|
|
430
|
+
safeLoad = async (opts?: { next?: ParsedLocation }) => {
|
|
431
|
+
try {
|
|
432
|
+
return this.load(opts)
|
|
433
|
+
} catch (err) {
|
|
434
|
+
// Don't do anything
|
|
435
|
+
}
|
|
436
|
+
}
|
|
552
437
|
|
|
553
|
-
|
|
554
|
-
const id = Math.random()
|
|
555
|
-
router.startedLoadingAt = id
|
|
438
|
+
latestLoadPromise: Promise<void> = Promise.resolve()
|
|
556
439
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
440
|
+
load = async (opts?: { next?: ParsedLocation; throwOnError?: boolean }) => {
|
|
441
|
+
const promise = new Promise<void>(async (resolve, reject) => {
|
|
442
|
+
let latestPromise: Promise<void> | undefined | null
|
|
443
|
+
|
|
444
|
+
const checkLatest = (): undefined | Promise<void> | null => {
|
|
445
|
+
return this.latestLoadPromise !== promise
|
|
446
|
+
? this.latestLoadPromise
|
|
447
|
+
: undefined
|
|
560
448
|
}
|
|
561
449
|
|
|
562
450
|
// Cancel any pending matches
|
|
563
|
-
|
|
451
|
+
// this.cancelMatches()
|
|
564
452
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
453
|
+
let pendingMatches!: RouteMatch<any, any>[]
|
|
454
|
+
|
|
455
|
+
this.__store.batch(() => {
|
|
456
|
+
if (opts?.next) {
|
|
457
|
+
// Ingest the new location
|
|
458
|
+
this.__store.setState((s) => ({
|
|
459
|
+
...s,
|
|
460
|
+
location: opts.next! as any,
|
|
461
|
+
}))
|
|
462
|
+
}
|
|
569
463
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
464
|
+
// Match the routes
|
|
465
|
+
pendingMatches = this.matchRoutes(
|
|
466
|
+
this.state.location.pathname,
|
|
467
|
+
this.state.location.search,
|
|
468
|
+
{
|
|
469
|
+
throwOnError: opts?.throwOnError,
|
|
470
|
+
debug: true,
|
|
576
471
|
},
|
|
577
|
-
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
this.__store.setState((s) => ({
|
|
475
|
+
...s,
|
|
476
|
+
status: 'pending',
|
|
477
|
+
pendingMatchIds: pendingMatches.map((d) => d.id),
|
|
478
|
+
matchesById: this.#mergeMatches(s.matchesById, pendingMatches),
|
|
479
|
+
}))
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
// Load the matches
|
|
484
|
+
try {
|
|
485
|
+
await this.loadMatches(pendingMatches)
|
|
486
|
+
} catch (err) {
|
|
487
|
+
// swallow this error, since we'll display the
|
|
488
|
+
// errors on the route components
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Only apply the latest transition
|
|
492
|
+
if ((latestPromise = checkLatest())) {
|
|
493
|
+
return latestPromise
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const prevLocation = this.state.resolvedLocation
|
|
497
|
+
|
|
498
|
+
this.__store.setState((s) => ({
|
|
499
|
+
...s,
|
|
500
|
+
status: 'idle',
|
|
501
|
+
resolvedLocation: s.location,
|
|
502
|
+
matchIds: s.pendingMatchIds,
|
|
503
|
+
pendingMatchIds: [],
|
|
504
|
+
}))
|
|
505
|
+
|
|
506
|
+
if (prevLocation!.href !== this.state.location.href) {
|
|
507
|
+
this.options.onRouteChange?.()
|
|
578
508
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
509
|
+
|
|
510
|
+
resolve()
|
|
511
|
+
} catch (err) {
|
|
512
|
+
// Only apply the latest transition
|
|
513
|
+
if ((latestPromise = checkLatest())) {
|
|
514
|
+
return latestPromise
|
|
585
515
|
}
|
|
516
|
+
|
|
517
|
+
reject(err)
|
|
586
518
|
}
|
|
519
|
+
})
|
|
587
520
|
|
|
588
|
-
|
|
521
|
+
this.latestLoadPromise = promise
|
|
589
522
|
|
|
590
|
-
|
|
591
|
-
|
|
523
|
+
return this.latestLoadPromise
|
|
524
|
+
}
|
|
592
525
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
526
|
+
#mergeMatches = (
|
|
527
|
+
prevMatchesById: Record<
|
|
528
|
+
string,
|
|
529
|
+
RouteMatch<TRouteTree, ParseRoute<TRouteTree>>
|
|
530
|
+
>,
|
|
531
|
+
nextMatches: AnyRouteMatch[],
|
|
532
|
+
): Record<string, RouteMatch<TRouteTree, ParseRoute<TRouteTree>>> => {
|
|
533
|
+
const nextMatchesById: any = {
|
|
534
|
+
...prevMatchesById,
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
let hadNew = false
|
|
538
|
+
|
|
539
|
+
nextMatches.forEach((match) => {
|
|
540
|
+
if (!nextMatchesById[match.id]) {
|
|
541
|
+
hadNew = true
|
|
542
|
+
nextMatchesById[match.id] = match
|
|
596
543
|
}
|
|
544
|
+
})
|
|
597
545
|
|
|
598
|
-
|
|
546
|
+
if (!hadNew) {
|
|
547
|
+
return prevMatchesById
|
|
548
|
+
}
|
|
599
549
|
|
|
600
|
-
|
|
601
|
-
|
|
550
|
+
return nextMatchesById
|
|
551
|
+
}
|
|
602
552
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
staying.push(d)
|
|
606
|
-
} else {
|
|
607
|
-
exiting.push(d)
|
|
608
|
-
}
|
|
609
|
-
})
|
|
553
|
+
getRoute = (id: string): Route => {
|
|
554
|
+
const route = (this.routesById as any)[id]
|
|
610
555
|
|
|
611
|
-
|
|
612
|
-
return !previousMatches.find((dd) => dd.matchId === d.matchId)
|
|
613
|
-
})
|
|
556
|
+
invariant(route, `Route with id "${id as string}" not found`)
|
|
614
557
|
|
|
615
|
-
|
|
558
|
+
return route as any
|
|
559
|
+
}
|
|
616
560
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
561
|
+
preloadRoute = async (
|
|
562
|
+
navigateOpts: BuildNextOptions & {
|
|
563
|
+
maxAge?: number
|
|
564
|
+
} = this.state.location,
|
|
565
|
+
) => {
|
|
566
|
+
const next = this.buildNext(navigateOpts)
|
|
567
|
+
const matches = this.matchRoutes(next.pathname, next.search, {
|
|
568
|
+
throwOnError: true,
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
this.__store.setState((s) => {
|
|
572
|
+
return {
|
|
573
|
+
...s,
|
|
574
|
+
matchesById: this.#mergeMatches(s.matchesById, matches),
|
|
575
|
+
}
|
|
576
|
+
})
|
|
622
577
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
578
|
+
await this.loadMatches(matches, {
|
|
579
|
+
preload: true,
|
|
580
|
+
maxAge: navigateOpts.maxAge,
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
return matches
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
cleanMatches = () => {
|
|
587
|
+
const now = Date.now()
|
|
588
|
+
|
|
589
|
+
const outdatedMatchIds = Object.values(this.state.matchesById)
|
|
590
|
+
.filter((match) => {
|
|
591
|
+
const route = this.getRoute(match.routeId)
|
|
592
|
+
return (
|
|
593
|
+
!this.state.matchIds.includes(match.id) &&
|
|
594
|
+
!this.state.pendingMatchIds.includes(match.id) &&
|
|
595
|
+
match.preloadInvalidAt < now &&
|
|
596
|
+
(route.options.gcMaxAge
|
|
597
|
+
? match.updatedAt + route.options.gcMaxAge < now
|
|
598
|
+
: true)
|
|
631
599
|
)
|
|
632
|
-
if (gc > 0) {
|
|
633
|
-
router.matchCache[d.matchId] = {
|
|
634
|
-
gc: gc == Infinity ? Number.MAX_SAFE_INTEGER : now + gc,
|
|
635
|
-
match: d,
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
600
|
})
|
|
601
|
+
.map((d) => d.id)
|
|
639
602
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
603
|
+
if (outdatedMatchIds.length) {
|
|
604
|
+
this.__store.setState((s) => {
|
|
605
|
+
const matchesById = { ...s.matchesById }
|
|
606
|
+
outdatedMatchIds.forEach((id) => {
|
|
607
|
+
delete matchesById[id]
|
|
644
608
|
})
|
|
609
|
+
return {
|
|
610
|
+
...s,
|
|
611
|
+
matchesById,
|
|
612
|
+
}
|
|
645
613
|
})
|
|
614
|
+
}
|
|
615
|
+
}
|
|
646
616
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
617
|
+
matchRoutes = (
|
|
618
|
+
pathname: string,
|
|
619
|
+
locationSearch: AnySearchSchema,
|
|
620
|
+
opts?: { throwOnError?: boolean; debug?: boolean },
|
|
621
|
+
): RouteMatch<TRouteTree, ParseRoute<TRouteTree>>[] => {
|
|
622
|
+
let routeParams: AnyPathParams = {}
|
|
623
|
+
|
|
624
|
+
let foundRoute = this.flatRoutes.find((route) => {
|
|
625
|
+
const matchedParams = matchPathname(
|
|
626
|
+
this.basepath,
|
|
627
|
+
trimPathRight(pathname),
|
|
628
|
+
{
|
|
629
|
+
to: route.fullPath,
|
|
630
|
+
caseSensitive:
|
|
631
|
+
route.options.caseSensitive ?? this.options.caseSensitive,
|
|
632
|
+
},
|
|
633
|
+
)
|
|
654
634
|
|
|
655
|
-
if (
|
|
656
|
-
|
|
657
|
-
return
|
|
635
|
+
if (matchedParams) {
|
|
636
|
+
routeParams = matchedParams
|
|
637
|
+
return true
|
|
658
638
|
}
|
|
659
639
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
if (match.action) {
|
|
663
|
-
match.action.current = undefined
|
|
664
|
-
match.action.submissions = []
|
|
665
|
-
}
|
|
666
|
-
})
|
|
640
|
+
return false
|
|
641
|
+
})
|
|
667
642
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
location: router.location,
|
|
671
|
-
matches,
|
|
672
|
-
pending: undefined,
|
|
673
|
-
status: 'idle',
|
|
674
|
-
}
|
|
643
|
+
let routeCursor: AnyRoute =
|
|
644
|
+
foundRoute || (this.routesById as any)['__root__']
|
|
675
645
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
646
|
+
let matchedRoutes: AnyRoute[] = [routeCursor]
|
|
647
|
+
// let includingLayouts = true
|
|
648
|
+
while (routeCursor?.parentRoute) {
|
|
649
|
+
routeCursor = routeCursor.parentRoute
|
|
650
|
+
if (routeCursor) matchedRoutes.unshift(routeCursor)
|
|
651
|
+
}
|
|
679
652
|
|
|
680
|
-
|
|
681
|
-
|
|
653
|
+
// Existing matches are matches that are already loaded along with
|
|
654
|
+
// pending matches that are still loading
|
|
682
655
|
|
|
683
|
-
|
|
684
|
-
|
|
656
|
+
const parseErrors = matchedRoutes.map((route) => {
|
|
657
|
+
let parsedParamsError
|
|
685
658
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
659
|
+
if (route.options.parseParams) {
|
|
660
|
+
try {
|
|
661
|
+
const parsedParams = route.options.parseParams(routeParams)
|
|
662
|
+
// Add the parsed params to the accumulated params bag
|
|
663
|
+
Object.assign(routeParams, parsedParams)
|
|
664
|
+
} catch (err: any) {
|
|
665
|
+
parsedParamsError = new PathParamError(err.message, {
|
|
666
|
+
cause: err,
|
|
667
|
+
})
|
|
690
668
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
669
|
+
if (opts?.throwOnError) {
|
|
670
|
+
throw parsedParamsError
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return parsedParamsError
|
|
694
674
|
}
|
|
675
|
+
}
|
|
695
676
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
})
|
|
699
|
-
},
|
|
677
|
+
return
|
|
678
|
+
})
|
|
700
679
|
|
|
701
|
-
|
|
702
|
-
const
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
680
|
+
const matches = matchedRoutes.map((route, index) => {
|
|
681
|
+
const interpolatedPath = interpolatePath(route.path, routeParams)
|
|
682
|
+
const key = route.options.key
|
|
683
|
+
? route.options.key({
|
|
684
|
+
params: routeParams,
|
|
685
|
+
search: locationSearch,
|
|
686
|
+
}) ?? ''
|
|
687
|
+
: ''
|
|
709
688
|
|
|
710
|
-
|
|
711
|
-
const next = router.buildNext(navigateOpts)
|
|
712
|
-
const matches = router.matchRoutes(next.pathname, {
|
|
713
|
-
strictParseParams: true,
|
|
714
|
-
})
|
|
715
|
-
await router.loadMatches(matches, {
|
|
716
|
-
preload: true,
|
|
717
|
-
maxAge:
|
|
718
|
-
loaderOpts.maxAge ??
|
|
719
|
-
router.options.defaultPreloadMaxAge ??
|
|
720
|
-
router.options.defaultLoaderMaxAge ??
|
|
721
|
-
0,
|
|
722
|
-
gcMaxAge:
|
|
723
|
-
loaderOpts.gcMaxAge ??
|
|
724
|
-
router.options.defaultPreloadGcMaxAge ??
|
|
725
|
-
router.options.defaultLoaderGcMaxAge ??
|
|
726
|
-
0,
|
|
727
|
-
})
|
|
728
|
-
return matches
|
|
729
|
-
},
|
|
689
|
+
const stringifiedKey = key ? JSON.stringify(key) : ''
|
|
730
690
|
|
|
731
|
-
|
|
732
|
-
|
|
691
|
+
const matchId =
|
|
692
|
+
interpolatePath(route.id, routeParams, true) + stringifiedKey
|
|
733
693
|
|
|
734
|
-
|
|
694
|
+
// Waste not, want not. If we already have a match for this route,
|
|
695
|
+
// reuse it. This is important for layout routes, which might stick
|
|
696
|
+
// around between navigation actions that only change leaf routes.
|
|
697
|
+
const existingMatch = this.getRouteMatch(matchId)
|
|
735
698
|
|
|
736
|
-
if (
|
|
737
|
-
return
|
|
699
|
+
if (existingMatch) {
|
|
700
|
+
return { ...existingMatch }
|
|
738
701
|
}
|
|
739
702
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
703
|
+
// Create a fresh route match
|
|
704
|
+
const hasLoaders = !!(
|
|
705
|
+
route.options.loader ||
|
|
706
|
+
componentTypes.some((d) => (route.options[d] as any)?.preload)
|
|
707
|
+
)
|
|
744
708
|
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
709
|
+
const routeMatch: AnyRouteMatch = {
|
|
710
|
+
id: matchId,
|
|
711
|
+
key: stringifiedKey,
|
|
712
|
+
routeId: route.id,
|
|
713
|
+
params: routeParams,
|
|
714
|
+
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
715
|
+
updatedAt: Date.now(),
|
|
716
|
+
invalidAt: Infinity,
|
|
717
|
+
preloadInvalidAt: Infinity,
|
|
718
|
+
routeSearch: {},
|
|
719
|
+
search: {} as any,
|
|
720
|
+
status: hasLoaders ? 'pending' : 'success',
|
|
721
|
+
isFetching: false,
|
|
722
|
+
invalid: false,
|
|
723
|
+
error: undefined,
|
|
724
|
+
paramsError: parseErrors[index],
|
|
725
|
+
searchError: undefined,
|
|
726
|
+
loaderData: undefined,
|
|
727
|
+
loadPromise: Promise.resolve(),
|
|
728
|
+
routeContext: undefined!,
|
|
729
|
+
context: undefined!,
|
|
730
|
+
abortController: new AbortController(),
|
|
731
|
+
fetchedAt: 0,
|
|
732
|
+
}
|
|
748
733
|
|
|
749
|
-
|
|
734
|
+
return routeMatch
|
|
735
|
+
})
|
|
736
|
+
|
|
737
|
+
// Take each match and resolve its search params and context
|
|
738
|
+
// This has to happen after the matches are created or found
|
|
739
|
+
// so that we can use the parent match's search params and context
|
|
740
|
+
matches.forEach((match, i): any => {
|
|
741
|
+
const parentMatch = matches[i - 1]
|
|
742
|
+
const route = this.getRoute(match.routeId)
|
|
743
|
+
|
|
744
|
+
const searchInfo = (() => {
|
|
745
|
+
// Validate the search params and stabilize them
|
|
746
|
+
const parentSearchInfo = {
|
|
747
|
+
search: parentMatch?.search ?? locationSearch,
|
|
748
|
+
routeSearch: parentMatch?.routeSearch ?? locationSearch,
|
|
749
|
+
}
|
|
750
750
|
|
|
751
|
-
|
|
751
|
+
try {
|
|
752
|
+
const validator =
|
|
753
|
+
typeof route.options.validateSearch === 'object'
|
|
754
|
+
? route.options.validateSearch.parse
|
|
755
|
+
: route.options.validateSearch
|
|
752
756
|
|
|
753
|
-
|
|
754
|
-
routes.some((route) => {
|
|
755
|
-
if (!route.routePath && route.childRoutes?.length) {
|
|
756
|
-
return findMatchInRoutes(
|
|
757
|
-
[...foundRoutes, route],
|
|
758
|
-
route.childRoutes,
|
|
759
|
-
)
|
|
760
|
-
}
|
|
757
|
+
const routeSearch = validator?.(parentSearchInfo.search) ?? {}
|
|
761
758
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
759
|
+
const search = {
|
|
760
|
+
...parentSearchInfo.search,
|
|
761
|
+
...routeSearch,
|
|
762
|
+
}
|
|
765
763
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
764
|
+
return {
|
|
765
|
+
routeSearch: replaceEqualDeep(match.routeSearch, routeSearch),
|
|
766
|
+
search: replaceEqualDeep(match.search, search),
|
|
767
|
+
}
|
|
768
|
+
} catch (err: any) {
|
|
769
|
+
match.searchError = new SearchParamError(err.message, {
|
|
770
|
+
cause: err,
|
|
771
|
+
})
|
|
772
772
|
|
|
773
|
-
|
|
774
|
-
|
|
773
|
+
if (opts?.throwOnError) {
|
|
774
|
+
throw match.searchError
|
|
775
|
+
}
|
|
775
776
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
} catch (err) {
|
|
780
|
-
if (opts?.strictParseParams) {
|
|
781
|
-
throw err
|
|
782
|
-
}
|
|
783
|
-
}
|
|
777
|
+
return parentSearchInfo
|
|
778
|
+
}
|
|
779
|
+
})()
|
|
784
780
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
}
|
|
781
|
+
Object.assign(match, {
|
|
782
|
+
...searchInfo,
|
|
783
|
+
})
|
|
790
784
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
785
|
+
const contextInfo = (() => {
|
|
786
|
+
try {
|
|
787
|
+
const routeContext =
|
|
788
|
+
route.options.getContext?.({
|
|
789
|
+
parentContext: parentMatch?.routeContext ?? {},
|
|
790
|
+
context: parentMatch?.context ?? this?.options.context ?? {},
|
|
791
|
+
params: match.params,
|
|
792
|
+
search: match.search,
|
|
793
|
+
}) || ({} as any)
|
|
794
|
+
|
|
795
|
+
const context = {
|
|
796
|
+
...(parentMatch?.context ?? this?.options.context),
|
|
797
|
+
...routeContext,
|
|
798
|
+
} as any
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
context,
|
|
802
|
+
routeContext,
|
|
803
|
+
}
|
|
804
|
+
} catch (err) {
|
|
805
|
+
route.options.onError?.(err)
|
|
806
|
+
throw err
|
|
807
|
+
}
|
|
808
|
+
})()
|
|
794
809
|
|
|
795
|
-
|
|
796
|
-
|
|
810
|
+
Object.assign(match, {
|
|
811
|
+
...contextInfo,
|
|
812
|
+
})
|
|
813
|
+
})
|
|
814
|
+
|
|
815
|
+
return matches as any
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
loadMatches = async (
|
|
819
|
+
resolvedMatches: AnyRouteMatch[],
|
|
820
|
+
opts?: {
|
|
821
|
+
preload?: boolean
|
|
822
|
+
maxAge?: number
|
|
823
|
+
},
|
|
824
|
+
) => {
|
|
825
|
+
this.cleanMatches()
|
|
826
|
+
|
|
827
|
+
if (!opts?.preload) {
|
|
828
|
+
resolvedMatches.forEach((match) => {
|
|
829
|
+
// Update each match with its latest route data
|
|
830
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
831
|
+
...s,
|
|
832
|
+
routeSearch: match.routeSearch,
|
|
833
|
+
search: match.search,
|
|
834
|
+
routeContext: match.routeContext,
|
|
835
|
+
context: match.context,
|
|
836
|
+
error: match.error,
|
|
837
|
+
paramsError: match.paramsError,
|
|
838
|
+
searchError: match.searchError,
|
|
839
|
+
params: match.params,
|
|
840
|
+
}))
|
|
841
|
+
})
|
|
842
|
+
}
|
|
797
843
|
|
|
798
|
-
|
|
844
|
+
let firstBadMatchIndex: number | undefined
|
|
845
|
+
|
|
846
|
+
// Check each match middleware to see if the route can be accessed
|
|
847
|
+
try {
|
|
848
|
+
for (const [index, match] of resolvedMatches.entries()) {
|
|
849
|
+
const route = this.getRoute(match.routeId)
|
|
850
|
+
|
|
851
|
+
const handleError = (
|
|
852
|
+
err: any,
|
|
853
|
+
handler: undefined | ((err: any) => void),
|
|
854
|
+
) => {
|
|
855
|
+
firstBadMatchIndex = firstBadMatchIndex ?? index
|
|
856
|
+
handler = handler || route.options.onError
|
|
857
|
+
|
|
858
|
+
if (isRedirect(err)) {
|
|
859
|
+
throw err
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
try {
|
|
863
|
+
handler?.(err)
|
|
864
|
+
} catch (errorHandlerErr) {
|
|
865
|
+
err = errorHandlerErr
|
|
866
|
+
|
|
867
|
+
if (isRedirect(errorHandlerErr)) {
|
|
868
|
+
throw errorHandlerErr
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
console.log('set error')
|
|
873
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
874
|
+
...s,
|
|
875
|
+
error: err,
|
|
876
|
+
status: 'error',
|
|
877
|
+
updatedAt: Date.now(),
|
|
878
|
+
}))
|
|
799
879
|
}
|
|
800
880
|
|
|
801
|
-
|
|
881
|
+
if (match.paramsError) {
|
|
882
|
+
handleError(match.paramsError, route.options.onParseParamsError)
|
|
883
|
+
}
|
|
802
884
|
|
|
803
|
-
if (
|
|
804
|
-
|
|
885
|
+
if (match.searchError) {
|
|
886
|
+
handleError(match.searchError, route.options.onValidateSearchError)
|
|
805
887
|
}
|
|
806
888
|
|
|
807
|
-
|
|
808
|
-
const interpolatedPath = interpolatePath(foundRoute.routePath, params)
|
|
809
|
-
const matchId = interpolatePath(foundRoute.routeId, params, true)
|
|
810
|
-
|
|
811
|
-
const match =
|
|
812
|
-
existingMatches.find((d) => d.matchId === matchId) ||
|
|
813
|
-
router.matchCache[matchId]?.match ||
|
|
814
|
-
createRouteMatch(router, foundRoute, {
|
|
815
|
-
parentMatch,
|
|
816
|
-
matchId,
|
|
817
|
-
params,
|
|
818
|
-
pathname: joinPaths([pathname, interpolatedPath]),
|
|
819
|
-
})
|
|
820
|
-
|
|
821
|
-
matches.push(match)
|
|
822
|
-
})
|
|
889
|
+
let didError = false
|
|
823
890
|
|
|
824
|
-
|
|
891
|
+
try {
|
|
892
|
+
await route.options.beforeLoad?.({
|
|
893
|
+
...match,
|
|
894
|
+
preload: !!opts?.preload,
|
|
895
|
+
})
|
|
896
|
+
} catch (err) {
|
|
897
|
+
handleError(err, route.options.onBeforeLoadError)
|
|
898
|
+
didError = true
|
|
899
|
+
}
|
|
825
900
|
|
|
826
|
-
|
|
827
|
-
|
|
901
|
+
// If we errored, do not run the next matches' middleware
|
|
902
|
+
if (didError) {
|
|
903
|
+
break
|
|
828
904
|
}
|
|
829
905
|
}
|
|
906
|
+
} catch (err) {
|
|
907
|
+
if (!opts?.preload) {
|
|
908
|
+
this.navigate(err as any)
|
|
909
|
+
}
|
|
830
910
|
|
|
831
|
-
|
|
911
|
+
throw err
|
|
912
|
+
}
|
|
832
913
|
|
|
833
|
-
|
|
914
|
+
const validResolvedMatches = resolvedMatches.slice(0, firstBadMatchIndex)
|
|
915
|
+
const matchPromises: Promise<any>[] = []
|
|
916
|
+
|
|
917
|
+
validResolvedMatches.forEach((match, index) => {
|
|
918
|
+
matchPromises.push(
|
|
919
|
+
(async () => {
|
|
920
|
+
const parentMatchPromise = matchPromises[index - 1]
|
|
921
|
+
const route = this.getRoute(match.routeId)
|
|
922
|
+
|
|
923
|
+
if (
|
|
924
|
+
match.isFetching ||
|
|
925
|
+
(match.status === 'success' &&
|
|
926
|
+
!this.getIsInvalid({ matchId: match.id, preload: opts?.preload }))
|
|
927
|
+
) {
|
|
928
|
+
return this.getRouteMatch(match.id)?.loadPromise
|
|
929
|
+
}
|
|
834
930
|
|
|
835
|
-
|
|
836
|
-
|
|
931
|
+
const fetchedAt = Date.now()
|
|
932
|
+
const checkLatest = () => {
|
|
933
|
+
const latest = this.getRouteMatch(match.id)
|
|
934
|
+
return latest && latest.fetchedAt !== fetchedAt
|
|
935
|
+
? latest.loadPromise
|
|
936
|
+
: undefined
|
|
937
|
+
}
|
|
837
938
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
// Validate the match (loads search params etc)
|
|
841
|
-
match.__.validate()
|
|
842
|
-
match.load(loaderOpts)
|
|
939
|
+
const load = async () => {
|
|
940
|
+
let latestPromise
|
|
843
941
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
942
|
+
const handleError = (err: any) => {
|
|
943
|
+
if (isRedirect(err)) {
|
|
944
|
+
if (!opts?.preload) {
|
|
945
|
+
this.navigate(err as any)
|
|
946
|
+
}
|
|
947
|
+
return true
|
|
948
|
+
}
|
|
849
949
|
|
|
850
|
-
|
|
950
|
+
return false
|
|
951
|
+
}
|
|
851
952
|
|
|
852
|
-
|
|
853
|
-
|
|
953
|
+
try {
|
|
954
|
+
const componentsPromise = Promise.all(
|
|
955
|
+
componentTypes.map(async (type) => {
|
|
956
|
+
const component = route.options[type]
|
|
854
957
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
;[
|
|
861
|
-
...router.state.matches,
|
|
862
|
-
...(router.state.pending?.matches ?? []),
|
|
863
|
-
].forEach((match) => {
|
|
864
|
-
if (unloadedMatchIds.includes(match.matchId)) {
|
|
865
|
-
match.invalidate()
|
|
866
|
-
}
|
|
867
|
-
})
|
|
868
|
-
},
|
|
958
|
+
if ((component as any)?.preload) {
|
|
959
|
+
await (component as any).preload()
|
|
960
|
+
}
|
|
961
|
+
}),
|
|
962
|
+
)
|
|
869
963
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
}),
|
|
964
|
+
const loaderPromise = route.options.loader?.({
|
|
965
|
+
...match,
|
|
966
|
+
preload: !!opts?.preload,
|
|
967
|
+
parentMatchPromise,
|
|
968
|
+
})
|
|
876
969
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
970
|
+
const [_, loader] = await Promise.all([
|
|
971
|
+
componentsPromise,
|
|
972
|
+
loaderPromise,
|
|
973
|
+
])
|
|
974
|
+
if ((latestPromise = checkLatest())) return await latestPromise
|
|
880
975
|
|
|
881
|
-
|
|
882
|
-
|
|
976
|
+
this.setRouteMatchData(match.id, () => loader, opts)
|
|
977
|
+
} catch (loaderError) {
|
|
978
|
+
if ((latestPromise = checkLatest())) return await latestPromise
|
|
979
|
+
handleError(loaderError)
|
|
883
980
|
|
|
884
|
-
|
|
885
|
-
...location,
|
|
886
|
-
to: location.to
|
|
887
|
-
? router.resolvePath(location.from ?? '', location.to)
|
|
888
|
-
: undefined,
|
|
889
|
-
}
|
|
981
|
+
let error = loaderError
|
|
890
982
|
|
|
891
|
-
|
|
983
|
+
try {
|
|
984
|
+
if (route.options.onLoadError) {
|
|
985
|
+
route.options.onLoadError?.(loaderError)
|
|
986
|
+
} else {
|
|
987
|
+
route.options.onError?.(loaderError)
|
|
988
|
+
}
|
|
989
|
+
} catch (errorHandlerErr) {
|
|
990
|
+
error = errorHandlerErr
|
|
991
|
+
handleError(error)
|
|
992
|
+
}
|
|
892
993
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
994
|
+
console.log('set error')
|
|
995
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
996
|
+
...s,
|
|
997
|
+
error: error,
|
|
998
|
+
status: 'error',
|
|
999
|
+
isFetching: false,
|
|
1000
|
+
updatedAt: Date.now(),
|
|
1001
|
+
}))
|
|
1002
|
+
console.log(this.getRouteMatch(match.id)?.status)
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
902
1005
|
|
|
903
|
-
|
|
904
|
-
...opts,
|
|
905
|
-
to: next.pathname,
|
|
906
|
-
})
|
|
907
|
-
},
|
|
1006
|
+
const loadPromise = load()
|
|
908
1007
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1008
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
1009
|
+
...s,
|
|
1010
|
+
status: s.status !== 'success' ? 'pending' : s.status,
|
|
1011
|
+
isFetching: true,
|
|
1012
|
+
loadPromise,
|
|
1013
|
+
fetchedAt,
|
|
1014
|
+
invalid: false,
|
|
1015
|
+
}))
|
|
912
1016
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1017
|
+
await loadPromise
|
|
1018
|
+
})(),
|
|
1019
|
+
)
|
|
1020
|
+
})
|
|
917
1021
|
|
|
918
|
-
|
|
1022
|
+
await Promise.all(matchPromises)
|
|
1023
|
+
}
|
|
919
1024
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1025
|
+
reload = () => {
|
|
1026
|
+
return this.navigate({
|
|
1027
|
+
fromCurrent: true,
|
|
1028
|
+
replace: true,
|
|
1029
|
+
search: true,
|
|
1030
|
+
} as any)
|
|
1031
|
+
}
|
|
924
1032
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
)
|
|
1033
|
+
resolvePath = (from: string, path: string) => {
|
|
1034
|
+
return resolvePath(this.basepath!, from, cleanPath(path))
|
|
1035
|
+
}
|
|
929
1036
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1037
|
+
navigate = async <
|
|
1038
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
1039
|
+
TTo extends string = '',
|
|
1040
|
+
>({
|
|
1041
|
+
from,
|
|
1042
|
+
to = '' as any,
|
|
1043
|
+
search,
|
|
1044
|
+
hash,
|
|
1045
|
+
replace,
|
|
1046
|
+
params,
|
|
1047
|
+
}: NavigateOptions<TRouteTree, TFrom, TTo>) => {
|
|
1048
|
+
// If this link simply reloads the current route,
|
|
1049
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
1050
|
+
|
|
1051
|
+
// If this `to` is a valid external URL, return
|
|
1052
|
+
// null for LinkUtils
|
|
1053
|
+
const toString = String(to)
|
|
1054
|
+
const fromString = typeof from === 'undefined' ? from : String(from)
|
|
1055
|
+
let isExternal
|
|
1056
|
+
|
|
1057
|
+
try {
|
|
1058
|
+
new URL(`${toString}`)
|
|
1059
|
+
isExternal = true
|
|
1060
|
+
} catch (e) {}
|
|
1061
|
+
|
|
1062
|
+
invariant(
|
|
1063
|
+
!isExternal,
|
|
1064
|
+
'Attempting to navigate to external url with this.navigate!',
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
return this.#commitLocation({
|
|
1068
|
+
from: fromString,
|
|
1069
|
+
to: toString,
|
|
1070
|
+
search,
|
|
1071
|
+
hash,
|
|
1072
|
+
replace,
|
|
1073
|
+
params,
|
|
1074
|
+
})
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
matchRoute = <
|
|
1078
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
1079
|
+
TTo extends string = '',
|
|
1080
|
+
TResolved extends string = ResolveRelativePath<TFrom, NoInfer<TTo>>,
|
|
1081
|
+
>(
|
|
1082
|
+
location: ToOptions<TRouteTree, TFrom, TTo>,
|
|
1083
|
+
opts?: MatchRouteOptions,
|
|
1084
|
+
): false | RouteById<TRouteTree, TResolved>['__types']['allParams'] => {
|
|
1085
|
+
location = {
|
|
1086
|
+
...location,
|
|
1087
|
+
to: location.to
|
|
1088
|
+
? this.resolvePath(location.from ?? '', location.to)
|
|
1089
|
+
: undefined,
|
|
1090
|
+
} as any
|
|
1091
|
+
|
|
1092
|
+
const next = this.buildNext(location)
|
|
1093
|
+
if (opts?.pending && this.state.status !== 'pending') {
|
|
1094
|
+
return false
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const baseLocation = opts?.pending
|
|
1098
|
+
? this.state.location
|
|
1099
|
+
: this.state.resolvedLocation
|
|
1100
|
+
|
|
1101
|
+
if (!baseLocation) {
|
|
1102
|
+
return false
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const match = matchPathname(this.basepath, baseLocation.pathname, {
|
|
1106
|
+
...opts,
|
|
1107
|
+
to: next.pathname,
|
|
1108
|
+
}) as any
|
|
1109
|
+
|
|
1110
|
+
if (!match) {
|
|
1111
|
+
return false
|
|
1112
|
+
}
|
|
939
1113
|
|
|
940
|
-
|
|
1114
|
+
if (opts?.includeSearch ?? true) {
|
|
1115
|
+
return partialDeepEqual(baseLocation.search, next.search) ? match : false
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return match
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
buildLink = <
|
|
1122
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
1123
|
+
TTo extends string = '',
|
|
1124
|
+
>({
|
|
1125
|
+
from,
|
|
1126
|
+
to = '.' as any,
|
|
1127
|
+
search,
|
|
1128
|
+
params,
|
|
1129
|
+
hash,
|
|
1130
|
+
target,
|
|
1131
|
+
replace,
|
|
1132
|
+
activeOptions,
|
|
1133
|
+
preload,
|
|
1134
|
+
preloadDelay: userPreloadDelay,
|
|
1135
|
+
disabled,
|
|
1136
|
+
state,
|
|
1137
|
+
}: LinkOptions<TRouteTree, TFrom, TTo>): LinkInfo => {
|
|
1138
|
+
// If this link simply reloads the current route,
|
|
1139
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
1140
|
+
|
|
1141
|
+
// If this `to` is a valid external URL, return
|
|
1142
|
+
// null for LinkUtils
|
|
1143
|
+
|
|
1144
|
+
try {
|
|
1145
|
+
new URL(`${to}`)
|
|
1146
|
+
return {
|
|
1147
|
+
type: 'external',
|
|
1148
|
+
href: to,
|
|
1149
|
+
}
|
|
1150
|
+
} catch (e) {}
|
|
1151
|
+
|
|
1152
|
+
const nextOpts = {
|
|
941
1153
|
from,
|
|
942
|
-
to
|
|
1154
|
+
to,
|
|
943
1155
|
search,
|
|
944
1156
|
params,
|
|
945
1157
|
hash,
|
|
946
|
-
target,
|
|
947
1158
|
replace,
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
preloadMaxAge: userPreloadMaxAge,
|
|
951
|
-
preloadGcMaxAge: userPreloadGcMaxAge,
|
|
952
|
-
preloadDelay: userPreloadDelay,
|
|
953
|
-
disabled,
|
|
954
|
-
}) => {
|
|
955
|
-
// If this link simply reloads the current route,
|
|
956
|
-
// make sure it has a new key so it will trigger a data refresh
|
|
1159
|
+
state,
|
|
1160
|
+
}
|
|
957
1161
|
|
|
958
|
-
|
|
959
|
-
|
|
1162
|
+
const next = this.buildNext(nextOpts)
|
|
1163
|
+
|
|
1164
|
+
preload = preload ?? this.options.defaultPreload
|
|
1165
|
+
const preloadDelay =
|
|
1166
|
+
userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0
|
|
1167
|
+
|
|
1168
|
+
// Compare path/hash for matches
|
|
1169
|
+
const currentPathSplit = this.state.location.pathname.split('/')
|
|
1170
|
+
const nextPathSplit = next.pathname.split('/')
|
|
1171
|
+
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
1172
|
+
(d, i) => d === currentPathSplit[i],
|
|
1173
|
+
)
|
|
1174
|
+
// Combine the matches based on user options
|
|
1175
|
+
const pathTest = activeOptions?.exact
|
|
1176
|
+
? this.state.location.pathname === next.pathname
|
|
1177
|
+
: pathIsFuzzyEqual
|
|
1178
|
+
const hashTest = activeOptions?.includeHash
|
|
1179
|
+
? this.state.location.hash === next.hash
|
|
1180
|
+
: true
|
|
1181
|
+
const searchTest =
|
|
1182
|
+
activeOptions?.includeSearch ?? true
|
|
1183
|
+
? partialDeepEqual(this.state.location.search, next.search)
|
|
1184
|
+
: true
|
|
1185
|
+
|
|
1186
|
+
// The final "active" test
|
|
1187
|
+
const isActive = pathTest && hashTest && searchTest
|
|
1188
|
+
|
|
1189
|
+
// The click handler
|
|
1190
|
+
const handleClick = (e: MouseEvent) => {
|
|
1191
|
+
if (
|
|
1192
|
+
!disabled &&
|
|
1193
|
+
!isCtrlEvent(e) &&
|
|
1194
|
+
!e.defaultPrevented &&
|
|
1195
|
+
(!target || target === '_self') &&
|
|
1196
|
+
e.button === 0
|
|
1197
|
+
) {
|
|
1198
|
+
e.preventDefault()
|
|
960
1199
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
return {
|
|
964
|
-
type: 'external',
|
|
965
|
-
href: to,
|
|
966
|
-
}
|
|
967
|
-
} catch (e) {}
|
|
968
|
-
|
|
969
|
-
const nextOpts = {
|
|
970
|
-
from,
|
|
971
|
-
to,
|
|
972
|
-
search,
|
|
973
|
-
params,
|
|
974
|
-
hash,
|
|
975
|
-
replace,
|
|
1200
|
+
// All is well? Navigate!
|
|
1201
|
+
this.#commitLocation(nextOpts as any)
|
|
976
1202
|
}
|
|
1203
|
+
}
|
|
977
1204
|
|
|
978
|
-
|
|
1205
|
+
// The click handler
|
|
1206
|
+
const handleFocus = (e: MouseEvent) => {
|
|
1207
|
+
if (preload) {
|
|
1208
|
+
this.preloadRoute(nextOpts).catch((err) => {
|
|
1209
|
+
console.warn(err)
|
|
1210
|
+
console.warn('Error preloading route! ☝️')
|
|
1211
|
+
})
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
979
1214
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1215
|
+
const handleTouchStart = (e: TouchEvent) => {
|
|
1216
|
+
this.preloadRoute(nextOpts).catch((err) => {
|
|
1217
|
+
console.warn(err)
|
|
1218
|
+
console.warn('Error preloading route! ☝️')
|
|
1219
|
+
})
|
|
1220
|
+
}
|
|
983
1221
|
|
|
984
|
-
|
|
985
|
-
const
|
|
986
|
-
const currentPathSplit = router.state.location.pathname.split('/')
|
|
987
|
-
const nextPathSplit = next.pathname.split('/')
|
|
988
|
-
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
989
|
-
(d, i) => d === currentPathSplit[i],
|
|
990
|
-
)
|
|
991
|
-
const hashIsEqual = router.state.location.hash === next.hash
|
|
992
|
-
// Combine the matches based on user options
|
|
993
|
-
const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual
|
|
994
|
-
const hashTest = activeOptions?.includeHash ? hashIsEqual : true
|
|
995
|
-
|
|
996
|
-
// The final "active" test
|
|
997
|
-
const isActive = pathTest && hashTest
|
|
998
|
-
|
|
999
|
-
// The click handler
|
|
1000
|
-
const handleClick = (e: MouseEvent) => {
|
|
1001
|
-
if (
|
|
1002
|
-
!disabled &&
|
|
1003
|
-
!isCtrlEvent(e) &&
|
|
1004
|
-
!e.defaultPrevented &&
|
|
1005
|
-
(!target || target === '_self') &&
|
|
1006
|
-
e.button === 0
|
|
1007
|
-
) {
|
|
1008
|
-
e.preventDefault()
|
|
1009
|
-
if (pathIsEqual && !search && !hash) {
|
|
1010
|
-
router.invalidateRoute(nextOpts)
|
|
1011
|
-
}
|
|
1222
|
+
const handleEnter = (e: MouseEvent) => {
|
|
1223
|
+
const target = (e.target || {}) as LinkCurrentTargetElement
|
|
1012
1224
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1225
|
+
if (preload) {
|
|
1226
|
+
if (target.preloadTimeout) {
|
|
1227
|
+
return
|
|
1015
1228
|
}
|
|
1016
|
-
}
|
|
1017
1229
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
gcMaxAge: userPreloadGcMaxAge,
|
|
1230
|
+
target.preloadTimeout = setTimeout(() => {
|
|
1231
|
+
target.preloadTimeout = null
|
|
1232
|
+
this.preloadRoute(nextOpts).catch((err) => {
|
|
1233
|
+
console.warn(err)
|
|
1234
|
+
console.warn('Error preloading route! ☝️')
|
|
1024
1235
|
})
|
|
1025
|
-
}
|
|
1236
|
+
}, preloadDelay)
|
|
1026
1237
|
}
|
|
1238
|
+
}
|
|
1027
1239
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
if (preload) {
|
|
1032
|
-
if (target.preloadTimeout) {
|
|
1033
|
-
return
|
|
1034
|
-
}
|
|
1240
|
+
const handleLeave = (e: MouseEvent) => {
|
|
1241
|
+
const target = (e.target || {}) as LinkCurrentTargetElement
|
|
1035
1242
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
maxAge: userPreloadMaxAge,
|
|
1040
|
-
gcMaxAge: userPreloadGcMaxAge,
|
|
1041
|
-
})
|
|
1042
|
-
}, preloadDelay)
|
|
1043
|
-
}
|
|
1243
|
+
if (target.preloadTimeout) {
|
|
1244
|
+
clearTimeout(target.preloadTimeout)
|
|
1245
|
+
target.preloadTimeout = null
|
|
1044
1246
|
}
|
|
1247
|
+
}
|
|
1045
1248
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1249
|
+
return {
|
|
1250
|
+
type: 'internal',
|
|
1251
|
+
next,
|
|
1252
|
+
handleFocus,
|
|
1253
|
+
handleClick,
|
|
1254
|
+
handleEnter,
|
|
1255
|
+
handleLeave,
|
|
1256
|
+
handleTouchStart,
|
|
1257
|
+
isActive,
|
|
1258
|
+
disabled,
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1048
1261
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1262
|
+
dehydrate = (): DehydratedRouter => {
|
|
1263
|
+
return {
|
|
1264
|
+
state: pick(this.state, ['location', 'status', 'lastUpdated']),
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
hydrate = async (__do_not_use_server_ctx?: HydrationCtx) => {
|
|
1269
|
+
let _ctx = __do_not_use_server_ctx
|
|
1270
|
+
// Client hydrates from window
|
|
1271
|
+
if (typeof document !== 'undefined') {
|
|
1272
|
+
_ctx = window.__TSR_DEHYDRATED__
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
invariant(
|
|
1276
|
+
_ctx,
|
|
1277
|
+
'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?',
|
|
1278
|
+
)
|
|
1054
1279
|
|
|
1280
|
+
const ctx = _ctx
|
|
1281
|
+
this.dehydratedData = ctx.payload as any
|
|
1282
|
+
this.options.hydrate?.(ctx.payload as any)
|
|
1283
|
+
const routerState = ctx.router.state as RouterState<TRouteTree>
|
|
1284
|
+
|
|
1285
|
+
this.__store.setState((s) => {
|
|
1055
1286
|
return {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
handleClick,
|
|
1060
|
-
handleEnter,
|
|
1061
|
-
handleLeave,
|
|
1062
|
-
isActive,
|
|
1063
|
-
disabled,
|
|
1287
|
+
...s,
|
|
1288
|
+
...routerState,
|
|
1289
|
+
resolvedLocation: routerState.location,
|
|
1064
1290
|
}
|
|
1065
|
-
}
|
|
1066
|
-
buildNext: (opts: BuildNextOptions) => {
|
|
1067
|
-
const next = router.__.buildLocation(opts)
|
|
1291
|
+
})
|
|
1068
1292
|
|
|
1069
|
-
|
|
1293
|
+
await this.load()
|
|
1070
1294
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
.flat()
|
|
1074
|
-
.filter(Boolean)
|
|
1295
|
+
return
|
|
1296
|
+
}
|
|
1075
1297
|
|
|
1076
|
-
|
|
1077
|
-
.map((match) => match.options.postSearchFilters ?? [])
|
|
1078
|
-
.flat()
|
|
1079
|
-
.filter(Boolean)
|
|
1298
|
+
injectedHtml: (string | (() => Promise<string> | string))[] = []
|
|
1080
1299
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1300
|
+
injectHtml = async (html: string | (() => Promise<string> | string)) => {
|
|
1301
|
+
this.injectedHtml.push(html)
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
dehydrateData = <T>(key: any, getData: T | (() => Promise<T> | T)) => {
|
|
1305
|
+
if (typeof document === 'undefined') {
|
|
1306
|
+
const strKey = typeof key === 'string' ? key : JSON.stringify(key)
|
|
1307
|
+
|
|
1308
|
+
this.injectHtml(async () => {
|
|
1309
|
+
const id = `__TSR_DEHYDRATED__${strKey}`
|
|
1310
|
+
const data =
|
|
1311
|
+
typeof getData === 'function' ? await (getData as any)() : getData
|
|
1312
|
+
return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(
|
|
1313
|
+
strKey,
|
|
1314
|
+
)}"] = ${JSON.stringify(data)}
|
|
1315
|
+
;(() => {
|
|
1316
|
+
var el = document.getElementById('${id}')
|
|
1317
|
+
el.parentElement.removeChild(el)
|
|
1318
|
+
})()
|
|
1319
|
+
</script>`
|
|
1085
1320
|
})
|
|
1086
|
-
},
|
|
1087
1321
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
const recurseRoutes = (
|
|
1091
|
-
routeConfigs: RouteConfig[],
|
|
1092
|
-
parent?: Route<TAllRouteInfo, any>,
|
|
1093
|
-
): Route<TAllRouteInfo, any>[] => {
|
|
1094
|
-
return routeConfigs.map((routeConfig) => {
|
|
1095
|
-
const routeOptions = routeConfig.options
|
|
1096
|
-
const route = createRoute(routeConfig, routeOptions, parent, router)
|
|
1097
|
-
const existingRoute = (router.routesById as any)[route.routeId]
|
|
1098
|
-
|
|
1099
|
-
if (existingRoute) {
|
|
1100
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1101
|
-
console.warn(
|
|
1102
|
-
`Duplicate routes found with id: ${String(route.routeId)}`,
|
|
1103
|
-
router.routesById,
|
|
1104
|
-
route,
|
|
1105
|
-
)
|
|
1106
|
-
}
|
|
1107
|
-
throw new Error()
|
|
1108
|
-
}
|
|
1322
|
+
return () => this.hydrateData<T>(key)
|
|
1323
|
+
}
|
|
1109
1324
|
|
|
1110
|
-
|
|
1325
|
+
return () => undefined
|
|
1326
|
+
}
|
|
1111
1327
|
|
|
1112
|
-
|
|
1328
|
+
hydrateData = <T = unknown>(key: any) => {
|
|
1329
|
+
if (typeof document !== 'undefined') {
|
|
1330
|
+
const strKey = typeof key === 'string' ? key : JSON.stringify(key)
|
|
1113
1331
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
: undefined
|
|
1332
|
+
return window[`__TSR_DEHYDRATED__${strKey}` as any] as T
|
|
1333
|
+
}
|
|
1117
1334
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1335
|
+
return undefined
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// resolveMatchPromise = (matchId: string, key: string, value: any) => {
|
|
1339
|
+
// this.state.matches
|
|
1340
|
+
// .find((d) => d.id === matchId)
|
|
1341
|
+
// ?.__promisesByKey[key]?.resolve(value)
|
|
1342
|
+
// }
|
|
1343
|
+
|
|
1344
|
+
#buildRouteTree = (routeTree: TRouteTree) => {
|
|
1345
|
+
this.routeTree = routeTree as any
|
|
1346
|
+
this.routesById = {} as any
|
|
1347
|
+
this.routesByPath = {} as any
|
|
1348
|
+
this.flatRoutes = [] as any
|
|
1349
|
+
|
|
1350
|
+
const recurseRoutes = (routes: AnyRoute[]) => {
|
|
1351
|
+
routes.forEach((route, i) => {
|
|
1352
|
+
route.init({ originalIndex: i, router: this })
|
|
1353
|
+
|
|
1354
|
+
const existingRoute = (this.routesById as any)[route.id]
|
|
1355
|
+
|
|
1356
|
+
invariant(
|
|
1357
|
+
!existingRoute,
|
|
1358
|
+
`Duplicate routes found with id: ${String(route.id)}`,
|
|
1359
|
+
)
|
|
1360
|
+
;(this.routesById as any)[route.id] = route
|
|
1361
|
+
|
|
1362
|
+
if (!route.isRoot && route.path) {
|
|
1363
|
+
const trimmedFullPath = trimPathRight(route.fullPath)
|
|
1364
|
+
if (
|
|
1365
|
+
!(this.routesByPath as any)[trimmedFullPath] ||
|
|
1366
|
+
route.fullPath.endsWith('/')
|
|
1367
|
+
) {
|
|
1368
|
+
;(this.routesByPath as any)[trimmedFullPath] = route
|
|
1369
|
+
}
|
|
1120
1370
|
}
|
|
1121
1371
|
|
|
1122
|
-
const
|
|
1372
|
+
const children = route.children as Route[]
|
|
1123
1373
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1374
|
+
if (children?.length) {
|
|
1375
|
+
recurseRoutes(children)
|
|
1376
|
+
}
|
|
1377
|
+
})
|
|
1378
|
+
}
|
|
1126
1379
|
|
|
1127
|
-
|
|
1128
|
-
location: History['location'],
|
|
1129
|
-
previousLocation?: Location,
|
|
1130
|
-
): Location => {
|
|
1131
|
-
const parsedSearch = router.options.parseSearch(location.search)
|
|
1380
|
+
recurseRoutes([routeTree])
|
|
1132
1381
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
key: location.key,
|
|
1382
|
+
this.flatRoutes = (Object.values(this.routesByPath) as AnyRoute[])
|
|
1383
|
+
.map((d, i) => {
|
|
1384
|
+
const trimmed = trimPath(d.fullPath)
|
|
1385
|
+
const parsed = parsePathname(trimmed)
|
|
1386
|
+
|
|
1387
|
+
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
1388
|
+
parsed.shift()
|
|
1141
1389
|
}
|
|
1142
|
-
},
|
|
1143
1390
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1391
|
+
const score = parsed.map((d) => {
|
|
1392
|
+
if (d.type === 'param') {
|
|
1393
|
+
return 0.5
|
|
1394
|
+
}
|
|
1148
1395
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
const fromPathname = dest.fromCurrent
|
|
1153
|
-
? router.location.pathname
|
|
1154
|
-
: dest.from ?? router.location.pathname
|
|
1155
|
-
|
|
1156
|
-
let pathname = resolvePath(
|
|
1157
|
-
router.basepath ?? '/',
|
|
1158
|
-
fromPathname,
|
|
1159
|
-
`${dest.to ?? '.'}`,
|
|
1160
|
-
)
|
|
1396
|
+
if (d.type === 'wildcard') {
|
|
1397
|
+
return 0.25
|
|
1398
|
+
}
|
|
1161
1399
|
|
|
1162
|
-
|
|
1163
|
-
strictParseParams: true,
|
|
1400
|
+
return 1
|
|
1164
1401
|
})
|
|
1165
1402
|
|
|
1166
|
-
|
|
1403
|
+
return { child: d, trimmed, parsed, index: i, score }
|
|
1404
|
+
})
|
|
1405
|
+
.sort((a, b) => {
|
|
1406
|
+
let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0
|
|
1167
1407
|
|
|
1168
|
-
|
|
1408
|
+
if (isIndex !== 0) return isIndex
|
|
1169
1409
|
|
|
1170
|
-
|
|
1171
|
-
(dest.params ?? true) === true
|
|
1172
|
-
? prevParams
|
|
1173
|
-
: functionalUpdate(dest.params!, prevParams)
|
|
1410
|
+
const length = Math.min(a.score.length, b.score.length)
|
|
1174
1411
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
.filter(Boolean)
|
|
1179
|
-
.forEach((fn) => {
|
|
1180
|
-
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
1181
|
-
})
|
|
1412
|
+
// Sort by length of score
|
|
1413
|
+
if (a.score.length !== b.score.length) {
|
|
1414
|
+
return b.score.length - a.score.length
|
|
1182
1415
|
}
|
|
1183
1416
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
router.location.search,
|
|
1191
|
-
)
|
|
1192
|
-
: router.location.search
|
|
1193
|
-
|
|
1194
|
-
// Then the link/navigate function
|
|
1195
|
-
const destSearch =
|
|
1196
|
-
dest.search === true
|
|
1197
|
-
? preFilteredSearch // Preserve resolvedFrom true
|
|
1198
|
-
: dest.search
|
|
1199
|
-
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1200
|
-
: dest.__preSearchFilters?.length
|
|
1201
|
-
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1202
|
-
: {}
|
|
1203
|
-
|
|
1204
|
-
// Then post filters
|
|
1205
|
-
const postFilteredSearch = dest.__postSearchFilters?.length
|
|
1206
|
-
? dest.__postSearchFilters.reduce(
|
|
1207
|
-
(prev, next) => next(prev),
|
|
1208
|
-
destSearch,
|
|
1209
|
-
)
|
|
1210
|
-
: destSearch
|
|
1211
|
-
|
|
1212
|
-
const search = replaceEqualDeep(
|
|
1213
|
-
router.location.search,
|
|
1214
|
-
postFilteredSearch,
|
|
1215
|
-
)
|
|
1417
|
+
// Sort by min available score
|
|
1418
|
+
for (let i = 0; i < length; i++) {
|
|
1419
|
+
if (a.score[i] !== b.score[i]) {
|
|
1420
|
+
return b.score[i]! - a.score[i]!
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1216
1423
|
|
|
1217
|
-
|
|
1218
|
-
let
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1424
|
+
// Sort by min available parsed value
|
|
1425
|
+
for (let i = 0; i < length; i++) {
|
|
1426
|
+
if (a.parsed[i]!.value !== b.parsed[i]!.value) {
|
|
1427
|
+
return a.parsed[i]!.value! > b.parsed[i]!.value! ? 1 : -1
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1223
1430
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
searchStr,
|
|
1228
|
-
state: router.location.state,
|
|
1229
|
-
hash,
|
|
1230
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
1231
|
-
key: dest.key,
|
|
1431
|
+
// Sort by length of trimmed full path
|
|
1432
|
+
if (a.trimmed !== b.trimmed) {
|
|
1433
|
+
return a.trimmed > b.trimmed ? 1 : -1
|
|
1232
1434
|
}
|
|
1233
|
-
},
|
|
1234
1435
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1436
|
+
// Sort by original index
|
|
1437
|
+
return a.index - b.index
|
|
1438
|
+
})
|
|
1439
|
+
.map((d, i) => {
|
|
1440
|
+
d.child.rank = i
|
|
1441
|
+
return d.child
|
|
1442
|
+
}) as any
|
|
1443
|
+
}
|
|
1237
1444
|
|
|
1238
|
-
|
|
1445
|
+
#parseLocation = (
|
|
1446
|
+
previousLocation?: ParsedLocation,
|
|
1447
|
+
): ParsedLocation<FullSearchSchema<TRouteTree>> => {
|
|
1448
|
+
let { pathname, search, hash, state } = this.history.location
|
|
1449
|
+
|
|
1450
|
+
const parsedSearch = this.options.parseSearch(search)
|
|
1451
|
+
|
|
1452
|
+
return {
|
|
1453
|
+
pathname: pathname,
|
|
1454
|
+
searchStr: search,
|
|
1455
|
+
search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
|
|
1456
|
+
hash: hash.split('#').reverse()[0] ?? '',
|
|
1457
|
+
href: `${pathname}${search}${hash}`,
|
|
1458
|
+
state: state as LocationState,
|
|
1459
|
+
key: state?.key || '__init__',
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1239
1462
|
|
|
1240
|
-
|
|
1463
|
+
#buildLocation = (dest: BuildNextOptions = {}): ParsedLocation => {
|
|
1464
|
+
dest.fromCurrent = dest.fromCurrent ?? dest.to === ''
|
|
1241
1465
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1466
|
+
const fromPathname = dest.fromCurrent
|
|
1467
|
+
? this.state.location.pathname
|
|
1468
|
+
: dest.from ?? this.state.location.pathname
|
|
1245
1469
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1470
|
+
let pathname = resolvePath(
|
|
1471
|
+
this.basepath ?? '/',
|
|
1472
|
+
fromPathname,
|
|
1473
|
+
`${dest.to ?? ''}`,
|
|
1474
|
+
)
|
|
1248
1475
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1476
|
+
const fromMatches = this.matchRoutes(
|
|
1477
|
+
this.state.location.pathname,
|
|
1478
|
+
this.state.location.search,
|
|
1479
|
+
)
|
|
1252
1480
|
|
|
1253
|
-
|
|
1254
|
-
history.replace(
|
|
1255
|
-
{
|
|
1256
|
-
pathname: next.pathname,
|
|
1257
|
-
hash: next.hash,
|
|
1258
|
-
search: next.searchStr,
|
|
1259
|
-
},
|
|
1260
|
-
{
|
|
1261
|
-
id,
|
|
1262
|
-
...next.state,
|
|
1263
|
-
},
|
|
1264
|
-
)
|
|
1265
|
-
} else {
|
|
1266
|
-
history.push(
|
|
1267
|
-
{
|
|
1268
|
-
pathname: next.pathname,
|
|
1269
|
-
hash: next.hash,
|
|
1270
|
-
search: next.searchStr,
|
|
1271
|
-
},
|
|
1272
|
-
{
|
|
1273
|
-
id,
|
|
1274
|
-
},
|
|
1275
|
-
)
|
|
1276
|
-
}
|
|
1481
|
+
const prevParams = { ...last(fromMatches)?.params }
|
|
1277
1482
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1483
|
+
let nextParams =
|
|
1484
|
+
(dest.params ?? true) === true
|
|
1485
|
+
? prevParams
|
|
1486
|
+
: functionalUpdate(dest.params!, prevParams)
|
|
1280
1487
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1488
|
+
if (nextParams) {
|
|
1489
|
+
dest.__matches
|
|
1490
|
+
?.map((d) => this.getRoute(d.routeId).options.stringifyParams)
|
|
1491
|
+
.filter(Boolean)
|
|
1492
|
+
.forEach((fn) => {
|
|
1493
|
+
nextParams = { ...nextParams!, ...fn!(nextParams!) }
|
|
1285
1494
|
})
|
|
1495
|
+
}
|
|
1286
1496
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1497
|
+
pathname = interpolatePath(pathname, nextParams ?? {})
|
|
1498
|
+
|
|
1499
|
+
const preSearchFilters =
|
|
1500
|
+
dest.__matches
|
|
1501
|
+
?.map(
|
|
1502
|
+
(match) =>
|
|
1503
|
+
this.getRoute(match.routeId).options.preSearchFilters ?? [],
|
|
1504
|
+
)
|
|
1505
|
+
.flat()
|
|
1506
|
+
.filter(Boolean) ?? []
|
|
1507
|
+
|
|
1508
|
+
const postSearchFilters =
|
|
1509
|
+
dest.__matches
|
|
1510
|
+
?.map(
|
|
1511
|
+
(match) =>
|
|
1512
|
+
this.getRoute(match.routeId).options.postSearchFilters ?? [],
|
|
1513
|
+
)
|
|
1514
|
+
.flat()
|
|
1515
|
+
.filter(Boolean) ?? []
|
|
1516
|
+
|
|
1517
|
+
// Pre filters first
|
|
1518
|
+
const preFilteredSearch = preSearchFilters?.length
|
|
1519
|
+
? preSearchFilters?.reduce(
|
|
1520
|
+
(prev, next) => next(prev),
|
|
1521
|
+
this.state.location.search,
|
|
1522
|
+
)
|
|
1523
|
+
: this.state.location.search
|
|
1524
|
+
|
|
1525
|
+
// Then the link/navigate function
|
|
1526
|
+
const destSearch =
|
|
1527
|
+
dest.search === true
|
|
1528
|
+
? preFilteredSearch // Preserve resolvedFrom true
|
|
1529
|
+
: dest.search
|
|
1530
|
+
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1531
|
+
: preSearchFilters?.length
|
|
1532
|
+
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1533
|
+
: {}
|
|
1534
|
+
|
|
1535
|
+
// Then post filters
|
|
1536
|
+
const postFilteredSearch = postSearchFilters?.length
|
|
1537
|
+
? postSearchFilters.reduce((prev, next) => next(prev), destSearch)
|
|
1538
|
+
: destSearch
|
|
1539
|
+
|
|
1540
|
+
const search = replaceEqualDeep(
|
|
1541
|
+
this.state.location.search,
|
|
1542
|
+
postFilteredSearch,
|
|
1543
|
+
)
|
|
1544
|
+
|
|
1545
|
+
const searchStr = this.options.stringifySearch(search)
|
|
1546
|
+
|
|
1547
|
+
const hash =
|
|
1548
|
+
dest.hash === true
|
|
1549
|
+
? this.state.location.hash
|
|
1550
|
+
: functionalUpdate(dest.hash!, this.state.location.hash)
|
|
1551
|
+
|
|
1552
|
+
const hashStr = hash ? `#${hash}` : ''
|
|
1553
|
+
|
|
1554
|
+
const nextState =
|
|
1555
|
+
dest.state === true
|
|
1556
|
+
? this.state.location.state
|
|
1557
|
+
: functionalUpdate(dest.state, this.state.location.state)!
|
|
1558
|
+
|
|
1559
|
+
return {
|
|
1560
|
+
pathname,
|
|
1561
|
+
search,
|
|
1562
|
+
searchStr,
|
|
1563
|
+
state: nextState,
|
|
1564
|
+
hash,
|
|
1565
|
+
href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
|
|
1566
|
+
key: dest.key,
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
#commitLocation = async (
|
|
1571
|
+
location: BuildNextOptions & { replace?: boolean },
|
|
1572
|
+
) => {
|
|
1573
|
+
const next = this.buildNext(location)
|
|
1574
|
+
const id = '' + Date.now() + Math.random()
|
|
1575
|
+
|
|
1576
|
+
if (this.navigateTimeout) clearTimeout(this.navigateTimeout)
|
|
1577
|
+
|
|
1578
|
+
let nextAction: 'push' | 'replace' = 'replace'
|
|
1579
|
+
|
|
1580
|
+
if (!location.replace) {
|
|
1581
|
+
nextAction = 'push'
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
const isSameUrl = this.state.location.href === next.href
|
|
1585
|
+
|
|
1586
|
+
if (isSameUrl && !next.key) {
|
|
1587
|
+
nextAction = 'replace'
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
const href = `${next.pathname}${next.searchStr}${
|
|
1591
|
+
next.hash ? `#${next.hash}` : ''
|
|
1592
|
+
}`
|
|
1593
|
+
|
|
1594
|
+
this.history[nextAction === 'push' ? 'push' : 'replace'](href, {
|
|
1595
|
+
id,
|
|
1596
|
+
...next.state,
|
|
1597
|
+
})
|
|
1598
|
+
|
|
1599
|
+
return this.latestLoadPromise
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
getRouteMatch = (
|
|
1603
|
+
id: string,
|
|
1604
|
+
): undefined | RouteMatch<TRouteTree, AnyRoute> => {
|
|
1605
|
+
return this.state.matchesById[id]
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
setRouteMatch = (
|
|
1609
|
+
id: string,
|
|
1610
|
+
updater: (
|
|
1611
|
+
prev: RouteMatch<TRouteTree, AnyRoute>,
|
|
1612
|
+
) => RouteMatch<TRouteTree, AnyRoute>,
|
|
1613
|
+
) => {
|
|
1614
|
+
this.__store.setState((prev) => {
|
|
1615
|
+
if (!prev.matchesById[id]) {
|
|
1616
|
+
console.warn(`No match found with id: ${id}`)
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
return {
|
|
1620
|
+
...prev,
|
|
1621
|
+
matchesById: {
|
|
1622
|
+
...prev.matchesById,
|
|
1623
|
+
[id]: updater(prev.matchesById[id] as any),
|
|
1624
|
+
},
|
|
1625
|
+
}
|
|
1626
|
+
})
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
setRouteMatchData = (
|
|
1630
|
+
id: string,
|
|
1631
|
+
updater: (prev: any) => any,
|
|
1632
|
+
opts?: {
|
|
1633
|
+
updatedAt?: number
|
|
1634
|
+
maxAge?: number
|
|
1289
1635
|
},
|
|
1636
|
+
) => {
|
|
1637
|
+
const match = this.getRouteMatch(id)
|
|
1638
|
+
|
|
1639
|
+
if (!match) return
|
|
1640
|
+
|
|
1641
|
+
const route = this.getRoute(match.routeId)
|
|
1642
|
+
const updatedAt = opts?.updatedAt ?? Date.now()
|
|
1643
|
+
|
|
1644
|
+
const preloadInvalidAt =
|
|
1645
|
+
updatedAt +
|
|
1646
|
+
(opts?.maxAge ??
|
|
1647
|
+
route.options.preloadMaxAge ??
|
|
1648
|
+
this.options.defaultPreloadMaxAge ??
|
|
1649
|
+
5000)
|
|
1650
|
+
|
|
1651
|
+
const invalidAt =
|
|
1652
|
+
updatedAt +
|
|
1653
|
+
(opts?.maxAge ??
|
|
1654
|
+
route.options.maxAge ??
|
|
1655
|
+
this.options.defaultMaxAge ??
|
|
1656
|
+
Infinity)
|
|
1657
|
+
|
|
1658
|
+
console.log('set success')
|
|
1659
|
+
this.setRouteMatch(id, (s) => ({
|
|
1660
|
+
...s,
|
|
1661
|
+
error: undefined,
|
|
1662
|
+
status: 'success',
|
|
1663
|
+
isFetching: false,
|
|
1664
|
+
updatedAt: Date.now(),
|
|
1665
|
+
loaderData: functionalUpdate(updater, s.loaderData),
|
|
1666
|
+
preloadInvalidAt,
|
|
1667
|
+
invalidAt,
|
|
1668
|
+
}))
|
|
1669
|
+
|
|
1670
|
+
if (this.state.matches.find((d) => d.id === id)) {
|
|
1671
|
+
}
|
|
1290
1672
|
}
|
|
1291
1673
|
|
|
1292
|
-
|
|
1674
|
+
invalidate = async (opts?: {
|
|
1675
|
+
matchId?: string
|
|
1676
|
+
reload?: boolean
|
|
1677
|
+
}): Promise<void> => {
|
|
1678
|
+
if (opts?.matchId) {
|
|
1679
|
+
this.setRouteMatch(opts.matchId, (s) => ({
|
|
1680
|
+
...s,
|
|
1681
|
+
invalid: true,
|
|
1682
|
+
}))
|
|
1683
|
+
const matchIndex = this.state.matches.findIndex(
|
|
1684
|
+
(d) => d.id === opts.matchId,
|
|
1685
|
+
)
|
|
1686
|
+
const childMatch = this.state.matches[matchIndex + 1]
|
|
1687
|
+
|
|
1688
|
+
if (childMatch) {
|
|
1689
|
+
return this.invalidate({ matchId: childMatch.id, reload: false })
|
|
1690
|
+
}
|
|
1691
|
+
} else {
|
|
1692
|
+
this.__store.batch(() => {
|
|
1693
|
+
Object.values(this.state.matchesById).forEach((match) => {
|
|
1694
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
1695
|
+
...s,
|
|
1696
|
+
invalid: true,
|
|
1697
|
+
}))
|
|
1698
|
+
})
|
|
1699
|
+
})
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
if (opts?.reload ?? true) {
|
|
1703
|
+
return this.reload()
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1293
1706
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1707
|
+
getIsInvalid = (opts?: { matchId: string; preload?: boolean }): boolean => {
|
|
1708
|
+
if (!opts?.matchId) {
|
|
1709
|
+
return !!this.state.matches.find((d) =>
|
|
1710
|
+
this.getIsInvalid({ matchId: d.id, preload: opts?.preload }),
|
|
1711
|
+
)
|
|
1712
|
+
}
|
|
1296
1713
|
|
|
1297
|
-
|
|
1714
|
+
const match = this.getRouteMatch(opts?.matchId)
|
|
1715
|
+
|
|
1716
|
+
if (!match) {
|
|
1717
|
+
return false
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
const now = Date.now()
|
|
1721
|
+
|
|
1722
|
+
return (
|
|
1723
|
+
match.invalid ||
|
|
1724
|
+
(opts?.preload ? match.preloadInvalidAt : match.invalidAt) < now
|
|
1725
|
+
)
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
// Detect if we're in the DOM
|
|
1730
|
+
const isServer = typeof window === 'undefined' || !window.document.createElement
|
|
1731
|
+
|
|
1732
|
+
function getInitialRouterState(): RouterState<any> {
|
|
1733
|
+
return {
|
|
1734
|
+
status: 'idle',
|
|
1735
|
+
isFetching: false,
|
|
1736
|
+
resolvedLocation: null!,
|
|
1737
|
+
location: null!,
|
|
1738
|
+
matchesById: {},
|
|
1739
|
+
matchIds: [],
|
|
1740
|
+
pendingMatchIds: [],
|
|
1741
|
+
matches: [],
|
|
1742
|
+
pendingMatches: [],
|
|
1743
|
+
lastUpdated: Date.now(),
|
|
1744
|
+
}
|
|
1298
1745
|
}
|
|
1299
1746
|
|
|
1300
1747
|
function isCtrlEvent(e: MouseEvent) {
|
|
1301
1748
|
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
|
|
1302
1749
|
}
|
|
1303
1750
|
|
|
1304
|
-
|
|
1305
|
-
matches.forEach((match, index) => {
|
|
1306
|
-
const parent = matches[index - 1]
|
|
1751
|
+
export type AnyRedirect = Redirect<any, any, any>
|
|
1307
1752
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1753
|
+
export type Redirect<
|
|
1754
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
1755
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
1756
|
+
TTo extends string = '',
|
|
1757
|
+
> = NavigateOptions<TRouteTree, TFrom, TTo> & {
|
|
1758
|
+
code?: number
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
export function redirect<
|
|
1762
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
1763
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
1764
|
+
TTo extends string = '',
|
|
1765
|
+
>(opts: Redirect<TRouteTree, TFrom, TTo>): Redirect<TRouteTree, TFrom, TTo> {
|
|
1766
|
+
;(opts as any).isRedirect = true
|
|
1767
|
+
return opts
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
export function isRedirect(obj: any): obj is AnyRedirect {
|
|
1771
|
+
return !!obj?.isRedirect
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
export class SearchParamError extends Error {}
|
|
1775
|
+
export class PathParamError extends Error {}
|
|
1776
|
+
|
|
1777
|
+
function escapeJSON(jsonString: string) {
|
|
1778
|
+
return jsonString
|
|
1779
|
+
.replace(/\\/g, '\\\\') // Escape backslashes
|
|
1780
|
+
.replace(/'/g, "\\'") // Escape single quotes
|
|
1781
|
+
.replace(/"/g, '\\"') // Escape double quotes
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// A function that takes an import() argument which is a function and returns a new function that will
|
|
1785
|
+
// proxy arguments from the caller to the imported function, retaining all type
|
|
1786
|
+
// information along the way
|
|
1787
|
+
export function lazyFn<
|
|
1788
|
+
T extends Record<string, (...args: any[]) => any>,
|
|
1789
|
+
TKey extends keyof T = 'default',
|
|
1790
|
+
>(fn: () => Promise<T>, key?: TKey) {
|
|
1791
|
+
return async (...args: Parameters<T[TKey]>): Promise<ReturnType<T[TKey]>> => {
|
|
1792
|
+
const imported = await fn()
|
|
1793
|
+
return imported[key || 'default'](...args)
|
|
1794
|
+
}
|
|
1315
1795
|
}
|