@tanstack/router-core 0.0.1-beta.9 → 1.97.21

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