@tanstack/router-core 1.171.3 → 1.171.5
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/index.cjs +0 -3
- package/dist/cjs/index.d.cts +1 -2
- package/dist/cjs/rewrite.cjs +1 -6
- package/dist/cjs/rewrite.cjs.map +1 -1
- package/dist/cjs/rewrite.d.cts +0 -4
- package/dist/cjs/router.cjs +9 -2
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +5 -1
- package/dist/cjs/scroll-restoration-inline.cjs +1 -1
- package/dist/cjs/scroll-restoration-inline.cjs.map +1 -1
- package/dist/cjs/scroll-restoration-inline.d.cts +1 -6
- package/dist/cjs/scroll-restoration-script/server.cjs +5 -12
- package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.cjs +117 -111
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.d.cts +0 -10
- package/dist/esm/index.d.ts +1 -2
- package/dist/esm/index.js +2 -3
- package/dist/esm/rewrite.d.ts +0 -4
- package/dist/esm/rewrite.js +1 -6
- package/dist/esm/rewrite.js.map +1 -1
- package/dist/esm/router.d.ts +5 -1
- package/dist/esm/router.js +9 -3
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration-inline.d.ts +1 -6
- package/dist/esm/scroll-restoration-inline.js +1 -1
- package/dist/esm/scroll-restoration-inline.js.map +1 -1
- package/dist/esm/scroll-restoration-script/server.js +5 -12
- package/dist/esm/scroll-restoration-script/server.js.map +1 -1
- package/dist/esm/scroll-restoration.d.ts +0 -10
- package/dist/esm/scroll-restoration.js +118 -111
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +0 -3
- package/src/rewrite.ts +1 -8
- package/src/router.ts +28 -4
- package/src/scroll-restoration-inline.ts +23 -51
- package/src/scroll-restoration-script/server.ts +5 -26
- package/src/scroll-restoration.ts +190 -174
- package/dist/cjs/hash-scroll.cjs +0 -20
- package/dist/cjs/hash-scroll.cjs.map +0 -1
- package/dist/cjs/hash-scroll.d.cts +0 -7
- package/dist/esm/hash-scroll.d.ts +0 -7
- package/dist/esm/hash-scroll.js +0 -20
- package/dist/esm/hash-scroll.js.map +0 -1
- package/src/hash-scroll.ts +0 -21
|
@@ -1,71 +1,43 @@
|
|
|
1
|
-
export default function (
|
|
2
|
-
storageKey: string
|
|
3
|
-
key?: string
|
|
4
|
-
behavior?: ScrollToOptions['behavior']
|
|
5
|
-
shouldScrollRestoration?: boolean
|
|
6
|
-
}) {
|
|
1
|
+
export default function (storageKey: string, key?: string) {
|
|
7
2
|
let byKey
|
|
8
3
|
|
|
9
4
|
try {
|
|
10
|
-
byKey = JSON.parse(sessionStorage.getItem(
|
|
11
|
-
} catch
|
|
12
|
-
console.error(error)
|
|
5
|
+
byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')
|
|
6
|
+
} catch {
|
|
13
7
|
return
|
|
14
8
|
}
|
|
15
9
|
|
|
16
|
-
const
|
|
17
|
-
|
|
10
|
+
const elementEntries = byKey?.[key || history.state?.__TSR_key]
|
|
11
|
+
let windowRestored = false
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Object.keys(elementEntries).length > 0
|
|
24
|
-
) {
|
|
25
|
-
for (const elementSelector in elementEntries) {
|
|
26
|
-
const entry = elementEntries[elementSelector]
|
|
27
|
-
|
|
28
|
-
if (!entry || typeof entry !== 'object') {
|
|
29
|
-
continue
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const scrollX = entry.scrollX
|
|
33
|
-
const scrollY = entry.scrollY
|
|
34
|
-
|
|
35
|
-
if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {
|
|
36
|
-
continue
|
|
37
|
-
}
|
|
13
|
+
for (const elementSelector in elementEntries) {
|
|
14
|
+
const entry = elementEntries[elementSelector]
|
|
15
|
+
const scrollX = entry?.scrollX
|
|
16
|
+
const scrollY = entry?.scrollY
|
|
38
17
|
|
|
18
|
+
if (Number.isFinite(scrollX) && Number.isFinite(scrollY)) {
|
|
39
19
|
if (elementSelector === 'window') {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
left: scrollX,
|
|
43
|
-
behavior: options.behavior,
|
|
44
|
-
})
|
|
20
|
+
scrollTo(scrollX, scrollY)
|
|
21
|
+
windowRestored = true
|
|
45
22
|
} else if (elementSelector) {
|
|
46
|
-
let element
|
|
47
|
-
|
|
48
23
|
try {
|
|
49
|
-
element = document.querySelector(elementSelector)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
element.scrollLeft = scrollX
|
|
56
|
-
element.scrollTop = scrollY
|
|
57
|
-
}
|
|
24
|
+
const element = document.querySelector(elementSelector)
|
|
25
|
+
if (element) {
|
|
26
|
+
element.scrollLeft = scrollX
|
|
27
|
+
element.scrollTop = scrollY
|
|
28
|
+
}
|
|
29
|
+
} catch {}
|
|
58
30
|
}
|
|
59
31
|
}
|
|
60
|
-
|
|
61
|
-
return
|
|
62
32
|
}
|
|
63
33
|
|
|
64
|
-
|
|
34
|
+
if (windowRestored) return
|
|
35
|
+
|
|
36
|
+
const hash = location.hash.slice(1)
|
|
65
37
|
|
|
66
38
|
if (hash) {
|
|
67
39
|
const hashScrollIntoViewOptions =
|
|
68
|
-
|
|
40
|
+
history.state?.__hashScrollIntoViewOptions ?? true
|
|
69
41
|
|
|
70
42
|
if (hashScrollIntoViewOptions) {
|
|
71
43
|
const el = document.getElementById(hash)
|
|
@@ -77,5 +49,5 @@ export default function (options: {
|
|
|
77
49
|
return
|
|
78
50
|
}
|
|
79
51
|
|
|
80
|
-
|
|
52
|
+
scrollTo(0, 0)
|
|
81
53
|
}
|
|
@@ -6,33 +6,16 @@ import {
|
|
|
6
6
|
import { escapeHtml } from '../utils'
|
|
7
7
|
import type { AnyRouter } from '../router'
|
|
8
8
|
|
|
9
|
-
type InlineScrollRestorationScriptOptions = {
|
|
10
|
-
storageKey: string
|
|
11
|
-
key?: string
|
|
12
|
-
behavior?: ScrollToOptions['behavior']
|
|
13
|
-
shouldScrollRestoration?: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
9
|
const defaultInlineScrollRestorationScript = `(${minifiedScrollRestorationScript})(${escapeHtml(
|
|
17
|
-
JSON.stringify(
|
|
18
|
-
storageKey,
|
|
19
|
-
shouldScrollRestoration: true,
|
|
20
|
-
} satisfies InlineScrollRestorationScriptOptions),
|
|
10
|
+
JSON.stringify(storageKey),
|
|
21
11
|
)})`
|
|
22
12
|
|
|
23
|
-
function getScrollRestorationScript(
|
|
24
|
-
|
|
25
|
-
) {
|
|
26
|
-
if (
|
|
27
|
-
options.storageKey === storageKey &&
|
|
28
|
-
options.shouldScrollRestoration === true &&
|
|
29
|
-
options.key === undefined &&
|
|
30
|
-
options.behavior === undefined
|
|
31
|
-
) {
|
|
13
|
+
function getScrollRestorationScript(key?: string) {
|
|
14
|
+
if (key === undefined) {
|
|
32
15
|
return defaultInlineScrollRestorationScript
|
|
33
16
|
}
|
|
34
17
|
|
|
35
|
-
return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(
|
|
18
|
+
return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(storageKey))},${escapeHtml(JSON.stringify(key))})`
|
|
36
19
|
}
|
|
37
20
|
|
|
38
21
|
export function getScrollRestorationScriptForRouter(router: AnyRouter) {
|
|
@@ -56,9 +39,5 @@ export function getScrollRestorationScriptForRouter(router: AnyRouter) {
|
|
|
56
39
|
return defaultInlineScrollRestorationScript
|
|
57
40
|
}
|
|
58
41
|
|
|
59
|
-
return getScrollRestorationScript(
|
|
60
|
-
storageKey,
|
|
61
|
-
shouldScrollRestoration: true,
|
|
62
|
-
key: userKey,
|
|
63
|
-
})
|
|
42
|
+
return getScrollRestorationScript(userKey)
|
|
64
43
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { isServer } from '@tanstack/router-core/isServer'
|
|
2
|
-
import {
|
|
2
|
+
import { locationHistoryActions } from './router'
|
|
3
3
|
import type { AnyRouter } from './router'
|
|
4
4
|
import type { ParsedLocation } from './location'
|
|
5
|
-
import type { NonNullableUpdater } from './utils'
|
|
6
5
|
|
|
7
6
|
export type ScrollRestorationEntry = { scrollX: number; scrollY: number }
|
|
8
7
|
|
|
@@ -10,12 +9,6 @@ type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>
|
|
|
10
9
|
|
|
11
10
|
type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>
|
|
12
11
|
|
|
13
|
-
type ScrollRestorationCache = {
|
|
14
|
-
readonly state: ScrollRestorationByKey
|
|
15
|
-
set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void
|
|
16
|
-
persist: () => void
|
|
17
|
-
}
|
|
18
|
-
|
|
19
12
|
export type ScrollRestorationOptions = {
|
|
20
13
|
getKey?: (location: ParsedLocation) => string
|
|
21
14
|
scrollBehavior?: ScrollToOptions['behavior']
|
|
@@ -23,60 +16,46 @@ export type ScrollRestorationOptions = {
|
|
|
23
16
|
|
|
24
17
|
function getSafeSessionStorage() {
|
|
25
18
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
: undefined
|
|
19
|
+
// Accessing sessionStorage itself can throw SecurityError in locked-down
|
|
20
|
+
// contexts, e.g. sandboxed/opaque origins or blocked storage policies.
|
|
21
|
+
return sessionStorage
|
|
30
22
|
} catch {
|
|
31
|
-
|
|
32
|
-
return undefined
|
|
23
|
+
return
|
|
33
24
|
}
|
|
34
25
|
}
|
|
35
26
|
|
|
36
27
|
// SessionStorage key used to store scroll positions across navigations.
|
|
37
28
|
export const storageKey = 'tsr-scroll-restoration-v1_3'
|
|
29
|
+
const safeSessionStorage = getSafeSessionStorage()
|
|
38
30
|
|
|
39
|
-
function createScrollRestorationCache()
|
|
40
|
-
const safeSessionStorage = getSafeSessionStorage()
|
|
41
|
-
if (!safeSessionStorage) {
|
|
42
|
-
return null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let state: ScrollRestorationByKey = {}
|
|
46
|
-
|
|
31
|
+
function createScrollRestorationCache() {
|
|
47
32
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
33
|
+
return JSON.parse(
|
|
34
|
+
safeSessionStorage?.getItem(storageKey) || '{}',
|
|
35
|
+
) as ScrollRestorationByKey
|
|
52
36
|
} catch {
|
|
53
37
|
// ignore invalid session storage payloads
|
|
38
|
+
return {}
|
|
54
39
|
}
|
|
40
|
+
}
|
|
55
41
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
42
|
+
function persistScrollRestorationCache() {
|
|
43
|
+
try {
|
|
44
|
+
safeSessionStorage?.setItem(
|
|
45
|
+
storageKey,
|
|
46
|
+
JSON.stringify(scrollRestorationCache),
|
|
47
|
+
)
|
|
48
|
+
} catch {
|
|
49
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
50
|
+
console.warn(
|
|
51
|
+
'[ts-router] Could not persist scroll restoration state to sessionStorage.',
|
|
52
|
+
)
|
|
65
53
|
}
|
|
66
54
|
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
get state() {
|
|
70
|
-
return state
|
|
71
|
-
},
|
|
72
|
-
set: (updater) => {
|
|
73
|
-
state = functionalUpdate(updater, state) || state
|
|
74
|
-
},
|
|
75
|
-
persist,
|
|
76
|
-
}
|
|
77
55
|
}
|
|
78
56
|
|
|
79
|
-
|
|
57
|
+
const scrollRestorationCache = /* @__PURE__ */ createScrollRestorationCache()
|
|
58
|
+
const scrollRestorationIdAttribute = 'data-scroll-restoration-id'
|
|
80
59
|
|
|
81
60
|
/**
|
|
82
61
|
* The default `getKey` function for `useScrollRestoration`.
|
|
@@ -88,16 +67,29 @@ export const defaultGetScrollRestorationKey = (location: ParsedLocation) => {
|
|
|
88
67
|
return location.state.__TSR_key! || location.href
|
|
89
68
|
}
|
|
90
69
|
|
|
91
|
-
function
|
|
92
|
-
const
|
|
70
|
+
function getScrollRestorationSelector(element: Element): string {
|
|
71
|
+
const attrId = element.getAttribute(scrollRestorationIdAttribute)
|
|
72
|
+
if (attrId) {
|
|
73
|
+
return `[${scrollRestorationIdAttribute}="${attrId}"]`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let selector = ''
|
|
77
|
+
let el: any = element
|
|
93
78
|
let parent: HTMLElement
|
|
79
|
+
|
|
94
80
|
while ((parent = el.parentNode)) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
81
|
+
let index = 1
|
|
82
|
+
let sibling = el
|
|
83
|
+
while ((sibling = sibling.previousElementSibling)) {
|
|
84
|
+
index++
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const part = `${el.localName}:nth-child(${index})`
|
|
88
|
+
selector = selector ? `${part} > ${selector}` : part
|
|
98
89
|
el = parent
|
|
99
90
|
}
|
|
100
|
-
|
|
91
|
+
|
|
92
|
+
return selector
|
|
101
93
|
}
|
|
102
94
|
|
|
103
95
|
export function getElementScrollRestorationEntry(
|
|
@@ -117,11 +109,14 @@ export function getElementScrollRestorationEntry(
|
|
|
117
109
|
): ScrollRestorationEntry | undefined {
|
|
118
110
|
const getKey = options.getKey || defaultGetScrollRestorationKey
|
|
119
111
|
const restoreKey = getKey(router.latestLocation)
|
|
112
|
+
const entries = scrollRestorationCache[restoreKey]
|
|
113
|
+
|
|
114
|
+
if (!entries) {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
120
117
|
|
|
121
118
|
if (options.id) {
|
|
122
|
-
return
|
|
123
|
-
`[${scrollRestorationIdAttribute}="${options.id}"]`
|
|
124
|
-
]
|
|
119
|
+
return entries[`[${scrollRestorationIdAttribute}="${options.id}"]`]
|
|
125
120
|
}
|
|
126
121
|
|
|
127
122
|
const element = options.getElement?.()
|
|
@@ -129,35 +124,55 @@ export function getElementScrollRestorationEntry(
|
|
|
129
124
|
return
|
|
130
125
|
}
|
|
131
126
|
|
|
132
|
-
return
|
|
133
|
-
element
|
|
127
|
+
return entries[
|
|
128
|
+
element === window
|
|
129
|
+
? windowScrollTarget
|
|
130
|
+
: getScrollRestorationSelector(element as Element)
|
|
134
131
|
]
|
|
135
132
|
}
|
|
136
133
|
|
|
137
134
|
let ignoreScroll = false
|
|
138
135
|
const windowScrollTarget = 'window'
|
|
139
|
-
const scrollRestorationIdAttribute = 'data-scroll-restoration-id'
|
|
140
136
|
type ScrollTarget = typeof windowScrollTarget | Element
|
|
141
137
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return
|
|
138
|
+
function getElement(selector: string | (() => Element | null | undefined)) {
|
|
139
|
+
try {
|
|
140
|
+
return typeof selector === 'function'
|
|
141
|
+
? selector()
|
|
142
|
+
: document.querySelector(selector)
|
|
143
|
+
} catch {}
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getScrollToTopElements(
|
|
148
|
+
scrollToTopSelectors: NonNullable<
|
|
149
|
+
AnyRouter['options']['scrollToTopSelectors']
|
|
150
|
+
>,
|
|
151
|
+
): Array<Element> {
|
|
152
|
+
const elements: Array<Element> = []
|
|
153
|
+
|
|
154
|
+
for (const selector of scrollToTopSelectors) {
|
|
155
|
+
if (selector === windowScrollTarget) {
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const element = getElement(selector)
|
|
160
|
+
if (element) {
|
|
161
|
+
elements.push(element)
|
|
162
|
+
}
|
|
145
163
|
}
|
|
146
164
|
|
|
147
|
-
|
|
165
|
+
return elements
|
|
166
|
+
}
|
|
148
167
|
|
|
149
|
-
|
|
150
|
-
|
|
168
|
+
export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
169
|
+
// Keep hash/top scrolling active even when sessionStorage is unavailable.
|
|
151
170
|
|
|
152
|
-
if (
|
|
171
|
+
if (force ?? router.options.scrollRestoration) {
|
|
153
172
|
router.isScrollRestoring = true
|
|
154
173
|
}
|
|
155
174
|
|
|
156
|
-
if (
|
|
157
|
-
(isServer ?? router.isServer) ||
|
|
158
|
-
router.isScrollRestorationSetup ||
|
|
159
|
-
!cache
|
|
160
|
-
) {
|
|
175
|
+
if ((isServer ?? router.isServer) || router.isScrollRestorationSetup) {
|
|
161
176
|
return
|
|
162
177
|
}
|
|
163
178
|
|
|
@@ -167,88 +182,77 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
167
182
|
const getKey =
|
|
168
183
|
router.options.getScrollRestorationKey || defaultGetScrollRestorationKey
|
|
169
184
|
const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()
|
|
185
|
+
const setTrackedScrollEntry = (
|
|
186
|
+
target: ScrollTarget,
|
|
187
|
+
scrollX: number,
|
|
188
|
+
scrollY: number,
|
|
189
|
+
) => {
|
|
190
|
+
const entry =
|
|
191
|
+
trackedScrollEntries.get(target) || ({} as ScrollRestorationEntry)
|
|
192
|
+
entry.scrollX = scrollX
|
|
193
|
+
entry.scrollY = scrollY
|
|
194
|
+
trackedScrollEntries.set(target, entry)
|
|
195
|
+
}
|
|
170
196
|
|
|
171
|
-
|
|
197
|
+
history.scrollRestoration = 'manual'
|
|
172
198
|
|
|
173
199
|
const onScroll = (event: Event) => {
|
|
174
200
|
if (ignoreScroll || !router.isScrollRestoring) {
|
|
175
201
|
return
|
|
176
202
|
}
|
|
177
203
|
|
|
178
|
-
if (event.target === document
|
|
179
|
-
|
|
180
|
-
scrollX: window.scrollX || 0,
|
|
181
|
-
scrollY: window.scrollY || 0,
|
|
182
|
-
})
|
|
204
|
+
if (event.target === document) {
|
|
205
|
+
setTrackedScrollEntry(windowScrollTarget, scrollX, scrollY)
|
|
183
206
|
} else {
|
|
184
207
|
const target = event.target as Element
|
|
185
|
-
|
|
186
|
-
scrollX: target.scrollLeft || 0,
|
|
187
|
-
scrollY: target.scrollTop || 0,
|
|
188
|
-
})
|
|
208
|
+
setTrackedScrollEntry(target, target.scrollLeft, target.scrollTop)
|
|
189
209
|
}
|
|
190
210
|
}
|
|
191
211
|
|
|
192
212
|
// Snapshot the current page's tracked scroll targets before navigation or unload.
|
|
193
|
-
const snapshotCurrentScrollTargets = (restoreKey
|
|
194
|
-
if (
|
|
195
|
-
!router.isScrollRestoring ||
|
|
196
|
-
!restoreKey ||
|
|
197
|
-
trackedScrollEntries.size === 0 ||
|
|
198
|
-
!cache
|
|
199
|
-
) {
|
|
213
|
+
const snapshotCurrentScrollTargets = (restoreKey: string) => {
|
|
214
|
+
if (!router.isScrollRestoring) {
|
|
200
215
|
return
|
|
201
216
|
}
|
|
202
217
|
|
|
203
|
-
const keyEntry = (
|
|
218
|
+
const keyEntry = (scrollRestorationCache[restoreKey] ||=
|
|
204
219
|
{} as ScrollRestorationByElement)
|
|
205
220
|
|
|
206
221
|
for (const [target, position] of trackedScrollEntries) {
|
|
207
|
-
let selector: string | undefined
|
|
208
|
-
|
|
209
222
|
if (target === windowScrollTarget) {
|
|
210
|
-
|
|
223
|
+
keyEntry[windowScrollTarget] = position
|
|
211
224
|
} else if (target.isConnected) {
|
|
212
|
-
|
|
213
|
-
selector = attrId
|
|
214
|
-
? `[${scrollRestorationIdAttribute}="${attrId}"]`
|
|
215
|
-
: getCssSelector(target)
|
|
225
|
+
keyEntry[getScrollRestorationSelector(target)] = position
|
|
216
226
|
}
|
|
217
|
-
|
|
218
|
-
if (!selector) {
|
|
219
|
-
continue
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
keyEntry[selector] = position
|
|
223
227
|
}
|
|
224
228
|
}
|
|
225
229
|
|
|
226
230
|
document.addEventListener('scroll', onScroll, true)
|
|
227
231
|
router.subscribe('onBeforeLoad', (event) => {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
232
|
+
if (event.fromLocation) {
|
|
233
|
+
snapshotCurrentScrollTargets(getKey(event.fromLocation))
|
|
234
|
+
}
|
|
231
235
|
trackedScrollEntries.clear()
|
|
232
236
|
})
|
|
233
|
-
|
|
237
|
+
addEventListener('pagehide', () => {
|
|
234
238
|
snapshotCurrentScrollTargets(
|
|
235
239
|
getKey(
|
|
236
240
|
router.stores.resolvedLocation.get() ?? router.stores.location.get(),
|
|
237
241
|
),
|
|
238
242
|
)
|
|
239
|
-
|
|
243
|
+
persistScrollRestorationCache()
|
|
240
244
|
})
|
|
241
245
|
|
|
242
246
|
// Restore destination scroll after the new route has rendered.
|
|
243
247
|
router.subscribe('onRendered', (event) => {
|
|
244
|
-
const cacheKey = getKey(event.toLocation)
|
|
245
248
|
const behavior = router.options.scrollRestorationBehavior
|
|
246
249
|
const scrollToTopSelectors = router.options.scrollToTopSelectors
|
|
250
|
+
const shouldResetScroll = router.resetNextScroll
|
|
251
|
+
let scrollToTopElements: Array<Element> | undefined
|
|
247
252
|
trackedScrollEntries.clear()
|
|
248
253
|
|
|
249
|
-
if (!
|
|
254
|
+
if (!shouldResetScroll) {
|
|
250
255
|
router.resetNextScroll = true
|
|
251
|
-
return
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
if (
|
|
@@ -258,100 +262,112 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
258
262
|
return
|
|
259
263
|
}
|
|
260
264
|
|
|
261
|
-
|
|
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
|
-
}
|
|
265
|
+
const cacheKey = getKey(event.toLocation)
|
|
266
|
+
const fromCacheKey = event.fromLocation && getKey(event.fromLocation)
|
|
276
267
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
scrollY?: unknown
|
|
280
|
-
}
|
|
268
|
+
if (router.isScrollRestoring && fromCacheKey && fromCacheKey !== cacheKey) {
|
|
269
|
+
const fromElementEntries = scrollRestorationCache[fromCacheKey]
|
|
281
270
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
271
|
+
if (fromElementEntries) {
|
|
272
|
+
let toElementEntries = scrollRestorationCache[cacheKey]
|
|
285
273
|
|
|
274
|
+
for (const elementSelector in fromElementEntries) {
|
|
286
275
|
if (elementSelector === windowScrollTarget) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
} else if (elementSelector) {
|
|
294
|
-
let element
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
element = document.querySelector(elementSelector)
|
|
298
|
-
} catch {
|
|
276
|
+
if (shouldResetScroll) {
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
const element = getElement(elementSelector)
|
|
281
|
+
if (!element) {
|
|
299
282
|
continue
|
|
300
283
|
}
|
|
301
284
|
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
285
|
+
if (shouldResetScroll && scrollToTopSelectors) {
|
|
286
|
+
scrollToTopElements ??=
|
|
287
|
+
getScrollToTopElements(scrollToTopSelectors)
|
|
288
|
+
if (scrollToTopElements.includes(element)) {
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
306
291
|
}
|
|
307
292
|
}
|
|
293
|
+
|
|
294
|
+
if (!toElementEntries) {
|
|
295
|
+
toElementEntries = scrollRestorationCache[cacheKey] =
|
|
296
|
+
{} as ScrollRestorationByElement
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
toElementEntries[elementSelector] ??=
|
|
300
|
+
fromElementEntries[elementSelector]!
|
|
308
301
|
}
|
|
309
302
|
}
|
|
303
|
+
}
|
|
310
304
|
|
|
311
|
-
|
|
312
|
-
const hash = router.history.location.hash.slice(1)
|
|
305
|
+
ignoreScroll = true
|
|
313
306
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
307
|
+
try {
|
|
308
|
+
const hash = event.toLocation.hash
|
|
309
|
+
const hashScrollIntoViewOptions =
|
|
310
|
+
event.toLocation.state.__hashScrollIntoViewOptions ?? true
|
|
311
|
+
let windowRestored = false
|
|
312
|
+
|
|
313
|
+
if (shouldResetScroll) {
|
|
314
|
+
const action = locationHistoryActions.get(event.toLocation)
|
|
315
|
+
const skipWindowRestore =
|
|
316
|
+
hash &&
|
|
317
|
+
hashScrollIntoViewOptions &&
|
|
318
|
+
(action === 'PUSH' || action === 'REPLACE')
|
|
319
|
+
|
|
320
|
+
const elementEntries = router.isScrollRestoring
|
|
321
|
+
? scrollRestorationCache[cacheKey]
|
|
322
|
+
: undefined
|
|
323
|
+
|
|
324
|
+
if (elementEntries) {
|
|
325
|
+
for (const elementSelector in elementEntries) {
|
|
326
|
+
const { scrollX, scrollY } = elementEntries[elementSelector]!
|
|
327
|
+
|
|
328
|
+
if (elementSelector === windowScrollTarget) {
|
|
329
|
+
if (skipWindowRestore) {
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
317
332
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
333
|
+
scrollTo({
|
|
334
|
+
top: scrollY,
|
|
335
|
+
left: scrollX,
|
|
336
|
+
behavior,
|
|
337
|
+
})
|
|
338
|
+
windowRestored = true
|
|
339
|
+
} else {
|
|
340
|
+
const element = getElement(elementSelector)
|
|
341
|
+
if (element) {
|
|
342
|
+
element.scrollLeft = scrollX
|
|
343
|
+
element.scrollTop = scrollY
|
|
344
|
+
}
|
|
322
345
|
}
|
|
323
346
|
}
|
|
324
|
-
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!windowRestored && !hash) {
|
|
325
350
|
const scrollOptions = {
|
|
326
351
|
top: 0,
|
|
327
352
|
left: 0,
|
|
328
353
|
behavior,
|
|
329
354
|
}
|
|
330
355
|
|
|
331
|
-
|
|
356
|
+
scrollTo(scrollOptions)
|
|
332
357
|
if (scrollToTopSelectors) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
typeof selector === 'function'
|
|
337
|
-
? selector()
|
|
338
|
-
: document.querySelector(selector)
|
|
339
|
-
if (element) {
|
|
340
|
-
element.scrollTo(scrollOptions)
|
|
341
|
-
}
|
|
358
|
+
scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors)
|
|
359
|
+
for (const element of scrollToTopElements) {
|
|
360
|
+
element.scrollTo(scrollOptions)
|
|
342
361
|
}
|
|
343
362
|
}
|
|
344
363
|
}
|
|
345
364
|
}
|
|
365
|
+
|
|
366
|
+
if (!windowRestored && hash && hashScrollIntoViewOptions) {
|
|
367
|
+
document.getElementById(hash)?.scrollIntoView(hashScrollIntoViewOptions)
|
|
368
|
+
}
|
|
346
369
|
} finally {
|
|
347
370
|
ignoreScroll = false
|
|
348
371
|
}
|
|
349
|
-
|
|
350
|
-
if (router.isScrollRestoring) {
|
|
351
|
-
cache.set((state) => {
|
|
352
|
-
state[cacheKey] ||= {} as ScrollRestorationByElement
|
|
353
|
-
return state
|
|
354
|
-
})
|
|
355
|
-
}
|
|
356
372
|
})
|
|
357
373
|
}
|