@tanstack/react-router 0.0.1-beta.20 → 0.0.1-beta.200

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.
package/src/index.tsx CHANGED
@@ -1,662 +1,7 @@
1
- import * as React from 'react'
2
-
3
- import { useSyncExternalStore } from 'use-sync-external-store/shim'
4
-
5
- import {
6
- AnyRoute,
7
- CheckId,
8
- rootRouteId,
9
- RouterState,
10
- ToIdOption,
11
- } from '@tanstack/router-core'
12
- import {
13
- warning,
14
- RouterOptions,
15
- RouteMatch,
16
- MatchRouteOptions,
17
- RouteConfig,
18
- AnyRouteConfig,
19
- AnyAllRouteInfo,
20
- DefaultAllRouteInfo,
21
- functionalUpdate,
22
- createRouter,
23
- AnyRouteInfo,
24
- AllRouteInfo,
25
- RouteInfo,
26
- ValidFromPath,
27
- LinkOptions,
28
- RouteInfoByPath,
29
- ResolveRelativePath,
30
- NoInfer,
31
- ToOptions,
32
- invariant,
33
- Router,
34
- } from '@tanstack/router-core'
35
-
36
- export * from '@tanstack/router-core'
37
-
38
- export interface RegisterRouter {
39
- // router: Router
40
- }
41
-
42
- export type ResolvedRouter = RegisterRouter extends {
43
- router: Router<infer TRouteConfig, infer TAllRouteInfo>
44
- }
45
- ? Router<TRouteConfig, TAllRouteInfo>
46
- : Router
47
-
48
- export type ResolvedAllRouteInfo = RegisterRouter extends {
49
- router: Router<infer TRouteConfig, infer TAllRouteInfo>
50
- }
51
- ? TAllRouteInfo
52
- : AnyAllRouteInfo
53
-
54
- export type SyncRouteComponent = (props?: {}) => JSX.Element | React.ReactNode
55
-
56
- export type RouteComponent = SyncRouteComponent & {
57
- preload?: () => Promise<SyncRouteComponent>
58
- }
59
-
60
- export function lazy(
61
- importer: () => Promise<{ default: SyncRouteComponent }>,
62
- ): RouteComponent {
63
- const lazyComp = React.lazy(importer as any)
64
- let promise: Promise<SyncRouteComponent>
65
- let resolvedComp: SyncRouteComponent
66
-
67
- const forwardedComp = React.forwardRef((props, ref) => {
68
- const resolvedCompRef = React.useRef(resolvedComp || lazyComp)
69
- return React.createElement(
70
- resolvedCompRef.current as any,
71
- { ...(ref ? { ref } : {}), ...props } as any,
72
- )
73
- })
74
-
75
- const finalComp = forwardedComp as unknown as RouteComponent
76
-
77
- finalComp.preload = () => {
78
- if (!promise) {
79
- promise = importer().then((module) => {
80
- resolvedComp = module.default
81
- return resolvedComp
82
- })
83
- }
84
-
85
- return promise
86
- }
87
-
88
- return finalComp
89
- }
90
-
91
- declare module '@tanstack/router-core' {
92
- interface FrameworkGenerics {
93
- Component: RouteComponent
94
- }
95
-
96
- interface Router<
97
- TRouteConfig extends AnyRouteConfig = RouteConfig,
98
- TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
99
- > {
100
- useState: () => RouterState
101
- useRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
102
- routeId: TId,
103
- ) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
104
- useMatch: <
105
- TId extends keyof TAllRouteInfo['routeInfoById'],
106
- TStrict extends true | false = true,
107
- >(
108
- routeId: TId,
109
- opts?: { strict?: TStrict },
110
- ) => TStrict extends true
111
- ? RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
112
- :
113
- | RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
114
- | undefined
115
- linkProps: <TTo extends string = '.'>(
116
- props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
117
- React.AnchorHTMLAttributes<HTMLAnchorElement>,
118
- ) => React.AnchorHTMLAttributes<HTMLAnchorElement>
119
- Link: <TTo extends string = '.'>(
120
- props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
121
- React.AnchorHTMLAttributes<HTMLAnchorElement> &
122
- Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
123
- // 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
124
- children?:
125
- | React.ReactNode
126
- | ((state: { isActive: boolean }) => React.ReactNode)
127
- },
128
- ) => JSX.Element
129
- MatchRoute: <TTo extends string = '.'>(
130
- props: ToOptions<TAllRouteInfo, '/', TTo> &
131
- MatchRouteOptions & {
132
- // 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
133
- children?:
134
- | React.ReactNode
135
- | ((
136
- params: RouteInfoByPath<
137
- TAllRouteInfo,
138
- ResolveRelativePath<'/', NoInfer<TTo>>
139
- >['allParams'],
140
- ) => React.ReactNode)
141
- },
142
- ) => JSX.Element
143
- }
144
-
145
- interface Route<
146
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
147
- TRouteInfo extends AnyRouteInfo = RouteInfo,
148
- > {
149
- useRoute: <
150
- TTo extends string = '.',
151
- TResolved extends string = ResolveRelativePath<
152
- TRouteInfo['id'],
153
- NoInfer<TTo>
154
- >,
155
- >(
156
- routeId: CheckId<
157
- TAllRouteInfo,
158
- TResolved,
159
- ToIdOption<TAllRouteInfo, TRouteInfo['id'], TTo>
160
- >,
161
- opts?: { strict?: boolean },
162
- ) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TResolved]>
163
- linkProps: <TTo extends string = '.'>(
164
- props: LinkPropsOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo> &
165
- React.AnchorHTMLAttributes<HTMLAnchorElement>,
166
- ) => React.AnchorHTMLAttributes<HTMLAnchorElement>
167
- Link: <TTo extends string = '.'>(
168
- props: LinkPropsOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo> &
169
- React.AnchorHTMLAttributes<HTMLAnchorElement> &
170
- Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
171
- // 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
172
- children?:
173
- | React.ReactNode
174
- | ((state: { isActive: boolean }) => React.ReactNode)
175
- },
176
- ) => JSX.Element
177
- MatchRoute: <TTo extends string = '.'>(
178
- props: ToOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo> &
179
- MatchRouteOptions & {
180
- // 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
181
- children?:
182
- | React.ReactNode
183
- | ((
184
- params: RouteInfoByPath<
185
- TAllRouteInfo,
186
- ResolveRelativePath<TRouteInfo['fullPath'], NoInfer<TTo>>
187
- >['allParams'],
188
- ) => React.ReactNode)
189
- },
190
- ) => JSX.Element
191
- }
192
- }
193
-
194
- type LinkPropsOptions<
195
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
196
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
197
- TTo extends string = '.',
198
- > = LinkOptions<TAllRouteInfo, TFrom, TTo> & {
199
- // 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)
200
- activeProps?:
201
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
202
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
203
- // 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)
204
- inactiveProps?:
205
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
206
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
207
- }
208
-
209
- export type PromptProps = {
210
- message: string
211
- when?: boolean | any
212
- children?: React.ReactNode
213
- }
214
-
215
1
  //
216
2
 
217
- export function Link<TTo extends string = '.'>(
218
- props: LinkPropsOptions<ResolvedAllRouteInfo, '/', TTo> &
219
- React.AnchorHTMLAttributes<HTMLAnchorElement> &
220
- Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
221
- children?:
222
- | React.ReactNode
223
- | ((state: { isActive: boolean }) => React.ReactNode)
224
- },
225
- ): JSX.Element {
226
- const router = useRouter()
227
- return <router.Link {...(props as any)} />
228
- }
229
-
230
- export const matchesContext = React.createContext<RouteMatch[]>(null!)
231
- export const routerContext = React.createContext<{ router: ResolvedRouter }>(
232
- null!,
233
- )
234
-
235
- export type MatchesProviderProps = {
236
- value: RouteMatch[]
237
- children: React.ReactNode
238
- }
239
-
240
- export function MatchesProvider(props: MatchesProviderProps) {
241
- return <matchesContext.Provider {...props} />
242
- }
243
-
244
- const useRouterSubscription = (router: Router<any, any>) => {
245
- useSyncExternalStore(
246
- (cb) => router.subscribe(() => cb()),
247
- () => router.state,
248
- () => router.state,
249
- )
250
- }
251
-
252
- export function createReactRouter<
253
- TRouteConfig extends AnyRouteConfig = RouteConfig,
254
- >(opts: RouterOptions<TRouteConfig>): Router<TRouteConfig> {
255
- const makeRouteExt = (
256
- route: AnyRoute,
257
- router: Router<any, any>,
258
- ): Pick<AnyRoute, 'useRoute' | 'linkProps' | 'Link' | 'MatchRoute'> => {
259
- return {
260
- useRoute: (subRouteId = '.' as any) => {
261
- const resolvedRouteId = router.resolvePath(
262
- route.routeId,
263
- subRouteId as string,
264
- )
265
- const resolvedRoute = router.getRoute(resolvedRouteId)
266
- useRouterSubscription(router)
267
- invariant(
268
- resolvedRoute,
269
- `Could not find a route for route "${
270
- resolvedRouteId as string
271
- }"! Did you forget to add it to your route config?`,
272
- )
273
- return resolvedRoute
274
- },
275
- linkProps: (options) => {
276
- const {
277
- // custom props
278
- type,
279
- children,
280
- target,
281
- activeProps = () => ({ className: 'active' }),
282
- inactiveProps = () => ({}),
283
- activeOptions,
284
- disabled,
285
- // fromCurrent,
286
- hash,
287
- search,
288
- params,
289
- to,
290
- preload,
291
- preloadDelay,
292
- preloadMaxAge,
293
- replace,
294
- // element props
295
- style,
296
- className,
297
- onClick,
298
- onFocus,
299
- onMouseEnter,
300
- onMouseLeave,
301
- onTouchStart,
302
- onTouchEnd,
303
- ...rest
304
- } = options
305
-
306
- const linkInfo = route.buildLink(options)
307
-
308
- if (linkInfo.type === 'external') {
309
- const { href } = linkInfo
310
- return { href }
311
- }
312
-
313
- const {
314
- handleClick,
315
- handleFocus,
316
- handleEnter,
317
- handleLeave,
318
- isActive,
319
- next,
320
- } = linkInfo
321
-
322
- const reactHandleClick = (e: Event) => {
323
- React.startTransition(() => {
324
- handleClick(e)
325
- })
326
- }
327
-
328
- const composeHandlers =
329
- (handlers: (undefined | ((e: any) => void))[]) =>
330
- (e: React.SyntheticEvent) => {
331
- e.persist()
332
- handlers.forEach((handler) => {
333
- if (handler) handler(e)
334
- })
335
- }
336
-
337
- // Get the active props
338
- const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> =
339
- isActive ? functionalUpdate(activeProps, {}) ?? {} : {}
340
-
341
- // Get the inactive props
342
- const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
343
- isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {}
344
-
345
- return {
346
- ...resolvedActiveProps,
347
- ...resolvedInactiveProps,
348
- ...rest,
349
- href: disabled ? undefined : next.href,
350
- onClick: composeHandlers([reactHandleClick, onClick]),
351
- onFocus: composeHandlers([handleFocus, onFocus]),
352
- onMouseEnter: composeHandlers([handleEnter, onMouseEnter]),
353
- onMouseLeave: composeHandlers([handleLeave, onMouseLeave]),
354
- target,
355
- style: {
356
- ...style,
357
- ...resolvedActiveProps.style,
358
- ...resolvedInactiveProps.style,
359
- },
360
- className:
361
- [
362
- className,
363
- resolvedActiveProps.className,
364
- resolvedInactiveProps.className,
365
- ]
366
- .filter(Boolean)
367
- .join(' ') || undefined,
368
- ...(disabled
369
- ? {
370
- role: 'link',
371
- 'aria-disabled': true,
372
- }
373
- : undefined),
374
- ['data-status']: isActive ? 'active' : undefined,
375
- }
376
- },
377
- Link: React.forwardRef((props: any, ref) => {
378
- const linkProps = route.linkProps(props)
379
-
380
- useRouterSubscription(router)
381
-
382
- return (
383
- <a
384
- {...{
385
- ref: ref as any,
386
- ...linkProps,
387
- children:
388
- typeof props.children === 'function'
389
- ? props.children({
390
- isActive: (linkProps as any)['data-status'] === 'active',
391
- })
392
- : props.children,
393
- }}
394
- />
395
- )
396
- }) as any,
397
- MatchRoute: (opts) => {
398
- const { pending, caseSensitive, children, ...rest } = opts
399
-
400
- const params = route.matchRoute(rest as any, {
401
- pending,
402
- caseSensitive,
403
- })
404
-
405
- if (!params) {
406
- return null
407
- }
408
-
409
- return typeof opts.children === 'function'
410
- ? opts.children(params as any)
411
- : (opts.children as any)
412
- },
413
- }
414
- }
415
-
416
- const coreRouter = createRouter<TRouteConfig>({
417
- ...opts,
418
- createRouter: (router) => {
419
- const routerExt: Pick<Router<any, any>, 'useMatch' | 'useState'> = {
420
- useState: () => {
421
- useRouterSubscription(router)
422
- return router.state
423
- },
424
- useMatch: (routeId, opts) => {
425
- useRouterSubscription(router)
426
-
427
- invariant(
428
- routeId !== rootRouteId,
429
- `"${rootRouteId}" cannot be used with useMatch! Did you mean to useRoute("${rootRouteId}")?`,
430
- )
431
-
432
- const runtimeMatch = useMatches()?.[0]!
433
- const match = router.state.matches.find((d) => d.routeId === routeId)
434
-
435
- if (opts?.strict ?? true) {
436
- invariant(
437
- match,
438
- `Could not find an active match for "${routeId as string}"!`,
439
- )
440
-
441
- invariant(
442
- runtimeMatch.routeId == match?.routeId,
443
- `useMatch("${
444
- match?.routeId as string
445
- }") is being called in a component that is meant to render the '${
446
- runtimeMatch.routeId
447
- }' route. Did you mean to 'useMatch("${
448
- match?.routeId as string
449
- }", { strict: false })' or 'useRoute("${
450
- match?.routeId as string
451
- }")' instead?`,
452
- )
453
- }
454
-
455
- return match as any
456
- },
457
- }
458
-
459
- const routeExt = makeRouteExt(router.getRoute('/'), router)
460
-
461
- Object.assign(router, routerExt, routeExt)
462
- },
463
- createRoute: ({ router, route }) => {
464
- const routeExt = makeRouteExt(route, router)
465
-
466
- Object.assign(route, routeExt)
467
- },
468
- loadComponent: async (component) => {
469
- if (component.preload && typeof document !== 'undefined') {
470
- component.preload()
471
- // return await component.preload()
472
- }
473
-
474
- return component as any
475
- },
476
- })
477
-
478
- return coreRouter as any
479
- }
480
-
481
- export type RouterProps<
482
- TRouteConfig extends AnyRouteConfig = RouteConfig,
483
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
484
- > = RouterOptions<TRouteConfig> & {
485
- router: Router<TRouteConfig, TAllRouteInfo>
486
- // Children will default to `<Outlet />` if not provided
487
- children?: React.ReactNode
488
- }
489
-
490
- export function RouterProvider<
491
- TRouteConfig extends AnyRouteConfig = RouteConfig,
492
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
493
- >({ children, router, ...rest }: RouterProps<TRouteConfig, TAllRouteInfo>) {
494
- router.update(rest)
495
-
496
- useRouterSubscription(router)
497
- React.useEffect(() => {
498
- console.log('hello')
499
- return router.mount()
500
- }, [router])
501
-
502
- return (
503
- <routerContext.Provider value={{ router: router as any }}>
504
- <MatchesProvider value={router.state.matches}>
505
- {children ?? <Outlet />}
506
- </MatchesProvider>
507
- </routerContext.Provider>
508
- )
509
- }
510
-
511
- export function useRouter(): ResolvedRouter {
512
- const value = React.useContext(routerContext)
513
- warning(!value, 'useRouter must be used inside a <Router> component!')
514
-
515
- useRouterSubscription(value.router)
516
-
517
- return value.router
518
- }
519
-
520
- export function useMatches(): RouteMatch[] {
521
- return React.useContext(matchesContext)
522
- }
523
-
524
- export function useMatch<
525
- TId extends keyof ResolvedAllRouteInfo['routeInfoById'],
526
- TStrict extends true | false = true,
527
- >(
528
- routeId: TId,
529
- opts?: { strict?: TStrict },
530
- ): TStrict extends true
531
- ? RouteMatch<ResolvedAllRouteInfo, ResolvedAllRouteInfo['routeInfoById'][TId]>
532
- :
533
- | RouteMatch<
534
- ResolvedAllRouteInfo,
535
- ResolvedAllRouteInfo['routeInfoById'][TId]
536
- >
537
- | undefined {
538
- const router = useRouter()
539
- return router.useMatch(routeId as any, opts) as any
540
- }
541
-
542
- export function Outlet() {
543
- const router = useRouter()
544
- const matches = useMatches().slice(1)
545
- const match = matches[0]
546
-
547
- const defaultPending = React.useCallback(() => null, [])
548
-
549
- if (!match) {
550
- return null
551
- }
552
-
553
- const PendingComponent = (match.__.pendingComponent ??
554
- router.options.defaultPendingComponent ??
555
- defaultPending) as any
556
-
557
- const errorComponent =
558
- match.__.errorComponent ?? router.options.defaultErrorComponent
559
-
560
- return (
561
- <MatchesProvider value={matches}>
562
- <React.Suspense fallback={<PendingComponent />}>
563
- <CatchBoundary errorComponent={errorComponent}>
564
- {
565
- ((): React.ReactNode => {
566
- if (match.status === 'error') {
567
- throw match.error
568
- }
569
-
570
- if (match.status === 'success') {
571
- return React.createElement(
572
- (match.__.component as any) ??
573
- router.options.defaultComponent ??
574
- Outlet,
575
- )
576
- }
577
-
578
- console.log(match.matchId, 'suspend')
579
- throw match.__.loadPromise
580
- })() as JSX.Element
581
- }
582
- </CatchBoundary>
583
- </React.Suspense>
584
- </MatchesProvider>
585
- )
586
- }
587
-
588
- class CatchBoundary extends React.Component<{
589
- children: any
590
- errorComponent: any
591
- }> {
592
- state = {
593
- error: false,
594
- }
595
- componentDidCatch(error: any, info: any) {
596
- console.error(error)
597
-
598
- this.setState({
599
- error,
600
- info,
601
- })
602
- }
603
- render() {
604
- const errorComponent = this.props.errorComponent ?? DefaultErrorBoundary
605
-
606
- if (this.state.error) {
607
- return React.createElement(errorComponent, this.state)
608
- }
609
-
610
- return this.props.children
611
- }
612
- }
613
-
614
- export function DefaultErrorBoundary({ error }: { error: any }) {
615
- return (
616
- <div style={{ padding: '.5rem', maxWidth: '100%' }}>
617
- <strong style={{ fontSize: '1.2rem' }}>Something went wrong!</strong>
618
- <div style={{ height: '.5rem' }} />
619
- <div>
620
- <pre>
621
- {error.message ? (
622
- <code
623
- style={{
624
- fontSize: '.7em',
625
- border: '1px solid red',
626
- borderRadius: '.25rem',
627
- padding: '.5rem',
628
- color: 'red',
629
- }}
630
- >
631
- {error.message}
632
- </code>
633
- ) : null}
634
- </pre>
635
- </div>
636
- </div>
637
- )
638
- }
639
-
640
- export function usePrompt(message: string, when: boolean | any): void {
641
- const router = useRouter()
642
-
643
- React.useEffect(() => {
644
- if (!when) return
645
-
646
- let unblock = router.history.block((transition) => {
647
- if (window.confirm(message)) {
648
- unblock()
649
- transition.retry()
650
- } else {
651
- router.location.pathname = window.location.pathname
652
- }
653
- })
654
-
655
- return unblock
656
- }, [when, location, message])
657
- }
658
-
659
- export function Prompt({ message, when, children }: PromptProps) {
660
- usePrompt(message, when ?? true)
661
- return (children ?? null) as React.ReactNode
662
- }
3
+ export { useStore } from '@tanstack/react-store'
4
+ export * from '@tanstack/router-core'
5
+ export * from './react'
6
+ export * from './scroll-restoration'
7
+ export * from './awaited'