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