@tanstack/react-router 0.0.1-beta.230 → 0.0.1-beta.232
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/build/cjs/Matches.js +4 -1
- package/build/cjs/Matches.js.map +1 -1
- package/build/cjs/RouterProvider.js +34 -37
- package/build/cjs/RouterProvider.js.map +1 -1
- package/build/cjs/awaited.js +1 -1
- package/build/cjs/awaited.js.map +1 -1
- package/build/cjs/index.js +4 -3
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/router.js +70 -70
- package/build/cjs/router.js.map +1 -1
- package/build/cjs/scroll-restoration.js +52 -29
- package/build/cjs/scroll-restoration.js.map +1 -1
- package/build/esm/index.js +155 -133
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +293 -293
- package/build/types/RouterProvider.d.ts +0 -5
- package/build/types/router.d.ts +14 -2
- package/build/types/scroll-restoration.d.ts +12 -0
- package/build/umd/index.development.js +154 -131
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +2 -2
- package/build/umd/index.production.js.map +1 -1
- package/package.json +2 -2
- package/src/Matches.tsx +3 -1
- package/src/RouterProvider.tsx +42 -46
- package/src/awaited.tsx +1 -1
- package/src/router.ts +106 -84
- package/src/scroll-restoration.tsx +81 -43
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-router",
|
|
3
3
|
"author": "Tanner Linsley",
|
|
4
|
-
"version": "0.0.1-beta.
|
|
4
|
+
"version": "0.0.1-beta.232",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "tanstack/router",
|
|
7
7
|
"homepage": "https://tanstack.com/router",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@babel/runtime": "^7.16.7",
|
|
43
43
|
"tiny-invariant": "^1.3.1",
|
|
44
44
|
"tiny-warning": "^1.0.3",
|
|
45
|
-
"@tanstack/history": "0.0.1-beta.
|
|
45
|
+
"@tanstack/history": "0.0.1-beta.232"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "rollup --config rollup.config.js"
|
package/src/Matches.tsx
CHANGED
|
@@ -94,7 +94,9 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
|
|
|
94
94
|
const match = matches[0]!
|
|
95
95
|
const routeId = match?.routeId
|
|
96
96
|
const route = routesById[routeId]!
|
|
97
|
-
const
|
|
97
|
+
const router = useRouter()
|
|
98
|
+
const locationKey = router.latestLocation.state?.key
|
|
99
|
+
// const locationKey = useRouterState().location.state?.key
|
|
98
100
|
|
|
99
101
|
const PendingComponent = (route.options.pendingComponent ??
|
|
100
102
|
options.defaultPendingComponent) as any
|
package/src/RouterProvider.tsx
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
RouterOptions,
|
|
19
19
|
RouterState,
|
|
20
20
|
} from './router'
|
|
21
|
-
import { NoInfer, PickAsRequired } from './utils'
|
|
21
|
+
import { NoInfer, PickAsRequired, useLayoutEffect } from './utils'
|
|
22
22
|
import { MatchRouteOptions } from './Matches'
|
|
23
23
|
import { RouteMatch } from './Matches'
|
|
24
24
|
|
|
@@ -72,48 +72,42 @@ if (typeof document !== 'undefined') {
|
|
|
72
72
|
window.__TSR_ROUTER_CONTEXT__ = routerContext as any
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
export class SearchParamError extends Error {}
|
|
76
|
-
|
|
77
|
-
export class PathParamError extends Error {}
|
|
78
|
-
|
|
79
|
-
export function getInitialRouterState(
|
|
80
|
-
location: ParsedLocation,
|
|
81
|
-
): RouterState<any> {
|
|
82
|
-
return {
|
|
83
|
-
status: 'idle',
|
|
84
|
-
resolvedLocation: location,
|
|
85
|
-
location,
|
|
86
|
-
matches: [],
|
|
87
|
-
pendingMatches: [],
|
|
88
|
-
lastUpdated: Date.now(),
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
75
|
export function RouterProvider<
|
|
93
76
|
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
94
77
|
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
95
78
|
>({ router, ...rest }: RouterProps<TRouteTree, TDehydrated>) {
|
|
96
79
|
// Allow the router to update options on the router instance
|
|
97
|
-
router.
|
|
80
|
+
router.update({
|
|
98
81
|
...router.options,
|
|
99
82
|
...rest,
|
|
100
|
-
|
|
101
83
|
context: {
|
|
102
84
|
...router.options.context,
|
|
103
85
|
...rest?.context,
|
|
104
86
|
},
|
|
105
|
-
} as
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
87
|
+
} as any)
|
|
88
|
+
|
|
89
|
+
const inner = <RouterProviderInner<TRouteTree, TDehydrated> router={router} />
|
|
90
|
+
|
|
91
|
+
if (router.options.Wrap) {
|
|
92
|
+
return <router.options.Wrap>{inner}</router.options.Wrap>
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return inner
|
|
96
|
+
}
|
|
109
97
|
|
|
98
|
+
function RouterProviderInner<
|
|
99
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
100
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
101
|
+
>({ router }: RouterProps<TRouteTree, TDehydrated>) {
|
|
110
102
|
const [preState, setState] = React.useState(() => router.state)
|
|
111
103
|
const [isTransitioning, startReactTransition] = React.useTransition()
|
|
104
|
+
const isAnyTransitioning =
|
|
105
|
+
isTransitioning || preState.matches.some((d) => d.status === 'pending')
|
|
112
106
|
|
|
113
107
|
const state = React.useMemo<RouterState<TRouteTree>>(
|
|
114
108
|
() => ({
|
|
115
109
|
...preState,
|
|
116
|
-
status:
|
|
110
|
+
status: isAnyTransitioning ? 'pending' : 'idle',
|
|
117
111
|
location: isTransitioning ? router.latestLocation : preState.location,
|
|
118
112
|
pendingMatches: router.pendingMatches,
|
|
119
113
|
}),
|
|
@@ -124,19 +118,22 @@ export function RouterProvider<
|
|
|
124
118
|
router.state = state
|
|
125
119
|
router.startReactTransition = startReactTransition
|
|
126
120
|
|
|
127
|
-
|
|
121
|
+
const tryLoad = () => {
|
|
122
|
+
if (state.location !== router.latestLocation) {
|
|
123
|
+
startReactTransition(() => {
|
|
124
|
+
try {
|
|
125
|
+
router.load()
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error(err)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
useLayoutEffect(() => {
|
|
128
134
|
const unsub = router.history.subscribe(() => {
|
|
129
135
|
router.latestLocation = router.parseLocation(router.latestLocation)
|
|
130
|
-
|
|
131
|
-
if (state.location !== router.latestLocation) {
|
|
132
|
-
startReactTransition(() => {
|
|
133
|
-
try {
|
|
134
|
-
router.load()
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.error(err)
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
}
|
|
136
|
+
tryLoad()
|
|
140
137
|
})
|
|
141
138
|
|
|
142
139
|
const nextLocation = router.buildLocation({
|
|
@@ -155,7 +152,7 @@ export function RouterProvider<
|
|
|
155
152
|
}
|
|
156
153
|
}, [router.history])
|
|
157
154
|
|
|
158
|
-
|
|
155
|
+
useLayoutEffect(() => {
|
|
159
156
|
if (!isTransitioning && state.resolvedLocation !== state.location) {
|
|
160
157
|
router.emit({
|
|
161
158
|
type: 'onResolved',
|
|
@@ -172,14 +169,10 @@ export function RouterProvider<
|
|
|
172
169
|
}
|
|
173
170
|
})
|
|
174
171
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} catch (err) {
|
|
180
|
-
console.error(err)
|
|
181
|
-
}
|
|
182
|
-
})
|
|
172
|
+
useLayoutEffect(() => {
|
|
173
|
+
if (!window.__TSR_DEHYDRATED__) {
|
|
174
|
+
tryLoad()
|
|
175
|
+
}
|
|
183
176
|
}, [])
|
|
184
177
|
|
|
185
178
|
return (
|
|
@@ -217,7 +210,10 @@ export type RouterProps<
|
|
|
217
210
|
export function useRouter<
|
|
218
211
|
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
219
212
|
>(): Router<TRouteTree> {
|
|
220
|
-
const resolvedContext =
|
|
213
|
+
const resolvedContext =
|
|
214
|
+
typeof document !== 'undefined'
|
|
215
|
+
? window.__TSR_ROUTER_CONTEXT__ || routerContext
|
|
216
|
+
: routerContext
|
|
221
217
|
const value = React.useContext(resolvedContext)
|
|
222
218
|
warning(value, 'useRouter must be used inside a <RouterProvider> component!')
|
|
223
219
|
return value as any
|
package/src/awaited.tsx
CHANGED
package/src/router.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
HistoryState,
|
|
4
4
|
RouterHistory,
|
|
5
5
|
createBrowserHistory,
|
|
6
|
+
createMemoryHistory,
|
|
6
7
|
} from '@tanstack/history'
|
|
7
8
|
|
|
8
9
|
//
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
functionalUpdate,
|
|
29
30
|
last,
|
|
30
31
|
pick,
|
|
32
|
+
PickAsPartial,
|
|
31
33
|
} from './utils'
|
|
32
34
|
import {
|
|
33
35
|
ErrorRouteComponent,
|
|
@@ -45,11 +47,9 @@ import {
|
|
|
45
47
|
InjectedHtmlEntry,
|
|
46
48
|
MatchRouteFn,
|
|
47
49
|
NavigateFn,
|
|
48
|
-
PathParamError,
|
|
49
|
-
SearchParamError,
|
|
50
|
-
getInitialRouterState,
|
|
51
50
|
getRouteMatch,
|
|
52
51
|
} from './RouterProvider'
|
|
52
|
+
|
|
53
53
|
import {
|
|
54
54
|
cleanPath,
|
|
55
55
|
interpolatePath,
|
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
} from './path'
|
|
63
63
|
import invariant from 'tiny-invariant'
|
|
64
64
|
import { isRedirect } from './redirects'
|
|
65
|
-
import warning from 'tiny-warning'
|
|
65
|
+
// import warning from 'tiny-warning'
|
|
66
66
|
|
|
67
67
|
//
|
|
68
68
|
|
|
@@ -125,10 +125,11 @@ export interface RouterOptions<
|
|
|
125
125
|
routeTree?: TRouteTree
|
|
126
126
|
basepath?: string
|
|
127
127
|
context?: TRouteTree['types']['routerContext']
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
dehydrate?: () => TDehydrated
|
|
129
|
+
hydrate?: (dehydrated: TDehydrated) => void
|
|
130
130
|
routeMasks?: RouteMask<TRouteTree>[]
|
|
131
131
|
unmaskOnReload?: boolean
|
|
132
|
+
Wrap?: (props: { children: any }) => JSX.Element
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
|
|
@@ -249,7 +250,7 @@ export class Router<
|
|
|
249
250
|
flatRoutes!: AnyRoute[]
|
|
250
251
|
|
|
251
252
|
constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated>) {
|
|
252
|
-
this.
|
|
253
|
+
this.update({
|
|
253
254
|
defaultPreloadDelay: 50,
|
|
254
255
|
defaultPendingMs: 1000,
|
|
255
256
|
defaultPendingMinMs: 500,
|
|
@@ -260,28 +261,17 @@ export class Router<
|
|
|
260
261
|
})
|
|
261
262
|
}
|
|
262
263
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
fn: (s: RouterState<TRouteTree>) => RouterState<TRouteTree>,
|
|
272
|
-
) => void = () => {
|
|
273
|
-
warning(
|
|
274
|
-
false,
|
|
275
|
-
'setState implementation is missing. If you see this, please file an issue.',
|
|
276
|
-
)
|
|
264
|
+
// These are default implementations that can optionally be overridden
|
|
265
|
+
// by the router provider once rendered. We provide these so that the
|
|
266
|
+
// router can be used in a non-react environment if necessary
|
|
267
|
+
startReactTransition: (fn: () => void) => void = (fn) => fn()
|
|
268
|
+
setState: (updater: NonNullableUpdater<RouterState<TRouteTree>>) => void = (
|
|
269
|
+
updater,
|
|
270
|
+
) => {
|
|
271
|
+
this.state = functionalUpdate(updater, this.state)
|
|
277
272
|
}
|
|
278
273
|
|
|
279
|
-
|
|
280
|
-
newOptions: PickAsRequired<
|
|
281
|
-
RouterOptions<TRouteTree, TDehydrated>,
|
|
282
|
-
'stringifySearch' | 'parseSearch' | 'context'
|
|
283
|
-
>,
|
|
284
|
-
) => {
|
|
274
|
+
update = (newOptions: RouterConstructorOptions<TRouteTree, TDehydrated>) => {
|
|
285
275
|
this.options = {
|
|
286
276
|
...this.options,
|
|
287
277
|
...newOptions,
|
|
@@ -293,7 +283,11 @@ export class Router<
|
|
|
293
283
|
!this.history ||
|
|
294
284
|
(this.options.history && this.options.history !== this.history)
|
|
295
285
|
) {
|
|
296
|
-
this.history =
|
|
286
|
+
this.history =
|
|
287
|
+
this.options.history ??
|
|
288
|
+
(typeof document !== 'undefined'
|
|
289
|
+
? createBrowserHistory()
|
|
290
|
+
: createMemoryHistory())
|
|
297
291
|
this.latestLocation = this.parseLocation()
|
|
298
292
|
}
|
|
299
293
|
|
|
@@ -1168,6 +1162,8 @@ export class Router<
|
|
|
1168
1162
|
// forcefully show the pending component
|
|
1169
1163
|
if (pendingPromise) {
|
|
1170
1164
|
pendingPromise.then(() => {
|
|
1165
|
+
if ((latestPromise = checkLatest())) return
|
|
1166
|
+
|
|
1171
1167
|
didShowPending = true
|
|
1172
1168
|
matches[index] = match = {
|
|
1173
1169
|
...match,
|
|
@@ -1195,6 +1191,8 @@ export class Router<
|
|
|
1195
1191
|
await new Promise((r) => setTimeout(r, pendingMinMs))
|
|
1196
1192
|
}
|
|
1197
1193
|
|
|
1194
|
+
if ((latestPromise = checkLatest())) return await latestPromise
|
|
1195
|
+
|
|
1198
1196
|
matches[index] = match = {
|
|
1199
1197
|
...match,
|
|
1200
1198
|
error: undefined,
|
|
@@ -1279,7 +1277,7 @@ export class Router<
|
|
|
1279
1277
|
// Ingest the new matches
|
|
1280
1278
|
this.setState((s) => ({
|
|
1281
1279
|
...s,
|
|
1282
|
-
status: 'pending',
|
|
1280
|
+
// status: 'pending',
|
|
1283
1281
|
location: next,
|
|
1284
1282
|
matches,
|
|
1285
1283
|
}))
|
|
@@ -1316,6 +1314,7 @@ export class Router<
|
|
|
1316
1314
|
// ...s,
|
|
1317
1315
|
// status: 'idle',
|
|
1318
1316
|
// resolvedLocation: s.location,
|
|
1317
|
+
// matches,
|
|
1319
1318
|
// }))
|
|
1320
1319
|
|
|
1321
1320
|
//
|
|
@@ -1578,62 +1577,69 @@ export class Router<
|
|
|
1578
1577
|
return undefined
|
|
1579
1578
|
}
|
|
1580
1579
|
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1580
|
+
dehydrate = (): DehydratedRouter => {
|
|
1581
|
+
return {
|
|
1582
|
+
state: {
|
|
1583
|
+
dehydratedMatches: this.state.matches.map((d) =>
|
|
1584
|
+
pick(d, [
|
|
1585
|
+
'fetchedAt',
|
|
1586
|
+
'invalid',
|
|
1587
|
+
'id',
|
|
1588
|
+
'status',
|
|
1589
|
+
'updatedAt',
|
|
1590
|
+
'loaderData',
|
|
1591
|
+
]),
|
|
1592
|
+
),
|
|
1593
|
+
},
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1590
1596
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1597
|
+
hydrate = async (__do_not_use_server_ctx?: HydrationCtx) => {
|
|
1598
|
+
let _ctx = __do_not_use_server_ctx
|
|
1599
|
+
// Client hydrates from window
|
|
1600
|
+
if (typeof document !== 'undefined') {
|
|
1601
|
+
_ctx = window.__TSR_DEHYDRATED__
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
invariant(
|
|
1605
|
+
_ctx,
|
|
1606
|
+
'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?',
|
|
1607
|
+
)
|
|
1608
|
+
|
|
1609
|
+
const ctx = _ctx
|
|
1610
|
+
this.dehydratedData = ctx.payload as any
|
|
1611
|
+
this.options.hydrate?.(ctx.payload as any)
|
|
1612
|
+
const dehydratedState = ctx.router.state
|
|
1613
|
+
|
|
1614
|
+
let matches = this.matchRoutes(
|
|
1615
|
+
this.state.location.pathname,
|
|
1616
|
+
this.state.location.search,
|
|
1617
|
+
).map((match) => {
|
|
1618
|
+
const dehydratedMatch = dehydratedState.dehydratedMatches.find(
|
|
1619
|
+
(d) => d.id === match.id,
|
|
1620
|
+
)
|
|
1621
|
+
|
|
1622
|
+
invariant(
|
|
1623
|
+
dehydratedMatch,
|
|
1624
|
+
`Could not find a client-side match for dehydrated match with id: ${match.id}!`,
|
|
1625
|
+
)
|
|
1626
|
+
|
|
1627
|
+
if (dehydratedMatch) {
|
|
1628
|
+
return {
|
|
1629
|
+
...match,
|
|
1630
|
+
...dehydratedMatch,
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
return match
|
|
1634
|
+
})
|
|
1635
|
+
|
|
1636
|
+
this.setState((s) => {
|
|
1637
|
+
return {
|
|
1638
|
+
...s,
|
|
1639
|
+
matches: matches as any,
|
|
1640
|
+
}
|
|
1641
|
+
})
|
|
1642
|
+
}
|
|
1637
1643
|
|
|
1638
1644
|
// resolveMatchPromise = (matchId: string, key: string, value: any) => {
|
|
1639
1645
|
// state.matches
|
|
@@ -1658,3 +1664,19 @@ export function lazyFn<
|
|
|
1658
1664
|
function isCtrlEvent(e: MouseEvent) {
|
|
1659
1665
|
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
|
|
1660
1666
|
}
|
|
1667
|
+
export class SearchParamError extends Error {}
|
|
1668
|
+
|
|
1669
|
+
export class PathParamError extends Error {}
|
|
1670
|
+
|
|
1671
|
+
export function getInitialRouterState(
|
|
1672
|
+
location: ParsedLocation,
|
|
1673
|
+
): RouterState<any> {
|
|
1674
|
+
return {
|
|
1675
|
+
status: 'idle',
|
|
1676
|
+
resolvedLocation: location,
|
|
1677
|
+
location,
|
|
1678
|
+
matches: [],
|
|
1679
|
+
pendingMatches: [],
|
|
1680
|
+
lastUpdated: Date.now(),
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
@@ -23,10 +23,26 @@ type Cache = {
|
|
|
23
23
|
set: (updater: NonNullableUpdater<CacheState>) => void
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
let cache: Cache
|
|
27
|
-
|
|
28
26
|
const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage
|
|
29
27
|
|
|
28
|
+
let cache: Cache = sessionsStorage
|
|
29
|
+
? (() => {
|
|
30
|
+
const storageKey = 'tsr-scroll-restoration-v2'
|
|
31
|
+
|
|
32
|
+
const state: CacheState = JSON.parse(
|
|
33
|
+
window.sessionStorage.getItem(storageKey) || 'null',
|
|
34
|
+
) || { cached: {}, next: {} }
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
state,
|
|
38
|
+
set: (updater) => {
|
|
39
|
+
cache.state = functionalUpdate(updater, cache.state)
|
|
40
|
+
window.sessionStorage.setItem(storageKey, JSON.stringify(cache.state))
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
})()
|
|
44
|
+
: (undefined as any)
|
|
45
|
+
|
|
30
46
|
export type ScrollRestorationOptions = {
|
|
31
47
|
getKey?: (location: ParsedLocation) => string
|
|
32
48
|
}
|
|
@@ -39,29 +55,6 @@ export function useScrollRestoration(options?: ScrollRestorationOptions) {
|
|
|
39
55
|
useLayoutEffect(() => {
|
|
40
56
|
const getKey = options?.getKey || defaultGetKey
|
|
41
57
|
|
|
42
|
-
if (sessionsStorage) {
|
|
43
|
-
if (!cache) {
|
|
44
|
-
cache = (() => {
|
|
45
|
-
const storageKey = 'tsr-scroll-restoration-v2'
|
|
46
|
-
|
|
47
|
-
const state: CacheState = JSON.parse(
|
|
48
|
-
window.sessionStorage.getItem(storageKey) || 'null',
|
|
49
|
-
) || { cached: {}, next: {} }
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
state,
|
|
53
|
-
set: (updater) => {
|
|
54
|
-
cache.state = functionalUpdate(updater, cache.state)
|
|
55
|
-
window.sessionStorage.setItem(
|
|
56
|
-
storageKey,
|
|
57
|
-
JSON.stringify(cache.state),
|
|
58
|
-
)
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
})()
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
58
|
const { history } = window
|
|
66
59
|
if (history.scrollRestoration) {
|
|
67
60
|
history.scrollRestoration = 'manual'
|
|
@@ -71,10 +64,21 @@ export function useScrollRestoration(options?: ScrollRestorationOptions) {
|
|
|
71
64
|
if (weakScrolledElements.has(event.target)) return
|
|
72
65
|
weakScrolledElements.add(event.target)
|
|
73
66
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
let elementSelector = ''
|
|
68
|
+
|
|
69
|
+
if (event.target === document || event.target === window) {
|
|
70
|
+
elementSelector = windowKey
|
|
71
|
+
} else {
|
|
72
|
+
const attrId = (event.target as Element).getAttribute(
|
|
73
|
+
'data-scroll-restoration-id',
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if (attrId) {
|
|
77
|
+
elementSelector = `[data-scroll-restoration-id="${attrId}"]`
|
|
78
|
+
} else {
|
|
79
|
+
elementSelector = getCssSelector(event.target)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
78
82
|
|
|
79
83
|
if (!cache.state.next[elementSelector]) {
|
|
80
84
|
cache.set((c) => ({
|
|
@@ -90,20 +94,6 @@ export function useScrollRestoration(options?: ScrollRestorationOptions) {
|
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
|
|
93
|
-
const getCssSelector = (el: any): string => {
|
|
94
|
-
let path = [],
|
|
95
|
-
parent
|
|
96
|
-
while ((parent = el.parentNode)) {
|
|
97
|
-
path.unshift(
|
|
98
|
-
`${el.tagName}:nth-child(${
|
|
99
|
-
([].indexOf as any).call(parent.children, el) + 1
|
|
100
|
-
})`,
|
|
101
|
-
)
|
|
102
|
-
el = parent
|
|
103
|
-
}
|
|
104
|
-
return `${path.join(' > ')}`.toLowerCase()
|
|
105
|
-
}
|
|
106
|
-
|
|
107
97
|
if (typeof document !== 'undefined') {
|
|
108
98
|
document.addEventListener('scroll', onScroll, true)
|
|
109
99
|
}
|
|
@@ -190,3 +180,51 @@ export function ScrollRestoration(props: ScrollRestorationOptions) {
|
|
|
190
180
|
useScrollRestoration(props)
|
|
191
181
|
return null
|
|
192
182
|
}
|
|
183
|
+
|
|
184
|
+
export function useElementScrollRestoration(
|
|
185
|
+
options: (
|
|
186
|
+
| {
|
|
187
|
+
id: string
|
|
188
|
+
getElement?: () => Element | undefined | null
|
|
189
|
+
}
|
|
190
|
+
| {
|
|
191
|
+
id?: string
|
|
192
|
+
getElement: () => Element | undefined | null
|
|
193
|
+
}
|
|
194
|
+
) & {
|
|
195
|
+
getKey?: (location: ParsedLocation) => string
|
|
196
|
+
},
|
|
197
|
+
) {
|
|
198
|
+
const router = useRouter()
|
|
199
|
+
const getKey = options?.getKey || defaultGetKey
|
|
200
|
+
|
|
201
|
+
let elementSelector = ''
|
|
202
|
+
|
|
203
|
+
if (options.id) {
|
|
204
|
+
elementSelector = `[data-scroll-restoration-id="${options.id}"]`
|
|
205
|
+
} else {
|
|
206
|
+
const element = options.getElement?.()
|
|
207
|
+
if (!element) {
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
elementSelector = getCssSelector(element)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const restoreKey = getKey(router.latestLocation)
|
|
214
|
+
const cacheKey = [restoreKey, elementSelector].join(delimiter)
|
|
215
|
+
return cache.state.cached[cacheKey]
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getCssSelector(el: any): string {
|
|
219
|
+
let path = [],
|
|
220
|
+
parent
|
|
221
|
+
while ((parent = el.parentNode)) {
|
|
222
|
+
path.unshift(
|
|
223
|
+
`${el.tagName}:nth-child(${
|
|
224
|
+
([].indexOf as any).call(parent.children, el) + 1
|
|
225
|
+
})`,
|
|
226
|
+
)
|
|
227
|
+
el = parent
|
|
228
|
+
}
|
|
229
|
+
return `${path.join(' > ')}`.toLowerCase()
|
|
230
|
+
}
|