1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
import {useEffect, useMemo, useState} from 'react'
import {type EventArg, useNavigation} from '@react-navigation/core'
if ('scrollRestoration' in history) {
// Tell the brower not to mess with the scroll.
// We're doing that manually below.
history.scrollRestoration = 'manual'
}
function createInitialScrollState() {
return {
scrollYs: new Map(),
focusedKey: null as string | null,
}
}
export function useWebScrollRestoration() {
const [state] = useState(createInitialScrollState)
const navigation = useNavigation()
useEffect(() => {
function onDispatch() {
if (state.focusedKey) {
// Remember where we were for later.
state.scrollYs.set(state.focusedKey, window.scrollY)
// TODO: Strictly speaking, this is a leak. We never clean up.
// This is because I'm not sure when it's appropriate to clean it up.
// It doesn't seem like popstate is enough because it can still Forward-Back again.
// Maybe we should use sessionStorage. Or check what Next.js is doing?
}
}
// We want to intercept any push/pop/replace *before* the re-render.
// There is no official way to do this yet, but this works okay for now.
// https://twitter.com/satya164/status/1737301243519725803
navigation.addListener('__unsafe_action__' as any, onDispatch)
return () => {
navigation.removeListener('__unsafe_action__' as any, onDispatch)
}
}, [state, navigation])
const screenListeners = useMemo(
() => ({
focus(e: EventArg<'focus', boolean | undefined, unknown>) {
const scrollY = state.scrollYs.get(e.target) ?? 0
window.scrollTo(0, scrollY)
state.focusedKey = e.target ?? null
},
}),
[state],
)
return screenListeners
}
|