@tanstack/vue-router 1.167.5 → 1.168.1
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/dist/esm/Match.js +55 -61
- package/dist/esm/Match.js.map +1 -1
- package/dist/esm/Matches.js +8 -15
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/Scripts.js +7 -6
- package/dist/esm/Scripts.js.map +1 -1
- package/dist/esm/Transitioner.js +18 -24
- package/dist/esm/Transitioner.js.map +1 -1
- package/dist/esm/headContentUtils.js +13 -15
- package/dist/esm/headContentUtils.js.map +1 -1
- package/dist/esm/index.dev.js +6 -6
- package/dist/esm/index.js +6 -6
- package/dist/esm/link.js +242 -178
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/matchContext.d.ts +8 -14
- package/dist/esm/matchContext.js +11 -9
- package/dist/esm/matchContext.js.map +1 -1
- package/dist/esm/not-found.js +6 -3
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/router.js +2 -1
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/routerStores.d.ts +13 -0
- package/dist/esm/routerStores.js +33 -0
- package/dist/esm/routerStores.js.map +1 -0
- package/dist/esm/ssr/RouterClient.js +1 -1
- package/dist/esm/ssr/RouterClient.js.map +1 -1
- package/dist/esm/ssr/renderRouterToStream.js +2 -2
- package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
- package/dist/esm/ssr/renderRouterToString.js +1 -1
- package/dist/esm/ssr/renderRouterToString.js.map +1 -1
- package/dist/esm/useCanGoBack.d.ts +1 -1
- package/dist/esm/useCanGoBack.js +3 -2
- package/dist/esm/useCanGoBack.js.map +1 -1
- package/dist/esm/useLocation.js +3 -2
- package/dist/esm/useLocation.js.map +1 -1
- package/dist/esm/useMatch.js +29 -19
- package/dist/esm/useMatch.js.map +1 -1
- package/dist/esm/useRouterState.js +4 -4
- package/dist/esm/useRouterState.js.map +1 -1
- package/dist/source/Match.jsx +121 -159
- package/dist/source/Match.jsx.map +1 -1
- package/dist/source/Matches.jsx +11 -28
- package/dist/source/Matches.jsx.map +1 -1
- package/dist/source/Scripts.jsx +32 -35
- package/dist/source/Scripts.jsx.map +1 -1
- package/dist/source/Transitioner.jsx +19 -21
- package/dist/source/Transitioner.jsx.map +1 -1
- package/dist/source/headContentUtils.jsx +51 -61
- package/dist/source/headContentUtils.jsx.map +1 -1
- package/dist/source/link.jsx +298 -249
- package/dist/source/link.jsx.map +1 -1
- package/dist/source/matchContext.d.ts +8 -14
- package/dist/source/matchContext.jsx +17 -23
- package/dist/source/matchContext.jsx.map +1 -1
- package/dist/source/not-found.jsx +6 -5
- package/dist/source/not-found.jsx.map +1 -1
- package/dist/source/router.js +2 -1
- package/dist/source/router.js.map +1 -1
- package/dist/source/routerStores.d.ts +13 -0
- package/dist/source/routerStores.js +37 -0
- package/dist/source/routerStores.js.map +1 -0
- package/dist/source/ssr/RouterClient.jsx +1 -1
- package/dist/source/ssr/RouterClient.jsx.map +1 -1
- package/dist/source/ssr/renderRouterToStream.jsx +2 -2
- package/dist/source/ssr/renderRouterToStream.jsx.map +1 -1
- package/dist/source/ssr/renderRouterToString.jsx +1 -1
- package/dist/source/ssr/renderRouterToString.jsx.map +1 -1
- package/dist/source/useCanGoBack.d.ts +1 -1
- package/dist/source/useCanGoBack.js +4 -2
- package/dist/source/useCanGoBack.js.map +1 -1
- package/dist/source/useLocation.jsx +4 -4
- package/dist/source/useLocation.jsx.map +1 -1
- package/dist/source/useMatch.jsx +60 -38
- package/dist/source/useMatch.jsx.map +1 -1
- package/dist/source/useRouterState.jsx +4 -4
- package/dist/source/useRouterState.jsx.map +1 -1
- package/package.json +4 -4
- package/skills/vue-router/SKILL.md +3 -0
- package/src/Match.tsx +168 -180
- package/src/Matches.tsx +18 -31
- package/src/Scripts.tsx +40 -40
- package/src/Transitioner.tsx +35 -23
- package/src/headContentUtils.tsx +101 -107
- package/src/link.tsx +445 -300
- package/src/matchContext.tsx +23 -25
- package/src/not-found.tsx +9 -5
- package/src/router.ts +2 -1
- package/src/routerStores.ts +54 -0
- package/src/ssr/RouterClient.tsx +1 -1
- package/src/ssr/renderRouterToStream.tsx +2 -2
- package/src/ssr/renderRouterToString.tsx +1 -1
- package/src/useCanGoBack.ts +7 -2
- package/src/useLocation.tsx +8 -5
- package/src/useMatch.tsx +95 -49
- package/src/useRouterState.tsx +6 -4
package/src/Match.tsx
CHANGED
|
@@ -9,12 +9,16 @@ import {
|
|
|
9
9
|
rootRouteId,
|
|
10
10
|
} from '@tanstack/router-core'
|
|
11
11
|
import { isServer } from '@tanstack/router-core/isServer'
|
|
12
|
+
import { useStore } from '@tanstack/vue-store'
|
|
12
13
|
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
|
|
13
14
|
import { ClientOnly } from './ClientOnly'
|
|
14
|
-
import { useRouterState } from './useRouterState'
|
|
15
15
|
import { useRouter } from './useRouter'
|
|
16
16
|
import { CatchNotFound } from './not-found'
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
matchContext,
|
|
19
|
+
pendingMatchContext,
|
|
20
|
+
routeIdContext,
|
|
21
|
+
} from './matchContext'
|
|
18
22
|
import { renderRouteNotFound } from './renderRouteNotFound'
|
|
19
23
|
import { ScrollRestoration } from './scroll-restoration'
|
|
20
24
|
import type { VNode } from 'vue'
|
|
@@ -31,55 +35,52 @@ export const Match = Vue.defineComponent({
|
|
|
31
35
|
setup(props) {
|
|
32
36
|
const router = useRouter()
|
|
33
37
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
select: (s) => {
|
|
41
|
-
// First try to find match by props.matchId
|
|
42
|
-
let match = s.matches.find((d) => d.id === props.matchId)
|
|
43
|
-
let matchIndex = match
|
|
44
|
-
? s.matches.findIndex((d) => d.id === props.matchId)
|
|
45
|
-
: -1
|
|
46
|
-
|
|
47
|
-
// If match found, update lastKnownRouteId
|
|
48
|
-
if (match) {
|
|
49
|
-
lastKnownRouteId = match.routeId as string
|
|
50
|
-
} else if (lastKnownRouteId) {
|
|
51
|
-
// Match not found - props.matchId might be stale during a same-route transition
|
|
52
|
-
// Try to find the NEW match by routeId
|
|
53
|
-
match = s.matches.find((d) => d.routeId === lastKnownRouteId)
|
|
54
|
-
matchIndex = match
|
|
55
|
-
? s.matches.findIndex((d) => d.routeId === lastKnownRouteId)
|
|
56
|
-
: -1
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!match) {
|
|
60
|
-
return null
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const routeId = match.routeId as string
|
|
64
|
-
const parentRouteId =
|
|
65
|
-
matchIndex > 0 ? (s.matches[matchIndex - 1]?.routeId as string) : null
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
matchId: match.id, // Return the actual matchId (may differ from props.matchId)
|
|
69
|
-
routeId,
|
|
70
|
-
parentRouteId,
|
|
71
|
-
loadedAt: s.loadedAt,
|
|
72
|
-
ssr: match.ssr,
|
|
73
|
-
_displayPending: match._displayPending,
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
})
|
|
38
|
+
// Derive routeId from initial props.matchId — stable for this component's
|
|
39
|
+
// lifetime. The routeId never changes for a given route position in the
|
|
40
|
+
// tree, even when matchId changes (loaderDepsHash, etc).
|
|
41
|
+
const routeId = router.stores.activeMatchStoresById.get(
|
|
42
|
+
props.matchId,
|
|
43
|
+
)?.routeId
|
|
77
44
|
|
|
78
45
|
invariant(
|
|
79
|
-
|
|
46
|
+
routeId,
|
|
80
47
|
`Could not find routeId for matchId "${props.matchId}". Please file an issue!`,
|
|
81
48
|
)
|
|
82
49
|
|
|
50
|
+
// Static route-tree check: is this route a direct child of the root?
|
|
51
|
+
// parentRoute is set at build time, so no reactive tracking needed.
|
|
52
|
+
const isChildOfRoot =
|
|
53
|
+
(router.routesById[routeId] as AnyRoute)?.parentRoute?.id === rootRouteId
|
|
54
|
+
|
|
55
|
+
// Single stable store subscription — getMatchStoreByRouteId returns a
|
|
56
|
+
// cached computed store that resolves routeId → current match state
|
|
57
|
+
// through the signal graph. No bridge needed.
|
|
58
|
+
const activeMatch = useStore(
|
|
59
|
+
router.stores.getMatchStoreByRouteId(routeId),
|
|
60
|
+
(value) => value,
|
|
61
|
+
)
|
|
62
|
+
const isPendingMatchRef = useStore(
|
|
63
|
+
router.stores.pendingRouteIds,
|
|
64
|
+
(pendingRouteIds) => Boolean(pendingRouteIds[routeId]),
|
|
65
|
+
{ equal: Object.is },
|
|
66
|
+
)
|
|
67
|
+
const loadedAt = useStore(router.stores.loadedAt, (value) => value)
|
|
68
|
+
|
|
69
|
+
const matchData = Vue.computed(() => {
|
|
70
|
+
const match = activeMatch.value
|
|
71
|
+
if (!match) {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
matchId: match.id,
|
|
77
|
+
routeId,
|
|
78
|
+
loadedAt: loadedAt.value,
|
|
79
|
+
ssr: match.ssr,
|
|
80
|
+
_displayPending: match._displayPending,
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
83
84
|
const route = Vue.computed(() =>
|
|
84
85
|
matchData.value ? router.routesById[matchData.value.routeId] : null,
|
|
85
86
|
)
|
|
@@ -123,27 +124,20 @@ export const Match = Vue.defineComponent({
|
|
|
123
124
|
: null,
|
|
124
125
|
)
|
|
125
126
|
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
127
|
+
// Provide routeId context (stable string) for children.
|
|
128
|
+
// MatchInner, Outlet, and useMatch all consume this.
|
|
129
|
+
Vue.provide(routeIdContext, routeId)
|
|
129
130
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
Vue.
|
|
133
|
-
|
|
134
|
-
([propsMatchId, dataMatchId]) => {
|
|
135
|
-
// Prefer the matchId from matchData (which handles fallback)
|
|
136
|
-
// Fall back to props.matchId if matchData is null
|
|
137
|
-
matchIdRef.value = dataMatchId ?? propsMatchId
|
|
138
|
-
},
|
|
139
|
-
{ immediate: true },
|
|
131
|
+
// Provide reactive nearest-match context for hooks that slice the active
|
|
132
|
+
// matches array relative to the current match.
|
|
133
|
+
const matchIdRef = Vue.computed(
|
|
134
|
+
() => activeMatch.value?.id ?? props.matchId,
|
|
140
135
|
)
|
|
141
|
-
|
|
142
|
-
// Provide the matchId to child components
|
|
143
136
|
Vue.provide(matchContext, matchIdRef)
|
|
144
137
|
|
|
138
|
+
Vue.provide(pendingMatchContext, isPendingMatchRef)
|
|
139
|
+
|
|
145
140
|
return (): VNode => {
|
|
146
|
-
// Use the actual matchId from matchData, not props (which may be stale)
|
|
147
141
|
const actualMatchId = matchData.value?.matchId ?? props.matchId
|
|
148
142
|
|
|
149
143
|
const resolvedNoSsr =
|
|
@@ -203,8 +197,7 @@ export const Match = Vue.defineComponent({
|
|
|
203
197
|
// Add scroll restoration if needed
|
|
204
198
|
const withScrollRestoration: Array<VNode> = [
|
|
205
199
|
content,
|
|
206
|
-
|
|
207
|
-
router.options.scrollRestoration
|
|
200
|
+
isChildOfRoot && router.options.scrollRestoration
|
|
208
201
|
? Vue.h(Vue.Fragment, null, [
|
|
209
202
|
Vue.h(OnRendered),
|
|
210
203
|
Vue.h(ScrollRestoration),
|
|
@@ -236,7 +229,7 @@ export const Match = Vue.defineComponent({
|
|
|
236
229
|
// On Rendered can't happen above the root layout because it actually
|
|
237
230
|
// renders a dummy dom element to track the rendered state of the app.
|
|
238
231
|
// We render a script tag with a key that changes based on the current
|
|
239
|
-
// location state.
|
|
232
|
+
// location state.__TSR_key. Also, because it's below the root layout, it
|
|
240
233
|
// allows us to fire onRendered events even after a hydration mismatch
|
|
241
234
|
// error that occurred above the root layout (like bad head/link tags,
|
|
242
235
|
// which is common).
|
|
@@ -245,20 +238,32 @@ const OnRendered = Vue.defineComponent({
|
|
|
245
238
|
setup() {
|
|
246
239
|
const router = useRouter()
|
|
247
240
|
|
|
248
|
-
const location =
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
})
|
|
241
|
+
const location = useStore(
|
|
242
|
+
router.stores.resolvedLocation,
|
|
243
|
+
(resolvedLocation) => resolvedLocation?.state.__TSR_key,
|
|
244
|
+
)
|
|
253
245
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
246
|
+
let prevHref: string | undefined
|
|
247
|
+
|
|
248
|
+
Vue.watch(
|
|
249
|
+
location,
|
|
250
|
+
() => {
|
|
251
|
+
if (location.value) {
|
|
252
|
+
const currentHref = router.latestLocation.href
|
|
253
|
+
if (prevHref === undefined || prevHref !== currentHref) {
|
|
254
|
+
router.emit({
|
|
255
|
+
type: 'onRendered',
|
|
256
|
+
...getLocationChangeInfo(
|
|
257
|
+
router.stores.location.state,
|
|
258
|
+
router.stores.resolvedLocation.state,
|
|
259
|
+
),
|
|
260
|
+
})
|
|
261
|
+
prevHref = currentHref
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{ immediate: true },
|
|
266
|
+
)
|
|
262
267
|
|
|
263
268
|
return () => null
|
|
264
269
|
},
|
|
@@ -275,68 +280,52 @@ export const MatchInner = Vue.defineComponent({
|
|
|
275
280
|
setup(props) {
|
|
276
281
|
const router = useRouter()
|
|
277
282
|
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
283
|
+
// Use routeId from context (provided by parent Match) — stable string.
|
|
284
|
+
const routeId = Vue.inject(routeIdContext)!
|
|
285
|
+
const activeMatch = useStore(
|
|
286
|
+
router.stores.getMatchStoreByRouteId(routeId),
|
|
287
|
+
(value) => value,
|
|
288
|
+
)
|
|
281
289
|
|
|
282
290
|
// Combined selector for match state AND remount key
|
|
283
291
|
// This ensures both are computed in the same selector call with consistent data
|
|
284
|
-
const combinedState =
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (match) {
|
|
291
|
-
lastKnownRouteId = match.routeId as string
|
|
292
|
-
} else if (lastKnownRouteId) {
|
|
293
|
-
// Match not found - props.matchId might be stale during a same-route transition
|
|
294
|
-
// (matchId changed due to loaderDepsHash but props haven't updated yet)
|
|
295
|
-
// Try to find the NEW match by routeId and use that instead
|
|
296
|
-
const sameRouteMatch = s.matches.find(
|
|
297
|
-
(d) => d.routeId === lastKnownRouteId,
|
|
298
|
-
)
|
|
299
|
-
if (sameRouteMatch) {
|
|
300
|
-
match = sameRouteMatch
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (!match) {
|
|
305
|
-
// Route no longer exists - truly navigating away
|
|
306
|
-
return null
|
|
307
|
-
}
|
|
292
|
+
const combinedState = Vue.computed(() => {
|
|
293
|
+
const match = activeMatch.value
|
|
294
|
+
if (!match) {
|
|
295
|
+
// Route no longer exists - truly navigating away
|
|
296
|
+
return null
|
|
297
|
+
}
|
|
308
298
|
|
|
309
|
-
|
|
299
|
+
const matchRouteId = match.routeId as string
|
|
310
300
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
301
|
+
// Compute remount key
|
|
302
|
+
const remountFn =
|
|
303
|
+
(router.routesById[matchRouteId] as AnyRoute).options.remountDeps ??
|
|
304
|
+
router.options.defaultRemountDeps
|
|
315
305
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
306
|
+
let remountKey: string | undefined
|
|
307
|
+
if (remountFn) {
|
|
308
|
+
const remountDeps = remountFn({
|
|
309
|
+
routeId: matchRouteId,
|
|
310
|
+
loaderDeps: match.loaderDeps,
|
|
311
|
+
params: match._strictParams,
|
|
312
|
+
search: match._strictSearch,
|
|
313
|
+
})
|
|
314
|
+
remountKey = remountDeps ? JSON.stringify(remountDeps) : undefined
|
|
315
|
+
}
|
|
326
316
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
},
|
|
317
|
+
return {
|
|
318
|
+
routeId: matchRouteId,
|
|
319
|
+
match: {
|
|
320
|
+
id: match.id,
|
|
321
|
+
status: match.status,
|
|
322
|
+
error: match.error,
|
|
323
|
+
ssr: match.ssr,
|
|
324
|
+
_forcePending: match._forcePending,
|
|
325
|
+
_displayPending: match._displayPending,
|
|
326
|
+
},
|
|
327
|
+
remountKey,
|
|
328
|
+
}
|
|
340
329
|
})
|
|
341
330
|
|
|
342
331
|
const route = Vue.computed(() => {
|
|
@@ -460,49 +449,56 @@ export const Outlet = Vue.defineComponent({
|
|
|
460
449
|
name: 'Outlet',
|
|
461
450
|
setup() {
|
|
462
451
|
const router = useRouter()
|
|
463
|
-
const
|
|
464
|
-
const safeMatchId = Vue.computed(() => matchId?.value || '')
|
|
452
|
+
const parentRouteId = Vue.inject(routeIdContext)
|
|
465
453
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
})
|
|
454
|
+
if (!parentRouteId) {
|
|
455
|
+
return (): VNode | null => null
|
|
456
|
+
}
|
|
470
457
|
|
|
471
|
-
|
|
458
|
+
// Parent state via stable routeId store — single subscription
|
|
459
|
+
const parentMatch = useStore(
|
|
460
|
+
router.stores.getMatchStoreByRouteId(parentRouteId),
|
|
461
|
+
(v) => v,
|
|
462
|
+
)
|
|
472
463
|
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
464
|
+
const route = Vue.computed(() =>
|
|
465
|
+
parentMatch.value
|
|
466
|
+
? router.routesById[parentMatch.value.routeId as string]!
|
|
467
|
+
: undefined,
|
|
468
|
+
)
|
|
477
469
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
return false
|
|
482
|
-
}
|
|
470
|
+
const parentGlobalNotFound = Vue.computed(
|
|
471
|
+
() => parentMatch.value?.globalNotFound ?? false,
|
|
472
|
+
)
|
|
483
473
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
474
|
+
// Child match lookup: read the child matchId from the shared derived
|
|
475
|
+
// map (one reactive node for the whole tree), then grab match state
|
|
476
|
+
// directly from the pool.
|
|
477
|
+
const childMatchIdMap = useStore(
|
|
478
|
+
router.stores.childMatchIdByRouteId,
|
|
479
|
+
(v) => v,
|
|
480
|
+
)
|
|
487
481
|
|
|
488
|
-
const childMatchData =
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
},
|
|
482
|
+
const childMatchData = Vue.computed(() => {
|
|
483
|
+
const childId = childMatchIdMap.value[parentRouteId]
|
|
484
|
+
if (!childId) return null
|
|
485
|
+
const child = router.stores.activeMatchStoresById.get(childId)?.state
|
|
486
|
+
if (!child) return null
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
id: child.id,
|
|
490
|
+
// Key based on routeId + params only (not loaderDeps)
|
|
491
|
+
// This ensures component recreates when params change,
|
|
492
|
+
// but NOT when only loaderDeps change
|
|
493
|
+
paramsKey: child.routeId + JSON.stringify(child._strictParams),
|
|
494
|
+
}
|
|
502
495
|
})
|
|
503
496
|
|
|
504
497
|
return (): VNode | null => {
|
|
505
498
|
if (parentGlobalNotFound.value) {
|
|
499
|
+
if (!route.value) {
|
|
500
|
+
return null
|
|
501
|
+
}
|
|
506
502
|
return renderRouteNotFound(router, route.value, undefined)
|
|
507
503
|
}
|
|
508
504
|
|
|
@@ -515,20 +511,12 @@ export const Outlet = Vue.defineComponent({
|
|
|
515
511
|
key: childMatchData.value.paramsKey,
|
|
516
512
|
})
|
|
517
513
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
: null,
|
|
525
|
-
},
|
|
526
|
-
{
|
|
527
|
-
default: () => nextMatch,
|
|
528
|
-
},
|
|
529
|
-
)
|
|
530
|
-
}
|
|
531
|
-
|
|
514
|
+
// Note: We intentionally do NOT wrap in Suspense here.
|
|
515
|
+
// The top-level Suspense in Matches already covers the root.
|
|
516
|
+
// The old code compared matchId (e.g. "__root__/") with rootRouteId ("__root__")
|
|
517
|
+
// which never matched, so this Suspense was effectively dead code.
|
|
518
|
+
// With routeId-based lookup, parentRouteId === rootRouteId would match,
|
|
519
|
+
// causing a double-Suspense that corrupts Vue's DOM during updates.
|
|
532
520
|
return nextMatch
|
|
533
521
|
}
|
|
534
522
|
},
|
package/src/Matches.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as Vue from 'vue'
|
|
2
2
|
import warning from 'tiny-warning'
|
|
3
3
|
import { isServer } from '@tanstack/router-core/isServer'
|
|
4
|
+
import { useStore } from '@tanstack/vue-store'
|
|
4
5
|
import { CatchBoundary } from './CatchBoundary'
|
|
5
|
-
import { useRouterState } from './useRouterState'
|
|
6
6
|
import { useRouter } from './useRouter'
|
|
7
7
|
import { useTransitionerSetup } from './Transitioner'
|
|
8
8
|
import { matchContext } from './matchContext'
|
|
@@ -21,7 +21,6 @@ import type {
|
|
|
21
21
|
ResolveRelativePath,
|
|
22
22
|
ResolveRoute,
|
|
23
23
|
RouteByPath,
|
|
24
|
-
RouterState,
|
|
25
24
|
ToSubOptionsProps,
|
|
26
25
|
} from '@tanstack/router-core'
|
|
27
26
|
|
|
@@ -104,15 +103,8 @@ const MatchesInner = Vue.defineComponent({
|
|
|
104
103
|
setup() {
|
|
105
104
|
const router = useRouter()
|
|
106
105
|
|
|
107
|
-
const matchId =
|
|
108
|
-
|
|
109
|
-
return s.matches[0]?.id
|
|
110
|
-
},
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
const resetKey = useRouterState({
|
|
114
|
-
select: (s) => s.loadedAt,
|
|
115
|
-
})
|
|
106
|
+
const matchId = useStore(router.stores.firstMatchId, (id) => id)
|
|
107
|
+
const resetKey = useStore(router.stores.loadedAt, (loadedAt) => loadedAt)
|
|
116
108
|
|
|
117
109
|
// Create a ref for the match id to provide
|
|
118
110
|
const matchIdRef = Vue.computed(() => matchId.value)
|
|
@@ -165,15 +157,10 @@ export type UseMatchRouteOptions<
|
|
|
165
157
|
export function useMatchRoute<TRouter extends AnyRouter = RegisteredRouter>() {
|
|
166
158
|
const router = useRouter()
|
|
167
159
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
locationHref: s.location.href,
|
|
173
|
-
resolvedLocationHref: s.resolvedLocation?.href,
|
|
174
|
-
status: s.status,
|
|
175
|
-
}),
|
|
176
|
-
})
|
|
160
|
+
const routerState = useStore(
|
|
161
|
+
router.stores.matchRouteReactivity,
|
|
162
|
+
(value) => value,
|
|
163
|
+
)
|
|
177
164
|
|
|
178
165
|
return <
|
|
179
166
|
const TFrom extends string = string,
|
|
@@ -272,9 +259,11 @@ export const MatchRoute = Vue.defineComponent({
|
|
|
272
259
|
},
|
|
273
260
|
},
|
|
274
261
|
setup(props, { slots }) {
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
262
|
+
const router = useRouter()
|
|
263
|
+
const status = useStore(
|
|
264
|
+
router.stores.matchRouteReactivity,
|
|
265
|
+
(value) => value.status,
|
|
266
|
+
)
|
|
278
267
|
|
|
279
268
|
return () => {
|
|
280
269
|
if (!status.value) return null
|
|
@@ -314,14 +303,12 @@ export function useMatches<
|
|
|
314
303
|
>(
|
|
315
304
|
opts?: UseMatchesBaseOptions<TRouter, TSelected>,
|
|
316
305
|
): Vue.Ref<UseMatchesResult<TRouter, TSelected>> {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
},
|
|
324
|
-
} as any) as Vue.Ref<UseMatchesResult<TRouter, TSelected>>
|
|
306
|
+
const router = useRouter<TRouter>()
|
|
307
|
+
return useStore(router.stores.activeMatchesSnapshot, (matches) => {
|
|
308
|
+
return opts?.select
|
|
309
|
+
? opts.select(matches as Array<MakeRouteMatchUnion<TRouter>>)
|
|
310
|
+
: (matches as any)
|
|
311
|
+
})
|
|
325
312
|
}
|
|
326
313
|
|
|
327
314
|
export function useParentMatches<
|
package/src/Scripts.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Vue from 'vue'
|
|
2
|
+
import { useStore } from '@tanstack/vue-store'
|
|
2
3
|
import { Asset } from './Asset'
|
|
3
|
-
import { useRouterState } from './useRouterState'
|
|
4
4
|
import { useRouter } from './useRouter'
|
|
5
5
|
import type { RouterManagedTag } from '@tanstack/router-core'
|
|
6
6
|
|
|
@@ -9,51 +9,51 @@ export const Scripts = Vue.defineComponent({
|
|
|
9
9
|
setup() {
|
|
10
10
|
const router = useRouter()
|
|
11
11
|
const nonce = router.options.ssr?.nonce
|
|
12
|
+
const matches = useStore(
|
|
13
|
+
router.stores.activeMatchesSnapshot,
|
|
14
|
+
(value) => value,
|
|
15
|
+
)
|
|
12
16
|
|
|
13
|
-
const assetScripts =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const manifest = router.ssr?.manifest
|
|
17
|
+
const assetScripts = Vue.computed<Array<RouterManagedTag>>(() => {
|
|
18
|
+
const assetScripts: Array<RouterManagedTag> = []
|
|
19
|
+
const manifest = router.ssr?.manifest
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
if (!manifest) {
|
|
22
|
+
return []
|
|
23
|
+
}
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
matches.value
|
|
26
|
+
.map((match) => router.looseRoutesById[match.routeId]!)
|
|
27
|
+
.forEach((route) =>
|
|
28
|
+
manifest.routes[route.id]?.assets
|
|
29
|
+
?.filter((d) => d.tag === 'script')
|
|
30
|
+
.forEach((asset) => {
|
|
31
|
+
assetScripts.push({
|
|
32
|
+
tag: 'script',
|
|
33
|
+
attrs: { ...asset.attrs, nonce },
|
|
34
|
+
children: asset.children,
|
|
35
|
+
} as RouterManagedTag)
|
|
36
|
+
}),
|
|
37
|
+
)
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
},
|
|
39
|
+
return assetScripts
|
|
38
40
|
})
|
|
39
41
|
|
|
40
|
-
const scripts =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}),
|
|
56
|
-
})
|
|
42
|
+
const scripts = Vue.computed(() => ({
|
|
43
|
+
scripts: (
|
|
44
|
+
matches.value
|
|
45
|
+
.map((match) => match.scripts!)
|
|
46
|
+
.flat(1)
|
|
47
|
+
.filter(Boolean) as Array<RouterManagedTag>
|
|
48
|
+
).map(({ children, ...script }) => ({
|
|
49
|
+
tag: 'script' as const,
|
|
50
|
+
attrs: {
|
|
51
|
+
...script,
|
|
52
|
+
nonce,
|
|
53
|
+
},
|
|
54
|
+
children,
|
|
55
|
+
})),
|
|
56
|
+
}))
|
|
57
57
|
|
|
58
58
|
const mounted = Vue.ref(false)
|
|
59
59
|
Vue.onMounted(() => {
|