@alaarab/ogrid-react-fluent 2.0.3 → 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 +48 -12
- package/dist/esm/DataGridTable/DataGridTable.module.css +100 -0
- 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, useColumnReorder, useVirtualScroll, 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,15 +38,17 @@ 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, 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, pinnedColumns, } = 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
53
|
const headerRows = useMemo(() => buildHeaderRows(columns, visibleColumns), [columns, visibleColumns]);
|
|
51
54
|
const hasGroupHeaders = headerRows.length > 1;
|
|
@@ -72,6 +75,9 @@ function DataGridTableInner(props) {
|
|
|
72
75
|
if (hasCheckboxCol) {
|
|
73
76
|
acc['__selection__'] = { minWidth: CHECKBOX_COLUMN_WIDTH, defaultWidth: CHECKBOX_COLUMN_WIDTH, idealWidth: CHECKBOX_COLUMN_WIDTH };
|
|
74
77
|
}
|
|
78
|
+
if (hasRowNumbersCol) {
|
|
79
|
+
acc['__row_number__'] = { minWidth: ROW_NUMBER_COLUMN_WIDTH, defaultWidth: ROW_NUMBER_COLUMN_WIDTH, idealWidth: ROW_NUMBER_COLUMN_WIDTH };
|
|
80
|
+
}
|
|
75
81
|
visibleCols.forEach((c) => {
|
|
76
82
|
const minW = c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
77
83
|
const defaultW = c.defaultWidth ?? 120;
|
|
@@ -85,7 +91,7 @@ function DataGridTableInner(props) {
|
|
|
85
91
|
};
|
|
86
92
|
});
|
|
87
93
|
return acc;
|
|
88
|
-
}, [visibleCols, columnSizingOverrides, hasCheckboxCol]);
|
|
94
|
+
}, [visibleCols, columnSizingOverrides, hasCheckboxCol, hasRowNumbersCol]);
|
|
89
95
|
const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
|
|
90
96
|
// Pre-compute column class maps (avoids per-cell .filter(Boolean).join(' '))
|
|
91
97
|
const { cellClassMap, headerClassMap } = useMemo(() => {
|
|
@@ -144,7 +150,10 @@ function DataGridTableInner(props) {
|
|
|
144
150
|
const dataCols = visibleCols.map((col, colIdx) => createTableColumn({
|
|
145
151
|
columnId: col.columnId,
|
|
146
152
|
compare: col.compare ?? (() => 0),
|
|
147
|
-
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: _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" })] }) })),
|
|
148
157
|
renderCell: (item) => {
|
|
149
158
|
const rowId = getRowId(item);
|
|
150
159
|
const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
|
|
@@ -195,11 +204,38 @@ function DataGridTableInner(props) {
|
|
|
195
204
|
}, "aria-label": `Select row ${rowIndex + 1}` }) }));
|
|
196
205
|
},
|
|
197
206
|
});
|
|
198
|
-
|
|
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];
|
|
199
235
|
}
|
|
200
236
|
return dataCols;
|
|
201
237
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
202
|
-
}, [visibleCols, hasCheckboxCol, getRowId, setPopoverAnchorEl, columnReorder]); // All volatile state/callbacks read via refs
|
|
238
|
+
}, [visibleCols, hasCheckboxCol, hasRowNumbersCol, getRowId, setPopoverAnchorEl, columnReorder, rowNumberOffset]); // All volatile state/callbacks read via refs
|
|
203
239
|
// Stable row-click handler
|
|
204
240
|
const handleSingleRowClick = useCallback((rowId) => {
|
|
205
241
|
if (rowSelection !== 'single')
|
|
@@ -252,7 +288,7 @@ function DataGridTableInner(props) {
|
|
|
252
288
|
[String(data.columnId)]: { widthPx: data.width },
|
|
253
289
|
}));
|
|
254
290
|
}, [setColumnSizingOverrides]);
|
|
255
|
-
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: {
|
|
256
292
|
['--data-table-column-count']: totalColCount,
|
|
257
293
|
['--data-table-width']: showEmptyInGrid
|
|
258
294
|
? '100%'
|
|
@@ -268,7 +304,7 @@ function DataGridTableInner(props) {
|
|
|
268
304
|
: fitToContent
|
|
269
305
|
? 'max-content'
|
|
270
306
|
: '100%',
|
|
271
|
-
}, 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 } })), 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) => {
|
|
272
308
|
if (cell.isGroup) {
|
|
273
309
|
return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
|
|
274
310
|
}
|
|
@@ -278,6 +314,6 @@ function DataGridTableInner(props) {
|
|
|
278
314
|
const rowIndex = rowIndexByRowId.get(rowId) ?? -1;
|
|
279
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));
|
|
280
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 &&
|
|
281
|
-
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)] }), 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 })] }) }))] }));
|
|
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 })] }) }))] }));
|
|
282
318
|
}
|
|
283
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;
|
|
@@ -601,4 +640,65 @@
|
|
|
601
640
|
/* Empty state: no extra bottom border (header row is last row, remove its border-bottom) */
|
|
602
641
|
.tableWrapper[data-empty=true] :global(.fui-DataGridHeader .fui-DataGridRow) {
|
|
603
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;
|
|
604
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",
|