@alaarab/ogrid-react-fluent 2.0.2 → 2.0.4
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.
- package/dist/esm/ColumnHeaderMenu/ColumnHeaderMenu.js +76 -0
- package/dist/esm/ColumnHeaderMenu/index.js +1 -0
- package/dist/esm/DataGridTable/DataGridTable.js +71 -15
- package/dist/esm/DataGridTable/DataGridTable.module.css +115 -3
- package/dist/types/ColumnHeaderMenu/ColumnHeaderMenu.d.ts +13 -0
- package/dist/types/ColumnHeaderMenu/index.d.ts +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect } from 'react';
|
|
3
|
+
import { COLUMN_HEADER_MENU_ITEMS } from '@alaarab/ogrid-core';
|
|
4
|
+
import { makeStyles, tokens } from '@fluentui/react-components';
|
|
5
|
+
const useStyles = makeStyles({
|
|
6
|
+
menu: {
|
|
7
|
+
position: 'fixed',
|
|
8
|
+
minWidth: '140px',
|
|
9
|
+
backgroundColor: tokens.colorNeutralBackground1,
|
|
10
|
+
borderRadius: tokens.borderRadiusMedium,
|
|
11
|
+
padding: '4px',
|
|
12
|
+
boxShadow: tokens.shadow16,
|
|
13
|
+
zIndex: 100,
|
|
14
|
+
},
|
|
15
|
+
menuItem: {
|
|
16
|
+
display: 'flex',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
height: '28px',
|
|
19
|
+
padding: '0 8px',
|
|
20
|
+
fontSize: tokens.fontSizeBase200,
|
|
21
|
+
color: tokens.colorNeutralForeground1,
|
|
22
|
+
borderRadius: tokens.borderRadiusSmall,
|
|
23
|
+
cursor: 'pointer',
|
|
24
|
+
userSelect: 'none',
|
|
25
|
+
outline: 'none',
|
|
26
|
+
backgroundColor: 'transparent',
|
|
27
|
+
border: 'none',
|
|
28
|
+
width: '100%',
|
|
29
|
+
textAlign: 'left',
|
|
30
|
+
':hover:not([disabled])': {
|
|
31
|
+
backgroundColor: tokens.colorNeutralBackground1Hover,
|
|
32
|
+
},
|
|
33
|
+
':active:not([disabled])': {
|
|
34
|
+
backgroundColor: tokens.colorNeutralBackground1Pressed,
|
|
35
|
+
},
|
|
36
|
+
':disabled': {
|
|
37
|
+
color: tokens.colorNeutralForegroundDisabled,
|
|
38
|
+
cursor: 'not-allowed',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
export function ColumnHeaderMenu(props) {
|
|
43
|
+
const { isOpen, anchorElement, onClose, onPinLeft, onPinRight, onUnpin, canPinLeft, canPinRight, canUnpin } = props;
|
|
44
|
+
const menuRef = useRef(null);
|
|
45
|
+
const styles = useStyles();
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!isOpen)
|
|
48
|
+
return;
|
|
49
|
+
const handleClickOutside = (e) => {
|
|
50
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
51
|
+
onClose();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const handleEscape = (e) => {
|
|
55
|
+
if (e.key === 'Escape') {
|
|
56
|
+
onClose();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
60
|
+
document.addEventListener('keydown', handleEscape);
|
|
61
|
+
return () => {
|
|
62
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
63
|
+
document.removeEventListener('keydown', handleEscape);
|
|
64
|
+
};
|
|
65
|
+
}, [isOpen, onClose]);
|
|
66
|
+
if (!isOpen || !anchorElement)
|
|
67
|
+
return null;
|
|
68
|
+
const rect = anchorElement.getBoundingClientRect();
|
|
69
|
+
const menuStyle = {
|
|
70
|
+
top: rect.bottom + 4,
|
|
71
|
+
left: rect.left,
|
|
72
|
+
};
|
|
73
|
+
const handlers = [onPinLeft, onPinRight, onUnpin];
|
|
74
|
+
const disabled = [!canPinLeft, !canPinRight, !canUnpin];
|
|
75
|
+
return (_jsx("div", { ref: menuRef, className: styles.menu, style: menuStyle, children: COLUMN_HEADER_MENU_ITEMS.map((item, idx) => (_jsx("button", { className: styles.menuItem, onClick: handlers[idx], disabled: disabled[idx], children: item.label }, item.id))) }));
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ColumnHeaderMenu } from './ColumnHeaderMenu';
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { useMemo, useRef, useEffect, useCallback } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { DataGrid, DataGridHeader, DataGridRow, DataGridHeaderCell, DataGridBody, DataGridCell, createTableColumn, Spinner, Checkbox, Popover, PopoverSurface, } from '@fluentui/react-components';
|
|
6
6
|
import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
|
|
7
|
+
import { ColumnHeaderMenu } from '../ColumnHeaderMenu';
|
|
7
8
|
import { InlineCellEditor } from './InlineCellEditor';
|
|
8
9
|
import { StatusBar } from './StatusBar';
|
|
9
10
|
import { GridContextMenu } from './GridContextMenu';
|
|
10
|
-
import { useDataGridState, useLatestRef, getHeaderFilterConfig, getCellRenderDescriptor, buildHeaderRows, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-react';
|
|
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
12
|
import styles from './DataGridTable.module.css';
|
|
12
13
|
// Module-scope stable constants (avoid per-render allocations)
|
|
13
14
|
const gridRootStyle = {
|
|
@@ -37,24 +38,46 @@ function DataGridTableInner(props) {
|
|
|
37
38
|
const wrapperRef = useRef(null);
|
|
38
39
|
const tableContainerRef = useRef(null);
|
|
39
40
|
const state = useDataGridState({ props, wrapperRef });
|
|
40
|
-
const { layout, rowSelection: rowSel, editing, interaction, contextMenu: ctxMenu, viewModels } = state;
|
|
41
|
-
const { flatColumns, visibleCols, totalColCount, hasCheckboxCol, colOffset, rowIndexByRowId, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides } = layout;
|
|
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;
|
|
42
43
|
const { selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
|
|
43
44
|
const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
44
45
|
const { activeCell, setActiveCell, handleCellMouseDown, handleSelectAllCells, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange, copyRange, canUndo, canRedo, onUndo, onRedo, isDragging } = interaction;
|
|
45
46
|
const handlePasteVoid = useCallback(() => { void handlePaste(); }, [handlePaste]);
|
|
46
47
|
const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
|
|
47
48
|
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
|
|
48
|
-
const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
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;
|
|
49
52
|
// Memoize header rows (recursive tree traversal)
|
|
50
|
-
const headerRows = useMemo(() => buildHeaderRows(
|
|
53
|
+
const headerRows = useMemo(() => buildHeaderRows(columns, visibleColumns), [columns, visibleColumns]);
|
|
51
54
|
const hasGroupHeaders = headerRows.length > 1;
|
|
52
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
|
+
});
|
|
53
73
|
const columnSizingOptions = useMemo(() => {
|
|
54
74
|
const acc = {};
|
|
55
75
|
if (hasCheckboxCol) {
|
|
56
76
|
acc['__selection__'] = { minWidth: CHECKBOX_COLUMN_WIDTH, defaultWidth: CHECKBOX_COLUMN_WIDTH, idealWidth: CHECKBOX_COLUMN_WIDTH };
|
|
57
77
|
}
|
|
78
|
+
if (hasRowNumbersCol) {
|
|
79
|
+
acc['__row_number__'] = { minWidth: ROW_NUMBER_COLUMN_WIDTH, defaultWidth: ROW_NUMBER_COLUMN_WIDTH, idealWidth: ROW_NUMBER_COLUMN_WIDTH };
|
|
80
|
+
}
|
|
58
81
|
visibleCols.forEach((c) => {
|
|
59
82
|
const minW = c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
60
83
|
const defaultW = c.defaultWidth ?? 120;
|
|
@@ -68,7 +91,7 @@ function DataGridTableInner(props) {
|
|
|
68
91
|
};
|
|
69
92
|
});
|
|
70
93
|
return acc;
|
|
71
|
-
}, [visibleCols, columnSizingOverrides, hasCheckboxCol]);
|
|
94
|
+
}, [visibleCols, columnSizingOverrides, hasCheckboxCol, hasRowNumbersCol]);
|
|
72
95
|
const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
|
|
73
96
|
// Pre-compute column class maps (avoids per-cell .filter(Boolean).join(' '))
|
|
74
97
|
const { cellClassMap, headerClassMap } = useMemo(() => {
|
|
@@ -121,11 +144,16 @@ function DataGridTableInner(props) {
|
|
|
121
144
|
const handleSelectAllRef = useLatestRef(handleSelectAll);
|
|
122
145
|
const handleRowCheckboxChangeRef = useLatestRef(handleRowCheckboxChange);
|
|
123
146
|
const rowIndexByRowIdRef = useLatestRef(rowIndexByRowId);
|
|
147
|
+
const handleHeaderMouseDownRef = useLatestRef(handleHeaderMouseDown);
|
|
148
|
+
const isReorderDraggingRef = useLatestRef(isReorderDragging);
|
|
124
149
|
const fluentColumns = useMemo(() => {
|
|
125
150
|
const dataCols = visibleCols.map((col, colIdx) => createTableColumn({
|
|
126
151
|
columnId: col.columnId,
|
|
127
152
|
compare: col.compare ?? (() => 0),
|
|
128
|
-
renderHeaderCell: () => (_jsx("div", { "data-column-id": col.columnId, children: _jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInputRef.current) })
|
|
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" })] }) })),
|
|
129
157
|
renderCell: (item) => {
|
|
130
158
|
const rowId = getRowId(item);
|
|
131
159
|
const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
|
|
@@ -176,11 +204,38 @@ function DataGridTableInner(props) {
|
|
|
176
204
|
}, "aria-label": `Select row ${rowIndex + 1}` }) }));
|
|
177
205
|
},
|
|
178
206
|
});
|
|
179
|
-
|
|
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];
|
|
222
|
+
}
|
|
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];
|
|
180
235
|
}
|
|
181
236
|
return dataCols;
|
|
182
237
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
183
|
-
}, [visibleCols, hasCheckboxCol, getRowId, setPopoverAnchorEl]); // All volatile state/callbacks read via refs
|
|
238
|
+
}, [visibleCols, hasCheckboxCol, hasRowNumbersCol, getRowId, setPopoverAnchorEl, columnReorder, rowNumberOffset]); // All volatile state/callbacks read via refs
|
|
184
239
|
// Stable row-click handler
|
|
185
240
|
const handleSingleRowClick = useCallback((rowId) => {
|
|
186
241
|
if (rowSelection !== 'single')
|
|
@@ -233,7 +288,7 @@ function DataGridTableInner(props) {
|
|
|
233
288
|
[String(data.columnId)]: { widthPx: data.width },
|
|
234
289
|
}));
|
|
235
290
|
}, [setColumnSizingOverrides]);
|
|
236
|
-
return (_jsxs("div", { style: gridRootStyle, children: [_jsxs("div", { ref: wrapperRef, tabIndex: 0, className: `${styles.tableWrapper} ${rowSelection !== 'none' ? styles.selectableGrid : ''}`, 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: {
|
|
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: {
|
|
237
292
|
['--data-table-column-count']: totalColCount,
|
|
238
293
|
['--data-table-width']: showEmptyInGrid
|
|
239
294
|
? '100%'
|
|
@@ -249,15 +304,16 @@ function DataGridTableInner(props) {
|
|
|
249
304
|
: fitToContent
|
|
250
305
|
? 'max-content'
|
|
251
306
|
: '100%',
|
|
252
|
-
}, 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: [_jsxs(DataGrid, { items: 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 } })), row.map((cell, cellIdx) => {
|
|
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) => {
|
|
253
308
|
if (cell.isGroup) {
|
|
254
309
|
return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
|
|
255
310
|
}
|
|
256
311
|
return (_jsx("th", { rowSpan: headerRows.length - rowIdx, className: styles.leafHeaderCellSpan, scope: "col", children: cell.columnDef?.name }, cellIdx));
|
|
257
312
|
})] }, `group-${rowIdx}`))), _jsx(DataGridRow, { children: ({ renderHeaderCell, columnId }) => (_jsx(DataGridHeaderCell, { className: headerClassMap[String(columnId)] || undefined, children: renderHeaderCell() })) })] }), _jsx(DataGridBody, { children: ({ item }) => {
|
|
258
313
|
const rowId = getRowId(item);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
314
|
+
const rowIndex = rowIndexByRowId.get(rowId) ?? -1;
|
|
315
|
+
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));
|
|
316
|
+
} })] }), 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 }), 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 &&
|
|
317
|
+
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, canPinLeft: pinning.headerMenu.canPinLeft, canPinRight: pinning.headerMenu.canPinRight, canUnpin: pinning.headerMenu.canUnpin }), 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 })] }) }))] }));
|
|
262
318
|
}
|
|
263
319
|
export const DataGridTable = React.memo(DataGridTableInner);
|
|
@@ -204,6 +204,14 @@
|
|
|
204
204
|
min-width: 48px !important;
|
|
205
205
|
max-width: 48px !important;
|
|
206
206
|
}
|
|
207
|
+
.tableWrapper {
|
|
208
|
+
/* Row numbers column must stay 50px even in auto-fit mode */
|
|
209
|
+
}
|
|
210
|
+
.tableWrapper[data-auto-fit=true][data-column-count] .rowNumberHeaderCellWrapper, .tableWrapper[data-auto-fit=true][data-column-count] .rowNumberCellWrapper {
|
|
211
|
+
width: 50px !important;
|
|
212
|
+
min-width: 50px !important;
|
|
213
|
+
max-width: 50px !important;
|
|
214
|
+
}
|
|
207
215
|
.tableWrapper {
|
|
208
216
|
/* Hide resize handle on last column only when table fits (autoFitColumns); when overflow/scroll, last column can resize (Fluent docs) */
|
|
209
217
|
}
|
|
@@ -246,6 +254,20 @@
|
|
|
246
254
|
padding: 0 !important;
|
|
247
255
|
}
|
|
248
256
|
|
|
257
|
+
.rowNumberHeaderCellWrapper {
|
|
258
|
+
width: 50px !important;
|
|
259
|
+
min-width: 50px !important;
|
|
260
|
+
max-width: 50px !important;
|
|
261
|
+
padding: 0 !important;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.rowNumberCellWrapper {
|
|
265
|
+
width: 50px !important;
|
|
266
|
+
min-width: 50px !important;
|
|
267
|
+
max-width: 50px !important;
|
|
268
|
+
padding: 0 !important;
|
|
269
|
+
}
|
|
270
|
+
|
|
249
271
|
.selectionHeaderCell {
|
|
250
272
|
display: flex;
|
|
251
273
|
align-items: center;
|
|
@@ -262,6 +284,19 @@
|
|
|
262
284
|
height: 100%;
|
|
263
285
|
}
|
|
264
286
|
|
|
287
|
+
.rowNumberHeaderCell,
|
|
288
|
+
.rowNumberCell {
|
|
289
|
+
display: flex;
|
|
290
|
+
align-items: center;
|
|
291
|
+
justify-content: center;
|
|
292
|
+
width: 100%;
|
|
293
|
+
height: 100%;
|
|
294
|
+
font-weight: 600;
|
|
295
|
+
font-variant-numeric: tabular-nums;
|
|
296
|
+
color: var(--colorNeutralForeground3, #666);
|
|
297
|
+
background: var(--colorNeutralBackground3, #f5f5f5);
|
|
298
|
+
}
|
|
299
|
+
|
|
265
300
|
.selectedRow :global .fui-DataGridCell {
|
|
266
301
|
background-color: var(--colorNeutralBackground1Selected, #e6f0fb) !important;
|
|
267
302
|
}
|
|
@@ -308,6 +343,10 @@
|
|
|
308
343
|
background-color: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12)) !important;
|
|
309
344
|
}
|
|
310
345
|
|
|
346
|
+
:global([data-drag-anchor]) {
|
|
347
|
+
background-color: var(--colorNeutralBackground1, #fff) !important;
|
|
348
|
+
}
|
|
349
|
+
|
|
311
350
|
.cellCut {
|
|
312
351
|
background-color: var(--colorNeutralBackground1Hover, rgba(0, 0, 0, 0.04)) !important;
|
|
313
352
|
opacity: 0.7;
|
|
@@ -379,7 +418,7 @@
|
|
|
379
418
|
right: -4px;
|
|
380
419
|
bottom: 0;
|
|
381
420
|
width: 4px;
|
|
382
|
-
background: linear-gradient(to right, rgba(0, 0, 0, 0.
|
|
421
|
+
background: linear-gradient(to right, var(--ogrid-pinned-shadow, rgba(0, 0, 0, 0.12)), transparent);
|
|
383
422
|
pointer-events: none;
|
|
384
423
|
}
|
|
385
424
|
|
|
@@ -393,7 +432,7 @@
|
|
|
393
432
|
left: -4px;
|
|
394
433
|
bottom: 0;
|
|
395
434
|
width: 4px;
|
|
396
|
-
background: linear-gradient(to left, rgba(0, 0, 0, 0.
|
|
435
|
+
background: linear-gradient(to left, var(--ogrid-pinned-shadow, rgba(0, 0, 0, 0.12)), transparent);
|
|
397
436
|
pointer-events: none;
|
|
398
437
|
}
|
|
399
438
|
|
|
@@ -493,7 +532,7 @@
|
|
|
493
532
|
display: flex;
|
|
494
533
|
align-items: center;
|
|
495
534
|
justify-content: center;
|
|
496
|
-
background: rgba(255, 255, 255, 0.7);
|
|
535
|
+
background: var(--ogrid-loading-bg, rgba(255, 255, 255, 0.7));
|
|
497
536
|
backdrop-filter: blur(1px);
|
|
498
537
|
pointer-events: all;
|
|
499
538
|
}
|
|
@@ -581,6 +620,18 @@
|
|
|
581
620
|
color: var(--colorBrandForeground1Hover, #115ea3);
|
|
582
621
|
}
|
|
583
622
|
|
|
623
|
+
/* Column reorder drop indicator */
|
|
624
|
+
.dropIndicator {
|
|
625
|
+
position: absolute;
|
|
626
|
+
top: 0;
|
|
627
|
+
bottom: 0;
|
|
628
|
+
width: 3px;
|
|
629
|
+
background: var(--ogrid-primary, #217346);
|
|
630
|
+
pointer-events: none;
|
|
631
|
+
z-index: 100;
|
|
632
|
+
transition: left 0.05s;
|
|
633
|
+
}
|
|
634
|
+
|
|
584
635
|
/* Empty state: hide body, keep header and empty message */
|
|
585
636
|
.tableWrapper[data-empty=true] :global(.fui-DataGrid) tbody {
|
|
586
637
|
display: none;
|
|
@@ -589,4 +640,65 @@
|
|
|
589
640
|
/* Empty state: no extra bottom border (header row is last row, remove its border-bottom) */
|
|
590
641
|
.tableWrapper[data-empty=true] :global(.fui-DataGridHeader .fui-DataGridRow) {
|
|
591
642
|
border-bottom: none !important;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.headerCellContent {
|
|
646
|
+
display: flex;
|
|
647
|
+
align-items: center;
|
|
648
|
+
gap: 4px;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.headerMenuTrigger {
|
|
652
|
+
background: transparent;
|
|
653
|
+
border: none;
|
|
654
|
+
cursor: pointer;
|
|
655
|
+
padding: 2px 4px;
|
|
656
|
+
font-size: 16px;
|
|
657
|
+
line-height: 1;
|
|
658
|
+
color: var(--colorNeutralForeground3, #666);
|
|
659
|
+
opacity: 0;
|
|
660
|
+
transition: opacity 0.15s, background-color 0.15s;
|
|
661
|
+
border-radius: 3px;
|
|
662
|
+
display: flex;
|
|
663
|
+
align-items: center;
|
|
664
|
+
justify-content: center;
|
|
665
|
+
min-width: 20px;
|
|
666
|
+
height: 20px;
|
|
667
|
+
}
|
|
668
|
+
.headerMenuTrigger:hover {
|
|
669
|
+
background: var(--colorNeutralBackground1Hover, #f3f2f1);
|
|
670
|
+
opacity: 1;
|
|
671
|
+
}
|
|
672
|
+
.headerMenuTrigger:active {
|
|
673
|
+
background: var(--colorNeutralBackground1Pressed, #e1dfdd);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.tableWrapper :global(.fui-DataGridHeaderCell:hover) .headerMenuTrigger {
|
|
677
|
+
opacity: 1;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.tableWrapper :global(.fui-DataGridHeaderCell.pinnedColLeft),
|
|
681
|
+
.tableWrapper :global(.fui-DataGridCell.pinnedColLeft) {
|
|
682
|
+
border-left: 2px solid var(--colorBrandForeground1, #0078d4) !important;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.tableWrapper :global(.fui-DataGridHeaderCell.pinnedColRight),
|
|
686
|
+
.tableWrapper :global(.fui-DataGridCell.pinnedColRight) {
|
|
687
|
+
border-right: 2px solid var(--colorBrandForeground1, #0078d4) !important;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.density-compact :global(.fui-DataGridHeaderCell),
|
|
691
|
+
.density-compact :global(.fui-DataGridCell) {
|
|
692
|
+
padding: 4px 8px;
|
|
693
|
+
}
|
|
694
|
+
.density-compact .cellContent {
|
|
695
|
+
padding: 4px 8px;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.density-comfortable :global(.fui-DataGridHeaderCell),
|
|
699
|
+
.density-comfortable :global(.fui-DataGridCell) {
|
|
700
|
+
padding: 12px 16px;
|
|
701
|
+
}
|
|
702
|
+
.density-comfortable .cellContent {
|
|
703
|
+
padding: 12px 16px;
|
|
592
704
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface ColumnHeaderMenuProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
anchorElement: HTMLElement | null;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
onPinLeft: () => void;
|
|
7
|
+
onPinRight: () => void;
|
|
8
|
+
onUnpin: () => void;
|
|
9
|
+
canPinLeft: boolean;
|
|
10
|
+
canPinRight: boolean;
|
|
11
|
+
canUnpin: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function ColumnHeaderMenu(props: ColumnHeaderMenuProps): React.ReactElement | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-react-fluent",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "OGrid Fluent UI implementation – DataGrid-powered data table with sorting, filtering, pagination, column chooser, and CSV export.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"node": ">=18"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@alaarab/ogrid-react": "2.0.
|
|
43
|
+
"@alaarab/ogrid-react": "2.0.4"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@fluentui/react-components": "^9.0.0",
|