@tanstack/vue-router 1.167.4 → 1.168.0

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 (95) hide show
  1. package/dist/esm/Match.js +55 -61
  2. package/dist/esm/Match.js.map +1 -1
  3. package/dist/esm/Matches.js +8 -15
  4. package/dist/esm/Matches.js.map +1 -1
  5. package/dist/esm/Scripts.js +7 -6
  6. package/dist/esm/Scripts.js.map +1 -1
  7. package/dist/esm/Transitioner.js +18 -24
  8. package/dist/esm/Transitioner.js.map +1 -1
  9. package/dist/esm/headContentUtils.js +13 -15
  10. package/dist/esm/headContentUtils.js.map +1 -1
  11. package/dist/esm/index.dev.js +6 -6
  12. package/dist/esm/index.js +6 -6
  13. package/dist/esm/link.js +242 -178
  14. package/dist/esm/link.js.map +1 -1
  15. package/dist/esm/matchContext.d.ts +8 -14
  16. package/dist/esm/matchContext.js +11 -9
  17. package/dist/esm/matchContext.js.map +1 -1
  18. package/dist/esm/not-found.js +6 -3
  19. package/dist/esm/not-found.js.map +1 -1
  20. package/dist/esm/router.js +2 -1
  21. package/dist/esm/router.js.map +1 -1
  22. package/dist/esm/routerStores.d.ts +13 -0
  23. package/dist/esm/routerStores.js +33 -0
  24. package/dist/esm/routerStores.js.map +1 -0
  25. package/dist/esm/ssr/RouterClient.js +1 -1
  26. package/dist/esm/ssr/RouterClient.js.map +1 -1
  27. package/dist/esm/ssr/renderRouterToStream.js +2 -2
  28. package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
  29. package/dist/esm/ssr/renderRouterToString.js +1 -1
  30. package/dist/esm/ssr/renderRouterToString.js.map +1 -1
  31. package/dist/esm/useCanGoBack.d.ts +1 -1
  32. package/dist/esm/useCanGoBack.js +3 -2
  33. package/dist/esm/useCanGoBack.js.map +1 -1
  34. package/dist/esm/useLocation.js +3 -2
  35. package/dist/esm/useLocation.js.map +1 -1
  36. package/dist/esm/useMatch.js +29 -19
  37. package/dist/esm/useMatch.js.map +1 -1
  38. package/dist/esm/useRouterState.js +4 -4
  39. package/dist/esm/useRouterState.js.map +1 -1
  40. package/dist/source/Match.jsx +121 -159
  41. package/dist/source/Match.jsx.map +1 -1
  42. package/dist/source/Matches.jsx +11 -28
  43. package/dist/source/Matches.jsx.map +1 -1
  44. package/dist/source/Scripts.jsx +32 -35
  45. package/dist/source/Scripts.jsx.map +1 -1
  46. package/dist/source/Transitioner.jsx +19 -21
  47. package/dist/source/Transitioner.jsx.map +1 -1
  48. package/dist/source/headContentUtils.jsx +51 -61
  49. package/dist/source/headContentUtils.jsx.map +1 -1
  50. package/dist/source/link.jsx +298 -249
  51. package/dist/source/link.jsx.map +1 -1
  52. package/dist/source/matchContext.d.ts +8 -14
  53. package/dist/source/matchContext.jsx +17 -23
  54. package/dist/source/matchContext.jsx.map +1 -1
  55. package/dist/source/not-found.jsx +6 -5
  56. package/dist/source/not-found.jsx.map +1 -1
  57. package/dist/source/router.js +2 -1
  58. package/dist/source/router.js.map +1 -1
  59. package/dist/source/routerStores.d.ts +13 -0
  60. package/dist/source/routerStores.js +37 -0
  61. package/dist/source/routerStores.js.map +1 -0
  62. package/dist/source/ssr/RouterClient.jsx +1 -1
  63. package/dist/source/ssr/RouterClient.jsx.map +1 -1
  64. package/dist/source/ssr/renderRouterToStream.jsx +2 -2
  65. package/dist/source/ssr/renderRouterToStream.jsx.map +1 -1
  66. package/dist/source/ssr/renderRouterToString.jsx +1 -1
  67. package/dist/source/ssr/renderRouterToString.jsx.map +1 -1
  68. package/dist/source/useCanGoBack.d.ts +1 -1
  69. package/dist/source/useCanGoBack.js +4 -2
  70. package/dist/source/useCanGoBack.js.map +1 -1
  71. package/dist/source/useLocation.jsx +4 -4
  72. package/dist/source/useLocation.jsx.map +1 -1
  73. package/dist/source/useMatch.jsx +60 -38
  74. package/dist/source/useMatch.jsx.map +1 -1
  75. package/dist/source/useRouterState.jsx +4 -4
  76. package/dist/source/useRouterState.jsx.map +1 -1
  77. package/package.json +2 -2
  78. package/skills/vue-router/SKILL.md +3 -0
  79. package/src/Match.tsx +168 -180
  80. package/src/Matches.tsx +18 -31
  81. package/src/Scripts.tsx +40 -40
  82. package/src/Transitioner.tsx +35 -23
  83. package/src/headContentUtils.tsx +101 -107
  84. package/src/link.tsx +445 -300
  85. package/src/matchContext.tsx +23 -25
  86. package/src/not-found.tsx +9 -5
  87. package/src/router.ts +2 -1
  88. package/src/routerStores.ts +54 -0
  89. package/src/ssr/RouterClient.tsx +1 -1
  90. package/src/ssr/renderRouterToStream.tsx +2 -2
  91. package/src/ssr/renderRouterToString.tsx +1 -1
  92. package/src/useCanGoBack.ts +7 -2
  93. package/src/useLocation.tsx +8 -5
  94. package/src/useMatch.tsx +95 -49
  95. package/src/useRouterState.tsx +6 -4
@@ -5,8 +5,8 @@ import {
5
5
  trimPathRight,
6
6
  } from '@tanstack/router-core'
7
7
  import { isServer } from '@tanstack/router-core/isServer'
8
+ import { batch, useStore } from '@tanstack/vue-store'
8
9
  import { useRouter } from './useRouter'
9
- import { useRouterState } from './useRouterState'
10
10
  import { usePrevious } from './utils'
11
11
 
12
12
  // Track mount state per router to avoid double-loading
@@ -30,17 +30,16 @@ export function useTransitionerSetup() {
30
30
  return
31
31
  }
32
32
 
33
- const isLoading = useRouterState({
34
- select: ({ isLoading }) => isLoading,
35
- })
33
+ const isLoading = useStore(router.stores.isLoading, (value) => value)
36
34
 
37
35
  // Track if we're in a transition - using a ref to track async transitions
38
36
  const isTransitioning = Vue.ref(false)
39
37
 
40
38
  // Track pending state changes
41
- const hasPendingMatches = useRouterState({
42
- select: (s) => s.matches.some((d) => d.status === 'pending'),
43
- })
39
+ const hasPendingMatches = useStore(
40
+ router.stores.hasPendingMatches,
41
+ (value) => value,
42
+ )
44
43
 
45
44
  const previousIsLoading = usePrevious(() => isLoading.value)
46
45
 
@@ -61,7 +60,7 @@ export function useTransitionerSetup() {
61
60
  isTransitioning.value = true
62
61
  // Also update the router state so useMatch knows we're transitioning
63
62
  try {
64
- router.__store.setState((s) => ({ ...s, isTransitioning: true }))
63
+ router.stores.isTransitioning.setState(() => true)
65
64
  } catch {
66
65
  // Ignore errors if component is unmounted
67
66
  }
@@ -72,7 +71,7 @@ export function useTransitionerSetup() {
72
71
  Vue.nextTick(() => {
73
72
  try {
74
73
  isTransitioning.value = false
75
- router.__store.setState((s) => ({ ...s, isTransitioning: false }))
74
+ router.stores.isTransitioning.setState(() => false)
76
75
  } catch {
77
76
  // Ignore errors if component is unmounted
78
77
  }
@@ -141,11 +140,14 @@ export function useTransitionerSetup() {
141
140
  Vue.onMounted(() => {
142
141
  isMounted.value = true
143
142
  if (!isAnyPending.value) {
144
- router.__store.setState((s) =>
145
- s.status === 'pending'
146
- ? { ...s, status: 'idle', resolvedLocation: s.location }
147
- : s,
148
- )
143
+ if (router.stores.status.state === 'pending') {
144
+ batch(() => {
145
+ router.stores.status.setState(() => 'idle')
146
+ router.stores.resolvedLocation.setState(
147
+ () => router.stores.location.state,
148
+ )
149
+ })
150
+ }
149
151
  }
150
152
  })
151
153
 
@@ -185,7 +187,10 @@ export function useTransitionerSetup() {
185
187
  if (previousIsLoading.value.previous && !newValue) {
186
188
  router.emit({
187
189
  type: 'onLoad',
188
- ...getLocationChangeInfo(router.state),
190
+ ...getLocationChangeInfo(
191
+ router.stores.location.state,
192
+ router.stores.resolvedLocation.state,
193
+ ),
189
194
  })
190
195
  }
191
196
  } catch {
@@ -201,7 +206,10 @@ export function useTransitionerSetup() {
201
206
  if (previousIsPagePending.value.previous && !newValue) {
202
207
  router.emit({
203
208
  type: 'onBeforeRouteMount',
204
- ...getLocationChangeInfo(router.state),
209
+ ...getLocationChangeInfo(
210
+ router.stores.location.state,
211
+ router.stores.resolvedLocation.state,
212
+ ),
205
213
  })
206
214
  }
207
215
  } catch {
@@ -212,17 +220,21 @@ export function useTransitionerSetup() {
212
220
  Vue.watch(isAnyPending, (newValue) => {
213
221
  if (!isMounted.value) return
214
222
  try {
215
- if (!newValue && router.__store.state.status === 'pending') {
216
- router.__store.setState((s) => ({
217
- ...s,
218
- status: 'idle',
219
- resolvedLocation: s.location,
220
- }))
223
+ if (!newValue && router.stores.status.state === 'pending') {
224
+ batch(() => {
225
+ router.stores.status.setState(() => 'idle')
226
+ router.stores.resolvedLocation.setState(
227
+ () => router.stores.location.state,
228
+ )
229
+ })
221
230
  }
222
231
 
223
232
  // The router was pending and now it's not
224
233
  if (previousIsAnyPending.value.previous && !newValue) {
225
- const changeInfo = getLocationChangeInfo(router.state)
234
+ const changeInfo = getLocationChangeInfo(
235
+ router.stores.location.state,
236
+ router.stores.resolvedLocation.state,
237
+ )
226
238
  router.emit({
227
239
  type: 'onResolved',
228
240
  ...changeInfo,
@@ -1,68 +1,67 @@
1
1
  import * as Vue from 'vue'
2
-
3
2
  import { escapeHtml } from '@tanstack/router-core'
3
+ import { useStore } from '@tanstack/vue-store'
4
4
  import { useRouter } from './useRouter'
5
- import { useRouterState } from './useRouterState'
6
5
  import type { RouterManagedTag } from '@tanstack/router-core'
7
6
 
8
7
  export const useTags = () => {
9
8
  const router = useRouter()
9
+ const matches = useStore(
10
+ router.stores.activeMatchesSnapshot,
11
+ (value) => value,
12
+ )
10
13
 
11
- const routeMeta = useRouterState({
12
- select: (state) => {
13
- return state.matches.map((match) => match.meta!).filter(Boolean)
14
- },
15
- })
16
-
17
- const meta: Vue.Ref<Array<RouterManagedTag>> = Vue.computed(() => {
14
+ const meta = Vue.computed<Array<RouterManagedTag>>(() => {
18
15
  const resultMeta: Array<RouterManagedTag> = []
19
16
  const metaByAttribute: Record<string, true> = {}
20
17
  let title: RouterManagedTag | undefined
21
- ;[...routeMeta.value].reverse().forEach((metas) => {
22
- ;[...metas].reverse().forEach((m) => {
23
- if (!m) return
24
-
25
- if (m.title) {
26
- if (!title) {
27
- title = {
28
- tag: 'title',
29
- children: m.title,
18
+ ;[...matches.value.map((match) => match.meta!).filter(Boolean)]
19
+ .reverse()
20
+ .forEach((metas) => {
21
+ ;[...metas].reverse().forEach((m) => {
22
+ if (!m) return
23
+
24
+ if (m.title) {
25
+ if (!title) {
26
+ title = {
27
+ tag: 'title',
28
+ children: m.title,
29
+ }
30
30
  }
31
- }
32
- } else if ('script:ld+json' in m) {
33
- // Handle JSON-LD structured data
34
- // Content is HTML-escaped to prevent XSS when injected via innerHTML
35
- try {
36
- const json = JSON.stringify(m['script:ld+json'])
31
+ } else if ('script:ld+json' in m) {
32
+ // Handle JSON-LD structured data
33
+ // Content is HTML-escaped to prevent XSS when injected via innerHTML
34
+ try {
35
+ const json = JSON.stringify(m['script:ld+json'])
36
+ resultMeta.push({
37
+ tag: 'script',
38
+ attrs: {
39
+ type: 'application/ld+json',
40
+ },
41
+ children: escapeHtml(json),
42
+ })
43
+ } catch {
44
+ // Skip invalid JSON-LD objects
45
+ }
46
+ } else {
47
+ const attribute = m.name ?? m.property
48
+ if (attribute) {
49
+ if (metaByAttribute[attribute]) {
50
+ return
51
+ } else {
52
+ metaByAttribute[attribute] = true
53
+ }
54
+ }
55
+
37
56
  resultMeta.push({
38
- tag: 'script',
57
+ tag: 'meta',
39
58
  attrs: {
40
- type: 'application/ld+json',
59
+ ...m,
41
60
  },
42
- children: escapeHtml(json),
43
61
  })
44
- } catch {
45
- // Skip invalid JSON-LD objects
46
- }
47
- } else {
48
- const attribute = m.name ?? m.property
49
- if (attribute) {
50
- if (metaByAttribute[attribute]) {
51
- return
52
- } else {
53
- metaByAttribute[attribute] = true
54
- }
55
62
  }
56
-
57
- resultMeta.push({
58
- tag: 'meta',
59
- attrs: {
60
- ...m,
61
- },
62
- })
63
- }
63
+ })
64
64
  })
65
- })
66
65
 
67
66
  if (title) {
68
67
  resultMeta.push(title)
@@ -73,9 +72,9 @@ export const useTags = () => {
73
72
  return resultMeta
74
73
  })
75
74
 
76
- const links = useRouterState({
77
- select: (state) =>
78
- state.matches
75
+ const links = Vue.computed<Array<RouterManagedTag>>(
76
+ () =>
77
+ matches.value
79
78
  .map((match) => match.links!)
80
79
  .filter(Boolean)
81
80
  .flat(1)
@@ -85,67 +84,62 @@ export const useTags = () => {
85
84
  ...link,
86
85
  },
87
86
  })) as Array<RouterManagedTag>,
88
- })
89
-
90
- const preloadMeta = useRouterState({
91
- select: (state) => {
92
- const preloadMeta: Array<RouterManagedTag> = []
93
-
94
- state.matches
95
- .map((match) => router.looseRoutesById[match.routeId]!)
96
- .forEach((route) =>
97
- router.ssr?.manifest?.routes[route.id]?.preloads
98
- ?.filter(Boolean)
99
- .forEach((preload) => {
100
- preloadMeta.push({
101
- tag: 'link',
102
- attrs: {
103
- rel: 'modulepreload',
104
- href: preload,
105
- },
106
- })
107
- }),
108
- )
109
-
110
- return preloadMeta
111
- },
112
- })
87
+ )
88
+
89
+ const preloadMeta = Vue.computed<Array<RouterManagedTag>>(() => {
90
+ const preloadMeta: Array<RouterManagedTag> = []
91
+
92
+ matches.value
93
+ .map((match) => router.looseRoutesById[match.routeId]!)
94
+ .forEach((route) =>
95
+ router.ssr?.manifest?.routes[route.id]?.preloads
96
+ ?.filter(Boolean)
97
+ .forEach((preload) => {
98
+ preloadMeta.push({
99
+ tag: 'link',
100
+ attrs: {
101
+ rel: 'modulepreload',
102
+ href: preload,
103
+ },
104
+ })
105
+ }),
106
+ )
113
107
 
114
- const headScripts = useRouterState({
115
- select: (state) =>
116
- (
117
- state.matches
118
- .map((match) => match.headScripts!)
119
- .flat(1)
120
- .filter(Boolean) as Array<RouterManagedTag>
121
- ).map(({ children, ...script }) => ({
122
- tag: 'script',
123
- attrs: {
124
- ...script,
125
- },
126
- children,
127
- })),
108
+ return preloadMeta
128
109
  })
129
110
 
130
- const manifestAssets = useRouterState({
131
- select: (state) => {
132
- const manifest = router.ssr?.manifest
133
-
134
- const assets = state.matches
135
- .map((match) => manifest?.routes[match.routeId]?.assets ?? [])
136
- .filter(Boolean)
111
+ const headScripts = Vue.computed<Array<RouterManagedTag>>(() =>
112
+ (
113
+ matches.value
114
+ .map((match) => match.headScripts!)
137
115
  .flat(1)
138
- .filter((asset) => asset.tag === 'link')
139
- .map(
140
- (asset) =>
141
- ({
142
- tag: 'link',
143
- attrs: { ...asset.attrs },
144
- }) satisfies RouterManagedTag,
145
- )
146
-
147
- return assets
148
- },
116
+ .filter(Boolean) as Array<RouterManagedTag>
117
+ ).map(({ children, ...script }) => ({
118
+ tag: 'script',
119
+ attrs: {
120
+ ...script,
121
+ },
122
+ children,
123
+ })),
124
+ )
125
+
126
+ const manifestAssets = Vue.computed<Array<RouterManagedTag>>(() => {
127
+ const manifest = router.ssr?.manifest
128
+
129
+ const assets = matches.value
130
+ .map((match) => manifest?.routes[match.routeId]?.assets ?? [])
131
+ .filter(Boolean)
132
+ .flat(1)
133
+ .filter((asset) => asset.tag === 'link')
134
+ .map(
135
+ (asset) =>
136
+ ({
137
+ tag: 'link',
138
+ attrs: { ...asset.attrs },
139
+ }) satisfies RouterManagedTag,
140
+ )
141
+
142
+ return assets
149
143
  })
150
144
 
151
145
  return () =>