@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.
Files changed (46) hide show
  1. package/dist/cjs/index.cjs +0 -3
  2. package/dist/cjs/index.d.cts +1 -2
  3. package/dist/cjs/rewrite.cjs +1 -6
  4. package/dist/cjs/rewrite.cjs.map +1 -1
  5. package/dist/cjs/rewrite.d.cts +0 -4
  6. package/dist/cjs/router.cjs +9 -2
  7. package/dist/cjs/router.cjs.map +1 -1
  8. package/dist/cjs/router.d.cts +5 -1
  9. package/dist/cjs/scroll-restoration-inline.cjs +1 -1
  10. package/dist/cjs/scroll-restoration-inline.cjs.map +1 -1
  11. package/dist/cjs/scroll-restoration-inline.d.cts +1 -6
  12. package/dist/cjs/scroll-restoration-script/server.cjs +5 -12
  13. package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
  14. package/dist/cjs/scroll-restoration.cjs +117 -111
  15. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  16. package/dist/cjs/scroll-restoration.d.cts +0 -10
  17. package/dist/esm/index.d.ts +1 -2
  18. package/dist/esm/index.js +2 -3
  19. package/dist/esm/rewrite.d.ts +0 -4
  20. package/dist/esm/rewrite.js +1 -6
  21. package/dist/esm/rewrite.js.map +1 -1
  22. package/dist/esm/router.d.ts +5 -1
  23. package/dist/esm/router.js +9 -3
  24. package/dist/esm/router.js.map +1 -1
  25. package/dist/esm/scroll-restoration-inline.d.ts +1 -6
  26. package/dist/esm/scroll-restoration-inline.js +1 -1
  27. package/dist/esm/scroll-restoration-inline.js.map +1 -1
  28. package/dist/esm/scroll-restoration-script/server.js +5 -12
  29. package/dist/esm/scroll-restoration-script/server.js.map +1 -1
  30. package/dist/esm/scroll-restoration.d.ts +0 -10
  31. package/dist/esm/scroll-restoration.js +118 -111
  32. package/dist/esm/scroll-restoration.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/index.ts +0 -3
  35. package/src/rewrite.ts +1 -8
  36. package/src/router.ts +28 -4
  37. package/src/scroll-restoration-inline.ts +23 -51
  38. package/src/scroll-restoration-script/server.ts +5 -26
  39. package/src/scroll-restoration.ts +190 -174
  40. package/dist/cjs/hash-scroll.cjs +0 -20
  41. package/dist/cjs/hash-scroll.cjs.map +0 -1
  42. package/dist/cjs/hash-scroll.d.cts +0 -7
  43. package/dist/esm/hash-scroll.d.ts +0 -7
  44. package/dist/esm/hash-scroll.js +0 -20
  45. package/dist/esm/hash-scroll.js.map +0 -1
  46. package/src/hash-scroll.ts +0 -21
@@ -1,71 +1,43 @@
1
- export default function (options: {
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(options.storageKey) || '{}')
11
- } catch (error) {
12
- console.error(error)
5
+ byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')
6
+ } catch {
13
7
  return
14
8
  }
15
9
 
16
- const resolvedKey = options.key || window.history.state?.__TSR_key
17
- const elementEntries = resolvedKey ? byKey[resolvedKey] : undefined
10
+ const elementEntries = byKey?.[key || history.state?.__TSR_key]
11
+ let windowRestored = false
18
12
 
19
- if (
20
- options.shouldScrollRestoration &&
21
- elementEntries &&
22
- typeof elementEntries === 'object' &&
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
- window.scrollTo({
41
- top: scrollY,
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
- } catch {
51
- continue
52
- }
53
-
54
- if (element) {
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
- const hash = window.location.hash.split('#', 2)[1]
34
+ if (windowRestored) return
35
+
36
+ const hash = location.hash.slice(1)
65
37
 
66
38
  if (hash) {
67
39
  const hashScrollIntoViewOptions =
68
- window.history.state?.__hashScrollIntoViewOptions ?? true
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
- window.scrollTo({ top: 0, left: 0, behavior: options.behavior })
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
- options: InlineScrollRestorationScriptOptions,
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(options))})`
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 { functionalUpdate, isPlainObject } from './utils'
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
- return typeof window !== 'undefined' &&
27
- typeof window.sessionStorage === 'object'
28
- ? window.sessionStorage
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
- // silent
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(): ScrollRestorationCache | null {
40
- const safeSessionStorage = getSafeSessionStorage()
41
- if (!safeSessionStorage) {
42
- return null
43
- }
44
-
45
- let state: ScrollRestorationByKey = {}
46
-
31
+ function createScrollRestorationCache() {
47
32
  try {
48
- const parsed = JSON.parse(safeSessionStorage.getItem(storageKey) || '{}')
49
- if (isPlainObject(parsed)) {
50
- state = parsed as ScrollRestorationByKey
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
- const persist = () => {
57
- try {
58
- safeSessionStorage.setItem(storageKey, JSON.stringify(state))
59
- } catch {
60
- if (process.env.NODE_ENV !== 'production') {
61
- console.warn(
62
- '[ts-router] Could not persist scroll restoration state to sessionStorage.',
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
- export const scrollRestorationCache = createScrollRestorationCache()
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 getCssSelector(el: any): string {
92
- const path = []
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
- path.push(
96
- `${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`,
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
- return `${path.reverse().join(' > ')}`.toLowerCase()
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 scrollRestorationCache?.state[restoreKey]?.[
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 scrollRestorationCache?.state[restoreKey]?.[
133
- element instanceof Window ? windowScrollTarget : getCssSelector(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
- export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
143
- if (!scrollRestorationCache && !(isServer ?? router.isServer)) {
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
- const cache = scrollRestorationCache
165
+ return elements
166
+ }
148
167
 
149
- const shouldScrollRestoration =
150
- force ?? router.options.scrollRestoration ?? false
168
+ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
169
+ // Keep hash/top scrolling active even when sessionStorage is unavailable.
151
170
 
152
- if (shouldScrollRestoration) {
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
- window.history.scrollRestoration = 'manual'
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 || event.target === window) {
179
- trackedScrollEntries.set(windowScrollTarget, {
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
- trackedScrollEntries.set(target, {
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?: string) => {
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 = (cache.state[restoreKey] ||=
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
- selector = windowScrollTarget
223
+ keyEntry[windowScrollTarget] = position
211
224
  } else if (target.isConnected) {
212
- const attrId = target.getAttribute(scrollRestorationIdAttribute)
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
- snapshotCurrentScrollTargets(
229
- event.fromLocation ? getKey(event.fromLocation) : undefined,
230
- )
232
+ if (event.fromLocation) {
233
+ snapshotCurrentScrollTargets(getKey(event.fromLocation))
234
+ }
231
235
  trackedScrollEntries.clear()
232
236
  })
233
- window.addEventListener('pagehide', () => {
237
+ addEventListener('pagehide', () => {
234
238
  snapshotCurrentScrollTargets(
235
239
  getKey(
236
240
  router.stores.resolvedLocation.get() ?? router.stores.location.get(),
237
241
  ),
238
242
  )
239
- cache.persist()
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 (!router.resetNextScroll) {
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
- 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
- }
265
+ const cacheKey = getKey(event.toLocation)
266
+ const fromCacheKey = event.fromLocation && getKey(event.fromLocation)
276
267
 
277
- const { scrollX, scrollY } = entry as {
278
- scrollX?: unknown
279
- scrollY?: unknown
280
- }
268
+ if (router.isScrollRestoring && fromCacheKey && fromCacheKey !== cacheKey) {
269
+ const fromElementEntries = scrollRestorationCache[fromCacheKey]
281
270
 
282
- if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {
283
- continue
284
- }
271
+ if (fromElementEntries) {
272
+ let toElementEntries = scrollRestorationCache[cacheKey]
285
273
 
274
+ for (const elementSelector in fromElementEntries) {
286
275
  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 {
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 (element) {
303
- element.scrollLeft = scrollX as number
304
- element.scrollTop = scrollY as number
305
- restored = true
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
- if (!restored) {
312
- const hash = router.history.location.hash.slice(1)
305
+ ignoreScroll = true
313
306
 
314
- if (hash) {
315
- const hashScrollIntoViewOptions =
316
- window.history.state?.__hashScrollIntoViewOptions ?? true
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
- if (hashScrollIntoViewOptions) {
319
- const el = document.getElementById(hash)
320
- if (el) {
321
- el.scrollIntoView(hashScrollIntoViewOptions)
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
- } else {
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
- window.scrollTo(scrollOptions)
356
+ scrollTo(scrollOptions)
332
357
  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
- }
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
  }