@tanstack/vue-router 1.140.1 → 1.141.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.
- package/dist/esm/Asset.js +122 -8
- package/dist/esm/Asset.js.map +1 -1
- package/dist/esm/Body.d.ts +4 -0
- package/dist/esm/Body.js +26 -0
- package/dist/esm/Body.js.map +1 -0
- package/dist/esm/CatchBoundary.d.ts +1 -1
- package/dist/esm/CatchBoundary.js +8 -8
- package/dist/esm/CatchBoundary.js.map +1 -1
- package/dist/esm/Html.d.ts +4 -0
- package/dist/esm/Html.js +63 -0
- package/dist/esm/Html.js.map +1 -0
- package/dist/esm/Match.js +87 -49
- package/dist/esm/Match.js.map +1 -1
- package/dist/esm/Matches.js +3 -2
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/RouterProvider.js +3 -0
- package/dist/esm/RouterProvider.js.map +1 -1
- package/dist/esm/ScriptOnce.d.ts +12 -5
- package/dist/esm/ScriptOnce.js +35 -15
- package/dist/esm/ScriptOnce.js.map +1 -1
- package/dist/esm/Scripts.d.ts +2 -1
- package/dist/esm/Scripts.js +101 -35
- package/dist/esm/Scripts.js.map +1 -1
- package/dist/esm/Transitioner.d.ts +16 -0
- package/dist/esm/Transitioner.js +136 -133
- package/dist/esm/Transitioner.js.map +1 -1
- package/dist/esm/awaited.d.ts +20 -5
- package/dist/esm/awaited.js +17 -20
- package/dist/esm/awaited.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lazyRouteComponent.js +2 -2
- package/dist/esm/lazyRouteComponent.js.map +1 -1
- package/dist/esm/link.js +27 -35
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/scroll-restoration.d.ts +8 -1
- package/dist/esm/scroll-restoration.js +44 -12
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/ssr/RouterClient.d.ts +15 -0
- package/dist/esm/ssr/RouterClient.js +46 -0
- package/dist/esm/ssr/RouterClient.js.map +1 -0
- package/dist/esm/ssr/RouterServer.d.ts +15 -0
- package/dist/esm/ssr/RouterServer.js +37 -0
- package/dist/esm/ssr/RouterServer.js.map +1 -0
- package/dist/esm/ssr/client.d.ts +1 -0
- package/dist/esm/ssr/client.js +5 -0
- package/dist/esm/ssr/client.js.map +1 -0
- package/dist/esm/ssr/defaultRenderHandler.d.ts +1 -0
- package/dist/esm/ssr/defaultRenderHandler.js +15 -0
- package/dist/esm/ssr/defaultRenderHandler.js.map +1 -0
- package/dist/esm/ssr/defaultStreamHandler.d.ts +1 -0
- package/dist/esm/ssr/defaultStreamHandler.js +17 -0
- package/dist/esm/ssr/defaultStreamHandler.js.map +1 -0
- package/dist/esm/ssr/renderRouterToStream.d.ts +8 -0
- package/dist/esm/ssr/renderRouterToStream.js +70 -0
- package/dist/esm/ssr/renderRouterToStream.js.map +1 -0
- package/dist/esm/ssr/renderRouterToString.d.ts +7 -0
- package/dist/esm/ssr/renderRouterToString.js +33 -0
- package/dist/esm/ssr/renderRouterToString.js.map +1 -0
- package/dist/esm/ssr/server.d.ts +6 -0
- package/dist/esm/ssr/server.js +14 -0
- package/dist/esm/ssr/server.js.map +1 -0
- package/dist/source/Asset.jsx +119 -7
- package/dist/source/Asset.jsx.map +1 -1
- package/dist/source/Body.d.ts +4 -0
- package/dist/source/Body.jsx +15 -0
- package/dist/source/Body.jsx.map +1 -0
- package/dist/source/CatchBoundary.d.ts +1 -1
- package/dist/source/CatchBoundary.jsx +10 -23
- package/dist/source/CatchBoundary.jsx.map +1 -1
- package/dist/source/Html.d.ts +4 -0
- package/dist/source/Html.jsx +56 -0
- package/dist/source/Html.jsx.map +1 -0
- package/dist/source/Match.jsx +119 -54
- package/dist/source/Match.jsx.map +1 -1
- package/dist/source/Matches.jsx +15 -3
- package/dist/source/Matches.jsx.map +1 -1
- package/dist/source/RouterProvider.jsx +5 -0
- package/dist/source/RouterProvider.jsx.map +1 -1
- package/dist/source/ScriptOnce.d.ts +12 -5
- package/dist/source/ScriptOnce.jsx +27 -16
- package/dist/source/ScriptOnce.jsx.map +1 -1
- package/dist/source/Scripts.d.ts +2 -1
- package/dist/source/Scripts.jsx +100 -42
- package/dist/source/Scripts.jsx.map +1 -1
- package/dist/source/Transitioner.d.ts +16 -0
- package/dist/source/Transitioner.jsx +180 -160
- package/dist/source/Transitioner.jsx.map +1 -1
- package/dist/source/awaited.d.ts +20 -5
- package/dist/source/awaited.jsx +18 -25
- package/dist/source/awaited.jsx.map +1 -1
- package/dist/source/index.d.ts +2 -0
- package/dist/source/index.jsx +2 -0
- package/dist/source/index.jsx.map +1 -1
- package/dist/source/lazyRouteComponent.jsx +4 -2
- package/dist/source/lazyRouteComponent.jsx.map +1 -1
- package/dist/source/link.jsx +37 -51
- package/dist/source/link.jsx.map +1 -1
- package/dist/source/scroll-restoration.d.ts +8 -1
- package/dist/source/scroll-restoration.jsx +55 -12
- package/dist/source/scroll-restoration.jsx.map +1 -1
- package/dist/source/ssr/RouterClient.d.ts +15 -0
- package/dist/source/ssr/RouterClient.jsx +48 -0
- package/dist/source/ssr/RouterClient.jsx.map +1 -0
- package/dist/source/ssr/RouterServer.d.ts +15 -0
- package/dist/source/ssr/RouterServer.jsx +40 -0
- package/dist/source/ssr/RouterServer.jsx.map +1 -0
- package/dist/source/ssr/client.d.ts +1 -0
- package/dist/source/ssr/client.js +2 -0
- package/dist/source/ssr/client.js.map +1 -0
- package/dist/source/ssr/defaultRenderHandler.d.ts +1 -0
- package/dist/source/ssr/defaultRenderHandler.jsx +9 -0
- package/dist/source/ssr/defaultRenderHandler.jsx.map +1 -0
- package/dist/source/ssr/defaultStreamHandler.d.ts +1 -0
- package/dist/source/ssr/defaultStreamHandler.jsx +10 -0
- package/dist/source/ssr/defaultStreamHandler.jsx.map +1 -0
- package/dist/source/ssr/renderRouterToStream.d.ts +8 -0
- package/dist/source/ssr/renderRouterToStream.jsx +55 -0
- package/dist/source/ssr/renderRouterToStream.jsx.map +1 -0
- package/dist/source/ssr/renderRouterToString.d.ts +7 -0
- package/dist/source/ssr/renderRouterToString.jsx +26 -0
- package/dist/source/ssr/renderRouterToString.jsx.map +1 -0
- package/dist/source/ssr/server.d.ts +6 -0
- package/dist/source/ssr/server.js +7 -0
- package/dist/source/ssr/server.js.map +1 -0
- package/package.json +16 -3
- package/src/Asset.tsx +157 -7
- package/src/Body.tsx +26 -0
- package/src/CatchBoundary.tsx +11 -25
- package/src/Html.tsx +65 -0
- package/src/Match.tsx +135 -58
- package/src/Matches.tsx +16 -4
- package/src/RouterProvider.tsx +6 -0
- package/src/ScriptOnce.tsx +43 -28
- package/src/Scripts.tsx +121 -56
- package/src/Transitioner.tsx +197 -176
- package/src/awaited.tsx +17 -28
- package/src/index.tsx +2 -0
- package/src/lazyRouteComponent.tsx +4 -2
- package/src/link.tsx +42 -47
- package/src/scroll-restoration.tsx +69 -21
- package/src/ssr/RouterClient.tsx +58 -0
- package/src/ssr/RouterServer.tsx +51 -0
- package/src/ssr/client.ts +1 -0
- package/src/ssr/defaultRenderHandler.tsx +12 -0
- package/src/ssr/defaultStreamHandler.tsx +13 -0
- package/src/ssr/renderRouterToStream.tsx +85 -0
- package/src/ssr/renderRouterToString.tsx +37 -0
- package/src/ssr/server.ts +6 -0
package/src/Transitioner.tsx
CHANGED
|
@@ -8,206 +8,227 @@ import { useRouter } from './useRouter'
|
|
|
8
8
|
import { useRouterState } from './useRouterState'
|
|
9
9
|
import { usePrevious } from './utils'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
// Track mount state per router to avoid double-loading
|
|
12
|
+
let mountLoadForRouter = { router: null as any, mounted: false }
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Composable that sets up router transition logic.
|
|
16
|
+
* This is called from MatchesContent to set up:
|
|
17
|
+
* - router.startTransition
|
|
18
|
+
* - router.startViewTransition
|
|
19
|
+
* - History subscription
|
|
20
|
+
* - Router event watchers
|
|
21
|
+
*
|
|
22
|
+
* Must be called during component setup phase.
|
|
23
|
+
*/
|
|
24
|
+
export function useTransitionerSetup() {
|
|
25
|
+
const router = useRouter()
|
|
26
|
+
|
|
27
|
+
// Skip on server - no transitions needed
|
|
28
|
+
if (router.isServer) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isLoading = useRouterState({
|
|
33
|
+
select: ({ isLoading }) => isLoading,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Track if we're in a transition - using a ref to track async transitions
|
|
37
|
+
const isTransitioning = Vue.ref(false)
|
|
38
|
+
|
|
39
|
+
// Track pending state changes
|
|
40
|
+
const hasPendingMatches = useRouterState({
|
|
41
|
+
select: (s) => s.matches.some((d) => d.status === 'pending'),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const previousIsLoading = usePrevious(() => isLoading.value)
|
|
45
|
+
|
|
46
|
+
const isAnyPending = Vue.computed(
|
|
47
|
+
() => isLoading.value || isTransitioning.value || hasPendingMatches.value,
|
|
48
|
+
)
|
|
49
|
+
const previousIsAnyPending = usePrevious(() => isAnyPending.value)
|
|
50
|
+
|
|
51
|
+
const isPagePending = Vue.computed(
|
|
52
|
+
() => isLoading.value || hasPendingMatches.value,
|
|
53
|
+
)
|
|
54
|
+
const previousIsPagePending = usePrevious(() => isPagePending.value)
|
|
55
|
+
|
|
56
|
+
// Implement startTransition similar to React/Solid
|
|
57
|
+
// Vue doesn't have a native useTransition like React 18, so we simulate it
|
|
58
|
+
// We also update the router state's isTransitioning flag so useMatch can check it
|
|
59
|
+
router.startTransition = (fn: () => void | Promise<void>) => {
|
|
60
|
+
isTransitioning.value = true
|
|
61
|
+
// Also update the router state so useMatch knows we're transitioning
|
|
62
|
+
try {
|
|
63
|
+
router.__store.setState((s) => ({ ...s, isTransitioning: true }))
|
|
64
|
+
} catch {
|
|
65
|
+
// Ignore errors if component is unmounted
|
|
19
66
|
}
|
|
20
67
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
68
|
+
// Helper to end the transition
|
|
69
|
+
const endTransition = () => {
|
|
70
|
+
// Use nextTick to ensure Vue has processed all reactive updates
|
|
71
|
+
Vue.nextTick(() => {
|
|
72
|
+
try {
|
|
73
|
+
isTransitioning.value = false
|
|
74
|
+
router.__store.setState((s) => ({ ...s, isTransitioning: false }))
|
|
75
|
+
} catch {
|
|
76
|
+
// Ignore errors if component is unmounted
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
27
80
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
81
|
+
// Execute the function synchronously
|
|
82
|
+
// The function internally may call startViewTransition which schedules async work
|
|
83
|
+
// via document.startViewTransition, but we don't need to wait for it here
|
|
84
|
+
// because Vue's reactivity will trigger re-renders when state changes
|
|
85
|
+
fn()
|
|
86
|
+
|
|
87
|
+
// End the transition on next tick to allow Vue to process reactive updates
|
|
88
|
+
endTransition()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// For Vue, we need to completely override startViewTransition because Vue's
|
|
92
|
+
// async rendering doesn't work well with the View Transitions API's requirement
|
|
93
|
+
// for synchronous DOM updates. The browser expects the DOM to be updated
|
|
94
|
+
// when the callback promise resolves, but Vue updates asynchronously.
|
|
95
|
+
//
|
|
96
|
+
// Our approach: Skip the actual view transition animation but still update state.
|
|
97
|
+
// This ensures navigation works correctly even without the visual transition.
|
|
98
|
+
// In the future, we could explore using viewTransition.captured like vue-view-transitions does.
|
|
99
|
+
router.startViewTransition = (fn: () => Promise<void>) => {
|
|
100
|
+
// Just run the callback directly without wrapping in document.startViewTransition
|
|
101
|
+
// This ensures the state updates happen and Vue can render them normally
|
|
102
|
+
fn()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Subscribe to location changes
|
|
106
|
+
// and try to load the new location
|
|
107
|
+
let unsubscribe: (() => void) | undefined
|
|
108
|
+
|
|
109
|
+
Vue.onMounted(() => {
|
|
110
|
+
unsubscribe = router.history.subscribe(router.load)
|
|
111
|
+
|
|
112
|
+
const nextLocation = router.buildLocation({
|
|
113
|
+
to: router.latestLocation.pathname,
|
|
114
|
+
search: true,
|
|
115
|
+
params: true,
|
|
116
|
+
hash: true,
|
|
117
|
+
state: true,
|
|
118
|
+
_includeValidateSearch: true,
|
|
31
119
|
})
|
|
32
120
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const isPagePending = Vue.computed(
|
|
41
|
-
() => isLoading.value || hasPendingMatches.value,
|
|
42
|
-
)
|
|
43
|
-
const previousIsPagePending = usePrevious(() => isPagePending.value)
|
|
44
|
-
|
|
45
|
-
// Implement startTransition similar to React/Solid
|
|
46
|
-
// Vue doesn't have a native useTransition like React 18, so we simulate it
|
|
47
|
-
// We also update the router state's isTransitioning flag so useMatch can check it
|
|
48
|
-
router.startTransition = (fn: () => void | Promise<void>) => {
|
|
49
|
-
isTransitioning.value = true
|
|
50
|
-
// Also update the router state so useMatch knows we're transitioning
|
|
51
|
-
try {
|
|
52
|
-
router.__store.setState((s) => ({ ...s, isTransitioning: true }))
|
|
53
|
-
} catch {
|
|
54
|
-
// Ignore errors if component is unmounted
|
|
55
|
-
}
|
|
121
|
+
if (
|
|
122
|
+
trimPathRight(router.latestLocation.href) !==
|
|
123
|
+
trimPathRight(nextLocation.href)
|
|
124
|
+
) {
|
|
125
|
+
router.commitLocation({ ...nextLocation, replace: true })
|
|
126
|
+
}
|
|
127
|
+
})
|
|
56
128
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Use nextTick to ensure Vue has processed all reactive updates
|
|
60
|
-
Vue.nextTick(() => {
|
|
61
|
-
try {
|
|
62
|
-
isTransitioning.value = false
|
|
63
|
-
router.__store.setState((s) => ({ ...s, isTransitioning: false }))
|
|
64
|
-
} catch {
|
|
65
|
-
// Ignore errors if component is unmounted
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
}
|
|
129
|
+
// Track if component is mounted to prevent updates after unmount
|
|
130
|
+
const isMounted = Vue.ref(false)
|
|
69
131
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// because Vue's reactivity will trigger re-renders when state changes
|
|
74
|
-
fn()
|
|
132
|
+
Vue.onMounted(() => {
|
|
133
|
+
isMounted.value = true
|
|
134
|
+
})
|
|
75
135
|
|
|
76
|
-
|
|
77
|
-
|
|
136
|
+
Vue.onUnmounted(() => {
|
|
137
|
+
isMounted.value = false
|
|
138
|
+
if (unsubscribe) {
|
|
139
|
+
unsubscribe()
|
|
78
140
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
router.startViewTransition = (fn: () => Promise<void>) => {
|
|
89
|
-
// Just run the callback directly without wrapping in document.startViewTransition
|
|
90
|
-
// This ensures the state updates happen and Vue can render them normally
|
|
91
|
-
fn()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Try to load the initial location
|
|
144
|
+
Vue.onMounted(() => {
|
|
145
|
+
if (
|
|
146
|
+
(typeof window !== 'undefined' && router.ssr) ||
|
|
147
|
+
(mountLoadForRouter.router === router && mountLoadForRouter.mounted)
|
|
148
|
+
) {
|
|
149
|
+
return
|
|
92
150
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
unsubscribe = router.history.subscribe(router.load)
|
|
100
|
-
|
|
101
|
-
const nextLocation = router.buildLocation({
|
|
102
|
-
to: router.latestLocation.pathname,
|
|
103
|
-
search: true,
|
|
104
|
-
params: true,
|
|
105
|
-
hash: true,
|
|
106
|
-
state: true,
|
|
107
|
-
_includeValidateSearch: true,
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
trimPathRight(router.latestLocation.href) !==
|
|
112
|
-
trimPathRight(nextLocation.href)
|
|
113
|
-
) {
|
|
114
|
-
router.commitLocation({ ...nextLocation, replace: true })
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
// Track if component is mounted to prevent updates after unmount
|
|
119
|
-
const isMounted = Vue.ref(false)
|
|
120
|
-
|
|
121
|
-
Vue.onMounted(() => {
|
|
122
|
-
isMounted.value = true
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
Vue.onUnmounted(() => {
|
|
126
|
-
isMounted.value = false
|
|
127
|
-
if (unsubscribe) {
|
|
128
|
-
unsubscribe()
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
// Try to load the initial location
|
|
133
|
-
Vue.onMounted(() => {
|
|
134
|
-
if (
|
|
135
|
-
(typeof window !== 'undefined' && router.ssr) ||
|
|
136
|
-
(mountLoadForRouter.router === router && mountLoadForRouter.mounted)
|
|
137
|
-
) {
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
mountLoadForRouter = { router, mounted: true }
|
|
141
|
-
const tryLoad = async () => {
|
|
142
|
-
try {
|
|
143
|
-
await router.load()
|
|
144
|
-
} catch (err) {
|
|
145
|
-
console.error(err)
|
|
146
|
-
}
|
|
151
|
+
mountLoadForRouter = { router, mounted: true }
|
|
152
|
+
const tryLoad = async () => {
|
|
153
|
+
try {
|
|
154
|
+
await router.load()
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error(err)
|
|
147
157
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
if (previousIsLoading.value.previous && !newValue) {
|
|
159
|
-
router.emit({
|
|
160
|
-
type: 'onLoad',
|
|
161
|
-
...getLocationChangeInfo(router.state),
|
|
162
|
-
})
|
|
163
|
-
}
|
|
164
|
-
} catch {
|
|
165
|
-
// Ignore errors if component is unmounted
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
Vue.watch(isPagePending, (newValue) => {
|
|
158
|
+
}
|
|
159
|
+
tryLoad()
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Setup watchers for emitting events
|
|
163
|
+
// All watchers check isMounted to prevent updates after unmount
|
|
164
|
+
Vue.watch(
|
|
165
|
+
() => isLoading.value,
|
|
166
|
+
(newValue) => {
|
|
171
167
|
if (!isMounted.value) return
|
|
172
168
|
try {
|
|
173
|
-
|
|
174
|
-
if (previousIsPagePending.value.previous && !newValue) {
|
|
169
|
+
if (previousIsLoading.value.previous && !newValue) {
|
|
175
170
|
router.emit({
|
|
176
|
-
type: '
|
|
171
|
+
type: 'onLoad',
|
|
177
172
|
...getLocationChangeInfo(router.state),
|
|
178
173
|
})
|
|
179
174
|
}
|
|
180
175
|
} catch {
|
|
181
176
|
// Ignore errors if component is unmounted
|
|
182
177
|
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
178
|
+
},
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
Vue.watch(isPagePending, (newValue) => {
|
|
182
|
+
if (!isMounted.value) return
|
|
183
|
+
try {
|
|
184
|
+
// emit onBeforeRouteMount
|
|
185
|
+
if (previousIsPagePending.value.previous && !newValue) {
|
|
186
|
+
router.emit({
|
|
187
|
+
type: 'onBeforeRouteMount',
|
|
188
|
+
...getLocationChangeInfo(router.state),
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// Ignore errors if component is unmounted
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
Vue.watch(isAnyPending, (newValue) => {
|
|
197
|
+
if (!isMounted.value) return
|
|
198
|
+
try {
|
|
199
|
+
// The router was pending and now it's not
|
|
200
|
+
if (previousIsAnyPending.value.previous && !newValue) {
|
|
201
|
+
const changeInfo = getLocationChangeInfo(router.state)
|
|
202
|
+
router.emit({
|
|
203
|
+
type: 'onResolved',
|
|
204
|
+
...changeInfo,
|
|
205
|
+
})
|
|
195
206
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
207
|
+
router.__store.setState((s) => ({
|
|
208
|
+
...s,
|
|
209
|
+
status: 'idle',
|
|
210
|
+
resolvedLocation: s.location,
|
|
211
|
+
}))
|
|
201
212
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
213
|
+
if (changeInfo.hrefChanged) {
|
|
214
|
+
handleHashScroll(router)
|
|
205
215
|
}
|
|
206
|
-
} catch {
|
|
207
|
-
// Ignore errors if component is unmounted
|
|
208
216
|
}
|
|
209
|
-
}
|
|
210
|
-
|
|
217
|
+
} catch {
|
|
218
|
+
// Ignore errors if component is unmounted
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @deprecated Use useTransitionerSetup() composable instead.
|
|
225
|
+
* This component is kept for backwards compatibility but the setup logic
|
|
226
|
+
* has been moved to useTransitionerSetup() for better SSR hydration.
|
|
227
|
+
*/
|
|
228
|
+
export const Transitioner = Vue.defineComponent({
|
|
229
|
+
name: 'Transitioner',
|
|
230
|
+
setup() {
|
|
231
|
+
useTransitionerSetup()
|
|
211
232
|
return () => null
|
|
212
233
|
},
|
|
213
234
|
})
|
package/src/awaited.tsx
CHANGED
|
@@ -23,32 +23,21 @@ export function useAwaited<T>({
|
|
|
23
23
|
return [promise[TSR_DEFERRED_PROMISE].data, promise]
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
export const Await = Vue.defineComponent({
|
|
27
|
+
name: 'Await',
|
|
28
|
+
props: {
|
|
29
|
+
promise: {
|
|
30
|
+
type: Promise,
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
children: {
|
|
34
|
+
type: Function,
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
30
37
|
},
|
|
31
|
-
) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
pending.value = true
|
|
38
|
-
try {
|
|
39
|
-
data.value = await props.promise
|
|
40
|
-
} catch (err) {
|
|
41
|
-
error.value = err as Error
|
|
42
|
-
} finally {
|
|
43
|
-
pending.value = false
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const inner = Vue.computed(() => {
|
|
48
|
-
if (error.value) throw error.value
|
|
49
|
-
if (pending.value) return props.fallback
|
|
50
|
-
return props.children(data.value as T)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
return () => inner.value
|
|
54
|
-
}
|
|
38
|
+
async setup(props) {
|
|
39
|
+
const deferred = defer(props.promise)
|
|
40
|
+
const data = await deferred
|
|
41
|
+
return () => (props.children as (result: unknown) => Vue.VNode)(data)
|
|
42
|
+
},
|
|
43
|
+
})
|
package/src/index.tsx
CHANGED
|
@@ -339,6 +339,8 @@ export { ScriptOnce } from './ScriptOnce'
|
|
|
339
339
|
export { Asset } from './Asset'
|
|
340
340
|
export { HeadContent } from './HeadContent'
|
|
341
341
|
export { Scripts } from './Scripts'
|
|
342
|
+
export { Body } from './Body'
|
|
343
|
+
export { Html } from './Html'
|
|
342
344
|
export { composeRewrites } from '@tanstack/router-core'
|
|
343
345
|
export type {
|
|
344
346
|
LocationRewrite,
|
|
@@ -98,7 +98,8 @@ export function lazyRouteComponent<
|
|
|
98
98
|
name: 'LazyRouteComponent',
|
|
99
99
|
setup(props: any) {
|
|
100
100
|
// Create refs to track component state
|
|
101
|
-
|
|
101
|
+
// Use shallowRef for component to avoid making it reactive (Vue warning)
|
|
102
|
+
const component = Vue.shallowRef<any>(comp ? Vue.markRaw(comp) : comp)
|
|
102
103
|
const errorState = Vue.ref<any>(error)
|
|
103
104
|
const loading = Vue.ref(!component.value && !errorState.value)
|
|
104
105
|
|
|
@@ -109,7 +110,8 @@ export function lazyRouteComponent<
|
|
|
109
110
|
|
|
110
111
|
load()
|
|
111
112
|
.then((result) => {
|
|
112
|
-
component
|
|
113
|
+
// Use markRaw to prevent Vue from making the component reactive
|
|
114
|
+
component.value = result ? Vue.markRaw(result) : result
|
|
113
115
|
loading.value = false
|
|
114
116
|
})
|
|
115
117
|
.catch((err) => {
|
package/src/link.tsx
CHANGED
|
@@ -446,13 +446,8 @@ export function useLinkProps<
|
|
|
446
446
|
return hrefValue
|
|
447
447
|
})
|
|
448
448
|
|
|
449
|
-
// Create
|
|
450
|
-
|
|
451
|
-
// Use shallowReactive to preserve the ref object without unwrapping it
|
|
452
|
-
const reactiveProps: HTMLAttributes = Vue.shallowReactive({
|
|
453
|
-
...getPropsSafeToSpread(),
|
|
454
|
-
href: undefined as string | undefined,
|
|
455
|
-
ref,
|
|
449
|
+
// Create static event handlers that don't change between renders
|
|
450
|
+
const staticEventHandlers = {
|
|
456
451
|
onClick: composeEventHandlers<MouseEvent>([
|
|
457
452
|
options.onClick,
|
|
458
453
|
handleClick,
|
|
@@ -481,72 +476,67 @@ export function useLinkProps<
|
|
|
481
476
|
options.onTouchStart,
|
|
482
477
|
handleTouchStart,
|
|
483
478
|
]) as any,
|
|
484
|
-
|
|
485
|
-
target: options.target,
|
|
486
|
-
})
|
|
487
|
-
|
|
488
|
-
// Watch computed values and update reactive props
|
|
489
|
-
Vue.watchEffect(() => {
|
|
490
|
-
// Update from resolved active/inactive props
|
|
491
|
-
const activeP = resolvedActiveProps.value
|
|
492
|
-
const inactiveP = resolvedInactiveProps.value
|
|
479
|
+
}
|
|
493
480
|
|
|
494
|
-
|
|
495
|
-
|
|
481
|
+
// Compute all props synchronously to avoid hydration mismatches
|
|
482
|
+
// Using Vue.computed ensures props are calculated at render time, not after
|
|
483
|
+
const computedProps = Vue.computed<HTMLAttributes>(() => {
|
|
484
|
+
const result: HTMLAttributes = {
|
|
485
|
+
...getPropsSafeToSpread(),
|
|
486
|
+
href: href.value,
|
|
487
|
+
ref,
|
|
488
|
+
...staticEventHandlers,
|
|
489
|
+
disabled: !!options.disabled,
|
|
490
|
+
target: options.target,
|
|
491
|
+
}
|
|
496
492
|
|
|
497
|
-
//
|
|
493
|
+
// Add style if present
|
|
498
494
|
if (resolvedStyle.value) {
|
|
499
|
-
|
|
500
|
-
} else {
|
|
501
|
-
delete reactiveProps.style
|
|
495
|
+
result.style = resolvedStyle.value
|
|
502
496
|
}
|
|
503
497
|
|
|
504
|
-
//
|
|
498
|
+
// Add class if present
|
|
505
499
|
if (resolvedClassName.value) {
|
|
506
|
-
|
|
507
|
-
} else {
|
|
508
|
-
delete reactiveProps.class
|
|
500
|
+
result.class = resolvedClassName.value
|
|
509
501
|
}
|
|
510
502
|
|
|
511
|
-
//
|
|
503
|
+
// Add disabled props
|
|
512
504
|
if (options.disabled) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
} else {
|
|
516
|
-
delete reactiveProps.role
|
|
517
|
-
delete reactiveProps['aria-disabled']
|
|
505
|
+
result.role = 'link'
|
|
506
|
+
result['aria-disabled'] = true
|
|
518
507
|
}
|
|
519
508
|
|
|
520
|
-
//
|
|
509
|
+
// Add active status
|
|
521
510
|
if (isActive.value) {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
} else {
|
|
525
|
-
delete reactiveProps['data-status']
|
|
526
|
-
delete reactiveProps['aria-current']
|
|
511
|
+
result['data-status'] = 'active'
|
|
512
|
+
result['aria-current'] = 'page'
|
|
527
513
|
}
|
|
528
514
|
|
|
529
|
-
//
|
|
515
|
+
// Add transitioning status
|
|
530
516
|
if (isTransitioning.value) {
|
|
531
|
-
|
|
532
|
-
} else {
|
|
533
|
-
delete reactiveProps['data-transitioning']
|
|
517
|
+
result['data-transitioning'] = 'transitioning'
|
|
534
518
|
}
|
|
535
519
|
|
|
536
520
|
// Merge active/inactive props (excluding class and style which are handled above)
|
|
521
|
+
const activeP = resolvedActiveProps.value
|
|
522
|
+
const inactiveP = resolvedInactiveProps.value
|
|
523
|
+
|
|
537
524
|
for (const key of Object.keys(activeP)) {
|
|
538
525
|
if (key !== 'class' && key !== 'style') {
|
|
539
|
-
|
|
526
|
+
result[key] = activeP[key]
|
|
540
527
|
}
|
|
541
528
|
}
|
|
542
529
|
for (const key of Object.keys(inactiveP)) {
|
|
543
530
|
if (key !== 'class' && key !== 'style') {
|
|
544
|
-
|
|
531
|
+
result[key] = inactiveP[key]
|
|
545
532
|
}
|
|
546
533
|
}
|
|
534
|
+
|
|
535
|
+
return result
|
|
547
536
|
})
|
|
548
537
|
|
|
549
|
-
|
|
538
|
+
// Return the computed ref itself - callers should access .value
|
|
539
|
+
return computedProps as unknown as HTMLAttributes
|
|
550
540
|
}
|
|
551
541
|
|
|
552
542
|
// Type definitions
|
|
@@ -682,13 +672,18 @@ const LinkImpl = Vue.defineComponent({
|
|
|
682
672
|
],
|
|
683
673
|
setup(props, { attrs, slots }) {
|
|
684
674
|
// Call useLinkProps ONCE during setup with combined props and attrs
|
|
685
|
-
// The returned object
|
|
675
|
+
// The returned object is a computed ref that updates reactively
|
|
686
676
|
const allProps = { ...props, ...attrs }
|
|
687
|
-
const
|
|
677
|
+
const linkPropsComputed = useLinkProps(
|
|
678
|
+
allProps as any,
|
|
679
|
+
) as unknown as Vue.ComputedRef<HTMLAttributes>
|
|
688
680
|
|
|
689
681
|
return () => {
|
|
690
682
|
const Component = props._asChild || 'a'
|
|
691
683
|
|
|
684
|
+
// Access the computed value to get fresh props each render
|
|
685
|
+
const linkProps = linkPropsComputed.value
|
|
686
|
+
|
|
692
687
|
const isActive = linkProps['data-status'] === 'active'
|
|
693
688
|
const isTransitioning =
|
|
694
689
|
linkProps['data-transitioning'] === 'transitioning'
|