LCOV - code coverage report
Current view: top level - dashql-app/src/views/query_result - data_table_sticky_views.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 72.1 % 111 80
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : import * as ReactDOM from 'react-dom';
       3              : import * as styles from './data_table.module.css';
       4              : 
       5              : import type { DataCellData, HeaderNameCellProps, HeaderPlotsCellProps } from './data_table_cell.js';
       6              : import { DataCell, HeaderNameCell, HeaderPlotsCell, TableColumnHeader } from './data_table_cell.js';
       7              : import type { DataTablePortalContainers } from './data_table_portals.js';
       8              : import { ChevronDownIcon, ChevronUpIcon } from '../../ui/foundations/symbol_icon.js';
       9              : import { arePropsEqualWithStyle } from './memo_props.js';
      10              : 
      11              : /// Row header cells of the sticky column only re-render when their own props change
      12            1 : const StickyRowHeaderCell = React.memo(DataCell, arePropsEqualWithStyle);
      13            1 : const STICKY_ROW_HEADER_ARIA = { 'aria-colindex': 1, role: 'gridcell' } as const;
      14              : 
      15              : interface StickyHeadersProps {
      16              :     container: DataTablePortalContainers['header'] | null;
      17              :     totalWidth: number;
      18              :     headerHeight: number;
      19              :     firstColumnWidth: number;
      20              :     leftSpacerWidth: number;
      21              :     rightSpacerWidth: number;
      22              :     firstRenderedColumn: number;
      23              :     renderedColumnCount: number;
      24              :     getColumnWidth: (columnIndex: number) => number;
      25              :     columnHeader: TableColumnHeader;
      26              :     plotsHeight: number;
      27              :     plotsCollapsed: boolean;
      28              :     onTogglePlots: () => void;
      29              :     nameCellProps: Omit<HeaderNameCellProps, 'columnIndex' | 'style' | 'sortAscending'> & {
      30              :         getSortDirection: (fieldId: number) => boolean | null;
      31              :     };
      32              :     plotCellProps: Omit<HeaderPlotsCellProps, 'columnIndex' | 'style'>;
      33              : }
      34              : 
      35            1 : export const DataTableStickyHeaders = React.memo((props: StickyHeadersProps) => {
      36            1 :     if (props.container == null) return null;
      37            1 :     const renderNameCell = (columnIndex: number, sticky: boolean) => (
      38            1 :         <HeaderNameCell
      39            1 :             key={`header-0-${columnIndex}`}
      40            1 :             columnIndex={columnIndex}
      41            1 :             style={{
      42            1 :                 width: props.getColumnWidth(columnIndex),
      43            1 :                 height: 32,
      44            1 :                 ...(!sticky ? { flexShrink: 0 } : {}),
      45            1 :             }}
      46            1 :             table={props.nameCellProps.table}
      47            1 :             gridLayout={props.nameCellProps.gridLayout}
      48            1 :             dataFrame={props.nameCellProps.dataFrame}
      49            1 :             rightmostVisibleColumn={props.nameCellProps.rightmostVisibleColumn}
      50            1 :             sortAscending={props.nameCellProps.getSortDirection(props.nameCellProps.gridLayout.arrowFieldByColumnIndex[columnIndex])}
      51            1 :             onOrderByColumn={props.nameCellProps.onOrderByColumn}
      52            1 :             onShowTable={props.nameCellProps.onShowTable}
      53            1 :         />
      54              :     );
      55            1 :     const renderPlotCell = (columnIndex: number, sticky: boolean) => (
      56            0 :         <HeaderPlotsCell
      57            0 :             key={`header-1-${columnIndex}`}
      58            0 :             {...props.plotCellProps}
      59            0 :             columnIndex={columnIndex}
      60            0 :             style={{
      61            0 :                 width: props.getColumnWidth(columnIndex),
      62            0 :                 height: props.plotsHeight,
      63            0 :                 ...(!sticky ? { flexShrink: 0 } : {}),
      64            0 :             }}
      65            0 :         />
      66              :     );
      67            1 :     const PlotsChevron = props.plotsCollapsed ? ChevronDownIcon : ChevronUpIcon;
      68              : 
      69            1 :     return ReactDOM.createPortal(
      70            1 :         <div
      71            1 :             className={styles.sticky_header_container}
      72            1 :             style={{ position: 'sticky', top: 0, zIndex: 10, width: props.totalWidth, height: props.headerHeight }}
      73              :         >
      74            1 :             <div className={styles.sticky_header_row} style={{ display: 'flex', height: 32 }}>
      75            1 :                 <div style={{ position: 'sticky', left: 0, zIndex: 11, flexShrink: 0 }}>{renderNameCell(0, true)}</div>
      76            1 :                 {props.leftSpacerWidth > 0 && <div style={{ width: props.leftSpacerWidth, flexShrink: 0 }} />}
      77            1 :                 {Array.from({ length: props.renderedColumnCount }, (_, index) => renderNameCell(props.firstRenderedColumn + index, false))}
      78            1 :                 {props.rightSpacerWidth > 0 && <div style={{ width: props.rightSpacerWidth, flexShrink: 0 }} />}
      79            1 :             </div>
      80            1 :             {props.columnHeader === TableColumnHeader.WithColumnPlots && (
      81            0 :                 <div className={styles.sticky_header_row} style={{ display: 'flex', height: props.plotsHeight }}>
      82            0 :                     <div style={{ position: 'sticky', left: 0, zIndex: 11, flexShrink: 0 }}>
      83            0 :                         <button
      84            0 :                             type="button"
      85            0 :                             className={styles.plots_toggle}
      86            0 :                             style={{ width: props.firstColumnWidth, height: props.plotsHeight }}
      87            0 :                             aria-label={props.plotsCollapsed ? 'Show column plots' : 'Hide column plots'}
      88            0 :                             aria-expanded={!props.plotsCollapsed}
      89            0 :                             title={props.plotsCollapsed ? 'Show column plots' : 'Hide column plots'}
      90            0 :                             onClick={props.onTogglePlots}
      91              :                         >
      92            0 :                             <PlotsChevron size={props.plotsCollapsed ? 12 : 16} />
      93            0 :                         </button>
      94            0 :                     </div>
      95            0 :                     {props.plotsCollapsed ? (
      96              :                         // The plots are not rendered at all, which also holds off their filtered aggregations
      97            0 :                         <div className={styles.plots_collapsed_cell} style={{ width: props.totalWidth - props.firstColumnWidth }} />
      98              :                     ) : (
      99            0 :                         <>
     100            0 :                             {props.leftSpacerWidth > 0 && <div style={{ width: props.leftSpacerWidth, flexShrink: 0 }} />}
     101            0 :                             {Array.from({ length: props.renderedColumnCount }, (_, index) => renderPlotCell(props.firstRenderedColumn + index, false))}
     102            0 :                             {props.rightSpacerWidth > 0 && <div style={{ width: props.rightSpacerWidth, flexShrink: 0 }} />}
     103            0 :                         </>
     104              :                     )}
     105            0 :                 </div>
     106              :             )}
     107            1 :         </div>,
     108            1 :         props.container,
     109            1 :     );
     110            1 : });
     111              : 
     112              : interface StickyColumnProps {
     113              :     container: DataTablePortalContainers['data'] | null;
     114              :     firstColumnWidth: number;
     115              :     totalDataHeight: number;
     116              :     startRow: number;
     117              :     stopRow: number;
     118              :     gridData: DataCellData;
     119              : }
     120              : 
     121            1 : export const DataTableStickyColumn = React.memo((props: StickyColumnProps) => {
     122            1 :     if (props.container == null) return null;
     123            1 :     const visibleCount = Math.max(0, props.stopRow - props.startRow + 1);
     124            1 :     return ReactDOM.createPortal(
     125            1 :         <div
     126            1 :             className={styles.sticky_column_container}
     127            1 :             style={{
     128            1 :                 position: 'sticky',
     129            1 :                 left: 0,
     130            1 :                 width: props.firstColumnWidth,
     131            1 :                 height: props.totalDataHeight,
     132            1 :                 marginTop: -props.totalDataHeight,
     133            1 :                 zIndex: 5,
     134            1 :                 pointerEvents: 'none',
     135            1 :             }}
     136              :         >
     137            1 :             {Array.from({ length: visibleCount }, (_, index) => {
     138            1 :                 const rowIndex = props.startRow + index;
     139            1 :                 return (
     140            1 :                     <div
     141            1 :                         key={`col0-${rowIndex}`}
     142            1 :                         style={{
     143            1 :                             position: 'absolute',
     144            1 :                             top: rowIndex * 26,
     145            1 :                             left: 0,
     146            1 :                             width: props.firstColumnWidth,
     147            1 :                             height: 26,
     148            1 :                             pointerEvents: 'auto',
     149            1 :                         }}
     150              :                     >
     151            1 :                         <StickyRowHeaderCell
     152            1 :                             ariaAttributes={STICKY_ROW_HEADER_ARIA}
     153            1 :                             rowIndex={rowIndex}
     154            1 :                             columnIndex={0}
     155            1 :                             style={{ width: props.firstColumnWidth, height: 26 }}
     156            1 :                             {...props.gridData}
     157            1 :                             hideRowHeader={false}
     158            1 :                         />
     159            1 :                     </div>
     160              :                 );
     161            1 :             })}
     162            1 :         </div>,
     163            1 :         props.container,
     164            1 :     );
     165            1 : });
        

Generated by: LCOV version 2.0-1