@tanstack/vue-router 1.141.4 → 1.141.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 (47) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/ClientOnly.js +33 -0
  3. package/dist/esm/ClientOnly.js.map +1 -0
  4. package/dist/esm/Match.js +66 -39
  5. package/dist/esm/Match.js.map +1 -1
  6. package/dist/esm/Transitioner.js +14 -5
  7. package/dist/esm/Transitioner.js.map +1 -1
  8. package/dist/esm/index.d.ts +2 -1
  9. package/dist/esm/index.js +2 -0
  10. package/dist/esm/index.js.map +1 -1
  11. package/dist/esm/lazyRouteComponent.d.ts +0 -6
  12. package/dist/esm/lazyRouteComponent.js +5 -24
  13. package/dist/esm/lazyRouteComponent.js.map +1 -1
  14. package/dist/esm/link.d.ts +20 -22
  15. package/dist/esm/link.js +3 -2
  16. package/dist/esm/link.js.map +1 -1
  17. package/dist/esm/route.d.ts +6 -1
  18. package/dist/esm/route.js +25 -0
  19. package/dist/esm/route.js.map +1 -1
  20. package/dist/esm/ssr/renderRouterToStream.js +1 -1
  21. package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
  22. package/dist/source/Match.jsx +90 -63
  23. package/dist/source/Match.jsx.map +1 -1
  24. package/dist/source/Transitioner.jsx +12 -5
  25. package/dist/source/Transitioner.jsx.map +1 -1
  26. package/dist/source/index.d.ts +2 -1
  27. package/dist/source/index.jsx +1 -0
  28. package/dist/source/index.jsx.map +1 -1
  29. package/dist/source/lazyRouteComponent.d.ts +0 -6
  30. package/dist/source/lazyRouteComponent.jsx +3 -23
  31. package/dist/source/lazyRouteComponent.jsx.map +1 -1
  32. package/dist/source/link.d.ts +20 -22
  33. package/dist/source/link.jsx +3 -2
  34. package/dist/source/link.jsx.map +1 -1
  35. package/dist/source/route.d.ts +6 -1
  36. package/dist/source/route.js +13 -0
  37. package/dist/source/route.js.map +1 -1
  38. package/dist/source/ssr/renderRouterToStream.jsx +1 -1
  39. package/dist/source/ssr/renderRouterToStream.jsx.map +1 -1
  40. package/package.json +3 -2
  41. package/src/Match.tsx +115 -73
  42. package/src/Transitioner.tsx +15 -6
  43. package/src/index.tsx +2 -0
  44. package/src/lazyRouteComponent.tsx +10 -32
  45. package/src/link.tsx +87 -59
  46. package/src/route.ts +33 -1
  47. package/src/ssr/renderRouterToStream.tsx +1 -1
package/src/link.tsx CHANGED
@@ -19,39 +19,48 @@ import type {
19
19
  RegisteredRouter,
20
20
  RoutePaths,
21
21
  } from '@tanstack/router-core'
22
+ import type { AnchorHTMLAttributes, ReservedProps } from '@vue/runtime-dom'
22
23
  import type {
23
24
  ValidateLinkOptions,
24
25
  ValidateLinkOptionsArray,
25
26
  } from './typePrimitives'
26
27
 
27
- // Type definitions to replace missing Vue JSX types
28
28
  type EventHandler<TEvent = Event> = (e: TEvent) => void
29
- interface HTMLAttributes {
30
- class?: string
31
- style?: Record<string, string | number>
32
- onClick?: EventHandler<MouseEvent>
33
- onFocus?: EventHandler<FocusEvent>
34
- // Vue 3's h() function expects lowercase event names after 'on' prefix
35
- onMouseenter?: EventHandler<MouseEvent>
36
- onMouseleave?: EventHandler<MouseEvent>
37
- onMouseover?: EventHandler<MouseEvent>
38
- onMouseout?: EventHandler<MouseEvent>
39
- onTouchstart?: EventHandler<TouchEvent>
40
- // Also accept the camelCase versions for external API compatibility
41
- onMouseEnter?: EventHandler<MouseEvent>
42
- onMouseLeave?: EventHandler<MouseEvent>
43
- onMouseOver?: EventHandler<MouseEvent>
44
- onMouseOut?: EventHandler<MouseEvent>
45
- onTouchStart?: EventHandler<TouchEvent>
46
- [key: string]: any
29
+
30
+ type DataAttributes = {
31
+ [K in `data-${string}`]?: unknown
47
32
  }
48
33
 
34
+ type LinkHTMLAttributes = AnchorHTMLAttributes &
35
+ ReservedProps &
36
+ DataAttributes & {
37
+ // Vue's runtime-dom types use lowercase event names.
38
+ // Also accept camelCase versions for external API compatibility.
39
+ onMouseEnter?: EventHandler<MouseEvent>
40
+ onMouseLeave?: EventHandler<MouseEvent>
41
+ onMouseOver?: EventHandler<MouseEvent>
42
+ onMouseOut?: EventHandler<MouseEvent>
43
+ onTouchStart?: EventHandler<TouchEvent>
44
+
45
+ // `disabled` is not a valid <a> attribute, but is useful when using `asChild`.
46
+ disabled?: boolean
47
+ }
48
+
49
49
  interface StyledProps {
50
- class?: string
51
- style?: Record<string, string | number>
52
- [key: string]: any
50
+ class?: LinkHTMLAttributes['class']
51
+ style?: LinkHTMLAttributes['style']
52
+ [key: string]: unknown
53
53
  }
54
54
 
55
+ type PropsOfComponent<TComp> =
56
+ // Functional components
57
+ TComp extends (props: infer P, ...args: Array<unknown>) => any
58
+ ? P
59
+ : // Vue components (defineComponent, class components, etc)
60
+ TComp extends Vue.Component<infer P>
61
+ ? P
62
+ : Record<string, unknown>
63
+
55
64
  export function useLinkProps<
56
65
  TRouter extends AnyRouter = RegisteredRouter,
57
66
  TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
@@ -60,7 +69,7 @@ export function useLinkProps<
60
69
  TMaskTo extends string = '',
61
70
  >(
62
71
  options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
63
- ): HTMLAttributes {
72
+ ): LinkHTMLAttributes {
64
73
  const router = useRouter()
65
74
  const isTransitioning = Vue.ref(false)
66
75
  let hasRenderFetched = false
@@ -195,6 +204,7 @@ export function useLinkProps<
195
204
  // Create safe props that can be spread
196
205
  const getPropsSafeToSpread = () => {
197
206
  const result: Record<string, any> = {}
207
+ const optionRecord = options as unknown as Record<string, unknown>
198
208
  for (const key in options) {
199
209
  if (
200
210
  ![
@@ -233,7 +243,7 @@ export function useLinkProps<
233
243
  'additionalProps',
234
244
  ].includes(key)
235
245
  ) {
236
- result[key] = options[key]
246
+ result[key] = optionRecord[key]
237
247
  }
238
248
  }
239
249
  return result
@@ -241,7 +251,7 @@ export function useLinkProps<
241
251
 
242
252
  if (type.value === 'external') {
243
253
  // External links just have simple props
244
- const externalProps: HTMLAttributes = {
254
+ const externalProps: Record<string, unknown> = {
245
255
  ...getPropsSafeToSpread(),
246
256
  ref,
247
257
  href: options.to,
@@ -265,11 +275,11 @@ export function useLinkProps<
265
275
  }
266
276
  })
267
277
 
268
- return externalProps
278
+ return externalProps as LinkHTMLAttributes
269
279
  }
270
280
 
271
281
  // The click handler
272
- const handleClick = (e: MouseEvent): void => {
282
+ const handleClick = (e: PointerEvent): void => {
273
283
  // Check actual element's target attribute as fallback
274
284
  const elementTarget = (
275
285
  e.currentTarget as HTMLAnchorElement | SVGAElement
@@ -307,7 +317,7 @@ export function useLinkProps<
307
317
  startTransition: options.startTransition,
308
318
  viewTransition: options.viewTransition,
309
319
  ignoreBlocker: options.ignoreBlocker,
310
- } as any)
320
+ })
311
321
  }
312
322
  }
313
323
 
@@ -448,7 +458,7 @@ export function useLinkProps<
448
458
 
449
459
  // Create static event handlers that don't change between renders
450
460
  const staticEventHandlers = {
451
- onClick: composeEventHandlers<MouseEvent>([
461
+ onClick: composeEventHandlers<PointerEvent>([
452
462
  options.onClick,
453
463
  handleClick,
454
464
  ]) as any,
@@ -480,8 +490,8 @@ export function useLinkProps<
480
490
 
481
491
  // Compute all props synchronously to avoid hydration mismatches
482
492
  // Using Vue.computed ensures props are calculated at render time, not after
483
- const computedProps = Vue.computed<HTMLAttributes>(() => {
484
- const result: HTMLAttributes = {
493
+ const computedProps = Vue.computed<LinkHTMLAttributes>(() => {
494
+ const result: Record<string, unknown> = {
485
495
  ...getPropsSafeToSpread(),
486
496
  href: href.value,
487
497
  ref,
@@ -523,20 +533,20 @@ export function useLinkProps<
523
533
 
524
534
  for (const key of Object.keys(activeP)) {
525
535
  if (key !== 'class' && key !== 'style') {
526
- result[key] = activeP[key]
536
+ result[key] = (activeP as any)[key]
527
537
  }
528
538
  }
529
539
  for (const key of Object.keys(inactiveP)) {
530
540
  if (key !== 'class' && key !== 'style') {
531
- result[key] = inactiveP[key]
541
+ result[key] = (inactiveP as any)[key]
532
542
  }
533
543
  }
534
544
 
535
- return result
545
+ return result as LinkHTMLAttributes
536
546
  })
537
547
 
538
548
  // Return the computed ref itself - callers should access .value
539
- return computedProps as unknown as HTMLAttributes
549
+ return computedProps as unknown as LinkHTMLAttributes
540
550
  }
541
551
 
542
552
  // Type definitions
@@ -547,7 +557,7 @@ export type UseLinkPropsOptions<
547
557
  TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
548
558
  TMaskTo extends string = '.',
549
559
  > = ActiveLinkOptions<'a', TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
550
- HTMLAttributes
560
+ LinkHTMLAttributes
551
561
 
552
562
  export type ActiveLinkOptions<
553
563
  TComp = 'a',
@@ -560,7 +570,9 @@ export type ActiveLinkOptions<
560
570
  ActiveLinkOptionProps<TComp>
561
571
 
562
572
  type ActiveLinkProps<TComp> = Partial<
563
- HTMLAttributes & {
573
+ (TComp extends keyof HTMLElementTagNameMap
574
+ ? LinkHTMLAttributes
575
+ : PropsOfComponent<TComp>) & {
564
576
  [key: `data-${string}`]: unknown
565
577
  }
566
578
  >
@@ -591,15 +603,16 @@ export type LinkProps<
591
603
  export interface LinkPropsChildren {
592
604
  // 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
593
605
  children?:
594
- | Vue.VNode
595
- | ((state: { isActive: boolean; isTransitioning: boolean }) => Vue.VNode)
606
+ | Vue.VNodeChild
607
+ | ((state: {
608
+ isActive: boolean
609
+ isTransitioning: boolean
610
+ }) => Vue.VNodeChild)
596
611
  }
597
612
 
598
613
  type LinkComponentVueProps<TComp> = TComp extends keyof HTMLElementTagNameMap
599
- ? Omit<HTMLAttributes, keyof CreateLinkProps>
600
- : TComp extends Vue.Component
601
- ? Record<string, any>
602
- : Record<string, any>
614
+ ? Omit<LinkHTMLAttributes, keyof CreateLinkProps>
615
+ : Omit<PropsOfComponent<TComp>, keyof CreateLinkProps>
603
616
 
604
617
  export type LinkComponentProps<
605
618
  TComp = 'a',
@@ -620,9 +633,12 @@ export type CreateLinkProps = LinkProps<
620
633
  string
621
634
  >
622
635
 
623
- export type LinkComponent<TComp> = <
636
+ export type LinkComponent<
637
+ in out TComp,
638
+ in out TDefaultFrom extends string = string,
639
+ > = <
624
640
  TRouter extends AnyRouter = RegisteredRouter,
625
- const TFrom extends string = string,
641
+ const TFrom extends string = TDefaultFrom,
626
642
  const TTo extends string | undefined = undefined,
627
643
  const TMaskFrom extends string = TFrom,
628
644
  const TMaskTo extends string = '',
@@ -630,6 +646,26 @@ export type LinkComponent<TComp> = <
630
646
  props: LinkComponentProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
631
647
  ) => Vue.VNode
632
648
 
649
+ export interface LinkComponentRoute<
650
+ in out TDefaultFrom extends string = string,
651
+ > {
652
+ defaultFrom: TDefaultFrom
653
+ <
654
+ TRouter extends AnyRouter = RegisteredRouter,
655
+ const TTo extends string | undefined = undefined,
656
+ const TMaskTo extends string = '',
657
+ >(
658
+ props: LinkComponentProps<
659
+ 'a',
660
+ TRouter,
661
+ this['defaultFrom'],
662
+ TTo,
663
+ this['defaultFrom'],
664
+ TMaskTo
665
+ >,
666
+ ): Vue.VNode
667
+ }
668
+
633
669
  export function createLink<const TComp>(
634
670
  Comp: Constrain<TComp, any, (props: CreateLinkProps) => Vue.VNode>,
635
671
  ): LinkComponent<TComp> {
@@ -637,7 +673,7 @@ export function createLink<const TComp>(
637
673
  name: 'CreatedLink',
638
674
  inheritAttrs: false,
639
675
  setup(_, { attrs, slots }) {
640
- return () => Vue.h(Link, { ...attrs, _asChild: Comp }, slots)
676
+ return () => Vue.h(LinkImpl as any, { ...attrs, _asChild: Comp }, slots)
641
677
  },
642
678
  }) as any
643
679
  }
@@ -676,7 +712,7 @@ const LinkImpl = Vue.defineComponent({
676
712
  const allProps = { ...props, ...attrs }
677
713
  const linkPropsComputed = useLinkProps(
678
714
  allProps as any,
679
- ) as unknown as Vue.ComputedRef<HTMLAttributes>
715
+ ) as unknown as Vue.ComputedRef<LinkHTMLAttributes>
680
716
 
681
717
  return () => {
682
718
  const Component = props._asChild || 'a'
@@ -700,7 +736,7 @@ const LinkImpl = Vue.defineComponent({
700
736
  if (Component === 'svg') {
701
737
  // Create props without class for svg link
702
738
  const svgLinkProps = { ...linkProps }
703
- delete (svgLinkProps as any).class
739
+ delete svgLinkProps.class
704
740
  return Vue.h('svg', {}, [Vue.h('a', svgLinkProps, slotContent)])
705
741
  }
706
742
 
@@ -723,17 +759,9 @@ const LinkImpl = Vue.defineComponent({
723
759
  /**
724
760
  * Link component with proper TypeScript generics support
725
761
  */
726
- export const Link = LinkImpl as unknown as {
727
- <
728
- TRouter extends AnyRouter = RegisteredRouter,
729
- TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
730
- TTo extends string | undefined = '.',
731
- TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
732
- TMaskTo extends string = '.',
733
- >(
734
- props: LinkComponentProps<'a', TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
735
- ): Vue.VNode
736
- }
762
+ export const Link = LinkImpl as unknown as Vue.Component<unknown> &
763
+ Vue.Component<CreateLinkProps> &
764
+ LinkComponent<'a'>
737
765
 
738
766
  function isCtrlEvent(e: MouseEvent) {
739
767
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
package/src/route.ts CHANGED
@@ -4,6 +4,8 @@ import {
4
4
  BaseRouteApi,
5
5
  notFound,
6
6
  } from '@tanstack/router-core'
7
+ import * as Vue from 'vue'
8
+ import { Link } from './link'
7
9
  import { useLoaderData } from './useLoaderData'
8
10
  import { useLoaderDeps } from './useLoaderDeps'
9
11
  import { useParams } from './useParams'
@@ -42,8 +44,8 @@ import type { UseMatchRoute } from './useMatch'
42
44
  import type { UseLoaderDepsRoute } from './useLoaderDeps'
43
45
  import type { UseParamsRoute } from './useParams'
44
46
  import type { UseSearchRoute } from './useSearch'
45
- import type * as Vue from 'vue'
46
47
  import type { UseRouteContextRoute } from './useRouteContext'
48
+ import type { LinkComponentRoute } from './link'
47
49
 
48
50
  // Structural type for Vue SFC components (.vue files)
49
51
  // Uses structural matching to accept Vue components without breaking
@@ -73,6 +75,7 @@ declare module '@tanstack/router-core' {
73
75
  useLoaderDeps: UseLoaderDepsRoute<TId>
74
76
  useLoaderData: UseLoaderDataRoute<TId>
75
77
  useNavigate: () => UseNavigateResult<TFullPath>
78
+ Link: LinkComponentRoute<TFullPath>
76
79
  }
77
80
  }
78
81
 
@@ -140,6 +143,19 @@ export class RouteApi<
140
143
  notFound = (opts?: NotFoundError) => {
141
144
  return notFound({ routeId: this.id as string, ...opts })
142
145
  }
146
+
147
+ Link: LinkComponentRoute<RouteTypesById<TRouter, TId>['fullPath']> = ((
148
+ props,
149
+ ctx?: Vue.SetupContext,
150
+ ) => {
151
+ const router = useRouter()
152
+ const fullPath = router.routesById[this.id as string].fullPath
153
+ return Vue.h(
154
+ Link as any,
155
+ { from: fullPath as never, ...(props as any) },
156
+ ctx?.slots,
157
+ )
158
+ }) as LinkComponentRoute<RouteTypesById<TRouter, TId>['fullPath']>
143
159
  }
144
160
 
145
161
  export class Route<
@@ -277,6 +293,14 @@ export class Route<
277
293
  useNavigate = (): UseNavigateResult<TFullPath> => {
278
294
  return useNavigate({ from: this.fullPath })
279
295
  }
296
+
297
+ Link: LinkComponentRoute<TFullPath> = ((props, ctx?: Vue.SetupContext) => {
298
+ return Vue.h(
299
+ Link as any,
300
+ { from: this.fullPath as never, ...(props as any) },
301
+ ctx?.slots,
302
+ )
303
+ }) as LinkComponentRoute<TFullPath>
280
304
  }
281
305
 
282
306
  export function createRoute<
@@ -515,6 +539,14 @@ export class RootRoute<
515
539
  useNavigate = (): UseNavigateResult<'/'> => {
516
540
  return useNavigate({ from: this.fullPath })
517
541
  }
542
+
543
+ Link: LinkComponentRoute<'/'> = ((props, ctx?: Vue.SetupContext) => {
544
+ return Vue.h(
545
+ Link as any,
546
+ { from: this.fullPath as never, ...(props as any) },
547
+ ctx?.slots,
548
+ )
549
+ }) as LinkComponentRoute<'/'>
518
550
  }
519
551
 
520
552
  export function createRouteMask<
@@ -14,7 +14,7 @@ function prependDoctype(
14
14
  let sentDoctype = false
15
15
 
16
16
  return new NodeReadableStream<Uint8Array>({
17
- async start(controller) {
17
+ start(controller) {
18
18
  const reader = readable.getReader()
19
19
 
20
20
  async function pump(): Promise<void> {