LCOV - code coverage report
Current view: top level - dashql-app/src/ui/foundations - vertical_scroll_shadows.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 52 52
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : 
       3              : import { classNames } from '../../utils/classnames.js';
       4              : import * as styles from './vertical_scroll_shadows.module.css';
       5              : 
       6            1 : const SCROLL_EDGE_TOLERANCE = 1;
       7              : 
       8              : export interface VerticalScrollOverflow {
       9              :     top: boolean;
      10              :     bottom: boolean;
      11              : }
      12              : 
      13            1 : export function getVerticalScrollOverflow(element: Pick<HTMLElement, 'clientHeight' | 'scrollHeight' | 'scrollTop'>): VerticalScrollOverflow {
      14            1 :     const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
      15            1 :     return {
      16            1 :         top: element.scrollTop > SCROLL_EDGE_TOLERANCE,
      17            1 :         bottom: maxScrollTop - element.scrollTop > SCROLL_EDGE_TOLERANCE,
      18            1 :     };
      19            1 : }
      20              : 
      21              : interface VerticalScrollShadowsProps {
      22              :     getScrollElement: () => HTMLElement | null;
      23              :     prominent?: boolean;
      24              :     refreshKey?: unknown;
      25              :     rightInset?: number;
      26              : }
      27              : 
      28            1 : const NO_OVERFLOW: VerticalScrollOverflow = { top: false, bottom: false };
      29              : 
      30            1 : export const VerticalScrollShadows: React.FC<VerticalScrollShadowsProps> = ({
      31            1 :     getScrollElement,
      32            1 :     prominent = false,
      33            1 :     refreshKey,
      34            1 :     rightInset = 0,
      35            1 : }) => {
      36            1 :     const [overflow, setOverflow] = React.useState(NO_OVERFLOW);
      37            1 :     const updateOverflow = React.useCallback(() => {
      38            1 :         const element = getScrollElement();
      39            1 :         const next = element == null ? NO_OVERFLOW : getVerticalScrollOverflow(element);
      40            1 :         setOverflow(current => current.top === next.top && current.bottom === next.bottom ? current : next);
      41            1 :     }, [getScrollElement]);
      42              : 
      43            1 :     React.useLayoutEffect(() => {
      44            1 :         const element = getScrollElement();
      45            1 :         updateOverflow();
      46            1 :         if (element == null) return;
      47              : 
      48            1 :         element.addEventListener('scroll', updateOverflow, { passive: true });
      49            1 :         const resizeObserver = new ResizeObserver(updateOverflow);
      50            1 :         resizeObserver.observe(element);
      51            1 :         return () => {
      52            1 :             element.removeEventListener('scroll', updateOverflow);
      53            1 :             resizeObserver.disconnect();
      54            1 :         };
      55            1 :     }, [getScrollElement, refreshKey, updateOverflow]);
      56              : 
      57            1 :     const insetStyle = rightInset === 0 ? undefined : { right: rightInset };
      58            1 :     return (
      59            1 :         <>
      60            1 :             <div
      61            1 :                 className={classNames(styles.shadow, styles.top, prominent && styles.prominent)}
      62            1 :                 style={insetStyle}
      63            1 :                 data-scroll-shadow="top"
      64            1 :                 data-visible={overflow.top}
      65            1 :                 aria-hidden="true"
      66            1 :             />
      67            1 :             <div
      68            1 :                 className={classNames(styles.shadow, styles.bottom, prominent && styles.prominent)}
      69            1 :                 style={insetStyle}
      70            1 :                 data-scroll-shadow="bottom"
      71            1 :                 data-visible={overflow.bottom}
      72            1 :                 aria-hidden="true"
      73            1 :             />
      74            1 :         </>
      75              :     );
      76            1 : };
        

Generated by: LCOV version 2.0-1