uui-core/src/hooks/useScrollShadows.ts (92 lines of code) (raw):
import * as React from 'react';
interface UseScrollShadowsProps {
root?: HTMLElement;
}
interface UseScrollShadowsApi {
verticalTop: boolean;
verticalBottom: boolean;
horizontalLeft: boolean;
horizontalRight: boolean;
}
export function useScrollShadows({ root }: UseScrollShadowsProps): UseScrollShadowsApi {
const [vertical, setVertical] = React.useState({ top: false, bottom: false });
const [horizontal, setHorizontal] = React.useState({ left: false, right: false });
const resizeObserver = React.useRef<ResizeObserver>();
const isRtl = window?.document.dir === 'rtl';
const rtlMultiplier = isRtl ? -1 : 1;
function shouldHaveRightShadow(rootRight: UseScrollShadowsProps['root']) {
if (!rootRight) return false;
const { scrollLeft, clientWidth, scrollWidth } = rootRight;
return scrollWidth - clientWidth - scrollLeft * rtlMultiplier > 1 && !horizontal.right;
}
function shouldNotHaveRightShadow(rootRight: UseScrollShadowsProps['root']) {
const { scrollLeft, clientWidth, scrollWidth } = rootRight;
return scrollWidth - clientWidth - scrollLeft * rtlMultiplier <= 1 && horizontal.right;
}
function shouldHaveLeftShadow(rootLeft: UseScrollShadowsProps['root']) {
if (!rootLeft) return false;
return rootLeft.scrollLeft * rtlMultiplier > 0 && !horizontal.left;
}
function shouldNotHaveLeftShadow(rootLeft: UseScrollShadowsProps['root']) {
return rootLeft.scrollLeft === 0 && horizontal.left;
}
function shouldHaveTopShadow(rootTop: UseScrollShadowsProps['root']) {
if (!rootTop) return false;
return rootTop.scrollTop > 0 && !vertical.top;
}
function shouldNotHaveTopShadow(rootTop: UseScrollShadowsProps['root']) {
return rootTop.scrollTop === 0 && vertical.top;
}
function shouldHaveBottomShadow(rootBottom: UseScrollShadowsProps['root']) {
if (!rootBottom) return false;
const { scrollHeight, scrollTop, clientHeight } = rootBottom;
return scrollHeight - clientHeight - scrollTop > 1 && !vertical.bottom;
}
function shouldNotHaveBottomShadow(rootBottom: UseScrollShadowsProps['root']) {
if (!rootBottom) return false;
const { scrollHeight, scrollTop, clientHeight } = rootBottom;
return scrollHeight - clientHeight - scrollTop <= 1 && vertical.bottom;
}
const updateScrollShadows = React.useCallback(() => {
if (!root) return;
// Horizontal shadow states
if (shouldHaveLeftShadow(root)) setHorizontal({ ...horizontal, left: true });
else if (shouldNotHaveLeftShadow(root)) setHorizontal({ ...horizontal, left: false });
else if (shouldHaveRightShadow(root)) setHorizontal({ ...horizontal, right: true });
else if (shouldNotHaveRightShadow(root)) setHorizontal({ ...horizontal, right: false });
// Vertical shadow states
if (shouldHaveTopShadow(root)) setVertical({ ...vertical, top: true });
else if (shouldNotHaveTopShadow(root)) setVertical({ ...vertical, top: false });
else if (shouldHaveBottomShadow(root)) setVertical({ ...vertical, bottom: true });
else if (shouldNotHaveBottomShadow(root)) setVertical({ ...vertical, bottom: false });
}, [
root, vertical, horizontal, setVertical, setHorizontal, isRtl,
]);
React.useEffect(() => {
if (!root) return;
root.addEventListener('scroll', updateScrollShadows);
return () => root.removeEventListener('scroll', updateScrollShadows);
}, [
root, horizontal, setHorizontal, vertical, setVertical,
]);
React.useEffect(() => {
if (!root) return;
resizeObserver.current = new ResizeObserver((entries) => {
requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) return;
updateScrollShadows();
});
});
resizeObserver.current.observe(root);
return () => resizeObserver.current.disconnect();
}, [root, resizeObserver.current]);
React.useEffect(() => {
if (!root) return;
window.addEventListener('resize', updateScrollShadows);
return () => window.removeEventListener('resize', updateScrollShadows);
}, [updateScrollShadows, root]);
return {
verticalTop: vertical.top || shouldHaveTopShadow(root),
verticalBottom: vertical.bottom || shouldHaveBottomShadow(root),
horizontalLeft: horizontal.right || shouldHaveRightShadow(root),
horizontalRight: horizontal.left || shouldHaveLeftShadow(root),
};
}