@tanstack/react-router 0.0.1-beta.209 → 0.0.1-beta.210

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 (72) hide show
  1. package/build/cjs/CatchBoundary.js +125 -0
  2. package/build/cjs/CatchBoundary.js.map +1 -0
  3. package/build/cjs/Matches.js +223 -0
  4. package/build/cjs/Matches.js.map +1 -0
  5. package/build/cjs/RouterProvider.js +99 -53
  6. package/build/cjs/RouterProvider.js.map +1 -1
  7. package/build/cjs/index.js +46 -37
  8. package/build/cjs/index.js.map +1 -1
  9. package/build/cjs/lazyRouteComponent.js +57 -0
  10. package/build/cjs/lazyRouteComponent.js.map +1 -0
  11. package/build/cjs/link.js +150 -0
  12. package/build/cjs/link.js.map +1 -0
  13. package/build/cjs/route.js +9 -5
  14. package/build/cjs/route.js.map +1 -1
  15. package/build/cjs/router.js.map +1 -1
  16. package/build/cjs/searchParams.js.map +1 -1
  17. package/build/cjs/useBlocker.js +64 -0
  18. package/build/cjs/useBlocker.js.map +1 -0
  19. package/build/cjs/useNavigate.js +78 -0
  20. package/build/cjs/useNavigate.js.map +1 -0
  21. package/build/cjs/useParams.js +28 -0
  22. package/build/cjs/useParams.js.map +1 -0
  23. package/build/cjs/useSearch.js +27 -0
  24. package/build/cjs/useSearch.js.map +1 -0
  25. package/build/cjs/utils.js +30 -1
  26. package/build/cjs/utils.js.map +1 -1
  27. package/build/esm/index.js +491 -514
  28. package/build/esm/index.js.map +1 -1
  29. package/build/stats-html.html +1 -1
  30. package/build/stats-react.json +484 -208
  31. package/build/types/CatchBoundary.d.ts +33 -0
  32. package/build/types/Matches.d.ts +31 -0
  33. package/build/types/RouterProvider.d.ts +42 -18
  34. package/build/types/fileRoute.d.ts +7 -7
  35. package/build/types/index.d.ts +13 -7
  36. package/build/types/injectHtml.d.ts +0 -0
  37. package/build/types/lazyRouteComponent.d.ts +2 -0
  38. package/build/types/link.d.ts +10 -3
  39. package/build/types/route.d.ts +39 -7
  40. package/build/types/router.d.ts +6 -7
  41. package/build/types/useBlocker.d.ts +8 -0
  42. package/build/types/useNavigate.d.ts +20 -0
  43. package/build/types/useParams.d.ts +7 -0
  44. package/build/types/useSearch.d.ts +7 -0
  45. package/build/types/utils.d.ts +17 -0
  46. package/build/umd/index.development.js +492 -513
  47. package/build/umd/index.development.js.map +1 -1
  48. package/build/umd/index.production.js +1 -1
  49. package/build/umd/index.production.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/CatchBoundary.tsx +97 -0
  52. package/src/Matches.tsx +315 -0
  53. package/src/RouterProvider.tsx +317 -251
  54. package/src/index.tsx +17 -8
  55. package/src/injectHtml.ts +28 -0
  56. package/src/lazyRouteComponent.tsx +33 -0
  57. package/src/{link.ts → link.tsx} +163 -3
  58. package/src/location.ts +1 -0
  59. package/src/route.ts +86 -16
  60. package/src/router.ts +6 -7
  61. package/src/searchParams.ts +1 -0
  62. package/src/useBlocker.tsx +34 -0
  63. package/src/useNavigate.tsx +109 -0
  64. package/src/useParams.tsx +25 -0
  65. package/src/useSearch.tsx +25 -0
  66. package/src/utils.ts +83 -3
  67. package/build/cjs/react.js +0 -620
  68. package/build/cjs/react.js.map +0 -1
  69. package/build/types/RouteMatch.d.ts +0 -23
  70. package/build/types/react.d.ts +0 -141
  71. package/src/RouteMatch.ts +0 -28
  72. package/src/react.tsx +0 -1013
package/src/react.tsx DELETED
@@ -1,1013 +0,0 @@
1
- import * as React from 'react'
2
- import invariant from 'tiny-invariant'
3
- import warning from 'tiny-warning'
4
- import {
5
- LinkOptions,
6
- ToOptions,
7
- ResolveRelativePath,
8
- NavigateOptions,
9
- } from './link'
10
- import {
11
- AnySearchSchema,
12
- AnyPathParams,
13
- AnyContext,
14
- AnyRoute,
15
- rootRouteId,
16
- } from './route'
17
- import {
18
- RoutePaths,
19
- RouteByPath,
20
- RouteIds,
21
- ParseRoute,
22
- RoutesById,
23
- RouteById,
24
- AllParams,
25
- } from './routeInfo'
26
- import { RegisteredRouter, RouterOptions, Router, RouterState } from './router'
27
- import { RouteMatch } from './RouteMatch'
28
- import { NoInfer, functionalUpdate, last } from './utils'
29
- import { MatchRouteOptions, RouterContext } from './RouterProvider'
30
- import { routerContext } from './RouterProvider'
31
-
32
- const useLayoutEffect =
33
- typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect
34
-
35
- export type RouteProps<
36
- TFullSearchSchema extends Record<string, any> = AnySearchSchema,
37
- TAllParams extends AnyPathParams = AnyPathParams,
38
- TAllContext extends Record<string, any> = AnyContext,
39
- > = {
40
- useMatch: <TSelected = TAllContext>(opts?: {
41
- select?: (search: TAllContext) => TSelected
42
- }) => TSelected
43
- useRouteContext: <TSelected = TAllContext>(opts?: {
44
- select?: (search: TAllContext) => TSelected
45
- }) => TSelected
46
- useSearch: <TSelected = TFullSearchSchema>(opts?: {
47
- select?: (search: TFullSearchSchema) => TSelected
48
- }) => TSelected
49
- useParams: <TSelected = TAllParams>(opts?: {
50
- select?: (search: TAllParams) => TSelected
51
- }) => TSelected
52
- }
53
-
54
- export type ErrorRouteProps<
55
- TFullSearchSchema extends Record<string, any> = AnySearchSchema,
56
- TAllParams extends AnyPathParams = AnyPathParams,
57
- TAllContext extends Record<string, any> = AnyContext,
58
- > = {
59
- error: unknown
60
- info: { componentStack: string }
61
- } & RouteProps<TFullSearchSchema, TAllParams, TAllContext>
62
-
63
- export type PendingRouteProps<
64
- TFullSearchSchema extends Record<string, any> = AnySearchSchema,
65
- TAllParams extends AnyPathParams = AnyPathParams,
66
- TAllContext extends Record<string, any> = AnyContext,
67
- > = RouteProps<TFullSearchSchema, TAllParams, TAllContext>
68
-
69
- //
70
-
71
- type ReactNode = any
72
-
73
- export type SyncRouteComponent<TProps> =
74
- | ((props: TProps) => ReactNode)
75
- | React.LazyExoticComponent<(props: TProps) => ReactNode>
76
-
77
- export type AsyncRouteComponent<TProps> = SyncRouteComponent<TProps> & {
78
- preload?: () => Promise<void>
79
- }
80
-
81
- export type RouteComponent<
82
- TFullSearchSchema extends Record<string, any>,
83
- TAllParams extends AnyPathParams,
84
- TAllContext extends Record<string, any>,
85
- > = AsyncRouteComponent<RouteProps<TFullSearchSchema, TAllParams, TAllContext>>
86
-
87
- export type ErrorRouteComponent<
88
- TFullSearchSchema extends Record<string, any>,
89
- TAllParams extends AnyPathParams,
90
- TAllContext extends Record<string, any>,
91
- > = AsyncRouteComponent<
92
- ErrorRouteProps<TFullSearchSchema, TAllParams, TAllContext>
93
- >
94
-
95
- export type PendingRouteComponent<
96
- TFullSearchSchema extends Record<string, any>,
97
- TAllParams extends AnyPathParams,
98
- TAllContext extends Record<string, any>,
99
- > = AsyncRouteComponent<
100
- PendingRouteProps<TFullSearchSchema, TAllParams, TAllContext>
101
- >
102
-
103
- export type AnyRouteComponent = RouteComponent<any, any, any>
104
-
105
- export function lazyRouteComponent<
106
- T extends Record<string, any>,
107
- TKey extends keyof T = 'default',
108
- >(
109
- importer: () => Promise<T>,
110
- exportName?: TKey,
111
- ): T[TKey] extends (props: infer TProps) => any
112
- ? AsyncRouteComponent<TProps>
113
- : never {
114
- let loadPromise: Promise<any>
115
-
116
- const load = () => {
117
- if (!loadPromise) {
118
- loadPromise = importer()
119
- }
120
-
121
- return loadPromise
122
- }
123
-
124
- const lazyComp = React.lazy(async () => {
125
- const moduleExports = await load()
126
- const comp = moduleExports[exportName ?? 'default']
127
- return {
128
- default: comp,
129
- }
130
- })
131
-
132
- ;(lazyComp as any).preload = load
133
-
134
- return lazyComp as any
135
- }
136
-
137
- export type LinkPropsOptions<
138
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
139
- TFrom extends RoutePaths<TRouteTree> = '/',
140
- TTo extends string = '',
141
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
142
- TMaskTo extends string = '',
143
- > = LinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
144
- // A function that returns additional props for the `active` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
145
- activeProps?:
146
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
147
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
148
- // A function that returns additional props for the `inactive` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
149
- inactiveProps?:
150
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
151
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
152
- // If set to `true`, the link's underlying navigate() call will be wrapped in a `React.startTransition` call. Defaults to `true`.
153
- startTransition?: boolean
154
- }
155
-
156
- export type MakeUseMatchRouteOptions<
157
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
158
- TFrom extends RoutePaths<TRouteTree> = '/',
159
- TTo extends string = '',
160
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
161
- TMaskTo extends string = '',
162
- > = ToOptions<AnyRoute, TFrom, TTo, TMaskFrom, TMaskTo> & MatchRouteOptions
163
-
164
- export type MakeMatchRouteOptions<
165
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
166
- TFrom extends RoutePaths<TRouteTree> = '/',
167
- TTo extends string = '',
168
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
169
- TMaskTo extends string = '',
170
- > = ToOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
171
- MatchRouteOptions & {
172
- // If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
173
- children?:
174
- | ((
175
- params?: RouteByPath<
176
- TRouteTree,
177
- ResolveRelativePath<TFrom, NoInfer<TTo>>
178
- >['types']['allParams'],
179
- ) => ReactNode)
180
- | React.ReactNode
181
- }
182
-
183
- export type MakeLinkPropsOptions<
184
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
185
- TFrom extends RoutePaths<TRouteTree> = '/',
186
- TTo extends string = '',
187
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
188
- TMaskTo extends string = '',
189
- > = LinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
190
- React.AnchorHTMLAttributes<HTMLAnchorElement>
191
-
192
- export type MakeLinkOptions<
193
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
194
- TFrom extends RoutePaths<TRouteTree> = '/',
195
- TTo extends string = '',
196
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
197
- TMaskTo extends string = '',
198
- > = LinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
199
- Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
200
- // If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
201
- children?:
202
- | React.ReactNode
203
- | ((state: { isActive: boolean }) => React.ReactNode)
204
- }
205
-
206
- export type PromptProps = {
207
- message: string
208
- condition?: boolean | any
209
- children?: ReactNode
210
- }
211
-
212
- //
213
-
214
- export function useLinkProps<
215
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
216
- TFrom extends RoutePaths<TRouteTree> = '/',
217
- TTo extends string = '',
218
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
219
- TMaskTo extends string = '',
220
- >(
221
- options: MakeLinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
222
- ): React.AnchorHTMLAttributes<HTMLAnchorElement> {
223
- const { buildLink, state: routerState } = useRouter()
224
- const match = useMatch({
225
- strict: false,
226
- })
227
-
228
- const {
229
- // custom props
230
- type,
231
- children,
232
- target,
233
- activeProps = () => ({ className: 'active' }),
234
- inactiveProps = () => ({}),
235
- activeOptions,
236
- disabled,
237
- hash,
238
- search,
239
- params,
240
- to,
241
- state,
242
- mask,
243
- preload,
244
- preloadDelay,
245
- replace,
246
- // element props
247
- style,
248
- className,
249
- onClick,
250
- onFocus,
251
- onMouseEnter,
252
- onMouseLeave,
253
- onTouchStart,
254
- ...rest
255
- } = options
256
-
257
- const linkInfo = buildLink(routerState, {
258
- from: options.to ? match.pathname : undefined,
259
- ...options,
260
- } as any)
261
-
262
- if (linkInfo.type === 'external') {
263
- const { href } = linkInfo
264
- return { href }
265
- }
266
-
267
- const {
268
- handleClick,
269
- handleFocus,
270
- handleEnter,
271
- handleLeave,
272
- handleTouchStart,
273
- isActive,
274
- next,
275
- } = linkInfo
276
-
277
- const handleReactClick = (e: Event) => {
278
- if (options.startTransition ?? true) {
279
- ;(React.startTransition || ((d) => d))(() => {
280
- handleClick(e)
281
- })
282
- }
283
- }
284
-
285
- const composeHandlers =
286
- (handlers: (undefined | ((e: any) => void))[]) =>
287
- (e: React.SyntheticEvent) => {
288
- if (e.persist) e.persist()
289
- handlers.filter(Boolean).forEach((handler) => {
290
- if (e.defaultPrevented) return
291
- handler!(e)
292
- })
293
- }
294
-
295
- // Get the active props
296
- const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
297
- ? functionalUpdate(activeProps as any, {}) ?? {}
298
- : {}
299
-
300
- // Get the inactive props
301
- const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
302
- isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {}
303
-
304
- return {
305
- ...resolvedActiveProps,
306
- ...resolvedInactiveProps,
307
- ...rest,
308
- href: disabled
309
- ? undefined
310
- : next.maskedLocation
311
- ? next.maskedLocation.href
312
- : next.href,
313
- onClick: composeHandlers([onClick, handleReactClick]),
314
- onFocus: composeHandlers([onFocus, handleFocus]),
315
- onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
316
- onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
317
- onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
318
- target,
319
- style: {
320
- ...style,
321
- ...resolvedActiveProps.style,
322
- ...resolvedInactiveProps.style,
323
- },
324
- className:
325
- [
326
- className,
327
- resolvedActiveProps.className,
328
- resolvedInactiveProps.className,
329
- ]
330
- .filter(Boolean)
331
- .join(' ') || undefined,
332
- ...(disabled
333
- ? {
334
- role: 'link',
335
- 'aria-disabled': true,
336
- }
337
- : undefined),
338
- ['data-status']: isActive ? 'active' : undefined,
339
- }
340
- }
341
-
342
- export interface LinkComponent<TProps extends Record<string, any> = {}> {
343
- <
344
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
345
- TFrom extends RoutePaths<TRouteTree> = '/',
346
- TTo extends string = '',
347
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
348
- TMaskTo extends string = '',
349
- >(
350
- props: MakeLinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
351
- TProps &
352
- React.RefAttributes<HTMLAnchorElement>,
353
- ): ReactNode
354
- }
355
-
356
- export const Link: LinkComponent = React.forwardRef((props: any, ref) => {
357
- const linkProps = useLinkProps(props)
358
-
359
- return (
360
- <a
361
- {...{
362
- ref: ref as any,
363
- ...linkProps,
364
- children:
365
- typeof props.children === 'function'
366
- ? props.children({
367
- isActive: (linkProps as any)['data-status'] === 'active',
368
- })
369
- : props.children,
370
- }}
371
- />
372
- )
373
- }) as any
374
-
375
- export function Navigate<
376
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
377
- TFrom extends RoutePaths<TRouteTree> = '/',
378
- TTo extends string = '',
379
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
380
- TMaskTo extends string = '',
381
- >(props: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>): null {
382
- const { navigate } = useRouter()
383
- const match = useMatch({ strict: false })
384
-
385
- useLayoutEffect(() => {
386
- navigate({
387
- from: props.to ? match.pathname : undefined,
388
- ...props,
389
- } as any)
390
- }, [])
391
-
392
- return null
393
- }
394
-
395
- export const matchesContext = React.createContext<RouteMatch[]>(null!)
396
- export type RouterProps<
397
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
398
- TDehydrated extends Record<string, any> = Record<string, any>,
399
- > = Omit<RouterOptions<TRouteTree, TDehydrated>, 'context'> & {
400
- router: Router<TRouteTree>
401
- context?: Partial<RouterOptions<TRouteTree, TDehydrated>['context']>
402
- }
403
-
404
- export function useRouter<
405
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
406
- >(): RouterContext<TRouteTree> {
407
- const resolvedContext = window.__TSR_ROUTER_CONTEXT__ || routerContext
408
- const value = React.useContext(resolvedContext)
409
- warning(value, 'useRouter must be used inside a <RouterProvider> component!')
410
- return value as any
411
- }
412
-
413
- export function useRouterState<
414
- TSelected = RouterState<RegisteredRouter['routeTree']>,
415
- >(opts?: {
416
- select: (state: RouterState<RegisteredRouter['routeTree']>) => TSelected
417
- }): TSelected {
418
- const { state } = useRouter()
419
- // return useStore(router.__store, opts?.select as any)
420
- return opts?.select ? opts.select(state) : (state as any)
421
- }
422
-
423
- export function useMatches<T = RouteMatch[]>(opts?: {
424
- select?: (matches: RouteMatch[]) => T
425
- }): T {
426
- const contextMatches = React.useContext(matchesContext)
427
-
428
- return useRouterState({
429
- select: (state) => {
430
- const matches = state.matches.slice(
431
- state.matches.findIndex((d) => d.id === contextMatches[0]?.id),
432
- )
433
- return opts?.select ? opts.select(matches) : (matches as T)
434
- },
435
- })
436
- }
437
-
438
- type StrictOrFrom<TFrom> =
439
- | {
440
- from: TFrom
441
- strict?: true
442
- }
443
- | {
444
- from?: never
445
- strict: false
446
- }
447
-
448
- export function useMatch<
449
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
450
- TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
451
- TStrict extends boolean = true,
452
- TRouteMatchState = RouteMatch<TRouteTree, TFrom>,
453
- TSelected = TRouteMatchState,
454
- >(
455
- opts: StrictOrFrom<TFrom> & {
456
- select?: (match: TRouteMatchState) => TSelected
457
- },
458
- ): TStrict extends true ? TRouteMatchState : TRouteMatchState | undefined {
459
- const nearestMatch = React.useContext(matchesContext)[0]!
460
- const nearestMatchRouteId = nearestMatch?.routeId
461
-
462
- const matchRouteId = useRouterState({
463
- select: (state) => {
464
- const match = opts?.from
465
- ? state.matches.find((d) => d.routeId === opts?.from)
466
- : state.matches.find((d) => d.id === nearestMatch.id)
467
-
468
- return match!.routeId
469
- },
470
- })
471
-
472
- if (opts?.strict ?? true) {
473
- invariant(
474
- nearestMatchRouteId == matchRouteId,
475
- `useMatch("${
476
- matchRouteId as string
477
- }") is being called in a component that is meant to render the '${nearestMatchRouteId}' route. Did you mean to 'useMatch("${
478
- matchRouteId as string
479
- }", { strict: false })' or 'useRoute("${
480
- matchRouteId as string
481
- }")' instead?`,
482
- )
483
- }
484
-
485
- const matchSelection = useRouterState({
486
- select: (state) => {
487
- const match = opts?.from
488
- ? state.matches.find((d) => d.routeId === opts?.from)
489
- : state.matches.find((d) => d.id === nearestMatch.id)
490
-
491
- invariant(
492
- match,
493
- `Could not find ${
494
- opts?.from
495
- ? `an active match from "${opts.from}"`
496
- : 'a nearest match!'
497
- }`,
498
- )
499
-
500
- return opts?.select ? opts.select(match as any) : match
501
- },
502
- })
503
-
504
- return matchSelection as any
505
- }
506
-
507
- export type RouteFromIdOrRoute<
508
- T,
509
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
510
- > = T extends ParseRoute<TRouteTree>
511
- ? T
512
- : T extends RouteIds<TRouteTree>
513
- ? RoutesById<TRouteTree>[T]
514
- : T extends string
515
- ? RouteIds<TRouteTree>
516
- : never
517
-
518
- export function useRouteContext<
519
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
520
- TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
521
- TStrict extends boolean = true,
522
- TRouteContext = RouteById<TRouteTree, TFrom>['types']['allContext'],
523
- TSelected = TRouteContext,
524
- >(
525
- opts: StrictOrFrom<TFrom> & {
526
- select?: (search: TRouteContext) => TSelected
527
- },
528
- ): TStrict extends true ? TSelected : TSelected | undefined {
529
- return useMatch({
530
- ...(opts as any),
531
- select: (match: RouteMatch) =>
532
- opts?.select
533
- ? opts.select(match.context as TRouteContext)
534
- : match.context,
535
- })
536
- }
537
-
538
- export function useSearch<
539
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
540
- TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
541
- TStrict extends boolean = true,
542
- TSearch = RouteById<TRouteTree, TFrom>['types']['fullSearchSchema'],
543
- TSelected = TSearch,
544
- >(
545
- opts: StrictOrFrom<TFrom> & {
546
- select?: (search: TSearch) => TSelected
547
- },
548
- ): TStrict extends true ? TSelected : TSelected | undefined {
549
- return useMatch({
550
- ...(opts as any),
551
- select: (match: RouteMatch) => {
552
- return opts?.select ? opts.select(match.search as TSearch) : match.search
553
- },
554
- })
555
- }
556
-
557
- export function useParams<
558
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
559
- TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
560
- TDefaultSelected = AllParams<TRouteTree> &
561
- RouteById<TRouteTree, TFrom>['types']['allParams'],
562
- TSelected = TDefaultSelected,
563
- >(
564
- opts: StrictOrFrom<TFrom> & {
565
- select?: (search: TDefaultSelected) => TSelected
566
- },
567
- ): TSelected {
568
- return useRouterState({
569
- select: (state: any) => {
570
- const params = (last(state.matches) as any)?.params
571
- return opts?.select ? opts.select(params) : params
572
- },
573
- })
574
- }
575
-
576
- export function useNavigate<
577
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
578
- TDefaultFrom extends RoutePaths<TRouteTree> = '/',
579
- >(defaultOpts?: { from?: TDefaultFrom }) {
580
- const { navigate } = useRouter()
581
- const match = useMatch({
582
- strict: false,
583
- })
584
- return React.useCallback(
585
- <
586
- TFrom extends RoutePaths<TRouteTree> = TDefaultFrom,
587
- TTo extends string = '',
588
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
589
- TMaskTo extends string = '',
590
- >(
591
- opts?: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
592
- ) => {
593
- return navigate({
594
- from: opts?.to ? match.pathname : undefined,
595
- ...defaultOpts,
596
- ...(opts as any),
597
- })
598
- },
599
- [],
600
- )
601
- }
602
-
603
- export function typedNavigate<
604
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
605
- TDefaultFrom extends RoutePaths<TRouteTree> = '/',
606
- >(navigate: (opts: NavigateOptions<any>) => Promise<void>) {
607
- return navigate as <
608
- TFrom extends RoutePaths<TRouteTree> = TDefaultFrom,
609
- TTo extends string = '',
610
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
611
- TMaskTo extends string = '',
612
- >(
613
- opts?: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
614
- ) => Promise<void>
615
- }
616
-
617
- export function useMatchRoute<
618
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
619
- >() {
620
- const { state, matchRoute } = useRouter()
621
-
622
- return React.useCallback(
623
- <
624
- TFrom extends RoutePaths<TRouteTree> = '/',
625
- TTo extends string = '',
626
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
627
- TMaskTo extends string = '',
628
- TResolved extends string = ResolveRelativePath<TFrom, NoInfer<TTo>>,
629
- >(
630
- opts: MakeUseMatchRouteOptions<
631
- TRouteTree,
632
- TFrom,
633
- TTo,
634
- TMaskFrom,
635
- TMaskTo
636
- >,
637
- ): false | RouteById<TRouteTree, TResolved>['types']['allParams'] => {
638
- const { pending, caseSensitive, ...rest } = opts
639
-
640
- return matchRoute(state, rest as any, {
641
- pending,
642
- caseSensitive,
643
- })
644
- },
645
- [],
646
- )
647
- }
648
-
649
- export function Matches() {
650
- const { routesById, state } = useRouter()
651
-
652
- // const matches = useRouterState({
653
- // select: (state) => {
654
- // return state.matches
655
- // },
656
- // })
657
-
658
- const { matches } = state
659
-
660
- const locationKey = useRouterState({
661
- select: (d) => d.resolvedLocation.state?.key,
662
- })
663
-
664
- const route = routesById[rootRouteId]
665
-
666
- const errorComponent = React.useCallback(
667
- (props: any) => {
668
- return React.createElement(ErrorComponent, {
669
- ...props,
670
- useMatch: route.useMatch,
671
- useRouteContext: route.useRouteContext,
672
- useSearch: route.useSearch,
673
- useParams: route.useParams,
674
- })
675
- },
676
- [route],
677
- )
678
-
679
- return (
680
- <matchesContext.Provider value={matches}>
681
- <CatchBoundary
682
- resetKey={locationKey}
683
- errorComponent={errorComponent}
684
- onCatch={() => {
685
- warning(
686
- false,
687
- `Error in router! Consider setting an 'errorComponent' in your RootRoute! 👍`,
688
- )
689
- }}
690
- >
691
- {matches.length ? <Match matches={matches} /> : null}
692
- </CatchBoundary>
693
- </matchesContext.Provider>
694
- )
695
- }
696
-
697
- export function MatchRoute<
698
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
699
- TFrom extends RoutePaths<TRouteTree> = '/',
700
- TTo extends string = '',
701
- TMaskFrom extends RoutePaths<TRouteTree> = '/',
702
- TMaskTo extends string = '',
703
- >(
704
- props: MakeMatchRouteOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
705
- ): any {
706
- const matchRoute = useMatchRoute()
707
- const params = matchRoute(props as any)
708
-
709
- if (typeof props.children === 'function') {
710
- return (props.children as any)(params)
711
- }
712
-
713
- return !!params ? props.children : null
714
- }
715
-
716
- export function Outlet() {
717
- const matches = React.useContext(matchesContext).slice(1)
718
-
719
- if (!matches[0]) {
720
- return null
721
- }
722
-
723
- return <Match matches={matches} />
724
- }
725
-
726
- const defaultPending = () => null
727
-
728
- function Match({ matches }: { matches: RouteMatch[] }) {
729
- const { options, routesById } = useRouter()
730
- const match = matches[0]!
731
- const routeId = match?.routeId
732
- const route = routesById[routeId]
733
- const locationKey = useRouterState({
734
- select: (s) => s.resolvedLocation.state?.key,
735
- })
736
-
737
- const PendingComponent = (route.options.pendingComponent ??
738
- options.defaultPendingComponent ??
739
- defaultPending) as any
740
-
741
- const routeErrorComponent =
742
- route.options.errorComponent ??
743
- options.defaultErrorComponent ??
744
- ErrorComponent
745
-
746
- const ResolvedSuspenseBoundary =
747
- route.options.wrapInSuspense ?? !route.isRoot
748
- ? React.Suspense
749
- : SafeFragment
750
-
751
- const ResolvedCatchBoundary = !!routeErrorComponent
752
- ? CatchBoundary
753
- : SafeFragment
754
-
755
- const errorComponent = React.useCallback(
756
- (props: any) => {
757
- return React.createElement(routeErrorComponent, {
758
- ...props,
759
- useMatch: route.useMatch,
760
- useRouteContext: route.useRouteContext,
761
- useSearch: route.useSearch,
762
- useParams: route.useParams,
763
- })
764
- },
765
- [route],
766
- )
767
-
768
- return (
769
- <matchesContext.Provider value={matches}>
770
- <ResolvedSuspenseBoundary
771
- fallback={React.createElement(PendingComponent, {
772
- useMatch: route.useMatch,
773
- useRouteContext: route.useRouteContext,
774
- useSearch: route.useSearch,
775
- useParams: route.useParams,
776
- })}
777
- >
778
- <ResolvedCatchBoundary
779
- resetKey={locationKey}
780
- errorComponent={errorComponent}
781
- onCatch={() => {
782
- warning(false, `Error in route match: ${match.id}`)
783
- }}
784
- >
785
- <MatchInner match={match} />
786
- </ResolvedCatchBoundary>
787
- </ResolvedSuspenseBoundary>
788
- </matchesContext.Provider>
789
- )
790
- }
791
-
792
- function MatchInner({ match }: { match: RouteMatch }): any {
793
- const { options, routesById } = useRouter()
794
- const route = routesById[match.routeId]
795
-
796
- if (match.status === 'error') {
797
- throw match.error
798
- }
799
-
800
- if (match.status === 'pending') {
801
- throw match.loadPromise
802
- }
803
-
804
- if (match.status === 'success') {
805
- let comp = route.options.component ?? options.defaultComponent
806
-
807
- if (comp) {
808
- return React.createElement(comp, {
809
- useMatch: route.useMatch,
810
- useRouteContext: route.useRouteContext as any,
811
- useSearch: route.useSearch,
812
- useParams: route.useParams as any,
813
- } as any)
814
- }
815
-
816
- return <Outlet />
817
- }
818
-
819
- invariant(
820
- false,
821
- 'Idle routeMatch status encountered during rendering! You should never see this. File an issue!',
822
- )
823
- }
824
-
825
- function SafeFragment(props: any) {
826
- return <>{props.children}</>
827
- }
828
-
829
- // export function useInjectHtml() {
830
- // const { } = useRouter()
831
-
832
- // return React.useCallback(
833
- // (html: string | (() => Promise<string> | string)) => {
834
- // router.injectHtml(html)
835
- // },
836
- // [],
837
- // )
838
- // }
839
-
840
- // export function useDehydrate() {
841
- // const { } = useRouter()
842
-
843
- // return React.useCallback(function dehydrate<T>(
844
- // key: any,
845
- // data: T | (() => Promise<T> | T),
846
- // ) {
847
- // return router.dehydrateData(key, data)
848
- // },
849
- // [])
850
- // }
851
-
852
- // export function useHydrate() {
853
- // const { } = useRouter()
854
-
855
- // return function hydrate<T = unknown>(key: any) {
856
- // return router.hydrateData(key) as T
857
- // }
858
- // }
859
-
860
- // This is the messiest thing ever... I'm either seriously tired (likely) or
861
- // there has to be a better way to reset error boundaries when the
862
- // router's location key changes.
863
-
864
- export function CatchBoundary(props: {
865
- resetKey: string
866
- children: any
867
- errorComponent?: any
868
- onCatch: (error: any) => void
869
- }) {
870
- const errorComponent = props.errorComponent ?? ErrorComponent
871
-
872
- return (
873
- <CatchBoundaryImpl
874
- resetKey={props.resetKey}
875
- onCatch={props.onCatch}
876
- children={({ error }) => {
877
- if (error) {
878
- return React.createElement(errorComponent, {
879
- error,
880
- })
881
- }
882
-
883
- return props.children
884
- }}
885
- />
886
- )
887
- }
888
-
889
- export class CatchBoundaryImpl extends React.Component<{
890
- resetKey: string
891
- children: (props: { error: any; reset: () => void }) => any
892
- onCatch?: (error: any) => void
893
- }> {
894
- state = { error: null } as any
895
- static getDerivedStateFromError(error: any) {
896
- return { error }
897
- }
898
- componentDidUpdate(
899
- prevProps: Readonly<{
900
- resetKey: string
901
- children: (props: { error: any; reset: () => void }) => any
902
- onCatch?: ((error: any, info: any) => void) | undefined
903
- }>,
904
- prevState: any,
905
- ): void {
906
- if (prevState.error && prevProps.resetKey !== this.props.resetKey) {
907
- this.setState({ error: null })
908
- }
909
- }
910
- componentDidCatch(error: any) {
911
- this.props.onCatch?.(error)
912
- }
913
- render() {
914
- return this.props.children(this.state)
915
- }
916
- }
917
-
918
- export function ErrorComponent({ error }: { error: any }) {
919
- const [show, setShow] = React.useState(process.env.NODE_ENV !== 'production')
920
-
921
- return (
922
- <div style={{ padding: '.5rem', maxWidth: '100%' }}>
923
- <div style={{ display: 'flex', alignItems: 'center', gap: '.5rem' }}>
924
- <strong style={{ fontSize: '1rem' }}>Something went wrong!</strong>
925
- <button
926
- style={{
927
- appearance: 'none',
928
- fontSize: '.6em',
929
- border: '1px solid currentColor',
930
- padding: '.1rem .2rem',
931
- fontWeight: 'bold',
932
- borderRadius: '.25rem',
933
- }}
934
- onClick={() => setShow((d) => !d)}
935
- >
936
- {show ? 'Hide Error' : 'Show Error'}
937
- </button>
938
- </div>
939
- <div style={{ height: '.25rem' }} />
940
- {show ? (
941
- <div>
942
- <pre
943
- style={{
944
- fontSize: '.7em',
945
- border: '1px solid red',
946
- borderRadius: '.25rem',
947
- padding: '.3rem',
948
- color: 'red',
949
- overflow: 'auto',
950
- }}
951
- >
952
- {error.message ? <code>{error.message}</code> : null}
953
- </pre>
954
- </div>
955
- ) : null}
956
- </div>
957
- )
958
- }
959
-
960
- export function useBlocker(
961
- message: string,
962
- condition: boolean | any = true,
963
- ): void {
964
- const { history } = useRouter()
965
-
966
- React.useEffect(() => {
967
- if (!condition) return
968
-
969
- let unblock = history.block((retry, cancel) => {
970
- if (window.confirm(message)) {
971
- unblock()
972
- retry()
973
- }
974
- })
975
-
976
- return unblock
977
- })
978
- }
979
-
980
- export function Block({ message, condition, children }: PromptProps) {
981
- useBlocker(message, condition)
982
- return (children ?? null) as ReactNode
983
- }
984
-
985
- export function shallow<T>(objA: T, objB: T) {
986
- if (Object.is(objA, objB)) {
987
- return true
988
- }
989
-
990
- if (
991
- typeof objA !== 'object' ||
992
- objA === null ||
993
- typeof objB !== 'object' ||
994
- objB === null
995
- ) {
996
- return false
997
- }
998
-
999
- const keysA = Object.keys(objA)
1000
- if (keysA.length !== Object.keys(objB).length) {
1001
- return false
1002
- }
1003
-
1004
- for (let i = 0; i < keysA.length; i++) {
1005
- if (
1006
- !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||
1007
- !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])
1008
- ) {
1009
- return false
1010
- }
1011
- }
1012
- return true
1013
- }