@alaarab/ogrid-react-material 2.0.8 → 2.0.11
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/ColumnChooser/ColumnChooser.js +1 -1
- package/dist/esm/ColumnHeaderMenu/ColumnHeaderMenu.js +48 -34
- package/dist/esm/DataGridTable/DataGridTable.js +81 -38
- package/dist/esm/PaginationControls/PaginationControls.js +14 -8
- package/dist/types/ColumnHeaderMenu/ColumnHeaderMenu.d.ts +10 -2
- package/package.json +4 -4
|
@@ -26,7 +26,7 @@ export const ColumnChooser = (props) => {
|
|
|
26
26
|
ev.stopPropagation();
|
|
27
27
|
setColumnVisible(columnKey)(ev.target.checked);
|
|
28
28
|
};
|
|
29
|
-
return (_jsxs(Box, { className: className, sx: { display: 'inline-flex' }, children: [_jsxs(Button, { ref: buttonRef, variant: "outlined", size: "small", startIcon: _jsx(ViewColumnIcon, {}), endIcon: isOpen ? _jsx(ExpandLessIcon, {}) : _jsx(ExpandMoreIcon, {}), onClick: handleToggle, "aria-expanded": isOpen, "aria-haspopup": "listbox", sx: {
|
|
29
|
+
return (_jsxs(Box, { className: className, sx: { display: 'inline-flex' }, children: [_jsxs(Button, { ref: buttonRef, variant: "outlined", size: "small", color: "inherit", startIcon: _jsx(ViewColumnIcon, {}), endIcon: isOpen ? _jsx(ExpandLessIcon, {}) : _jsx(ExpandMoreIcon, {}), onClick: handleToggle, "aria-expanded": isOpen, "aria-haspopup": "listbox", sx: {
|
|
30
30
|
textTransform: 'none',
|
|
31
31
|
fontWeight: 600,
|
|
32
32
|
borderColor: isOpen ? 'primary.main' : 'divider',
|
|
@@ -1,39 +1,53 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import Menu from '@mui/material
|
|
4
|
-
import
|
|
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';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useMemo, useEffect, useState } from 'react';
|
|
3
|
+
import { Menu, MenuItem, Divider } from '@mui/material';
|
|
4
|
+
import { getColumnHeaderMenuItems } from '@alaarab/ogrid-core';
|
|
8
5
|
/**
|
|
9
|
-
* Column header dropdown menu for pin/
|
|
10
|
-
* Uses Material UI Menu component.
|
|
6
|
+
* Column header dropdown menu for pin/sort/autosize actions.
|
|
7
|
+
* Uses Material UI Menu component with anchor position.
|
|
11
8
|
*/
|
|
12
9
|
export function ColumnHeaderMenu(props) {
|
|
13
|
-
const {
|
|
14
|
-
const [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
const { isOpen, anchorElement, onClose, onPinLeft, onPinRight, onUnpin, onSortAsc, onSortDesc, onClearSort, onAutosizeThis, onAutosizeAll, canPinLeft, canPinRight, canUnpin, currentSort, isSortable, isResizable, } = props;
|
|
11
|
+
const [anchorPosition, setAnchorPosition] = useState(undefined);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (isOpen && anchorElement) {
|
|
14
|
+
const rect = anchorElement.getBoundingClientRect();
|
|
15
|
+
setAnchorPosition({
|
|
16
|
+
top: rect.bottom + 4,
|
|
17
|
+
left: rect.left,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
setAnchorPosition(undefined);
|
|
22
|
+
}
|
|
23
|
+
}, [isOpen, anchorElement]);
|
|
24
|
+
const menuInput = useMemo(() => ({
|
|
25
|
+
canPinLeft,
|
|
26
|
+
canPinRight,
|
|
27
|
+
canUnpin,
|
|
28
|
+
currentSort,
|
|
29
|
+
isSortable,
|
|
30
|
+
isResizable,
|
|
31
|
+
}), [canPinLeft, canPinRight, canUnpin, currentSort, isSortable, isResizable]);
|
|
32
|
+
const items = useMemo(() => getColumnHeaderMenuItems(menuInput), [menuInput]);
|
|
33
|
+
const handlers = {
|
|
34
|
+
pinLeft: onPinLeft,
|
|
35
|
+
pinRight: onPinRight,
|
|
36
|
+
unpin: onUnpin,
|
|
37
|
+
sortAsc: onSortAsc,
|
|
38
|
+
sortDesc: onSortDesc,
|
|
39
|
+
clearSort: onClearSort,
|
|
40
|
+
autosizeThis: onAutosizeThis,
|
|
41
|
+
autosizeAll: onAutosizeAll,
|
|
18
42
|
};
|
|
19
|
-
return (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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 })] })] }));
|
|
43
|
+
return (_jsx(Menu, { open: isOpen && !!anchorPosition, onClose: onClose, anchorReference: "anchorPosition", anchorPosition: anchorPosition, slotProps: {
|
|
44
|
+
paper: {
|
|
45
|
+
sx: {
|
|
46
|
+
minWidth: 140,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}, children: items.map((item, idx) => (_jsxs(React.Fragment, { children: [_jsx(MenuItem, { disabled: item.disabled, onClick: () => {
|
|
50
|
+
handlers[item.id]();
|
|
51
|
+
onClose();
|
|
52
|
+
}, children: item.label }), item.divider && idx < items.length - 1 && _jsx(Divider, {})] }, item.id))) }));
|
|
39
53
|
}
|
|
@@ -18,7 +18,13 @@ const CHECKBOX_CELL_SX = { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLU
|
|
|
18
18
|
const CHECKBOX_WRAPPER_SX = { display: 'flex', alignItems: 'center', justifyContent: 'center' };
|
|
19
19
|
const CHECKBOX_PLACEHOLDER_SX = { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLUMN_WIDTH, p: 0 };
|
|
20
20
|
// Header
|
|
21
|
-
const STICKY_HEADER_SX = {
|
|
21
|
+
const STICKY_HEADER_SX = {
|
|
22
|
+
/* Removed position: 'sticky', top: 0 - breaks horizontal sticky on pinned columns.
|
|
23
|
+
Instead, apply sticky to individual header cells (HEADER_BASE_SX). */
|
|
24
|
+
zIndex: 8,
|
|
25
|
+
bgcolor: 'action.hover',
|
|
26
|
+
'& th': { bgcolor: 'action.hover' }
|
|
27
|
+
};
|
|
22
28
|
const HEADER_ROW_SX = { bgcolor: 'action.hover' };
|
|
23
29
|
const GROUP_HEADER_CELL_SX = { textAlign: 'center', fontWeight: 600, borderBottom: 2, borderColor: 'divider', py: 0.75 };
|
|
24
30
|
// Density padding helper
|
|
@@ -34,6 +40,7 @@ const CELL_CONTENT_BASE_SX = {
|
|
|
34
40
|
width: '100%', height: '100%', display: 'flex', alignItems: 'center', minWidth: 0,
|
|
35
41
|
px: '10px', py: '6px', boxSizing: 'border-box', overflow: 'hidden',
|
|
36
42
|
textOverflow: 'ellipsis', whiteSpace: 'nowrap', userSelect: 'none', outline: 'none',
|
|
43
|
+
'&:focus-visible': { outline: '2px solid', outlineColor: 'primary.main', outlineOffset: '-2px', zIndex: 3 },
|
|
37
44
|
};
|
|
38
45
|
const CELL_CONTENT_NUMERIC_SX = { ...CELL_CONTENT_BASE_SX, justifyContent: 'flex-end', textAlign: 'right' };
|
|
39
46
|
const CELL_CONTENT_BOOLEAN_SX = { ...CELL_CONTENT_BASE_SX, justifyContent: 'center', textAlign: 'center' };
|
|
@@ -101,9 +108,15 @@ const CELL_TD_BASE_SX = { position: 'relative', p: 0, height: '1px' };
|
|
|
101
108
|
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
109
|
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' };
|
|
103
110
|
// Header cell positioning variants
|
|
104
|
-
const HEADER_BASE_SX = {
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
const HEADER_BASE_SX = {
|
|
112
|
+
fontWeight: 600,
|
|
113
|
+
position: 'sticky', /* Changed from relative - enables vertical sticky for all headers */
|
|
114
|
+
top: 0, /* Sticky vertically */
|
|
115
|
+
zIndex: 8, /* Stack above body cells */
|
|
116
|
+
bgcolor: 'action.hover' /* Required for sticky overlap */
|
|
117
|
+
};
|
|
118
|
+
const HEADER_PINNED_LEFT_SX = { ...HEADER_BASE_SX, position: 'sticky', left: 0, top: 0, zIndex: 10 /* Increased from 9 to stack above base header cells (z-index: 8) */, bgcolor: 'action.hover', willChange: 'transform', borderLeft: '2px solid', borderLeftColor: 'primary.main' };
|
|
119
|
+
const HEADER_PINNED_RIGHT_SX = { ...HEADER_BASE_SX, position: 'sticky', right: 0, top: 0, zIndex: 10 /* Increased from 9 to stack above base header cells (z-index: 8) */, bgcolor: 'action.hover', willChange: 'transform', borderRight: '2px solid', borderRightColor: 'primary.main' };
|
|
107
120
|
// Resize handle
|
|
108
121
|
const RESIZE_HANDLE_SX = {
|
|
109
122
|
position: 'absolute', top: 0, right: '-3px', bottom: 0, width: '8px',
|
|
@@ -150,7 +163,7 @@ function GridRowInner(props) {
|
|
|
150
163
|
position: 'sticky',
|
|
151
164
|
left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
|
|
152
165
|
zIndex: 3,
|
|
153
|
-
}, children: rowNumberOffset + rowIndex + 1 })), columnLayouts.map((cl, colIdx) => (_jsx(TableCell, { sx: cl.tdSx,
|
|
166
|
+
}, children: rowNumberOffset + rowIndex + 1 })), columnLayouts.map((cl, colIdx) => (_jsx(TableCell, { sx: [cl.tdSx, { minWidth: cl.minWidth, width: cl.width, maxWidth: cl.maxWidth }], children: renderCellContent(item, cl.col, rowIndex, colIdx) }, cl.col.columnId)))] }));
|
|
154
167
|
}
|
|
155
168
|
const GridRow = React.memo(GridRowInner, areGridRowPropsEqual);
|
|
156
169
|
function DataGridTableInner(props) {
|
|
@@ -268,27 +281,40 @@ function DataGridTableInner(props) {
|
|
|
268
281
|
},
|
|
269
282
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- *Ref vars are stable refs from useLatestRef
|
|
270
283
|
[editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit, getRowId, onCellError]);
|
|
271
|
-
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, {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
284
|
+
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, { ...{
|
|
285
|
+
component: "th",
|
|
286
|
+
scope: "col",
|
|
287
|
+
rowSpan: headerRows.length > 1 ? 1 : undefined,
|
|
288
|
+
sx: {
|
|
289
|
+
width: ROW_NUMBER_COLUMN_WIDTH,
|
|
290
|
+
minWidth: ROW_NUMBER_COLUMN_WIDTH,
|
|
291
|
+
maxWidth: ROW_NUMBER_COLUMN_WIDTH,
|
|
292
|
+
textAlign: 'center',
|
|
293
|
+
fontWeight: 600,
|
|
294
|
+
backgroundColor: 'action.hover',
|
|
295
|
+
position: 'sticky',
|
|
296
|
+
left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
|
|
297
|
+
zIndex: 4,
|
|
298
|
+
...headerCellSx,
|
|
299
|
+
}
|
|
300
|
+
}, children: "#" })), rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol && (_jsx(TableCell, { ...{
|
|
301
|
+
rowSpan: headerRows.length - 1,
|
|
302
|
+
sx: {
|
|
303
|
+
width: ROW_NUMBER_COLUMN_WIDTH,
|
|
304
|
+
minWidth: ROW_NUMBER_COLUMN_WIDTH,
|
|
305
|
+
position: 'sticky',
|
|
306
|
+
left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
|
|
307
|
+
zIndex: 4,
|
|
308
|
+
backgroundColor: 'background.paper',
|
|
309
|
+
}
|
|
289
310
|
} })), row.map((cell, cellIdx) => {
|
|
290
311
|
if (cell.isGroup) {
|
|
291
|
-
return (_jsx(TableCell, {
|
|
312
|
+
return (_jsx(TableCell, { ...{
|
|
313
|
+
colSpan: cell.colSpan,
|
|
314
|
+
component: "th",
|
|
315
|
+
scope: "colgroup",
|
|
316
|
+
sx: GROUP_HEADER_CELL_SX
|
|
317
|
+
}, children: cell.label }, cellIdx));
|
|
292
318
|
}
|
|
293
319
|
// Leaf cell
|
|
294
320
|
const col = cell.columnDef;
|
|
@@ -298,12 +324,33 @@ function DataGridTableInner(props) {
|
|
|
298
324
|
const isPinnedRight = col.pinned === 'right';
|
|
299
325
|
const columnWidth = getColumnWidth(col);
|
|
300
326
|
const headerSx = isPinnedLeft || (isFreezeCol && colIdx === 0) ? HEADER_PINNED_LEFT_SX : isPinnedRight ? HEADER_PINNED_RIGHT_SX : HEADER_BASE_SX;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
327
|
+
// Determine aria-sort value for sorted columns
|
|
328
|
+
const isSorted = props.sortBy === col.columnId;
|
|
329
|
+
const ariaSort = isSorted
|
|
330
|
+
? (props.sortDirection === 'asc' ? 'ascending' : 'descending')
|
|
331
|
+
: undefined;
|
|
332
|
+
return (_jsxs(TableCell, { ...{
|
|
333
|
+
component: "th",
|
|
334
|
+
scope: "col",
|
|
335
|
+
'data-column-id': col.columnId,
|
|
336
|
+
rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : undefined,
|
|
337
|
+
'aria-sort': ariaSort,
|
|
338
|
+
sx: {
|
|
339
|
+
...headerSx,
|
|
340
|
+
...headerCellSx,
|
|
341
|
+
minWidth: col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH,
|
|
342
|
+
width: columnWidth,
|
|
343
|
+
maxWidth: columnWidth,
|
|
344
|
+
...(columnReorder ? { cursor: isReorderDragging ? 'grabbing' : 'grab' } : {}),
|
|
345
|
+
'&:focus-visible': {
|
|
346
|
+
outline: '2px solid',
|
|
347
|
+
outlineColor: 'primary.main',
|
|
348
|
+
outlineOffset: '-2px',
|
|
349
|
+
zIndex: 11,
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
onMouseDown: columnReorder ? (e) => handleHeaderMouseDown(col.columnId, e) : undefined
|
|
353
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx(Box, { component: "button", onClick: (e) => {
|
|
307
354
|
e.stopPropagation();
|
|
308
355
|
pinning.headerMenu.open(col.columnId, e.currentTarget);
|
|
309
356
|
}, "aria-label": "Column options", title: "Column options", sx: {
|
|
@@ -314,8 +361,8 @@ function DataGridTableInner(props) {
|
|
|
314
361
|
fontSize: '16px',
|
|
315
362
|
lineHeight: 1,
|
|
316
363
|
color: 'text.secondary',
|
|
317
|
-
opacity:
|
|
318
|
-
transition: '
|
|
364
|
+
opacity: 1,
|
|
365
|
+
transition: 'background-color 0.15s',
|
|
319
366
|
borderRadius: '3px',
|
|
320
367
|
display: 'flex',
|
|
321
368
|
alignItems: 'center',
|
|
@@ -324,10 +371,6 @@ function DataGridTableInner(props) {
|
|
|
324
371
|
height: '20px',
|
|
325
372
|
'&:hover': {
|
|
326
373
|
bgcolor: 'action.hover',
|
|
327
|
-
opacity: 1,
|
|
328
|
-
},
|
|
329
|
-
'th:hover &': {
|
|
330
|
-
opacity: 1,
|
|
331
374
|
},
|
|
332
375
|
}, children: "\u22EE" })] }), _jsx(Box, { onMouseDown: (e) => handleResizeStart(e, col), sx: RESIZE_HANDLE_SX })] }, col.columnId));
|
|
333
376
|
})] }, rowIdx))) }), !showEmptyInGrid && (_jsxs(TableBody, { children: [virtualScrollEnabled && visibleRange.offsetTop > 0 && (_jsx(TableRow, { style: { height: visibleRange.offsetTop }, "aria-hidden": true })), (virtualScrollEnabled
|
|
@@ -349,7 +392,7 @@ function DataGridTableInner(props) {
|
|
|
349
392
|
zIndex: 100,
|
|
350
393
|
transition: 'left 0.05s',
|
|
351
394
|
left: dropIndicatorX - (wrapperRef.current?.getBoundingClientRect().left ?? 0),
|
|
352
|
-
} })), _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 &&
|
|
353
|
-
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 })] }) }))] }));
|
|
395
|
+
} })), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset, items: items, visibleColumns: props.visibleColumns, columnSizingOverrides: columnSizingOverrides, columnOrder: props.columnOrder }), 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 &&
|
|
396
|
+
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, onSortAsc: pinning.headerMenu.handleSortAsc, onSortDesc: pinning.headerMenu.handleSortDesc, onClearSort: pinning.headerMenu.handleClearSort, onAutosizeThis: pinning.headerMenu.handleAutosizeThis, onAutosizeAll: pinning.headerMenu.handleAutosizeAll, canPinLeft: pinning.headerMenu.canPinLeft, canPinRight: pinning.headerMenu.canPinRight, canUnpin: pinning.headerMenu.canUnpin, currentSort: pinning.headerMenu.currentSort, isSortable: pinning.headerMenu.isSortable, isResizable: pinning.headerMenu.isResizable })] }), 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 })] }) }))] }));
|
|
354
397
|
}
|
|
355
398
|
export const DataGridTable = React.memo(DataGridTableInner);
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { useMemo, useCallback } from 'react';
|
|
4
3
|
import { IconButton, Button, Select, MenuItem, Box, Typography, } from '@mui/material';
|
|
5
4
|
import { FirstPage as FirstPageIcon, LastPage as LastPageIcon, ChevronLeft as ChevronLeftIcon, ChevronRight as ChevronRightIcon, } from '@mui/icons-material';
|
|
6
|
-
import {
|
|
5
|
+
import { usePaginationControls } from '@alaarab/ogrid-react';
|
|
7
6
|
export const PaginationControls = React.memo((props) => {
|
|
8
7
|
const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, pageSizeOptions, entityLabelPlural, className, } = props;
|
|
9
|
-
const labelPlural
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const { labelPlural, vm, handlePageSizeChange } = usePaginationControls({
|
|
9
|
+
currentPage,
|
|
10
|
+
pageSize,
|
|
11
|
+
totalCount,
|
|
12
|
+
onPageChange,
|
|
13
|
+
onPageSizeChange,
|
|
14
|
+
pageSizeOptions,
|
|
15
|
+
entityLabelPlural,
|
|
16
|
+
});
|
|
17
|
+
const handlePageSizeChangeEvent = (event) => {
|
|
18
|
+
handlePageSizeChange(Number(event.target.value));
|
|
19
|
+
};
|
|
14
20
|
if (!vm) {
|
|
15
21
|
return null;
|
|
16
22
|
}
|
|
@@ -25,5 +31,5 @@ export const PaginationControls = React.memo((props) => {
|
|
|
25
31
|
width: '100%',
|
|
26
32
|
minWidth: 0,
|
|
27
33
|
boxSizing: 'border-box',
|
|
28
|
-
}, children: [_jsxs(Typography, { variant: "body2", color: "text.secondary", children: ["Showing ", startItem, " to ", endItem, " of ", totalCount.toLocaleString(), " ", labelPlural] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(IconButton, { size: "small", onClick: () => onPageChange(1), disabled: currentPage === 1, "aria-label": "First page", children: _jsx(FirstPageIcon, { fontSize: "small" }) }), _jsx(IconButton, { size: "small", onClick: () => onPageChange(currentPage - 1), disabled: currentPage === 1, "aria-label": "Previous page", children: _jsx(ChevronLeftIcon, { fontSize: "small" }) }), showStartEllipsis && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outlined", size: "small", onClick: () => onPageChange(1), "aria-label": "Page 1", sx: { minWidth: 32, px: 0.5 }, children: "1" }), _jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mx: 0.5 }, "aria-hidden": true, children: "\u2026" })] })), pageNumbers.map((pageNum) => (_jsx(Button, { variant: currentPage === pageNum ? 'contained' : 'outlined', size: "small", onClick: () => onPageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": currentPage === pageNum ? 'page' : undefined, sx: { minWidth: 32, px: 0.5 }, children: pageNum }, pageNum))), showEndEllipsis && (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mx: 0.5 }, "aria-hidden": true, children: "\u2026" }), _jsx(Button, { variant: "outlined", size: "small", onClick: () => onPageChange(totalPages), "aria-label": `Page ${totalPages}`, sx: { minWidth: 32, px: 0.5 }, children: totalPages })] })), _jsx(IconButton, { size: "small", onClick: () => onPageChange(currentPage + 1), disabled: currentPage >= totalPages, "aria-label": "Next page", children: _jsx(ChevronRightIcon, { fontSize: "small" }) }), _jsx(IconButton, { size: "small", onClick: () => onPageChange(totalPages), disabled: currentPage >= totalPages, "aria-label": "Last page", children: _jsx(LastPageIcon, { fontSize: "small" }) })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(Typography, { variant: "body2", color: "text.secondary", children: "Rows" }), _jsx(Select, { value: pageSize, onChange:
|
|
34
|
+
}, children: [_jsxs(Typography, { variant: "body2", color: "text.secondary", children: ["Showing ", startItem, " to ", endItem, " of ", totalCount.toLocaleString(), " ", labelPlural] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(IconButton, { size: "small", onClick: () => onPageChange(1), disabled: currentPage === 1, "aria-label": "First page", children: _jsx(FirstPageIcon, { fontSize: "small" }) }), _jsx(IconButton, { size: "small", onClick: () => onPageChange(currentPage - 1), disabled: currentPage === 1, "aria-label": "Previous page", children: _jsx(ChevronLeftIcon, { fontSize: "small" }) }), showStartEllipsis && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outlined", size: "small", onClick: () => onPageChange(1), "aria-label": "Page 1", sx: { minWidth: 32, px: 0.5 }, children: "1" }), _jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mx: 0.5 }, "aria-hidden": true, children: "\u2026" })] })), pageNumbers.map((pageNum) => (_jsx(Button, { variant: currentPage === pageNum ? 'contained' : 'outlined', size: "small", onClick: () => onPageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": currentPage === pageNum ? 'page' : undefined, sx: { minWidth: 32, px: 0.5 }, children: pageNum }, pageNum))), showEndEllipsis && (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mx: 0.5 }, "aria-hidden": true, children: "\u2026" }), _jsx(Button, { variant: "outlined", size: "small", onClick: () => onPageChange(totalPages), "aria-label": `Page ${totalPages}`, sx: { minWidth: 32, px: 0.5 }, children: totalPages })] })), _jsx(IconButton, { size: "small", onClick: () => onPageChange(currentPage + 1), disabled: currentPage >= totalPages, "aria-label": "Next page", children: _jsx(ChevronRightIcon, { fontSize: "small" }) }), _jsx(IconButton, { size: "small", onClick: () => onPageChange(totalPages), disabled: currentPage >= totalPages, "aria-label": "Last page", children: _jsx(LastPageIcon, { fontSize: "small" }) })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(Typography, { variant: "body2", color: "text.secondary", children: "Rows" }), _jsx(Select, { value: pageSize, onChange: handlePageSizeChangeEvent, size: "small", "aria-label": "Rows per page", sx: { minWidth: 70 }, children: vm.pageSizeOptions.map((n) => (_jsx(MenuItem, { value: n, children: n }, n))) })] })] }));
|
|
29
35
|
});
|
|
@@ -6,12 +6,20 @@ export interface ColumnHeaderMenuProps {
|
|
|
6
6
|
onPinLeft: () => void;
|
|
7
7
|
onPinRight: () => void;
|
|
8
8
|
onUnpin: () => void;
|
|
9
|
+
onSortAsc: () => void;
|
|
10
|
+
onSortDesc: () => void;
|
|
11
|
+
onClearSort: () => void;
|
|
12
|
+
onAutosizeThis: () => void;
|
|
13
|
+
onAutosizeAll: () => void;
|
|
9
14
|
canPinLeft: boolean;
|
|
10
15
|
canPinRight: boolean;
|
|
11
16
|
canUnpin: boolean;
|
|
17
|
+
currentSort: 'asc' | 'desc' | null;
|
|
18
|
+
isSortable: boolean;
|
|
19
|
+
isResizable: boolean;
|
|
12
20
|
}
|
|
13
21
|
/**
|
|
14
|
-
* Column header dropdown menu for pin/
|
|
15
|
-
* Uses Material UI Menu component.
|
|
22
|
+
* Column header dropdown menu for pin/sort/autosize actions.
|
|
23
|
+
* Uses Material UI Menu component with anchor position.
|
|
16
24
|
*/
|
|
17
25
|
export declare function ColumnHeaderMenu(props: ColumnHeaderMenuProps): import("react/jsx-runtime").JSX.Element;
|
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.11",
|
|
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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=18"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@alaarab/ogrid-react": "2.0.
|
|
42
|
+
"@alaarab/ogrid-react": "2.0.11"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@emotion/react": "^11.0.0",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"@testing-library/jest-dom": "^6.9.1",
|
|
59
59
|
"@testing-library/react": "^16.3.2",
|
|
60
60
|
"@testing-library/user-event": "^14.6.1",
|
|
61
|
-
"@types/react": "^
|
|
62
|
-
"@types/react-dom": "^
|
|
61
|
+
"@types/react": "^19.0.0",
|
|
62
|
+
"@types/react-dom": "^19.0.0",
|
|
63
63
|
"eslint-plugin-storybook": "10.2.8",
|
|
64
64
|
"react": "^18.3.1",
|
|
65
65
|
"react-dom": "^18.3.1",
|