UNPKG

@ui5/webcomponents-react

Version:

React Wrapper for UI5 Web Components and additional components

47 lines (45 loc) 1.81 kB
import { useEffect, useRef, useState } from 'react'; export function useSyncScroll(refContent, refScrollbar, isScrollable, disabled = false) { const isProgrammatic = useRef(false); const [isMounted, setIsMounted] = useState(false); // DOM scrollTop manipulation, not React state // eslint-disable-next-line react-hooks/immutability useEffect(() => { if (disabled || !isScrollable) { return; } const content = refContent.current; const scrollbar = refScrollbar.current; if (!content || !scrollbar || !isMounted) { // Forces a second effect run so refs are populated // eslint-disable-next-line react-hooks/set-state-in-effect setIsMounted(true); return; } // Is a React ref // eslint-disable-next-line react-hooks/immutability scrollbar.scrollTop = content.scrollTop; const sync = source => { const sourceEl = source === 'content' ? content : scrollbar; const targetEl = source === 'content' ? scrollbar : content; if (!isProgrammatic.current && targetEl.scrollTop !== sourceEl.scrollTop) { isProgrammatic.current = true; targetEl.scrollTop = sourceEl.scrollTop; // Clear the flag on next frame requestAnimationFrame(() => isProgrammatic.current = false); } }; const onScrollContent = () => sync('content'); const onScrollScrollbar = () => sync('scrollbar'); content.addEventListener('scroll', onScrollContent, { passive: true }); scrollbar.addEventListener('scroll', onScrollScrollbar, { passive: true }); return () => { content.removeEventListener('scroll', onScrollContent); scrollbar.removeEventListener('scroll', onScrollScrollbar); }; }, [isMounted, refContent, refScrollbar, disabled, isScrollable]); }