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