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 : });
|