@alaarab/ogrid-react-material 2.0.23 → 2.1.0

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.
@@ -4,10 +4,10 @@ import { Button, Popover, Checkbox, Box, Typography, FormControlLabel, } from '@
4
4
  import { ViewColumn as ViewColumnIcon, ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon, } from '@mui/icons-material';
5
5
  import { useColumnChooserState } from '@alaarab/ogrid-react';
6
6
  export const ColumnChooser = (props) => {
7
- const { columns, visibleColumns, onVisibilityChange, className } = props;
7
+ const { columns, visibleColumns, onVisibilityChange, onSetVisibleColumns, className } = props;
8
8
  const [anchorEl, setAnchorEl] = useState(null);
9
9
  const buttonRef = useRef(null);
10
- const { open: isOpen, setOpen, handleClose, handleCheckboxChange: setColumnVisible, handleSelectAll, handleClearAll, visibleCount, totalCount, } = useColumnChooserState({ columns, visibleColumns, onVisibilityChange });
10
+ const { open: isOpen, setOpen, handleClose, handleCheckboxChange: setColumnVisible, handleSelectAll, handleClearAll, visibleCount, totalCount, } = useColumnChooserState({ columns, visibleColumns, onVisibilityChange, onSetVisibleColumns });
11
11
  const handleToggle = (e) => {
12
12
  if (isOpen) {
13
13
  handleClose();
@@ -40,7 +40,7 @@ export const ColumnChooser = (props) => {
40
40
  borderBottom: 1,
41
41
  borderColor: 'divider',
42
42
  bgcolor: 'action.hover',
43
- }, children: _jsxs(Typography, { variant: "subtitle2", fontWeight: 600, children: ["Select Columns (", visibleCount, " of ", totalCount, ")"] }) }), _jsx(Box, { sx: { maxHeight: 320, overflowY: 'auto', py: 0.5 }, children: columns.map((column) => (_jsx(Box, { sx: { px: 1.5, minHeight: 32, display: 'flex', alignItems: 'center' }, children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { size: "small", checked: visibleColumns.has(column.columnId), onChange: handleCheckboxChange(column.columnId) }), label: _jsx(Typography, { variant: "body2", children: column.name }), sx: { m: 0 } }) }, column.columnId))) }), _jsxs(Box, { sx: {
43
+ }, children: _jsxs(Typography, { variant: "subtitle2", fontWeight: 600, children: ["Select Columns (", visibleCount, " of ", totalCount, ")"] }) }), _jsx(Box, { sx: { maxHeight: 320, overflowY: 'auto', py: 0.5 }, children: columns.map((column) => (_jsx(Box, { sx: { px: 1.5, minHeight: 32, display: 'flex', alignItems: 'center' }, children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { size: "small", checked: visibleColumns.has(column.columnId), onChange: handleCheckboxChange(column.columnId), disabled: column.required === true }), label: _jsx(Typography, { variant: "body2", children: column.name }), sx: { m: 0 } }) }, column.columnId))) }), _jsxs(Box, { sx: {
44
44
  display: 'flex',
45
45
  justifyContent: 'flex-end',
46
46
  gap: 1,
@@ -11,7 +11,7 @@ import { GridContextMenu } from './GridContextMenu';
11
11
  import { EmptyState } from './EmptyState';
12
12
  import { LoadingOverlay } from './LoadingOverlay';
13
13
  import { DropIndicator } from './DropIndicator';
14
- import { useDataGridTableOrchestration, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, PREVENT_DEFAULT, NOOP, STOP_PROPAGATION, } from '@alaarab/ogrid-react';
14
+ import { useDataGridTableOrchestration, useColumnMeta, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, PREVENT_DEFAULT, NOOP, STOP_PROPAGATION, } from '@alaarab/ogrid-react';
15
15
  // ── Module-scope stable styles (avoid per-render Emotion resolutions) ──
16
16
  const gridRootSx = { position: 'relative', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' };
17
17
  // Editing cell wrapper (plain div, not MUI)
@@ -229,11 +229,22 @@ function DataGridTableInner(props) {
229
229
  // Density-aware cell padding
230
230
  const densityPadding = useMemo(() => getDensityPadding(density), [density]);
231
231
  const headerCellSx = useMemo(() => ({ px: densityPadding.px, py: densityPadding.py }), [densityPadding]);
232
- // Pre-compute per-column layout (tdSx, widths) so GridRow doesn't recalculate per-cell
232
+ // Shared width/minWidth computation (deduped with Radix/Fluent via useColumnMeta)
233
+ const columnMeta = useColumnMeta({
234
+ visibleCols,
235
+ getColumnWidth,
236
+ columnSizingOverrides,
237
+ measuredColumnWidths,
238
+ pinnedColumns: pinning.pinnedColumns,
239
+ leftOffsets: pinning.leftOffsets,
240
+ rightOffsets: pinning.rightOffsets,
241
+ pinnedColLeftClass: '',
242
+ pinnedColRightClass: '',
243
+ });
244
+ // Pre-compute per-column layout (tdSx + widths from columnMeta) so GridRow doesn't recalculate per-cell
233
245
  const columnLayouts = useMemo(() => visibleCols.map((col) => {
234
246
  const isPinnedLeft = pinning.pinnedColumns[col.columnId] === 'left';
235
247
  const isPinnedRight = pinning.pinnedColumns[col.columnId] === 'right';
236
- const columnWidth = getColumnWidth(col);
237
248
  const baseTdSx = isPinnedLeft ? CELL_TD_PINNED_LEFT_SX : isPinnedRight ? CELL_TD_PINNED_RIGHT_SX : CELL_TD_BASE_SX;
238
249
  // Override sticky offset for pinned columns (supports multiple pinned columns)
239
250
  const tdSx = isPinnedLeft && pinning.leftOffsets[col.columnId] != null
@@ -241,14 +252,15 @@ function DataGridTableInner(props) {
241
252
  : isPinnedRight && pinning.rightOffsets[col.columnId] != null
242
253
  ? { ...baseTdSx, right: pinning.rightOffsets[col.columnId] }
243
254
  : baseTdSx;
244
- const hasResizeOverride = !!columnSizingOverrides[col.columnId];
245
- // Use previously-measured DOM width as a minWidth floor to prevent columns
246
- // from shrinking when new data loads (e.g. server-side pagination).
247
- const measuredW = measuredColumnWidths[col.columnId];
248
- const baseMinWidth = col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
249
- const effectiveMinWidth = hasResizeOverride ? columnWidth : Math.max(baseMinWidth, measuredW ?? 0);
250
- return { col, tdSx, minWidth: effectiveMinWidth, width: columnWidth, maxWidth: columnWidth };
251
- }), [visibleCols, getColumnWidth, columnSizingOverrides, measuredColumnWidths, pinning.pinnedColumns, pinning.leftOffsets, pinning.rightOffsets]);
255
+ const cellStyle = columnMeta.cellStyles[col.columnId];
256
+ return {
257
+ col,
258
+ tdSx,
259
+ minWidth: cellStyle?.minWidth ?? 0,
260
+ width: cellStyle?.width ?? getColumnWidth(col),
261
+ maxWidth: cellStyle?.maxWidth ?? getColumnWidth(col),
262
+ };
263
+ }), [visibleCols, columnMeta, pinning.pinnedColumns, pinning.leftOffsets, pinning.rightOffsets, getColumnWidth]);
252
264
  // Wrapper sx (depends on dynamic values — memoize to avoid recreation)
253
265
  const wrapperSx = useMemo(() => ({
254
266
  position: 'relative',
@@ -284,14 +296,10 @@ function DataGridTableInner(props) {
284
296
  // Select pre-computed sx variant (module-scope = no per-cell allocation)
285
297
  const cellSx = getCellSx(col.type, descriptor.canEditAny, descriptor.isActive && !descriptor.isInRange, descriptor.isInRange, descriptor.isInCutRange);
286
298
  const interactionProps = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
287
- cellContent = (_jsxs(Box, { component: "div", ...interactionProps,
288
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
289
- 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 }))] }));
299
+ 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 }))] }));
290
300
  }
291
301
  return (_jsx(CellErrorBoundary, { onError: onCellError, children: cellContent }, `${rowId}-${col.columnId}`));
292
- },
293
- // eslint-disable-next-line react-hooks/exhaustive-deps -- *Ref vars are stable refs from useLatestRef
294
- [editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit, getRowId, onCellError]);
302
+ }, [editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit, getRowId, onCellError, cellDescriptorInputRef, densityPadding, pendingEditorValueRef, popoverAnchorElRef]);
295
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, { ...{
296
304
  component: "th",
297
305
  scope: "col",
@@ -328,10 +336,11 @@ function DataGridTableInner(props) {
328
336
  }, children: cell.label }, cellIdx));
329
337
  }
330
338
  // Leaf cell
339
+ if (!cell.columnDef)
340
+ return null;
331
341
  const col = cell.columnDef;
332
342
  const isPinnedLeft = pinning.pinnedColumns[col.columnId] === 'left';
333
343
  const isPinnedRight = pinning.pinnedColumns[col.columnId] === 'right';
334
- const columnWidth = getColumnWidth(col);
335
344
  const baseHeaderSx = isPinnedLeft ? HEADER_PINNED_LEFT_SX : isPinnedRight ? HEADER_PINNED_RIGHT_SX : HEADER_BASE_SX;
336
345
  // Override sticky offset for pinned columns (supports multiple pinned columns)
337
346
  const headerSx = isPinnedLeft && pinning.leftOffsets[col.columnId] != null
@@ -339,6 +348,8 @@ function DataGridTableInner(props) {
339
348
  : isPinnedRight && pinning.rightOffsets[col.columnId] != null
340
349
  ? { ...baseHeaderSx, right: pinning.rightOffsets[col.columnId] }
341
350
  : baseHeaderSx;
351
+ // Width/minWidth from shared useColumnMeta (avoids duplicate calculation)
352
+ const hdrStyle = columnMeta.hdrStyles[col.columnId];
342
353
  // Determine aria-sort value for sorted columns
343
354
  const isSorted = props.sortBy === col.columnId;
344
355
  const ariaSort = isSorted
@@ -353,9 +364,9 @@ function DataGridTableInner(props) {
353
364
  sx: {
354
365
  ...headerSx,
355
366
  ...headerCellSx,
356
- minWidth: Math.max(col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH, measuredColumnWidths[col.columnId] ?? 0),
357
- width: columnWidth,
358
- maxWidth: columnWidth,
367
+ minWidth: hdrStyle?.minWidth,
368
+ width: hdrStyle?.width,
369
+ maxWidth: hdrStyle?.maxWidth,
359
370
  ...(columnReorder ? { cursor: isReorderDragging ? 'grabbing' : 'grab' } : {}),
360
371
  '&:focus-visible': {
361
372
  outline: '2px solid',
@@ -22,7 +22,6 @@ const OGridInner = forwardRef(function OGridInner(props, ref) {
22
22
  }), [theme]);
23
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
24
  pagination.setPageSize(size);
25
- pagination.setPage(1);
26
25
  }, pageSizeOptions: pagination.pageSizeOptions, entityLabelPlural: pagination.entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
27
26
  });
28
27
  OGridInner.displayName = 'OGrid';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-react-material",
3
- "version": "2.0.23",
3
+ "version": "2.1.0",
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.0.23"
42
+ "@alaarab/ogrid-react": "2.1.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@emotion/react": "^11.0.0",
@@ -68,5 +68,12 @@
68
68
  },
69
69
  "publishConfig": {
70
70
  "access": "public"
71
- }
71
+ },
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "https://github.com/alaarab/ogrid.git",
75
+ "directory": "packages/react-material"
76
+ },
77
+ "homepage": "https://ogrid.dev",
78
+ "bugs": "https://github.com/alaarab/ogrid/issues"
72
79
  }