@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.
Files changed (43) hide show
  1. package/dist/cjs/hash-scroll.cjs +20 -0
  2. package/dist/cjs/hash-scroll.cjs.map +1 -0
  3. package/dist/cjs/hash-scroll.d.cts +7 -0
  4. package/dist/cjs/index.cjs +3 -3
  5. package/dist/cjs/index.d.cts +2 -1
  6. package/dist/cjs/scroll-restoration-inline.cjs +6 -0
  7. package/dist/cjs/scroll-restoration-inline.cjs.map +1 -0
  8. package/dist/cjs/scroll-restoration-inline.d.cts +6 -0
  9. package/dist/cjs/scroll-restoration-script/client.cjs +9 -0
  10. package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -0
  11. package/dist/cjs/scroll-restoration-script/client.d.cts +2 -0
  12. package/dist/cjs/scroll-restoration-script/server.cjs +30 -0
  13. package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -0
  14. package/dist/cjs/scroll-restoration-script/server.d.cts +2 -0
  15. package/dist/cjs/scroll-restoration.cjs +124 -142
  16. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  17. package/dist/cjs/scroll-restoration.d.cts +15 -38
  18. package/dist/cjs/ssr/tsrScript.cjs +1 -1
  19. package/dist/esm/hash-scroll.d.ts +7 -0
  20. package/dist/esm/hash-scroll.js +20 -0
  21. package/dist/esm/hash-scroll.js.map +1 -0
  22. package/dist/esm/index.d.ts +2 -1
  23. package/dist/esm/index.js +3 -2
  24. package/dist/esm/scroll-restoration-inline.d.ts +6 -0
  25. package/dist/esm/scroll-restoration-inline.js +6 -0
  26. package/dist/esm/scroll-restoration-inline.js.map +1 -0
  27. package/dist/esm/scroll-restoration-script/client.d.ts +2 -0
  28. package/dist/esm/scroll-restoration-script/client.js +8 -0
  29. package/dist/esm/scroll-restoration-script/client.js.map +1 -0
  30. package/dist/esm/scroll-restoration-script/server.d.ts +2 -0
  31. package/dist/esm/scroll-restoration-script/server.js +29 -0
  32. package/dist/esm/scroll-restoration-script/server.js.map +1 -0
  33. package/dist/esm/scroll-restoration.d.ts +15 -38
  34. package/dist/esm/scroll-restoration.js +125 -141
  35. package/dist/esm/scroll-restoration.js.map +1 -1
  36. package/dist/esm/ssr/tsrScript.js +1 -1
  37. package/package.json +21 -1
  38. package/src/hash-scroll.ts +21 -0
  39. package/src/index.ts +3 -3
  40. package/src/scroll-restoration-inline.ts +81 -0
  41. package/src/scroll-restoration-script/client.ts +5 -0
  42. package/src/scroll-restoration-script/server.ts +64 -0
  43. 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
- export type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>
9
+ type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>
11
10
 
12
- export type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>
11
+ type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>
13
12
 
14
- export type ScrollRestorationCache = {
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
- if (
26
- typeof window !== 'undefined' &&
26
+ return typeof window !== 'undefined' &&
27
27
  typeof window.sessionStorage === 'object'
28
- ) {
29
- return window.sessionStorage
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
- /** SessionStorage key used to persist scroll restoration state. */
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
- const persistedState = safeSessionStorage.getItem(storageKey)
61
- let state: ScrollRestorationByKey = persistedState
62
- ? JSON.parse(persistedState)
63
- : {}
45
+ let state: ScrollRestorationByKey = {}
64
46
 
65
- return {
66
- state,
67
- // This setter is simply to make sure that we set the sessionStorage right
68
- // after the state is updated. It doesn't necessarily need to be a functional
69
- // update.
70
- set: (updater) => {
71
- state = functionalUpdate(updater, state) || state
72
- try {
73
- safeSessionStorage.setItem(storageKey, JSON.stringify(state))
74
- } catch {
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
- /** Best-effort nth-child CSS selector for a given element. */
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
- let ignoreScroll = false
114
-
115
- // NOTE: This function must remain pure and not use any outside variables
116
- // unless they are passed in as arguments. Why? Because we need to be able to
117
- // toString() it into a script tag to execute as early as possible in the browser
118
- // during SSR. Additionally, we also call it from within the router lifecycle
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
- break scroll
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
- break scroll
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
- ignoreScroll = false
132
+ return scrollRestorationCache?.state[restoreKey]?.[
133
+ element instanceof Window ? windowScrollTarget : getCssSelector(element)
134
+ ]
216
135
  }
217
136
 
218
- /** Setup global listeners and hooks to support scroll restoration. */
219
- /** Setup global listeners and hooks to support scroll restoration. */
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
- !scrollRestorationCache
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
- elementSelector = 'window'
179
+ trackedScrollEntries.set(windowScrollTarget, {
180
+ scrollX: window.scrollX || 0,
181
+ scrollY: window.scrollY || 0,
182
+ })
297
183
  } else {
298
- const attrId = (event.target as Element).getAttribute(
299
- 'data-scroll-restoration-id',
300
- )
301
-
302
- if (attrId) {
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
- const restoreKey = getKey(router.stores.location.state)
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
- scrollRestorationCache.set((state) => {
312
- const keyEntry = (state[restoreKey] ||= {} as ScrollRestorationByElement)
203
+ const keyEntry = (cache.state[restoreKey] ||=
204
+ {} as ScrollRestorationByElement)
313
205
 
314
- const elementEntry = (keyEntry[elementSelector] ||=
315
- {} as ScrollRestorationEntry)
206
+ for (const [target, position] of trackedScrollEntries) {
207
+ let selector: string | undefined
316
208
 
317
- if (elementSelector === 'window') {
318
- elementEntry.scrollX = window.scrollX || 0
319
- elementEntry.scrollY = window.scrollY || 0
320
- } else if (elementSelector) {
321
- const element = document.querySelector(elementSelector)
322
- if (element) {
323
- elementEntry.scrollX = element.scrollLeft || 0
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
- return state
329
- })
330
- }
218
+ if (!selector) {
219
+ continue
220
+ }
331
221
 
332
- // Throttle the scroll event to avoid excessive updates
333
- if (typeof document !== 'undefined') {
334
- document.addEventListener('scroll', throttle(onScroll, 100), true)
222
+ keyEntry[selector] = position
223
+ }
335
224
  }
336
225
 
337
- router.subscribe('onRendered', (event) => {
338
- // unobserveDom()
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
- if (typeof router.options.scrollRestoration === 'function') {
349
- const shouldRestore = router.options.scrollRestoration({
350
- location: router.latestLocation,
351
- })
352
- if (!shouldRestore) {
353
- return
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
- restoreScroll({
358
- storageKey,
359
- key: cacheKey,
360
- behavior: router.options.scrollRestorationBehavior,
361
- shouldScrollRestoration: router.isScrollRestoring,
362
- scrollToTopSelectors: router.options.scrollToTopSelectors,
363
- location: router.history.location,
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
- // Mark the location as having been seen
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
- }