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

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.165",
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.165"
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'
package/src/router.ts CHANGED
@@ -186,11 +186,11 @@ export interface RouterState<
186
186
  > {
187
187
  status: 'idle' | 'pending'
188
188
  isFetching: boolean
189
- matchesById: Record<string, RouteMatch<TRouteTree, AnyRoute>>
189
+ matchesById: Record<string, RouteMatch<TRouteTree, ParseRoute<TRouteTree>>>
190
190
  matchIds: string[]
191
191
  pendingMatchIds: string[]
192
- matches: RouteMatch<TRouteTree, AnyRoute>[]
193
- pendingMatches: RouteMatch<TRouteTree, AnyRoute>[]
192
+ matches: RouteMatch<TRouteTree, ParseRoute<TRouteTree>>[]
193
+ pendingMatches: RouteMatch<TRouteTree, ParseRoute<TRouteTree>>[]
194
194
  location: ParsedLocation<FullSearchSchema<TRouteTree>>
195
195
  resolvedLocation: ParsedLocation<FullSearchSchema<TRouteTree>>
196
196
  lastUpdated: number
@@ -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
+ }