@tanstack/router-core 1.168.4 → 1.168.6
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/cjs/hash-scroll.cjs +20 -0
- package/dist/cjs/hash-scroll.cjs.map +1 -0
- package/dist/cjs/hash-scroll.d.cts +7 -0
- package/dist/cjs/index.cjs +3 -3
- package/dist/cjs/index.d.cts +2 -1
- package/dist/cjs/scroll-restoration-inline.cjs +6 -0
- package/dist/cjs/scroll-restoration-inline.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-inline.d.cts +6 -0
- package/dist/cjs/scroll-restoration-script/client.cjs +9 -0
- package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-script/client.d.cts +2 -0
- package/dist/cjs/scroll-restoration-script/server.cjs +30 -0
- package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-script/server.d.cts +2 -0
- package/dist/cjs/scroll-restoration.cjs +124 -142
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.d.cts +15 -38
- package/dist/cjs/ssr/tsrScript.cjs +1 -1
- package/dist/esm/hash-scroll.d.ts +7 -0
- package/dist/esm/hash-scroll.js +20 -0
- package/dist/esm/hash-scroll.js.map +1 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +3 -2
- package/dist/esm/scroll-restoration-inline.d.ts +6 -0
- package/dist/esm/scroll-restoration-inline.js +6 -0
- package/dist/esm/scroll-restoration-inline.js.map +1 -0
- package/dist/esm/scroll-restoration-script/client.d.ts +2 -0
- package/dist/esm/scroll-restoration-script/client.js +8 -0
- package/dist/esm/scroll-restoration-script/client.js.map +1 -0
- package/dist/esm/scroll-restoration-script/server.d.ts +2 -0
- package/dist/esm/scroll-restoration-script/server.js +29 -0
- package/dist/esm/scroll-restoration-script/server.js.map +1 -0
- package/dist/esm/scroll-restoration.d.ts +15 -38
- package/dist/esm/scroll-restoration.js +125 -141
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/ssr/tsrScript.js +1 -1
- package/package.json +21 -1
- package/src/hash-scroll.ts +21 -0
- package/src/index.ts +3 -3
- package/src/scroll-restoration-inline.ts +81 -0
- package/src/scroll-restoration-script/client.ts +5 -0
- package/src/scroll-restoration-script/server.ts +64 -0
- package/src/scroll-restoration.ts +226 -272
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { isServer } from '@tanstack/router-core/isServer'
|
|
2
|
-
import { functionalUpdate } from './utils'
|
|
2
|
+
import { functionalUpdate, isPlainObject } from './utils'
|
|
3
3
|
import type { AnyRouter } from './router'
|
|
4
4
|
import type { ParsedLocation } from './location'
|
|
5
5
|
import type { NonNullableUpdater } from './utils'
|
|
6
|
-
import type { HistoryLocation } from '@tanstack/history'
|
|
7
6
|
|
|
8
7
|
export type ScrollRestorationEntry = { scrollX: number; scrollY: number }
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
state: ScrollRestorationByKey
|
|
13
|
+
type ScrollRestorationCache = {
|
|
14
|
+
readonly state: ScrollRestorationByKey
|
|
16
15
|
set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void
|
|
16
|
+
persist: () => void
|
|
17
17
|
}
|
|
18
|
+
|
|
18
19
|
export type ScrollRestorationOptions = {
|
|
19
20
|
getKey?: (location: ParsedLocation) => string
|
|
20
21
|
scrollBehavior?: ScrollToOptions['behavior']
|
|
@@ -22,65 +23,59 @@ export type ScrollRestorationOptions = {
|
|
|
22
23
|
|
|
23
24
|
function getSafeSessionStorage() {
|
|
24
25
|
try {
|
|
25
|
-
|
|
26
|
-
typeof window !== 'undefined' &&
|
|
26
|
+
return typeof window !== 'undefined' &&
|
|
27
27
|
typeof window.sessionStorage === 'object'
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
28
|
+
? window.sessionStorage
|
|
29
|
+
: undefined
|
|
31
30
|
} catch {
|
|
32
31
|
// silent
|
|
32
|
+
return undefined
|
|
33
33
|
}
|
|
34
|
-
return undefined
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
/** SessionStorage key used to store scroll positions across navigations. */
|
|
39
|
-
/** SessionStorage key used to store scroll positions across navigations. */
|
|
36
|
+
// SessionStorage key used to store scroll positions across navigations.
|
|
40
37
|
export const storageKey = 'tsr-scroll-restoration-v1_3'
|
|
41
38
|
|
|
42
|
-
const throttle = (fn: (...args: Array<any>) => void, wait: number) => {
|
|
43
|
-
let timeout: any
|
|
44
|
-
return (...args: Array<any>) => {
|
|
45
|
-
if (!timeout) {
|
|
46
|
-
timeout = setTimeout(() => {
|
|
47
|
-
fn(...args)
|
|
48
|
-
timeout = null
|
|
49
|
-
}, wait)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
39
|
function createScrollRestorationCache(): ScrollRestorationCache | null {
|
|
55
40
|
const safeSessionStorage = getSafeSessionStorage()
|
|
56
41
|
if (!safeSessionStorage) {
|
|
57
42
|
return null
|
|
58
43
|
}
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
let state: ScrollRestorationByKey = persistedState
|
|
62
|
-
? JSON.parse(persistedState)
|
|
63
|
-
: {}
|
|
45
|
+
let state: ScrollRestorationByKey = {}
|
|
64
46
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(safeSessionStorage.getItem(storageKey) || '{}')
|
|
49
|
+
if (isPlainObject(parsed)) {
|
|
50
|
+
state = parsed as ScrollRestorationByKey
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore invalid session storage payloads
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const persist = () => {
|
|
57
|
+
try {
|
|
58
|
+
safeSessionStorage.setItem(storageKey, JSON.stringify(state))
|
|
59
|
+
} catch {
|
|
60
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
75
61
|
console.warn(
|
|
76
62
|
'[ts-router] Could not persist scroll restoration state to sessionStorage.',
|
|
77
63
|
)
|
|
78
64
|
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
get state() {
|
|
70
|
+
return state
|
|
71
|
+
},
|
|
72
|
+
set: (updater) => {
|
|
73
|
+
state = functionalUpdate(updater, state) || state
|
|
79
74
|
},
|
|
75
|
+
persist,
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
/** In-memory handle to the persisted scroll restoration cache. */
|
|
84
79
|
export const scrollRestorationCache = createScrollRestorationCache()
|
|
85
80
|
|
|
86
81
|
/**
|
|
@@ -89,16 +84,11 @@ export const scrollRestorationCache = createScrollRestorationCache()
|
|
|
89
84
|
*
|
|
90
85
|
* The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.
|
|
91
86
|
*/
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Default scroll restoration cache key: location state key or full href.
|
|
95
|
-
*/
|
|
96
87
|
export const defaultGetScrollRestorationKey = (location: ParsedLocation) => {
|
|
97
88
|
return location.state.__TSR_key! || location.href
|
|
98
89
|
}
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
export function getCssSelector(el: any): string {
|
|
91
|
+
function getCssSelector(el: any): string {
|
|
102
92
|
const path = []
|
|
103
93
|
let parent: HTMLElement
|
|
104
94
|
while ((parent = el.parentNode)) {
|
|
@@ -110,117 +100,52 @@ export function getCssSelector(el: any): string {
|
|
|
110
100
|
return `${path.reverse().join(' > ')}`.toLowerCase()
|
|
111
101
|
}
|
|
112
102
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
export function restoreScroll({
|
|
120
|
-
storageKey,
|
|
121
|
-
key,
|
|
122
|
-
behavior,
|
|
123
|
-
shouldScrollRestoration,
|
|
124
|
-
scrollToTopSelectors,
|
|
125
|
-
location,
|
|
126
|
-
}: {
|
|
127
|
-
storageKey: string
|
|
128
|
-
key?: string
|
|
129
|
-
behavior?: ScrollToOptions['behavior']
|
|
130
|
-
shouldScrollRestoration?: boolean
|
|
131
|
-
scrollToTopSelectors?: Array<string | (() => Element | null | undefined)>
|
|
132
|
-
location?: HistoryLocation
|
|
133
|
-
}) {
|
|
134
|
-
let byKey: ScrollRestorationByKey
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error(error)
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const resolvedKey = key || window.history.state?.__TSR_key
|
|
144
|
-
const elementEntries = byKey[resolvedKey]
|
|
145
|
-
|
|
146
|
-
//
|
|
147
|
-
ignoreScroll = true
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
scroll: {
|
|
151
|
-
// If we have a cached entry for this location state,
|
|
152
|
-
// we always need to prefer that over the hash scroll.
|
|
153
|
-
if (
|
|
154
|
-
shouldScrollRestoration &&
|
|
155
|
-
elementEntries &&
|
|
156
|
-
Object.keys(elementEntries).length > 0
|
|
157
|
-
) {
|
|
158
|
-
for (const elementSelector in elementEntries) {
|
|
159
|
-
const entry = elementEntries[elementSelector]!
|
|
160
|
-
if (elementSelector === 'window') {
|
|
161
|
-
window.scrollTo({
|
|
162
|
-
top: entry.scrollY,
|
|
163
|
-
left: entry.scrollX,
|
|
164
|
-
behavior,
|
|
165
|
-
})
|
|
166
|
-
} else if (elementSelector) {
|
|
167
|
-
const element = document.querySelector(elementSelector)
|
|
168
|
-
if (element) {
|
|
169
|
-
element.scrollLeft = entry.scrollX
|
|
170
|
-
element.scrollTop = entry.scrollY
|
|
171
|
-
}
|
|
172
|
-
}
|
|
103
|
+
export function getElementScrollRestorationEntry(
|
|
104
|
+
router: AnyRouter,
|
|
105
|
+
options: (
|
|
106
|
+
| {
|
|
107
|
+
id: string
|
|
108
|
+
getElement?: () => Window | Element | undefined | null
|
|
173
109
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// If we don't have a cached entry for the hash,
|
|
179
|
-
// Which means we've never seen this location before,
|
|
180
|
-
// we need to check if there is a hash in the URL.
|
|
181
|
-
// If there is, we need to scroll it's ID into view.
|
|
182
|
-
const hash = (location ?? window.location).hash.split('#', 2)[1]
|
|
183
|
-
|
|
184
|
-
if (hash) {
|
|
185
|
-
const hashScrollIntoViewOptions =
|
|
186
|
-
window.history.state?.__hashScrollIntoViewOptions ?? true
|
|
187
|
-
|
|
188
|
-
if (hashScrollIntoViewOptions) {
|
|
189
|
-
const el = document.getElementById(hash)
|
|
190
|
-
if (el) {
|
|
191
|
-
el.scrollIntoView(hashScrollIntoViewOptions)
|
|
192
|
-
}
|
|
110
|
+
| {
|
|
111
|
+
id?: string
|
|
112
|
+
getElement: () => Window | Element | undefined | null
|
|
193
113
|
}
|
|
114
|
+
) & {
|
|
115
|
+
getKey?: (location: ParsedLocation) => string
|
|
116
|
+
},
|
|
117
|
+
): ScrollRestorationEntry | undefined {
|
|
118
|
+
const getKey = options.getKey || defaultGetScrollRestorationKey
|
|
119
|
+
const restoreKey = getKey(router.latestLocation)
|
|
120
|
+
|
|
121
|
+
if (options.id) {
|
|
122
|
+
return scrollRestorationCache?.state[restoreKey]?.[
|
|
123
|
+
`[${scrollRestorationIdAttribute}="${options.id}"]`
|
|
124
|
+
]
|
|
125
|
+
}
|
|
194
126
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// If there is no cached entry for the hash and there is no hash in the URL,
|
|
199
|
-
// we need to scroll to the top of the page for every scrollToTop element
|
|
200
|
-
const scrollOptions = { top: 0, left: 0, behavior }
|
|
201
|
-
window.scrollTo(scrollOptions)
|
|
202
|
-
if (scrollToTopSelectors) {
|
|
203
|
-
for (const selector of scrollToTopSelectors) {
|
|
204
|
-
if (selector === 'window') continue
|
|
205
|
-
const element =
|
|
206
|
-
typeof selector === 'function'
|
|
207
|
-
? selector()
|
|
208
|
-
: document.querySelector(selector)
|
|
209
|
-
if (element) element.scrollTo(scrollOptions)
|
|
210
|
-
}
|
|
211
|
-
}
|
|
127
|
+
const element = options.getElement?.()
|
|
128
|
+
if (!element) {
|
|
129
|
+
return
|
|
212
130
|
}
|
|
213
131
|
|
|
214
|
-
|
|
215
|
-
|
|
132
|
+
return scrollRestorationCache?.state[restoreKey]?.[
|
|
133
|
+
element instanceof Window ? windowScrollTarget : getCssSelector(element)
|
|
134
|
+
]
|
|
216
135
|
}
|
|
217
136
|
|
|
218
|
-
|
|
219
|
-
|
|
137
|
+
let ignoreScroll = false
|
|
138
|
+
const windowScrollTarget = 'window'
|
|
139
|
+
const scrollRestorationIdAttribute = 'data-scroll-restoration-id'
|
|
140
|
+
type ScrollTarget = typeof windowScrollTarget | Element
|
|
141
|
+
|
|
220
142
|
export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
221
143
|
if (!scrollRestorationCache && !(isServer ?? router.isServer)) {
|
|
222
144
|
return
|
|
223
145
|
}
|
|
146
|
+
|
|
147
|
+
const cache = scrollRestorationCache
|
|
148
|
+
|
|
224
149
|
const shouldScrollRestoration =
|
|
225
150
|
force ?? router.options.scrollRestoration ?? false
|
|
226
151
|
|
|
@@ -231,173 +156,202 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
231
156
|
if (
|
|
232
157
|
(isServer ?? router.isServer) ||
|
|
233
158
|
router.isScrollRestorationSetup ||
|
|
234
|
-
!
|
|
159
|
+
!cache
|
|
235
160
|
) {
|
|
236
161
|
return
|
|
237
162
|
}
|
|
238
163
|
|
|
239
164
|
router.isScrollRestorationSetup = true
|
|
240
|
-
|
|
241
|
-
//
|
|
242
165
|
ignoreScroll = false
|
|
243
166
|
|
|
244
167
|
const getKey =
|
|
245
168
|
router.options.getScrollRestorationKey || defaultGetScrollRestorationKey
|
|
169
|
+
const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()
|
|
246
170
|
|
|
247
171
|
window.history.scrollRestoration = 'manual'
|
|
248
172
|
|
|
249
|
-
// // Create a MutationObserver to monitor DOM changes
|
|
250
|
-
// const mutationObserver = new MutationObserver(() => {
|
|
251
|
-
// ;ignoreScroll = true
|
|
252
|
-
// requestAnimationFrame(() => {
|
|
253
|
-
// ;ignoreScroll = false
|
|
254
|
-
|
|
255
|
-
// // Attempt to restore scroll position on each dom
|
|
256
|
-
// // mutation until the user scrolls. We do this
|
|
257
|
-
// // because dynamic content may come in at different
|
|
258
|
-
// // ticks after the initial render and we want to
|
|
259
|
-
// // keep up with that content as much as possible.
|
|
260
|
-
// // As soon as the user scrolls, we no longer need
|
|
261
|
-
// // to attempt router.
|
|
262
|
-
// // console.log('mutation observer restoreScroll')
|
|
263
|
-
// restoreScroll(
|
|
264
|
-
// storageKey,
|
|
265
|
-
// getKey(router.stores.location.state),
|
|
266
|
-
// router.options.scrollRestorationBehavior,
|
|
267
|
-
// )
|
|
268
|
-
// })
|
|
269
|
-
// })
|
|
270
|
-
|
|
271
|
-
// const observeDom = () => {
|
|
272
|
-
// // Observe changes to the entire document
|
|
273
|
-
// mutationObserver.observe(document, {
|
|
274
|
-
// childList: true, // Detect added or removed child nodes
|
|
275
|
-
// subtree: true, // Monitor all descendants
|
|
276
|
-
// characterData: true, // Detect text content changes
|
|
277
|
-
// })
|
|
278
|
-
// }
|
|
279
|
-
|
|
280
|
-
// const unobserveDom = () => {
|
|
281
|
-
// mutationObserver.disconnect()
|
|
282
|
-
// }
|
|
283
|
-
|
|
284
|
-
// observeDom()
|
|
285
|
-
|
|
286
173
|
const onScroll = (event: Event) => {
|
|
287
|
-
// unobserveDom()
|
|
288
|
-
|
|
289
174
|
if (ignoreScroll || !router.isScrollRestoring) {
|
|
290
175
|
return
|
|
291
176
|
}
|
|
292
177
|
|
|
293
|
-
let elementSelector = ''
|
|
294
|
-
|
|
295
178
|
if (event.target === document || event.target === window) {
|
|
296
|
-
|
|
179
|
+
trackedScrollEntries.set(windowScrollTarget, {
|
|
180
|
+
scrollX: window.scrollX || 0,
|
|
181
|
+
scrollY: window.scrollY || 0,
|
|
182
|
+
})
|
|
297
183
|
} else {
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
elementSelector = `[data-scroll-restoration-id="${attrId}"]`
|
|
304
|
-
} else {
|
|
305
|
-
elementSelector = getCssSelector(event.target)
|
|
306
|
-
}
|
|
184
|
+
const target = event.target as Element
|
|
185
|
+
trackedScrollEntries.set(target, {
|
|
186
|
+
scrollX: target.scrollLeft || 0,
|
|
187
|
+
scrollY: target.scrollTop || 0,
|
|
188
|
+
})
|
|
307
189
|
}
|
|
190
|
+
}
|
|
308
191
|
|
|
309
|
-
|
|
192
|
+
// Snapshot the current page's tracked scroll targets before navigation or unload.
|
|
193
|
+
const snapshotCurrentScrollTargets = (restoreKey?: string) => {
|
|
194
|
+
if (
|
|
195
|
+
!router.isScrollRestoring ||
|
|
196
|
+
!restoreKey ||
|
|
197
|
+
trackedScrollEntries.size === 0 ||
|
|
198
|
+
!cache
|
|
199
|
+
) {
|
|
200
|
+
return
|
|
201
|
+
}
|
|
310
202
|
|
|
311
|
-
|
|
312
|
-
|
|
203
|
+
const keyEntry = (cache.state[restoreKey] ||=
|
|
204
|
+
{} as ScrollRestorationByElement)
|
|
313
205
|
|
|
314
|
-
|
|
315
|
-
|
|
206
|
+
for (const [target, position] of trackedScrollEntries) {
|
|
207
|
+
let selector: string | undefined
|
|
316
208
|
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
elementEntry.scrollY = element.scrollTop || 0
|
|
325
|
-
}
|
|
209
|
+
if (target === windowScrollTarget) {
|
|
210
|
+
selector = windowScrollTarget
|
|
211
|
+
} else if (target.isConnected) {
|
|
212
|
+
const attrId = target.getAttribute(scrollRestorationIdAttribute)
|
|
213
|
+
selector = attrId
|
|
214
|
+
? `[${scrollRestorationIdAttribute}="${attrId}"]`
|
|
215
|
+
: getCssSelector(target)
|
|
326
216
|
}
|
|
327
217
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
218
|
+
if (!selector) {
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
331
221
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
document.addEventListener('scroll', throttle(onScroll, 100), true)
|
|
222
|
+
keyEntry[selector] = position
|
|
223
|
+
}
|
|
335
224
|
}
|
|
336
225
|
|
|
337
|
-
|
|
338
|
-
|
|
226
|
+
document.addEventListener('scroll', onScroll, true)
|
|
227
|
+
router.subscribe('onBeforeLoad', (event) => {
|
|
228
|
+
snapshotCurrentScrollTargets(
|
|
229
|
+
event.fromLocation ? getKey(event.fromLocation) : undefined,
|
|
230
|
+
)
|
|
231
|
+
trackedScrollEntries.clear()
|
|
232
|
+
})
|
|
233
|
+
window.addEventListener('pagehide', () => {
|
|
234
|
+
snapshotCurrentScrollTargets(
|
|
235
|
+
getKey(
|
|
236
|
+
router.stores.resolvedLocation.state ?? router.stores.location.state,
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
cache.persist()
|
|
240
|
+
})
|
|
339
241
|
|
|
242
|
+
// Restore destination scroll after the new route has rendered.
|
|
243
|
+
router.subscribe('onRendered', (event) => {
|
|
340
244
|
const cacheKey = getKey(event.toLocation)
|
|
245
|
+
const behavior = router.options.scrollRestorationBehavior
|
|
246
|
+
const scrollToTopSelectors = router.options.scrollToTopSelectors
|
|
247
|
+
trackedScrollEntries.clear()
|
|
341
248
|
|
|
342
|
-
// If the user doesn't want to restore the scroll position,
|
|
343
|
-
// we don't need to do anything.
|
|
344
249
|
if (!router.resetNextScroll) {
|
|
345
250
|
router.resetNextScroll = true
|
|
346
251
|
return
|
|
347
252
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
253
|
+
|
|
254
|
+
if (
|
|
255
|
+
typeof router.options.scrollRestoration === 'function' &&
|
|
256
|
+
!router.options.scrollRestoration({ location: router.latestLocation })
|
|
257
|
+
) {
|
|
258
|
+
return
|
|
355
259
|
}
|
|
356
260
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
261
|
+
ignoreScroll = true
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const elementEntries = router.isScrollRestoring
|
|
265
|
+
? cache.state[cacheKey]
|
|
266
|
+
: undefined
|
|
267
|
+
let restored = false
|
|
268
|
+
|
|
269
|
+
if (elementEntries) {
|
|
270
|
+
for (const elementSelector in elementEntries) {
|
|
271
|
+
const entry = elementEntries[elementSelector]
|
|
272
|
+
|
|
273
|
+
if (!isPlainObject(entry)) {
|
|
274
|
+
continue
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const { scrollX, scrollY } = entry as {
|
|
278
|
+
scrollX?: unknown
|
|
279
|
+
scrollY?: unknown
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {
|
|
283
|
+
continue
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (elementSelector === windowScrollTarget) {
|
|
287
|
+
window.scrollTo({
|
|
288
|
+
top: scrollY as number,
|
|
289
|
+
left: scrollX as number,
|
|
290
|
+
behavior,
|
|
291
|
+
})
|
|
292
|
+
restored = true
|
|
293
|
+
} else if (elementSelector) {
|
|
294
|
+
let element
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
element = document.querySelector(elementSelector)
|
|
298
|
+
} catch {
|
|
299
|
+
continue
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (element) {
|
|
303
|
+
element.scrollLeft = scrollX as number
|
|
304
|
+
element.scrollTop = scrollY as number
|
|
305
|
+
restored = true
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (!restored) {
|
|
312
|
+
const hash = router.history.location.hash.slice(1)
|
|
313
|
+
|
|
314
|
+
if (hash) {
|
|
315
|
+
const hashScrollIntoViewOptions =
|
|
316
|
+
window.history.state?.__hashScrollIntoViewOptions ?? true
|
|
317
|
+
|
|
318
|
+
if (hashScrollIntoViewOptions) {
|
|
319
|
+
const el = document.getElementById(hash)
|
|
320
|
+
if (el) {
|
|
321
|
+
el.scrollIntoView(hashScrollIntoViewOptions)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
const scrollOptions = {
|
|
326
|
+
top: 0,
|
|
327
|
+
left: 0,
|
|
328
|
+
behavior,
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
window.scrollTo(scrollOptions)
|
|
332
|
+
if (scrollToTopSelectors) {
|
|
333
|
+
for (const selector of scrollToTopSelectors) {
|
|
334
|
+
if (selector === windowScrollTarget) continue
|
|
335
|
+
const element =
|
|
336
|
+
typeof selector === 'function'
|
|
337
|
+
? selector()
|
|
338
|
+
: document.querySelector(selector)
|
|
339
|
+
if (element) {
|
|
340
|
+
element.scrollTo(scrollOptions)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} finally {
|
|
347
|
+
ignoreScroll = false
|
|
348
|
+
}
|
|
365
349
|
|
|
366
350
|
if (router.isScrollRestoring) {
|
|
367
|
-
|
|
368
|
-
scrollRestorationCache.set((state) => {
|
|
351
|
+
cache.set((state) => {
|
|
369
352
|
state[cacheKey] ||= {} as ScrollRestorationByElement
|
|
370
|
-
|
|
371
353
|
return state
|
|
372
354
|
})
|
|
373
355
|
}
|
|
374
356
|
})
|
|
375
357
|
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* @private
|
|
379
|
-
* Handles hash-based scrolling after navigation completes.
|
|
380
|
-
* To be used in framework-specific <Transitioner> components during the onResolved event.
|
|
381
|
-
*
|
|
382
|
-
* Provides hash scrolling for programmatic navigation when default browser handling is prevented.
|
|
383
|
-
* @param router The router instance containing current location and state
|
|
384
|
-
*/
|
|
385
|
-
/**
|
|
386
|
-
* @private
|
|
387
|
-
* Handles hash-based scrolling after navigation completes.
|
|
388
|
-
* To be used in framework-specific Transitioners.
|
|
389
|
-
*/
|
|
390
|
-
export function handleHashScroll(router: AnyRouter) {
|
|
391
|
-
if (typeof document !== 'undefined' && (document as any).querySelector) {
|
|
392
|
-
const location = router.stores.location.state
|
|
393
|
-
const hashScrollIntoViewOptions =
|
|
394
|
-
location.state.__hashScrollIntoViewOptions ?? true
|
|
395
|
-
|
|
396
|
-
if (hashScrollIntoViewOptions && location.hash !== '') {
|
|
397
|
-
const el = document.getElementById(location.hash)
|
|
398
|
-
if (el) {
|
|
399
|
-
el.scrollIntoView(hashScrollIntoViewOptions)
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|