@alaarab/ogrid-react-fluent 2.0.11 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/ColumnChooser/ColumnChooser.js +10 -128
  3. package/dist/esm/ColumnChooser/ColumnChooser.module.css +45 -10
  4. package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.js +16 -36
  5. package/dist/esm/ColumnHeaderMenu/ColumnHeaderMenu.js +9 -3
  6. package/dist/esm/ColumnHeaderMenu/ColumnHeaderMenu.module.css +7 -7
  7. package/dist/esm/DataGridTable/DataGridTable.js +118 -295
  8. package/dist/esm/DataGridTable/DataGridTable.module.css +546 -437
  9. package/dist/esm/DataGridTable/DropIndicator.js +5 -0
  10. package/dist/esm/DataGridTable/EmptyState.js +5 -0
  11. package/dist/esm/DataGridTable/GridContextMenu.js +10 -32
  12. package/dist/esm/DataGridTable/LoadingOverlay.js +6 -0
  13. package/dist/esm/{FluentDataTable/FluentDataTable.js → OGrid/OGrid.js} +0 -2
  14. package/dist/esm/OGrid/index.js +1 -0
  15. package/dist/esm/index.js +1 -1
  16. package/dist/types/ColumnChooser/ColumnChooser.d.ts +2 -8
  17. package/dist/types/ColumnHeaderFilter/ColumnHeaderFilter.d.ts +2 -20
  18. package/dist/types/DataGridTable/DropIndicator.d.ts +7 -0
  19. package/dist/types/DataGridTable/EmptyState.d.ts +11 -0
  20. package/dist/types/DataGridTable/LoadingOverlay.d.ts +6 -0
  21. package/dist/types/{FluentDataTable/FluentDataTable.d.ts → OGrid/OGrid.d.ts} +0 -2
  22. package/dist/types/OGrid/index.d.ts +1 -0
  23. package/dist/types/PaginationControls/PaginationControls.d.ts +2 -10
  24. package/dist/types/index.d.ts +1 -1
  25. package/package.json +3 -3
  26. package/dist/esm/FluentDataTable/index.js +0 -1
  27. package/dist/types/FluentDataTable/index.d.ts +0 -1
@@ -1,325 +1,148 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
- import { useMemo, useRef, useEffect, useCallback } from 'react';
3
+ import { useCallback, useMemo } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
- import { DataGrid, DataGridHeader, DataGridRow, DataGridHeaderCell, DataGridBody, DataGridCell, createTableColumn, Spinner, Checkbox, Popover, PopoverSurface, } from '@fluentui/react-components';
5
+ import { Table, TableHeader, TableRow, TableHeaderCell, TableBody, TableCell, Checkbox, Popover, PopoverSurface, } from '@fluentui/react-components';
6
6
  import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
7
7
  import { ColumnHeaderMenu } from '../ColumnHeaderMenu';
8
8
  import { InlineCellEditor } from './InlineCellEditor';
9
9
  import { StatusBar } from './StatusBar';
10
10
  import { GridContextMenu } from './GridContextMenu';
11
- import { useDataGridState, useColumnReorder, useVirtualScroll, useLatestRef, getHeaderFilterConfig, getCellRenderDescriptor, buildHeaderRows, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-react';
11
+ import { EmptyState } from './EmptyState';
12
+ import { LoadingOverlay } from './LoadingOverlay';
13
+ import { DropIndicator } from './DropIndicator';
14
+ import { useDataGridTableOrchestration, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, DEFAULT_MIN_COLUMN_WIDTH, GRID_ROOT_STYLE, CURSOR_CELL_STYLE, POPOVER_ANCHOR_STYLE, PREVENT_DEFAULT, NOOP, } from '@alaarab/ogrid-react';
12
15
  import styles from './DataGridTable.module.css';
13
- // Module-scope stable constants (avoid per-render allocations)
14
- const gridRootStyle = {
15
- position: 'relative',
16
- flex: 1,
17
- minHeight: 0,
18
- display: 'flex',
19
- flexDirection: 'column',
20
- };
21
- const CURSOR_CELL_STYLE = { cursor: 'cell' };
22
- const NUMERIC_STYLE = { justifyContent: 'flex-end', textAlign: 'right' };
23
- const BOOLEAN_STYLE = { justifyContent: 'center', textAlign: 'center' };
24
- const EDITABLE_NUMERIC_STYLE = { cursor: 'cell', justifyContent: 'flex-end', textAlign: 'right' };
25
- const EDITABLE_BOOLEAN_STYLE = { cursor: 'cell', justifyContent: 'center', textAlign: 'center' };
26
- const POPOVER_ANCHOR_STYLE = { minHeight: '100%', minWidth: 40 };
27
- const PREVENT_DEFAULT = (e) => { e.preventDefault(); };
28
- const NOOP = () => { };
29
16
  function GridRowInner(props) {
30
- const { item, rowId, rowIndex, isSelected, cellClassMap, handleSingleRowClick, activeCell } = props;
31
- const rowClassName = `${isSelected ? styles.selectedRow : ''}${activeCell !== null && rowIndex === activeCell.rowIndex ? (isSelected ? ` ${styles.activeRow}` : styles.activeRow) : ''}` || undefined;
32
- return (
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- _jsx(DataGridRow, { className: rowClassName, onClick: () => handleSingleRowClick(rowId), children: ({ renderCell, columnId }) => (_jsx(DataGridCell, { className: cellClassMap[String(columnId)] || undefined, children: renderCell(item) })) }));
17
+ const { item, rowIndex, rowId, isSelected, visibleCols, columnMeta, renderCellContent, handleSingleRowClick, handleRowCheckboxChange, lastMouseShiftRef, hasCheckboxCol, hasRowNumbersCol, rowNumberOffset, } = props;
18
+ return (_jsxs(TableRow, { className: isSelected ? styles.selectedRow : undefined, "data-row-id": rowId, onClick: handleSingleRowClick, children: [hasCheckboxCol && (_jsx(TableCell, { className: styles.selectionCellWrapper, children: _jsx("div", { className: styles.selectionCellInner, "data-row-index": rowIndex, "data-col-index": 0, onClick: (e) => e.stopPropagation(), children: _jsx(Checkbox, { checked: isSelected, onChange: (e, data) => {
19
+ handleRowCheckboxChange(rowId, !!data.checked, rowIndex, lastMouseShiftRef.current);
20
+ }, "aria-label": `Select row ${rowIndex + 1}` }) }) })), hasRowNumbersCol && (_jsx(TableCell, { className: styles.rowNumberCellWrapper, children: _jsx("div", { className: styles.rowNumberCellInner, children: rowNumberOffset + rowIndex + 1 }) })), visibleCols.map((col, colIdx) => (_jsx(TableCell, { "data-column-id": col.columnId, className: columnMeta.cellClasses[col.columnId] || undefined, style: columnMeta.cellStyles[col.columnId], children: renderCellContent(item, col, rowIndex, colIdx) }, col.columnId)))] }));
35
21
  }
36
22
  const GridRow = React.memo(GridRowInner, areGridRowPropsEqual);
37
23
  function DataGridTableInner(props) {
38
- const wrapperRef = useRef(null);
39
- const tableContainerRef = useRef(null);
40
- const state = useDataGridState({ props, wrapperRef });
41
- const { layout, rowSelection: rowSel, editing, interaction, contextMenu: ctxMenu, viewModels, pinning } = state;
42
- const { flatColumns, visibleCols, totalColCount, hasCheckboxCol, hasRowNumbersCol, colOffset, rowIndexByRowId, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides } = layout;
43
- const { selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
44
- const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
45
- const { activeCell, setActiveCell, handleCellMouseDown, handleSelectAllCells, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange, copyRange, canUndo, canRedo, onUndo, onRedo, isDragging } = interaction;
46
- const handlePasteVoid = useCallback(() => { void handlePaste(); }, [handlePaste]);
47
- const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
48
- const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
49
- const { items, columns, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, visibleColumns, columnOrder, onColumnOrderChange, columnReorder, virtualScroll, density = 'normal', pinnedColumns, currentPage = 1, pageSize: propPageSize = 25, } = props;
50
- // Calculate row number offset for pagination
51
- const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * propPageSize : 0;
52
- // Memoize header rows (recursive tree traversal)
53
- const headerRows = useMemo(() => buildHeaderRows(columns, visibleColumns), [columns, visibleColumns]);
54
- const hasGroupHeaders = headerRows.length > 1;
55
- const fitToContent = layoutMode === 'content';
56
- const { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown } = useColumnReorder({
57
- columns: visibleCols,
58
- columnOrder,
59
- onColumnOrderChange,
60
- enabled: columnReorder === true,
61
- pinnedColumns,
62
- wrapperRef,
63
- });
64
- const virtualScrollEnabled = virtualScroll?.enabled === true;
65
- const virtualRowHeight = virtualScroll?.rowHeight ?? 36;
66
- const { visibleRange } = useVirtualScroll({
67
- totalRows: items.length,
68
- rowHeight: virtualRowHeight,
69
- enabled: virtualScrollEnabled,
70
- overscan: virtualScroll?.overscan,
71
- containerRef: wrapperRef,
72
- });
73
- const columnSizingOptions = useMemo(() => {
74
- const acc = {};
75
- if (hasCheckboxCol) {
76
- acc['__selection__'] = { minWidth: CHECKBOX_COLUMN_WIDTH, defaultWidth: CHECKBOX_COLUMN_WIDTH, idealWidth: CHECKBOX_COLUMN_WIDTH };
77
- }
78
- if (hasRowNumbersCol) {
79
- acc['__row_number__'] = { minWidth: ROW_NUMBER_COLUMN_WIDTH, defaultWidth: ROW_NUMBER_COLUMN_WIDTH, idealWidth: ROW_NUMBER_COLUMN_WIDTH };
80
- }
81
- visibleCols.forEach((c) => {
82
- const minW = c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
83
- const defaultW = c.defaultWidth ?? 120;
84
- const base = c.idealWidth ?? Math.max(minW, defaultW);
85
- const override = columnSizingOverrides[c.columnId];
86
- const w = override ? Math.max(minW, override.widthPx) : base;
87
- acc[c.columnId] = {
88
- minWidth: minW,
89
- defaultWidth: w,
90
- idealWidth: w,
91
- };
92
- });
93
- return acc;
94
- }, [visibleCols, columnSizingOverrides, hasCheckboxCol, hasRowNumbersCol]);
95
- const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
96
- // Pre-compute column class maps (avoids per-cell .filter(Boolean).join(' '))
97
- const { cellClassMap, headerClassMap } = useMemo(() => {
98
- const cm = {};
99
- const hm = {};
24
+ const o = useDataGridTableOrchestration({ props });
25
+ const { wrapperRef, tableContainerRef, lastMouseShiftRef, interaction, pinning, handleResizeStart, getColumnWidth, isReorderDragging, dropIndicatorX, handleHeaderMouseDown, virtualScrollEnabled, visibleRange, items, getRowId, emptyState, rowSelection, freezeRows, freezeCols, isLoading, loadingMessage, ariaLabel, ariaLabelledBy, visibleColumns, columnOrder, columnReorder, density, rowNumberOffset, headerRows, allowOverflowX, fitToContent, editCallbacks, interactionHandlers, cellDescriptorInputRef, pendingEditorValueRef, popoverAnchorElRef, handleSingleRowClick, handlePasteVoid, visibleCols, totalColCount, hasCheckboxCol, hasRowNumbersCol, colOffset, containerWidth, minTableWidth, columnSizingOverrides, selectedRowIds, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, editingCell, setPopoverAnchorEl, cancelPopoverEdit, setActiveCell, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, cutRange, copyRange, canUndo, canRedo, onUndo, onRedo, isDragging, menuPosition, closeContextMenu, headerFilterInput, statusBarConfig, showEmptyInGrid, onCellError, headerMenu, } = o;
26
+ // Pre-compute column styles and classNames (avoids per-cell object creation in the row loop)
27
+ const columnMeta = useMemo(() => {
28
+ const cellStyles = {};
29
+ const cellClasses = {};
30
+ const hdrStyles = {};
31
+ const hdrClasses = {};
100
32
  for (let i = 0; i < visibleCols.length; i++) {
101
33
  const col = visibleCols[i];
34
+ const columnWidth = getColumnWidth(col);
35
+ const hasExplicitWidth = !!(columnSizingOverrides[col.columnId] || col.idealWidth != null || col.defaultWidth != null);
102
36
  const isFreezeCol = freezeCols != null && freezeCols >= 1 && i < freezeCols;
103
- const isPinnedLeft = col.pinned === 'left';
104
- const isPinnedRight = col.pinned === 'right';
37
+ const isPinnedLeft = pinning.pinnedColumns[col.columnId] === 'left';
38
+ const isPinnedRight = pinning.pinnedColumns[col.columnId] === 'right';
39
+ const hasResizeOverride = !!columnSizingOverrides[col.columnId];
40
+ const isPinned = isPinnedLeft || isPinnedRight;
41
+ cellStyles[col.columnId] = {
42
+ minWidth: hasResizeOverride ? columnWidth : (col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH),
43
+ width: hasExplicitWidth ? columnWidth : undefined,
44
+ maxWidth: hasExplicitWidth ? columnWidth : undefined,
45
+ textAlign: col.type === 'numeric' ? 'right' : col.type === 'boolean' ? 'center' : undefined,
46
+ // Fluent UI's TableCell injects atomic CSS `position: relative` which overrides the
47
+ // shared `.pinnedColLeft { position: sticky }` class. Inline style wins over atomic CSS.
48
+ ...(isPinned ? { position: 'sticky' } : undefined),
49
+ ...(isPinnedLeft && pinning.leftOffsets[col.columnId] != null ? { left: pinning.leftOffsets[col.columnId] } : undefined),
50
+ ...(isPinnedRight && pinning.rightOffsets[col.columnId] != null ? { right: pinning.rightOffsets[col.columnId] } : undefined),
51
+ };
52
+ hdrStyles[col.columnId] = {
53
+ minWidth: hasResizeOverride ? columnWidth : (col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH),
54
+ width: hasExplicitWidth ? columnWidth : undefined,
55
+ maxWidth: hasExplicitWidth ? columnWidth : undefined,
56
+ ...(isPinned ? { position: 'sticky' } : undefined),
57
+ ...(isPinnedLeft && pinning.leftOffsets[col.columnId] != null ? { left: pinning.leftOffsets[col.columnId] } : undefined),
58
+ ...(isPinnedRight && pinning.rightOffsets[col.columnId] != null ? { right: pinning.rightOffsets[col.columnId] } : undefined),
59
+ };
105
60
  const parts = [];
106
61
  if (isFreezeCol)
107
62
  parts.push(styles.freezeCol);
108
63
  if (isFreezeCol && i === 0)
109
64
  parts.push(styles.freezeColFirst);
110
- if (isPinnedLeft) {
111
- parts.push(styles.pinnedCell);
112
- parts.push(styles.pinnedLeft);
113
- }
114
- if (isPinnedRight) {
115
- parts.push(styles.pinnedCell);
116
- parts.push(styles.pinnedRight);
117
- }
118
- cm[col.columnId] = parts.join(' ');
119
- hm[col.columnId] = parts.join(' ');
65
+ if (isPinnedLeft)
66
+ parts.push(styles.pinnedColLeft);
67
+ if (isPinnedRight)
68
+ parts.push(styles.pinnedColRight);
69
+ const cn = parts.join(' ');
70
+ cellClasses[col.columnId] = cn;
71
+ hdrClasses[col.columnId] = cn;
72
+ }
73
+ return { cellStyles, cellClasses, hdrStyles, hdrClasses };
74
+ }, [visibleCols, getColumnWidth, columnSizingOverrides, freezeCols, pinning.pinnedColumns, pinning.leftOffsets, pinning.rightOffsets]);
75
+ // renderCellContent reads volatile state from refs -- keeps function identity stable so
76
+ // GridRow's React.memo comparator can skip rows whose selection state hasn't changed.
77
+ const renderCellContent = useCallback((item, col, rowIndex, colIdx) => {
78
+ const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInputRef.current);
79
+ const rowId = getRowId(item);
80
+ let content;
81
+ if (descriptor.mode === 'editing-inline') {
82
+ content = _jsx(InlineCellEditor, { ...buildInlineEditorProps(item, col, descriptor, editCallbacks) });
120
83
  }
121
- cm['__selection__'] = styles.selectionCellWrapper;
122
- hm['__selection__'] = styles.selectionHeaderCellWrapper;
123
- return { cellClassMap: cm, headerClassMap: hm };
124
- }, [visibleCols, freezeCols]);
125
- // Refs for volatile state (read inside fluentColumns render closures without adding to deps)
126
- const cellDescriptorInputRef = useLatestRef(cellDescriptorInput);
127
- const selectedRowIdsRef = useLatestRef(selectedRowIds);
128
- const activeCellRef = useLatestRef(activeCell);
129
- const pendingEditorValueRef = useLatestRef(pendingEditorValue);
130
- const popoverAnchorElRef = useLatestRef(popoverAnchorEl);
131
- const allSelectedRef = useLatestRef(allSelected);
132
- const someSelectedRef = useLatestRef(someSelected);
133
- // Callback refs — stabilize fluentColumns memo (these change identity on state updates
134
- // but the columns structure doesn't need rebuilding for that)
135
- const headerFilterInputRef = useLatestRef(headerFilterInput);
136
- const commitCellEditRef = useLatestRef(commitCellEdit);
137
- const cancelPopoverEditRef = useLatestRef(cancelPopoverEdit);
138
- const handleCellMouseDownRef = useLatestRef(handleCellMouseDown);
139
- const handleFillHandleMouseDownRef = useLatestRef(handleFillHandleMouseDown);
140
- const handleCellContextMenuRef = useLatestRef(handleCellContextMenu);
141
- const setActiveCellRef = useLatestRef(setActiveCell);
142
- const setEditingCellRef = useLatestRef(setEditingCell);
143
- const setPendingEditorValueRef = useLatestRef(setPendingEditorValue);
144
- const handleSelectAllRef = useLatestRef(handleSelectAll);
145
- const handleRowCheckboxChangeRef = useLatestRef(handleRowCheckboxChange);
146
- const rowIndexByRowIdRef = useLatestRef(rowIndexByRowId);
147
- const handleHeaderMouseDownRef = useLatestRef(handleHeaderMouseDown);
148
- const isReorderDraggingRef = useLatestRef(isReorderDragging);
149
- const fluentColumns = useMemo(() => {
150
- const dataCols = visibleCols.map((col, colIdx) => createTableColumn({
151
- columnId: col.columnId,
152
- compare: col.compare ?? (() => 0),
153
- renderHeaderCell: () => (_jsx("div", { "data-column-id": col.columnId, style: columnReorder ? { cursor: isReorderDraggingRef.current ? 'grabbing' : 'grab' } : undefined, onMouseDown: columnReorder ? (e) => handleHeaderMouseDownRef.current(col.columnId, e) : undefined, children: _jsxs("div", { className: styles.headerCellContent, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInputRef.current) }), _jsx("button", { className: styles.headerMenuTrigger, onClick: (e) => {
154
- e.stopPropagation();
155
- pinning.headerMenu.open(col.columnId, e.currentTarget);
156
- }, "aria-label": "Column options", title: "Column options", children: "\u22EE" })] }) })),
157
- renderCell: (item) => {
158
- const rowId = getRowId(item);
159
- const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
160
- const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInputRef.current);
161
- let cellContent;
162
- if (descriptor.mode === 'editing-inline') {
163
- cellContent = _jsx(InlineCellEditor, { ...buildInlineEditorProps(item, col, descriptor, { commitCellEdit: commitCellEditRef.current, setEditingCell: setEditingCellRef.current }) });
164
- }
165
- else if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
166
- const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValueRef.current, { setPendingEditorValue: setPendingEditorValueRef.current, commitCellEdit: commitCellEditRef.current, cancelPopoverEdit: cancelPopoverEditRef.current });
167
- const CustomEditor = col.cellEditor;
168
- cellContent = (_jsxs(_Fragment, { children: [_jsx("div", { ref: (el) => { if (el)
169
- setPopoverAnchorEl(el); }, style: POPOVER_ANCHOR_STYLE, "aria-hidden": true }), _jsx(Popover, { open: !!popoverAnchorElRef.current, onOpenChange: (_, data) => { if (!data.open)
170
- cancelPopoverEditRef.current(); }, positioning: { target: popoverAnchorElRef.current ?? undefined }, children: _jsx(PopoverSurface, { children: _jsx(CustomEditor, { ...editorProps }) }) })] }));
171
- }
172
- else {
173
- const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
174
- const cellStyle = resolveCellStyle(col, item);
175
- const styledContent = cellStyle ? _jsx("span", { style: cellStyle, children: content }) : content;
176
- const cellClassNames = `${styles.cellContent}${descriptor.isActive && !descriptor.isInRange ? ` ${styles.activeCellContent}` : ''}${descriptor.isInRange ? ` ${styles.cellInRange}` : ''}${descriptor.isInCutRange ? ` ${styles.cellCut}` : ''}${descriptor.isInCopyRange ? ` ${styles.cellCopied}` : ''}`;
177
- const colType = col.type;
178
- const interactionProps = getCellInteractionProps(descriptor, col.columnId, { handleCellMouseDown: handleCellMouseDownRef.current, setActiveCell: setActiveCellRef.current, setEditingCell: setEditingCellRef.current, handleCellContextMenu: handleCellContextMenuRef.current });
179
- // Select stable style constant by type + editability
180
- const computedStyle = descriptor.canEditAny
181
- ? (colType === 'numeric' ? EDITABLE_NUMERIC_STYLE : colType === 'boolean' ? EDITABLE_BOOLEAN_STYLE : CURSOR_CELL_STYLE)
182
- : (colType === 'numeric' ? NUMERIC_STYLE : colType === 'boolean' ? BOOLEAN_STYLE : undefined);
183
- cellContent = (_jsxs("div", { className: cellClassNames, ...interactionProps, style: computedStyle, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: (e) => handleFillHandleMouseDownRef.current(e), "aria-label": "Fill handle" }))] }));
184
- }
185
- return (_jsx(CellErrorBoundary, { onError: onCellError, children: cellContent }, `${rowId}-${col.columnId}`));
186
- },
187
- }));
188
- if (hasCheckboxCol) {
189
- const checkboxCol = createTableColumn({
190
- columnId: '__selection__',
191
- compare: () => 0,
192
- renderHeaderCell: () => (_jsx("div", { className: styles.selectionHeaderCell, children: _jsx(Checkbox, { checked: allSelectedRef.current ? true : someSelectedRef.current ? 'mixed' : false, onChange: (_, data) => handleSelectAllRef.current(!!data.checked), "aria-label": "Select all rows" }) })),
193
- renderCell: (item) => {
194
- const rowId = getRowId(item);
195
- const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
196
- const isChecked = selectedRowIdsRef.current.has(rowId);
197
- const ac = activeCellRef.current;
198
- const isActive = ac?.rowIndex === rowIndex && ac?.columnIndex === 0;
199
- return (_jsx("div", { className: `${styles.selectionCell} ${isActive ? styles.activeCellContent : ''}`, "data-row-index": rowIndex, "data-col-index": 0, onClick: (e) => {
200
- e.stopPropagation();
201
- setActiveCellRef.current({ rowIndex, columnIndex: 0 });
202
- }, children: _jsx(Checkbox, { checked: isChecked, onChange: (e, data) => {
203
- handleRowCheckboxChangeRef.current(rowId, !!data.checked, rowIndex, e.nativeEvent.shiftKey);
204
- }, "aria-label": `Select row ${rowIndex + 1}` }) }));
205
- },
206
- });
207
- const cols = [checkboxCol];
208
- if (hasRowNumbersCol) {
209
- const rowNumberCol = createTableColumn({
210
- columnId: '__row_number__',
211
- compare: () => 0,
212
- renderHeaderCell: () => (_jsx("div", { className: styles.rowNumberHeaderCell, children: "#" })),
213
- renderCell: (item) => {
214
- const rowId = getRowId(item);
215
- const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
216
- return (_jsx("div", { className: styles.rowNumberCell, children: rowNumberOffset + rowIndex + 1 }));
217
- },
218
- });
219
- cols.push(rowNumberCol);
220
- }
221
- return [...cols, ...dataCols];
84
+ else if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
85
+ const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValueRef.current, editCallbacks);
86
+ const CustomEditor = col.cellEditor;
87
+ content = (_jsxs(_Fragment, { children: [_jsx("div", { ref: (el) => { if (el)
88
+ setPopoverAnchorEl(el); }, style: POPOVER_ANCHOR_STYLE, "aria-hidden": true }), _jsx(Popover, { open: !!popoverAnchorElRef.current, onOpenChange: (_, data) => { if (!data.open)
89
+ cancelPopoverEdit(); }, positioning: { target: popoverAnchorElRef.current ?? undefined }, children: _jsx(PopoverSurface, { children: _jsx(CustomEditor, { ...editorProps }) }) })] }));
222
90
  }
223
- if (hasRowNumbersCol) {
224
- const rowNumberCol = createTableColumn({
225
- columnId: '__row_number__',
226
- compare: () => 0,
227
- renderHeaderCell: () => (_jsx("div", { className: styles.rowNumberHeaderCell, children: "#" })),
228
- renderCell: (item) => {
229
- const rowId = getRowId(item);
230
- const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
231
- return (_jsx("div", { className: styles.rowNumberCell, children: rowNumberOffset + rowIndex + 1 }));
232
- },
233
- });
234
- return [rowNumberCol, ...dataCols];
91
+ else {
92
+ const displayContent = resolveCellDisplayContent(col, item, descriptor.displayValue);
93
+ const cellStyle = resolveCellStyle(col, item);
94
+ const styledContent = cellStyle ? _jsx("span", { style: cellStyle, children: displayContent }) : displayContent;
95
+ const cellClassNames = `${styles.cellContent}${descriptor.isActive && !descriptor.isInRange ? ` ${styles.activeCellContent}` : ''}${descriptor.isInRange ? ` ${styles.cellInRange}` : ''}${descriptor.isInCutRange ? ` ${styles.cellCut}` : ''}${descriptor.isInCopyRange ? ` ${styles.cellCopied}` : ''}`;
96
+ const interactionProps = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
97
+ content = (_jsxs("div", { className: cellClassNames, ...interactionProps, style: descriptor.canEditAny ? CURSOR_CELL_STYLE : undefined, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
235
98
  }
236
- return dataCols;
237
- // eslint-disable-next-line react-hooks/exhaustive-deps
238
- }, [visibleCols, hasCheckboxCol, hasRowNumbersCol, getRowId, setPopoverAnchorEl, columnReorder, rowNumberOffset]); // All volatile state/callbacks read via refs
239
- // Stable row-click handler
240
- const handleSingleRowClick = useCallback((rowId) => {
241
- if (rowSelection !== 'single')
242
- return;
243
- const ids = selectedRowIdsRef.current;
244
- updateSelection(ids.has(rowId) ? new Set() : new Set([rowId]));
245
- // eslint-disable-next-line react-hooks/exhaustive-deps -- selectedRowIdsRef is a stable ref
246
- }, [rowSelection, updateSelection]);
247
- // Stable getRowId wrapper for Fluent DataGrid
248
- const fluentGetRowId = useCallback((item) => String(getRowId(item)), [getRowId]);
249
- // Double-click to auto-fit column width
250
- useEffect(() => {
251
- const root = wrapperRef.current;
252
- if (!root)
253
- return;
254
- const onDblClick = (e) => {
255
- const target = e.target;
256
- if (!target)
257
- return;
258
- if (!target.closest('.fui-TableResizeHandle'))
259
- return;
260
- const headerCell = target.closest('[role="columnheader"]');
261
- if (!headerCell)
262
- return;
263
- const colId = headerCell.querySelector('[data-column-id]')?.getAttribute('data-column-id');
264
- if (!colId)
265
- return;
266
- const label = headerCell.querySelector('[data-header-label]');
267
- const labelWidth = label ? label.scrollWidth : 0;
268
- const EXTRA_PX = 44;
269
- const MAX_PX = 520;
270
- const colDef = flatColumns.find((c) => c.columnId === colId);
271
- const minW = colDef?.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
272
- const desired = Math.min(MAX_PX, Math.max(minW, Math.ceil(labelWidth + EXTRA_PX)));
273
- setColumnSizingOverrides((prev) => ({
274
- ...prev,
275
- [colId]: { widthPx: desired },
276
- }));
277
- e.preventDefault();
278
- e.stopPropagation();
279
- };
280
- root.addEventListener('dblclick', onDblClick, true);
281
- return () => root.removeEventListener('dblclick', onDblClick, true);
282
- }, [flatColumns, setColumnSizingOverrides]);
283
- // Sync Fluent's internal resize state back to our React state so that
284
- // re-renders (e.g. on cell click) don't reset column widths.
285
- const handleColumnResize = useCallback((_e, data) => {
286
- setColumnSizingOverrides((prev) => ({
287
- ...prev,
288
- [String(data.columnId)]: { widthPx: data.width },
289
- }));
290
- }, [setColumnSizingOverrides]);
291
- return (_jsxs("div", { style: gridRootStyle, children: [_jsxs("div", { ref: wrapperRef, tabIndex: 0, className: `${styles.tableWrapper} ${rowSelection !== 'none' ? styles.selectableGrid : ''} ${styles[`density-${density}`] || ''}`, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, "data-empty": showEmptyInGrid ? 'true' : undefined, "data-auto-fit": layoutMode === 'fill' && !allowOverflowX ? 'true' : undefined, "data-column-count": totalColCount, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, "data-overflow-x": allowOverflowX ? 'true' : 'false', "data-container-width": containerWidth, "data-min-table-width": Math.round(minTableWidth), "data-has-selection": rowSelection !== 'none' ? 'true' : undefined, onContextMenu: PREVENT_DEFAULT, style: {
99
+ return (_jsx(CellErrorBoundary, { onError: onCellError, children: content }, `${rowId}-${col.columnId}`));
100
+ },
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- *Ref vars are stable refs from useLatestRef
102
+ [editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit, getRowId, onCellError]);
103
+ return (_jsxs("div", { style: GRID_ROOT_STYLE, children: [_jsxs("div", { ref: wrapperRef, tabIndex: 0, onMouseDown: (e) => { lastMouseShiftRef.current = e.shiftKey; }, className: `${styles.tableWrapper} ${rowSelection !== 'none' ? styles.selectableGrid : ''} ${styles[`density-${density}`] || ''}`, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, "data-empty": showEmptyInGrid ? 'true' : undefined, "data-column-count": totalColCount, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, "data-overflow-x": allowOverflowX ? 'true' : 'false', "data-container-width": containerWidth, "data-min-table-width": Math.round(minTableWidth), "data-has-selection": rowSelection !== 'none' ? 'true' : undefined, onContextMenu: PREVENT_DEFAULT, onKeyDown: handleGridKeyDown, style: {
292
104
  ['--data-table-column-count']: totalColCount,
293
- ['--data-table-width']: showEmptyInGrid
294
- ? '100%'
295
- : allowOverflowX
296
- ? 'fit-content'
297
- : fitToContent
298
- ? 'fit-content'
299
- : '100%',
300
- ['--data-table-min-width']: showEmptyInGrid
301
- ? '100%'
302
- : allowOverflowX
303
- ? 'max-content'
304
- : fitToContent
305
- ? 'max-content'
306
- : '100%',
307
- }, onKeyDown: handleGridKeyDown, children: [_jsx("div", { className: styles.tableScrollContent, children: _jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [virtualScrollEnabled && visibleRange.offsetTop > 0 && (_jsx("div", { style: { height: visibleRange.offsetTop }, "aria-hidden": true })), _jsxs(DataGrid, { items: virtualScrollEnabled ? items.slice(visibleRange.startIndex, visibleRange.endIndex + 1) : items, columns: fluentColumns, resizableColumns: true, resizableColumnsOptions: { autoFitColumns: layoutMode === 'fill' && !allowOverflowX }, columnSizingOptions: columnSizingOptions, onColumnResize: handleColumnResize, getRowId: fluentGetRowId, focusMode: "composite", className: styles.dataGrid, children: [_jsxs(DataGridHeader, { className: styles.stickyHeader, children: [hasGroupHeaders && headerRows.slice(0, -1).map((row, rowIdx) => (_jsxs("tr", { className: styles.groupHeaderRow, children: [rowIdx === 0 && hasCheckboxCol && (_jsx("th", { rowSpan: headerRows.length - 1, style: { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLUMN_WIDTH } })), rowIdx === 0 && hasRowNumbersCol && (_jsx("th", { rowSpan: headerRows.length - 1, style: { width: ROW_NUMBER_COLUMN_WIDTH, minWidth: ROW_NUMBER_COLUMN_WIDTH } })), row.map((cell, cellIdx) => {
308
- if (cell.isGroup) {
309
- return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
310
- }
311
- return (_jsx("th", { rowSpan: headerRows.length - rowIdx, className: styles.leafHeaderCellSpan, scope: "col", children: cell.columnDef?.name }, cellIdx));
312
- })] }, `group-${rowIdx}`))), _jsx(DataGridRow, { children: ({ renderHeaderCell, columnId }) => {
313
- const isSorted = props.sortBy === String(columnId);
105
+ ['--data-table-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'fit-content' : fitToContent ? 'fit-content' : '100%',
106
+ ['--data-table-min-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'max-content' : fitToContent ? 'max-content' : '100%',
107
+ ['--data-table-total-min-width']: `${minTableWidth}px`,
108
+ }, children: [_jsx("div", { className: styles.tableScrollContent, children: _jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [_jsxs(Table, { role: "grid", className: styles.dataTable, children: [_jsx(TableHeader, { className: styles.stickyHeader, children: headerRows.map((row, rowIdx) => (_jsxs(TableRow, { children: [rowIdx === headerRows.length - 1 && hasCheckboxCol && (_jsx(TableHeaderCell, { className: styles.selectionHeaderCellWrapper, children: _jsx("div", { className: styles.selectionHeaderCellInner, children: _jsx(Checkbox, { checked: allSelected ? true : someSelected ? 'mixed' : false, onChange: (_, data) => handleSelectAll(!!data.checked), "aria-label": "Select all rows" }) }) }, "__selection__")), rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol && (_jsx("th", { rowSpan: headerRows.length - 1 }, "__selection_placeholder__")), rowIdx === headerRows.length - 1 && hasRowNumbersCol && (_jsx(TableHeaderCell, { className: styles.rowNumberHeaderCellWrapper, children: _jsx("div", { className: styles.rowNumberHeaderCellInner, children: "#" }) }, "__row_number__")), rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol && (_jsx("th", { rowSpan: headerRows.length - 1 }, "__row_number_placeholder__")), row.map((cell, cellIdx) => {
109
+ if (cell.isGroup) {
110
+ return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
111
+ }
112
+ // Leaf cell
113
+ const col = cell.columnDef;
114
+ // Determine aria-sort value for sorted columns
115
+ const isSorted = props.sortBy === col.columnId;
314
116
  const ariaSort = isSorted
315
117
  ? (props.sortDirection === 'asc' ? 'ascending' : 'descending')
316
118
  : undefined;
317
- return (_jsx(DataGridHeaderCell, { className: headerClassMap[String(columnId)] || undefined, "aria-sort": ariaSort, children: renderHeaderCell() }));
318
- } })] }), _jsx(DataGridBody, { children: ({ item }) => {
319
- const rowId = getRowId(item);
320
- const rowIndex = rowIndexByRowId.get(rowId) ?? -1;
321
- return (_jsx(GridRow, { item: item, rowId: rowId, rowIndex: rowIndex, isSelected: selectedRowIds.has(rowId), hasCheckboxCol: hasCheckboxCol, cellClassMap: cellClassMap, handleSingleRowClick: handleSingleRowClick, selectionRange: selectionRange, activeCell: activeCell, cutRange: cutRange, copyRange: copyRange, isDragging: isDragging, editingRowId: editingCell?.rowId ?? null }, rowId));
322
- } })] }), virtualScrollEnabled && visibleRange.offsetBottom > 0 && (_jsx("div", { style: { height: visibleRange.offsetBottom }, "aria-hidden": true })), isReorderDragging && dropIndicatorX != null && (_jsx("div", { className: styles.dropIndicator, style: { left: dropIndicatorX - (wrapperRef.current?.getBoundingClientRect().left ?? 0) } })), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset, items: items, visibleColumns: visibleColumns, columnSizingOverrides: columnSizingOverrides, columnOrder: columnOrder }), showEmptyInGrid && emptyState && (_jsx("div", { className: styles.emptyStateInGrid, children: _jsx("div", { className: styles.emptyStateInGridMessageSticky, children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.emptyStateInGridIcon, "aria-hidden": true, children: "\uD83D\uDCCB" }), _jsx("div", { className: styles.emptyStateInGridTitle, children: "No results found" }), _jsx("div", { className: styles.emptyStateInGridMessage, children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx("button", { type: "button", className: styles.emptyStateInGridLink, onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }) }))] }) }) }), menuPosition &&
323
- createPortal(_jsx(GridContextMenu, { x: menuPosition.x, y: menuPosition.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? NOOP, onRedo: onRedo ?? NOOP, onCopy: handleCopy, onCut: handleCut, onPaste: handlePasteVoid, onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body), createPortal(_jsx(ColumnHeaderMenu, { isOpen: pinning.headerMenu.isOpen, anchorElement: pinning.headerMenu.anchorElement, onClose: pinning.headerMenu.close, onPinLeft: pinning.headerMenu.handlePinLeft, onPinRight: pinning.headerMenu.handlePinRight, onUnpin: pinning.headerMenu.handleUnpin, onSortAsc: pinning.headerMenu.handleSortAsc, onSortDesc: pinning.headerMenu.handleSortDesc, onClearSort: pinning.headerMenu.handleClearSort, onAutosizeThis: pinning.headerMenu.handleAutosizeThis, onAutosizeAll: pinning.headerMenu.handleAutosizeAll, canPinLeft: pinning.headerMenu.canPinLeft, canPinRight: pinning.headerMenu.canPinRight, canUnpin: pinning.headerMenu.canUnpin, currentSort: pinning.headerMenu.currentSort, isSortable: pinning.headerMenu.isSortable, isResizable: pinning.headerMenu.isResizable }), document.body)] }), statusBarConfig && (_jsx(StatusBar, { totalCount: statusBarConfig.totalCount, filteredCount: statusBarConfig.filteredCount, selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size, selectedCellCount: selectionRange ? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1) : undefined, aggregation: statusBarConfig.aggregation, suppressRowCount: statusBarConfig.suppressRowCount })), isLoading && (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx(Spinner, { size: "small" }), _jsx("span", { className: styles.loadingOverlayText, children: loadingMessage })] }) }))] }));
119
+ return (_jsxs(TableHeaderCell, { "data-column-id": col.columnId,
120
+ // rowSpan not supported by TableHeaderCell, use native th for grouped headers
121
+ className: columnMeta.hdrClasses[col.columnId] || undefined, style: {
122
+ ...columnMeta.hdrStyles[col.columnId],
123
+ ...(columnReorder ? { cursor: isReorderDragging ? 'grabbing' : 'grab' } : undefined),
124
+ }, "aria-sort": ariaSort, onMouseDown: columnReorder ? (e) => handleHeaderMouseDown(col.columnId, e) : undefined, children: [_jsxs("div", { className: styles.headerCellContent, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx("button", { className: styles.headerMenuTrigger, onClick: (e) => {
125
+ e.stopPropagation();
126
+ headerMenu.open(col.columnId, e.currentTarget);
127
+ }, "aria-label": "Column options", title: "Column options", children: "\u22EE" })] }), _jsx("div", { className: styles.resizeHandle, onMouseDown: (e) => {
128
+ // Clear cell selection/focus before resize so green outlines
129
+ // and blue :focus-visible rings don't persist during drag.
130
+ setActiveCell(null);
131
+ interaction.setSelectionRange(null);
132
+ // Move DOM focus to wrapper so no cell keeps :focus-visible
133
+ wrapperRef.current?.focus({ preventScroll: true });
134
+ handleResizeStart(e, col);
135
+ }, "aria-label": `Resize ${col.name}` })] }, col.columnId));
136
+ })] }, rowIdx))) }), !showEmptyInGrid && (_jsxs(TableBody, { children: [virtualScrollEnabled && visibleRange.offsetTop > 0 && (_jsx("tr", { style: { height: visibleRange.offsetTop }, "aria-hidden": true })), (virtualScrollEnabled
137
+ ? items.slice(visibleRange.startIndex, visibleRange.endIndex + 1).map((item, i) => {
138
+ const rowIndex = visibleRange.startIndex + i;
139
+ const rowIdStr = getRowId(item);
140
+ return (_jsx(GridRow, { item: item, rowIndex: rowIndex, rowId: rowIdStr, isSelected: selectedRowIds.has(rowIdStr), visibleCols: visibleCols, columnMeta: columnMeta, renderCellContent: renderCellContent, handleSingleRowClick: handleSingleRowClick, handleRowCheckboxChange: handleRowCheckboxChange, lastMouseShiftRef: lastMouseShiftRef, hasCheckboxCol: hasCheckboxCol, hasRowNumbersCol: hasRowNumbersCol, rowNumberOffset: rowNumberOffset, selectionRange: selectionRange, activeCell: interaction.activeCell, cutRange: cutRange, copyRange: copyRange, isDragging: isDragging, editingRowId: editingCell?.rowId ?? null }, rowIdStr));
141
+ })
142
+ : items.map((item, rowIndex) => {
143
+ const rowIdStr = getRowId(item);
144
+ return (_jsx(GridRow, { item: item, rowIndex: rowIndex, rowId: rowIdStr, isSelected: selectedRowIds.has(rowIdStr), visibleCols: visibleCols, columnMeta: columnMeta, renderCellContent: renderCellContent, handleSingleRowClick: handleSingleRowClick, handleRowCheckboxChange: handleRowCheckboxChange, lastMouseShiftRef: lastMouseShiftRef, hasCheckboxCol: hasCheckboxCol, hasRowNumbersCol: hasRowNumbersCol, rowNumberOffset: rowNumberOffset, selectionRange: selectionRange, activeCell: interaction.activeCell, cutRange: cutRange, copyRange: copyRange, isDragging: isDragging, editingRowId: editingCell?.rowId ?? null }, rowIdStr));
145
+ })), virtualScrollEnabled && visibleRange.offsetBottom > 0 && (_jsx("tr", { style: { height: visibleRange.offsetBottom }, "aria-hidden": true }))] }))] }), isReorderDragging && dropIndicatorX != null && (_jsx(DropIndicator, { dropIndicatorX: dropIndicatorX, wrapperLeft: wrapperRef.current?.getBoundingClientRect().left ?? 0 })), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset, items: items, visibleColumns: visibleColumns, columnSizingOverrides: columnSizingOverrides, columnOrder: columnOrder }), showEmptyInGrid && emptyState && (_jsx(EmptyState, { emptyState: emptyState }))] }) }) }), menuPosition &&
146
+ createPortal(_jsx(GridContextMenu, { x: menuPosition.x, y: menuPosition.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? NOOP, onRedo: onRedo ?? NOOP, onCopy: handleCopy, onCut: handleCut, onPaste: handlePasteVoid, onSelectAll: o.interaction.handleSelectAllCells, onClose: closeContextMenu }), wrapperRef.current?.closest('.fui-FluentProvider') ?? document.body), _jsx(ColumnHeaderMenu, { isOpen: headerMenu.isOpen, anchorElement: headerMenu.anchorElement, onClose: headerMenu.close, onPinLeft: headerMenu.handlePinLeft, onPinRight: headerMenu.handlePinRight, onUnpin: headerMenu.handleUnpin, onSortAsc: headerMenu.handleSortAsc, onSortDesc: headerMenu.handleSortDesc, onClearSort: headerMenu.handleClearSort, onAutosizeThis: headerMenu.handleAutosizeThis, onAutosizeAll: headerMenu.handleAutosizeAll, canPinLeft: headerMenu.canPinLeft, canPinRight: headerMenu.canPinRight, canUnpin: headerMenu.canUnpin, currentSort: headerMenu.currentSort, isSortable: headerMenu.isSortable, isResizable: headerMenu.isResizable })] }), statusBarConfig && (_jsx(StatusBar, { totalCount: statusBarConfig.totalCount, filteredCount: statusBarConfig.filteredCount, selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size, selectedCellCount: selectionRange ? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1) : undefined, aggregation: statusBarConfig.aggregation, suppressRowCount: statusBarConfig.suppressRowCount })), isLoading && (_jsx(LoadingOverlay, { message: loadingMessage }))] }));
324
147
  }
325
148
  export const DataGridTable = React.memo(DataGridTableInner);