@tanstack/router-core 0.0.1-alpha.1 → 0.0.1-alpha.11

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 (43) hide show
  1. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js +30 -0
  2. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +1 -0
  3. package/build/cjs/packages/router-core/src/index.js +35 -1421
  4. package/build/cjs/packages/router-core/src/index.js.map +1 -1
  5. package/build/cjs/packages/router-core/src/path.js +222 -0
  6. package/build/cjs/packages/router-core/src/path.js.map +1 -0
  7. package/build/cjs/packages/router-core/src/qss.js +1 -1
  8. package/build/cjs/packages/router-core/src/qss.js.map +1 -1
  9. package/build/cjs/packages/router-core/src/route.js +161 -0
  10. package/build/cjs/packages/router-core/src/route.js.map +1 -0
  11. package/build/cjs/packages/router-core/src/routeConfig.js +69 -0
  12. package/build/cjs/packages/router-core/src/routeConfig.js.map +1 -0
  13. package/build/cjs/packages/router-core/src/routeMatch.js +266 -0
  14. package/build/cjs/packages/router-core/src/routeMatch.js.map +1 -0
  15. package/build/cjs/packages/router-core/src/router.js +789 -0
  16. package/build/cjs/packages/router-core/src/router.js.map +1 -0
  17. package/build/cjs/packages/router-core/src/searchParams.js +70 -0
  18. package/build/cjs/packages/router-core/src/searchParams.js.map +1 -0
  19. package/build/cjs/packages/router-core/src/utils.js +118 -0
  20. package/build/cjs/packages/router-core/src/utils.js.map +1 -0
  21. package/build/esm/index.js +1385 -1232
  22. package/build/esm/index.js.map +1 -1
  23. package/build/stats-html.html +1 -1
  24. package/build/stats-react.json +388 -46
  25. package/build/types/index.d.ts +433 -338
  26. package/build/umd/index.development.js +1226 -1065
  27. package/build/umd/index.development.js.map +1 -1
  28. package/build/umd/index.production.js +1 -1
  29. package/build/umd/index.production.js.map +1 -1
  30. package/package.json +2 -2
  31. package/src/frameworks.ts +12 -0
  32. package/src/index.ts +15 -2969
  33. package/src/link.ts +319 -0
  34. package/src/path.ts +236 -0
  35. package/src/qss.ts +1 -1
  36. package/src/route.ts +243 -0
  37. package/src/routeConfig.ts +495 -0
  38. package/src/routeInfo.ts +228 -0
  39. package/src/routeMatch.ts +374 -0
  40. package/src/router.ts +1230 -0
  41. package/src/searchParams.ts +54 -0
  42. package/src/utils.ts +157 -0
  43. package/src/createRoutes.test.ts +0 -328
package/src/index.ts CHANGED
@@ -1,2974 +1,20 @@
1
- import {
1
+ export {
2
2
  createHashHistory,
3
3
  createBrowserHistory,
4
4
  createMemoryHistory,
5
- BrowserHistory,
6
- MemoryHistory,
7
- History,
8
- HashHistory,
9
5
  } from 'history'
10
- import React from 'react'
11
6
 
12
- export { createHashHistory, createBrowserHistory, createMemoryHistory }
13
-
14
- import { decode, encode } from './qss'
15
-
16
- // Types
17
-
18
- export type NoInfer<T> = [T][T extends any ? 0 : never]
19
- export type IsAny<T, Y, N> = 1 extends 0 & T ? Y : N
20
- export type IsAnyBoolean<T> = 1 extends 0 & T ? true : false
21
- export type IsKnown<T, Y, N> = unknown extends T ? N : Y
22
- export type PickAsRequired<T, K extends keyof T> = Omit<T, K> &
23
- Required<Pick<T, K>>
24
- export type PickAsPartial<T, K extends keyof T> = Omit<T, K> &
25
- Partial<Pick<T, K>>
26
- export type PickUnsafe<T, K> = K extends keyof T ? Pick<T, K> : never
27
- export type PickExtra<T, K> = Expand<{
28
- [TKey in keyof K as string extends TKey
29
- ? never
30
- : TKey extends keyof T
31
- ? never
32
- : TKey]: K[TKey]
33
- }>
34
- type PickRequired<T> = {
35
- [K in keyof T as undefined extends T[K] ? never : K]: T[K]
36
- }
37
- // type LooseAutocomplete<T> = T extends string ? T | Omit<string, T> : never
38
- type StartsWith<A, B> = A extends `${B extends string ? B : never}${infer _}`
39
- ? true
40
- : false
41
- type Expand<T> = T extends object
42
- ? T extends infer O
43
- ? { [K in keyof O]: O[K] }
44
- : never
45
- : T
46
-
47
- type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
48
- k: infer I,
49
- ) => any
50
- ? I
51
- : never
52
-
53
- export interface FrameworkGenerics {
54
- // The following properties are used internally
55
- // and are extended by framework adapters, but cannot be
56
- // pre-defined as constraints:
57
- //
58
- // Element: any
59
- // AsyncElement: any
60
- // SyncOrAsyncElement?: any
61
- }
62
-
63
- export interface RouteConfig<
64
- TId extends string = string,
65
- TPath extends string = string,
66
- TFullPath extends string = string,
67
- TRouteLoaderData extends AnyLoaderData = AnyLoaderData,
68
- TLoaderData extends AnyLoaderData = AnyLoaderData,
69
- TActionPayload = unknown,
70
- TActionResponse = unknown,
71
- TParentSearchSchema extends {} = {},
72
- TSearchSchema extends AnySearchSchema = {},
73
- TFullSearchSchema extends AnySearchSchema = {},
74
- TParentParams extends AnyPathParams = {},
75
- TParams extends Record<ParsePathParams<TPath>, unknown> = Record<
76
- ParsePathParams<TPath>,
77
- string
78
- >,
79
- TAllParams extends AnyPathParams = {},
80
- TKnownChildren = unknown,
81
- > {
82
- id: TId
83
- path: NoInfer<TPath>
84
- fullPath: TFullPath
85
- options: RouteOptions<
86
- TPath,
87
- TRouteLoaderData,
88
- TLoaderData,
89
- TActionPayload,
90
- TActionResponse,
91
- TParentSearchSchema,
92
- TSearchSchema,
93
- TFullSearchSchema,
94
- TParentParams,
95
- TParams,
96
- TAllParams
97
- >
98
- children?: TKnownChildren
99
- addChildren: IsAny<
100
- TId,
101
- any,
102
- <TNewChildren extends any>(
103
- cb: (
104
- createChildRoute: CreateRouteConfigFn<
105
- false,
106
- TId,
107
- TLoaderData,
108
- TFullSearchSchema,
109
- TAllParams
110
- >,
111
- ) => TNewChildren extends AnyRouteConfig[]
112
- ? TNewChildren
113
- : { error: 'Invalid route detected'; route: TNewChildren },
114
- ) => RouteConfig<
115
- TId,
116
- TPath,
117
- TFullPath,
118
- TRouteLoaderData,
119
- TLoaderData,
120
- TActionPayload,
121
- TActionResponse,
122
- TParentSearchSchema,
123
- TSearchSchema,
124
- TFullSearchSchema,
125
- TParentParams,
126
- TParams,
127
- TAllParams,
128
- TNewChildren
129
- >
130
- >
131
- }
132
-
133
- type CreateRouteConfigFn<
134
- TIsRoot extends boolean = false,
135
- TParentId extends string = string,
136
- TParentAllLoaderData extends AnyLoaderData = {},
137
- TParentSearchSchema extends AnySearchSchema = {},
138
- TParentParams extends AnyPathParams = {},
139
- > = <
140
- TPath extends string,
141
- TRouteLoaderData extends AnyLoaderData,
142
- TActionPayload,
143
- TActionResponse,
144
- TSearchSchema extends AnySearchSchema = AnySearchSchema,
145
- TParams extends Record<ParsePathParams<TPath>, unknown> = Record<
146
- ParsePathParams<TPath>,
147
- string
148
- >,
149
- TAllParams extends AnyPathParams extends TParams
150
- ? Record<ParsePathParams<TPath>, string>
151
- : NoInfer<TParams> = AnyPathParams extends TParams
152
- ? Record<ParsePathParams<TPath>, string>
153
- : NoInfer<TParams>,
154
- TKnownChildren extends RouteConfig[] = RouteConfig[],
155
- >(
156
- options?: TIsRoot extends true
157
- ? Omit<
158
- RouteOptions<
159
- TPath,
160
- TRouteLoaderData,
161
- Expand<TParentAllLoaderData & DeepAwaited<NoInfer<TRouteLoaderData>>>,
162
- TActionPayload,
163
- TActionResponse,
164
- TParentSearchSchema,
165
- TSearchSchema,
166
- Expand<TParentSearchSchema & TSearchSchema>,
167
- TParentParams,
168
- TParams,
169
- Expand<TParentParams & TAllParams>
170
- >,
171
- 'path'
172
- > & { path?: never }
173
- : RouteOptions<
174
- TPath,
175
- TRouteLoaderData,
176
- Expand<TParentAllLoaderData & DeepAwaited<NoInfer<TRouteLoaderData>>>,
177
- TActionPayload,
178
- TActionResponse,
179
- TParentSearchSchema,
180
- TSearchSchema,
181
- Expand<TParentSearchSchema & TSearchSchema>,
182
- TParentParams,
183
- TParams,
184
- Expand<TParentParams & TAllParams>
185
- >,
186
- children?: TKnownChildren,
187
- isRoot?: boolean,
188
- parentId?: string,
189
- ) => RouteConfig<
190
- RouteId<TParentId, TPath>,
191
- TPath,
192
- RouteIdToPath<RouteId<TParentId, TPath>>,
193
- TRouteLoaderData,
194
- Expand<TParentAllLoaderData & DeepAwaited<NoInfer<TRouteLoaderData>>>,
195
- TActionPayload,
196
- TActionResponse,
197
- TParentSearchSchema,
198
- TSearchSchema,
199
- Expand<TParentSearchSchema & TSearchSchema>,
200
- TParentParams,
201
- TParams,
202
- Expand<TParentParams & TAllParams>,
203
- TKnownChildren
204
- >
205
-
206
- export const createRouteConfig: CreateRouteConfigFn<true> = (
207
- options = {} as any,
208
- children,
209
- isRoot = true,
210
- parentId,
211
- ) => {
212
- if (isRoot) {
213
- ;(options as any).path = rootRouteId
214
- } else {
215
- warning(!options.path, 'Routes must have a path property.')
216
- }
217
-
218
- // Strip the root from parentIds
219
- if (parentId === rootRouteId) {
220
- parentId = ''
221
- }
222
-
223
- let path = String(isRoot ? rootRouteId : options.path)
224
-
225
- // If the path is anything other than an index path, trim it up
226
- if (path !== '/') {
227
- path = trimPath(path)
228
- }
229
-
230
- let id = joinPaths([parentId, path])
231
-
232
- if (path === rootRouteId) {
233
- path = '/'
234
- }
235
-
236
- if (id !== rootRouteId) {
237
- id = joinPaths(['/', id])
238
- }
239
-
240
- const fullPath = id === rootRouteId ? '/' : trimPathRight(id)
241
-
242
- return {
243
- id: id as any,
244
- path: path as any,
245
- fullPath: fullPath as any,
246
- options: options as any,
247
- children,
248
- addChildren: (cb: any) =>
249
- createRouteConfig(
250
- options,
251
- cb((childOptions: any) =>
252
- createRouteConfig(childOptions, undefined, false, id),
253
- ),
254
- false,
255
- parentId,
256
- ),
257
- }
258
- }
259
-
260
- export interface AnyRouteConfig
261
- extends RouteConfig<
262
- any,
263
- any,
264
- any,
265
- any,
266
- any,
267
- any,
268
- any,
269
- any,
270
- any,
271
- any,
272
- any,
273
- any,
274
- any
275
- > {}
276
-
277
- export interface AnyRouteConfigWithChildren<TChildren>
278
- extends RouteConfig<
279
- any,
280
- any,
281
- any,
282
- any,
283
- any,
284
- any,
285
- any,
286
- any,
287
- any,
288
- any,
289
- any,
290
- any,
291
- any,
292
- TChildren
293
- > {}
294
-
295
- export interface AnyAllRouteInfo {
296
- routeConfig: AnyRouteConfig
297
- routeInfo: AnyRouteInfo
298
- routeInfoById: Record<string, AnyRouteInfo>
299
- routeInfoByFullPath: Record<string, AnyRouteInfo>
300
- fullPath: string
301
- }
302
-
303
- export interface DefaultAllRouteInfo {
304
- routeConfig: RouteConfig
305
- routeInfo: RouteInfo
306
- routeInfoById: Record<string, RouteInfo>
307
- routeInfoByFullPath: Record<string, RouteInfo>
308
- fullPath: string
309
- }
310
-
311
- export interface AllRouteInfo<TRouteConfig extends AnyRouteConfig = RouteConfig>
312
- extends RoutesInfoInner<TRouteConfig, ParseRouteConfig<TRouteConfig>> {}
313
-
314
- export interface RoutesInfoInner<
315
- TRouteConfig extends AnyRouteConfig,
316
- TRouteInfo extends RouteInfo<
317
- string,
318
- string,
319
- any,
320
- any,
321
- any,
322
- any,
323
- any,
324
- any,
325
- any,
326
- any,
327
- any,
328
- any
329
- > = RouteInfo,
330
- > {
331
- routeConfig: TRouteConfig
332
- routeInfo: TRouteInfo
333
- routeInfoById: {
334
- [TInfo in TRouteInfo as TInfo['id']]: TInfo
335
- }
336
- routeInfoByFullPath: {
337
- [TInfo in TRouteInfo as TInfo['id'] extends RootRouteId
338
- ? never
339
- : RouteIdToPath<TInfo['id']>]: TInfo
340
- }
341
- fullPath: RouteIdToPath<TRouteInfo['id']>
342
- }
343
-
344
- export interface AnyRoute extends Route<any, any> {}
345
- export interface AnyRouteInfo
346
- extends RouteInfo<
347
- any,
348
- any,
349
- any,
350
- any,
351
- any,
352
- any,
353
- any,
354
- any,
355
- any,
356
- any,
357
- any,
358
- any,
359
- any
360
- > {}
361
-
362
- // type IndexObj<T extends Record<string, any>, TKey extends keyof T> = {
363
- // [E in T as E[TKey]]: E
364
- // }
365
-
366
- type RouteIdToPath<T extends string> = T extends RootRouteId
367
- ? '/'
368
- : TrimPathRight<`${T}`>
369
-
370
- type ParseRouteConfig<TRouteConfig = AnyRouteConfig> =
371
- TRouteConfig extends AnyRouteConfig
372
- ? RouteConfigRoute<TRouteConfig> | ParseRouteChildren<TRouteConfig>
373
- : never
374
-
375
- type ParseRouteChildren<TRouteConfig> =
376
- TRouteConfig extends AnyRouteConfigWithChildren<infer TChildren>
377
- ? unknown extends TChildren
378
- ? never
379
- : TChildren extends AnyRouteConfig[]
380
- ? Values<{
381
- [TId in TChildren[number]['id']]: ParseRouteChild<
382
- TChildren[number],
383
- TId
384
- >
385
- }>
386
- : never // Children are not routes
387
- : never // No children
388
-
389
- type ParseRouteChild<TRouteConfig, TId> = TRouteConfig & {
390
- id: TId
391
- } extends AnyRouteConfig
392
- ? ParseRouteConfig<TRouteConfig>
393
- : never
394
-
395
- export type Values<O> = O[ValueKeys<O>]
396
- export type ValueKeys<O> = Extract<keyof O, PropertyKey>
397
-
398
- export type RouteConfigRoute<TRouteConfig> = TRouteConfig extends RouteConfig<
399
- infer TId,
400
- infer TPath,
401
- infer TFullPath,
402
- infer TRouteLoaderData,
403
- infer TLoaderData,
404
- infer TActionPayload,
405
- infer TActionResponse,
406
- infer TParentSearchSchema,
407
- infer TSearchSchema,
408
- infer TFullSearchSchema,
409
- infer TParentParams,
410
- infer TParams,
411
- infer TAllParams,
412
- any
413
- >
414
- ? string extends TId
415
- ? never
416
- : RouteInfo<
417
- TId,
418
- TPath,
419
- TFullPath,
420
- TRouteLoaderData,
421
- TLoaderData,
422
- TActionPayload,
423
- TActionResponse,
424
- TParentSearchSchema,
425
- TSearchSchema,
426
- TFullSearchSchema,
427
- TParentParams,
428
- TParams,
429
- TAllParams
430
- >
431
- : never
432
-
433
- export interface RouteInfo<
434
- TId extends string = string,
435
- TPath extends string = string,
436
- TFullPath extends {} = string,
437
- TRouteLoaderData extends AnyLoaderData = {},
438
- TLoaderData extends AnyLoaderData = {},
439
- TActionPayload = unknown,
440
- TActionResponse = unknown,
441
- TParentSearchSchema extends {} = {},
442
- TSearchSchema extends AnySearchSchema = {},
443
- TFullSearchSchema extends AnySearchSchema = {},
444
- TParentParams extends AnyPathParams = {},
445
- TParams extends Record<ParsePathParams<TPath>, unknown> = Record<
446
- ParsePathParams<TPath>,
447
- string
448
- >,
449
- TAllParams extends AnyPathParams = {},
450
- > {
451
- id: TId
452
- path: TPath
453
- fullPath: TFullPath
454
- routeLoaderData: TRouteLoaderData
455
- loaderData: TLoaderData
456
- actionPayload: TActionPayload
457
- actionResponse: TActionResponse
458
- searchSchema: TSearchSchema
459
- fullSearchSchema: TFullSearchSchema
460
- parentParams: TParentParams
461
- params: TParams
462
- allParams: TAllParams
463
- options: RouteOptions<
464
- TPath,
465
- TRouteLoaderData,
466
- TLoaderData,
467
- TActionPayload,
468
- TActionResponse,
469
- TParentSearchSchema,
470
- TSearchSchema,
471
- TFullSearchSchema,
472
- TParentParams,
473
- TParams,
474
- TAllParams
475
- >
476
- }
477
-
478
- type DeepAwaited<T> = T extends Promise<infer A>
479
- ? DeepAwaited<A>
480
- : T extends Record<infer A, Promise<infer B>>
481
- ? { [K in A]: DeepAwaited<B> }
482
- : T
483
-
484
- export const rootRouteId = '__root__' as const
485
- export type RootRouteId = typeof rootRouteId
486
-
487
- type RouteId<
488
- TPrefix extends string,
489
- TPath extends string,
490
- > = string extends TPath
491
- ? RootRouteId
492
- : `${TPrefix}/${TPath}` extends '/'
493
- ? '/'
494
- : `/${TrimPathLeft<`${TrimPathRight<TPrefix>}/${TrimPath<TPath>}`>}`
495
-
496
- type CleanPath<T extends string> = T extends `${infer L}//${infer R}`
497
- ? CleanPath<`${CleanPath<L>}/${CleanPath<R>}`>
498
- : T extends `${infer L}//`
499
- ? `${CleanPath<L>}/`
500
- : T extends `//${infer L}`
501
- ? `/${CleanPath<L>}`
502
- : T
503
-
504
- type TrimPath<T extends string> = '' extends T
505
- ? ''
506
- : TrimPathRight<TrimPathLeft<T>>
507
-
508
- type TrimPathLeft<T extends string> = T extends `${RootRouteId}/${infer U}`
509
- ? TrimPathLeft<U>
510
- : T extends `/${infer U}`
511
- ? TrimPathLeft<U>
512
- : T
513
- type TrimPathRight<T extends string> = T extends '/'
514
- ? '/'
515
- : T extends `${infer U}/`
516
- ? TrimPathRight<U>
517
- : T
518
-
519
- export type ParsePathParams<T extends string> = Split<T>[number] extends infer U
520
- ? U extends `:${infer V}`
521
- ? V
522
- : never
523
- : never
524
-
525
- export type PathParamMask<TRoutePath extends string> =
526
- TRoutePath extends `${infer L}/:${infer C}/${infer R}`
527
- ? PathParamMask<`${L}/${string}/${R}`>
528
- : TRoutePath extends `${infer L}/:${infer C}`
529
- ? PathParamMask<`${L}/${string}`>
530
- : TRoutePath
531
-
532
- type Split<S, TTrailing = true> = S extends unknown
533
- ? string extends S
534
- ? string[]
535
- : S extends string
536
- ? CleanPath<S> extends ''
537
- ? []
538
- : TTrailing extends true
539
- ? CleanPath<S> extends `${infer T}/`
540
- ? [T, '/']
541
- : CleanPath<S> extends `/${infer U}`
542
- ? ['/', U]
543
- : CleanPath<S> extends `${infer T}/${infer U}`
544
- ? [T, ...Split<U>]
545
- : [S]
546
- : CleanPath<S> extends `${infer T}/${infer U}`
547
- ? [T, ...Split<U>]
548
- : [S]
549
- : never
550
- : never
551
-
552
- type Join<T> = T extends []
553
- ? ''
554
- : T extends [infer L extends string]
555
- ? L
556
- : T extends [infer L extends string, ...infer Tail extends [...string[]]]
557
- ? CleanPath<`${L}/${Join<Tail>}`>
558
- : never
559
-
560
- export type AnySearchSchema = {}
561
- export type AnyLoaderData = {}
562
- export type AnyPathParams = {}
563
- export interface RouteMeta {}
564
- export interface LocationState {}
565
-
566
- type Timeout = ReturnType<typeof setTimeout>
567
-
568
- export type SearchSerializer = (searchObj: Record<string, any>) => string
569
- export type SearchParser = (searchStr: string) => Record<string, any>
570
-
571
- export type Updater<TPrevious, TResult = TPrevious> =
572
- | TResult
573
- | ((prev?: TPrevious) => TResult)
574
-
575
- export interface Location<
576
- TSearchObj extends AnySearchSchema = {},
577
- TState extends LocationState = LocationState,
578
- > {
579
- href: string
580
- pathname: string
581
- search: TSearchObj
582
- searchStr: string
583
- state: TState
584
- hash: string
585
- key?: string
586
- }
587
-
588
- export interface FromLocation {
589
- pathname: string
590
- search?: unknown
591
- key?: string
592
- hash?: string
593
- }
594
-
595
- export type PickExtract<T, U> = {
596
- [K in keyof T as T[K] extends U ? K : never]: T[K]
597
- }
598
-
599
- export type PickExclude<T, U> = {
600
- [K in keyof T as T[K] extends U ? never : K]: T[K]
601
- }
602
-
603
- export type SearchSchemaValidator<TReturn, TParentSchema> = (
604
- searchObj: Record<string, unknown>,
605
- ) => {} extends TParentSchema
606
- ? TReturn
607
- : keyof TReturn extends keyof TParentSchema
608
- ? {
609
- error: 'Top level search params cannot be redefined by child routes!'
610
- keys: keyof TReturn & keyof TParentSchema
611
- }
612
- : TReturn
613
-
614
- export type DefinedPathParamWarning =
615
- 'Path params cannot be redefined by child routes!'
616
-
617
- export type ParentParams<TParentParams> = AnyPathParams extends TParentParams
618
- ? {}
619
- : {
620
- [Key in keyof TParentParams]?: DefinedPathParamWarning
621
- }
622
-
623
- export type RouteOptions<
624
- TPath extends string = string,
625
- TRouteLoaderData extends AnyLoaderData = {},
626
- TLoaderData extends AnyLoaderData = {},
627
- TActionPayload = unknown,
628
- TActionResponse = unknown,
629
- TParentSearchSchema extends {} = {},
630
- TSearchSchema extends AnySearchSchema = {},
631
- TFullSearchSchema extends AnySearchSchema = TSearchSchema,
632
- TParentParams extends AnyPathParams = {},
633
- TParams extends Record<ParsePathParams<TPath>, unknown> = Record<
634
- ParsePathParams<TPath>,
635
- string
636
- >,
637
- TAllParams extends AnyPathParams = {},
638
- > = {
639
- // The path to match (relative to the nearest parent `Route` component or root basepath)
640
- path: TPath
641
- // If true, this route will be matched as case-sensitive
642
- caseSensitive?: boolean
643
- validateSearch?: SearchSchemaValidator<TSearchSchema, TParentSearchSchema>
644
- // Filter functions that can manipulate search params *before* they are passed to links and navigate
645
- // calls that match this route.
646
- preSearchFilters?: SearchFilter<TFullSearchSchema>[]
647
- // Filter functions that can manipulate search params *after* they are passed to links and navigate
648
- // calls that match this route.
649
- postSearchFilters?: SearchFilter<TFullSearchSchema>[]
650
- // The duration to wait during `loader` execution before showing the `pendingElement`
651
- pendingMs?: number
652
- // _If the `pendingElement` is shown_, the minimum duration for which it will be visible.
653
- pendingMinMs?: number
654
- // // An array of child routes
655
- // children?: Route<any, any, any, any>[]
656
- } & (
657
- | {
658
- parseParams?: never
659
- stringifyParams?: never
660
- }
661
- | {
662
- // Parse params optionally receives path params as strings and returns them in a parsed format (like a number or boolean)
663
- parseParams: (
664
- rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>,
665
- ) => TParams
666
- stringifyParams: (
667
- params: TParams,
668
- ) => Record<ParsePathParams<TPath>, string>
669
- }
670
- ) &
671
- RouteLoaders<
672
- // Route Loaders (see below) can be inline on the route, or resolved async
673
- TRouteLoaderData,
674
- TLoaderData,
675
- TActionPayload,
676
- TActionResponse,
677
- TFullSearchSchema,
678
- TAllParams
679
- > & {
680
- // If `import` is defined, this route can resolve its elements and loaders in a single asynchronous call
681
- // This is particularly useful for code-splitting or module federation
682
- import?: (opts: {
683
- params: AnyPathParams
684
- }) => Promise<
685
- RouteLoaders<
686
- TRouteLoaderData,
687
- TLoaderData,
688
- TActionPayload,
689
- TActionResponse,
690
- TFullSearchSchema,
691
- TAllParams
692
- >
693
- >
694
- } & (PickUnsafe<TParentParams, ParsePathParams<TPath>> extends never // Detect if an existing path param is being redefined
695
- ? {}
696
- : 'Cannot redefined path params in child routes!')
697
-
698
- export interface RouteLoaders<
699
- TRouteLoaderData extends AnyLoaderData = {},
700
- TLoaderData extends AnyLoaderData = {},
701
- TActionPayload = unknown,
702
- TActionResponse = unknown,
703
- TFullSearchSchema extends AnySearchSchema = {},
704
- TAllParams extends AnyPathParams = {},
705
- > {
706
- // The content to be rendered when the route is matched. If no element is provided, defaults to `<Outlet />`
707
- element?: GetFrameworkGeneric<'SyncOrAsyncElement', NoInfer<TLoaderData>>
708
- // The content to be rendered when `loader` encounters an error
709
- errorElement?: GetFrameworkGeneric<'SyncOrAsyncElement', NoInfer<TLoaderData>>
710
- // The content to be rendered when rendering encounters an error
711
- catchElement?: GetFrameworkGeneric<'SyncOrAsyncElement', NoInfer<TLoaderData>>
712
- // The content to be rendered when the duration of `loader` execution surpasses the `pendingMs` duration
713
- pendingElement?: GetFrameworkGeneric<
714
- 'SyncOrAsyncElement',
715
- NoInfer<TLoaderData>
716
- >
717
- // An asynchronous function responsible for preparing or fetching data for the route before it is rendered
718
- loader?: LoaderFn<TRouteLoaderData, TFullSearchSchema, TAllParams>
719
- // An asynchronous function made available to the route for performing asynchronous or mutative actions that
720
- // might invalidate the route's data.
721
- action?: ActionFn<TActionPayload, TActionResponse>
722
- // Set this to true to rethrow errors up the component tree to either the nearest error boundary or
723
- // route with error element, whichever comes first.
724
- useErrorBoundary?: boolean
725
- // This function is called
726
- // when moving from an inactive state to an active one. Likewise, when moving from
727
- // an active to an inactive state, the return function (if provided) is called.
728
- onMatch?: (matchContext: {
729
- params: TAllParams
730
- search: TFullSearchSchema
731
- }) =>
732
- | void
733
- | undefined
734
- | ((match: { params: TAllParams; search: TFullSearchSchema }) => void)
735
- // This function is called when the route remains active from one transition to the next.
736
- onTransition?: (match: {
737
- params: TAllParams
738
- search: TFullSearchSchema
739
- }) => void
740
- // An object of whatever you want! This object is accessible anywhere matches are.
741
- meta?: RouteMeta // TODO: Make this nested and mergeable
742
- }
743
-
744
- export type SearchFilter<T, U = T> = (prev: T) => U
745
-
746
- export interface MatchLocation {
747
- to?: string | number | null
748
- fuzzy?: boolean
749
- caseSensitive?: boolean
750
- from?: string
751
- fromCurrent?: boolean
752
- }
753
-
754
- export type SearchPredicate<TSearch extends AnySearchSchema = {}> = (
755
- search: TSearch,
756
- ) => any
757
-
758
- export type LoaderFn<
759
- TRouteLoaderData extends AnyLoaderData,
760
- TFullSearchSchema extends AnySearchSchema = {},
761
- TAllParams extends AnyPathParams = {},
762
- > = (loaderContext: {
763
- params: TAllParams
764
- search: TFullSearchSchema
765
- signal?: AbortSignal
766
- }) => Promise<TRouteLoaderData>
767
-
768
- export type ActionFn<TActionPayload = unknown, TActionResponse = unknown> = (
769
- submission: TActionPayload,
770
- ) => TActionResponse | Promise<TActionResponse>
771
-
772
- export type UnloaderFn<TPath extends string> = (
773
- routeMatch: RouteMatch<any, RouteInfo<string, TPath>>,
774
- ) => void
775
-
776
- export interface RouterState {
777
- status: 'idle' | 'loading'
778
- location: Location
779
- matches: RouteMatch[]
780
- lastUpdated: number
781
- loaderData: unknown
782
- action?: ActionState
783
- actions: Record<string, Action>
784
- pending?: PendingState
785
- }
786
-
787
- export interface PendingState {
788
- location: Location
789
- matches: RouteMatch[]
790
- }
791
-
792
- export type ListenerFn = () => void
793
-
794
- export interface Segment {
795
- type: 'pathname' | 'param' | 'wildcard'
796
- value: string
797
- }
798
-
799
- type GetFrameworkGeneric<U, TData = unknown> = U extends keyof FrameworkGenerics
800
- ? FrameworkGenerics[U]
801
- : any
802
-
803
- export interface __Experimental__RouterSnapshot {
804
- location: Location
805
- matches: SnapshotRouteMatch<unknown>[]
806
- }
807
-
808
- export interface SnapshotRouteMatch<TData> {
809
- matchId: string
810
- loaderData: TData
811
- }
812
-
813
- export interface BuildNextOptions {
814
- to?: string | number | null
815
- params?: true | Updater<Record<string, any>>
816
- search?: true | Updater<unknown>
817
- hash?: true | Updater<string>
818
- key?: string
819
- from?: string
820
- fromCurrent?: boolean
821
- __preSearchFilters?: SearchFilter<any>[]
822
- __postSearchFilters?: SearchFilter<any>[]
823
- }
824
-
825
- interface ActiveOptions {
826
- exact?: boolean
827
- includeHash?: boolean
828
- }
829
-
830
- export type FilterRoutesFn = <TRoute extends Route<any, RouteInfo>>(
831
- routeConfigs: TRoute[],
832
- ) => TRoute[]
833
-
834
- type Listener = () => void
835
-
836
- // Source
837
-
838
- // Detect if we're in the DOM
839
- const isDOM = Boolean(
840
- typeof window !== 'undefined' &&
841
- window.document &&
842
- window.document.createElement,
843
- )
844
- // This is the default history object if none is defined
845
- const createDefaultHistory = () =>
846
- isDOM ? createBrowserHistory() : createMemoryHistory()
847
-
848
- export interface MatchRouteOptions {
849
- pending: boolean
850
- caseSensitive?: boolean
851
- }
852
-
853
- export type LinkInfo =
854
- | {
855
- type: 'external'
856
- href: string
857
- }
858
- | {
859
- type: 'internal'
860
- next: Location
861
- handleFocus: (e: any) => void
862
- handleClick: (e: any) => void
863
- handleEnter: (e: any) => void
864
- handleLeave: (e: any) => void
865
- isActive: boolean
866
- disabled?: boolean
867
- }
868
-
869
- export type PreloadCacheEntry = {
870
- maxAge: number
871
- match: RouteMatch
872
- }
873
-
874
- export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
875
- history?: BrowserHistory | MemoryHistory | HashHistory
876
- stringifySearch?: SearchSerializer
877
- parseSearch?: SearchParser
878
- filterRoutes?: FilterRoutesFn
879
- defaultLinkPreload?: false | 'intent'
880
- defaultLinkPreloadMaxAge?: number
881
- defaultLinkPreloadDelay?: number
882
- useErrorBoundary?: boolean
883
- defaultElement?: GetFrameworkGeneric<'Element'>
884
- defaultErrorElement?: GetFrameworkGeneric<'Element'>
885
- defaultCatchElement?: GetFrameworkGeneric<'Element'>
886
- defaultPendingElement?: GetFrameworkGeneric<'Element'>
887
- defaultPendingMs?: number
888
- defaultPendingMinMs?: number
889
- caseSensitive?: boolean
890
- __experimental__snapshot?: __Experimental__RouterSnapshot
891
- routeConfig?: TRouteConfig
892
- basepath?: string
893
- createRouter?: (router: Router<any, any>) => void
894
- createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
895
- }
896
-
897
- export interface Action<
898
- TPayload = unknown,
899
- TResponse = unknown,
900
- // TError = unknown,
901
- > {
902
- submit: (submission?: TPayload) => Promise<TResponse>
903
- latest?: ActionState
904
- pending: ActionState<TPayload, TResponse>[]
905
- }
906
-
907
- export interface ActionState<
908
- TPayload = unknown,
909
- TResponse = unknown,
910
- // TError = unknown,
911
- > {
912
- submittedAt: number
913
- status: 'idle' | 'pending' | 'success' | 'error'
914
- submission: TPayload
915
- data?: TResponse
916
- error?: unknown
917
- }
918
-
919
- type RoutesById<TAllRouteInfo extends AnyAllRouteInfo> = {
920
- [K in keyof TAllRouteInfo['routeInfoById']]: Route<
921
- TAllRouteInfo,
922
- TAllRouteInfo['routeInfoById'][K]
923
- >
924
- }
925
-
926
- // type RoutesByPath<TAllRouteInfo extends AnyAllRouteInfo> = {
927
- // [K in TAllRouteInfo['fullPath']]: Route<
928
- // TAllRouteInfo,
929
- // TAllRouteInfo['routeInfoByFullPath'][K]
930
- // >
931
- // }
932
-
933
- export type ValidFromPath<
934
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
935
- > =
936
- | undefined
937
- | (string extends TAllRouteInfo['fullPath']
938
- ? string
939
- : TAllRouteInfo['fullPath'])
940
-
941
- // type ValidToPath<
942
- // TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
943
- // TFrom = undefined,
944
- // > = TFrom extends undefined
945
- // ? TAllRouteInfo['fullPath'] | `...unsafe-relative-path (cast "as any")`
946
- // : LooseAutocomplete<'.' | TAllRouteInfo['fullPath']>
947
-
948
- export interface Router<
949
- TRouteConfig extends AnyRouteConfig = RouteConfig,
950
- TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
951
- > {
952
- options: PickAsRequired<
953
- RouterOptions<TRouteConfig>,
954
- 'stringifySearch' | 'parseSearch'
955
- >
956
- // Computed in this.update()
957
- basepath: string
958
- // Internal:
959
- allRouteInfo: TAllRouteInfo
960
- listeners: Listener[]
961
- location: Location
962
- navigateTimeout?: Timeout
963
- nextAction?: 'push' | 'replace'
964
- state: RouterState
965
- routeTree: Route<TAllRouteInfo, RouteInfo>
966
- routesById: RoutesById<TAllRouteInfo>
967
- navigationPromise: Promise<void>
968
- startedLoadingAt: number
969
- destroy: () => void
970
- resolveNavigation: () => void
971
- subscribe: (listener: Listener) => () => void
972
- notify: () => void
973
- mount: () => Promise<void>
974
- update: <TRouteConfig extends RouteConfig = RouteConfig>(
975
- opts?: RouterOptions<TRouteConfig>,
976
- ) => Router<TRouteConfig>
977
- buildRouteTree: (
978
- routeConfig: RouteConfig,
979
- ) => Route<TAllRouteInfo, AnyRouteInfo>
980
- parseLocation: (
981
- location: History['location'],
982
- previousLocation?: Location,
983
- ) => Location
984
- buildLocation: (dest: BuildNextOptions) => Location
985
- commitLocation: (next: Location, replace?: boolean) => Promise<void>
986
- buildNext: (opts: BuildNextOptions) => Location
987
- cancelMatches: () => void
988
- loadLocation: (next?: Location) => Promise<void>
989
- preloadCache: Record<string, PreloadCacheEntry>
990
- cleanPreloadCache: () => void
991
- getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
992
- id: TId,
993
- ) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
994
- loadRoute: (
995
- navigateOpts: BuildNextOptions,
996
- loaderOpts: { maxAge: number },
997
- ) => Promise<RouteMatch[]>
998
- matchRoutes: (
999
- pathname: string,
1000
- opts?: { strictParseParams?: boolean },
1001
- ) => RouteMatch[]
1002
- loadMatches: (
1003
- resolvedMatches: RouteMatch[],
1004
- loaderOpts?: { withPending?: boolean } & (
1005
- | { preload: true; maxAge: number }
1006
- | { preload?: false; maxAge?: never }
1007
- ),
1008
- ) => Promise<RouteMatch[]>
1009
- invalidateRoute: (opts: MatchLocation) => void
1010
- reload: () => Promise<void>
1011
- resolvePath: (from: string, path: string) => string
1012
- _navigate: (
1013
- location: BuildNextOptions & { replace?: boolean },
1014
- ) => Promise<void>
1015
- navigate: <
1016
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
1017
- TTo extends string = '.',
1018
- >(
1019
- opts: NavigateOptionsAbsolute<TAllRouteInfo, TFrom, TTo>,
1020
- ) => Promise<void>
1021
- matchRoute: <
1022
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
1023
- TTo extends string = '.',
1024
- >(
1025
- matchLocation: ToOptions<TAllRouteInfo, TFrom, TTo>,
1026
- opts?: MatchRouteOptions,
1027
- ) => boolean
1028
- buildLink: <
1029
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
1030
- TTo extends string = '.',
1031
- >(
1032
- opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
1033
- ) => LinkInfo
1034
- __experimental__createSnapshot: () => __Experimental__RouterSnapshot
1035
- }
1036
-
1037
- export function createRouter<
1038
- TRouteConfig extends AnyRouteConfig = RouteConfig,
1039
- TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
1040
- >(
1041
- userOptions?: RouterOptions<TRouteConfig>,
1042
- ): Router<TRouteConfig, TAllRouteInfo> {
1043
- const history = userOptions?.history || createDefaultHistory()
1044
-
1045
- const originalOptions = {
1046
- ...userOptions,
1047
- stringifySearch: userOptions?.stringifySearch ?? defaultStringifySearch,
1048
- parseSearch: userOptions?.parseSearch ?? defaultParseSearch,
1049
- }
1050
-
1051
- let router: Router<TRouteConfig, TAllRouteInfo> = {
1052
- options: originalOptions,
1053
- listeners: [],
1054
- // Resolved after construction
1055
- basepath: '',
1056
- routeTree: undefined!,
1057
- routesById: {} as any,
1058
- location: undefined!,
1059
- allRouteInfo: undefined!,
1060
- //
1061
- navigationPromise: Promise.resolve(),
1062
- resolveNavigation: () => {},
1063
- preloadCache: {},
1064
- state: {
1065
- status: 'idle',
1066
- location: null!,
1067
- matches: [],
1068
- actions: {},
1069
- loaderData: {} as any,
1070
- lastUpdated: Date.now(),
1071
- },
1072
- startedLoadingAt: Date.now(),
1073
- subscribe: (listener: Listener): (() => void) => {
1074
- router.listeners.push(listener as Listener)
1075
- return () => {
1076
- router.listeners = router.listeners.filter((x) => x !== listener)
1077
- }
1078
- },
1079
- getRoute: (id) => {
1080
- return router.routesById[id]
1081
- },
1082
- notify: (): void => {
1083
- router.state = {
1084
- ...router.state,
1085
- }
1086
- router.listeners.forEach((listener) => listener())
1087
- },
1088
-
1089
- mount: () => {
1090
- const next = router.buildLocation({
1091
- to: '.',
1092
- search: true,
1093
- hash: true,
1094
- })
1095
-
1096
- // If the current location isn't updated, trigger a navigation
1097
- // to the current location. Otherwise, load the current location.
1098
- if (next.href !== router.location.href) {
1099
- return router.commitLocation(next, true)
1100
- } else {
1101
- return router.loadLocation()
1102
- }
1103
- },
1104
-
1105
- update: (opts) => {
1106
- Object.assign(router.options, opts)
1107
-
1108
- const { basepath, routeConfig } = router.options
1109
-
1110
- router.basepath = cleanPath(`/${basepath ?? ''}`)
1111
-
1112
- if (routeConfig) {
1113
- router.routesById = {} as any
1114
- router.routeTree = router.buildRouteTree(routeConfig)
1115
- }
1116
-
1117
- return router as any
1118
- },
1119
-
1120
- destroy: history.listen((event) => {
1121
- router.loadLocation(router.parseLocation(event.location, router.location))
1122
- }),
1123
-
1124
- buildRouteTree: (rootRouteConfig: RouteConfig) => {
1125
- const recurseRoutes = (
1126
- routeConfigs: RouteConfig[],
1127
- parent?: Route<TAllRouteInfo, any>,
1128
- ): Route<TAllRouteInfo, any>[] => {
1129
- return routeConfigs.map((routeConfig) => {
1130
- const routeOptions = routeConfig.options
1131
- const route = createRoute(routeConfig, routeOptions, parent, router)
1132
-
1133
- // {
1134
- // pendingMs: routeOptions.pendingMs ?? router.defaultPendingMs,
1135
- // pendingMinMs: routeOptions.pendingMinMs ?? router.defaultPendingMinMs,
1136
- // }
1137
-
1138
- const existingRoute = (router.routesById as any)[route.routeId]
1139
-
1140
- if (existingRoute) {
1141
- if (process.env.NODE_ENV !== 'production') {
1142
- console.warn(
1143
- `Duplicate routes found with id: ${String(route.routeId)}`,
1144
- router.routesById,
1145
- route,
1146
- )
1147
- }
1148
- throw new Error()
1149
- }
1150
-
1151
- ;(router.routesById as any)[route.routeId] = route
1152
-
1153
- const children = routeConfig.children as RouteConfig[]
1154
-
1155
- route.childRoutes = children?.length
1156
- ? recurseRoutes(children, route)
1157
- : undefined
1158
-
1159
- return route
1160
- })
1161
- }
1162
-
1163
- const routes = recurseRoutes([rootRouteConfig])
1164
-
1165
- return routes[0]!
1166
- },
1167
-
1168
- parseLocation: (
1169
- location: History['location'],
1170
- previousLocation?: Location,
1171
- ): Location => {
1172
- const parsedSearch = router.options.parseSearch(location.search)
1173
-
1174
- return {
1175
- pathname: location.pathname,
1176
- searchStr: location.search,
1177
- search: replaceEqualDeep(previousLocation?.search, parsedSearch),
1178
- hash: location.hash.split('#').reverse()[0] ?? '',
1179
- href: `${location.pathname}${location.search}${location.hash}`,
1180
- state: location.state as LocationState,
1181
- key: location.key,
1182
- }
1183
- },
1184
-
1185
- buildLocation: (dest: BuildNextOptions = {}): Location => {
1186
- // const resolvedFrom: Location = {
1187
- // ...router.location,
1188
- const fromPathname = dest.fromCurrent
1189
- ? router.location.pathname
1190
- : dest.from ?? router.location.pathname
1191
-
1192
- let pathname = resolvePath(
1193
- router.basepath ?? '/',
1194
- fromPathname,
1195
- `${dest.to ?? '.'}`,
1196
- )
1197
-
1198
- const fromMatches = router.matchRoutes(router.location.pathname, {
1199
- strictParseParams: true,
1200
- })
1201
-
1202
- const toMatches = router.matchRoutes(pathname)
1203
-
1204
- const prevParams = last(fromMatches)?.params
1205
-
1206
- let nextParams =
1207
- (dest.params ?? true) === true
1208
- ? prevParams
1209
- : functionalUpdate(dest.params, prevParams)
1210
-
1211
- if (nextParams) {
1212
- toMatches
1213
- .map((d) => d.options.stringifyParams)
1214
- .filter(Boolean)
1215
- .forEach((fn) => {
1216
- Object.assign(nextParams!, fn!(nextParams!))
1217
- })
1218
- }
1219
-
1220
- pathname = interpolatePath(pathname, nextParams ?? {})
1221
-
1222
- // Pre filters first
1223
- const preFilteredSearch = dest.__preSearchFilters?.length
1224
- ? dest.__preSearchFilters.reduce(
1225
- (prev, next) => next(prev),
1226
- router.location.search,
1227
- )
1228
- : router.location.search
1229
-
1230
- // Then the link/navigate function
1231
- const destSearch =
1232
- dest.search === true
1233
- ? preFilteredSearch // Preserve resolvedFrom true
1234
- : dest.search
1235
- ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1236
- : dest.__preSearchFilters?.length
1237
- ? preFilteredSearch // Preserve resolvedFrom filters
1238
- : {}
1239
-
1240
- // Then post filters
1241
- const postFilteredSearch = dest.__postSearchFilters?.length
1242
- ? dest.__postSearchFilters.reduce(
1243
- (prev, next) => next(prev),
1244
- destSearch,
1245
- )
1246
- : destSearch
1247
-
1248
- const search = replaceEqualDeep(
1249
- router.location.search,
1250
- postFilteredSearch,
1251
- )
1252
-
1253
- const searchStr = router.options.stringifySearch(search)
1254
- let hash =
1255
- dest.hash === true
1256
- ? router.location.hash
1257
- : functionalUpdate(dest.hash, router.location.hash)
1258
- hash = hash ? `#${hash}` : ''
1259
-
1260
- return {
1261
- pathname,
1262
- search,
1263
- searchStr,
1264
- state: router.location.state,
1265
- hash,
1266
- href: `${pathname}${searchStr}${hash}`,
1267
- key: dest.key,
1268
- }
1269
- },
1270
-
1271
- commitLocation: (next: Location, replace?: boolean): Promise<void> => {
1272
- const id = '' + Date.now() + Math.random()
1273
-
1274
- if (router.navigateTimeout) clearTimeout(router.navigateTimeout)
1275
-
1276
- let nextAction: 'push' | 'replace' = 'replace'
1277
-
1278
- if (!replace) {
1279
- nextAction = 'push'
1280
- }
1281
-
1282
- const isSameUrl =
1283
- router.parseLocation(history.location).href === next.href
1284
-
1285
- if (isSameUrl && !next.key) {
1286
- nextAction = 'replace'
1287
- }
1288
-
1289
- if (nextAction === 'replace') {
1290
- history.replace(
1291
- {
1292
- pathname: next.pathname,
1293
- hash: next.hash,
1294
- search: next.searchStr,
1295
- },
1296
- {
1297
- id,
1298
- },
1299
- )
1300
- } else {
1301
- history.push(
1302
- {
1303
- pathname: next.pathname,
1304
- hash: next.hash,
1305
- search: next.searchStr,
1306
- },
1307
- {
1308
- id,
1309
- },
1310
- )
1311
- }
1312
-
1313
- router.navigationPromise = new Promise((resolve) => {
1314
- const previousNavigationResolve = router.resolveNavigation
1315
-
1316
- router.resolveNavigation = () => {
1317
- previousNavigationResolve()
1318
- resolve()
1319
- }
1320
- })
1321
-
1322
- return router.navigationPromise
1323
- },
1324
-
1325
- buildNext: (opts: BuildNextOptions) => {
1326
- const next = router.buildLocation(opts)
1327
-
1328
- const matches = router.matchRoutes(next.pathname)
1329
-
1330
- const __preSearchFilters = matches
1331
- .map((match) => match.options.preSearchFilters ?? [])
1332
- .flat()
1333
- .filter(Boolean)
1334
-
1335
- const __postSearchFilters = matches
1336
- .map((match) => match.options.postSearchFilters ?? [])
1337
- .flat()
1338
- .filter(Boolean)
1339
-
1340
- return router.buildLocation({
1341
- ...opts,
1342
- __preSearchFilters,
1343
- __postSearchFilters,
1344
- })
1345
- },
1346
-
1347
- cancelMatches: () => {
1348
- ;[
1349
- ...router.state.matches,
1350
- ...(router.state.pending?.matches ?? []),
1351
- ].forEach((match) => {
1352
- match.cancel()
1353
- })
1354
- },
1355
-
1356
- loadLocation: async (next?: Location) => {
1357
- const id = Math.random()
1358
- router.startedLoadingAt = id
1359
-
1360
- if (next) {
1361
- // Ingest the new location
1362
- router.location = next
1363
- }
1364
-
1365
- // Cancel any pending matches
1366
- router.cancelMatches()
1367
-
1368
- // Match the routes
1369
- const unloadedMatches = router.matchRoutes(location.pathname, {
1370
- strictParseParams: true,
1371
- })
1372
-
1373
- unloadedMatches.forEach((match, index) => {
1374
- const parent = unloadedMatches[index - 1]
1375
- const child = unloadedMatches[index + 1]
1376
-
1377
- if (parent) match.__.setParentMatch(parent)
1378
- if (child) match.__.addChildMatch(child)
1379
- })
1380
-
1381
- router.state = {
1382
- ...router.state,
1383
- pending: {
1384
- matches: unloadedMatches,
1385
- location: router.location,
1386
- },
1387
- }
1388
- router.notify()
1389
-
1390
- // Load the matches
1391
- const matches = await router.loadMatches(unloadedMatches, {
1392
- withPending: true,
1393
- })
1394
-
1395
- if (router.startedLoadingAt !== id) {
1396
- // Ignore side-effects of match loading
1397
- return router.navigationPromise
1398
- }
1399
-
1400
- const previousMatches = router.state.matches
1401
-
1402
- previousMatches
1403
- .filter((d) => {
1404
- return !matches.find((dd) => dd.matchId === d.matchId)
1405
- })
1406
- .forEach((d) => {
1407
- d.__.onExit?.({
1408
- params: d.params,
1409
- search: d.routeSearch,
1410
- })
1411
- })
1412
-
1413
- previousMatches
1414
- .filter((d) => {
1415
- return matches.find((dd) => dd.matchId === d.matchId)
1416
- })
1417
- .forEach((d) => {
1418
- d.options.onTransition?.({
1419
- params: d.params,
1420
- search: d.routeSearch,
1421
- })
1422
- })
1423
-
1424
- matches
1425
- .filter((d) => {
1426
- return !previousMatches.find((dd) => dd.matchId === d.matchId)
1427
- })
1428
- .forEach((d) => {
1429
- d.__.onExit = d.options.onMatch?.({
1430
- params: d.params,
1431
- search: d.search,
1432
- })
1433
- })
1434
-
1435
- router.state = {
1436
- ...router.state,
1437
- location: router.location,
1438
- matches,
1439
- pending: undefined,
1440
- }
1441
-
1442
- if (matches.some((d) => d.status === 'loading')) {
1443
- router.notify()
1444
- await Promise.all(
1445
- matches.map((d) => d.__.loaderPromise || Promise.resolve()),
1446
- )
1447
- }
1448
- if (router.startedLoadingAt !== id) {
1449
- // Ignore side-effects of match loading
1450
- return
1451
- }
1452
- router.notify()
1453
- router.resolveNavigation()
1454
- },
1455
-
1456
- cleanPreloadCache: () => {
1457
- const now = Date.now()
1458
-
1459
- Object.keys(router.preloadCache).forEach((matchId) => {
1460
- const entry = router.preloadCache[matchId]!
1461
-
1462
- // Don't remove loading matches
1463
- if (entry.match.status === 'loading') {
1464
- return
1465
- }
1466
-
1467
- // Do not remove successful matches that are still valid
1468
- if (
1469
- entry.match.updatedAt &&
1470
- entry.match.updatedAt + entry.maxAge > now
1471
- ) {
1472
- return
1473
- }
1474
-
1475
- // Everything else gets removed
1476
- delete router.preloadCache[matchId]
1477
- })
1478
- },
1479
-
1480
- loadRoute: async (
1481
- navigateOpts: BuildNextOptions = router.location,
1482
- loaderOpts: { maxAge: number },
1483
- ) => {
1484
- const next = router.buildNext(navigateOpts)
1485
- const matches = router.matchRoutes(next.pathname, {
1486
- strictParseParams: true,
1487
- })
1488
- await router.loadMatches(matches, {
1489
- preload: true,
1490
- maxAge: loaderOpts.maxAge,
1491
- })
1492
- return matches
1493
- },
1494
-
1495
- matchRoutes: (pathname, opts) => {
1496
- router.cleanPreloadCache()
1497
-
1498
- const matches: RouteMatch[] = []
1499
-
1500
- if (!router.routeTree) {
1501
- return matches
1502
- }
1503
-
1504
- const existingMatches = [
1505
- ...router.state.matches,
1506
- ...(router.state.pending?.matches ?? []),
1507
- ]
1508
-
1509
- const recurse = async (
1510
- routes: Route<any, any>[],
1511
- parentMatch?: RouteMatch,
1512
- ): Promise<void> => {
1513
- let params = parentMatch?.params ?? {}
1514
-
1515
- const filteredRoutes = router.options.filterRoutes?.(routes) ?? routes
1516
-
1517
- const route = filteredRoutes?.find((route) => {
1518
- const fuzzy = !!(route.routePath !== '/' || route.childRoutes?.length)
1519
- const matchParams = matchPathname(pathname, {
1520
- to: route.fullPath,
1521
- fuzzy,
1522
- caseSensitive:
1523
- route.options.caseSensitive ?? router.options.caseSensitive,
1524
- })
1525
-
1526
- if (matchParams) {
1527
- let parsedParams
1528
-
1529
- try {
1530
- parsedParams =
1531
- route.options.parseParams?.(matchParams!) ?? matchParams
1532
- } catch (err) {
1533
- if (opts?.strictParseParams) {
1534
- throw err
1535
- }
1536
- }
1537
-
1538
- params = {
1539
- ...params,
1540
- ...parsedParams,
1541
- }
1542
- }
1543
-
1544
- return !!matchParams
1545
- })
1546
-
1547
- if (!route) {
1548
- return
1549
- }
1550
-
1551
- const interpolatedPath = interpolatePath(route.routePath, params)
1552
- const matchId = interpolatePath(route.routeId, params, true)
1553
-
1554
- const match =
1555
- existingMatches.find((d) => d.matchId === matchId) ||
1556
- router.preloadCache[matchId]?.match ||
1557
- createRouteMatch(router, route, {
1558
- matchId,
1559
- params,
1560
- pathname: joinPaths([pathname, interpolatedPath]),
1561
- })
1562
-
1563
- matches.push(match)
1564
-
1565
- if (route.childRoutes?.length) {
1566
- recurse(route.childRoutes, match)
1567
- }
1568
- }
1569
-
1570
- recurse([router.routeTree])
1571
-
1572
- return matches
1573
- },
1574
-
1575
- loadMatches: async (
1576
- resolvedMatches: RouteMatch[],
1577
- loaderOpts?: { withPending?: boolean } & (
1578
- | { preload: true; maxAge: number }
1579
- | { preload?: false; maxAge?: never }
1580
- ),
1581
- ): Promise<RouteMatch[]> => {
1582
- const matchPromises = resolvedMatches.map(async (match) => {
1583
- // Validate the match (loads search params etc)
1584
- match.__.validate()
1585
-
1586
- // If this is a preload, add it to the preload cache
1587
- if (loaderOpts?.preload) {
1588
- router.preloadCache[match.matchId] = {
1589
- maxAge: loaderOpts?.maxAge,
1590
- match,
1591
- }
1592
- }
1593
-
1594
- // If the match is invalid, errored or idle, trigger it to load
1595
- if (
1596
- (match.status === 'success' && match.isInvalid) ||
1597
- match.status === 'error' ||
1598
- match.status === 'idle'
1599
- ) {
1600
- match.load()
1601
- }
1602
-
1603
- // If requested, start the pending timers
1604
- if (loaderOpts?.withPending) match.__.startPending()
1605
-
1606
- // Wait for the first sign of activity from the match
1607
- // This might be completion, error, or a pending state
1608
- await match.__.loadPromise
1609
- })
1610
-
1611
- router.notify()
1612
-
1613
- await Promise.all(matchPromises)
1614
-
1615
- return resolvedMatches
1616
- },
1617
-
1618
- invalidateRoute: (opts: MatchLocation) => {
1619
- const next = router.buildNext(opts)
1620
- const unloadedMatchIds = router
1621
- .matchRoutes(next.pathname)
1622
- .map((d) => d.matchId)
1623
- ;[
1624
- ...router.state.matches,
1625
- ...(router.state.pending?.matches ?? []),
1626
- ].forEach((match) => {
1627
- if (unloadedMatchIds.includes(match.matchId)) {
1628
- match.isInvalid = true
1629
- }
1630
- })
1631
- },
1632
-
1633
- reload: () =>
1634
- router._navigate({
1635
- fromCurrent: true,
1636
- replace: true,
1637
- search: true,
1638
- }),
1639
-
1640
- resolvePath: (from: string, path: string) => {
1641
- return resolvePath(router.basepath!, from, cleanPath(path))
1642
- },
1643
-
1644
- matchRoute: (location, opts) => {
1645
- // const location = router.buildNext(opts)
1646
-
1647
- location = {
1648
- ...location,
1649
- to: location.to
1650
- ? router.resolvePath(location.from ?? '', location.to)
1651
- : undefined,
1652
- }
1653
-
1654
- const next = router.buildNext(location)
1655
-
1656
- if (opts?.pending) {
1657
- if (!router.state.pending?.location) {
1658
- return false
1659
- }
1660
- return !!matchPathname(router.state.pending.location.pathname, {
1661
- ...opts,
1662
- to: next.pathname,
1663
- })
1664
- }
1665
-
1666
- return !!matchPathname(router.state.location.pathname, {
1667
- ...opts,
1668
- to: next.pathname,
1669
- })
1670
- },
1671
-
1672
- _navigate: (location: BuildNextOptions & { replace?: boolean }) => {
1673
- const next = router.buildNext(location)
1674
- return router.commitLocation(next, location.replace)
1675
- },
1676
-
1677
- navigate: async ({ from, to = '.', search, hash, replace }) => {
1678
- // If this link simply reloads the current route,
1679
- // make sure it has a new key so it will trigger a data refresh
1680
-
1681
- // If this `to` is a valid external URL, return
1682
- // null for LinkUtils
1683
- const toString = String(to)
1684
- const fromString = String(from)
1685
-
1686
- let isExternal
1687
-
1688
- try {
1689
- new URL(`${toString}`)
1690
- isExternal = true
1691
- } catch (e) {}
1692
-
1693
- if (isExternal) {
1694
- if (process.env.NODE_ENV !== 'production') {
1695
- throw new Error(
1696
- 'Attempting to navigate to external url with router.navigate!',
1697
- )
1698
- }
1699
- return
1700
- }
1701
-
1702
- return router._navigate({
1703
- from: fromString,
1704
- to: toString,
1705
- search,
1706
- hash,
1707
- })
1708
- },
1709
-
1710
- buildLink: ({
1711
- from,
1712
- to = '.',
1713
- search,
1714
- params,
1715
- hash,
1716
- target,
1717
- replace,
1718
- activeOptions,
1719
- preload,
1720
- preloadMaxAge: userPreloadMaxAge,
1721
- preloadDelay: userPreloadDelay,
1722
- disabled,
1723
- }) => {
1724
- // If this link simply reloads the current route,
1725
- // make sure it has a new key so it will trigger a data refresh
1726
-
1727
- // If this `to` is a valid external URL, return
1728
- // null for LinkUtils
1729
-
1730
- try {
1731
- new URL(`${to}`)
1732
- return {
1733
- type: 'external',
1734
- href: to,
1735
- }
1736
- } catch (e) {}
1737
-
1738
- const nextOpts = {
1739
- from,
1740
- to,
1741
- search,
1742
- params,
1743
- hash,
1744
- replace,
1745
- }
1746
-
1747
- const next = router.buildNext(nextOpts)
1748
-
1749
- preload = preload ?? router.options.defaultLinkPreload
1750
- const preloadMaxAge =
1751
- userPreloadMaxAge ?? router.options.defaultLinkPreloadMaxAge ?? 2000
1752
- const preloadDelay =
1753
- userPreloadDelay ?? router.options.defaultLinkPreloadDelay ?? 50
1754
-
1755
- // Compare path/hash for matches
1756
- const pathIsEqual = router.state.location.pathname === next.pathname
1757
- const currentPathSplit = router.state.location.pathname.split('/')
1758
- const nextPathSplit = next.pathname.split('/')
1759
- const pathIsFuzzyEqual = nextPathSplit.every(
1760
- (d, i) => d === currentPathSplit[i],
1761
- )
1762
- const hashIsEqual = router.state.location.hash === next.hash
1763
- // Combine the matches based on user options
1764
- const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual
1765
- const hashTest = activeOptions?.includeHash ? hashIsEqual : true
1766
-
1767
- // The final "active" test
1768
- const isActive = pathTest && hashTest
1769
-
1770
- // The click handler
1771
- const handleClick = (e: MouseEvent) => {
1772
- if (
1773
- !disabled &&
1774
- !isCtrlEvent(e) &&
1775
- !e.defaultPrevented &&
1776
- (!target || target === '_self') &&
1777
- e.button === 0
1778
- ) {
1779
- e.preventDefault()
1780
- if (pathIsEqual && !search && !hash) {
1781
- router.invalidateRoute(nextOpts)
1782
- }
1783
-
1784
- // All is well? Navigate!)
1785
- router._navigate(nextOpts)
1786
- }
1787
- }
1788
-
1789
- // The click handler
1790
- const handleFocus = (e: MouseEvent) => {
1791
- if (preload && preloadMaxAge > 0) {
1792
- router.loadRoute(nextOpts, { maxAge: preloadMaxAge })
1793
- }
1794
- }
1795
-
1796
- const handleEnter = (e: MouseEvent) => {
1797
- const target = (e.target || {}) as LinkCurrentTargetElement
1798
-
1799
- if (preload && preloadMaxAge > 0) {
1800
- if (target.preloadTimeout) {
1801
- return
1802
- }
1803
-
1804
- target.preloadTimeout = setTimeout(() => {
1805
- target.preloadTimeout = null
1806
- router.loadRoute(nextOpts, { maxAge: preloadMaxAge })
1807
- }, preloadDelay)
1808
- }
1809
- }
1810
-
1811
- const handleLeave = (e: MouseEvent) => {
1812
- const target = (e.target || {}) as LinkCurrentTargetElement
1813
-
1814
- if (target.preloadTimeout) {
1815
- clearTimeout(target.preloadTimeout)
1816
- target.preloadTimeout = null
1817
- }
1818
- }
1819
-
1820
- return {
1821
- type: 'internal',
1822
- next,
1823
- handleFocus,
1824
- handleClick,
1825
- handleEnter,
1826
- handleLeave,
1827
- isActive,
1828
- disabled,
1829
- }
1830
- },
1831
-
1832
- __experimental__createSnapshot: (): __Experimental__RouterSnapshot => {
1833
- return {
1834
- ...router.state,
1835
- matches: router.state.matches.map(
1836
- ({ routeLoaderData: loaderData, matchId }) => {
1837
- return {
1838
- matchId,
1839
- loaderData,
1840
- }
1841
- },
1842
- ),
1843
- }
1844
- },
1845
- }
1846
-
1847
- router.location = router.parseLocation(history.location)
1848
- // router.state.location = __experimental__snapshot?.location ?? router.location
1849
- router.state.location = router.location
1850
-
1851
- router.update(userOptions)
1852
-
1853
- // Allow frameworks to hook into the router creation
1854
- router.options.createRouter?.(router)
1855
-
1856
- // router.mount()
1857
-
1858
- return router
1859
- }
1860
-
1861
- type LinkCurrentTargetElement = {
1862
- preloadTimeout?: null | ReturnType<typeof setTimeout>
1863
- }
1864
-
1865
- export interface Route<
1866
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
1867
- TRouteInfo extends AnyRouteInfo = RouteInfo,
1868
- > {
1869
- routeId: TRouteInfo['id']
1870
- routePath: TRouteInfo['path']
1871
- fullPath: TRouteInfo['fullPath']
1872
- parentRoute?: AnyRoute
1873
- childRoutes?: AnyRoute[]
1874
- options: RouteOptions
1875
- router: Router<TAllRouteInfo['routeConfig'], TAllRouteInfo>
1876
- buildLink: <TTo extends string = '.'>(
1877
- options: // CheckRelativePath<
1878
- // TAllRouteInfo,
1879
- // TRouteInfo['fullPath'],
1880
- // NoInfer<TTo>
1881
- // > &
1882
- Omit<LinkOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>, 'from'>,
1883
- ) => LinkInfo
1884
- matchRoute: <
1885
- TTo extends string = '.',
1886
- TResolved extends string = ResolveRelativePath<TRouteInfo['id'], TTo>,
1887
- >(
1888
- matchLocation: // CheckRelativePath<
1889
- // TAllRouteInfo,
1890
- // TRouteInfo['fullPath'],
1891
- // NoInfer<TTo>
1892
- // > &
1893
- Omit<ToOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>, 'from'>,
1894
- opts?: MatchRouteOptions,
1895
- ) => RouteInfoByPath<TAllRouteInfo, TResolved>['allParams']
1896
- navigate: <TTo extends string = '.'>(
1897
- options: Omit<LinkOptions<TAllRouteInfo, TRouteInfo['id'], TTo>, 'from'>,
1898
- ) => Promise<void>
1899
- action: unknown extends TRouteInfo['actionResponse']
1900
- ?
1901
- | Action<TRouteInfo['actionPayload'], TRouteInfo['actionResponse']>
1902
- | undefined
1903
- : Action<TRouteInfo['actionPayload'], TRouteInfo['actionResponse']>
1904
- }
1905
-
1906
- export function createRoute<
1907
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
1908
- TRouteInfo extends AnyRouteInfo = RouteInfo,
1909
- >(
1910
- routeConfig: RouteConfig,
1911
- options: TRouteInfo['options'],
1912
- parent: undefined | Route<TAllRouteInfo, any>,
1913
- router: Router<TAllRouteInfo['routeConfig'], TAllRouteInfo>,
1914
- ): Route<TAllRouteInfo, TRouteInfo> {
1915
- // const id = (
1916
- // options.path === rootRouteId
1917
- // ? rootRouteId
1918
- // : joinPaths([
1919
- // parent!.id,
1920
- // `${options.path?.replace(/(.)\/$/, '$1')}`,
1921
- // ]).replace(new RegExp(`^${rootRouteId}`), '')
1922
- // ) as TRouteInfo['id']
1923
-
1924
- const { id: routeId, path: routePath, fullPath } = routeConfig
1925
-
1926
- const action =
1927
- router.state.actions[routeId] ||
1928
- (() => {
1929
- router.state.actions[routeId] = {
1930
- pending: [],
1931
- submit: async <T, U>(
1932
- submission: T,
1933
- actionOpts?: { invalidate?: boolean },
1934
- ) => {
1935
- if (!route) {
1936
- return
1937
- }
1938
-
1939
- const invalidate = actionOpts?.invalidate ?? true
1940
-
1941
- const actionState: ActionState<T, U> = {
1942
- submittedAt: Date.now(),
1943
- status: 'pending',
1944
- submission,
1945
- }
1946
-
1947
- action.latest = actionState
1948
- action.pending.push(actionState)
1949
-
1950
- router.state = {
1951
- ...router.state,
1952
- action: actionState,
1953
- }
1954
-
1955
- router.notify()
1956
-
1957
- try {
1958
- const res = await route.options.action?.(submission)
1959
- actionState.data = res as U
1960
- if (invalidate) {
1961
- router.invalidateRoute({ to: '.', fromCurrent: true })
1962
- await router.reload()
1963
- }
1964
- actionState.status = 'success'
1965
- return res
1966
- } catch (err) {
1967
- console.error(err)
1968
- actionState.error = err
1969
- actionState.status = 'error'
1970
- } finally {
1971
- action.pending = action.pending.filter((d) => d !== actionState)
1972
- if (actionState === router.state.action) {
1973
- router.state.action = undefined
1974
- }
1975
- router.notify()
1976
- }
1977
- },
1978
- }
1979
- return router.state.actions[routeId]!
1980
- })()
1981
-
1982
- let route: Route<TAllRouteInfo, TRouteInfo> = {
1983
- routeId,
1984
- routePath,
1985
- fullPath,
1986
- options,
1987
- router,
1988
- childRoutes: undefined!,
1989
- parentRoute: parent,
1990
- action,
1991
-
1992
- buildLink: (options) => {
1993
- return router.buildLink({
1994
- ...options,
1995
- from: fullPath,
1996
- } as any) as any
1997
- },
1998
-
1999
- navigate: (options) => {
2000
- return router.navigate({
2001
- ...options,
2002
- from: fullPath,
2003
- } as any) as any
2004
- },
2005
-
2006
- matchRoute: (matchLocation, opts) => {
2007
- return router.matchRoute(
2008
- {
2009
- ...matchLocation,
2010
- from: fullPath,
2011
- } as any,
2012
- opts,
2013
- )
2014
- },
2015
- }
2016
-
2017
- router.options.createRoute?.({ router, route })
2018
-
2019
- return route
2020
- }
2021
-
2022
- export type RelativeToPathAutoComplete<
2023
- AllPaths extends string,
2024
- TFrom extends string,
2025
- TTo extends string,
2026
- SplitPaths extends string[] = Split<AllPaths, false>,
2027
- > = TTo extends `..${infer _}`
2028
- ? SplitPaths extends [
2029
- ...Split<ResolveRelativePath<TFrom, TTo>, false>,
2030
- ...infer TToRest,
2031
- ]
2032
- ? `${CleanPath<
2033
- Join<
2034
- [
2035
- ...Split<TTo, false>,
2036
- ...(
2037
- | TToRest
2038
- | (Split<
2039
- ResolveRelativePath<TFrom, TTo>,
2040
- false
2041
- >['length'] extends 1
2042
- ? never
2043
- : ['../'])
2044
- ),
2045
- ]
2046
- >
2047
- >}`
2048
- : never
2049
- : TTo extends `./${infer RestTTo}`
2050
- ? SplitPaths extends [
2051
- ...Split<TFrom, false>,
2052
- ...Split<RestTTo, false>,
2053
- ...infer RestPath,
2054
- ]
2055
- ? `${TTo}${Join<RestPath>}`
2056
- : never
2057
- : './' | '../' | AllPaths
2058
-
2059
- type MapToUnknown<T extends object> = { [_ in keyof T]: unknown }
2060
-
2061
- // type GetMatchingPath<
2062
- // TFrom extends string,
2063
- // TTo extends string,
2064
- // SplitTTo = MapToUnknown<Split<TTo>>,
2065
- // > = Split<TFrom> extends [...infer Matching, ...Extract<SplitTTo, unknown[]>]
2066
- // ? Matching['length'] extends 0
2067
- // ? never
2068
- // : Matching
2069
- // : never
2070
-
2071
- // type Test1 = Split<'a/b/c'>
2072
- // // ^?
2073
- // type Test = Extract<MapToUnknown<Split<'../e'>>, unknown[]>
2074
- // // ^?
2075
- // type Test3 = Test1 extends [...infer Matching, ...Test] ? Matching : never
2076
- // // ^?
2077
- // type Test4 = ResolveRelativePath<'a/b/c', '../e'>
2078
- // // ^?
2079
-
2080
- export type NavigateOptionsAbsolute<
2081
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
2082
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
2083
- TTo extends string = '.',
2084
- > = ToOptions<TAllRouteInfo, TFrom, TTo> & {
2085
- // Whether to replace the current history stack instead of pushing a new one
2086
- replace?: boolean
2087
- }
2088
-
2089
- export type ToOptions<
2090
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
2091
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
2092
- TTo extends string = '.',
2093
- TResolvedTo = ResolveRelativePath<TFrom, NoInfer<TTo>>,
2094
- > = {
2095
- to?: ToPathOption<TAllRouteInfo, TFrom, TTo>
2096
- // The new has string or a function to update it
2097
- hash?: Updater<string>
2098
- // The source route path. This is automatically set when using route-level APIs, but for type-safe relative routing on the router itself, this is required
2099
- from?: TFrom
2100
- // // When using relative route paths, this option forces resolution from the current path, instead of the route API's path or `from` path
2101
- // fromCurrent?: boolean
2102
- } & SearchParamOptions<TAllRouteInfo, TFrom, TResolvedTo> &
2103
- PathParamOptions<TAllRouteInfo, TFrom, TResolvedTo>
2104
-
2105
- export type ToPathOption<
2106
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
2107
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
2108
- TTo extends string = '.',
2109
- > =
2110
- | TTo
2111
- | RelativeToPathAutoComplete<
2112
- TAllRouteInfo['fullPath'],
2113
- NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
2114
- NoInfer<TTo> & string
2115
- >
2116
-
2117
- export type ToIdOption<
2118
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
2119
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
2120
- TTo extends string = '.',
2121
- > =
2122
- | TTo
2123
- | RelativeToPathAutoComplete<
2124
- TAllRouteInfo['routeInfo']['id'],
2125
- NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
2126
- NoInfer<TTo> & string
2127
- >
2128
-
2129
- export type LinkOptions<
2130
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
2131
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
2132
- TTo extends string = '.',
2133
- > = NavigateOptionsAbsolute<TAllRouteInfo, TFrom, TTo> & {
2134
- // The standard anchor tag target attribute
2135
- target?: HTMLAnchorElement['target']
2136
- // Defaults to `{ exact: false, includeHash: false }`
2137
- activeOptions?: ActiveOptions
2138
- // If set, will preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there.
2139
- preload?: false | 'intent'
2140
- // When preloaded and set, will cache the preloaded result for this duration in milliseconds
2141
- preloadMaxAge?: number
2142
- // Delay intent preloading by this many milliseconds. If the intent exits before this delay, the preload will be cancelled.
2143
- preloadDelay?: number
2144
- // If true, will render the link without the href attribute
2145
- disabled?: boolean
2146
- }
2147
-
2148
- export type CheckRelativePath<
2149
- TAllRouteInfo extends AnyAllRouteInfo,
2150
- TFrom,
2151
- TTo,
2152
- > = TTo extends string
2153
- ? TFrom extends string
2154
- ? ResolveRelativePath<TFrom, TTo> extends TAllRouteInfo['fullPath']
2155
- ? {}
2156
- : {
2157
- Error: `${TFrom} + ${TTo} resolves to ${ResolveRelativePath<
2158
- TFrom,
2159
- TTo
2160
- >}, which is not a valid route path.`
2161
- 'Valid Route Paths': TAllRouteInfo['fullPath']
2162
- }
2163
- : {}
2164
- : {}
2165
-
2166
- export type ResolveRelativePath<
2167
- TFrom,
2168
- TTo = '.',
2169
- TRooted = false,
2170
- > = TFrom extends string
2171
- ? TTo extends string
2172
- ? TTo extends '.'
2173
- ? TFrom
2174
- : TTo extends `./`
2175
- ? Join<[TFrom, '/']>
2176
- : TTo extends `./${infer TRest}`
2177
- ? ResolveRelativePath<TFrom, TRest>
2178
- : TTo extends `/${infer TRest}`
2179
- ? TTo
2180
- : Split<TTo> extends ['..', ...infer ToRest]
2181
- ? Split<TFrom> extends [...infer FromRest, infer FromTail]
2182
- ? ResolveRelativePath<Join<FromRest>, Join<ToRest>>
2183
- : never
2184
- : Split<TTo> extends ['.', ...infer ToRest]
2185
- ? ResolveRelativePath<TFrom, Join<ToRest>>
2186
- : CleanPath<Join<['/', ...Split<TFrom>, ...Split<TTo>]>>
2187
- : never
2188
- : never
2189
-
2190
- export type RouteInfoById<
2191
- TAllRouteInfo extends AnyAllRouteInfo,
2192
- TId,
2193
- > = TId extends keyof TAllRouteInfo['routeInfoById']
2194
- ? IsAny<
2195
- TAllRouteInfo['routeInfoById'][TId]['id'],
2196
- RouteInfo,
2197
- TAllRouteInfo['routeInfoById'][TId]
2198
- >
2199
- : never
2200
-
2201
- export type RouteInfoByPath<
2202
- TAllRouteInfo extends AnyAllRouteInfo,
2203
- TPath,
2204
- > = TPath extends keyof TAllRouteInfo['routeInfoByFullPath']
2205
- ? IsAny<
2206
- TAllRouteInfo['routeInfoByFullPath'][TPath]['id'],
2207
- RouteInfo,
2208
- TAllRouteInfo['routeInfoByFullPath'][TPath]
2209
- >
2210
- : never
2211
-
2212
- type SearchParamOptions<
2213
- TAllRouteInfo extends AnyAllRouteInfo,
2214
- TFrom,
2215
- TTo,
2216
- TFromSchema = RouteInfoByPath<TAllRouteInfo, TFrom>['fullSearchSchema'],
2217
- TToSchema = RouteInfoByPath<TAllRouteInfo, TTo>['fullSearchSchema'],
2218
- > =
2219
- // If the next route search extend or cover the from route, params will be optional
2220
- StartsWith<TFrom, TTo> extends true
2221
- ? {
2222
- search?: SearchReducer<TFromSchema, TToSchema>
2223
- }
2224
- : // Optional search params? Allow it
2225
- keyof PickRequired<TToSchema> extends never
2226
- ? {
2227
- search?: SearchReducer<TFromSchema, TToSchema>
2228
- }
2229
- : {
2230
- // Must have required search params, enforce it
2231
- search: SearchReducer<TFromSchema, TToSchema>
2232
- }
2233
-
2234
- type SearchReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
2235
-
2236
- type PathParamOptions<
2237
- TAllRouteInfo extends AnyAllRouteInfo,
2238
- TFrom,
2239
- TTo,
2240
- TFromParams = RouteInfoByPath<TAllRouteInfo, TFrom>['allParams'],
2241
- TToParams = RouteInfoByPath<TAllRouteInfo, TTo>['allParams'],
2242
- > =
2243
- // If the next routes params extend or cover the from route, params will be optional
2244
- StartsWith<TFrom, TTo> extends true
2245
- ? {
2246
- params?: ParamsReducer<TFromParams, TToParams>
2247
- }
2248
- : // If the next route doesn't have params, warn if any have been passed
2249
- AnyPathParams extends TToParams
2250
- ? {
2251
- params?: ParamsReducer<TFromParams, Record<string, never>>
2252
- }
2253
- : // If the next route has params, enforce them
2254
- {
2255
- params: ParamsReducer<TFromParams, TToParams>
2256
- }
2257
-
2258
- type ParamsReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
2259
-
2260
- export interface RouteMatch<
2261
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
2262
- TRouteInfo extends AnyRouteInfo = RouteInfo,
2263
- > extends Route<TAllRouteInfo, TRouteInfo> {
2264
- matchId: string
2265
- pathname: string
2266
- params: AnyPathParams
2267
- parentMatch?: RouteMatch
2268
- childMatches: RouteMatch[]
2269
- routeSearch: TRouteInfo['searchSchema']
2270
- search: TRouteInfo['fullSearchSchema']
2271
- status: 'idle' | 'loading' | 'success' | 'error'
2272
- updatedAt?: number
2273
- error?: unknown
2274
- isInvalid: boolean
2275
- loaderData: TRouteInfo['loaderData']
2276
- routeLoaderData: TRouteInfo['routeLoaderData']
2277
- isFetching: boolean
2278
- isPending: boolean
2279
- __: {
2280
- element?: GetFrameworkGeneric<'Element', TRouteInfo['loaderData']>
2281
- errorElement?: GetFrameworkGeneric<'Element', TRouteInfo['loaderData']>
2282
- catchElement?: GetFrameworkGeneric<'Element', TRouteInfo['loaderData']>
2283
- pendingElement?: GetFrameworkGeneric<'Element', TRouteInfo['loaderData']>
2284
- loadPromise?: Promise<void>
2285
- loaderPromise?: Promise<void>
2286
- importPromise?: Promise<void>
2287
- elementsPromise?: Promise<void>
2288
- dataPromise?: Promise<void>
2289
- pendingTimeout?: Timeout
2290
- pendingMinTimeout?: Timeout
2291
- pendingMinPromise?: Promise<void>
2292
- onExit?:
2293
- | void
2294
- | ((matchContext: {
2295
- params: TRouteInfo['allParams']
2296
- search: TRouteInfo['fullSearchSchema']
2297
- }) => void)
2298
- abortController: AbortController
2299
- latestId: string
2300
- setParentMatch: (parentMatch: RouteMatch) => void
2301
- addChildMatch: (childMatch: RouteMatch) => void
2302
- validate: () => void
2303
- startPending: () => void
2304
- cancelPending: () => void
2305
- notify: () => void
2306
- resolve: () => void
2307
- }
2308
- cancel: () => void
2309
- load: () => Promise<void>
2310
- }
2311
-
2312
- export function createRouteMatch<
2313
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
2314
- TRouteInfo extends AnyRouteInfo = RouteInfo,
2315
- >(
2316
- router: Router<any, any>,
2317
- route: Route<TAllRouteInfo, TRouteInfo>,
2318
- opts: {
2319
- matchId: string
2320
- params: TRouteInfo['allParams']
2321
- pathname: string
2322
- },
2323
- ): RouteMatch<TAllRouteInfo, TRouteInfo> {
2324
- const routeMatch: RouteMatch<TAllRouteInfo, TRouteInfo> = {
2325
- ...route,
2326
- ...opts,
2327
- router,
2328
- routeSearch: {},
2329
- search: {},
2330
- childMatches: [],
2331
- status: 'idle',
2332
- routeLoaderData: {} as TRouteInfo['routeLoaderData'],
2333
- loaderData: {} as TRouteInfo['loaderData'],
2334
- isPending: false,
2335
- isFetching: false,
2336
- isInvalid: false,
2337
- __: {
2338
- abortController: new AbortController(),
2339
- latestId: '',
2340
- resolve: () => {},
2341
- notify: () => {
2342
- routeMatch.__.resolve()
2343
- routeMatch.router.notify()
2344
- },
2345
- startPending: () => {
2346
- const pendingMs =
2347
- routeMatch.options.pendingMs ?? router.options.defaultPendingMs
2348
- const pendingMinMs =
2349
- routeMatch.options.pendingMinMs ?? router.options.defaultPendingMinMs
2350
-
2351
- if (
2352
- routeMatch.__.pendingTimeout ||
2353
- routeMatch.status !== 'loading' ||
2354
- typeof pendingMs === 'undefined'
2355
- ) {
2356
- return
2357
- }
2358
-
2359
- routeMatch.__.pendingTimeout = setTimeout(() => {
2360
- routeMatch.isPending = true
2361
- routeMatch.__.resolve()
2362
- if (typeof pendingMinMs !== 'undefined') {
2363
- routeMatch.__.pendingMinPromise = new Promise(
2364
- (r) =>
2365
- (routeMatch.__.pendingMinTimeout = setTimeout(r, pendingMinMs)),
2366
- )
2367
- }
2368
- }, pendingMs)
2369
- },
2370
- cancelPending: () => {
2371
- routeMatch.isPending = false
2372
- clearTimeout(routeMatch.__.pendingTimeout)
2373
- clearTimeout(routeMatch.__.pendingMinTimeout)
2374
- delete routeMatch.__.pendingMinPromise
2375
- },
2376
- setParentMatch: (parentMatch?: RouteMatch) => {
2377
- routeMatch.parentMatch = parentMatch
2378
- },
2379
- addChildMatch: (childMatch: RouteMatch) => {
2380
- if (
2381
- routeMatch.childMatches.find((d) => d.matchId === childMatch.matchId)
2382
- ) {
2383
- return
2384
- }
2385
-
2386
- routeMatch.childMatches.push(childMatch)
2387
- },
2388
- validate: () => {
2389
- // Validate the search params and stabilize them
2390
- const parentSearch =
2391
- routeMatch.parentMatch?.search ?? router.location.search
2392
-
2393
- try {
2394
- const prevSearch = routeMatch.routeSearch
2395
-
2396
- let nextSearch = replaceEqualDeep(
2397
- prevSearch,
2398
- routeMatch.options.validateSearch?.(parentSearch),
2399
- )
2400
-
2401
- // Invalidate route matches when search param stability changes
2402
- if (prevSearch !== nextSearch) {
2403
- routeMatch.isInvalid = true
2404
- }
2405
-
2406
- routeMatch.routeSearch = nextSearch
2407
-
2408
- routeMatch.search = replaceEqualDeep(parentSearch, {
2409
- ...parentSearch,
2410
- ...nextSearch,
2411
- })
2412
- } catch (err: any) {
2413
- console.error(err)
2414
- const error = new (Error as any)('Invalid search params found', {
2415
- cause: err,
2416
- })
2417
- error.code = 'INVALID_SEARCH_PARAMS'
2418
- routeMatch.status = 'error'
2419
- routeMatch.error = error
2420
- // Do not proceed with loading the route
2421
- return
2422
- }
2423
- },
2424
- },
2425
- cancel: () => {
2426
- routeMatch.__.abortController?.abort()
2427
- routeMatch.__.cancelPending()
2428
- },
2429
- load: async () => {
2430
- const id = '' + Date.now() + Math.random()
2431
- routeMatch.__.latestId = id
2432
-
2433
- // If the match was in an error state, set it
2434
- // to a loading state again. Otherwise, keep it
2435
- // as loading or resolved
2436
- if (routeMatch.status === 'error' || routeMatch.status === 'idle') {
2437
- routeMatch.status = 'loading'
2438
- }
2439
-
2440
- // We started loading the route, so it's no longer invalid
2441
- routeMatch.isInvalid = false
2442
-
2443
- routeMatch.__.loadPromise = new Promise(async (resolve) => {
2444
- // We are now fetching, even if it's in the background of a
2445
- // resolved state
2446
- routeMatch.isFetching = true
2447
- routeMatch.__.resolve = resolve as () => void
2448
-
2449
- const loaderPromise = (async () => {
2450
- const importer = routeMatch.options.import
2451
-
2452
- // First, run any importers
2453
- if (importer) {
2454
- routeMatch.__.importPromise = importer({
2455
- params: routeMatch.params,
2456
- // search: routeMatch.search,
2457
- }).then((imported) => {
2458
- routeMatch.__ = {
2459
- ...routeMatch.__,
2460
- ...imported,
2461
- }
2462
- })
2463
- }
2464
-
2465
- // Wait for the importer to finish before
2466
- // attempting to load elements and data
2467
- await routeMatch.__.importPromise
2468
-
2469
- // Next, load the elements and data in parallel
2470
-
2471
- routeMatch.__.elementsPromise = (async () => {
2472
- // then run all element and data loaders in parallel
2473
- // For each element type, potentially load it asynchronously
2474
- const elementTypes = [
2475
- 'element',
2476
- 'errorElement',
2477
- 'catchElement',
2478
- 'pendingElement',
2479
- ] as const
2480
-
2481
- await Promise.all(
2482
- elementTypes.map(async (type) => {
2483
- const routeElement = routeMatch.options[type]
2484
-
2485
- if (routeMatch.__[type]) {
2486
- return
2487
- }
2488
-
2489
- if (typeof routeElement === 'function') {
2490
- const res = await (routeElement as any)(routeMatch)
2491
-
2492
- routeMatch.__[type] = res
2493
- } else {
2494
- routeMatch.__[type] = routeMatch.options[type] as any
2495
- }
2496
- }),
2497
- )
2498
- })()
2499
-
2500
- routeMatch.__.dataPromise = Promise.resolve().then(async () => {
2501
- try {
2502
- const data = await routeMatch.options.loader?.({
2503
- params: routeMatch.params,
2504
- search: routeMatch.routeSearch,
2505
- signal: routeMatch.__.abortController.signal,
2506
- })
2507
- if (id !== routeMatch.__.latestId) {
2508
- return routeMatch.__.loaderPromise
2509
- }
2510
-
2511
- routeMatch.routeLoaderData = replaceEqualDeep(
2512
- routeMatch.routeLoaderData,
2513
- data,
2514
- )
2515
-
2516
- cascadeLoaderData(routeMatch)
2517
- routeMatch.error = undefined
2518
- routeMatch.status = 'success'
2519
- routeMatch.updatedAt = Date.now()
2520
- } catch (err) {
2521
- if (id !== routeMatch.__.latestId) {
2522
- return routeMatch.__.loaderPromise
2523
- }
2524
-
2525
- if (process.env.NODE_ENV !== 'production') {
2526
- console.error(err)
2527
- }
2528
- routeMatch.error = err
2529
- routeMatch.status = 'error'
2530
- routeMatch.updatedAt = Date.now()
2531
- }
2532
- })
2533
-
2534
- try {
2535
- await Promise.all([
2536
- routeMatch.__.elementsPromise,
2537
- routeMatch.__.dataPromise,
2538
- ])
2539
- if (id !== routeMatch.__.latestId) {
2540
- return routeMatch.__.loaderPromise
2541
- }
2542
-
2543
- if (routeMatch.__.pendingMinPromise) {
2544
- await routeMatch.__.pendingMinPromise
2545
- delete routeMatch.__.pendingMinPromise
2546
- }
2547
- } finally {
2548
- if (id !== routeMatch.__.latestId) {
2549
- return routeMatch.__.loaderPromise
2550
- }
2551
- routeMatch.__.cancelPending()
2552
- routeMatch.isPending = false
2553
- routeMatch.isFetching = false
2554
- routeMatch.__.notify()
2555
- }
2556
- })()
2557
-
2558
- routeMatch.__.loaderPromise = loaderPromise
2559
- await loaderPromise
2560
-
2561
- if (id !== routeMatch.__.latestId) {
2562
- return routeMatch.__.loaderPromise
2563
- }
2564
- delete routeMatch.__.loaderPromise
2565
- })
2566
-
2567
- return await routeMatch.__.loadPromise
2568
- },
2569
- }
2570
-
2571
- return routeMatch
2572
- }
2573
-
2574
- function cascadeLoaderData(routeMatch: RouteMatch<any, any>) {
2575
- if (routeMatch.parentMatch) {
2576
- routeMatch.loaderData = replaceEqualDeep(routeMatch.loaderData, {
2577
- ...routeMatch.parentMatch.loaderData,
2578
- ...routeMatch.routeLoaderData,
2579
- })
2580
- }
2581
-
2582
- if (routeMatch.childMatches.length) {
2583
- routeMatch.childMatches.forEach((childMatch) => {
2584
- cascadeLoaderData(childMatch)
2585
- })
2586
- }
2587
- }
2588
-
2589
- export function matchPathname(
2590
- currentPathname: string,
2591
- matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>,
2592
- ): AnyPathParams | undefined {
2593
- const pathParams = matchByPath(currentPathname, matchLocation)
2594
- // const searchMatched = matchBySearch(currentLocation.search, matchLocation)
2595
-
2596
- if (matchLocation.to && !pathParams) {
2597
- return
2598
- }
2599
-
2600
- // if (matchLocation.search && !searchMatched) {
2601
- // return
2602
- // }
2603
-
2604
- return pathParams ?? {}
2605
- }
2606
-
2607
- function interpolatePath(
2608
- path: string | undefined,
2609
- params: any,
2610
- leaveWildcard?: boolean,
2611
- ) {
2612
- const interpolatedPathSegments = parsePathname(path)
2613
-
2614
- return joinPaths(
2615
- interpolatedPathSegments.map((segment) => {
2616
- if (segment.value === '*' && !leaveWildcard) {
2617
- return ''
2618
- }
2619
-
2620
- if (segment.type === 'param') {
2621
- return params![segment.value.substring(1)] ?? ''
2622
- }
2623
-
2624
- return segment.value
2625
- }),
2626
- )
2627
- }
2628
-
2629
- export function warning(cond: any, message: string): cond is true {
2630
- if (cond) {
2631
- if (typeof console !== 'undefined') console.warn(message)
2632
-
2633
- try {
2634
- throw new Error(message)
2635
- } catch {}
2636
- }
2637
-
2638
- return true
2639
- }
2640
-
2641
- function isFunction(d: any): d is Function {
2642
- return typeof d === 'function'
2643
- }
2644
-
2645
- export function functionalUpdate<TResult>(
2646
- updater?: Updater<TResult>,
2647
- previous?: TResult,
2648
- ) {
2649
- if (isFunction(updater)) {
2650
- return updater(previous as TResult)
2651
- }
2652
-
2653
- return updater
2654
- }
2655
-
2656
- function joinPaths(paths: (string | undefined)[]) {
2657
- return cleanPath(paths.filter(Boolean).join('/'))
2658
- }
2659
-
2660
- function cleanPath(path: string) {
2661
- // remove double slashes
2662
- return path.replace(/\/{2,}/g, '/')
2663
- }
2664
-
2665
- function trimPathLeft(path: string) {
2666
- return path === '/' ? path : path.replace(/^\/{1,}/, '')
2667
- }
2668
-
2669
- function trimPathRight(path: string) {
2670
- return path === '/' ? path : path.replace(/\/{1,}$/, '')
2671
- }
2672
-
2673
- function trimPath(path: string) {
2674
- return trimPathRight(trimPathLeft(path))
2675
- }
2676
-
2677
- export function matchByPath(
2678
- from: string,
2679
- matchLocation: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,
2680
- ): Record<string, string> | undefined {
2681
- const baseSegments = parsePathname(from)
2682
- const routeSegments = parsePathname(`${matchLocation.to ?? '*'}`)
2683
-
2684
- const params: Record<string, string> = {}
2685
-
2686
- let isMatch = (() => {
2687
- for (
2688
- let i = 0;
2689
- i < Math.max(baseSegments.length, routeSegments.length);
2690
- i++
2691
- ) {
2692
- const baseSegment = baseSegments[i]
2693
- const routeSegment = routeSegments[i]
2694
-
2695
- const isLastRouteSegment = i === routeSegments.length - 1
2696
- const isLastBaseSegment = i === baseSegments.length - 1
2697
-
2698
- if (routeSegment) {
2699
- if (routeSegment.type === 'wildcard') {
2700
- if (baseSegment?.value) {
2701
- params['*'] = joinPaths(baseSegments.slice(i).map((d) => d.value))
2702
- return true
2703
- }
2704
- return false
2705
- }
2706
-
2707
- if (routeSegment.type === 'pathname') {
2708
- if (routeSegment.value === '/' && !baseSegment?.value) {
2709
- return true
2710
- }
2711
-
2712
- if (baseSegment) {
2713
- if (matchLocation.caseSensitive) {
2714
- if (routeSegment.value !== baseSegment.value) {
2715
- return false
2716
- }
2717
- } else if (
2718
- routeSegment.value.toLowerCase() !==
2719
- baseSegment.value.toLowerCase()
2720
- ) {
2721
- return false
2722
- }
2723
- }
2724
- }
2725
-
2726
- if (!baseSegment) {
2727
- return false
2728
- }
2729
-
2730
- if (routeSegment.type === 'param') {
2731
- if (baseSegment?.value === '/') {
2732
- return false
2733
- }
2734
- if (!baseSegment.value.startsWith(':')) {
2735
- params[routeSegment.value.substring(1)] = baseSegment.value
2736
- }
2737
- }
2738
- }
2739
-
2740
- if (isLastRouteSegment && !isLastBaseSegment) {
2741
- return !!matchLocation.fuzzy
2742
- }
2743
- }
2744
- return true
2745
- })()
2746
-
2747
- return isMatch ? (params as Record<string, string>) : undefined
2748
- }
2749
-
2750
- // function matchBySearch(
2751
- // search: SearchSchema,
2752
- // matchLocation: MatchLocation,
2753
- // ) {
2754
- // return !!(matchLocation.search && matchLocation.search(search))
2755
- // }
2756
-
2757
- export function parsePathname(pathname?: string): Segment[] {
2758
- if (!pathname) {
2759
- return []
2760
- }
2761
-
2762
- pathname = cleanPath(pathname)
2763
-
2764
- const segments: Segment[] = []
2765
-
2766
- if (pathname.slice(0, 1) === '/') {
2767
- pathname = pathname.substring(1)
2768
- segments.push({
2769
- type: 'pathname',
2770
- value: '/',
2771
- })
2772
- }
2773
-
2774
- if (!pathname) {
2775
- return segments
2776
- }
2777
-
2778
- // Remove empty segments and '.' segments
2779
- const split = pathname.split('/').filter(Boolean)
2780
-
2781
- segments.push(
2782
- ...split.map((part): Segment => {
2783
- if (part.startsWith('*')) {
2784
- return {
2785
- type: 'wildcard',
2786
- value: part,
2787
- }
2788
- }
2789
-
2790
- if (part.charAt(0) === ':') {
2791
- return {
2792
- type: 'param',
2793
- value: part,
2794
- }
2795
- }
2796
-
2797
- return {
2798
- type: 'pathname',
2799
- value: part,
2800
- }
2801
- }),
2802
- )
2803
-
2804
- if (pathname.slice(-1) === '/') {
2805
- pathname = pathname.substring(1)
2806
- segments.push({
2807
- type: 'pathname',
2808
- value: '/',
2809
- })
2810
- }
2811
-
2812
- return segments
2813
- }
2814
-
2815
- export function resolvePath(basepath: string, base: string, to: string) {
2816
- base = base.replace(new RegExp(`^${basepath}`), '/')
2817
- to = to.replace(new RegExp(`^${basepath}`), '/')
2818
-
2819
- let baseSegments = parsePathname(base)
2820
- const toSegments = parsePathname(to)
2821
-
2822
- toSegments.forEach((toSegment, index) => {
2823
- if (toSegment.value === '/') {
2824
- if (!index) {
2825
- // Leading slash
2826
- baseSegments = [toSegment]
2827
- } else if (index === toSegments.length - 1) {
2828
- // Trailing Slash
2829
- baseSegments.push(toSegment)
2830
- } else {
2831
- // ignore inter-slashes
2832
- }
2833
- } else if (toSegment.value === '..') {
2834
- // Extra trailing slash? pop it off
2835
- if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
2836
- baseSegments.pop()
2837
- }
2838
- baseSegments.pop()
2839
- } else if (toSegment.value === '.') {
2840
- return
2841
- } else {
2842
- baseSegments.push(toSegment)
2843
- }
2844
- })
2845
-
2846
- const joined = joinPaths([basepath, ...baseSegments.map((d) => d.value)])
2847
-
2848
- return cleanPath(joined)
2849
- }
2850
-
2851
- /**
2852
- * This function returns `a` if `b` is deeply equal.
2853
- * If not, it will replace any deeply equal children of `b` with those of `a`.
2854
- * This can be used for structural sharing between JSON values for example.
2855
- */
2856
- export function replaceEqualDeep(prev: any, next: any) {
2857
- if (prev === next) {
2858
- return prev
2859
- }
2860
-
2861
- const array = Array.isArray(prev) && Array.isArray(next)
2862
-
2863
- if (array || (isPlainObject(prev) && isPlainObject(next))) {
2864
- const aSize = array ? prev.length : Object.keys(prev).length
2865
- const bItems = array ? next : Object.keys(next)
2866
- const bSize = bItems.length
2867
- const copy: any = array ? [] : {}
2868
-
2869
- let equalItems = 0
2870
-
2871
- for (let i = 0; i < bSize; i++) {
2872
- const key = array ? i : bItems[i]
2873
- copy[key] = replaceEqualDeep(prev[key], next[key])
2874
- if (copy[key] === prev[key]) {
2875
- equalItems++
2876
- }
2877
- }
2878
-
2879
- return aSize === bSize && equalItems === aSize ? prev : copy
2880
- }
2881
-
2882
- return next
2883
- }
2884
-
2885
- // Copied from: https://github.com/jonschlinkert/is-plain-object
2886
- function isPlainObject(o: any) {
2887
- if (!hasObjectPrototype(o)) {
2888
- return false
2889
- }
2890
-
2891
- // If has modified constructor
2892
- const ctor = o.constructor
2893
- if (typeof ctor === 'undefined') {
2894
- return true
2895
- }
2896
-
2897
- // If has modified prototype
2898
- const prot = ctor.prototype
2899
- if (!hasObjectPrototype(prot)) {
2900
- return false
2901
- }
2902
-
2903
- // If constructor does not have an Object-specific method
2904
- if (!prot.hasOwnProperty('isPrototypeOf')) {
2905
- return false
2906
- }
2907
-
2908
- // Most likely a plain Object
2909
- return true
2910
- }
2911
-
2912
- function hasObjectPrototype(o: any) {
2913
- return Object.prototype.toString.call(o) === '[object Object]'
2914
- }
2915
-
2916
- export const defaultParseSearch = parseSearchWith(JSON.parse)
2917
- export const defaultStringifySearch = stringifySearchWith(JSON.stringify)
2918
-
2919
- export function parseSearchWith(parser: (str: string) => any) {
2920
- return (searchStr: string): AnySearchSchema => {
2921
- if (searchStr.substring(0, 1) === '?') {
2922
- searchStr = searchStr.substring(1)
2923
- }
2924
-
2925
- let query: Record<string, unknown> = decode(searchStr)
2926
-
2927
- // Try to parse any query params that might be json
2928
- for (let key in query) {
2929
- const value = query[key]
2930
- if (typeof value === 'string') {
2931
- try {
2932
- query[key] = parser(value)
2933
- } catch (err) {
2934
- //
2935
- }
2936
- }
2937
- }
2938
-
2939
- return query
2940
- }
2941
- }
2942
-
2943
- export function stringifySearchWith(stringify: (search: any) => string) {
2944
- return (search: Record<string, any>) => {
2945
- search = { ...search }
2946
-
2947
- if (search) {
2948
- Object.keys(search).forEach((key) => {
2949
- const val = search[key]
2950
- if (typeof val === 'undefined' || val === undefined) {
2951
- delete search[key]
2952
- } else if (val && typeof val === 'object' && val !== null) {
2953
- try {
2954
- search[key] = stringify(val)
2955
- } catch (err) {
2956
- // silent
2957
- }
2958
- }
2959
- })
2960
- }
2961
-
2962
- const searchStr = encode(search as Record<string, string>).toString()
2963
-
2964
- return searchStr ? `?${searchStr}` : ''
2965
- }
2966
- }
2967
-
2968
- function isCtrlEvent(e: MouseEvent) {
2969
- return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
2970
- }
2971
-
2972
- export function last<T>(arr: T[]) {
2973
- return arr[arr.length - 1]
2974
- }
7
+ export { default as invariant } from 'tiny-invariant'
8
+
9
+ export * from './frameworks'
10
+ export * from './index'
11
+ export * from './link'
12
+ export * from './path'
13
+ export * from './qss'
14
+ export * from './route'
15
+ export * from './routeConfig'
16
+ export * from './routeInfo'
17
+ export * from './routeMatch'
18
+ export * from './router'
19
+ export * from './searchParams'
20
+ export * from './utils'