@tanstack/router-core 0.0.1-beta.163 → 0.0.1-beta.164

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.163",
4
+ "version": "0.0.1-beta.164",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -43,7 +43,7 @@
43
43
  "tiny-invariant": "^1.3.1",
44
44
  "tiny-warning": "^1.0.3",
45
45
  "@gisatcz/cross-package-react-context": "^0.2.0",
46
- "@tanstack/react-store": "0.0.1-beta.163"
46
+ "@tanstack/react-store": "0.0.1-beta.164"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "rollup --config rollup.config.js",
package/src/index.ts CHANGED
@@ -10,3 +10,4 @@ export * from './routeInfo'
10
10
  export * from './router'
11
11
  export * from './searchParams'
12
12
  export * from './utils'
13
+ export * from './scroll-restoration'
@@ -0,0 +1,175 @@
1
+ import { AnyRouter, ParsedLocation } from './router'
2
+
3
+ const windowKey = 'window'
4
+ const delimiter = '___'
5
+
6
+ let weakScrolledElementsByRestoreKey: Record<string, WeakSet<any>> = {}
7
+
8
+ type CacheValue = Record<string, { scrollX: number; scrollY: number }>
9
+
10
+ type Cache = {
11
+ current: CacheValue
12
+ set: (key: string, value: any) => void
13
+ }
14
+
15
+ let cache: Cache
16
+
17
+ let pathDidChange = false
18
+
19
+ const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage
20
+
21
+ export type ScrollRestorationOptions = {
22
+ getKey?: (location: ParsedLocation) => string
23
+ }
24
+
25
+ const defaultGetKey = (location: ParsedLocation) => location.key!
26
+
27
+ export function watchScrollPositions(
28
+ router: AnyRouter,
29
+ opts?: ScrollRestorationOptions,
30
+ ) {
31
+ const getKey = opts?.getKey || defaultGetKey
32
+
33
+ if (sessionsStorage) {
34
+ if (!cache) {
35
+ cache = (() => {
36
+ const storageKey = 'tsr-scroll-restoration-v1'
37
+
38
+ const current: CacheValue = JSON.parse(
39
+ window.sessionStorage.getItem(storageKey) || '{}',
40
+ )
41
+
42
+ return {
43
+ current,
44
+ set: (key: string, value: any) => {
45
+ current[key] = value
46
+ window.sessionStorage.setItem(storageKey, JSON.stringify(cache))
47
+ },
48
+ }
49
+ })()
50
+ }
51
+ }
52
+
53
+ const { history } = window
54
+ if (history.scrollRestoration) {
55
+ history.scrollRestoration = 'manual'
56
+ }
57
+
58
+ const onScroll = (event: Event) => {
59
+ const restoreKey = getKey(router.state.resolvedLocation)
60
+
61
+ if (!weakScrolledElementsByRestoreKey[restoreKey]) {
62
+ weakScrolledElementsByRestoreKey[restoreKey] = new WeakSet()
63
+ }
64
+
65
+ const set = weakScrolledElementsByRestoreKey[restoreKey]!
66
+
67
+ if (set.has(event.target)) return
68
+ set.add(event.target)
69
+
70
+ const cacheKey = [
71
+ restoreKey,
72
+ event.target === document || event.target === window
73
+ ? windowKey
74
+ : getCssSelector(event.target),
75
+ ].join(delimiter)
76
+
77
+ if (!cache.current[cacheKey]) {
78
+ cache.set(cacheKey, {
79
+ scrollX: NaN,
80
+ scrollY: NaN,
81
+ })
82
+ }
83
+ }
84
+
85
+ const getCssSelector = (el: any): string => {
86
+ let path = [],
87
+ parent
88
+ while ((parent = el.parentNode)) {
89
+ path.unshift(
90
+ `${el.tagName}:nth-child(${
91
+ ([].indexOf as any).call(parent.children, el) + 1
92
+ })`,
93
+ )
94
+ el = parent
95
+ }
96
+ return `${path.join(' > ')}`.toLowerCase()
97
+ }
98
+
99
+ const onPathWillChange = (from: ParsedLocation) => {
100
+ const restoreKey = getKey(from)
101
+ for (const cacheKey in cache.current) {
102
+ const entry = cache.current[cacheKey]!
103
+ const [key, elementSelector] = cacheKey.split(delimiter)
104
+ if (restoreKey === key) {
105
+ if (elementSelector === windowKey) {
106
+ entry.scrollX = window.scrollX || 0
107
+ entry.scrollY = window.scrollY || 0
108
+ } else if (elementSelector) {
109
+ const element = document.querySelector(elementSelector)
110
+ entry.scrollX = element?.scrollLeft || 0
111
+ entry.scrollY = element?.scrollTop || 0
112
+ }
113
+
114
+ cache.set(cacheKey, entry)
115
+ }
116
+ }
117
+ }
118
+
119
+ const onPathChange = () => {
120
+ pathDidChange = true
121
+ }
122
+
123
+ if (typeof document !== 'undefined') {
124
+ document.addEventListener('scroll', onScroll, true)
125
+ }
126
+
127
+ const unsubOnBeforeLoad = router.subscribe('onBeforeLoad', (event) => {
128
+ if (event.pathChanged) onPathWillChange(event.from)
129
+ })
130
+
131
+ const unsubOnLoad = router.subscribe('onLoad', (event) => {
132
+ if (event.pathChanged) onPathChange()
133
+ })
134
+
135
+ return () => {
136
+ document.removeEventListener('scroll', onScroll)
137
+ unsubOnBeforeLoad()
138
+ unsubOnLoad()
139
+ }
140
+ }
141
+
142
+ export function restoreScrollPositions(
143
+ router: AnyRouter,
144
+ opts?: ScrollRestorationOptions,
145
+ ) {
146
+ if (pathDidChange) {
147
+ const getKey = opts?.getKey || defaultGetKey
148
+
149
+ pathDidChange = false
150
+
151
+ const restoreKey = getKey(router.state.location)
152
+ let windowRestored = false
153
+
154
+ for (const cacheKey in cache.current) {
155
+ const entry = cache.current[cacheKey]!
156
+ const [key, elementSelector] = cacheKey.split(delimiter)
157
+ if (key === restoreKey) {
158
+ if (elementSelector === windowKey) {
159
+ windowRestored = true
160
+ window.scrollTo(entry.scrollX, entry.scrollY)
161
+ } else if (elementSelector) {
162
+ const element = document.querySelector(elementSelector)
163
+ if (element) {
164
+ element.scrollLeft = entry.scrollX
165
+ element.scrollTop = entry.scrollY
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ if (!windowRestored) {
172
+ window.scrollTo(0, 0)
173
+ }
174
+ }
175
+ }