@tanstack/router-core 0.0.1-alpha.5 → 0.0.1-alpha.7

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 -1451
  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 +787 -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 +1304 -1238
  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 -333
  24. package/build/umd/index.development.js +1313 -1238
  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 -3054
  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 +1182 -0
  39. package/src/searchParams.ts +54 -0
  40. package/src/utils.ts +157 -0
package/src/index.ts CHANGED
@@ -1,3059 +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
- // If the location.href has changed
1414
-
1415
- // Ingest the new location
1416
- router.location = next
1417
- }
1418
-
1419
- // Clear out old actions
1420
- router.removeActionQueue.forEach(({ action, actionState }) => {
1421
- if (router.state.currentAction === actionState) {
1422
- router.state.currentAction = undefined
1423
- }
1424
- if (action.current === actionState) {
1425
- action.current = undefined
1426
- }
1427
- })
1428
- router.removeActionQueue = []
1429
-
1430
- // Cancel any pending matches
1431
- router.cancelMatches()
1432
-
1433
- // Match the routes
1434
- const unloadedMatches = router.matchRoutes(location.pathname, {
1435
- strictParseParams: true,
1436
- })
1437
-
1438
- router.state = {
1439
- ...router.state,
1440
- pending: {
1441
- matches: unloadedMatches,
1442
- location: router.location,
1443
- },
1444
- }
1445
- router.notify()
1446
-
1447
- // Load the matches
1448
- const matches = await router.loadMatches(unloadedMatches, {
1449
- withPending: true,
1450
- })
1451
-
1452
- if (router.startedLoadingAt !== id) {
1453
- // Ignore side-effects of match loading
1454
- return router.navigationPromise
1455
- }
1456
-
1457
- const previousMatches = router.state.matches
1458
-
1459
- previousMatches
1460
- .filter((d) => {
1461
- return !matches.find((dd) => dd.matchId === d.matchId)
1462
- })
1463
- .forEach((d) => {
1464
- d.__.onExit?.({
1465
- params: d.params,
1466
- search: d.routeSearch,
1467
- })
1468
- })
1469
-
1470
- previousMatches
1471
- .filter((d) => {
1472
- return matches.find((dd) => dd.matchId === d.matchId)
1473
- })
1474
- .forEach((d) => {
1475
- d.options.onTransition?.({
1476
- params: d.params,
1477
- search: d.routeSearch,
1478
- })
1479
- })
1480
-
1481
- matches
1482
- .filter((d) => {
1483
- return !previousMatches.find((dd) => dd.matchId === d.matchId)
1484
- })
1485
- .forEach((d) => {
1486
- d.__.onExit = d.options.onMatch?.({
1487
- params: d.params,
1488
- search: d.search,
1489
- })
1490
- })
1491
-
1492
- router.state = {
1493
- ...router.state,
1494
- location: router.location,
1495
- matches,
1496
- pending: undefined,
1497
- }
1498
-
1499
- if (matches.some((d) => d.status === 'loading')) {
1500
- router.notify()
1501
- await Promise.all(
1502
- matches.map((d) => d.__.loaderPromise || Promise.resolve()),
1503
- )
1504
- }
1505
- if (router.startedLoadingAt !== id) {
1506
- // Ignore side-effects of match loading
1507
- return
1508
- }
1509
- router.notify()
1510
- router.resolveNavigation()
1511
- },
1512
-
1513
- cleanPreloadCache: () => {
1514
- const now = Date.now()
1515
-
1516
- Object.keys(router.preloadCache).forEach((matchId) => {
1517
- const entry = router.preloadCache[matchId]!
1518
-
1519
- // Don't remove loading matches
1520
- if (entry.match.status === 'loading') {
1521
- return
1522
- }
1523
-
1524
- // Do not remove successful matches that are still valid
1525
- if (
1526
- entry.match.updatedAt &&
1527
- entry.match.updatedAt + entry.maxAge > now
1528
- ) {
1529
- return
1530
- }
1531
-
1532
- // Everything else gets removed
1533
- delete router.preloadCache[matchId]
1534
- })
1535
- },
1536
-
1537
- loadRoute: async (
1538
- navigateOpts: BuildNextOptions = router.location,
1539
- loaderOpts: { maxAge: number },
1540
- ) => {
1541
- const next = router.buildNext(navigateOpts)
1542
- const matches = router.matchRoutes(next.pathname, {
1543
- strictParseParams: true,
1544
- })
1545
- await router.loadMatches(matches, {
1546
- preload: true,
1547
- maxAge: loaderOpts.maxAge,
1548
- })
1549
- return matches
1550
- },
1551
-
1552
- matchRoutes: (pathname, opts) => {
1553
- router.cleanPreloadCache()
1554
-
1555
- const matches: RouteMatch[] = []
1556
-
1557
- if (!router.routeTree) {
1558
- return matches
1559
- }
1560
-
1561
- const existingMatches = [
1562
- ...router.state.matches,
1563
- ...(router.state.pending?.matches ?? []),
1564
- ]
1565
-
1566
- const recurse = async (routes: Route<any, any>[]): Promise<void> => {
1567
- const parentMatch = last(matches)
1568
- let params = parentMatch?.params ?? {}
1569
-
1570
- const filteredRoutes = router.options.filterRoutes?.(routes) ?? routes
1571
-
1572
- let foundRoutes: Route[] = []
1573
-
1574
- const findMatchInRoutes = (parentRoutes: Route[], routes: Route[]) => {
1575
- routes.some((route) => {
1576
- if (!route.routePath && route.childRoutes?.length) {
1577
- return findMatchInRoutes(
1578
- [...foundRoutes, route],
1579
- route.childRoutes,
1580
- )
1581
- }
1582
-
1583
- const fuzzy = !!(
1584
- route.routePath !== '/' || route.childRoutes?.length
1585
- )
1586
-
1587
- const matchParams = matchPathname(pathname, {
1588
- to: route.fullPath,
1589
- fuzzy,
1590
- caseSensitive:
1591
- route.options.caseSensitive ?? router.options.caseSensitive,
1592
- })
1593
-
1594
- if (matchParams) {
1595
- let parsedParams
1596
-
1597
- try {
1598
- parsedParams =
1599
- route.options.parseParams?.(matchParams!) ?? matchParams
1600
- } catch (err) {
1601
- if (opts?.strictParseParams) {
1602
- throw err
1603
- }
1604
- }
1605
-
1606
- params = {
1607
- ...params,
1608
- ...parsedParams,
1609
- }
1610
- }
1611
-
1612
- if (!!matchParams) {
1613
- foundRoutes = [...parentRoutes, route]
1614
- }
1615
-
1616
- return !!foundRoutes.length
1617
- })
1618
-
1619
- return !!foundRoutes.length
1620
- }
1621
-
1622
- findMatchInRoutes([], filteredRoutes)
1623
-
1624
- if (!foundRoutes.length) {
1625
- return
1626
- }
1627
-
1628
- foundRoutes.forEach((foundRoute) => {
1629
- const interpolatedPath = interpolatePath(foundRoute.routePath, params)
1630
- const matchId = interpolatePath(foundRoute.routeId, params, true)
1631
-
1632
- const match =
1633
- existingMatches.find((d) => d.matchId === matchId) ||
1634
- router.preloadCache[matchId]?.match ||
1635
- createRouteMatch(router, foundRoute, {
1636
- matchId,
1637
- params,
1638
- pathname: joinPaths([pathname, interpolatedPath]),
1639
- })
1640
-
1641
- matches.push(match)
1642
- })
1643
-
1644
- const foundRoute = last(foundRoutes)!
1645
-
1646
- if (foundRoute.childRoutes?.length) {
1647
- recurse(foundRoute.childRoutes)
1648
- }
1649
- }
1650
-
1651
- recurse([router.routeTree])
1652
-
1653
- cascadeLoaderData(matches)
1654
-
1655
- return matches
1656
- },
1657
-
1658
- loadMatches: async (
1659
- resolvedMatches: RouteMatch[],
1660
- loaderOpts?: { withPending?: boolean } & (
1661
- | { preload: true; maxAge: number }
1662
- | { preload?: false; maxAge?: never }
1663
- ),
1664
- ): Promise<RouteMatch[]> => {
1665
- const matchPromises = resolvedMatches.map(async (match) => {
1666
- // Validate the match (loads search params etc)
1667
- match.__.validate()
1668
-
1669
- // If this is a preload, add it to the preload cache
1670
- if (loaderOpts?.preload) {
1671
- router.preloadCache[match.matchId] = {
1672
- maxAge: loaderOpts?.maxAge,
1673
- match,
1674
- }
1675
- }
1676
-
1677
- // If the match is invalid, errored or idle, trigger it to load
1678
- if (
1679
- (match.status === 'success' && match.isInvalid) ||
1680
- match.status === 'error' ||
1681
- match.status === 'idle'
1682
- ) {
1683
- match.load()
1684
- }
1685
-
1686
- // If requested, start the pending timers
1687
- if (loaderOpts?.withPending) match.__.startPending()
1688
-
1689
- // Wait for the first sign of activity from the match
1690
- // This might be completion, error, or a pending state
1691
- await match.__.loadPromise
1692
- })
1693
-
1694
- router.notify()
1695
-
1696
- await Promise.all(matchPromises)
1697
-
1698
- return resolvedMatches
1699
- },
1700
-
1701
- invalidateRoute: (opts: MatchLocation) => {
1702
- const next = router.buildNext(opts)
1703
- const unloadedMatchIds = router
1704
- .matchRoutes(next.pathname)
1705
- .map((d) => d.matchId)
1706
- ;[
1707
- ...router.state.matches,
1708
- ...(router.state.pending?.matches ?? []),
1709
- ].forEach((match) => {
1710
- if (unloadedMatchIds.includes(match.matchId)) {
1711
- match.isInvalid = true
1712
- }
1713
- })
1714
- },
1715
-
1716
- reload: () =>
1717
- router._navigate({
1718
- fromCurrent: true,
1719
- replace: true,
1720
- search: true,
1721
- }),
1722
-
1723
- resolvePath: (from: string, path: string) => {
1724
- return resolvePath(router.basepath!, from, cleanPath(path))
1725
- },
1726
-
1727
- matchRoute: (location, opts) => {
1728
- // const location = router.buildNext(opts)
1729
-
1730
- location = {
1731
- ...location,
1732
- to: location.to
1733
- ? router.resolvePath(location.from ?? '', location.to)
1734
- : undefined,
1735
- }
1736
-
1737
- const next = router.buildNext(location)
1738
-
1739
- if (opts?.pending) {
1740
- if (!router.state.pending?.location) {
1741
- return false
1742
- }
1743
- return !!matchPathname(router.state.pending.location.pathname, {
1744
- ...opts,
1745
- to: next.pathname,
1746
- })
1747
- }
1748
-
1749
- return !!matchPathname(router.state.location.pathname, {
1750
- ...opts,
1751
- to: next.pathname,
1752
- })
1753
- },
1754
-
1755
- _navigate: (location: BuildNextOptions & { replace?: boolean }) => {
1756
- const next = router.buildNext(location)
1757
- return router.commitLocation(next, location.replace)
1758
- },
1759
-
1760
- navigate: async ({ from, to = '.', search, hash, replace }) => {
1761
- // If this link simply reloads the current route,
1762
- // make sure it has a new key so it will trigger a data refresh
1763
-
1764
- // If this `to` is a valid external URL, return
1765
- // null for LinkUtils
1766
- const toString = String(to)
1767
- const fromString = String(from)
1768
-
1769
- let isExternal
1770
-
1771
- try {
1772
- new URL(`${toString}`)
1773
- isExternal = true
1774
- } catch (e) {}
1775
-
1776
- invariant(
1777
- !isExternal,
1778
- 'Attempting to navigate to external url with router.navigate!',
1779
- )
1780
-
1781
- return router._navigate({
1782
- from: fromString,
1783
- to: toString,
1784
- search,
1785
- hash,
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> = TTo | ((current: TFrom) => TTo)
2323
-
2324
- type PathParamOptions<
2325
- TAllRouteInfo extends AnyAllRouteInfo,
2326
- TFrom,
2327
- TTo,
2328
- TFromParams = RouteInfoByPath<TAllRouteInfo, TFrom>['allParams'],
2329
- TToParams = RouteInfoByPath<TAllRouteInfo, TTo>['allParams'],
2330
- > =
2331
- // If the next routes params extend or cover the from route, params will be optional
2332
- StartsWith<TFrom, TTo> extends true
2333
- ? {
2334
- params?: ParamsReducer<TFromParams, TToParams>
2335
- }
2336
- : // If the next route doesn't have params, warn if any have been passed
2337
- AnyPathParams extends TToParams
2338
- ? {
2339
- params?: ParamsReducer<TFromParams, Record<string, never>>
2340
- }
2341
- : // If the next route has params, enforce them
2342
- {
2343
- params: ParamsReducer<TFromParams, TToParams>
2344
- }
2345
-
2346
- type ParamsReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
2347
-
2348
- export interface RouteMatch<
2349
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
2350
- TRouteInfo extends AnyRouteInfo = RouteInfo,
2351
- > extends Route<TAllRouteInfo, TRouteInfo> {
2352
- matchId: string
2353
- pathname: string
2354
- params: AnyPathParams
2355
- parentMatch?: RouteMatch
2356
- childMatches: RouteMatch[]
2357
- routeSearch: TRouteInfo['searchSchema']
2358
- search: TRouteInfo['fullSearchSchema']
2359
- status: 'idle' | 'loading' | 'success' | 'error'
2360
- updatedAt?: number
2361
- error?: unknown
2362
- isInvalid: boolean
2363
- loaderData: TRouteInfo['loaderData']
2364
- routeLoaderData: TRouteInfo['routeLoaderData']
2365
- isFetching: boolean
2366
- isPending: boolean
2367
- __: {
2368
- element?: GetFrameworkGeneric<'Element', TRouteInfo['loaderData']>
2369
- errorElement?: GetFrameworkGeneric<'Element', TRouteInfo['loaderData']>
2370
- catchElement?: GetFrameworkGeneric<'Element', TRouteInfo['loaderData']>
2371
- pendingElement?: GetFrameworkGeneric<'Element', TRouteInfo['loaderData']>
2372
- loadPromise?: Promise<void>
2373
- loaderPromise?: Promise<void>
2374
- importPromise?: Promise<void>
2375
- elementsPromise?: Promise<void>
2376
- dataPromise?: Promise<void>
2377
- pendingTimeout?: Timeout
2378
- pendingMinTimeout?: Timeout
2379
- pendingMinPromise?: Promise<void>
2380
- onExit?:
2381
- | void
2382
- | ((matchContext: {
2383
- params: TRouteInfo['allParams']
2384
- search: TRouteInfo['fullSearchSchema']
2385
- }) => void)
2386
- abortController: AbortController
2387
- latestId: string
2388
- // setParentMatch: (parentMatch: RouteMatch) => void
2389
- // addChildMatch: (childMatch: RouteMatch) => void
2390
- validate: () => void
2391
- startPending: () => void
2392
- cancelPending: () => void
2393
- notify: () => void
2394
- resolve: () => void
2395
- }
2396
- cancel: () => void
2397
- load: () => Promise<void>
2398
- }
2399
-
2400
- export function createRouteMatch<
2401
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
2402
- TRouteInfo extends AnyRouteInfo = RouteInfo,
2403
- >(
2404
- router: Router<any, any>,
2405
- route: Route<TAllRouteInfo, TRouteInfo>,
2406
- opts: {
2407
- matchId: string
2408
- params: TRouteInfo['allParams']
2409
- pathname: string
2410
- },
2411
- ): RouteMatch<TAllRouteInfo, TRouteInfo> {
2412
- const routeMatch: RouteMatch<TAllRouteInfo, TRouteInfo> = {
2413
- ...route,
2414
- ...opts,
2415
- router,
2416
- routeSearch: {},
2417
- search: {},
2418
- childMatches: [],
2419
- status: 'idle',
2420
- routeLoaderData: {} as TRouteInfo['routeLoaderData'],
2421
- loaderData: {} as TRouteInfo['loaderData'],
2422
- isPending: false,
2423
- isFetching: false,
2424
- isInvalid: false,
2425
- __: {
2426
- abortController: new AbortController(),
2427
- latestId: '',
2428
- resolve: () => {},
2429
- notify: () => {
2430
- routeMatch.__.resolve()
2431
- routeMatch.router.notify()
2432
- },
2433
- startPending: () => {
2434
- const pendingMs =
2435
- routeMatch.options.pendingMs ?? router.options.defaultPendingMs
2436
- const pendingMinMs =
2437
- routeMatch.options.pendingMinMs ?? router.options.defaultPendingMinMs
2438
-
2439
- if (
2440
- routeMatch.__.pendingTimeout ||
2441
- routeMatch.status !== 'loading' ||
2442
- typeof pendingMs === 'undefined'
2443
- ) {
2444
- return
2445
- }
2446
-
2447
- routeMatch.__.pendingTimeout = setTimeout(() => {
2448
- routeMatch.isPending = true
2449
- routeMatch.__.resolve()
2450
- if (typeof pendingMinMs !== 'undefined') {
2451
- routeMatch.__.pendingMinPromise = new Promise(
2452
- (r) =>
2453
- (routeMatch.__.pendingMinTimeout = setTimeout(r, pendingMinMs)),
2454
- )
2455
- }
2456
- }, pendingMs)
2457
- },
2458
- cancelPending: () => {
2459
- routeMatch.isPending = false
2460
- clearTimeout(routeMatch.__.pendingTimeout)
2461
- clearTimeout(routeMatch.__.pendingMinTimeout)
2462
- delete routeMatch.__.pendingMinPromise
2463
- },
2464
- // setParentMatch: (parentMatch?: RouteMatch) => {
2465
- // routeMatch.parentMatch = parentMatch
2466
- // },
2467
- // addChildMatch: (childMatch: RouteMatch) => {
2468
- // if (
2469
- // routeMatch.childMatches.find((d) => d.matchId === childMatch.matchId)
2470
- // ) {
2471
- // return
2472
- // }
2473
-
2474
- // routeMatch.childMatches.push(childMatch)
2475
- // },
2476
- validate: () => {
2477
- // Validate the search params and stabilize them
2478
- const parentSearch =
2479
- routeMatch.parentMatch?.search ?? router.location.search
2480
-
2481
- try {
2482
- const prevSearch = routeMatch.routeSearch
2483
-
2484
- let nextSearch = replaceEqualDeep(
2485
- prevSearch,
2486
- routeMatch.options.validateSearch?.(parentSearch),
2487
- )
2488
-
2489
- // Invalidate route matches when search param stability changes
2490
- if (prevSearch !== nextSearch) {
2491
- routeMatch.isInvalid = true
2492
- }
2493
-
2494
- routeMatch.routeSearch = nextSearch
2495
-
2496
- routeMatch.search = replaceEqualDeep(parentSearch, {
2497
- ...parentSearch,
2498
- ...nextSearch,
2499
- })
2500
- } catch (err: any) {
2501
- console.error(err)
2502
- const error = new (Error as any)('Invalid search params found', {
2503
- cause: err,
2504
- })
2505
- error.code = 'INVALID_SEARCH_PARAMS'
2506
- routeMatch.status = 'error'
2507
- routeMatch.error = error
2508
- // Do not proceed with loading the route
2509
- return
2510
- }
2511
- },
2512
- },
2513
- cancel: () => {
2514
- routeMatch.__.abortController?.abort()
2515
- routeMatch.__.cancelPending()
2516
- },
2517
- load: async () => {
2518
- const id = '' + Date.now() + Math.random()
2519
- routeMatch.__.latestId = id
2520
-
2521
- // If the match was in an error state, set it
2522
- // to a loading state again. Otherwise, keep it
2523
- // as loading or resolved
2524
- if (routeMatch.status === 'error' || routeMatch.status === 'idle') {
2525
- routeMatch.status = 'loading'
2526
- }
2527
-
2528
- // We started loading the route, so it's no longer invalid
2529
- routeMatch.isInvalid = false
2530
-
2531
- routeMatch.__.loadPromise = new Promise(async (resolve) => {
2532
- // We are now fetching, even if it's in the background of a
2533
- // resolved state
2534
- routeMatch.isFetching = true
2535
- routeMatch.__.resolve = resolve as () => void
2536
-
2537
- const loaderPromise = (async () => {
2538
- const importer = routeMatch.options.import
2539
-
2540
- // First, run any importers
2541
- if (importer) {
2542
- routeMatch.__.importPromise = importer({
2543
- params: routeMatch.params,
2544
- // search: routeMatch.search,
2545
- }).then((imported) => {
2546
- routeMatch.__ = {
2547
- ...routeMatch.__,
2548
- ...imported,
2549
- }
2550
- })
2551
- }
2552
-
2553
- // Wait for the importer to finish before
2554
- // attempting to load elements and data
2555
- await routeMatch.__.importPromise
2556
-
2557
- // Next, load the elements and data in parallel
2558
-
2559
- routeMatch.__.elementsPromise = (async () => {
2560
- // then run all element and data loaders in parallel
2561
- // For each element type, potentially load it asynchronously
2562
- const elementTypes = [
2563
- 'element',
2564
- 'errorElement',
2565
- 'catchElement',
2566
- 'pendingElement',
2567
- ] as const
2568
-
2569
- await Promise.all(
2570
- elementTypes.map(async (type) => {
2571
- const routeElement = routeMatch.options[type]
2572
-
2573
- if (routeMatch.__[type]) {
2574
- return
2575
- }
2576
-
2577
- if (typeof routeElement === 'function') {
2578
- const res = await (routeElement as any)(routeMatch)
2579
-
2580
- routeMatch.__[type] = res
2581
- } else {
2582
- routeMatch.__[type] = routeMatch.options[type] as any
2583
- }
2584
- }),
2585
- )
2586
- })()
2587
-
2588
- routeMatch.__.dataPromise = Promise.resolve().then(async () => {
2589
- try {
2590
- const data = await routeMatch.options.loader?.({
2591
- params: routeMatch.params,
2592
- search: routeMatch.routeSearch,
2593
- signal: routeMatch.__.abortController.signal,
2594
- })
2595
- if (id !== routeMatch.__.latestId) {
2596
- return routeMatch.__.loaderPromise
2597
- }
2598
-
2599
- routeMatch.routeLoaderData = replaceEqualDeep(
2600
- routeMatch.routeLoaderData,
2601
- data,
2602
- )
2603
-
2604
- routeMatch.error = undefined
2605
- routeMatch.status = 'success'
2606
- routeMatch.updatedAt = Date.now()
2607
- } catch (err) {
2608
- if (id !== routeMatch.__.latestId) {
2609
- return routeMatch.__.loaderPromise
2610
- }
2611
-
2612
- if (process.env.NODE_ENV !== 'production') {
2613
- console.error(err)
2614
- }
2615
- routeMatch.error = err
2616
- routeMatch.status = 'error'
2617
- routeMatch.updatedAt = Date.now()
2618
- }
2619
- })
2620
-
2621
- try {
2622
- await Promise.all([
2623
- routeMatch.__.elementsPromise,
2624
- routeMatch.__.dataPromise,
2625
- ])
2626
- if (id !== routeMatch.__.latestId) {
2627
- return routeMatch.__.loaderPromise
2628
- }
2629
-
2630
- if (routeMatch.__.pendingMinPromise) {
2631
- await routeMatch.__.pendingMinPromise
2632
- delete routeMatch.__.pendingMinPromise
2633
- }
2634
- } finally {
2635
- if (id !== routeMatch.__.latestId) {
2636
- return routeMatch.__.loaderPromise
2637
- }
2638
- routeMatch.__.cancelPending()
2639
- routeMatch.isPending = false
2640
- routeMatch.isFetching = false
2641
- routeMatch.__.notify()
2642
- }
2643
- })()
2644
-
2645
- routeMatch.__.loaderPromise = loaderPromise
2646
- await loaderPromise
2647
-
2648
- if (id !== routeMatch.__.latestId) {
2649
- return routeMatch.__.loaderPromise
2650
- }
2651
- delete routeMatch.__.loaderPromise
2652
- })
2653
-
2654
- return await routeMatch.__.loadPromise
2655
- },
2656
- }
2657
-
2658
- return routeMatch
2659
- }
2660
-
2661
- function cascadeLoaderData(matches: RouteMatch<any, any>[]) {
2662
- matches.forEach((match, index) => {
2663
- const parent = matches[index - 1]
2664
-
2665
- if (parent) {
2666
- match.loaderData = replaceEqualDeep(match.loaderData, {
2667
- ...parent.loaderData,
2668
- ...match.routeLoaderData,
2669
- })
2670
- }
2671
- })
2672
- }
2673
-
2674
- export function matchPathname(
2675
- currentPathname: string,
2676
- matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>,
2677
- ): AnyPathParams | undefined {
2678
- const pathParams = matchByPath(currentPathname, matchLocation)
2679
- // const searchMatched = matchBySearch(currentLocation.search, matchLocation)
2680
-
2681
- if (matchLocation.to && !pathParams) {
2682
- return
2683
- }
2684
-
2685
- // if (matchLocation.search && !searchMatched) {
2686
- // return
2687
- // }
2688
-
2689
- return pathParams ?? {}
2690
- }
2691
-
2692
- function interpolatePath(
2693
- path: string | undefined,
2694
- params: any,
2695
- leaveWildcard?: boolean,
2696
- ) {
2697
- const interpolatedPathSegments = parsePathname(path)
2698
-
2699
- return joinPaths(
2700
- interpolatedPathSegments.map((segment) => {
2701
- if (segment.value === '*' && !leaveWildcard) {
2702
- return ''
2703
- }
2704
-
2705
- if (segment.type === 'param') {
2706
- return params![segment.value.substring(1)] ?? ''
2707
- }
2708
-
2709
- return segment.value
2710
- }),
2711
- )
2712
- }
2713
-
2714
- export function warning(cond: any, message: string): cond is true {
2715
- if (cond) {
2716
- if (typeof console !== 'undefined') console.warn(message)
2717
-
2718
- try {
2719
- throw new Error(message)
2720
- } catch {}
2721
- }
2722
-
2723
- return true
2724
- }
2725
-
2726
- function isFunction(d: any): d is Function {
2727
- return typeof d === 'function'
2728
- }
2729
-
2730
- export function functionalUpdate<TResult>(
2731
- updater?: Updater<TResult>,
2732
- previous?: TResult,
2733
- ) {
2734
- if (isFunction(updater)) {
2735
- return updater(previous as TResult)
2736
- }
2737
-
2738
- return updater
2739
- }
2740
-
2741
- function joinPaths(paths: (string | undefined)[]) {
2742
- return cleanPath(paths.filter(Boolean).join('/'))
2743
- }
2744
-
2745
- function cleanPath(path: string) {
2746
- // remove double slashes
2747
- return path.replace(/\/{2,}/g, '/')
2748
- }
2749
-
2750
- function trimPathLeft(path: string) {
2751
- return path === '/' ? path : path.replace(/^\/{1,}/, '')
2752
- }
2753
-
2754
- function trimPathRight(path: string) {
2755
- return path === '/' ? path : path.replace(/\/{1,}$/, '')
2756
- }
2757
-
2758
- function trimPath(path: string) {
2759
- return trimPathRight(trimPathLeft(path))
2760
- }
2761
-
2762
- export function matchByPath(
2763
- from: string,
2764
- matchLocation: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,
2765
- ): Record<string, string> | undefined {
2766
- const baseSegments = parsePathname(from)
2767
- const routeSegments = parsePathname(`${matchLocation.to ?? '*'}`)
2768
-
2769
- const params: Record<string, string> = {}
2770
-
2771
- let isMatch = (() => {
2772
- for (
2773
- let i = 0;
2774
- i < Math.max(baseSegments.length, routeSegments.length);
2775
- i++
2776
- ) {
2777
- const baseSegment = baseSegments[i]
2778
- const routeSegment = routeSegments[i]
2779
-
2780
- const isLastRouteSegment = i === routeSegments.length - 1
2781
- const isLastBaseSegment = i === baseSegments.length - 1
2782
-
2783
- if (routeSegment) {
2784
- if (routeSegment.type === 'wildcard') {
2785
- if (baseSegment?.value) {
2786
- params['*'] = joinPaths(baseSegments.slice(i).map((d) => d.value))
2787
- return true
2788
- }
2789
- return false
2790
- }
2791
-
2792
- if (routeSegment.type === 'pathname') {
2793
- if (routeSegment.value === '/' && !baseSegment?.value) {
2794
- return true
2795
- }
2796
-
2797
- if (baseSegment) {
2798
- if (matchLocation.caseSensitive) {
2799
- if (routeSegment.value !== baseSegment.value) {
2800
- return false
2801
- }
2802
- } else if (
2803
- routeSegment.value.toLowerCase() !==
2804
- baseSegment.value.toLowerCase()
2805
- ) {
2806
- return false
2807
- }
2808
- }
2809
- }
2810
-
2811
- if (!baseSegment) {
2812
- return false
2813
- }
2814
-
2815
- if (routeSegment.type === 'param') {
2816
- if (baseSegment?.value === '/') {
2817
- return false
2818
- }
2819
- if (!baseSegment.value.startsWith(':')) {
2820
- params[routeSegment.value.substring(1)] = baseSegment.value
2821
- }
2822
- }
2823
- }
2824
-
2825
- if (isLastRouteSegment && !isLastBaseSegment) {
2826
- return !!matchLocation.fuzzy
2827
- }
2828
- }
2829
- return true
2830
- })()
2831
-
2832
- return isMatch ? (params as Record<string, string>) : undefined
2833
- }
2834
-
2835
- // function matchBySearch(
2836
- // search: SearchSchema,
2837
- // matchLocation: MatchLocation,
2838
- // ) {
2839
- // return !!(matchLocation.search && matchLocation.search(search))
2840
- // }
2841
-
2842
- export function parsePathname(pathname?: string): Segment[] {
2843
- if (!pathname) {
2844
- return []
2845
- }
2846
-
2847
- pathname = cleanPath(pathname)
2848
-
2849
- const segments: Segment[] = []
2850
-
2851
- if (pathname.slice(0, 1) === '/') {
2852
- pathname = pathname.substring(1)
2853
- segments.push({
2854
- type: 'pathname',
2855
- value: '/',
2856
- })
2857
- }
2858
-
2859
- if (!pathname) {
2860
- return segments
2861
- }
2862
-
2863
- // Remove empty segments and '.' segments
2864
- const split = pathname.split('/').filter(Boolean)
2865
-
2866
- segments.push(
2867
- ...split.map((part): Segment => {
2868
- if (part.startsWith('*')) {
2869
- return {
2870
- type: 'wildcard',
2871
- value: part,
2872
- }
2873
- }
2874
-
2875
- if (part.charAt(0) === ':') {
2876
- return {
2877
- type: 'param',
2878
- value: part,
2879
- }
2880
- }
2881
-
2882
- return {
2883
- type: 'pathname',
2884
- value: part,
2885
- }
2886
- }),
2887
- )
2888
-
2889
- if (pathname.slice(-1) === '/') {
2890
- pathname = pathname.substring(1)
2891
- segments.push({
2892
- type: 'pathname',
2893
- value: '/',
2894
- })
2895
- }
2896
-
2897
- return segments
2898
- }
2899
-
2900
- export function resolvePath(basepath: string, base: string, to: string) {
2901
- base = base.replace(new RegExp(`^${basepath}`), '/')
2902
- to = to.replace(new RegExp(`^${basepath}`), '/')
2903
-
2904
- let baseSegments = parsePathname(base)
2905
- const toSegments = parsePathname(to)
2906
-
2907
- toSegments.forEach((toSegment, index) => {
2908
- if (toSegment.value === '/') {
2909
- if (!index) {
2910
- // Leading slash
2911
- baseSegments = [toSegment]
2912
- } else if (index === toSegments.length - 1) {
2913
- // Trailing Slash
2914
- baseSegments.push(toSegment)
2915
- } else {
2916
- // ignore inter-slashes
2917
- }
2918
- } else if (toSegment.value === '..') {
2919
- // Extra trailing slash? pop it off
2920
- if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
2921
- baseSegments.pop()
2922
- }
2923
- baseSegments.pop()
2924
- } else if (toSegment.value === '.') {
2925
- return
2926
- } else {
2927
- baseSegments.push(toSegment)
2928
- }
2929
- })
2930
-
2931
- const joined = joinPaths([basepath, ...baseSegments.map((d) => d.value)])
2932
-
2933
- return cleanPath(joined)
2934
- }
2935
-
2936
- /**
2937
- * This function returns `a` if `b` is deeply equal.
2938
- * If not, it will replace any deeply equal children of `b` with those of `a`.
2939
- * This can be used for structural sharing between JSON values for example.
2940
- */
2941
- export function replaceEqualDeep(prev: any, next: any) {
2942
- if (prev === next) {
2943
- return prev
2944
- }
2945
-
2946
- const array = Array.isArray(prev) && Array.isArray(next)
2947
-
2948
- if (array || (isPlainObject(prev) && isPlainObject(next))) {
2949
- const aSize = array ? prev.length : Object.keys(prev).length
2950
- const bItems = array ? next : Object.keys(next)
2951
- const bSize = bItems.length
2952
- const copy: any = array ? [] : {}
2953
-
2954
- let equalItems = 0
2955
-
2956
- for (let i = 0; i < bSize; i++) {
2957
- const key = array ? i : bItems[i]
2958
- copy[key] = replaceEqualDeep(prev[key], next[key])
2959
- if (copy[key] === prev[key]) {
2960
- equalItems++
2961
- }
2962
- }
2963
-
2964
- return aSize === bSize && equalItems === aSize ? prev : copy
2965
- }
2966
-
2967
- return next
2968
- }
2969
-
2970
- // Copied from: https://github.com/jonschlinkert/is-plain-object
2971
- function isPlainObject(o: any) {
2972
- if (!hasObjectPrototype(o)) {
2973
- return false
2974
- }
2975
-
2976
- // If has modified constructor
2977
- const ctor = o.constructor
2978
- if (typeof ctor === 'undefined') {
2979
- return true
2980
- }
2981
-
2982
- // If has modified prototype
2983
- const prot = ctor.prototype
2984
- if (!hasObjectPrototype(prot)) {
2985
- return false
2986
- }
2987
-
2988
- // If constructor does not have an Object-specific method
2989
- if (!prot.hasOwnProperty('isPrototypeOf')) {
2990
- return false
2991
- }
2992
-
2993
- // Most likely a plain Object
2994
- return true
2995
- }
2996
-
2997
- function hasObjectPrototype(o: any) {
2998
- return Object.prototype.toString.call(o) === '[object Object]'
2999
- }
3000
-
3001
- export const defaultParseSearch = parseSearchWith(JSON.parse)
3002
- export const defaultStringifySearch = stringifySearchWith(JSON.stringify)
3003
-
3004
- export function parseSearchWith(parser: (str: string) => any) {
3005
- return (searchStr: string): AnySearchSchema => {
3006
- if (searchStr.substring(0, 1) === '?') {
3007
- searchStr = searchStr.substring(1)
3008
- }
3009
-
3010
- let query: Record<string, unknown> = decode(searchStr)
3011
-
3012
- // Try to parse any query params that might be json
3013
- for (let key in query) {
3014
- const value = query[key]
3015
- if (typeof value === 'string') {
3016
- try {
3017
- query[key] = parser(value)
3018
- } catch (err) {
3019
- //
3020
- }
3021
- }
3022
- }
3023
-
3024
- return query
3025
- }
3026
- }
3027
-
3028
- export function stringifySearchWith(stringify: (search: any) => string) {
3029
- return (search: Record<string, any>) => {
3030
- search = { ...search }
3031
-
3032
- if (search) {
3033
- Object.keys(search).forEach((key) => {
3034
- const val = search[key]
3035
- if (typeof val === 'undefined' || val === undefined) {
3036
- delete search[key]
3037
- } else if (val && typeof val === 'object' && val !== null) {
3038
- try {
3039
- search[key] = stringify(val)
3040
- } catch (err) {
3041
- // silent
3042
- }
3043
- }
3044
- })
3045
- }
3046
-
3047
- const searchStr = encode(search as Record<string, string>).toString()
3048
-
3049
- return searchStr ? `?${searchStr}` : ''
3050
- }
3051
- }
3052
-
3053
- function isCtrlEvent(e: MouseEvent) {
3054
- return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
3055
- }
3056
-
3057
- export function last<T>(arr: T[]) {
3058
- return arr[arr.length - 1]
3059
- }
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'