@tanstack/react-router 0.0.1-alpha.2 → 0.0.1-alpha.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-alpha.2",
4
+ "version": "0.0.1-alpha.4",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://react-router.tanstack.com/",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@babel/runtime": "^7.16.7",
43
- "@tanstack/router-core": "0.0.1-alpha.2",
43
+ "@tanstack/router-core": "0.0.1-alpha.4",
44
44
  "use-sync-external-store": "^1.2.0"
45
45
  },
46
46
  "devDependencies": {
package/src/index.tsx CHANGED
@@ -6,7 +6,6 @@ import {
6
6
  AnyRoute,
7
7
  RootRouteId,
8
8
  rootRouteId,
9
- Route,
10
9
  Router,
11
10
  } from '@tanstack/router-core'
12
11
  import {
@@ -29,6 +28,7 @@ import {
29
28
  ResolveRelativePath,
30
29
  NoInfer,
31
30
  ToOptions,
31
+ invariant,
32
32
  } from '@tanstack/router-core'
33
33
 
34
34
  export * from '@tanstack/router-core'
@@ -55,34 +55,34 @@ declare module '@tanstack/router-core' {
55
55
  useMatch: <TId extends keyof TAllRouteInfo['routeInfoById']>(
56
56
  routeId: TId,
57
57
  ) => RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
58
- // linkProps: <TTo extends string = '.'>(
59
- // props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
60
- // React.AnchorHTMLAttributes<HTMLAnchorElement>,
61
- // ) => React.AnchorHTMLAttributes<HTMLAnchorElement>
62
- // Link: <TTo extends string = '.'>(
63
- // props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
64
- // React.AnchorHTMLAttributes<HTMLAnchorElement> &
65
- // Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
66
- // // 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
67
- // children?:
68
- // | React.ReactNode
69
- // | ((state: { isActive: boolean }) => React.ReactNode)
70
- // },
71
- // ) => JSX.Element
72
- // MatchRoute: <TTo extends string = '.'>(
73
- // props: ToOptions<TAllRouteInfo, '/', TTo> &
74
- // MatchRouteOptions & {
75
- // // 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
76
- // children?:
77
- // | React.ReactNode
78
- // | ((
79
- // params: RouteInfoByPath<
80
- // TAllRouteInfo,
81
- // ResolveRelativePath<'/', NoInfer<TTo>>
82
- // >['allParams'],
83
- // ) => React.ReactNode)
84
- // },
85
- // ) => JSX.Element
58
+ linkProps: <TTo extends string = '.'>(
59
+ props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
60
+ React.AnchorHTMLAttributes<HTMLAnchorElement>,
61
+ ) => React.AnchorHTMLAttributes<HTMLAnchorElement>
62
+ Link: <TTo extends string = '.'>(
63
+ props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
64
+ React.AnchorHTMLAttributes<HTMLAnchorElement> &
65
+ Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
66
+ // 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
67
+ children?:
68
+ | React.ReactNode
69
+ | ((state: { isActive: boolean }) => React.ReactNode)
70
+ },
71
+ ) => JSX.Element
72
+ MatchRoute: <TTo extends string = '.'>(
73
+ props: ToOptions<TAllRouteInfo, '/', TTo> &
74
+ MatchRouteOptions & {
75
+ // 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
76
+ children?:
77
+ | React.ReactNode
78
+ | ((
79
+ params: RouteInfoByPath<
80
+ TAllRouteInfo,
81
+ ResolveRelativePath<'/', NoInfer<TTo>>
82
+ >['allParams'],
83
+ ) => React.ReactNode)
84
+ },
85
+ ) => JSX.Element
86
86
  }
87
87
 
88
88
  interface Route<
@@ -174,6 +174,148 @@ const useRouterSubscription = (router: Router<any, any>) => {
174
174
  export function createReactRouter<
175
175
  TRouteConfig extends AnyRouteConfig = RouteConfig,
176
176
  >(opts: RouterOptions<TRouteConfig>): Router<TRouteConfig> {
177
+ const makeRouteExt = (
178
+ route: AnyRoute,
179
+ router: Router<any, any>,
180
+ ): Pick<AnyRoute, 'linkProps' | 'Link' | 'MatchRoute'> => {
181
+ return {
182
+ linkProps: (options) => {
183
+ const {
184
+ // custom props
185
+ type,
186
+ children,
187
+ target,
188
+ activeProps = () => ({ className: 'active' }),
189
+ inactiveProps = () => ({}),
190
+ activeOptions,
191
+ disabled,
192
+ // fromCurrent,
193
+ hash,
194
+ search,
195
+ params,
196
+ to,
197
+ preload,
198
+ preloadDelay,
199
+ preloadMaxAge,
200
+ replace,
201
+ // element props
202
+ style,
203
+ className,
204
+ onClick,
205
+ onFocus,
206
+ onMouseEnter,
207
+ onMouseLeave,
208
+ onTouchStart,
209
+ onTouchEnd,
210
+ ...rest
211
+ } = options
212
+
213
+ const linkInfo = route.buildLink(options)
214
+
215
+ if (linkInfo.type === 'external') {
216
+ const { href } = linkInfo
217
+ return { href }
218
+ }
219
+
220
+ const {
221
+ handleClick,
222
+ handleFocus,
223
+ handleEnter,
224
+ handleLeave,
225
+ isActive,
226
+ next,
227
+ } = linkInfo
228
+
229
+ const composeHandlers =
230
+ (handlers: (undefined | ((e: any) => void))[]) =>
231
+ (e: React.SyntheticEvent) => {
232
+ e.persist()
233
+ handlers.forEach((handler) => {
234
+ if (handler) handler(e)
235
+ })
236
+ }
237
+
238
+ // Get the active props
239
+ const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> =
240
+ isActive ? functionalUpdate(activeProps) ?? {} : {}
241
+
242
+ // Get the inactive props
243
+ const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
244
+ isActive ? {} : functionalUpdate(inactiveProps) ?? {}
245
+
246
+ return {
247
+ ...resolvedActiveProps,
248
+ ...resolvedInactiveProps,
249
+ ...rest,
250
+ href: disabled ? undefined : next.href,
251
+ onClick: composeHandlers([handleClick, onClick]),
252
+ onFocus: composeHandlers([handleFocus, onFocus]),
253
+ onMouseEnter: composeHandlers([handleEnter, onMouseEnter]),
254
+ onMouseLeave: composeHandlers([handleLeave, onMouseLeave]),
255
+ target,
256
+ style: {
257
+ ...style,
258
+ ...resolvedActiveProps.style,
259
+ ...resolvedInactiveProps.style,
260
+ },
261
+ className:
262
+ [
263
+ className,
264
+ resolvedActiveProps.className,
265
+ resolvedInactiveProps.className,
266
+ ]
267
+ .filter(Boolean)
268
+ .join(' ') || undefined,
269
+ ...(disabled
270
+ ? {
271
+ role: 'link',
272
+ 'aria-disabled': true,
273
+ }
274
+ : undefined),
275
+ ['data-status']: isActive ? 'active' : undefined,
276
+ }
277
+ },
278
+ Link: React.forwardRef((props: any, ref) => {
279
+ const linkProps = route.linkProps(props)
280
+
281
+ useRouterSubscription(router)
282
+
283
+ return (
284
+ <a
285
+ {...{
286
+ ref: ref as any,
287
+ ...linkProps,
288
+ children:
289
+ typeof props.children === 'function'
290
+ ? props.children({
291
+ isActive: (linkProps as any)['data-status'] === 'active',
292
+ })
293
+ : props.children,
294
+ }}
295
+ />
296
+ )
297
+ }) as any,
298
+ MatchRoute: (opts) => {
299
+ const { pending, caseSensitive, children, ...rest } = opts
300
+
301
+ const params = route.matchRoute(rest as any, {
302
+ pending,
303
+ caseSensitive,
304
+ })
305
+
306
+ // useRouterSubscription(router)
307
+
308
+ if (!params) {
309
+ return null
310
+ }
311
+
312
+ return typeof opts.children === 'function'
313
+ ? opts.children(params as any)
314
+ : (opts.children as any)
315
+ },
316
+ }
317
+ }
318
+
177
319
  const coreRouter = createRouter<TRouteConfig>({
178
320
  ...opts,
179
321
  createRouter: (router) => {
@@ -181,194 +323,57 @@ export function createReactRouter<
181
323
  useRoute: (routeId) => {
182
324
  const route = router.getRoute(routeId)
183
325
  useRouterSubscription(router)
184
- if (!route) {
185
- throw new Error(
186
- `Could not find a route for route "${
187
- routeId as string
188
- }"! Did you forget to add it to your route config?`,
189
- )
190
- }
326
+ invariant(
327
+ route,
328
+ `Could not find a route for route "${
329
+ routeId as string
330
+ }"! Did you forget to add it to your route config?`,
331
+ )
191
332
  return route
192
333
  },
193
334
  useMatch: (routeId) => {
194
- if (routeId === rootRouteId) {
195
- throw new Error(
196
- `"${rootRouteId}" cannot be used with useMatch! Did you mean to useRoute("${rootRouteId}")?`,
197
- )
198
- }
335
+ invariant(
336
+ routeId !== rootRouteId,
337
+ `"${rootRouteId}" cannot be used with useMatch! Did you mean to useRoute("${rootRouteId}")?`,
338
+ )
339
+
199
340
  const runtimeMatch = useMatch()
200
341
  const match = router.state.matches.find((d) => d.routeId === routeId)
201
342
 
202
- if (!match) {
203
- throw new Error(
204
- `Could not find a match for route "${
205
- routeId as string
206
- }" being rendered in this component!`,
207
- )
208
- }
343
+ invariant(
344
+ match,
345
+ `Could not find a match for route "${
346
+ routeId as string
347
+ }" being rendered in this component!`,
348
+ )
209
349
 
210
- if (runtimeMatch.routeId !== match?.routeId) {
211
- throw new Error(
212
- `useMatch('${
213
- match?.routeId as string
214
- }') is being called in a component that is meant to render the '${
215
- runtimeMatch.routeId
216
- }' route. Did you mean to 'useRoute(${
217
- match?.routeId as string
218
- })' instead?`,
219
- )
220
- }
350
+ invariant(
351
+ runtimeMatch.routeId == match?.routeId,
352
+ `useMatch('${
353
+ match?.routeId as string
354
+ }') is being called in a component that is meant to render the '${
355
+ runtimeMatch.routeId
356
+ }' route. Did you mean to 'useRoute(${
357
+ match?.routeId as string
358
+ })' instead?`,
359
+ )
221
360
 
222
361
  useRouterSubscription(router)
223
362
 
224
363
  if (!match) {
225
- throw new Error('Match not found!')
364
+ invariant('Match not found!')
226
365
  }
227
366
 
228
367
  return match
229
368
  },
230
369
  }
231
370
 
232
- Object.assign(router, routerExt)
371
+ const routeExt = makeRouteExt(router.getRoute('/'), router)
372
+
373
+ Object.assign(router, routerExt, routeExt)
233
374
  },
234
375
  createRoute: ({ router, route }) => {
235
- const routeExt: Pick<AnyRoute, 'linkProps' | 'Link' | 'MatchRoute'> = {
236
- linkProps: (options) => {
237
- const {
238
- // custom props
239
- type,
240
- children,
241
- target,
242
- activeProps = () => ({ className: 'active' }),
243
- inactiveProps = () => ({}),
244
- activeOptions,
245
- disabled,
246
- // fromCurrent,
247
- hash,
248
- search,
249
- params,
250
- to,
251
- preload,
252
- preloadDelay,
253
- preloadMaxAge,
254
- replace,
255
- // element props
256
- style,
257
- className,
258
- onClick,
259
- onFocus,
260
- onMouseEnter,
261
- onMouseLeave,
262
- onTouchStart,
263
- onTouchEnd,
264
- ...rest
265
- } = options
266
-
267
- const linkInfo = route.buildLink(options)
268
-
269
- if (linkInfo.type === 'external') {
270
- const { href } = linkInfo
271
- return { href }
272
- }
273
-
274
- const {
275
- handleClick,
276
- handleFocus,
277
- handleEnter,
278
- handleLeave,
279
- isActive,
280
- next,
281
- } = linkInfo
282
-
283
- const composeHandlers =
284
- (handlers: (undefined | ((e: any) => void))[]) =>
285
- (e: React.SyntheticEvent) => {
286
- e.persist()
287
- handlers.forEach((handler) => {
288
- if (handler) handler(e)
289
- })
290
- }
291
-
292
- // Get the active props
293
- const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> =
294
- isActive ? functionalUpdate(activeProps) ?? {} : {}
295
-
296
- // Get the inactive props
297
- const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
298
- isActive ? {} : functionalUpdate(inactiveProps) ?? {}
299
-
300
- return {
301
- ...resolvedActiveProps,
302
- ...resolvedInactiveProps,
303
- ...rest,
304
- href: disabled ? undefined : next.href,
305
- onClick: composeHandlers([handleClick, onClick]),
306
- onFocus: composeHandlers([handleFocus, onFocus]),
307
- onMouseEnter: composeHandlers([handleEnter, onMouseEnter]),
308
- onMouseLeave: composeHandlers([handleLeave, onMouseLeave]),
309
- target,
310
- style: {
311
- ...style,
312
- ...resolvedActiveProps.style,
313
- ...resolvedInactiveProps.style,
314
- },
315
- className:
316
- [
317
- className,
318
- resolvedActiveProps.className,
319
- resolvedInactiveProps.className,
320
- ]
321
- .filter(Boolean)
322
- .join(' ') || undefined,
323
- ...(disabled
324
- ? {
325
- role: 'link',
326
- 'aria-disabled': true,
327
- }
328
- : undefined),
329
- ['data-status']: isActive ? 'active' : undefined,
330
- }
331
- },
332
- Link: React.forwardRef((props: any, ref) => {
333
- const linkProps = route.linkProps(props)
334
-
335
- useRouterSubscription(router)
336
-
337
- return (
338
- <a
339
- {...{
340
- ref: ref as any,
341
- ...linkProps,
342
- children:
343
- typeof props.children === 'function'
344
- ? props.children({
345
- isActive:
346
- (linkProps as any)['data-status'] === 'active',
347
- })
348
- : props.children,
349
- }}
350
- />
351
- )
352
- }) as any,
353
- MatchRoute: (opts) => {
354
- const { pending, caseSensitive, children, ...rest } = opts
355
-
356
- const params = route.matchRoute(rest as any, {
357
- pending,
358
- caseSensitive,
359
- })
360
-
361
- // useRouterSubscription(router)
362
-
363
- if (!params) {
364
- return null
365
- }
366
-
367
- return typeof opts.children === 'function'
368
- ? opts.children(params as any)
369
- : (opts.children as any)
370
- },
371
- }
376
+ const routeExt = makeRouteExt(route, router)
372
377
 
373
378
  Object.assign(route, routeExt)
374
379
  },