Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Expand scroll restoration to both X/Y scrolls

+28 -21
+25 -18
src/useScrollRestoration.ts
··· 1 1 import { useLayoutEffect } from './utils/react'; 2 - import { observeScrollHeight } from './utils/observeScrollHeight'; 2 + import { observeScrollArea } from './utils/observeScrollArea'; 3 3 import { Ref } from './types'; 4 4 5 5 const getIdForState = (() => { ··· 16 16 }; 17 17 })(); 18 18 19 - const scrollPositions: Record<string, number> = {}; 19 + const scrollPositions: Record<string, [number, number]> = {}; 20 20 21 21 export function useScrollRestoration<T extends HTMLElement>( 22 22 ref: 'window' | Ref<T> ··· 33 33 const id = `${addonId}${getIdForState( 34 34 event ? event.state : history.state 35 35 )}:${window.location}`; 36 - const scrollHeight = 37 - ref === 'window' 38 - ? document.body.scrollHeight 39 - : ref.current!.scrollHeight; 40 - const scrollY = scrollPositions[id]; 41 - if (!scrollY) { 36 + const { scrollWidth, scrollHeight } = scrollTarget; 37 + const scrollTo = scrollPositions[id]; 38 + if (!scrollTo) { 42 39 // noop 43 - } else if (scrollHeight >= scrollY) { 44 - scrollTarget.scrollTo(0, scrollY); 40 + } else if (scrollWidth >= scrollTo[0] && scrollHeight >= scrollTo[1]) { 41 + scrollTarget.scrollTo(scrollTo[0], scrollTo[1]); 45 42 } else { 46 43 if (unsubscribe) unsubscribe(); 47 - unsubscribe = observeScrollHeight( 44 + unsubscribe = observeScrollArea( 48 45 ref === 'window' ? document.body : ref.current!, 49 - (scrollHeight: number) => { 50 - // the scroll position shouldn't have changed by more than half the screen height 46 + (scrollWidth: number, scrollHeight: number) => { 47 + // check whether the scroll position has already moved too far 48 + const halfViewportX = window.innerWidth / 2; 49 + const halfViewportY = window.innerHeight / 2; 50 + const newScrollTo = scrollPositions[id]; 51 51 const hasMoved = 52 - Math.abs(scrollY - scrollPositions[id]) > window.innerHeight / 2; 52 + Math.abs(scrollTo[0] - newScrollTo[0]) > halfViewportX || 53 + Math.abs(scrollTo[1] - newScrollTo[1]) > halfViewportY; 53 54 // then we restore the position as it's now possible 54 - if (!hasMoved && scrollHeight >= scrollY) 55 - scrollTarget.scrollTo(0, scrollY); 56 - if (unsubscribe) unsubscribe(); 55 + if ( 56 + hasMoved || 57 + (scrollWidth >= scrollTo[0] && scrollHeight >= scrollTo[1]) 58 + ) { 59 + if (!hasMoved) scrollTarget.scrollTo(scrollTo[0], scrollTo[1]); 60 + if (unsubscribe) unsubscribe(); 61 + } 57 62 } 58 63 ); 59 64 } ··· 63 68 const id = `${addonId}${getIdForState(history.state)}:${window.location}`; 64 69 const scrollY = 65 70 ref === 'window' ? window.scrollY : ref.current!.scrollTop; 66 - scrollPositions[id] = scrollY || 0; 71 + const scrollX = 72 + ref === 'window' ? window.scrollX : ref.current!.scrollLeft; 73 + scrollPositions[id] = [scrollX, scrollY]; 67 74 if (unsubscribe) { 68 75 unsubscribe(); 69 76 unsubscribe = undefined;
+3 -3
src/utils/observeScrollHeight.ts src/utils/observeScrollArea.ts
··· 13 13 } 14 14 }); 15 15 16 - export function observeScrollHeight( 16 + export function observeScrollArea( 17 17 element: HTMLElement, 18 - onScrollHeightChange: (scrollHeight: number) => void 18 + onAreaChange: (width: number, height: number) => void 19 19 ): () => void { 20 20 const listeners = resizeListeners.get(element) || []; 21 21 const isFirstListener = !listeners.length; ··· 26 26 const onResize = () => { 27 27 const scrollHeight = element.scrollHeight || 0; 28 28 if (!hasUnmounted && scrollHeight !== previousScrollHeight) { 29 - onScrollHeightChange(element.scrollHeight); 29 + onAreaChange(element.scrollWidth, element.scrollHeight); 30 30 previousScrollHeight = scrollHeight; 31 31 } 32 32 };