@tanstack/router-core 0.0.1-alpha.6 → 0.0.1-alpha.8

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