diff --git a/packages/desktop-client/src/components/ScrollProvider.tsx b/packages/desktop-client/src/components/ScrollProvider.tsx index a7cb382f915..1921681e2af 100644 --- a/packages/desktop-client/src/components/ScrollProvider.tsx +++ b/packages/desktop-client/src/components/ScrollProvider.tsx @@ -34,12 +34,14 @@ const ScrollContext = createContext(undefined); type ScrollProviderProps = { scrollableRef: RefObject; isDisabled?: boolean; + delayMs?: number; children?: ReactNode; }; export function ScrollProvider({ scrollableRef, isDisabled, + delayMs = 10, children, }: ScrollProviderProps) { const previousScrollX = useRef(undefined); @@ -54,54 +56,40 @@ export function ScrollProvider({ const hasScrolledToEnd = useCallback( (direction: ScrollDirection, tolerance = 1) => { + const isAtStart = (currentCoordinate?: number) => + currentCoordinate !== undefined && currentCoordinate <= tolerance; + + const isAtEnd = ( + totalSize?: number, + currentCoordinate?: number, + viewportSize?: number, + ) => + totalSize !== undefined && + currentCoordinate !== undefined && + viewportSize !== undefined && + totalSize - currentCoordinate <= viewportSize + tolerance; + switch (direction) { - case 'up': - const hasScrolledToTop = () => { - if (scrollY.current !== undefined) { - return scrollY.current <= tolerance; - } - return false; - }; - return hasScrolledToTop(); - case 'down': - const hasScrolledToBottom = () => { - if ( - scrollHeight.current !== undefined && - scrollY.current !== undefined && - clientHeight.current !== undefined - ) { - return ( - scrollHeight.current - scrollY.current <= - clientHeight.current + tolerance - ); - } - return false; - }; - return hasScrolledToBottom(); - case 'left': - const hasScrollToLeft = () => { - if (scrollX.current !== undefined) { - return scrollX.current <= tolerance; - } - return false; - }; - return hasScrollToLeft(); - case 'right': - const hasScrolledToRight = () => { - if ( - scrollWidth.current !== undefined && - scrollX.current !== undefined && - clientWidth.current !== undefined - ) { - return ( - scrollWidth.current - scrollX.current <= - clientWidth.current + tolerance - ); - } - - return false; - }; - return hasScrolledToRight(); + case 'up': { + return isAtStart(scrollY.current); + } + case 'down': { + return isAtEnd( + scrollHeight.current, + scrollY.current, + clientHeight.current, + ); + } + case 'left': { + return isAtStart(scrollX.current); + } + case 'right': { + return isAtEnd( + scrollWidth.current, + scrollX.current, + clientWidth.current, + ); + } default: return false; } @@ -150,21 +138,27 @@ export function ScrollProvider({ if (target instanceof Element) { previousScrollX.current = scrollX.current; scrollX.current = target.scrollLeft; + scrollHeight.current = target.scrollHeight; + previousScrollY.current = scrollY.current; scrollY.current = target.scrollTop; - scrollHeight.current = target.scrollHeight; clientHeight.current = target.clientHeight; - listeners.current.forEach(listener => - listener({ - scrollX: scrollX.current!, - scrollY: scrollY.current!, - isScrolling, - hasScrolledToEnd, - }), - ); + const currentScrollX = scrollX.current; + const currentScrollY = scrollY.current; + + if (currentScrollX !== undefined && currentScrollY !== undefined) { + listeners.current.forEach(listener => + listener({ + scrollX: currentScrollX, + scrollY: currentScrollY, + isScrolling, + hasScrolledToEnd, + }), + ); + } } - }, 10); + }, delayMs); const ref = scrollableRef.current; @@ -176,7 +170,7 @@ export function ScrollProvider({ ref?.removeEventListener('scroll', listenToScroll, { capture: true, }); - }, [hasScrolledToEnd, isDisabled, isScrolling, scrollableRef]); + }, [delayMs, hasScrolledToEnd, isDisabled, isScrolling, scrollableRef]); const registerScrollListener: RegisterScrollListener = useCallback( listener => {