Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 52 lines 1.8 kB view raw
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}