@alaarab/ogrid-react-material 2.0.3 → 2.0.5
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 +39 -0
- package/dist/esm/ColumnHeaderMenu/index.js +1 -0
- package/dist/esm/DataGridTable/DataGridTable.js +89 -17
- package/dist/esm/index.js +1 -0
- package/dist/types/ColumnHeaderMenu/ColumnHeaderMenu.d.ts +17 -0
- package/dist/types/ColumnHeaderMenu/index.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import Menu from '@mui/material/Menu';
|
|
4
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
5
|
+
import IconButton from '@mui/material/IconButton';
|
|
6
|
+
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
|
7
|
+
import { COLUMN_HEADER_MENU_ITEMS } from '@alaarab/ogrid-core';
|
|
8
|
+
/**
|
|
9
|
+
* Column header dropdown menu for pin/unpin actions.
|
|
10
|
+
* Uses Material UI Menu component.
|
|
11
|
+
*/
|
|
12
|
+
export function ColumnHeaderMenu(props) {
|
|
13
|
+
const { columnId, isOpen, anchorElement, onClose, onPinLeft, onPinRight, onUnpin, canPinLeft, canPinRight, canUnpin, } = props;
|
|
14
|
+
const [triggerEl, setTriggerEl] = React.useState(null);
|
|
15
|
+
const handleTriggerClick = (event) => {
|
|
16
|
+
event.stopPropagation();
|
|
17
|
+
setTriggerEl(event.currentTarget);
|
|
18
|
+
};
|
|
19
|
+
return (_jsxs(_Fragment, { children: [_jsx(IconButton, { size: "small", onClick: handleTriggerClick, "aria-label": `Column options for ${columnId}`, sx: {
|
|
20
|
+
opacity: 0,
|
|
21
|
+
transition: 'opacity 0.15s',
|
|
22
|
+
padding: '4px',
|
|
23
|
+
'.MuiTableCell-root:hover &': {
|
|
24
|
+
opacity: 1,
|
|
25
|
+
},
|
|
26
|
+
'&:focus': {
|
|
27
|
+
opacity: 1,
|
|
28
|
+
},
|
|
29
|
+
}, children: _jsx(MoreVertIcon, { fontSize: "small" }) }), _jsxs(Menu, { anchorEl: triggerEl, open: Boolean(triggerEl), onClose: () => {
|
|
30
|
+
setTriggerEl(null);
|
|
31
|
+
onClose();
|
|
32
|
+
}, anchorOrigin: {
|
|
33
|
+
vertical: 'bottom',
|
|
34
|
+
horizontal: 'left',
|
|
35
|
+
}, transformOrigin: {
|
|
36
|
+
vertical: 'top',
|
|
37
|
+
horizontal: 'left',
|
|
38
|
+
}, children: [_jsx(MenuItem, { disabled: !canPinLeft, onClick: onPinLeft, children: COLUMN_HEADER_MENU_ITEMS[0].label }), _jsx(MenuItem, { disabled: !canPinRight, onClick: onPinRight, children: COLUMN_HEADER_MENU_ITEMS[1].label }), _jsx(MenuItem, { disabled: !canUnpin, onClick: onUnpin, children: COLUMN_HEADER_MENU_ITEMS[2].label })] })] }));
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ColumnHeaderMenu } from './ColumnHeaderMenu';
|
|
@@ -4,10 +4,11 @@ import { useCallback, useRef, useMemo } from 'react';
|
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { Box, CircularProgress, Typography, Button, Popover, Checkbox, Table, TableHead, TableBody, TableRow, TableCell, TableContainer, } from '@mui/material';
|
|
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, useColumnResize, useColumnReorder, useVirtualScroll, useLatestRef, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, buildHeaderRows, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-react';
|
|
11
|
+
import { useDataGridState, useColumnResize, useColumnReorder, useVirtualScroll, useLatestRef, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, buildHeaderRows, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-react';
|
|
11
12
|
// ── Module-scope stable styles (avoid per-render Emotion resolutions) ──
|
|
12
13
|
const gridRootSx = { position: 'relative', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' };
|
|
13
14
|
// Row
|
|
@@ -20,6 +21,14 @@ const CHECKBOX_PLACEHOLDER_SX = { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKB
|
|
|
20
21
|
const STICKY_HEADER_SX = { position: 'sticky', top: 0, zIndex: 8, bgcolor: 'action.hover', '& th': { bgcolor: 'action.hover' } };
|
|
21
22
|
const HEADER_ROW_SX = { bgcolor: 'action.hover' };
|
|
22
23
|
const GROUP_HEADER_CELL_SX = { textAlign: 'center', fontWeight: 600, borderBottom: 2, borderColor: 'divider', py: 0.75 };
|
|
24
|
+
// Density padding helper
|
|
25
|
+
function getDensityPadding(density) {
|
|
26
|
+
switch (density) {
|
|
27
|
+
case 'compact': return { px: '8px', py: '4px' };
|
|
28
|
+
case 'comfortable': return { px: '16px', py: '12px' };
|
|
29
|
+
default: return { px: '10px', py: '6px' };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
23
32
|
// Cell content base variants (selected by column type + editability)
|
|
24
33
|
const CELL_CONTENT_BASE_SX = {
|
|
25
34
|
width: '100%', height: '100%', display: 'flex', alignItems: 'center', minWidth: 0,
|
|
@@ -89,12 +98,12 @@ const FILL_HANDLE_SX = {
|
|
|
89
98
|
};
|
|
90
99
|
// Cell <td> positioning variants
|
|
91
100
|
const CELL_TD_BASE_SX = { position: 'relative', p: 0, height: '1px' };
|
|
92
|
-
const CELL_TD_PINNED_LEFT_SX = { ...CELL_TD_BASE_SX, position: 'sticky', left: 0, zIndex: 6, bgcolor: 'background.paper', willChange: 'transform' };
|
|
93
|
-
const CELL_TD_PINNED_RIGHT_SX = { ...CELL_TD_BASE_SX, position: 'sticky', right: 0, zIndex: 6, bgcolor: 'background.paper', willChange: 'transform' };
|
|
101
|
+
const CELL_TD_PINNED_LEFT_SX = { ...CELL_TD_BASE_SX, position: 'sticky', left: 0, zIndex: 6, bgcolor: 'background.paper', willChange: 'transform', borderLeft: '2px solid', borderLeftColor: 'primary.main' };
|
|
102
|
+
const CELL_TD_PINNED_RIGHT_SX = { ...CELL_TD_BASE_SX, position: 'sticky', right: 0, zIndex: 6, bgcolor: 'background.paper', willChange: 'transform', borderRight: '2px solid', borderRightColor: 'primary.main' };
|
|
94
103
|
// Header cell positioning variants
|
|
95
104
|
const HEADER_BASE_SX = { fontWeight: 600, position: 'relative' };
|
|
96
|
-
const HEADER_PINNED_LEFT_SX = { ...HEADER_BASE_SX, position: 'sticky', left: 0, top: 0, zIndex: 9, bgcolor: 'action.hover', willChange: 'transform' };
|
|
97
|
-
const HEADER_PINNED_RIGHT_SX = { ...HEADER_BASE_SX, position: 'sticky', right: 0, top: 0, zIndex: 9, bgcolor: 'action.hover', willChange: 'transform' };
|
|
105
|
+
const HEADER_PINNED_LEFT_SX = { ...HEADER_BASE_SX, position: 'sticky', left: 0, top: 0, zIndex: 9, bgcolor: 'action.hover', willChange: 'transform', borderLeft: '2px solid', borderLeftColor: 'primary.main' };
|
|
106
|
+
const HEADER_PINNED_RIGHT_SX = { ...HEADER_BASE_SX, position: 'sticky', right: 0, top: 0, zIndex: 9, bgcolor: 'action.hover', willChange: 'transform', borderRight: '2px solid', borderRightColor: 'primary.main' };
|
|
98
107
|
// Resize handle
|
|
99
108
|
const RESIZE_HANDLE_SX = {
|
|
100
109
|
position: 'absolute', top: 0, right: '-3px', bottom: 0, width: '8px',
|
|
@@ -128,8 +137,20 @@ const STOP_PROPAGATION = (e) => e.stopPropagation();
|
|
|
128
137
|
const PREVENT_DEFAULT = (e) => { e.preventDefault(); };
|
|
129
138
|
const NOOP = () => { };
|
|
130
139
|
function GridRowInner(props) {
|
|
131
|
-
const { item, rowIndex, rowId, isSelected, columnLayouts, renderCellContent, handleSingleRowClick, handleRowCheckboxChange, lastMouseShiftRef, hasCheckboxCol, } = props;
|
|
132
|
-
return (_jsxs(TableRow, { selected: isSelected, "data-row-id": rowId, onClick: handleSingleRowClick, sx: ROW_HOVER_SX, children: [hasCheckboxCol && (_jsx(TableCell, { padding: "checkbox", sx: CHECKBOX_CELL_SX, children: _jsx(Box, { "data-row-index": rowIndex, "data-col-index": 0, onClick: STOP_PROPAGATION, sx: CHECKBOX_WRAPPER_SX, children: _jsx(Checkbox, { checked: isSelected, onChange: (_, checked) => handleRowCheckboxChange(rowId, checked, rowIndex, lastMouseShiftRef.current), size: "small", "aria-label": `Select row ${rowIndex + 1}` }) }) })),
|
|
140
|
+
const { item, rowIndex, rowId, isSelected, columnLayouts, renderCellContent, handleSingleRowClick, handleRowCheckboxChange, lastMouseShiftRef, hasCheckboxCol, hasRowNumbersCol, rowNumberOffset, } = props;
|
|
141
|
+
return (_jsxs(TableRow, { selected: isSelected, "data-row-id": rowId, onClick: handleSingleRowClick, sx: ROW_HOVER_SX, children: [hasCheckboxCol && (_jsx(TableCell, { padding: "checkbox", sx: CHECKBOX_CELL_SX, children: _jsx(Box, { "data-row-index": rowIndex, "data-col-index": 0, onClick: STOP_PROPAGATION, sx: CHECKBOX_WRAPPER_SX, children: _jsx(Checkbox, { checked: isSelected, onChange: (_, checked) => handleRowCheckboxChange(rowId, checked, rowIndex, lastMouseShiftRef.current), size: "small", "aria-label": `Select row ${rowIndex + 1}` }) }) })), hasRowNumbersCol && (_jsx(TableCell, { sx: {
|
|
142
|
+
width: ROW_NUMBER_COLUMN_WIDTH,
|
|
143
|
+
minWidth: ROW_NUMBER_COLUMN_WIDTH,
|
|
144
|
+
maxWidth: ROW_NUMBER_COLUMN_WIDTH,
|
|
145
|
+
textAlign: 'center',
|
|
146
|
+
fontWeight: 600,
|
|
147
|
+
fontVariantNumeric: 'tabular-nums',
|
|
148
|
+
color: 'text.secondary',
|
|
149
|
+
backgroundColor: 'action.hover',
|
|
150
|
+
position: 'sticky',
|
|
151
|
+
left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
|
|
152
|
+
zIndex: 3,
|
|
153
|
+
}, children: rowNumberOffset + rowIndex + 1 })), columnLayouts.map((cl, colIdx) => (_jsx(TableCell, { sx: cl.tdSx, style: { minWidth: cl.minWidth, width: cl.width, maxWidth: cl.maxWidth }, children: renderCellContent(item, cl.col, rowIndex, colIdx) }, cl.col.columnId)))] }));
|
|
133
154
|
}
|
|
134
155
|
const GridRow = React.memo(GridRowInner, areGridRowPropsEqual);
|
|
135
156
|
function DataGridTableInner(props) {
|
|
@@ -137,17 +158,23 @@ function DataGridTableInner(props) {
|
|
|
137
158
|
const tableContainerRef = useRef(null);
|
|
138
159
|
const state = useDataGridState({ props, wrapperRef });
|
|
139
160
|
const lastMouseShiftRef = useRef(false);
|
|
140
|
-
const { layout, rowSelection: rowSel, editing, interaction, contextMenu: ctxMenu, viewModels } = state;
|
|
141
|
-
const { visibleCols, hasCheckboxCol, colOffset, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides } = layout;
|
|
161
|
+
const { layout, rowSelection: rowSel, editing, interaction, contextMenu: ctxMenu, viewModels, pinning } = state;
|
|
162
|
+
const { visibleCols, hasCheckboxCol, hasRowNumbersCol, colOffset, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides } = layout;
|
|
142
163
|
const { selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
|
|
143
164
|
const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
144
165
|
const { setActiveCell, handleCellMouseDown, handleSelectAllCells, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange, copyRange, canUndo, canRedo, onUndo, onRedo, isDragging } = interaction;
|
|
145
166
|
const handlePasteVoid = useCallback(() => { void handlePaste(); }, [handlePaste]);
|
|
146
167
|
const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
|
|
147
168
|
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
|
|
148
|
-
const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, columnOrder, onColumnOrderChange, columnReorder, virtualScroll, pinnedColumns, } = props;
|
|
169
|
+
const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, columnOrder, onColumnOrderChange, columnReorder, virtualScroll, density = 'normal', pinnedColumns, currentPage = 1, pageSize: propPageSize = 25, } = props;
|
|
170
|
+
// Calculate row number offset for pagination
|
|
171
|
+
const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * propPageSize : 0;
|
|
149
172
|
const fitToContent = layoutMode === 'content';
|
|
150
173
|
const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
|
|
174
|
+
// Density-aware cell padding
|
|
175
|
+
const densityPadding = useMemo(() => getDensityPadding(density), [density]);
|
|
176
|
+
const cellSx = useMemo(() => ({ ...CELL_CONTENT_BASE_SX, ...densityPadding }), [densityPadding]);
|
|
177
|
+
const headerCellSx = useMemo(() => ({ px: densityPadding.px, py: densityPadding.py }), [densityPadding]);
|
|
151
178
|
// Memoize header rows (recursive tree traversal)
|
|
152
179
|
const headerRows = useMemo(() => buildHeaderRows(props.columns, props.visibleColumns), [props.columns, props.visibleColumns]);
|
|
153
180
|
const { handleResizeStart, getColumnWidth } = useColumnResize({
|
|
@@ -211,6 +238,7 @@ function DataGridTableInner(props) {
|
|
|
211
238
|
bgcolor: 'background.paper',
|
|
212
239
|
willChange: 'scroll-position',
|
|
213
240
|
'& [data-drag-range]': { bgcolor: 'rgba(33, 115, 70, 0.12) !important' },
|
|
241
|
+
'& [data-drag-anchor]': { bgcolor: 'background.paper !important' },
|
|
214
242
|
}), [fitToContent, suppressHorizontalScroll, allowOverflowX]);
|
|
215
243
|
const renderCellContent = useCallback((item, col, rowIndex, colIdx) => {
|
|
216
244
|
const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInputRef.current);
|
|
@@ -232,13 +260,31 @@ function DataGridTableInner(props) {
|
|
|
232
260
|
// Select pre-computed sx variant (module-scope = no per-cell allocation)
|
|
233
261
|
const cellSx = getCellSx(col.type, descriptor.canEditAny, descriptor.isActive && !descriptor.isInRange, descriptor.isInRange, descriptor.isInCutRange);
|
|
234
262
|
const interactionProps = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
|
|
235
|
-
cellContent = (_jsxs(Box, { component: "div", ...interactionProps, sx: cellSx, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx(Box, { component: "div", onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle", sx: FILL_HANDLE_SX }))] }));
|
|
263
|
+
cellContent = (_jsxs(Box, { component: "div", ...interactionProps, sx: Array.isArray(cellSx) ? [...cellSx, densityPadding] : { ...cellSx, ...densityPadding }, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx(Box, { component: "div", onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle", sx: FILL_HANDLE_SX }))] }));
|
|
236
264
|
}
|
|
237
265
|
return (_jsx(CellErrorBoundary, { onError: onCellError, children: cellContent }, `${rowId}-${col.columnId}`));
|
|
238
266
|
},
|
|
239
267
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- *Ref vars are stable refs from useLatestRef
|
|
240
268
|
[editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit, getRowId, onCellError]);
|
|
241
|
-
return (_jsxs(Box, { sx: gridRootSx, children: [_jsxs(Box, { ref: wrapperRef, tabIndex: 0, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, onMouseDown: (e) => { lastMouseShiftRef.current = e.shiftKey; }, onKeyDown: handleGridKeyDown, onContextMenu: PREVENT_DEFAULT, "data-overflow-x": allowOverflowX ? 'true' : 'false', sx: wrapperSx, children: [_jsx(Box, { sx: WRAPPER_SCROLL_SX, children: _jsx(TableContainer, { sx: { minWidth: allowOverflowX ? minTableWidth : undefined }, children: _jsxs(Box, { ref: tableContainerRef, sx: isLoading && items.length > 0 ? TABLE_WRAPPER_LOADING_SX : TABLE_WRAPPER_SX, children: [_jsxs(Table, { size: "small", sx: { overflow: 'hidden', minWidth: minTableWidth }, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, children: [_jsx(TableHead, { sx: STICKY_HEADER_SX, children: headerRows.map((row, rowIdx) => (_jsxs(TableRow, { sx: HEADER_ROW_SX, children: [rowIdx === headerRows.length - 1 && hasCheckboxCol && (_jsx(TableCell, { padding: "checkbox", rowSpan: headerRows.length > 1 ? 1 : undefined, sx: CHECKBOX_CELL_SX, children: _jsx(Checkbox, { checked: allSelected, indeterminate: someSelected, onChange: (_, c) => handleSelectAll(!!c), size: "small", "aria-label": "Select all rows" }) })), rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol && (_jsx(TableCell, { rowSpan: headerRows.length - 1, sx: CHECKBOX_PLACEHOLDER_SX })),
|
|
269
|
+
return (_jsxs(Box, { sx: gridRootSx, children: [_jsxs(Box, { ref: wrapperRef, tabIndex: 0, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, onMouseDown: (e) => { lastMouseShiftRef.current = e.shiftKey; }, onKeyDown: handleGridKeyDown, onContextMenu: PREVENT_DEFAULT, "data-overflow-x": allowOverflowX ? 'true' : 'false', "data-density": density, sx: wrapperSx, children: [_jsx(Box, { sx: WRAPPER_SCROLL_SX, children: _jsx(TableContainer, { sx: { minWidth: allowOverflowX ? minTableWidth : undefined }, children: _jsxs(Box, { ref: tableContainerRef, sx: isLoading && items.length > 0 ? TABLE_WRAPPER_LOADING_SX : TABLE_WRAPPER_SX, children: [_jsxs(Table, { size: "small", sx: { overflow: 'hidden', minWidth: minTableWidth }, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, children: [_jsx(TableHead, { sx: STICKY_HEADER_SX, children: headerRows.map((row, rowIdx) => (_jsxs(TableRow, { sx: HEADER_ROW_SX, children: [rowIdx === headerRows.length - 1 && hasCheckboxCol && (_jsx(TableCell, { padding: "checkbox", rowSpan: headerRows.length > 1 ? 1 : undefined, sx: CHECKBOX_CELL_SX, children: _jsx(Checkbox, { checked: allSelected, indeterminate: someSelected, onChange: (_, c) => handleSelectAll(!!c), size: "small", "aria-label": "Select all rows" }) })), rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol && (_jsx(TableCell, { rowSpan: headerRows.length - 1, sx: CHECKBOX_PLACEHOLDER_SX })), rowIdx === headerRows.length - 1 && hasRowNumbersCol && (_jsx(TableCell, { component: "th", scope: "col", rowSpan: headerRows.length > 1 ? 1 : undefined, sx: {
|
|
270
|
+
width: ROW_NUMBER_COLUMN_WIDTH,
|
|
271
|
+
minWidth: ROW_NUMBER_COLUMN_WIDTH,
|
|
272
|
+
maxWidth: ROW_NUMBER_COLUMN_WIDTH,
|
|
273
|
+
textAlign: 'center',
|
|
274
|
+
fontWeight: 600,
|
|
275
|
+
backgroundColor: 'action.hover',
|
|
276
|
+
position: 'sticky',
|
|
277
|
+
left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
|
|
278
|
+
zIndex: 4,
|
|
279
|
+
...headerCellSx,
|
|
280
|
+
}, children: "#" })), rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol && (_jsx(TableCell, { rowSpan: headerRows.length - 1, sx: {
|
|
281
|
+
width: ROW_NUMBER_COLUMN_WIDTH,
|
|
282
|
+
minWidth: ROW_NUMBER_COLUMN_WIDTH,
|
|
283
|
+
position: 'sticky',
|
|
284
|
+
left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
|
|
285
|
+
zIndex: 4,
|
|
286
|
+
backgroundColor: 'background.paper',
|
|
287
|
+
} })), row.map((cell, cellIdx) => {
|
|
242
288
|
if (cell.isGroup) {
|
|
243
289
|
return (_jsx(TableCell, { colSpan: cell.colSpan, component: "th", scope: "colgroup", sx: GROUP_HEADER_CELL_SX, children: cell.label }, cellIdx));
|
|
244
290
|
}
|
|
@@ -250,21 +296,47 @@ function DataGridTableInner(props) {
|
|
|
250
296
|
const isPinnedRight = col.pinned === 'right';
|
|
251
297
|
const columnWidth = getColumnWidth(col);
|
|
252
298
|
const headerSx = isPinnedLeft || (isFreezeCol && colIdx === 0) ? HEADER_PINNED_LEFT_SX : isPinnedRight ? HEADER_PINNED_RIGHT_SX : HEADER_BASE_SX;
|
|
253
|
-
return (_jsxs(TableCell, { component: "th", scope: "col", "data-column-id": col.columnId, rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : undefined, sx: headerSx, style: {
|
|
299
|
+
return (_jsxs(TableCell, { component: "th", scope: "col", "data-column-id": col.columnId, rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : undefined, sx: { ...headerSx, ...headerCellSx }, style: {
|
|
254
300
|
minWidth: col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH,
|
|
255
301
|
width: columnWidth,
|
|
256
302
|
maxWidth: columnWidth,
|
|
257
303
|
...(columnReorder ? { cursor: isReorderDragging ? 'grabbing' : 'grab' } : undefined),
|
|
258
|
-
}, onMouseDown: columnReorder ? (e) => handleHeaderMouseDown(col.columnId, e) : undefined, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx(Box, {
|
|
304
|
+
}, onMouseDown: columnReorder ? (e) => handleHeaderMouseDown(col.columnId, e) : undefined, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx(Box, { component: "button", onClick: (e) => {
|
|
305
|
+
e.stopPropagation();
|
|
306
|
+
pinning.headerMenu.open(col.columnId, e.currentTarget);
|
|
307
|
+
}, "aria-label": "Column options", title: "Column options", sx: {
|
|
308
|
+
background: 'transparent',
|
|
309
|
+
border: 'none',
|
|
310
|
+
cursor: 'pointer',
|
|
311
|
+
padding: '2px 4px',
|
|
312
|
+
fontSize: '16px',
|
|
313
|
+
lineHeight: 1,
|
|
314
|
+
color: 'text.secondary',
|
|
315
|
+
opacity: 0,
|
|
316
|
+
transition: 'opacity 0.15s, background-color 0.15s',
|
|
317
|
+
borderRadius: '3px',
|
|
318
|
+
display: 'flex',
|
|
319
|
+
alignItems: 'center',
|
|
320
|
+
justifyContent: 'center',
|
|
321
|
+
minWidth: '20px',
|
|
322
|
+
height: '20px',
|
|
323
|
+
'&:hover': {
|
|
324
|
+
bgcolor: 'action.hover',
|
|
325
|
+
opacity: 1,
|
|
326
|
+
},
|
|
327
|
+
'th:hover &': {
|
|
328
|
+
opacity: 1,
|
|
329
|
+
},
|
|
330
|
+
}, children: "\u22EE" })] }), _jsx(Box, { onMouseDown: (e) => handleResizeStart(e, col), sx: RESIZE_HANDLE_SX })] }, col.columnId));
|
|
259
331
|
})] }, rowIdx))) }), !showEmptyInGrid && (_jsxs(TableBody, { children: [virtualScrollEnabled && visibleRange.offsetTop > 0 && (_jsx(TableRow, { style: { height: visibleRange.offsetTop }, "aria-hidden": true })), (virtualScrollEnabled
|
|
260
332
|
? items.slice(visibleRange.startIndex, visibleRange.endIndex + 1).map((item, i) => {
|
|
261
333
|
const rowIndex = visibleRange.startIndex + i;
|
|
262
334
|
const rowIdStr = getRowId(item);
|
|
263
|
-
return (_jsx(GridRow, { item: item, rowIndex: rowIndex, rowId: rowIdStr, isSelected: selectedRowIds.has(rowIdStr), columnLayouts: columnLayouts, renderCellContent: renderCellContent, handleSingleRowClick: handleSingleRowClick, handleRowCheckboxChange: handleRowCheckboxChange, lastMouseShiftRef: lastMouseShiftRef, hasCheckboxCol: hasCheckboxCol, selectionRange: selectionRange, activeCell: interaction.activeCell, cutRange: cutRange, copyRange: copyRange, isDragging: isDragging, editingRowId: editingCell?.rowId ?? null }, rowIdStr));
|
|
335
|
+
return (_jsx(GridRow, { item: item, rowIndex: rowIndex, rowId: rowIdStr, isSelected: selectedRowIds.has(rowIdStr), columnLayouts: columnLayouts, 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));
|
|
264
336
|
})
|
|
265
337
|
: items.map((item, rowIndex) => {
|
|
266
338
|
const rowIdStr = getRowId(item);
|
|
267
|
-
return (_jsx(GridRow, { item: item, rowIndex: rowIndex, rowId: rowIdStr, isSelected: selectedRowIds.has(rowIdStr), columnLayouts: columnLayouts, renderCellContent: renderCellContent, handleSingleRowClick: handleSingleRowClick, handleRowCheckboxChange: handleRowCheckboxChange, lastMouseShiftRef: lastMouseShiftRef, hasCheckboxCol: hasCheckboxCol, selectionRange: selectionRange, activeCell: interaction.activeCell, cutRange: cutRange, copyRange: copyRange, isDragging: isDragging, editingRowId: editingCell?.rowId ?? null }, rowIdStr));
|
|
339
|
+
return (_jsx(GridRow, { item: item, rowIndex: rowIndex, rowId: rowIdStr, isSelected: selectedRowIds.has(rowIdStr), columnLayouts: columnLayouts, 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));
|
|
268
340
|
})), virtualScrollEnabled && visibleRange.offsetBottom > 0 && (_jsx(TableRow, { style: { height: visibleRange.offsetBottom }, "aria-hidden": true }))] }))] }), isReorderDragging && dropIndicatorX != null && (_jsx(Box, { sx: {
|
|
269
341
|
position: 'absolute',
|
|
270
342
|
top: 0,
|
|
@@ -276,6 +348,6 @@ function DataGridTableInner(props) {
|
|
|
276
348
|
transition: 'left 0.05s',
|
|
277
349
|
left: dropIndicatorX - (wrapperRef.current?.getBoundingClientRect().left ?? 0),
|
|
278
350
|
} })), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset }), showEmptyInGrid && emptyState && (_jsx(Box, { sx: EMPTY_STATE_SX, children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: "No results found" }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx(Button, { variant: "text", size: "small", onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }))] }) }) }), menuPosition &&
|
|
279
|
-
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(Box, { sx: LOADING_OVERLAY_SX, children: _jsxs(Box, { sx: LOADING_INNER_SX, children: [_jsx(CircularProgress, { size: 24 }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: loadingMessage })] }) }))] }));
|
|
351
|
+
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), _jsx(ColumnHeaderMenu, { columnId: pinning.headerMenu.openForColumn || '', 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 })] }), 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(Box, { sx: LOADING_OVERLAY_SX, children: _jsxs(Box, { sx: LOADING_INNER_SX, children: [_jsx(CircularProgress, { size: 24 }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: loadingMessage })] }) }))] }));
|
|
280
352
|
}
|
|
281
353
|
export const DataGridTable = React.memo(DataGridTableInner);
|
package/dist/esm/index.js
CHANGED
|
@@ -4,5 +4,6 @@ export { DataGridTable } from './DataGridTable/DataGridTable';
|
|
|
4
4
|
export { ColumnChooser } from './ColumnChooser/ColumnChooser';
|
|
5
5
|
export { ColumnHeaderFilter } from './ColumnHeaderFilter/ColumnHeaderFilter';
|
|
6
6
|
export { PaginationControls } from './PaginationControls/PaginationControls';
|
|
7
|
+
export { ColumnHeaderMenu } from './ColumnHeaderMenu/ColumnHeaderMenu';
|
|
7
8
|
// Re-export everything from core
|
|
8
9
|
export * from '@alaarab/ogrid-react';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ColumnHeaderMenuProps {
|
|
2
|
+
columnId: string;
|
|
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
|
+
/**
|
|
14
|
+
* Column header dropdown menu for pin/unpin actions.
|
|
15
|
+
* Uses Material UI Menu component.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ColumnHeaderMenu(props: ColumnHeaderMenuProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export { DataGridTable } from './DataGridTable/DataGridTable';
|
|
|
3
3
|
export { ColumnChooser, type IColumnChooserProps } from './ColumnChooser/ColumnChooser';
|
|
4
4
|
export { ColumnHeaderFilter, type IColumnHeaderFilterProps } from './ColumnHeaderFilter/ColumnHeaderFilter';
|
|
5
5
|
export { PaginationControls, type IPaginationControlsProps } from './PaginationControls/PaginationControls';
|
|
6
|
+
export { ColumnHeaderMenu, type ColumnHeaderMenuProps } from './ColumnHeaderMenu/ColumnHeaderMenu';
|
|
6
7
|
export * from '@alaarab/ogrid-react';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-react-material",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "OGrid Material UI implementation – MUI Table–based data grid with sorting, filtering, pagination, column chooser, spreadsheet selection, and CSV export.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"node": ">=18"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@alaarab/ogrid-react": "2.0.
|
|
41
|
+
"@alaarab/ogrid-react": "2.0.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@emotion/react": "^11.0.0",
|