@alaarab/ogrid-react-material 2.2.0 → 2.4.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.
Files changed (2) hide show
  1. package/dist/esm/index.js +304 -426
  2. package/package.json +2 -2
package/dist/esm/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as React4 from 'react';
2
2
  import { useMemo, useCallback, useState, useRef, useEffect } from 'react';
3
- import { Box, Tooltip, Typography, IconButton, Popover, Button, Select, MenuItem, useTheme, Checkbox, Table, TableHead, TableRow, TableCell, FormControlLabel, Avatar, TextField, InputAdornment, CircularProgress, Menu, Divider } from '@mui/material';
4
- import { useColumnHeaderFilterState, getColumnHeaderFilterStateParams, renderFilterContent, areGridRowPropsEqual, usePaginationControls, createOGrid, DateFilterContent, CHECKBOX_COLUMN_WIDTH, STOP_PROPAGATION, ROW_NUMBER_COLUMN_WIDTH, useDataGridTableOrchestration, useColumnMeta, getCellRenderDescriptor, buildInlineEditorProps, buildPopoverEditorProps, resolveCellDisplayContent, resolveCellStyle, getCellInteractionProps, CellErrorBoundary, PREVENT_DEFAULT, getHeaderFilterConfig, MarchingAntsOverlay, NOOP, useColumnChooserState, useListVirtualizer, BaseInlineCellEditor, partitionColumnsForVirtualization, getContextMenuHandlers, GRID_CONTEXT_MENU_ITEMS, formatShortcut, getColumnHeaderMenuItems, getStatusBarParts } from '@alaarab/ogrid-react';
3
+ import { Box, Tooltip, Typography, IconButton, Popover, useTheme, TextField, InputAdornment, Button, CircularProgress, FormControlLabel, Checkbox, Avatar, Table, TableHead, TableRow, TableCell, Select, MenuItem, Menu, Divider } from '@mui/material';
4
+ import { createBaseFilterRenderers, useColumnHeaderFilterState, getColumnHeaderFilterStateParams, renderFilterContent, areGridRowPropsEqual, PaginationControlsBase, createOGrid, useListVirtualizer, CHECKBOX_COLUMN_WIDTH, STOP_PROPAGATION, ROW_NUMBER_COLUMN_WIDTH, useDataGridTableOrchestration, useColumnMeta, getCellRenderDescriptor, buildInlineEditorProps, buildPopoverEditorProps, resolveCellDisplayContent, resolveCellStyle, getCellInteractionProps, CellErrorBoundary, PREVENT_DEFAULT, indexToColumnLetter, getHeaderFilterConfig, MarchingAntsOverlay, FormulaRefOverlay, NOOP, getColumnHeaderMenuProps, useColumnChooserState, ColumnChooserContent, BaseInlineCellEditor, partitionColumnsForVirtualization, getContextMenuHandlers, GRID_CONTEXT_MENU_ITEMS, formatShortcut, getColumnHeaderMenuItems, getStatusBarParts } from '@alaarab/ogrid-react';
5
5
  export { BaseColumnHeaderMenu, BaseDropIndicator, BaseEmptyState, BaseInlineCellEditor, BaseLoadingOverlay, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, CURSOR_CELL_STYLE, CellErrorBoundary, DEFAULT_MIN_COLUMN_WIDTH, DateFilterContent, EmptyState, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, GRID_ROOT_STYLE, GridContextMenu, MAX_PAGE_BUTTONS, MarchingAntsOverlay, NOOP, OGridLayout, PAGE_SIZE_OPTIONS, POPOVER_ANCHOR_STYLE, PREVENT_DEFAULT, ROW_NUMBER_COLUMN_WIDTH, STOP_PROPAGATION, SideBar, StatusBar, UndoRedoStack, areGridRowPropsEqual, booleanParser, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps, buildPopoverEditorProps, clampSelectionToBounds, computeAggregations, computeAutoScrollSpeed, computeTabNavigation, createOGrid, currencyParser, dateParser, deriveFilterOptionsFromData, editorInputStyle, editorWrapperStyle, emailParser, escapeCsvValue, exportToCsv, findCtrlArrowTarget, flattenColumns, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, getCellInteractionProps, getCellRenderDescriptor, getCellValue, getColumnHeaderFilterStateParams, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getDateFilterContentProps, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getStatusBarParts, isInSelectionRange, isRowInRange, mergeFilter, normalizeSelectionRange, numberParser, parseTsvClipboard, parseValue, processClientSideData, rangesEqual, renderFilterContent, resolveCellDisplayContent, resolveCellStyle, richSelectDropdownStyle, richSelectNoMatchesStyle, richSelectOptionHighlightedStyle, richSelectOptionStyle, richSelectWrapperStyle, selectChevronStyle, selectDisplayStyle, selectEditorStyle, toUserLike, triggerCsvDownload, useActiveCell, useCellEditing, useCellSelection, useClipboard, useColumnChooserState, useColumnHeaderFilterState, useColumnMeta, useColumnReorder, useColumnResize, useContextMenu, useDataGridState, useDataGridTableOrchestration, useDateFilterState, useDebounce, useFillHandle, useFilterOptions, useInlineCellEditorState, useKeyboardNavigation, useLatestRef, useListVirtualizer, useMultiSelectFilterState, useOGrid, usePaginationControls, usePeopleFilterState, useRichSelectState, useRowSelection, useSelectState, useSideBarState, useTableLayout, useTextFilterState, useUndoRedo, useVirtualScroll } from '@alaarab/ogrid-react';
6
6
  import { createPortal } from 'react-dom';
7
- import { FilterList, FirstPage, ChevronLeft, ChevronRight, LastPage, ExpandLess, ExpandMore, ViewColumn, Clear, Search } from '@mui/icons-material';
7
+ import { FilterList, Search, Clear, ExpandLess, ExpandMore, ViewColumn, LastPage, ChevronRight, ChevronLeft, FirstPage } from '@mui/icons-material';
8
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
9
 
10
10
  // src/OGrid/OGrid.tsx
@@ -29,6 +29,7 @@ table:not([data-virtual-scroll]) .ogrid-mat-tbody tr { content-visibility: auto;
29
29
 
30
30
  .ogrid-mat-cell--active { outline: 2px solid var(--ogrid-selection, #217346); outline-offset: -1px; z-index: var(--ogrid-z-active-cell, 2); position: relative; overflow: visible; background-color: var(--ogrid-hover-bg); }
31
31
  .ogrid-mat-cell--active:focus-visible { outline: 2px solid var(--ogrid-selection, #217346); outline-offset: -1px; }
32
+ .ogrid-mat-cell--active-in-range { outline: none; background-color: var(--ogrid-bg, #fff); }
32
33
  .ogrid-mat-cell--range { background-color: var(--ogrid-bg-range, rgba(33,115,70,0.12)); }
33
34
  .ogrid-mat-cell--range:focus-visible { outline: none; }
34
35
  .ogrid-mat-cell--cut { background-color: var(--ogrid-hover-bg); opacity: 0.7; }
@@ -227,56 +228,9 @@ var PeopleFilterPopover = ({
227
228
  selectedUser && /* @__PURE__ */ jsx(Box, { sx: { p: 1.5, pt: 1, borderTop: 1, borderColor: "divider" }, children: /* @__PURE__ */ jsx(Button, { size: "small", fullWidth: true, onClick: onClearUser, children: "Clear Filter" }) })
228
229
  ] });
229
230
  PeopleFilterPopover.displayName = "PeopleFilterPopover";
230
- var materialRenderers = {
231
- renderMultiSelect: (p) => /* @__PURE__ */ jsx(
232
- MultiSelectFilterPopover,
233
- {
234
- searchText: p.searchText,
235
- onSearchChange: p.onSearchChange,
236
- options: p.options,
237
- filteredOptions: p.filteredOptions,
238
- selected: p.selected,
239
- onOptionToggle: p.onOptionToggle,
240
- onSelectAll: p.onSelectAll,
241
- onClearSelection: p.onClearSelection,
242
- onApply: p.onApply,
243
- isLoading: p.isLoading
244
- }
245
- ),
246
- renderText: (p) => /* @__PURE__ */ jsx(
247
- TextFilterPopover,
248
- {
249
- value: p.value,
250
- onValueChange: p.onValueChange,
251
- onApply: p.onApply,
252
- onClear: p.onClear
253
- }
254
- ),
255
- renderPeople: (p) => /* @__PURE__ */ jsx(
256
- PeopleFilterPopover,
257
- {
258
- selectedUser: p.selectedUser,
259
- searchText: p.searchText,
260
- onSearchChange: p.onSearchChange,
261
- suggestions: p.suggestions,
262
- isLoading: p.isLoading,
263
- onUserSelect: p.onUserSelect,
264
- onClearUser: p.onClearUser,
265
- inputRef: p.inputRef
266
- }
267
- ),
268
- renderDate: (p) => /* @__PURE__ */ jsx(
269
- DateFilterContent,
270
- {
271
- tempDateFrom: p.tempDateFrom,
272
- setTempDateFrom: p.setTempDateFrom,
273
- tempDateTo: p.tempDateTo,
274
- setTempDateTo: p.setTempDateTo,
275
- onApply: p.onApply,
276
- onClear: p.onClear
277
- }
278
- )
279
- };
231
+ var materialRenderers = createBaseFilterRenderers(
232
+ { MultiSelectFilterPopover, TextFilterPopover, PeopleFilterPopover }
233
+ );
280
234
  var ColumnHeaderFilter = React4.memo((props) => {
281
235
  const {
282
236
  columnName,
@@ -624,6 +578,19 @@ var STICKY_HEADER_SX = {
624
578
  "& th": { bgcolor: HEADER_BG }
625
579
  };
626
580
  var HEADER_ROW_SX = { bgcolor: HEADER_BG };
581
+ var COLUMN_LETTER_CELL_SX = {
582
+ textAlign: "center",
583
+ fontSize: "11px",
584
+ fontWeight: 500,
585
+ color: "text.secondary",
586
+ py: "2px",
587
+ px: "4px",
588
+ bgcolor: HEADER_BG,
589
+ borderBottom: 1,
590
+ borderColor: "divider",
591
+ userSelect: "none",
592
+ fontVariantNumeric: "tabular-nums"
593
+ };
627
594
  var GROUP_HEADER_CELL_SX = { textAlign: "center", fontWeight: 600, borderBottom: 2, borderColor: "divider", py: 0.75 };
628
595
  function getDensityPadding(density) {
629
596
  switch (density) {
@@ -947,6 +914,7 @@ function DataGridTableInner(props) {
947
914
  headerRows,
948
915
  allowOverflowX,
949
916
  fitToContent,
917
+ showColumnLetters,
950
918
  editCallbacks,
951
919
  interactionHandlers,
952
920
  cellDescriptorInputRef,
@@ -1070,13 +1038,14 @@ function DataGridTableInner(props) {
1070
1038
  ] });
1071
1039
  } else {
1072
1040
  const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
1073
- const cellStyle = resolveCellStyle(col, item);
1041
+ const cellStyle = resolveCellStyle(col, item, descriptor.displayValue);
1074
1042
  const styledContent = cellStyle ? /* @__PURE__ */ jsx("span", { style: cellStyle, children: content }) : content;
1075
1043
  let cls = "ogrid-mat-cell";
1076
1044
  if (col.type === "numeric") cls += " ogrid-mat-cell--numeric";
1077
1045
  else if (col.type === "boolean") cls += " ogrid-mat-cell--boolean";
1078
1046
  if (descriptor.canEditAny) cls += " ogrid-mat-cell--editable";
1079
1047
  if (descriptor.isActive) cls += " ogrid-mat-cell--active";
1048
+ if (descriptor.isActive && descriptor.isInRange) cls += " ogrid-mat-cell--active-in-range";
1080
1049
  if (descriptor.isInRange && !descriptor.isActive) cls += " ogrid-mat-cell--range";
1081
1050
  if (descriptor.isInCutRange) cls += " ogrid-mat-cell--cut";
1082
1051
  const interactionProps = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
@@ -1126,143 +1095,168 @@ function DataGridTableInner(props) {
1126
1095
  sx: { minWidth: minTableWidth, borderCollapse: "separate", borderSpacing: 0 },
1127
1096
  "data-virtual-scroll": virtualScrollEnabled ? "" : void 0,
1128
1097
  children: [
1129
- /* @__PURE__ */ jsx(TableHead, { sx: STICKY_HEADER_SX, children: headerRows.map((row, rowIdx) => /* @__PURE__ */ jsxs(TableRow, { sx: HEADER_ROW_SX, children: [
1130
- rowIdx === headerRows.length - 1 && hasCheckboxCol && /* @__PURE__ */ jsx(
1131
- TableCell,
1132
- {
1133
- ...{ padding: "checkbox", rowSpan: headerRows.length > 1 ? 1 : void 0, sx: CHECKBOX_CELL_SX },
1134
- children: /* @__PURE__ */ jsx(
1135
- Checkbox,
1136
- {
1137
- checked: allSelected,
1138
- indeterminate: someSelected,
1139
- onChange: (_, c) => handleSelectAll(!!c),
1140
- size: "small",
1141
- "aria-label": "Select all rows"
1142
- }
1143
- )
1144
- }
1145
- ),
1146
- rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol && /* @__PURE__ */ jsx(TableCell, { ...{ rowSpan: headerRows.length - 1, sx: CHECKBOX_PLACEHOLDER_SX } }),
1147
- rowIdx === headerRows.length - 1 && hasRowNumbersCol && /* @__PURE__ */ jsx(
1148
- TableCell,
1149
- {
1150
- ...{
1151
- component: "th",
1152
- scope: "col",
1153
- rowSpan: headerRows.length > 1 ? 1 : void 0,
1154
- sx: {
1155
- width: ROW_NUMBER_COLUMN_WIDTH,
1156
- minWidth: ROW_NUMBER_COLUMN_WIDTH,
1157
- maxWidth: ROW_NUMBER_COLUMN_WIDTH,
1158
- textAlign: "center",
1159
- fontWeight: 600,
1160
- backgroundColor: HEADER_BG,
1161
- position: "sticky",
1162
- left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
1163
- zIndex: 4,
1164
- ...headerCellSx
1165
- }
1166
- },
1167
- children: "#"
1168
- }
1169
- ),
1170
- rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol && /* @__PURE__ */ jsx(
1171
- TableCell,
1172
- {
1173
- ...{
1174
- rowSpan: headerRows.length - 1,
1175
- sx: {
1176
- width: ROW_NUMBER_COLUMN_WIDTH,
1177
- minWidth: ROW_NUMBER_COLUMN_WIDTH,
1178
- position: "sticky",
1179
- left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
1180
- zIndex: 4,
1181
- backgroundColor: "background.paper"
1182
- }
1183
- }
1184
- }
1185
- ),
1186
- row.map((cell, cellIdx) => {
1187
- if (cell.isGroup) {
1098
+ /* @__PURE__ */ jsxs(TableHead, { sx: STICKY_HEADER_SX, children: [
1099
+ showColumnLetters && /* @__PURE__ */ jsxs(TableRow, { sx: HEADER_ROW_SX, children: [
1100
+ hasCheckboxCol && /* @__PURE__ */ jsx(TableCell, { sx: COLUMN_LETTER_CELL_SX }),
1101
+ hasRowNumbersCol && /* @__PURE__ */ jsx(TableCell, { sx: COLUMN_LETTER_CELL_SX }),
1102
+ visibleCols.map((col, colIdx) => {
1103
+ const hdrStyle = columnMeta.hdrStyles[col.columnId];
1188
1104
  return /* @__PURE__ */ jsx(
1189
1105
  TableCell,
1190
1106
  {
1191
1107
  ...{
1192
- colSpan: cell.colSpan,
1193
1108
  component: "th",
1194
- scope: "colgroup",
1195
- sx: GROUP_HEADER_CELL_SX
1109
+ sx: {
1110
+ ...COLUMN_LETTER_CELL_SX,
1111
+ minWidth: hdrStyle?.minWidth,
1112
+ width: hdrStyle?.width,
1113
+ maxWidth: hdrStyle?.maxWidth
1114
+ }
1196
1115
  },
1197
- children: cell.label
1116
+ children: indexToColumnLetter(colIdx)
1198
1117
  },
1199
- cellIdx
1118
+ col.columnId
1200
1119
  );
1201
- }
1202
- if (!cell.columnDef) return null;
1203
- const col = cell.columnDef;
1204
- const isPinnedLeft = pinning.pinnedColumns[col.columnId] === "left";
1205
- const isPinnedRight = pinning.pinnedColumns[col.columnId] === "right";
1206
- const baseHeaderSx = o.stickyHeader ? isPinnedLeft ? HEADER_PINNED_LEFT_SX : isPinnedRight ? HEADER_PINNED_RIGHT_SX : HEADER_BASE_SX : isPinnedLeft ? HEADER_PINNED_LEFT_NO_STICKY_SX : isPinnedRight ? HEADER_PINNED_RIGHT_NO_STICKY_SX : HEADER_BASE_NO_STICKY_SX;
1207
- const headerSx = isPinnedLeft && pinning.leftOffsets[col.columnId] != null ? { ...baseHeaderSx, left: pinning.leftOffsets[col.columnId] } : isPinnedRight && pinning.rightOffsets[col.columnId] != null ? { ...baseHeaderSx, right: pinning.rightOffsets[col.columnId] } : baseHeaderSx;
1208
- const hdrStyle = columnMeta.hdrStyles[col.columnId];
1209
- const isSorted = props.sortBy === col.columnId;
1210
- const ariaSort = isSorted ? props.sortDirection === "asc" ? "ascending" : "descending" : void 0;
1211
- return /* @__PURE__ */ jsxs(
1120
+ })
1121
+ ] }),
1122
+ headerRows.map((row, rowIdx) => /* @__PURE__ */ jsxs(TableRow, { sx: HEADER_ROW_SX, children: [
1123
+ rowIdx === headerRows.length - 1 && hasCheckboxCol && /* @__PURE__ */ jsx(
1124
+ TableCell,
1125
+ {
1126
+ ...{ padding: "checkbox", rowSpan: headerRows.length > 1 ? 1 : void 0, sx: CHECKBOX_CELL_SX },
1127
+ children: /* @__PURE__ */ jsx(
1128
+ Checkbox,
1129
+ {
1130
+ checked: allSelected,
1131
+ indeterminate: someSelected,
1132
+ onChange: (_, c) => handleSelectAll(!!c),
1133
+ size: "small",
1134
+ "aria-label": "Select all rows"
1135
+ }
1136
+ )
1137
+ }
1138
+ ),
1139
+ rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol && /* @__PURE__ */ jsx(TableCell, { ...{ rowSpan: headerRows.length - 1, sx: CHECKBOX_PLACEHOLDER_SX } }),
1140
+ rowIdx === headerRows.length - 1 && hasRowNumbersCol && /* @__PURE__ */ jsx(
1212
1141
  TableCell,
1213
1142
  {
1214
1143
  ...{
1215
1144
  component: "th",
1216
1145
  scope: "col",
1217
- "data-column-id": col.columnId,
1218
- rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : void 0,
1219
- "aria-sort": ariaSort,
1146
+ rowSpan: headerRows.length > 1 ? 1 : void 0,
1220
1147
  sx: {
1221
- ...headerSx,
1222
- ...headerCellSx,
1223
- minWidth: hdrStyle?.minWidth,
1224
- width: hdrStyle?.width,
1225
- maxWidth: hdrStyle?.maxWidth,
1226
- ...columnReorder ? { cursor: isReorderDragging ? "grabbing" : "grab" } : {},
1227
- "&:focus-visible": {
1228
- outline: "2px solid",
1229
- outlineColor: "primary.main",
1230
- outlineOffset: "-2px",
1231
- zIndex: 11
1232
- }
1233
- },
1234
- onMouseDown: columnReorder ? (e) => handleHeaderMouseDown(col.columnId, e) : void 0
1148
+ width: ROW_NUMBER_COLUMN_WIDTH,
1149
+ minWidth: ROW_NUMBER_COLUMN_WIDTH,
1150
+ maxWidth: ROW_NUMBER_COLUMN_WIDTH,
1151
+ textAlign: "center",
1152
+ fontWeight: 600,
1153
+ backgroundColor: HEADER_BG,
1154
+ position: "sticky",
1155
+ left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
1156
+ zIndex: 4,
1157
+ ...headerCellSx
1158
+ }
1235
1159
  },
1236
- children: [
1237
- /* @__PURE__ */ jsxs(Box, { sx: HEADER_CONTENT_FLEX_SX, children: [
1238
- /* @__PURE__ */ jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }),
1239
- /* @__PURE__ */ jsx(
1240
- Box,
1241
- {
1242
- component: "button",
1243
- onClick: (e) => {
1244
- e.stopPropagation();
1245
- headerMenu.open(col.columnId, e.currentTarget);
1246
- },
1247
- "aria-label": "Column options",
1248
- title: "Column options",
1249
- sx: COLUMN_OPTIONS_BUTTON_SX,
1250
- children: "\u22EE"
1160
+ children: "#"
1161
+ }
1162
+ ),
1163
+ rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol && /* @__PURE__ */ jsx(
1164
+ TableCell,
1165
+ {
1166
+ ...{
1167
+ rowSpan: headerRows.length - 1,
1168
+ sx: {
1169
+ width: ROW_NUMBER_COLUMN_WIDTH,
1170
+ minWidth: ROW_NUMBER_COLUMN_WIDTH,
1171
+ position: "sticky",
1172
+ left: hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0,
1173
+ zIndex: 4,
1174
+ backgroundColor: "background.paper"
1175
+ }
1176
+ }
1177
+ }
1178
+ ),
1179
+ row.map((cell, cellIdx) => {
1180
+ if (cell.isGroup) {
1181
+ return /* @__PURE__ */ jsx(
1182
+ TableCell,
1183
+ {
1184
+ ...{
1185
+ colSpan: cell.colSpan,
1186
+ component: "th",
1187
+ scope: "colgroup",
1188
+ sx: GROUP_HEADER_CELL_SX
1189
+ },
1190
+ children: cell.label
1191
+ },
1192
+ cellIdx
1193
+ );
1194
+ }
1195
+ if (!cell.columnDef) return null;
1196
+ const col = cell.columnDef;
1197
+ const isPinnedLeft = pinning.pinnedColumns[col.columnId] === "left";
1198
+ const isPinnedRight = pinning.pinnedColumns[col.columnId] === "right";
1199
+ const baseHeaderSx = o.stickyHeader ? isPinnedLeft ? HEADER_PINNED_LEFT_SX : isPinnedRight ? HEADER_PINNED_RIGHT_SX : HEADER_BASE_SX : isPinnedLeft ? HEADER_PINNED_LEFT_NO_STICKY_SX : isPinnedRight ? HEADER_PINNED_RIGHT_NO_STICKY_SX : HEADER_BASE_NO_STICKY_SX;
1200
+ const headerSx = isPinnedLeft && pinning.leftOffsets[col.columnId] != null ? { ...baseHeaderSx, left: pinning.leftOffsets[col.columnId] } : isPinnedRight && pinning.rightOffsets[col.columnId] != null ? { ...baseHeaderSx, right: pinning.rightOffsets[col.columnId] } : baseHeaderSx;
1201
+ const hdrStyle = columnMeta.hdrStyles[col.columnId];
1202
+ const isSorted = props.sortBy === col.columnId;
1203
+ const ariaSort = isSorted ? props.sortDirection === "asc" ? "ascending" : "descending" : void 0;
1204
+ return /* @__PURE__ */ jsxs(
1205
+ TableCell,
1206
+ {
1207
+ ...{
1208
+ component: "th",
1209
+ scope: "col",
1210
+ "data-column-id": col.columnId,
1211
+ rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : void 0,
1212
+ "aria-sort": ariaSort,
1213
+ sx: {
1214
+ ...headerSx,
1215
+ ...headerCellSx,
1216
+ minWidth: hdrStyle?.minWidth,
1217
+ width: hdrStyle?.width,
1218
+ maxWidth: hdrStyle?.maxWidth,
1219
+ ...columnReorder ? { cursor: isReorderDragging ? "grabbing" : "grab" } : {},
1220
+ "&:focus-visible": {
1221
+ outline: "2px solid",
1222
+ outlineColor: "primary.main",
1223
+ outlineOffset: "-2px",
1224
+ zIndex: 11
1251
1225
  }
1252
- )
1253
- ] }),
1254
- /* @__PURE__ */ jsx(Box, { onMouseDown: (e) => {
1255
- setActiveCell(null);
1256
- interaction.setSelectionRange(null);
1257
- wrapperRef.current?.focus({ preventScroll: true });
1258
- handleResizeStart(e, col);
1259
- }, onDoubleClick: (e) => handleResizeDoubleClick(e, col), sx: RESIZE_HANDLE_SX })
1260
- ]
1261
- },
1262
- col.columnId
1263
- );
1264
- })
1265
- ] }, rowIdx)) }),
1226
+ },
1227
+ onMouseDown: columnReorder ? (e) => handleHeaderMouseDown(col.columnId, e) : void 0
1228
+ },
1229
+ children: [
1230
+ /* @__PURE__ */ jsxs(Box, { sx: HEADER_CONTENT_FLEX_SX, children: [
1231
+ /* @__PURE__ */ jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }),
1232
+ /* @__PURE__ */ jsx(
1233
+ Box,
1234
+ {
1235
+ component: "button",
1236
+ onClick: (e) => {
1237
+ e.stopPropagation();
1238
+ headerMenu.open(col.columnId, e.currentTarget);
1239
+ },
1240
+ "aria-label": "Column options",
1241
+ title: "Column options",
1242
+ sx: COLUMN_OPTIONS_BUTTON_SX,
1243
+ children: "\u22EE"
1244
+ }
1245
+ )
1246
+ ] }),
1247
+ /* @__PURE__ */ jsx(Box, { onMouseDown: (e) => {
1248
+ setActiveCell(null);
1249
+ interaction.setSelectionRange(null);
1250
+ wrapperRef.current?.focus({ preventScroll: true });
1251
+ handleResizeStart(e, col);
1252
+ }, onDoubleClick: (e) => handleResizeDoubleClick(e, col), sx: RESIZE_HANDLE_SX })
1253
+ ]
1254
+ },
1255
+ col.columnId
1256
+ );
1257
+ })
1258
+ ] }, rowIdx))
1259
+ ] }),
1266
1260
  !showEmptyInGrid && /* @__PURE__ */ jsx(
1267
1261
  MaterialTableBody,
1268
1262
  {
@@ -1310,6 +1304,14 @@ function DataGridTableInner(props) {
1310
1304
  isDragging
1311
1305
  }
1312
1306
  ),
1307
+ props.formulaReferences && props.formulaReferences.length > 0 && /* @__PURE__ */ jsx(
1308
+ FormulaRefOverlay,
1309
+ {
1310
+ containerRef: tableContainerRef,
1311
+ references: props.formulaReferences,
1312
+ colOffset
1313
+ }
1314
+ ),
1313
1315
  showEmptyInGrid && emptyState && /* @__PURE__ */ jsx(EmptyState, { emptyState })
1314
1316
  ] }) }) }),
1315
1317
  menuPosition && createPortal(
@@ -1332,28 +1334,7 @@ function DataGridTableInner(props) {
1332
1334
  ),
1333
1335
  document.body
1334
1336
  ),
1335
- /* @__PURE__ */ jsx(
1336
- ColumnHeaderMenu,
1337
- {
1338
- isOpen: headerMenu.isOpen,
1339
- anchorElement: headerMenu.anchorElement,
1340
- onClose: headerMenu.close,
1341
- onPinLeft: headerMenu.handlePinLeft,
1342
- onPinRight: headerMenu.handlePinRight,
1343
- onUnpin: headerMenu.handleUnpin,
1344
- onSortAsc: headerMenu.handleSortAsc,
1345
- onSortDesc: headerMenu.handleSortDesc,
1346
- onClearSort: headerMenu.handleClearSort,
1347
- onAutosizeThis: headerMenu.handleAutosizeThis,
1348
- onAutosizeAll: headerMenu.handleAutosizeAll,
1349
- canPinLeft: headerMenu.canPinLeft,
1350
- canPinRight: headerMenu.canPinRight,
1351
- canUnpin: headerMenu.canUnpin,
1352
- currentSort: headerMenu.currentSort,
1353
- isSortable: headerMenu.isSortable,
1354
- isResizable: headerMenu.isResizable
1355
- }
1356
- )
1337
+ /* @__PURE__ */ jsx(ColumnHeaderMenu, { ...getColumnHeaderMenuProps(headerMenu) })
1357
1338
  ]
1358
1339
  }
1359
1340
  ),
@@ -1372,6 +1353,53 @@ function DataGridTableInner(props) {
1372
1353
  ] });
1373
1354
  }
1374
1355
  var DataGridTable = React4.memo(DataGridTableInner);
1356
+ var CheckboxItem = ({ columnId: _columnId, columnName, checked, disabled, onChange }) => /* @__PURE__ */ jsx(
1357
+ FormControlLabel,
1358
+ {
1359
+ control: /* @__PURE__ */ jsx(
1360
+ Checkbox,
1361
+ {
1362
+ size: "small",
1363
+ checked,
1364
+ onChange: (ev) => {
1365
+ ev.stopPropagation();
1366
+ onChange(ev.target.checked);
1367
+ },
1368
+ disabled
1369
+ }
1370
+ ),
1371
+ label: /* @__PURE__ */ jsx(Typography, { variant: "body2", children: columnName }),
1372
+ sx: { m: 0 }
1373
+ }
1374
+ );
1375
+ var Header = ({ visibleCount, totalCount }) => /* @__PURE__ */ jsx(Box, { sx: { px: 1.5, py: 1, borderBottom: 1, borderColor: "divider", bgcolor: "action.hover" }, children: /* @__PURE__ */ jsxs(Typography, { variant: "subtitle2", fontWeight: 600, children: [
1376
+ "Select Columns (",
1377
+ visibleCount,
1378
+ " of ",
1379
+ totalCount,
1380
+ ")"
1381
+ ] }) });
1382
+ var OptionsListContainer = ({ children }) => /* @__PURE__ */ jsx(Box, { sx: { maxHeight: 320, overflowY: "auto", py: 0.5 }, children });
1383
+ var OptionItemContainer = ({ children }) => /* @__PURE__ */ jsx(Box, { sx: { px: 1.5, minHeight: 32, display: "flex", alignItems: "center" }, children });
1384
+ var Actions = ({ onClearAll, onSelectAll }) => /* @__PURE__ */ jsxs(
1385
+ Box,
1386
+ {
1387
+ sx: {
1388
+ display: "flex",
1389
+ justifyContent: "flex-end",
1390
+ gap: 1,
1391
+ px: 1.5,
1392
+ py: 1,
1393
+ borderTop: 1,
1394
+ borderColor: "divider",
1395
+ bgcolor: "action.hover"
1396
+ },
1397
+ children: [
1398
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: onClearAll, sx: { textTransform: "none" }, children: "Clear All" }),
1399
+ /* @__PURE__ */ jsx(Button, { size: "small", variant: "contained", onClick: onSelectAll, sx: { textTransform: "none" }, children: "Select All" })
1400
+ ]
1401
+ }
1402
+ );
1375
1403
  var ColumnChooser = (props) => {
1376
1404
  const { columns, visibleColumns, onVisibilityChange, onSetVisibleColumns, className } = props;
1377
1405
  const [anchorEl, setAnchorEl] = useState(null);
@@ -1399,10 +1427,7 @@ var ColumnChooser = (props) => {
1399
1427
  handleClose();
1400
1428
  setAnchorEl(null);
1401
1429
  };
1402
- const handleCheckboxChange = (columnKey) => (ev) => {
1403
- ev.stopPropagation();
1404
- setColumnVisible(columnKey)(ev.target.checked);
1405
- };
1430
+ const handleCheckboxChange = (columnKey) => (checked) => setColumnVisible(columnKey)(checked);
1406
1431
  return /* @__PURE__ */ jsxs(Box, { className, sx: { display: "inline-flex" }, children: [
1407
1432
  /* @__PURE__ */ jsxs(
1408
1433
  Button,
@@ -1430,7 +1455,7 @@ var ColumnChooser = (props) => {
1430
1455
  ]
1431
1456
  }
1432
1457
  ),
1433
- /* @__PURE__ */ jsxs(
1458
+ /* @__PURE__ */ jsx(
1434
1459
  Popover,
1435
1460
  {
1436
1461
  open: isOpen,
@@ -1438,236 +1463,89 @@ var ColumnChooser = (props) => {
1438
1463
  onClose: handlePopoverClose,
1439
1464
  anchorOrigin: { vertical: "bottom", horizontal: "right" },
1440
1465
  transformOrigin: { vertical: "top", horizontal: "right" },
1441
- slotProps: {
1442
- paper: {
1443
- sx: { mt: 0.5, minWidth: 220 }
1466
+ slotProps: { paper: { sx: { mt: 0.5, minWidth: 220 } } },
1467
+ children: /* @__PURE__ */ jsx(
1468
+ ColumnChooserContent,
1469
+ {
1470
+ columns,
1471
+ visibleColumns,
1472
+ visibleCount,
1473
+ totalCount,
1474
+ handleSelectAll,
1475
+ handleClearAll,
1476
+ handleCheckboxChange,
1477
+ CheckboxItem,
1478
+ Header,
1479
+ OptionsListContainer,
1480
+ OptionItemContainer,
1481
+ Actions
1444
1482
  }
1445
- },
1446
- children: [
1447
- /* @__PURE__ */ jsx(
1448
- Box,
1449
- {
1450
- sx: {
1451
- px: 1.5,
1452
- py: 1,
1453
- borderBottom: 1,
1454
- borderColor: "divider",
1455
- bgcolor: "action.hover"
1456
- },
1457
- children: /* @__PURE__ */ jsxs(Typography, { variant: "subtitle2", fontWeight: 600, children: [
1458
- "Select Columns (",
1459
- visibleCount,
1460
- " of ",
1461
- totalCount,
1462
- ")"
1463
- ] })
1464
- }
1465
- ),
1466
- /* @__PURE__ */ jsx(Box, { sx: { maxHeight: 320, overflowY: "auto", py: 0.5 }, children: columns.map((column) => /* @__PURE__ */ jsx(Box, { sx: { px: 1.5, minHeight: 32, display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx(
1467
- FormControlLabel,
1468
- {
1469
- control: /* @__PURE__ */ jsx(
1470
- Checkbox,
1471
- {
1472
- size: "small",
1473
- checked: visibleColumns.has(column.columnId),
1474
- onChange: handleCheckboxChange(column.columnId),
1475
- disabled: column.required === true
1476
- }
1477
- ),
1478
- label: /* @__PURE__ */ jsx(Typography, { variant: "body2", children: column.name }),
1479
- sx: { m: 0 }
1480
- }
1481
- ) }, column.columnId)) }),
1482
- /* @__PURE__ */ jsxs(
1483
- Box,
1484
- {
1485
- sx: {
1486
- display: "flex",
1487
- justifyContent: "flex-end",
1488
- gap: 1,
1489
- px: 1.5,
1490
- py: 1,
1491
- borderTop: 1,
1492
- borderColor: "divider",
1493
- bgcolor: "action.hover"
1494
- },
1495
- children: [
1496
- /* @__PURE__ */ jsx(Button, { size: "small", onClick: handleClearAll, sx: { textTransform: "none" }, children: "Clear All" }),
1497
- /* @__PURE__ */ jsx(
1498
- Button,
1499
- {
1500
- size: "small",
1501
- variant: "contained",
1502
- onClick: handleSelectAll,
1503
- sx: { textTransform: "none" },
1504
- children: "Select All"
1505
- }
1506
- )
1507
- ]
1508
- }
1509
- )
1510
- ]
1483
+ )
1511
1484
  }
1512
1485
  )
1513
1486
  ] });
1514
1487
  };
1515
- var PaginationControls = React4.memo((props) => {
1516
- const {
1517
- currentPage,
1518
- pageSize,
1519
- totalCount,
1520
- onPageChange,
1521
- onPageSizeChange,
1522
- pageSizeOptions,
1523
- entityLabelPlural,
1524
- className
1525
- } = props;
1526
- const { labelPlural, vm, handlePageSizeChange } = usePaginationControls({
1527
- currentPage,
1528
- pageSize,
1529
- totalCount,
1530
- onPageChange,
1531
- onPageSizeChange,
1532
- pageSizeOptions,
1533
- entityLabelPlural
1534
- });
1535
- const handlePageSizeChangeEvent = (event) => {
1536
- handlePageSizeChange(Number(event.target.value));
1537
- };
1538
- if (!vm) {
1539
- return null;
1488
+ var MUI_NAV_ICONS = {
1489
+ first: /* @__PURE__ */ jsx(FirstPage, { fontSize: "small" }),
1490
+ prev: /* @__PURE__ */ jsx(ChevronLeft, { fontSize: "small" }),
1491
+ next: /* @__PURE__ */ jsx(ChevronRight, { fontSize: "small" }),
1492
+ last: /* @__PURE__ */ jsx(LastPage, { fontSize: "small" })
1493
+ };
1494
+ var NavButton = ({ variant, onClick, disabled, "aria-label": ariaLabel }) => /* @__PURE__ */ jsx(IconButton, { size: "small", onClick, disabled, "aria-label": ariaLabel, children: MUI_NAV_ICONS[variant] });
1495
+ var PageButton = ({ onClick, active, "aria-label": ariaLabel, "aria-current": ariaCurrent, children }) => /* @__PURE__ */ jsx(
1496
+ Button,
1497
+ {
1498
+ variant: active ? "contained" : "outlined",
1499
+ size: "small",
1500
+ onClick,
1501
+ "aria-label": ariaLabel,
1502
+ "aria-current": ariaCurrent,
1503
+ sx: { minWidth: 32, px: 0.5 },
1504
+ children
1540
1505
  }
1541
- const { pageNumbers, showStartEllipsis, showEndEllipsis, totalPages, startItem, endItem } = vm;
1542
- return /* @__PURE__ */ jsxs(
1543
- Box,
1544
- {
1545
- className,
1546
- role: "navigation",
1547
- "aria-label": "Pagination",
1548
- sx: {
1549
- display: "flex",
1550
- alignItems: "center",
1551
- justifyContent: "space-between",
1552
- flexWrap: "wrap",
1553
- gap: 2,
1554
- px: 1.5,
1555
- width: "100%",
1556
- minWidth: 0,
1557
- boxSizing: "border-box"
1558
- },
1559
- children: [
1560
- /* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "text.secondary", children: [
1561
- "Showing ",
1562
- startItem,
1563
- " to ",
1564
- endItem,
1565
- " of ",
1566
- totalCount.toLocaleString(),
1567
- " ",
1568
- labelPlural
1569
- ] }),
1570
- /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 }, children: [
1571
- /* @__PURE__ */ jsx(
1572
- IconButton,
1573
- {
1574
- size: "small",
1575
- onClick: () => onPageChange(1),
1576
- disabled: currentPage === 1,
1577
- "aria-label": "First page",
1578
- children: /* @__PURE__ */ jsx(FirstPage, { fontSize: "small" })
1579
- }
1580
- ),
1581
- /* @__PURE__ */ jsx(
1582
- IconButton,
1583
- {
1584
- size: "small",
1585
- onClick: () => onPageChange(currentPage - 1),
1586
- disabled: currentPage === 1,
1587
- "aria-label": "Previous page",
1588
- children: /* @__PURE__ */ jsx(ChevronLeft, { fontSize: "small" })
1589
- }
1590
- ),
1591
- showStartEllipsis && /* @__PURE__ */ jsxs(Fragment, { children: [
1592
- /* @__PURE__ */ jsx(
1593
- Button,
1594
- {
1595
- variant: "outlined",
1596
- size: "small",
1597
- onClick: () => onPageChange(1),
1598
- "aria-label": "Page 1",
1599
- sx: { minWidth: 32, px: 0.5 },
1600
- children: "1"
1601
- }
1602
- ),
1603
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mx: 0.5 }, "aria-hidden": true, children: "\u2026" })
1604
- ] }),
1605
- pageNumbers.map((pageNum) => /* @__PURE__ */ jsx(
1606
- Button,
1607
- {
1608
- variant: currentPage === pageNum ? "contained" : "outlined",
1609
- size: "small",
1610
- onClick: () => onPageChange(pageNum),
1611
- "aria-label": `Page ${pageNum}`,
1612
- "aria-current": currentPage === pageNum ? "page" : void 0,
1613
- sx: { minWidth: 32, px: 0.5 },
1614
- children: pageNum
1615
- },
1616
- pageNum
1617
- )),
1618
- showEndEllipsis && /* @__PURE__ */ jsxs(Fragment, { children: [
1619
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mx: 0.5 }, "aria-hidden": true, children: "\u2026" }),
1620
- /* @__PURE__ */ jsx(
1621
- Button,
1622
- {
1623
- variant: "outlined",
1624
- size: "small",
1625
- onClick: () => onPageChange(totalPages),
1626
- "aria-label": `Page ${totalPages}`,
1627
- sx: { minWidth: 32, px: 0.5 },
1628
- children: totalPages
1629
- }
1630
- )
1631
- ] }),
1632
- /* @__PURE__ */ jsx(
1633
- IconButton,
1634
- {
1635
- size: "small",
1636
- onClick: () => onPageChange(currentPage + 1),
1637
- disabled: currentPage >= totalPages,
1638
- "aria-label": "Next page",
1639
- children: /* @__PURE__ */ jsx(ChevronRight, { fontSize: "small" })
1640
- }
1641
- ),
1642
- /* @__PURE__ */ jsx(
1643
- IconButton,
1644
- {
1645
- size: "small",
1646
- onClick: () => onPageChange(totalPages),
1647
- disabled: currentPage >= totalPages,
1648
- "aria-label": "Last page",
1649
- children: /* @__PURE__ */ jsx(LastPage, { fontSize: "small" })
1650
- }
1651
- )
1652
- ] }),
1653
- /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
1654
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: "Rows" }),
1655
- /* @__PURE__ */ jsx(
1656
- Select,
1657
- {
1658
- value: pageSize,
1659
- onChange: handlePageSizeChangeEvent,
1660
- size: "small",
1661
- "aria-label": "Rows per page",
1662
- sx: { minWidth: 70 },
1663
- children: vm.pageSizeOptions.map((n) => /* @__PURE__ */ jsx(MenuItem, { value: n, children: n }, n))
1664
- }
1665
- )
1666
- ] })
1667
- ]
1668
- }
1669
- );
1670
- });
1506
+ );
1507
+ var PageSizeSelect = ({ value, options, onChange, "aria-label": ariaLabel }) => {
1508
+ const handleChange = (event) => onChange(Number(event.target.value));
1509
+ return /* @__PURE__ */ jsx(Select, { value, onChange: handleChange, size: "small", "aria-label": ariaLabel, sx: { minWidth: 70 }, children: options.map((n) => /* @__PURE__ */ jsx(MenuItem, { value: n, children: n }, n)) });
1510
+ };
1511
+ var OuterContainer = ({ children, className, role, "aria-label": ariaLabel }) => /* @__PURE__ */ jsx(
1512
+ Box,
1513
+ {
1514
+ className,
1515
+ role,
1516
+ "aria-label": ariaLabel,
1517
+ sx: {
1518
+ display: "flex",
1519
+ alignItems: "center",
1520
+ justifyContent: "space-between",
1521
+ flexWrap: "wrap",
1522
+ gap: 2,
1523
+ px: 1.5,
1524
+ width: "100%",
1525
+ minWidth: 0,
1526
+ boxSizing: "border-box"
1527
+ },
1528
+ children
1529
+ }
1530
+ );
1531
+ var InfoText = ({ children }) => /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children });
1532
+ var NavButtonsContainer = ({ children }) => /* @__PURE__ */ jsx(Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 }, children });
1533
+ var PageSizeContainer = ({ children }) => /* @__PURE__ */ jsx(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children });
1534
+ var PageSizeLabel = () => /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: "Rows" });
1535
+ var Ellipsis = () => /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mx: 0.5 }, "aria-hidden": true, children: "\u2026" });
1536
+ var SLOTS = {
1537
+ NavButton,
1538
+ PageButton,
1539
+ PageSizeSelect,
1540
+ OuterContainer,
1541
+ InfoText,
1542
+ NavButtonsContainer,
1543
+ PageSizeContainer,
1544
+ PageSizeLabel,
1545
+ Ellipsis
1546
+ };
1547
+ var PaginationControls = React4.memo((props) => /* @__PURE__ */ jsx(PaginationControlsBase, { ...props, slots: SLOTS }));
1548
+ PaginationControls.displayName = "PaginationControls";
1671
1549
  var MuiThemeContainer = React4.forwardRef(
1672
1550
  function MuiThemeContainer2(props, ref) {
1673
1551
  const theme = useTheme();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-react-material",
3
- "version": "2.2.0",
3
+ "version": "2.4.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.2.0"
42
+ "@alaarab/ogrid-react": "2.4.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@emotion/react": "^11.0.0",