@tanstack/router-core 0.0.1-beta.5 → 0.0.1-beta.50
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/actions.js +94 -0
- package/build/cjs/actions.js.map +1 -0
- package/build/cjs/history.js +163 -0
- package/build/cjs/history.js.map +1 -0
- package/build/cjs/{packages/router-core/src/index.js → index.js} +26 -11
- package/build/cjs/{packages/router-core/src/index.js.map → index.js.map} +1 -1
- package/build/cjs/interop.js +175 -0
- package/build/cjs/interop.js.map +1 -0
- package/build/cjs/{packages/router-core/src/path.js → path.js} +23 -48
- package/build/cjs/path.js.map +1 -0
- package/build/cjs/{packages/router-core/src/qss.js → qss.js} +8 -13
- package/build/cjs/qss.js.map +1 -0
- package/build/cjs/route.js +33 -0
- package/build/cjs/route.js.map +1 -0
- package/build/cjs/{packages/router-core/src/routeConfig.js → routeConfig.js} +13 -18
- package/build/cjs/routeConfig.js.map +1 -0
- package/build/cjs/routeMatch.js +237 -0
- package/build/cjs/routeMatch.js.map +1 -0
- package/build/cjs/router.js +821 -0
- package/build/cjs/router.js.map +1 -0
- package/build/cjs/{packages/router-core/src/searchParams.js → searchParams.js} +10 -12
- package/build/cjs/searchParams.js.map +1 -0
- package/build/cjs/store.js +54 -0
- package/build/cjs/store.js.map +1 -0
- package/build/cjs/utils.js +47 -0
- package/build/cjs/utils.js.map +1 -0
- package/build/esm/index.js +1384 -2059
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +59 -49
- package/build/stats-react.json +248 -193
- package/build/types/index.d.ts +385 -317
- package/build/umd/index.development.js +1486 -2142
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +6 -4
- package/src/actions.ts +157 -0
- package/src/frameworks.ts +2 -2
- package/src/history.ts +199 -0
- package/src/index.ts +4 -7
- package/src/interop.ts +169 -0
- package/src/link.ts +87 -44
- package/src/path.ts +12 -8
- package/src/route.ts +36 -229
- package/src/routeConfig.ts +99 -102
- package/src/routeInfo.ts +28 -25
- package/src/routeMatch.ts +294 -322
- package/src/router.ts +1047 -884
- package/src/store.ts +52 -0
- package/src/utils.ts +14 -72
- 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/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 -161
- package/build/cjs/packages/router-core/src/route.js.map +0 -1
- 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 -797
- 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 +0 -118
- package/build/cjs/packages/router-core/src/utils.js.map +0 -1
package/src/router.ts
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BrowserHistory,
|
|
3
|
-
createBrowserHistory,
|
|
4
|
-
createMemoryHistory,
|
|
5
|
-
HashHistory,
|
|
6
|
-
History,
|
|
7
|
-
MemoryHistory,
|
|
8
|
-
} from 'history'
|
|
9
1
|
import invariant from 'tiny-invariant'
|
|
10
2
|
import { GetFrameworkGeneric } from './frameworks'
|
|
11
3
|
|
|
12
4
|
import {
|
|
13
5
|
LinkInfo,
|
|
14
6
|
LinkOptions,
|
|
15
|
-
|
|
7
|
+
NavigateOptions,
|
|
16
8
|
ToOptions,
|
|
17
9
|
ValidFromPath,
|
|
10
|
+
ResolveRelativePath,
|
|
18
11
|
} from './link'
|
|
19
12
|
import {
|
|
20
13
|
cleanPath,
|
|
@@ -22,8 +15,9 @@ import {
|
|
|
22
15
|
joinPaths,
|
|
23
16
|
matchPathname,
|
|
24
17
|
resolvePath,
|
|
18
|
+
trimPath,
|
|
25
19
|
} from './path'
|
|
26
|
-
import { AnyRoute,
|
|
20
|
+
import { AnyRoute, Route } from './route'
|
|
27
21
|
import {
|
|
28
22
|
AnyLoaderData,
|
|
29
23
|
AnyPathParams,
|
|
@@ -36,25 +30,50 @@ import {
|
|
|
36
30
|
import {
|
|
37
31
|
AllRouteInfo,
|
|
38
32
|
AnyAllRouteInfo,
|
|
39
|
-
AnyRouteInfo,
|
|
40
33
|
RouteInfo,
|
|
41
34
|
RoutesById,
|
|
42
35
|
} from './routeInfo'
|
|
43
|
-
import {
|
|
36
|
+
import { RouteMatch, RouteMatchStore } from './routeMatch'
|
|
44
37
|
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
38
|
+
import { createStore, batch, Store } from './store'
|
|
45
39
|
import {
|
|
46
40
|
functionalUpdate,
|
|
47
41
|
last,
|
|
42
|
+
NoInfer,
|
|
43
|
+
pick,
|
|
48
44
|
PickAsRequired,
|
|
49
45
|
PickRequired,
|
|
50
|
-
replaceEqualDeep,
|
|
51
46
|
Timeout,
|
|
52
47
|
Updater,
|
|
53
48
|
} from './utils'
|
|
49
|
+
import { replaceEqualDeep } from './interop'
|
|
50
|
+
import {
|
|
51
|
+
createBrowserHistory,
|
|
52
|
+
createMemoryHistory,
|
|
53
|
+
RouterHistory,
|
|
54
|
+
} from './history'
|
|
55
|
+
|
|
56
|
+
export interface RegisterRouter {
|
|
57
|
+
// router: Router
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type AnyRouter = Router<any, any, any>
|
|
61
|
+
|
|
62
|
+
export type RegisteredRouter = RegisterRouter extends {
|
|
63
|
+
router: Router<infer TRouteConfig, infer TAllRouteInfo, infer TRouterContext>
|
|
64
|
+
}
|
|
65
|
+
? Router<TRouteConfig, TAllRouteInfo, TRouterContext>
|
|
66
|
+
: Router
|
|
67
|
+
|
|
68
|
+
export type RegisteredAllRouteInfo = RegisterRouter extends {
|
|
69
|
+
router: Router<infer TRouteConfig, infer TAllRouteInfo, infer TRouterContext>
|
|
70
|
+
}
|
|
71
|
+
? TAllRouteInfo
|
|
72
|
+
: AnyAllRouteInfo
|
|
54
73
|
|
|
55
74
|
export interface LocationState {}
|
|
56
75
|
|
|
57
|
-
export interface
|
|
76
|
+
export interface ParsedLocation<
|
|
58
77
|
TSearchObj extends AnySearchSchema = {},
|
|
59
78
|
TState extends LocationState = LocationState,
|
|
60
79
|
> {
|
|
@@ -80,8 +99,11 @@ export type FilterRoutesFn = <TRoute extends Route<any, RouteInfo>>(
|
|
|
80
99
|
routeConfigs: TRoute[],
|
|
81
100
|
) => TRoute[]
|
|
82
101
|
|
|
83
|
-
export interface RouterOptions<
|
|
84
|
-
|
|
102
|
+
export interface RouterOptions<
|
|
103
|
+
TRouteConfig extends AnyRouteConfig,
|
|
104
|
+
TRouterContext,
|
|
105
|
+
> {
|
|
106
|
+
history?: RouterHistory
|
|
85
107
|
stringifySearch?: SearchSerializer
|
|
86
108
|
parseSearch?: SearchParser
|
|
87
109
|
filterRoutes?: FilterRoutesFn
|
|
@@ -89,47 +111,29 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
|
|
|
89
111
|
defaultPreloadMaxAge?: number
|
|
90
112
|
defaultPreloadGcMaxAge?: number
|
|
91
113
|
defaultPreloadDelay?: number
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
defaultCatchElement?: GetFrameworkGeneric<'Element'>
|
|
96
|
-
defaultPendingElement?: GetFrameworkGeneric<'Element'>
|
|
97
|
-
defaultPendingMs?: number
|
|
98
|
-
defaultPendingMinMs?: number
|
|
114
|
+
defaultComponent?: GetFrameworkGeneric<'Component'>
|
|
115
|
+
defaultErrorComponent?: GetFrameworkGeneric<'ErrorComponent'>
|
|
116
|
+
defaultPendingComponent?: GetFrameworkGeneric<'Component'>
|
|
99
117
|
defaultLoaderMaxAge?: number
|
|
100
118
|
defaultLoaderGcMaxAge?: number
|
|
101
119
|
caseSensitive?: boolean
|
|
102
120
|
routeConfig?: TRouteConfig
|
|
103
121
|
basepath?: string
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
useServerData?: boolean
|
|
123
|
+
createRouter?: (router: AnyRouter) => void
|
|
124
|
+
createRoute?: (opts: { route: AnyRoute; router: AnyRouter }) => void
|
|
125
|
+
context?: TRouterContext
|
|
126
|
+
loadComponent?: (
|
|
127
|
+
component: GetFrameworkGeneric<'Component'>,
|
|
128
|
+
) => Promise<GetFrameworkGeneric<'Component'>>
|
|
129
|
+
onRouteChange?: () => void
|
|
130
|
+
fetchServerDataFn?: FetchServerDataFn
|
|
109
131
|
}
|
|
110
132
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
> {
|
|
116
|
-
submit: (submission?: TPayload) => Promise<TResponse>
|
|
117
|
-
current?: ActionState<TPayload, TResponse>
|
|
118
|
-
latest?: ActionState<TPayload, TResponse>
|
|
119
|
-
pending: ActionState<TPayload, TResponse>[]
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export interface ActionState<
|
|
123
|
-
TPayload = unknown,
|
|
124
|
-
TResponse = unknown,
|
|
125
|
-
// TError = unknown,
|
|
126
|
-
> {
|
|
127
|
-
submittedAt: number
|
|
128
|
-
status: 'idle' | 'pending' | 'success' | 'error'
|
|
129
|
-
submission: TPayload
|
|
130
|
-
data?: TResponse
|
|
131
|
-
error?: unknown
|
|
132
|
-
}
|
|
133
|
+
type FetchServerDataFn = (ctx: {
|
|
134
|
+
router: AnyRouter
|
|
135
|
+
routeMatch: RouteMatch
|
|
136
|
+
}) => Promise<any>
|
|
133
137
|
|
|
134
138
|
export interface Loader<
|
|
135
139
|
TFullSearchSchema extends AnySearchSchema = {},
|
|
@@ -160,41 +164,38 @@ export interface Loader<
|
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
export interface LoaderState<
|
|
163
|
-
TFullSearchSchema =
|
|
164
|
-
TAllParams =
|
|
167
|
+
TFullSearchSchema extends AnySearchSchema = {},
|
|
168
|
+
TAllParams extends AnyPathParams = {},
|
|
165
169
|
> {
|
|
166
170
|
loadedAt: number
|
|
167
171
|
loaderContext: LoaderContext<TFullSearchSchema, TAllParams>
|
|
168
172
|
}
|
|
169
173
|
|
|
170
|
-
export interface
|
|
174
|
+
export interface RouterStore<
|
|
175
|
+
TSearchObj extends AnySearchSchema = {},
|
|
176
|
+
TState extends LocationState = LocationState,
|
|
177
|
+
> {
|
|
171
178
|
status: 'idle' | 'loading'
|
|
172
|
-
|
|
173
|
-
|
|
179
|
+
latestLocation: ParsedLocation<TSearchObj, TState>
|
|
180
|
+
currentMatches: RouteMatch[]
|
|
181
|
+
currentLocation: ParsedLocation<TSearchObj, TState>
|
|
182
|
+
pendingMatches?: RouteMatch[]
|
|
183
|
+
pendingLocation?: ParsedLocation<TSearchObj, TState>
|
|
174
184
|
lastUpdated: number
|
|
175
|
-
currentAction?: ActionState
|
|
176
|
-
latestAction?: ActionState
|
|
177
|
-
actions: Record<string, Action>
|
|
178
185
|
loaders: Record<string, Loader>
|
|
179
|
-
pending?: PendingState
|
|
180
186
|
isFetching: boolean
|
|
181
187
|
isPreloading: boolean
|
|
188
|
+
matchCache: Record<string, MatchCacheEntry>
|
|
182
189
|
}
|
|
183
190
|
|
|
184
|
-
export interface PendingState {
|
|
185
|
-
location: Location
|
|
186
|
-
matches: RouteMatch[]
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
type Listener = (router: Router<any, any>) => void
|
|
190
|
-
|
|
191
191
|
export type ListenerFn = () => void
|
|
192
192
|
|
|
193
193
|
export interface BuildNextOptions {
|
|
194
194
|
to?: string | number | null
|
|
195
|
-
params?: true | Updater<
|
|
195
|
+
params?: true | Updater<unknown>
|
|
196
196
|
search?: true | Updater<unknown>
|
|
197
197
|
hash?: true | Updater<string>
|
|
198
|
+
state?: LocationState
|
|
198
199
|
key?: string
|
|
199
200
|
from?: string
|
|
200
201
|
fromCurrent?: boolean
|
|
@@ -216,397 +217,354 @@ export interface MatchLocation {
|
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
export interface MatchRouteOptions {
|
|
219
|
-
pending
|
|
220
|
+
pending?: boolean
|
|
220
221
|
caseSensitive?: boolean
|
|
222
|
+
fuzzy?: boolean
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
type LinkCurrentTargetElement = {
|
|
224
226
|
preloadTimeout?: null | ReturnType<typeof setTimeout>
|
|
225
227
|
}
|
|
226
228
|
|
|
227
|
-
export interface
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
229
|
+
export interface DehydratedRouterState
|
|
230
|
+
extends Pick<
|
|
231
|
+
RouterStore,
|
|
232
|
+
'status' | 'latestLocation' | 'currentLocation' | 'lastUpdated'
|
|
233
|
+
> {
|
|
234
|
+
currentMatches: DehydratedRouteMatch[]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface DehydratedRouter<TRouterContext = unknown> {
|
|
238
|
+
// location: Router['__location']
|
|
239
|
+
state: DehydratedRouterState
|
|
240
|
+
context: TRouterContext
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export type MatchCache = Record<string, MatchCacheEntry>
|
|
244
|
+
|
|
245
|
+
interface DehydratedRouteMatch {
|
|
246
|
+
matchId: string
|
|
247
|
+
state: Pick<
|
|
248
|
+
RouteMatchStore<any, any>,
|
|
249
|
+
'status' | 'routeLoaderData' | 'invalid' | 'invalidAt'
|
|
235
250
|
>
|
|
236
|
-
// Computed in this.update()
|
|
237
|
-
basepath: string
|
|
238
|
-
// Internal:
|
|
239
|
-
allRouteInfo: TAllRouteInfo
|
|
240
|
-
listeners: Listener[]
|
|
241
|
-
location: Location
|
|
242
|
-
navigateTimeout?: Timeout
|
|
243
|
-
nextAction?: 'push' | 'replace'
|
|
244
|
-
state: RouterState
|
|
245
|
-
routeTree: Route<TAllRouteInfo, RouteInfo>
|
|
246
|
-
routesById: RoutesById<TAllRouteInfo>
|
|
247
|
-
navigationPromise: Promise<void>
|
|
248
|
-
removeActionQueue: { action: Action; actionState: ActionState }[]
|
|
249
|
-
startedLoadingAt: number
|
|
250
|
-
resolveNavigation: () => void
|
|
251
|
-
subscribe: (listener: Listener) => () => void
|
|
252
|
-
notify: () => void
|
|
253
|
-
mount: () => () => void
|
|
254
|
-
onFocus: () => void
|
|
255
|
-
update: <TRouteConfig extends RouteConfig = RouteConfig>(
|
|
256
|
-
opts?: RouterOptions<TRouteConfig>,
|
|
257
|
-
) => Router<TRouteConfig>
|
|
258
|
-
|
|
259
|
-
buildNext: (opts: BuildNextOptions) => Location
|
|
260
|
-
cancelMatches: () => void
|
|
261
|
-
loadLocation: (next?: Location) => Promise<void>
|
|
262
|
-
matchCache: Record<string, MatchCacheEntry>
|
|
263
|
-
cleanMatchCache: () => void
|
|
264
|
-
getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
265
|
-
id: TId,
|
|
266
|
-
) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
267
|
-
loadRoute: (navigateOpts: BuildNextOptions) => Promise<RouteMatch[]>
|
|
268
|
-
preloadRoute: (
|
|
269
|
-
navigateOpts: BuildNextOptions,
|
|
270
|
-
loaderOpts: { maxAge?: number; gcMaxAge?: number },
|
|
271
|
-
) => Promise<RouteMatch[]>
|
|
272
|
-
matchRoutes: (
|
|
273
|
-
pathname: string,
|
|
274
|
-
opts?: { strictParseParams?: boolean },
|
|
275
|
-
) => RouteMatch[]
|
|
276
|
-
loadMatches: (
|
|
277
|
-
resolvedMatches: RouteMatch[],
|
|
278
|
-
loaderOpts?: { withPending?: boolean } & (
|
|
279
|
-
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
280
|
-
| { preload?: false; maxAge?: never; gcMaxAge?: never }
|
|
281
|
-
),
|
|
282
|
-
) => Promise<void>
|
|
283
|
-
invalidateRoute: (opts: MatchLocation) => void
|
|
284
|
-
reload: () => Promise<void>
|
|
285
|
-
resolvePath: (from: string, path: string) => string
|
|
286
|
-
navigate: <
|
|
287
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
288
|
-
TTo extends string = '.',
|
|
289
|
-
>(
|
|
290
|
-
opts: NavigateOptionsAbsolute<TAllRouteInfo, TFrom, TTo>,
|
|
291
|
-
) => Promise<void>
|
|
292
|
-
matchRoute: <
|
|
293
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
294
|
-
TTo extends string = '.',
|
|
295
|
-
>(
|
|
296
|
-
matchLocation: ToOptions<TAllRouteInfo, TFrom, TTo>,
|
|
297
|
-
opts?: MatchRouteOptions,
|
|
298
|
-
) => boolean
|
|
299
|
-
buildLink: <
|
|
300
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
301
|
-
TTo extends string = '.',
|
|
302
|
-
>(
|
|
303
|
-
opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
|
|
304
|
-
) => LinkInfo
|
|
305
|
-
__: {
|
|
306
|
-
buildRouteTree: (
|
|
307
|
-
routeConfig: RouteConfig,
|
|
308
|
-
) => Route<TAllRouteInfo, AnyRouteInfo>
|
|
309
|
-
parseLocation: (
|
|
310
|
-
location: History['location'],
|
|
311
|
-
previousLocation?: Location,
|
|
312
|
-
) => Location
|
|
313
|
-
buildLocation: (dest: BuildNextOptions) => Location
|
|
314
|
-
commitLocation: (next: Location, replace?: boolean) => Promise<void>
|
|
315
|
-
navigate: (
|
|
316
|
-
location: BuildNextOptions & { replace?: boolean },
|
|
317
|
-
) => Promise<void>
|
|
318
|
-
}
|
|
319
251
|
}
|
|
320
252
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
253
|
+
export interface RouterContext {}
|
|
254
|
+
|
|
255
|
+
export const defaultFetchServerDataFn: FetchServerDataFn = async ({
|
|
256
|
+
router,
|
|
257
|
+
routeMatch,
|
|
258
|
+
}) => {
|
|
259
|
+
const next = router.buildNext({
|
|
260
|
+
to: '.',
|
|
261
|
+
search: (d: any) => ({
|
|
262
|
+
...(d ?? {}),
|
|
263
|
+
__data: {
|
|
264
|
+
matchId: routeMatch.id,
|
|
265
|
+
},
|
|
266
|
+
}),
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const res = await fetch(next.href, {
|
|
270
|
+
method: 'GET',
|
|
271
|
+
signal: routeMatch.abortController.signal,
|
|
272
|
+
})
|
|
324
273
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
274
|
+
if (res.ok) {
|
|
275
|
+
return res.json()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
throw new Error('Failed to fetch match data')
|
|
279
|
+
}
|
|
328
280
|
|
|
329
|
-
export
|
|
281
|
+
export class Router<
|
|
330
282
|
TRouteConfig extends AnyRouteConfig = RouteConfig,
|
|
331
283
|
TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
339
|
-
defaultLoaderMaxAge: 0,
|
|
340
|
-
defaultPreloadMaxAge: 2000,
|
|
341
|
-
defaultPreloadDelay: 50,
|
|
342
|
-
...userOptions,
|
|
343
|
-
stringifySearch: userOptions?.stringifySearch ?? defaultStringifySearch,
|
|
344
|
-
parseSearch: userOptions?.parseSearch ?? defaultParseSearch,
|
|
284
|
+
TRouterContext = unknown,
|
|
285
|
+
> {
|
|
286
|
+
types!: {
|
|
287
|
+
// Super secret internal stuff
|
|
288
|
+
RouteConfig: TRouteConfig
|
|
289
|
+
AllRouteInfo: TAllRouteInfo
|
|
345
290
|
}
|
|
346
291
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
router.state.status === 'loading' ||
|
|
387
|
-
router.state.matches.some((d) => d.isFetching),
|
|
388
|
-
isPreloading: Object.values(router.matchCache).some(
|
|
389
|
-
(d) =>
|
|
390
|
-
d.match.isFetching &&
|
|
391
|
-
!router.state.matches.find((dd) => dd.matchId === d.match.matchId),
|
|
392
|
-
),
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
cascadeLoaderData(router.state.matches)
|
|
396
|
-
router.listeners.forEach((listener) => listener(router))
|
|
397
|
-
},
|
|
292
|
+
options: PickAsRequired<
|
|
293
|
+
RouterOptions<TRouteConfig, TRouterContext>,
|
|
294
|
+
'stringifySearch' | 'parseSearch' | 'context'
|
|
295
|
+
>
|
|
296
|
+
history: RouterHistory
|
|
297
|
+
basepath: string
|
|
298
|
+
// __location: Location<TAllRouteInfo['fullSearchSchema']>
|
|
299
|
+
routeTree!: Route<TAllRouteInfo, RouteInfo>
|
|
300
|
+
routesById!: RoutesById<TAllRouteInfo>
|
|
301
|
+
navigateTimeout: undefined | Timeout
|
|
302
|
+
nextAction: undefined | 'push' | 'replace'
|
|
303
|
+
navigationPromise: undefined | Promise<void>
|
|
304
|
+
|
|
305
|
+
store: Store<RouterStore<TAllRouteInfo['fullSearchSchema']>>
|
|
306
|
+
startedLoadingAt = Date.now()
|
|
307
|
+
resolveNavigation = () => {}
|
|
308
|
+
|
|
309
|
+
constructor(options?: RouterOptions<TRouteConfig, TRouterContext>) {
|
|
310
|
+
this.options = {
|
|
311
|
+
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
312
|
+
defaultLoaderMaxAge: 0,
|
|
313
|
+
defaultPreloadMaxAge: 2000,
|
|
314
|
+
defaultPreloadDelay: 50,
|
|
315
|
+
context: undefined!,
|
|
316
|
+
...options,
|
|
317
|
+
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
318
|
+
parseSearch: options?.parseSearch ?? defaultParseSearch,
|
|
319
|
+
fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.history = this.options?.history ?? createBrowserHistory()
|
|
323
|
+
this.store = createStore(getInitialRouterState())
|
|
324
|
+
this.basepath = ''
|
|
325
|
+
|
|
326
|
+
this.update(options)
|
|
327
|
+
|
|
328
|
+
// Allow frameworks to hook into the router creation
|
|
329
|
+
this.options.createRouter?.(this)
|
|
330
|
+
}
|
|
398
331
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
search: true,
|
|
403
|
-
hash: true,
|
|
404
|
-
})
|
|
332
|
+
reset = () => {
|
|
333
|
+
this.store.setState((s) => Object.assign(s, getInitialRouterState()))
|
|
334
|
+
}
|
|
405
335
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
336
|
+
mount = () => {
|
|
337
|
+
// Mount only does anything on the client
|
|
338
|
+
if (!isServer) {
|
|
339
|
+
// If the router matches are empty, load the matches
|
|
340
|
+
if (!this.store.state.currentMatches.length) {
|
|
341
|
+
this.load()
|
|
410
342
|
}
|
|
411
343
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const unsub = router.history.listen((event) => {
|
|
415
|
-
console.log(event.location)
|
|
416
|
-
router.loadLocation(
|
|
417
|
-
router.__.parseLocation(event.location, router.location),
|
|
418
|
-
)
|
|
344
|
+
const unsubHistory = this.history.listen(() => {
|
|
345
|
+
this.load(this.#parseLocation(this.store.state.latestLocation))
|
|
419
346
|
})
|
|
420
347
|
|
|
348
|
+
const visibilityChangeEvent = 'visibilitychange'
|
|
349
|
+
const focusEvent = 'focus'
|
|
350
|
+
|
|
421
351
|
// addEventListener does not exist in React Native, but window does
|
|
352
|
+
// In the future, we might need to invert control here for more adapters
|
|
422
353
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
423
|
-
if (
|
|
424
|
-
// Listen to
|
|
425
|
-
window.addEventListener(
|
|
426
|
-
window.addEventListener(
|
|
354
|
+
if (window.addEventListener) {
|
|
355
|
+
// Listen to visibilitychange and focus
|
|
356
|
+
window.addEventListener(visibilityChangeEvent, this.#onFocus, false)
|
|
357
|
+
window.addEventListener(focusEvent, this.#onFocus, false)
|
|
427
358
|
}
|
|
428
359
|
|
|
429
360
|
return () => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
window.removeEventListener('focus', router.onFocus)
|
|
434
|
-
}
|
|
435
|
-
},
|
|
361
|
+
unsubHistory()
|
|
362
|
+
if (window.removeEventListener) {
|
|
363
|
+
// Be sure to unsubscribe if a new handler is set
|
|
436
364
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
},
|
|
440
|
-
|
|
441
|
-
update: (opts) => {
|
|
442
|
-
const newHistory = opts?.history !== router.history
|
|
443
|
-
if (!router.location || newHistory) {
|
|
444
|
-
if (opts?.history) {
|
|
445
|
-
router.history = opts.history
|
|
365
|
+
window.removeEventListener(visibilityChangeEvent, this.#onFocus)
|
|
366
|
+
window.removeEventListener(focusEvent, this.#onFocus)
|
|
446
367
|
}
|
|
447
|
-
router.location = router.__.parseLocation(router.history.location)
|
|
448
|
-
router.state.location = router.location
|
|
449
368
|
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return () => {}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
update = <
|
|
375
|
+
TRouteConfig extends RouteConfig = RouteConfig,
|
|
376
|
+
TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
|
|
377
|
+
TRouterContext = unknown,
|
|
378
|
+
>(
|
|
379
|
+
opts?: RouterOptions<TRouteConfig, TRouterContext>,
|
|
380
|
+
): Router<TRouteConfig, TAllRouteInfo, TRouterContext> => {
|
|
381
|
+
if (!this.store.state.latestLocation) {
|
|
382
|
+
this.store.setState((s) => {
|
|
383
|
+
s.latestLocation = this.#parseLocation()
|
|
384
|
+
s.currentLocation = s.latestLocation
|
|
385
|
+
})
|
|
386
|
+
}
|
|
450
387
|
|
|
451
|
-
|
|
388
|
+
Object.assign(this.options, opts)
|
|
452
389
|
|
|
453
|
-
|
|
390
|
+
const { basepath, routeConfig } = this.options
|
|
454
391
|
|
|
455
|
-
|
|
392
|
+
this.basepath = `/${trimPath(basepath ?? '') ?? ''}`
|
|
456
393
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
394
|
+
if (routeConfig) {
|
|
395
|
+
this.routesById = {} as any
|
|
396
|
+
this.routeTree = this.#buildRouteTree(routeConfig)
|
|
397
|
+
}
|
|
461
398
|
|
|
462
|
-
|
|
463
|
-
|
|
399
|
+
return this as any
|
|
400
|
+
}
|
|
464
401
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
402
|
+
buildNext = (opts: BuildNextOptions) => {
|
|
403
|
+
const next = this.#buildLocation(opts)
|
|
404
|
+
|
|
405
|
+
const matches = this.matchRoutes(next.pathname)
|
|
406
|
+
|
|
407
|
+
const __preSearchFilters = matches
|
|
408
|
+
.map((match) => match.route.options.preSearchFilters ?? [])
|
|
409
|
+
.flat()
|
|
410
|
+
.filter(Boolean)
|
|
411
|
+
|
|
412
|
+
const __postSearchFilters = matches
|
|
413
|
+
.map((match) => match.route.options.postSearchFilters ?? [])
|
|
414
|
+
.flat()
|
|
415
|
+
.filter(Boolean)
|
|
416
|
+
|
|
417
|
+
return this.#buildLocation({
|
|
418
|
+
...opts,
|
|
419
|
+
__preSearchFilters,
|
|
420
|
+
__postSearchFilters,
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
cancelMatches = () => {
|
|
425
|
+
;[
|
|
426
|
+
...this.store.state.currentMatches,
|
|
427
|
+
...(this.store.state.pendingMatches || []),
|
|
428
|
+
].forEach((match) => {
|
|
429
|
+
match.cancel()
|
|
430
|
+
})
|
|
431
|
+
}
|
|
473
432
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
433
|
+
load = async (next?: ParsedLocation) => {
|
|
434
|
+
let now = Date.now()
|
|
435
|
+
const startedAt = now
|
|
436
|
+
this.startedLoadingAt = startedAt
|
|
477
437
|
|
|
438
|
+
// Cancel any pending matches
|
|
439
|
+
this.cancelMatches()
|
|
440
|
+
|
|
441
|
+
let matches!: RouteMatch<any, any>[]
|
|
442
|
+
|
|
443
|
+
batch(() => {
|
|
478
444
|
if (next) {
|
|
479
445
|
// Ingest the new location
|
|
480
|
-
|
|
446
|
+
this.store.setState((s) => {
|
|
447
|
+
s.latestLocation = next
|
|
448
|
+
})
|
|
481
449
|
}
|
|
482
450
|
|
|
483
|
-
// Clear out old actions
|
|
484
|
-
router.removeActionQueue.forEach(({ action, actionState }) => {
|
|
485
|
-
if (router.state.currentAction === actionState) {
|
|
486
|
-
router.state.currentAction = undefined
|
|
487
|
-
}
|
|
488
|
-
if (action.current === actionState) {
|
|
489
|
-
action.current = undefined
|
|
490
|
-
}
|
|
491
|
-
})
|
|
492
|
-
router.removeActionQueue = []
|
|
493
|
-
|
|
494
|
-
// Cancel any pending matches
|
|
495
|
-
router.cancelMatches()
|
|
496
|
-
|
|
497
451
|
// Match the routes
|
|
498
|
-
|
|
452
|
+
matches = this.matchRoutes(this.store.state.latestLocation.pathname, {
|
|
499
453
|
strictParseParams: true,
|
|
500
454
|
})
|
|
501
455
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
status: 'loading',
|
|
509
|
-
}
|
|
456
|
+
this.store.setState((s) => {
|
|
457
|
+
s.status = 'loading'
|
|
458
|
+
s.pendingMatches = matches
|
|
459
|
+
s.pendingLocation = this.store.state.latestLocation
|
|
460
|
+
})
|
|
461
|
+
})
|
|
510
462
|
|
|
511
|
-
|
|
463
|
+
// Load the matches
|
|
464
|
+
try {
|
|
465
|
+
await this.loadMatches(matches)
|
|
466
|
+
} catch (err: any) {
|
|
467
|
+
console.warn(err)
|
|
468
|
+
invariant(
|
|
469
|
+
false,
|
|
470
|
+
'Matches failed to load due to error above ☝️. Navigation cancelled!',
|
|
471
|
+
)
|
|
472
|
+
}
|
|
512
473
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
474
|
+
if (this.startedLoadingAt !== startedAt) {
|
|
475
|
+
// Ignore side-effects of outdated side-effects
|
|
476
|
+
return this.navigationPromise
|
|
477
|
+
}
|
|
517
478
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
479
|
+
const previousMatches = this.store.state.currentMatches
|
|
480
|
+
|
|
481
|
+
const exiting: RouteMatch[] = [],
|
|
482
|
+
staying: RouteMatch[] = []
|
|
483
|
+
|
|
484
|
+
previousMatches.forEach((d) => {
|
|
485
|
+
if (matches.find((dd) => dd.id === d.id)) {
|
|
486
|
+
staying.push(d)
|
|
487
|
+
} else {
|
|
488
|
+
exiting.push(d)
|
|
521
489
|
}
|
|
490
|
+
})
|
|
522
491
|
|
|
523
|
-
|
|
492
|
+
const entering = matches.filter((d) => {
|
|
493
|
+
return !previousMatches.find((dd) => dd.id === d.id)
|
|
494
|
+
})
|
|
524
495
|
|
|
525
|
-
|
|
526
|
-
staying: RouteMatch[] = []
|
|
496
|
+
now = Date.now()
|
|
527
497
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
exiting.push(d)
|
|
533
|
-
}
|
|
498
|
+
exiting.forEach((d) => {
|
|
499
|
+
d.__onExit?.({
|
|
500
|
+
params: d.params,
|
|
501
|
+
search: d.store.state.routeSearch,
|
|
534
502
|
})
|
|
535
503
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
search: d.routeSearch,
|
|
504
|
+
// Clear non-loading error states when match leaves
|
|
505
|
+
if (d.store.state.status === 'error' && !d.store.state.isFetching) {
|
|
506
|
+
d.store.setState((s) => {
|
|
507
|
+
s.status = 'idle'
|
|
508
|
+
s.error = undefined
|
|
542
509
|
})
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const gc = Math.max(
|
|
513
|
+
d.route.options.loaderGcMaxAge ??
|
|
514
|
+
this.options.defaultLoaderGcMaxAge ??
|
|
515
|
+
0,
|
|
516
|
+
d.route.options.loaderMaxAge ?? this.options.defaultLoaderMaxAge ?? 0,
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
if (gc > 0) {
|
|
520
|
+
this.store.setState((s) => {
|
|
521
|
+
s.matchCache[d.id] = {
|
|
554
522
|
gc: gc == Infinity ? Number.MAX_SAFE_INTEGER : now + gc,
|
|
555
523
|
match: d,
|
|
556
524
|
}
|
|
557
|
-
}
|
|
558
|
-
})
|
|
559
|
-
|
|
560
|
-
staying.forEach((d) => {
|
|
561
|
-
d.options.onTransition?.({
|
|
562
|
-
params: d.params,
|
|
563
|
-
search: d.routeSearch,
|
|
564
525
|
})
|
|
565
|
-
}
|
|
526
|
+
}
|
|
527
|
+
})
|
|
566
528
|
|
|
567
|
-
|
|
568
|
-
|
|
529
|
+
staying.forEach((d) => {
|
|
530
|
+
d.route.options.onTransition?.({
|
|
531
|
+
params: d.params,
|
|
532
|
+
search: d.store.state.routeSearch,
|
|
569
533
|
})
|
|
534
|
+
})
|
|
570
535
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
})
|
|
576
|
-
delete router.matchCache[d.matchId]
|
|
536
|
+
entering.forEach((d) => {
|
|
537
|
+
d.__onExit = d.route.options.onLoaded?.({
|
|
538
|
+
params: d.params,
|
|
539
|
+
search: d.store.state.search,
|
|
577
540
|
})
|
|
541
|
+
delete this.store.state.matchCache[d.id]
|
|
542
|
+
})
|
|
578
543
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
await Promise.all(
|
|
582
|
-
matches.map((d) => d.__.loaderPromise || Promise.resolve()),
|
|
583
|
-
)
|
|
584
|
-
}
|
|
585
|
-
if (router.startedLoadingAt !== id) {
|
|
586
|
-
// Ignore side-effects of match loading
|
|
587
|
-
return
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
router.state = {
|
|
591
|
-
...router.state,
|
|
592
|
-
location: router.location,
|
|
593
|
-
matches,
|
|
594
|
-
pending: undefined,
|
|
544
|
+
this.store.setState((s) => {
|
|
545
|
+
Object.assign(s, {
|
|
595
546
|
status: 'idle',
|
|
596
|
-
|
|
547
|
+
currentLocation: this.store.state.latestLocation,
|
|
548
|
+
currentMatches: matches,
|
|
549
|
+
pendingLocation: undefined,
|
|
550
|
+
pendingMatches: undefined,
|
|
551
|
+
})
|
|
552
|
+
})
|
|
597
553
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
554
|
+
this.options.onRouteChange?.()
|
|
555
|
+
|
|
556
|
+
this.resolveNavigation()
|
|
557
|
+
}
|
|
601
558
|
|
|
602
|
-
|
|
603
|
-
|
|
559
|
+
cleanMatchCache = () => {
|
|
560
|
+
const now = Date.now()
|
|
604
561
|
|
|
605
|
-
|
|
606
|
-
|
|
562
|
+
this.store.setState((s) => {
|
|
563
|
+
Object.keys(s.matchCache).forEach((matchId) => {
|
|
564
|
+
const entry = s.matchCache[matchId]!
|
|
607
565
|
|
|
608
566
|
// Don't remove loading matches
|
|
609
|
-
if (entry.match.status === 'loading') {
|
|
567
|
+
if (entry.match.store.state.status === 'loading') {
|
|
610
568
|
return
|
|
611
569
|
}
|
|
612
570
|
|
|
@@ -616,617 +574,822 @@ export function createRouter<
|
|
|
616
574
|
}
|
|
617
575
|
|
|
618
576
|
// Everything else gets removed
|
|
619
|
-
delete
|
|
577
|
+
delete s.matchCache[matchId]
|
|
620
578
|
})
|
|
621
|
-
}
|
|
579
|
+
})
|
|
580
|
+
}
|
|
622
581
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
})
|
|
628
|
-
await router.loadMatches(matches)
|
|
629
|
-
return matches
|
|
630
|
-
},
|
|
582
|
+
getRoute = <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
583
|
+
id: TId,
|
|
584
|
+
): Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]> => {
|
|
585
|
+
const route = this.routesById[id]
|
|
631
586
|
|
|
632
|
-
|
|
633
|
-
const next = router.buildNext(navigateOpts)
|
|
634
|
-
const matches = router.matchRoutes(next.pathname, {
|
|
635
|
-
strictParseParams: true,
|
|
636
|
-
})
|
|
637
|
-
await router.loadMatches(matches, {
|
|
638
|
-
preload: true,
|
|
639
|
-
maxAge:
|
|
640
|
-
loaderOpts.maxAge ??
|
|
641
|
-
router.options.defaultPreloadMaxAge ??
|
|
642
|
-
router.options.defaultLoaderMaxAge ??
|
|
643
|
-
0,
|
|
644
|
-
gcMaxAge:
|
|
645
|
-
loaderOpts.gcMaxAge ??
|
|
646
|
-
router.options.defaultPreloadGcMaxAge ??
|
|
647
|
-
router.options.defaultLoaderGcMaxAge ??
|
|
648
|
-
0,
|
|
649
|
-
})
|
|
650
|
-
return matches
|
|
651
|
-
},
|
|
587
|
+
invariant(route, `Route with id "${id as string}" not found`)
|
|
652
588
|
|
|
653
|
-
|
|
654
|
-
|
|
589
|
+
return route
|
|
590
|
+
}
|
|
655
591
|
|
|
656
|
-
|
|
592
|
+
loadRoute = async (
|
|
593
|
+
navigateOpts: BuildNextOptions = this.store.state.latestLocation,
|
|
594
|
+
): Promise<RouteMatch[]> => {
|
|
595
|
+
const next = this.buildNext(navigateOpts)
|
|
596
|
+
const matches = this.matchRoutes(next.pathname, {
|
|
597
|
+
strictParseParams: true,
|
|
598
|
+
})
|
|
599
|
+
await this.loadMatches(matches)
|
|
600
|
+
return matches
|
|
601
|
+
}
|
|
657
602
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
603
|
+
preloadRoute = async (
|
|
604
|
+
navigateOpts: BuildNextOptions = this.store.state.latestLocation,
|
|
605
|
+
loaderOpts: { maxAge?: number; gcMaxAge?: number },
|
|
606
|
+
) => {
|
|
607
|
+
const next = this.buildNext(navigateOpts)
|
|
608
|
+
const matches = this.matchRoutes(next.pathname, {
|
|
609
|
+
strictParseParams: true,
|
|
610
|
+
})
|
|
611
|
+
|
|
612
|
+
await this.loadMatches(matches, {
|
|
613
|
+
preload: true,
|
|
614
|
+
maxAge:
|
|
615
|
+
loaderOpts.maxAge ??
|
|
616
|
+
this.options.defaultPreloadMaxAge ??
|
|
617
|
+
this.options.defaultLoaderMaxAge ??
|
|
618
|
+
0,
|
|
619
|
+
gcMaxAge:
|
|
620
|
+
loaderOpts.gcMaxAge ??
|
|
621
|
+
this.options.defaultPreloadGcMaxAge ??
|
|
622
|
+
this.options.defaultLoaderGcMaxAge ??
|
|
623
|
+
0,
|
|
624
|
+
})
|
|
625
|
+
return matches
|
|
626
|
+
}
|
|
661
627
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
...(router.state.pending?.matches ?? []),
|
|
665
|
-
]
|
|
628
|
+
matchRoutes = (pathname: string, opts?: { strictParseParams?: boolean }) => {
|
|
629
|
+
const matches: RouteMatch[] = []
|
|
666
630
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
631
|
+
if (!this.routeTree) {
|
|
632
|
+
return matches
|
|
633
|
+
}
|
|
670
634
|
|
|
671
|
-
|
|
635
|
+
const existingMatches = [
|
|
636
|
+
...this.store.state.currentMatches,
|
|
637
|
+
...(this.store.state.pendingMatches ?? []),
|
|
638
|
+
]
|
|
672
639
|
|
|
673
|
-
|
|
640
|
+
const recurse = async (routes: Route<any, any>[]): Promise<void> => {
|
|
641
|
+
const parentMatch = last(matches)
|
|
642
|
+
let params = parentMatch?.params ?? {}
|
|
674
643
|
|
|
675
|
-
|
|
676
|
-
routes.some((route) => {
|
|
677
|
-
if (!route.routePath && route.childRoutes?.length) {
|
|
678
|
-
return findMatchInRoutes(
|
|
679
|
-
[...foundRoutes, route],
|
|
680
|
-
route.childRoutes,
|
|
681
|
-
)
|
|
682
|
-
}
|
|
644
|
+
const filteredRoutes = this.options.filterRoutes?.(routes) ?? routes
|
|
683
645
|
|
|
684
|
-
|
|
685
|
-
route.routePath !== '/' || route.childRoutes?.length
|
|
686
|
-
)
|
|
646
|
+
let foundRoutes: Route[] = []
|
|
687
647
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
})
|
|
648
|
+
const findMatchInRoutes = (parentRoutes: Route[], routes: Route[]) => {
|
|
649
|
+
routes.some((route) => {
|
|
650
|
+
if (!route.path && route.childRoutes?.length) {
|
|
651
|
+
return findMatchInRoutes([...foundRoutes, route], route.childRoutes)
|
|
652
|
+
}
|
|
694
653
|
|
|
695
|
-
|
|
696
|
-
let parsedParams
|
|
654
|
+
const fuzzy = !!(route.path !== '/' || route.childRoutes?.length)
|
|
697
655
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
656
|
+
const matchParams = matchPathname(this.basepath, pathname, {
|
|
657
|
+
to: route.fullPath,
|
|
658
|
+
fuzzy,
|
|
659
|
+
caseSensitive:
|
|
660
|
+
route.options.caseSensitive ?? this.options.caseSensitive,
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
if (matchParams) {
|
|
664
|
+
let parsedParams
|
|
706
665
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
666
|
+
try {
|
|
667
|
+
parsedParams =
|
|
668
|
+
route.options.parseParams?.(matchParams!) ?? matchParams
|
|
669
|
+
} catch (err) {
|
|
670
|
+
if (opts?.strictParseParams) {
|
|
671
|
+
throw err
|
|
710
672
|
}
|
|
711
673
|
}
|
|
712
674
|
|
|
713
|
-
|
|
714
|
-
|
|
675
|
+
params = {
|
|
676
|
+
...params,
|
|
677
|
+
...parsedParams,
|
|
715
678
|
}
|
|
679
|
+
}
|
|
716
680
|
|
|
717
|
-
|
|
718
|
-
|
|
681
|
+
if (!!matchParams) {
|
|
682
|
+
foundRoutes = [...parentRoutes, route]
|
|
683
|
+
}
|
|
719
684
|
|
|
720
685
|
return !!foundRoutes.length
|
|
721
|
-
}
|
|
686
|
+
})
|
|
722
687
|
|
|
723
|
-
|
|
688
|
+
return !!foundRoutes.length
|
|
689
|
+
}
|
|
724
690
|
|
|
725
|
-
|
|
726
|
-
return
|
|
727
|
-
}
|
|
691
|
+
findMatchInRoutes([], filteredRoutes)
|
|
728
692
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
693
|
+
if (!foundRoutes.length) {
|
|
694
|
+
return
|
|
695
|
+
}
|
|
732
696
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
697
|
+
foundRoutes.forEach((foundRoute) => {
|
|
698
|
+
const interpolatedPath = interpolatePath(foundRoute.path, params)
|
|
699
|
+
const matchId = interpolatePath(foundRoute.id, params, true)
|
|
700
|
+
|
|
701
|
+
const match =
|
|
702
|
+
existingMatches.find((d) => d.id === matchId) ||
|
|
703
|
+
this.store.state.matchCache[matchId]?.match ||
|
|
704
|
+
new RouteMatch(this, foundRoute, {
|
|
705
|
+
matchId,
|
|
706
|
+
params,
|
|
707
|
+
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
708
|
+
})
|
|
741
709
|
|
|
742
|
-
|
|
743
|
-
|
|
710
|
+
matches.push(match)
|
|
711
|
+
})
|
|
744
712
|
|
|
745
|
-
|
|
713
|
+
const foundRoute = last(foundRoutes)!
|
|
746
714
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
715
|
+
if (foundRoute.childRoutes?.length) {
|
|
716
|
+
recurse(foundRoute.childRoutes)
|
|
750
717
|
}
|
|
718
|
+
}
|
|
751
719
|
|
|
752
|
-
|
|
720
|
+
recurse([this.routeTree])
|
|
753
721
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
return matches
|
|
757
|
-
},
|
|
722
|
+
linkMatches(matches)
|
|
758
723
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
// Validate the match (loads search params etc)
|
|
762
|
-
match.__.validate()
|
|
763
|
-
match.load(loaderOpts)
|
|
724
|
+
return matches
|
|
725
|
+
}
|
|
764
726
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
727
|
+
loadMatches = async (
|
|
728
|
+
resolvedMatches: RouteMatch[],
|
|
729
|
+
loaderOpts?:
|
|
730
|
+
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
731
|
+
| { preload?: false; maxAge?: never; gcMaxAge?: never },
|
|
732
|
+
) => {
|
|
733
|
+
this.cleanMatchCache()
|
|
734
|
+
resolvedMatches.forEach(async (match) => {
|
|
735
|
+
// Validate the match (loads search params etc)
|
|
736
|
+
match.__validate()
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
// Check each match middleware to see if the route can be accessed
|
|
740
|
+
await Promise.all(
|
|
741
|
+
resolvedMatches.map(async (match) => {
|
|
742
|
+
try {
|
|
743
|
+
await match.route.options.beforeLoad?.({
|
|
744
|
+
router: this as any,
|
|
745
|
+
match,
|
|
746
|
+
})
|
|
747
|
+
} catch (err) {
|
|
748
|
+
if (!loaderOpts?.preload) {
|
|
749
|
+
match.route.options.onLoadError?.(err)
|
|
750
|
+
}
|
|
768
751
|
|
|
769
|
-
|
|
770
|
-
// This might be completion, error, or a pending state
|
|
771
|
-
await match.__.loadPromise
|
|
752
|
+
throw err
|
|
772
753
|
}
|
|
773
|
-
})
|
|
754
|
+
}),
|
|
755
|
+
)
|
|
774
756
|
|
|
775
|
-
|
|
757
|
+
const matchPromises = resolvedMatches.map(async (match, index) => {
|
|
758
|
+
const prevMatch = resolvedMatches[(index = 1)]
|
|
759
|
+
const search = match.store.state.search as { __data?: any }
|
|
776
760
|
|
|
777
|
-
|
|
778
|
-
|
|
761
|
+
if (search.__data?.matchId && search.__data.matchId !== match.id) {
|
|
762
|
+
return
|
|
763
|
+
}
|
|
779
764
|
|
|
780
|
-
|
|
781
|
-
const next = router.buildNext(opts)
|
|
782
|
-
const unloadedMatchIds = router
|
|
783
|
-
.matchRoutes(next.pathname)
|
|
784
|
-
.map((d) => d.matchId)
|
|
785
|
-
;[
|
|
786
|
-
...router.state.matches,
|
|
787
|
-
...(router.state.pending?.matches ?? []),
|
|
788
|
-
].forEach((match) => {
|
|
789
|
-
if (unloadedMatchIds.includes(match.matchId)) {
|
|
790
|
-
match.invalidate()
|
|
791
|
-
}
|
|
792
|
-
})
|
|
793
|
-
},
|
|
765
|
+
match.load(loaderOpts)
|
|
794
766
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
search: true,
|
|
800
|
-
}),
|
|
767
|
+
if (match.store.state.status !== 'success' && match.__loadPromise) {
|
|
768
|
+
// Wait for the first sign of activity from the match
|
|
769
|
+
await match.__loadPromise
|
|
770
|
+
}
|
|
801
771
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
772
|
+
if (prevMatch) {
|
|
773
|
+
await prevMatch.__loadPromise
|
|
774
|
+
}
|
|
775
|
+
})
|
|
805
776
|
|
|
806
|
-
|
|
807
|
-
|
|
777
|
+
await Promise.all(matchPromises)
|
|
778
|
+
}
|
|
808
779
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
780
|
+
loadMatchData = async (
|
|
781
|
+
routeMatch: RouteMatch<any, any>,
|
|
782
|
+
): Promise<Record<string, unknown>> => {
|
|
783
|
+
if (isServer || !this.options.useServerData) {
|
|
784
|
+
return (
|
|
785
|
+
(await routeMatch.route.options.loader?.({
|
|
786
|
+
// parentLoaderPromise: routeMatch.parentMatch.dataPromise,
|
|
787
|
+
params: routeMatch.params,
|
|
788
|
+
search: routeMatch.store.state.routeSearch,
|
|
789
|
+
signal: routeMatch.abortController.signal,
|
|
790
|
+
})) || {}
|
|
791
|
+
)
|
|
792
|
+
} else {
|
|
793
|
+
// Refresh:
|
|
794
|
+
// '/dashboard'
|
|
795
|
+
// '/dashboard/invoices/'
|
|
796
|
+
// '/dashboard/invoices/123'
|
|
797
|
+
|
|
798
|
+
// New:
|
|
799
|
+
// '/dashboard/invoices/456'
|
|
815
800
|
|
|
816
|
-
|
|
801
|
+
// TODO: batch requests when possible
|
|
817
802
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
803
|
+
return this.options.fetchServerDataFn!({ router: this, routeMatch })
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
invalidateRoute = async (opts: MatchLocation) => {
|
|
808
|
+
const next = this.buildNext(opts)
|
|
809
|
+
const unloadedMatchIds = this.matchRoutes(next.pathname).map((d) => d.id)
|
|
810
|
+
|
|
811
|
+
await Promise.allSettled(
|
|
812
|
+
[
|
|
813
|
+
...this.store.state.currentMatches,
|
|
814
|
+
...(this.store.state.pendingMatches ?? []),
|
|
815
|
+
].map(async (match) => {
|
|
816
|
+
if (unloadedMatchIds.includes(match.id)) {
|
|
817
|
+
return match.invalidate()
|
|
821
818
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
})
|
|
826
|
-
}
|
|
819
|
+
}),
|
|
820
|
+
)
|
|
821
|
+
}
|
|
827
822
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
823
|
+
reload = () => {
|
|
824
|
+
this.navigate({
|
|
825
|
+
fromCurrent: true,
|
|
826
|
+
replace: true,
|
|
827
|
+
search: true,
|
|
828
|
+
} as any)
|
|
829
|
+
}
|
|
833
830
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
831
|
+
resolvePath = (from: string, path: string) => {
|
|
832
|
+
return resolvePath(this.basepath!, from, cleanPath(path))
|
|
833
|
+
}
|
|
837
834
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
835
|
+
navigate = async <
|
|
836
|
+
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
837
|
+
TTo extends string = '.',
|
|
838
|
+
>({
|
|
839
|
+
from,
|
|
840
|
+
to = '.' as any,
|
|
841
|
+
search,
|
|
842
|
+
hash,
|
|
843
|
+
replace,
|
|
844
|
+
params,
|
|
845
|
+
}: NavigateOptions<TAllRouteInfo, TFrom, TTo>) => {
|
|
846
|
+
// If this link simply reloads the current route,
|
|
847
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
848
|
+
|
|
849
|
+
// If this `to` is a valid external URL, return
|
|
850
|
+
// null for LinkUtils
|
|
851
|
+
const toString = String(to)
|
|
852
|
+
const fromString = String(from)
|
|
853
|
+
|
|
854
|
+
let isExternal
|
|
855
|
+
|
|
856
|
+
try {
|
|
857
|
+
new URL(`${toString}`)
|
|
858
|
+
isExternal = true
|
|
859
|
+
} catch (e) {}
|
|
860
|
+
|
|
861
|
+
invariant(
|
|
862
|
+
!isExternal,
|
|
863
|
+
'Attempting to navigate to external url with this.navigate!',
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
return this.#commitLocation({
|
|
867
|
+
from: fromString,
|
|
868
|
+
to: toString,
|
|
869
|
+
search,
|
|
870
|
+
hash,
|
|
871
|
+
replace,
|
|
872
|
+
params,
|
|
873
|
+
})
|
|
874
|
+
}
|
|
842
875
|
|
|
843
|
-
|
|
876
|
+
matchRoute = <
|
|
877
|
+
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
878
|
+
TTo extends string = '.',
|
|
879
|
+
>(
|
|
880
|
+
location: ToOptions<TAllRouteInfo, TFrom, TTo>,
|
|
881
|
+
opts?: MatchRouteOptions,
|
|
882
|
+
):
|
|
883
|
+
| false
|
|
884
|
+
| TAllRouteInfo['routeInfoById'][ResolveRelativePath<
|
|
885
|
+
TFrom,
|
|
886
|
+
NoInfer<TTo>
|
|
887
|
+
>]['allParams'] => {
|
|
888
|
+
location = {
|
|
889
|
+
...location,
|
|
890
|
+
to: location.to
|
|
891
|
+
? this.resolvePath(location.from ?? '', location.to)
|
|
892
|
+
: undefined,
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const next = this.buildNext(location)
|
|
896
|
+
|
|
897
|
+
if (opts?.pending) {
|
|
898
|
+
if (!this.store.state.pendingLocation) {
|
|
899
|
+
return false
|
|
900
|
+
}
|
|
844
901
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
902
|
+
return matchPathname(
|
|
903
|
+
this.basepath,
|
|
904
|
+
this.store.state.pendingLocation!.pathname,
|
|
905
|
+
{
|
|
906
|
+
...opts,
|
|
907
|
+
to: next.pathname,
|
|
908
|
+
},
|
|
909
|
+
) as any
|
|
910
|
+
}
|
|
849
911
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
912
|
+
return matchPathname(
|
|
913
|
+
this.basepath,
|
|
914
|
+
this.store.state.currentLocation.pathname,
|
|
915
|
+
{
|
|
916
|
+
...opts,
|
|
917
|
+
to: next.pathname,
|
|
918
|
+
},
|
|
919
|
+
) as any
|
|
920
|
+
}
|
|
854
921
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
922
|
+
buildLink = <
|
|
923
|
+
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
924
|
+
TTo extends string = '.',
|
|
925
|
+
>({
|
|
926
|
+
from,
|
|
927
|
+
to = '.' as any,
|
|
928
|
+
search,
|
|
929
|
+
params,
|
|
930
|
+
hash,
|
|
931
|
+
target,
|
|
932
|
+
replace,
|
|
933
|
+
activeOptions,
|
|
934
|
+
preload,
|
|
935
|
+
preloadMaxAge: userPreloadMaxAge,
|
|
936
|
+
preloadGcMaxAge: userPreloadGcMaxAge,
|
|
937
|
+
preloadDelay: userPreloadDelay,
|
|
938
|
+
disabled,
|
|
939
|
+
}: LinkOptions<TAllRouteInfo, TFrom, TTo>): LinkInfo => {
|
|
940
|
+
// If this link simply reloads the current route,
|
|
941
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
942
|
+
|
|
943
|
+
// If this `to` is a valid external URL, return
|
|
944
|
+
// null for LinkUtils
|
|
945
|
+
|
|
946
|
+
try {
|
|
947
|
+
new URL(`${to}`)
|
|
948
|
+
return {
|
|
949
|
+
type: 'external',
|
|
950
|
+
href: to,
|
|
951
|
+
}
|
|
952
|
+
} catch (e) {}
|
|
864
953
|
|
|
865
|
-
|
|
954
|
+
const nextOpts = {
|
|
866
955
|
from,
|
|
867
|
-
to
|
|
956
|
+
to,
|
|
868
957
|
search,
|
|
869
958
|
params,
|
|
870
959
|
hash,
|
|
871
|
-
target,
|
|
872
960
|
replace,
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const next = this.buildNext(nextOpts)
|
|
964
|
+
|
|
965
|
+
preload = preload ?? this.options.defaultPreload
|
|
966
|
+
const preloadDelay =
|
|
967
|
+
userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0
|
|
968
|
+
|
|
969
|
+
// Compare path/hash for matches
|
|
970
|
+
const pathIsEqual =
|
|
971
|
+
this.store.state.currentLocation.pathname === next.pathname
|
|
972
|
+
const currentPathSplit =
|
|
973
|
+
this.store.state.currentLocation.pathname.split('/')
|
|
974
|
+
const nextPathSplit = next.pathname.split('/')
|
|
975
|
+
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
976
|
+
(d, i) => d === currentPathSplit[i],
|
|
977
|
+
)
|
|
978
|
+
const hashIsEqual = this.store.state.currentLocation.hash === next.hash
|
|
979
|
+
// Combine the matches based on user options
|
|
980
|
+
const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual
|
|
981
|
+
const hashTest = activeOptions?.includeHash ? hashIsEqual : true
|
|
982
|
+
|
|
983
|
+
// The final "active" test
|
|
984
|
+
const isActive = pathTest && hashTest
|
|
985
|
+
|
|
986
|
+
// The click handler
|
|
987
|
+
const handleClick = (e: MouseEvent) => {
|
|
988
|
+
if (
|
|
989
|
+
!disabled &&
|
|
990
|
+
!isCtrlEvent(e) &&
|
|
991
|
+
!e.defaultPrevented &&
|
|
992
|
+
(!target || target === '_self') &&
|
|
993
|
+
e.button === 0
|
|
994
|
+
) {
|
|
995
|
+
e.preventDefault()
|
|
996
|
+
if (pathIsEqual && !search && !hash) {
|
|
997
|
+
this.invalidateRoute(nextOpts)
|
|
891
998
|
}
|
|
892
|
-
} catch (e) {}
|
|
893
|
-
|
|
894
|
-
const nextOpts = {
|
|
895
|
-
from,
|
|
896
|
-
to,
|
|
897
|
-
search,
|
|
898
|
-
params,
|
|
899
|
-
hash,
|
|
900
|
-
replace,
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
const next = router.buildNext(nextOpts)
|
|
904
999
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1000
|
+
// All is well? Navigate!
|
|
1001
|
+
this.#commitLocation(nextOpts as any)
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// The click handler
|
|
1006
|
+
const handleFocus = (e: MouseEvent) => {
|
|
1007
|
+
if (preload) {
|
|
1008
|
+
this.preloadRoute(nextOpts, {
|
|
1009
|
+
maxAge: userPreloadMaxAge,
|
|
1010
|
+
gcMaxAge: userPreloadGcMaxAge,
|
|
1011
|
+
}).catch((err) => {
|
|
1012
|
+
console.warn(err)
|
|
1013
|
+
console.warn('Error preloading route! ☝️')
|
|
1014
|
+
})
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
908
1017
|
|
|
909
|
-
|
|
910
|
-
const
|
|
911
|
-
const currentPathSplit = router.state.location.pathname.split('/')
|
|
912
|
-
const nextPathSplit = next.pathname.split('/')
|
|
913
|
-
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
914
|
-
(d, i) => d === currentPathSplit[i],
|
|
915
|
-
)
|
|
916
|
-
const hashIsEqual = router.state.location.hash === next.hash
|
|
917
|
-
// Combine the matches based on user options
|
|
918
|
-
const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual
|
|
919
|
-
const hashTest = activeOptions?.includeHash ? hashIsEqual : true
|
|
920
|
-
|
|
921
|
-
// The final "active" test
|
|
922
|
-
const isActive = pathTest && hashTest
|
|
923
|
-
|
|
924
|
-
// The click handler
|
|
925
|
-
const handleClick = (e: MouseEvent) => {
|
|
926
|
-
if (
|
|
927
|
-
!disabled &&
|
|
928
|
-
!isCtrlEvent(e) &&
|
|
929
|
-
!e.defaultPrevented &&
|
|
930
|
-
(!target || target === '_self') &&
|
|
931
|
-
e.button === 0
|
|
932
|
-
) {
|
|
933
|
-
e.preventDefault()
|
|
934
|
-
if (pathIsEqual && !search && !hash) {
|
|
935
|
-
router.invalidateRoute(nextOpts)
|
|
936
|
-
}
|
|
1018
|
+
const handleEnter = (e: MouseEvent) => {
|
|
1019
|
+
const target = (e.target || {}) as LinkCurrentTargetElement
|
|
937
1020
|
|
|
938
|
-
|
|
939
|
-
|
|
1021
|
+
if (preload) {
|
|
1022
|
+
if (target.preloadTimeout) {
|
|
1023
|
+
return
|
|
940
1024
|
}
|
|
941
|
-
}
|
|
942
1025
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
router.preloadRoute(nextOpts, {
|
|
1026
|
+
target.preloadTimeout = setTimeout(() => {
|
|
1027
|
+
target.preloadTimeout = null
|
|
1028
|
+
this.preloadRoute(nextOpts, {
|
|
947
1029
|
maxAge: userPreloadMaxAge,
|
|
948
1030
|
gcMaxAge: userPreloadGcMaxAge,
|
|
1031
|
+
}).catch((err) => {
|
|
1032
|
+
console.warn(err)
|
|
1033
|
+
console.warn('Error preloading route! ☝️')
|
|
949
1034
|
})
|
|
950
|
-
}
|
|
1035
|
+
}, preloadDelay)
|
|
951
1036
|
}
|
|
1037
|
+
}
|
|
952
1038
|
|
|
953
|
-
|
|
954
|
-
|
|
1039
|
+
const handleLeave = (e: MouseEvent) => {
|
|
1040
|
+
const target = (e.target || {}) as LinkCurrentTargetElement
|
|
955
1041
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
target.preloadTimeout = setTimeout(() => {
|
|
962
|
-
target.preloadTimeout = null
|
|
963
|
-
router.preloadRoute(nextOpts, {
|
|
964
|
-
maxAge: userPreloadMaxAge,
|
|
965
|
-
gcMaxAge: userPreloadGcMaxAge,
|
|
966
|
-
})
|
|
967
|
-
}, preloadDelay)
|
|
968
|
-
}
|
|
1042
|
+
if (target.preloadTimeout) {
|
|
1043
|
+
clearTimeout(target.preloadTimeout)
|
|
1044
|
+
target.preloadTimeout = null
|
|
969
1045
|
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
return {
|
|
1049
|
+
type: 'internal',
|
|
1050
|
+
next,
|
|
1051
|
+
handleFocus,
|
|
1052
|
+
handleClick,
|
|
1053
|
+
handleEnter,
|
|
1054
|
+
handleLeave,
|
|
1055
|
+
isActive,
|
|
1056
|
+
disabled,
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
970
1059
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1060
|
+
dehydrate = (): DehydratedRouter<TRouterContext> => {
|
|
1061
|
+
return {
|
|
1062
|
+
state: {
|
|
1063
|
+
...pick(this.store.state, [
|
|
1064
|
+
'latestLocation',
|
|
1065
|
+
'currentLocation',
|
|
1066
|
+
'status',
|
|
1067
|
+
'lastUpdated',
|
|
1068
|
+
]),
|
|
1069
|
+
currentMatches: this.store.state.currentMatches.map((match) => ({
|
|
1070
|
+
matchId: match.id,
|
|
1071
|
+
state: {
|
|
1072
|
+
...pick(match.store.state, [
|
|
1073
|
+
'status',
|
|
1074
|
+
'routeLoaderData',
|
|
1075
|
+
'invalidAt',
|
|
1076
|
+
'invalid',
|
|
1077
|
+
]),
|
|
1078
|
+
},
|
|
1079
|
+
})),
|
|
1080
|
+
},
|
|
1081
|
+
context: this.options.context as TRouterContext,
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
979
1084
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
handleClick,
|
|
985
|
-
handleEnter,
|
|
986
|
-
handleLeave,
|
|
987
|
-
isActive,
|
|
988
|
-
disabled,
|
|
989
|
-
}
|
|
990
|
-
},
|
|
991
|
-
buildNext: (opts: BuildNextOptions) => {
|
|
992
|
-
const next = router.__.buildLocation(opts)
|
|
1085
|
+
hydrate = (dehydratedRouter: DehydratedRouter<TRouterContext>) => {
|
|
1086
|
+
this.store.setState((s) => {
|
|
1087
|
+
// Update the context TODO: make this part of state?
|
|
1088
|
+
this.options.context = dehydratedRouter.context
|
|
993
1089
|
|
|
994
|
-
|
|
1090
|
+
// Match the routes
|
|
1091
|
+
const currentMatches = this.matchRoutes(
|
|
1092
|
+
dehydratedRouter.state.latestLocation.pathname,
|
|
1093
|
+
{
|
|
1094
|
+
strictParseParams: true,
|
|
1095
|
+
},
|
|
1096
|
+
)
|
|
995
1097
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1098
|
+
currentMatches.forEach((match, index) => {
|
|
1099
|
+
const dehydratedMatch = dehydratedRouter.state.currentMatches[index]
|
|
1100
|
+
invariant(
|
|
1101
|
+
dehydratedMatch && dehydratedMatch.matchId === match.id,
|
|
1102
|
+
'Oh no! There was a hydration mismatch when attempting to rethis.store the state of the router! 😬',
|
|
1103
|
+
)
|
|
1104
|
+
Object.assign(match, dehydratedMatch)
|
|
1105
|
+
})
|
|
1000
1106
|
|
|
1001
|
-
|
|
1002
|
-
.map((match) => match.options.postSearchFilters ?? [])
|
|
1003
|
-
.flat()
|
|
1004
|
-
.filter(Boolean)
|
|
1107
|
+
currentMatches.forEach((match) => match.__validate())
|
|
1005
1108
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
__postSearchFilters,
|
|
1010
|
-
})
|
|
1011
|
-
},
|
|
1109
|
+
Object.assign(s, { ...dehydratedRouter.state, currentMatches })
|
|
1110
|
+
})
|
|
1111
|
+
}
|
|
1012
1112
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1113
|
+
getLoader = <TFrom extends keyof TAllRouteInfo['routeInfoById'] = '/'>(opts: {
|
|
1114
|
+
from: TFrom
|
|
1115
|
+
}): unknown extends TAllRouteInfo['routeInfoById'][TFrom]['routeLoaderData']
|
|
1116
|
+
?
|
|
1117
|
+
| Loader<
|
|
1118
|
+
LoaderContext<
|
|
1119
|
+
TAllRouteInfo['routeInfoById'][TFrom]['fullSearchSchema'],
|
|
1120
|
+
TAllRouteInfo['routeInfoById'][TFrom]['allParams']
|
|
1121
|
+
>,
|
|
1122
|
+
TAllRouteInfo['routeInfoById'][TFrom]['routeLoaderData']
|
|
1123
|
+
>
|
|
1124
|
+
| undefined
|
|
1125
|
+
: Loader<
|
|
1126
|
+
TAllRouteInfo['routeInfoById'][TFrom]['fullSearchSchema'],
|
|
1127
|
+
TAllRouteInfo['routeInfoById'][TFrom]['allParams'],
|
|
1128
|
+
TAllRouteInfo['routeInfoById'][TFrom]['routeLoaderData']
|
|
1129
|
+
> => {
|
|
1130
|
+
const id = opts.from || ('/' as any)
|
|
1131
|
+
|
|
1132
|
+
const route = this.getRoute(id)
|
|
1133
|
+
|
|
1134
|
+
if (!route) return undefined as any
|
|
1135
|
+
|
|
1136
|
+
let loader =
|
|
1137
|
+
this.store.state.loaders[id] ||
|
|
1138
|
+
(() => {
|
|
1139
|
+
this.store.setState((s) => {
|
|
1140
|
+
s.loaders[id] = {
|
|
1141
|
+
pending: [],
|
|
1142
|
+
fetch: (async (loaderContext: LoaderContext<any, any>) => {
|
|
1143
|
+
if (!route) {
|
|
1144
|
+
return
|
|
1037
1145
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1146
|
+
const loaderState: LoaderState<any, any> = {
|
|
1147
|
+
loadedAt: Date.now(),
|
|
1148
|
+
loaderContext,
|
|
1149
|
+
}
|
|
1150
|
+
this.store.setState((s) => {
|
|
1151
|
+
s.loaders[id]!.current = loaderState
|
|
1152
|
+
s.loaders[id]!.latest = loaderState
|
|
1153
|
+
s.loaders[id]!.pending.push(loaderState)
|
|
1154
|
+
})
|
|
1155
|
+
try {
|
|
1156
|
+
return await route.options.loader?.(loaderContext)
|
|
1157
|
+
} finally {
|
|
1158
|
+
this.store.setState((s) => {
|
|
1159
|
+
s.loaders[id]!.pending = s.loaders[id]!.pending.filter(
|
|
1160
|
+
(d) => d !== loaderState,
|
|
1161
|
+
)
|
|
1162
|
+
})
|
|
1163
|
+
}
|
|
1164
|
+
}) as any,
|
|
1165
|
+
}
|
|
1166
|
+
})
|
|
1167
|
+
return this.store.state.loaders[id]!
|
|
1168
|
+
})()
|
|
1040
1169
|
|
|
1041
|
-
|
|
1170
|
+
return loader as any
|
|
1171
|
+
}
|
|
1042
1172
|
|
|
1043
|
-
|
|
1173
|
+
#buildRouteTree = (rootRouteConfig: RouteConfig) => {
|
|
1174
|
+
const recurseRoutes = (
|
|
1175
|
+
routeConfigs: RouteConfig[],
|
|
1176
|
+
parent?: Route<TAllRouteInfo, any, any>,
|
|
1177
|
+
): Route<TAllRouteInfo, any, any>[] => {
|
|
1178
|
+
return routeConfigs.map((routeConfig, i) => {
|
|
1179
|
+
const routeOptions = routeConfig.options
|
|
1180
|
+
const route = new Route(routeConfig, routeOptions, i, parent, this)
|
|
1181
|
+
const existingRoute = (this.routesById as any)[route.id]
|
|
1182
|
+
|
|
1183
|
+
if (existingRoute) {
|
|
1184
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1185
|
+
console.warn(
|
|
1186
|
+
`Duplicate routes found with id: ${String(route.id)}`,
|
|
1187
|
+
this.routesById,
|
|
1188
|
+
route,
|
|
1189
|
+
)
|
|
1190
|
+
}
|
|
1191
|
+
throw new Error()
|
|
1192
|
+
}
|
|
1044
1193
|
|
|
1045
|
-
|
|
1046
|
-
? recurseRoutes(children, route)
|
|
1047
|
-
: undefined
|
|
1194
|
+
;(this.routesById as any)[route.id] = route
|
|
1048
1195
|
|
|
1049
|
-
|
|
1050
|
-
})
|
|
1051
|
-
}
|
|
1196
|
+
const children = routeConfig.children as RouteConfig[]
|
|
1052
1197
|
|
|
1053
|
-
|
|
1198
|
+
route.childRoutes = children.length
|
|
1199
|
+
? recurseRoutes(children, route)
|
|
1200
|
+
: undefined
|
|
1054
1201
|
|
|
1055
|
-
return
|
|
1056
|
-
}
|
|
1202
|
+
return route
|
|
1203
|
+
})
|
|
1204
|
+
}
|
|
1057
1205
|
|
|
1058
|
-
|
|
1059
|
-
location: History['location'],
|
|
1060
|
-
previousLocation?: Location,
|
|
1061
|
-
): Location => {
|
|
1062
|
-
const parsedSearch = router.options.parseSearch(location.search)
|
|
1063
|
-
|
|
1064
|
-
return {
|
|
1065
|
-
pathname: location.pathname,
|
|
1066
|
-
searchStr: location.search,
|
|
1067
|
-
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1068
|
-
hash: location.hash.split('#').reverse()[0] ?? '',
|
|
1069
|
-
href: `${location.pathname}${location.search}${location.hash}`,
|
|
1070
|
-
state: location.state as LocationState,
|
|
1071
|
-
key: location.key,
|
|
1072
|
-
}
|
|
1073
|
-
},
|
|
1206
|
+
const routes = recurseRoutes([rootRouteConfig])
|
|
1074
1207
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
return router.__.commitLocation(next, location.replace)
|
|
1078
|
-
},
|
|
1208
|
+
return routes[0]!
|
|
1209
|
+
}
|
|
1079
1210
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
// ...router.location,
|
|
1083
|
-
const fromPathname = dest.fromCurrent
|
|
1084
|
-
? router.location.pathname
|
|
1085
|
-
: dest.from ?? router.location.pathname
|
|
1086
|
-
|
|
1087
|
-
let pathname = resolvePath(
|
|
1088
|
-
router.basepath ?? '/',
|
|
1089
|
-
fromPathname,
|
|
1090
|
-
`${dest.to ?? '.'}`,
|
|
1091
|
-
)
|
|
1211
|
+
#parseLocation = (previousLocation?: ParsedLocation): ParsedLocation => {
|
|
1212
|
+
let { pathname, search, hash, state } = this.history.location
|
|
1092
1213
|
|
|
1093
|
-
|
|
1094
|
-
strictParseParams: true,
|
|
1095
|
-
})
|
|
1214
|
+
const parsedSearch = this.options.parseSearch(search)
|
|
1096
1215
|
|
|
1097
|
-
|
|
1216
|
+
return {
|
|
1217
|
+
pathname: pathname,
|
|
1218
|
+
searchStr: search,
|
|
1219
|
+
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1220
|
+
hash: hash.split('#').reverse()[0] ?? '',
|
|
1221
|
+
href: `${pathname}${search}${hash}`,
|
|
1222
|
+
state: state as LocationState,
|
|
1223
|
+
key: state?.key || '__init__',
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1098
1226
|
|
|
1099
|
-
|
|
1227
|
+
#onFocus = () => {
|
|
1228
|
+
this.load()
|
|
1229
|
+
}
|
|
1100
1230
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1231
|
+
#buildLocation = (dest: BuildNextOptions = {}): ParsedLocation => {
|
|
1232
|
+
const fromPathname = dest.fromCurrent
|
|
1233
|
+
? this.store.state.latestLocation.pathname
|
|
1234
|
+
: dest.from ?? this.store.state.latestLocation.pathname
|
|
1105
1235
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
1112
|
-
})
|
|
1113
|
-
}
|
|
1236
|
+
let pathname = resolvePath(
|
|
1237
|
+
this.basepath ?? '/',
|
|
1238
|
+
fromPathname,
|
|
1239
|
+
`${dest.to ?? '.'}`,
|
|
1240
|
+
)
|
|
1114
1241
|
|
|
1115
|
-
|
|
1242
|
+
const fromMatches = this.matchRoutes(
|
|
1243
|
+
this.store.state.latestLocation.pathname,
|
|
1244
|
+
{
|
|
1245
|
+
strictParseParams: true,
|
|
1246
|
+
},
|
|
1247
|
+
)
|
|
1116
1248
|
|
|
1117
|
-
|
|
1118
|
-
const preFilteredSearch = dest.__preSearchFilters?.length
|
|
1119
|
-
? dest.__preSearchFilters.reduce(
|
|
1120
|
-
(prev, next) => next(prev),
|
|
1121
|
-
router.location.search,
|
|
1122
|
-
)
|
|
1123
|
-
: router.location.search
|
|
1124
|
-
|
|
1125
|
-
// Then the link/navigate function
|
|
1126
|
-
const destSearch =
|
|
1127
|
-
dest.search === true
|
|
1128
|
-
? preFilteredSearch // Preserve resolvedFrom true
|
|
1129
|
-
: dest.search
|
|
1130
|
-
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1131
|
-
: dest.__preSearchFilters?.length
|
|
1132
|
-
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1133
|
-
: {}
|
|
1134
|
-
|
|
1135
|
-
// Then post filters
|
|
1136
|
-
const postFilteredSearch = dest.__postSearchFilters?.length
|
|
1137
|
-
? dest.__postSearchFilters.reduce(
|
|
1138
|
-
(prev, next) => next(prev),
|
|
1139
|
-
destSearch,
|
|
1140
|
-
)
|
|
1141
|
-
: destSearch
|
|
1249
|
+
const toMatches = this.matchRoutes(pathname)
|
|
1142
1250
|
|
|
1143
|
-
|
|
1144
|
-
router.location.search,
|
|
1145
|
-
postFilteredSearch,
|
|
1146
|
-
)
|
|
1251
|
+
const prevParams = { ...last(fromMatches)?.params }
|
|
1147
1252
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
: functionalUpdate(dest.hash!, router.location.hash)
|
|
1153
|
-
hash = hash ? `#${hash}` : ''
|
|
1154
|
-
|
|
1155
|
-
return {
|
|
1156
|
-
pathname,
|
|
1157
|
-
search,
|
|
1158
|
-
searchStr,
|
|
1159
|
-
state: router.location.state,
|
|
1160
|
-
hash,
|
|
1161
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
1162
|
-
key: dest.key,
|
|
1163
|
-
}
|
|
1164
|
-
},
|
|
1253
|
+
let nextParams =
|
|
1254
|
+
(dest.params ?? true) === true
|
|
1255
|
+
? prevParams
|
|
1256
|
+
: functionalUpdate(dest.params!, prevParams)
|
|
1165
1257
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1258
|
+
if (nextParams) {
|
|
1259
|
+
toMatches
|
|
1260
|
+
.map((d) => d.route.options.stringifyParams)
|
|
1261
|
+
.filter(Boolean)
|
|
1262
|
+
.forEach((fn) => {
|
|
1263
|
+
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
1264
|
+
})
|
|
1265
|
+
}
|
|
1168
1266
|
|
|
1169
|
-
|
|
1267
|
+
pathname = interpolatePath(pathname, nextParams ?? {})
|
|
1170
1268
|
|
|
1171
|
-
|
|
1269
|
+
// Pre filters first
|
|
1270
|
+
const preFilteredSearch = dest.__preSearchFilters?.length
|
|
1271
|
+
? dest.__preSearchFilters?.reduce(
|
|
1272
|
+
(prev, next) => next(prev),
|
|
1273
|
+
this.store.state.latestLocation.search,
|
|
1274
|
+
)
|
|
1275
|
+
: this.store.state.latestLocation.search
|
|
1276
|
+
|
|
1277
|
+
// Then the link/navigate function
|
|
1278
|
+
const destSearch =
|
|
1279
|
+
dest.search === true
|
|
1280
|
+
? preFilteredSearch // Preserve resolvedFrom true
|
|
1281
|
+
: dest.search
|
|
1282
|
+
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1283
|
+
: dest.__preSearchFilters?.length
|
|
1284
|
+
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1285
|
+
: {}
|
|
1286
|
+
|
|
1287
|
+
// Then post filters
|
|
1288
|
+
const postFilteredSearch = dest.__postSearchFilters?.length
|
|
1289
|
+
? dest.__postSearchFilters.reduce((prev, next) => next(prev), destSearch)
|
|
1290
|
+
: destSearch
|
|
1291
|
+
|
|
1292
|
+
const search = replaceEqualDeep(
|
|
1293
|
+
this.store.state.latestLocation.search,
|
|
1294
|
+
postFilteredSearch,
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
const searchStr = this.options.stringifySearch(search)
|
|
1298
|
+
let hash =
|
|
1299
|
+
dest.hash === true
|
|
1300
|
+
? this.store.state.latestLocation.hash
|
|
1301
|
+
: functionalUpdate(dest.hash!, this.store.state.latestLocation.hash)
|
|
1302
|
+
hash = hash ? `#${hash}` : ''
|
|
1303
|
+
|
|
1304
|
+
return {
|
|
1305
|
+
pathname,
|
|
1306
|
+
search,
|
|
1307
|
+
searchStr,
|
|
1308
|
+
state: this.store.state.latestLocation.state,
|
|
1309
|
+
hash,
|
|
1310
|
+
href: `${pathname}${searchStr}${hash}`,
|
|
1311
|
+
key: dest.key,
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1172
1314
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1315
|
+
#commitLocation = (location: BuildNextOptions & { replace?: boolean }) => {
|
|
1316
|
+
const next = this.buildNext(location)
|
|
1317
|
+
const id = '' + Date.now() + Math.random()
|
|
1176
1318
|
|
|
1177
|
-
|
|
1178
|
-
router.__.parseLocation(history.location).href === next.href
|
|
1319
|
+
if (this.navigateTimeout) clearTimeout(this.navigateTimeout)
|
|
1179
1320
|
|
|
1180
|
-
|
|
1181
|
-
nextAction = 'replace'
|
|
1182
|
-
}
|
|
1321
|
+
let nextAction: 'push' | 'replace' = 'replace'
|
|
1183
1322
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
pathname: next.pathname,
|
|
1188
|
-
hash: next.hash,
|
|
1189
|
-
search: next.searchStr,
|
|
1190
|
-
},
|
|
1191
|
-
{
|
|
1192
|
-
id,
|
|
1193
|
-
},
|
|
1194
|
-
)
|
|
1195
|
-
} else {
|
|
1196
|
-
history.push(
|
|
1197
|
-
{
|
|
1198
|
-
pathname: next.pathname,
|
|
1199
|
-
hash: next.hash,
|
|
1200
|
-
search: next.searchStr,
|
|
1201
|
-
},
|
|
1202
|
-
{
|
|
1203
|
-
id,
|
|
1204
|
-
},
|
|
1205
|
-
)
|
|
1206
|
-
}
|
|
1323
|
+
if (!location.replace) {
|
|
1324
|
+
nextAction = 'push'
|
|
1325
|
+
}
|
|
1207
1326
|
|
|
1208
|
-
|
|
1209
|
-
const previousNavigationResolve = router.resolveNavigation
|
|
1327
|
+
const isSameUrl = this.store.state.latestLocation.href === next.href
|
|
1210
1328
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1215
|
-
})
|
|
1329
|
+
if (isSameUrl && !next.key) {
|
|
1330
|
+
nextAction = 'replace'
|
|
1331
|
+
}
|
|
1216
1332
|
|
|
1217
|
-
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1333
|
+
const href = `${next.pathname}${next.searchStr}${
|
|
1334
|
+
next.hash ? `#${next.hash}` : ''
|
|
1335
|
+
}`
|
|
1221
1336
|
|
|
1222
|
-
|
|
1337
|
+
this.history[nextAction === 'push' ? 'push' : 'replace'](href, {
|
|
1338
|
+
id,
|
|
1339
|
+
...next.state,
|
|
1340
|
+
})
|
|
1223
1341
|
|
|
1224
|
-
|
|
1225
|
-
router.options.createRouter?.(router)
|
|
1342
|
+
this.load(this.#parseLocation(this.store.state.latestLocation))
|
|
1226
1343
|
|
|
1227
|
-
|
|
1344
|
+
return (this.navigationPromise = new Promise((resolve) => {
|
|
1345
|
+
const previousNavigationResolve = this.resolveNavigation
|
|
1346
|
+
|
|
1347
|
+
this.resolveNavigation = () => {
|
|
1348
|
+
previousNavigationResolve()
|
|
1349
|
+
resolve()
|
|
1350
|
+
}
|
|
1351
|
+
}))
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Detect if we're in the DOM
|
|
1356
|
+
const isServer = typeof window === 'undefined' || !window.document.createElement
|
|
1357
|
+
|
|
1358
|
+
function getInitialRouterState(): RouterStore {
|
|
1359
|
+
return {
|
|
1360
|
+
status: 'idle',
|
|
1361
|
+
latestLocation: null!,
|
|
1362
|
+
currentLocation: null!,
|
|
1363
|
+
currentMatches: [],
|
|
1364
|
+
loaders: {},
|
|
1365
|
+
lastUpdated: Date.now(),
|
|
1366
|
+
matchCache: {},
|
|
1367
|
+
get isFetching() {
|
|
1368
|
+
return (
|
|
1369
|
+
this.status === 'loading' ||
|
|
1370
|
+
this.currentMatches.some((d) => d.store.state.isFetching)
|
|
1371
|
+
)
|
|
1372
|
+
},
|
|
1373
|
+
get isPreloading() {
|
|
1374
|
+
return Object.values(this.matchCache).some(
|
|
1375
|
+
(d) =>
|
|
1376
|
+
d.match.store.state.isFetching &&
|
|
1377
|
+
!this.currentMatches.find((dd) => dd.id === d.match.id),
|
|
1378
|
+
)
|
|
1379
|
+
},
|
|
1380
|
+
}
|
|
1228
1381
|
}
|
|
1229
1382
|
|
|
1230
1383
|
function isCtrlEvent(e: MouseEvent) {
|
|
1231
1384
|
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
|
|
1232
1385
|
}
|
|
1386
|
+
|
|
1387
|
+
function linkMatches(matches: RouteMatch<any, any>[]) {
|
|
1388
|
+
matches.forEach((match, index) => {
|
|
1389
|
+
const parent = matches[index - 1]
|
|
1390
|
+
|
|
1391
|
+
if (parent) {
|
|
1392
|
+
match.__setParentMatch(parent)
|
|
1393
|
+
}
|
|
1394
|
+
})
|
|
1395
|
+
}
|