@alaarab/ogrid-react-material 2.1.1 → 2.1.3

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.
@@ -1,5 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useMemo, useEffect, useState } from 'react';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo, useEffect, useState } from 'react';
3
3
  import { Menu, MenuItem, Divider } from '@mui/material';
4
4
  import { getColumnHeaderMenuItems } from '@alaarab/ogrid-react';
5
5
  /**
@@ -46,8 +46,16 @@ export function ColumnHeaderMenu(props) {
46
46
  minWidth: 140,
47
47
  },
48
48
  },
49
- }, children: items.map((item, idx) => (_jsxs(React.Fragment, { children: [_jsx(MenuItem, { disabled: item.disabled, onClick: () => {
49
+ }, children: items.flatMap((item, idx) => {
50
+ const elements = [
51
+ _jsx(MenuItem, { disabled: item.disabled, onClick: () => {
50
52
  handlers[item.id]();
51
53
  onClose();
52
- }, children: item.label }), item.divider && idx < items.length - 1 && _jsx(Divider, {})] }, item.id))) }));
54
+ }, children: item.label }, item.id),
55
+ ];
56
+ if (item.divider && idx < items.length - 1) {
57
+ elements.push(_jsx(Divider, {}, `${item.id}-divider`));
58
+ }
59
+ return elements;
60
+ }) }));
53
61
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import * as React from 'react';
3
3
  import { useCallback, useMemo } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
- import { Box, Popover, Checkbox, Table, TableHead, TableBody, TableRow, TableCell, TableContainer, } from '@mui/material';
5
+ import { Box, Popover, Checkbox, Table, TableHead, TableBody, TableRow, TableCell, } from '@mui/material';
6
6
  import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
7
7
  import { ColumnHeaderMenu } from '../ColumnHeaderMenu';
8
8
  import { InlineCellEditor } from './InlineCellEditor';
@@ -35,15 +35,18 @@ const ROW_HOVER_SX = { '&:hover': { bgcolor: 'action.hover' } };
35
35
  const CHECKBOX_CELL_SX = { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLUMN_WIDTH, maxWidth: CHECKBOX_COLUMN_WIDTH, textAlign: 'center' };
36
36
  const CHECKBOX_WRAPPER_SX = { display: 'flex', alignItems: 'center', justifyContent: 'center' };
37
37
  const CHECKBOX_PLACEHOLDER_SX = { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLUMN_WIDTH, p: 0 };
38
- // Header
38
+ // Header — use opaque var(--ogrid-header-bg) (not semi-transparent action.hover) so sticky
39
+ // headers fully occlude pinned-column content scrolling beneath them.
40
+ // The CSS variable is theme-aware: light=#f5f5f5, dark=#2c2c2c (set by each UI package).
41
+ const HEADER_BG = 'var(--ogrid-header-bg, #f5f5f5)';
39
42
  const STICKY_HEADER_SX = {
40
43
  /* Removed position: 'sticky', top: 0 - breaks horizontal sticky on pinned columns.
41
44
  Instead, apply sticky to individual header cells (HEADER_BASE_SX). */
42
45
  zIndex: 8,
43
- bgcolor: 'action.hover',
44
- '& th': { bgcolor: 'action.hover' }
46
+ bgcolor: HEADER_BG,
47
+ '& th': { bgcolor: HEADER_BG }
45
48
  };
46
- const HEADER_ROW_SX = { bgcolor: 'action.hover' };
49
+ const HEADER_ROW_SX = { bgcolor: HEADER_BG };
47
50
  const GROUP_HEADER_CELL_SX = { textAlign: 'center', fontWeight: 600, borderBottom: 2, borderColor: 'divider', py: 0.75 };
48
51
  // Density padding helper
49
52
  function getDensityPadding(density) {
@@ -127,42 +130,53 @@ const CELL_TD_BASE_SX = { position: 'relative', p: 0, height: '1px' };
127
130
  const CELL_TD_PINNED_LEFT_SX = {
128
131
  ...CELL_TD_BASE_SX, position: 'sticky', left: 0, zIndex: 6,
129
132
  bgcolor: 'background.paper', willChange: 'transform',
130
- '&::after': {
131
- content: '""', position: 'absolute', top: '-1px', right: '-4px', bottom: '-1px',
132
- width: '4px', background: 'linear-gradient(to right, rgba(0,0,0,0.12), transparent)', pointerEvents: 'none',
133
- },
133
+ borderRight: '1px solid', borderRightColor: 'divider',
134
+ boxShadow: '2px 0 4px -1px rgba(0,0,0,0.1)',
134
135
  };
135
136
  const CELL_TD_PINNED_RIGHT_SX = {
136
137
  ...CELL_TD_BASE_SX, position: 'sticky', right: 0, zIndex: 6,
137
138
  bgcolor: 'background.paper', willChange: 'transform',
138
- '&::before': {
139
- content: '""', position: 'absolute', top: '-1px', left: '-4px', bottom: '-1px',
140
- width: '4px', background: 'linear-gradient(to left, rgba(0,0,0,0.12), transparent)', pointerEvents: 'none',
141
- },
139
+ borderLeft: '1px solid', borderLeftColor: 'divider',
140
+ boxShadow: '-2px 0 4px -1px rgba(0,0,0,0.1)',
142
141
  };
143
- // Header cell positioning variants
142
+ // Header cell positioning variants (sticky)
143
+ // Use opaque HEADER_BG so headers fully occlude content scrolling beneath them.
144
144
  const HEADER_BASE_SX = {
145
145
  fontWeight: 600,
146
146
  position: 'sticky', /* Enables vertical sticky for all headers */
147
147
  top: 0, /* Sticky vertically */
148
148
  zIndex: 8, /* Stack above body cells */
149
- bgcolor: 'action.hover' /* Required for sticky overlap */
149
+ bgcolor: HEADER_BG /* Opaque — required for sticky overlap */
150
150
  };
151
151
  const HEADER_PINNED_LEFT_SX = {
152
152
  ...HEADER_BASE_SX, position: 'sticky', left: 0, top: 0,
153
- zIndex: 10, bgcolor: 'action.hover', willChange: 'transform',
154
- '&::after': {
155
- content: '""', position: 'absolute', top: '-1px', right: '-4px', bottom: '-1px',
156
- width: '4px', background: 'linear-gradient(to right, rgba(0,0,0,0.12), transparent)', pointerEvents: 'none',
157
- },
153
+ zIndex: 10, bgcolor: HEADER_BG, willChange: 'transform',
154
+ borderRight: '1px solid', borderRightColor: 'divider',
155
+ boxShadow: '2px 0 4px -1px rgba(0,0,0,0.1)',
158
156
  };
159
157
  const HEADER_PINNED_RIGHT_SX = {
160
158
  ...HEADER_BASE_SX, position: 'sticky', right: 0, top: 0,
161
- zIndex: 10, bgcolor: 'action.hover', willChange: 'transform',
162
- '&::before': {
163
- content: '""', position: 'absolute', top: '-1px', left: '-4px', bottom: '-1px',
164
- width: '4px', background: 'linear-gradient(to left, rgba(0,0,0,0.12), transparent)', pointerEvents: 'none',
165
- },
159
+ zIndex: 10, bgcolor: HEADER_BG, willChange: 'transform',
160
+ borderLeft: '1px solid', borderLeftColor: 'divider',
161
+ boxShadow: '-2px 0 4px -1px rgba(0,0,0,0.1)',
162
+ };
163
+ // Header cell variants (non-sticky — stickyHeader=false)
164
+ const HEADER_BASE_NO_STICKY_SX = {
165
+ fontWeight: 600,
166
+ zIndex: 8,
167
+ bgcolor: HEADER_BG,
168
+ };
169
+ const HEADER_PINNED_LEFT_NO_STICKY_SX = {
170
+ ...HEADER_BASE_NO_STICKY_SX, position: 'sticky', left: 0,
171
+ zIndex: 10, bgcolor: HEADER_BG, willChange: 'transform',
172
+ borderRight: '1px solid', borderRightColor: 'divider',
173
+ boxShadow: '2px 0 4px -1px rgba(0,0,0,0.1)',
174
+ };
175
+ const HEADER_PINNED_RIGHT_NO_STICKY_SX = {
176
+ ...HEADER_BASE_NO_STICKY_SX, position: 'sticky', right: 0,
177
+ zIndex: 10, bgcolor: HEADER_BG, willChange: 'transform',
178
+ borderLeft: '1px solid', borderLeftColor: 'divider',
179
+ boxShadow: '-2px 0 4px -1px rgba(0,0,0,0.1)',
166
180
  };
167
181
  // Resize handle
168
182
  const RESIZE_HANDLE_SX = {
@@ -300,7 +314,7 @@ function DataGridTableInner(props) {
300
314
  }
301
315
  return (_jsx(CellErrorBoundary, { onError: onCellError, children: cellContent }, `${rowId}-${col.columnId}`));
302
316
  }, [editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit, getRowId, onCellError, cellDescriptorInputRef, densityPadding, pendingEditorValueRef, popoverAnchorElRef]);
303
- 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: { minWidth: minTableWidth, borderCollapse: 'separate', borderSpacing: 0 }, 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, { ...{
317
+ 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("div", { style: { 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: { minWidth: minTableWidth, borderCollapse: 'separate', borderSpacing: 0 }, 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, { ...{
304
318
  component: "th",
305
319
  scope: "col",
306
320
  rowSpan: headerRows.length > 1 ? 1 : undefined,
@@ -310,7 +324,7 @@ function DataGridTableInner(props) {
310
324
  maxWidth: ROW_NUMBER_COLUMN_WIDTH,
311
325
  textAlign: 'center',
312
326
  fontWeight: 600,
313
- backgroundColor: 'action.hover',
327
+ backgroundColor: HEADER_BG,
314
328
  position: 'sticky',
315
329
  left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
316
330
  zIndex: 4,
@@ -341,7 +355,9 @@ function DataGridTableInner(props) {
341
355
  const col = cell.columnDef;
342
356
  const isPinnedLeft = pinning.pinnedColumns[col.columnId] === 'left';
343
357
  const isPinnedRight = pinning.pinnedColumns[col.columnId] === 'right';
344
- const baseHeaderSx = isPinnedLeft ? HEADER_PINNED_LEFT_SX : isPinnedRight ? HEADER_PINNED_RIGHT_SX : HEADER_BASE_SX;
358
+ const baseHeaderSx = o.stickyHeader
359
+ ? (isPinnedLeft ? HEADER_PINNED_LEFT_SX : isPinnedRight ? HEADER_PINNED_RIGHT_SX : HEADER_BASE_SX)
360
+ : (isPinnedLeft ? HEADER_PINNED_LEFT_NO_STICKY_SX : isPinnedRight ? HEADER_PINNED_RIGHT_NO_STICKY_SX : HEADER_BASE_NO_STICKY_SX);
345
361
  // Override sticky offset for pinned columns (supports multiple pinned columns)
346
362
  const headerSx = isPinnedLeft && pinning.leftOffsets[col.columnId] != null
347
363
  ? { ...baseHeaderSx, left: pinning.leftOffsets[col.columnId] }
@@ -14,5 +14,12 @@ export function GridContextMenu(props) {
14
14
  return true;
15
15
  return false;
16
16
  }, [hasSelection, canUndo, canRedo]);
17
- return (_jsx(Menu, { open: true, onClose: onClose, anchorReference: "anchorPosition", anchorPosition: { top: y, left: x }, MenuListProps: { dense: true, 'aria-label': 'Grid context menu' }, children: GRID_CONTEXT_MENU_ITEMS.map((item) => (_jsxs(React.Fragment, { children: [item.dividerBefore && _jsx(Divider, {}), _jsxs(MenuItem, { onClick: handlers[item.id], disabled: isDisabled(item), children: [_jsx("span", { style: { flex: 1 }, children: item.label }), item.shortcut && (_jsx("span", { style: { marginLeft: 24, color: 'var(--ogrid-fg-muted, rgba(0,0,0,0.4))', fontSize: '0.8em' }, children: formatShortcut(item.shortcut) }))] })] }, item.id))) }));
17
+ return (_jsx(Menu, { open: true, onClose: onClose, anchorReference: "anchorPosition", anchorPosition: { top: y, left: x }, MenuListProps: { dense: true, 'aria-label': 'Grid context menu' }, children: GRID_CONTEXT_MENU_ITEMS.flatMap((item) => {
18
+ const elements = [];
19
+ if (item.dividerBefore) {
20
+ elements.push(_jsx(Divider, {}, `${item.id}-divider`));
21
+ }
22
+ elements.push(_jsxs(MenuItem, { onClick: handlers[item.id], disabled: isDisabled(item), children: [_jsx("span", { style: { flex: 1 }, children: item.label }), item.shortcut && (_jsx("span", { style: { marginLeft: 24, color: 'var(--ogrid-fg-muted, rgba(0,0,0,0.4))', fontSize: '0.8em' }, children: formatShortcut(item.shortcut) }))] }, item.id));
23
+ return elements;
24
+ }) }));
18
25
  }
@@ -1,28 +1,31 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
- import { forwardRef } from 'react';
4
3
  import { Box, useTheme } from '@mui/material';
4
+ import { createOGrid } from '@alaarab/ogrid-react';
5
5
  import { DataGridTable } from '../DataGridTable/DataGridTable';
6
6
  import { ColumnChooser } from '../ColumnChooser/ColumnChooser';
7
7
  import { PaginationControls } from '../PaginationControls/PaginationControls';
8
- import { useOGrid, OGridLayout, } from '@alaarab/ogrid-react';
9
- const OGridInner = forwardRef(function OGridInner(props, ref) {
10
- const { dataGridProps, pagination, columnChooser, layout } = useOGrid(props, ref);
8
+ /**
9
+ * MUI theme bridge: reads the MUI palette and sets --ogrid-* CSS variables
10
+ * so the shared OGridLayout and DataGridTable styles adapt to the MUI theme.
11
+ */
12
+ const MuiThemeContainer = React.forwardRef(function MuiThemeContainer(props, ref) {
11
13
  const theme = useTheme();
12
- // Set --ogrid-* CSS variables so the shared OGridLayout adapts to MUI theme (both modes)
13
- const containerSx = React.useMemo(() => ({
14
+ const sx = React.useMemo(() => ({
14
15
  display: 'flex', flexDirection: 'column', gap: 1,
15
16
  '--ogrid-bg': theme.palette.background.default,
16
17
  '--ogrid-border': theme.palette.divider,
17
- '--ogrid-header-bg': theme.palette.action.hover,
18
+ '--ogrid-header-bg': theme.palette.mode === 'dark' ? theme.palette.grey[800] : theme.palette.grey[100],
18
19
  '--ogrid-fg': theme.palette.text.primary,
19
20
  '--ogrid-fg-secondary': theme.palette.text.secondary,
20
21
  '--ogrid-fg-muted': theme.palette.text.disabled,
21
22
  '--ogrid-hover-bg': theme.palette.action.hover,
22
23
  }), [theme]);
23
- return (_jsx(OGridLayout, { containerComponent: Box, containerProps: { sx: containerSx }, className: layout.className, sideBar: layout.sideBarProps, toolbar: layout.toolbar, toolbarBelow: layout.toolbarBelow, toolbarEnd: columnChooser.placement === 'toolbar' ? (_jsx(ColumnChooser, { columns: columnChooser.columns, visibleColumns: columnChooser.visibleColumns, onVisibilityChange: columnChooser.onVisibilityChange })) : undefined, pagination: _jsx(PaginationControls, { currentPage: pagination.page, pageSize: pagination.pageSize, totalCount: pagination.displayTotalCount, onPageChange: pagination.setPage, onPageSizeChange: (size) => {
24
- pagination.setPageSize(size);
25
- }, pageSizeOptions: pagination.pageSizeOptions, entityLabelPlural: pagination.entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
24
+ return _jsx(Box, { ref: ref, sx: sx, ...props });
25
+ });
26
+ export const OGrid = createOGrid({
27
+ DataGridTable: DataGridTable,
28
+ ColumnChooser: ColumnChooser,
29
+ PaginationControls,
30
+ containerComponent: MuiThemeContainer,
26
31
  });
27
- OGridInner.displayName = 'OGrid';
28
- export const OGrid = React.memo(OGridInner);
@@ -1,5 +1,3 @@
1
1
  import * as React from 'react';
2
- import { type IOGridProps, type IOGridApi } from '@alaarab/ogrid-react';
3
2
  export type { IOGridProps } from '@alaarab/ogrid-react';
4
- declare const OGridInner: React.ForwardRefExoticComponent<IOGridProps<unknown> & React.RefAttributes<IOGridApi<unknown>>>;
5
- export declare const OGrid: typeof OGridInner;
3
+ export declare const OGrid: React.ForwardRefExoticComponent<import("@alaarab/ogrid-react").IOGridProps<unknown> & React.RefAttributes<import("@alaarab/ogrid-react").IOGridApi<unknown>>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-react-material",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "OGrid React Material 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.1.1"
42
+ "@alaarab/ogrid-react": "2.1.3"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@emotion/react": "^11.0.0",