@alaarab/ogrid-react-fluent 2.0.3 → 2.0.4

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.
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRef, useEffect } from 'react';
3
+ import { COLUMN_HEADER_MENU_ITEMS } from '@alaarab/ogrid-core';
4
+ import { makeStyles, tokens } from '@fluentui/react-components';
5
+ const useStyles = makeStyles({
6
+ menu: {
7
+ position: 'fixed',
8
+ minWidth: '140px',
9
+ backgroundColor: tokens.colorNeutralBackground1,
10
+ borderRadius: tokens.borderRadiusMedium,
11
+ padding: '4px',
12
+ boxShadow: tokens.shadow16,
13
+ zIndex: 100,
14
+ },
15
+ menuItem: {
16
+ display: 'flex',
17
+ alignItems: 'center',
18
+ height: '28px',
19
+ padding: '0 8px',
20
+ fontSize: tokens.fontSizeBase200,
21
+ color: tokens.colorNeutralForeground1,
22
+ borderRadius: tokens.borderRadiusSmall,
23
+ cursor: 'pointer',
24
+ userSelect: 'none',
25
+ outline: 'none',
26
+ backgroundColor: 'transparent',
27
+ border: 'none',
28
+ width: '100%',
29
+ textAlign: 'left',
30
+ ':hover:not([disabled])': {
31
+ backgroundColor: tokens.colorNeutralBackground1Hover,
32
+ },
33
+ ':active:not([disabled])': {
34
+ backgroundColor: tokens.colorNeutralBackground1Pressed,
35
+ },
36
+ ':disabled': {
37
+ color: tokens.colorNeutralForegroundDisabled,
38
+ cursor: 'not-allowed',
39
+ },
40
+ },
41
+ });
42
+ export function ColumnHeaderMenu(props) {
43
+ const { isOpen, anchorElement, onClose, onPinLeft, onPinRight, onUnpin, canPinLeft, canPinRight, canUnpin } = props;
44
+ const menuRef = useRef(null);
45
+ const styles = useStyles();
46
+ useEffect(() => {
47
+ if (!isOpen)
48
+ return;
49
+ const handleClickOutside = (e) => {
50
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
51
+ onClose();
52
+ }
53
+ };
54
+ const handleEscape = (e) => {
55
+ if (e.key === 'Escape') {
56
+ onClose();
57
+ }
58
+ };
59
+ document.addEventListener('mousedown', handleClickOutside);
60
+ document.addEventListener('keydown', handleEscape);
61
+ return () => {
62
+ document.removeEventListener('mousedown', handleClickOutside);
63
+ document.removeEventListener('keydown', handleEscape);
64
+ };
65
+ }, [isOpen, onClose]);
66
+ if (!isOpen || !anchorElement)
67
+ return null;
68
+ const rect = anchorElement.getBoundingClientRect();
69
+ const menuStyle = {
70
+ top: rect.bottom + 4,
71
+ left: rect.left,
72
+ };
73
+ const handlers = [onPinLeft, onPinRight, onUnpin];
74
+ const disabled = [!canPinLeft, !canPinRight, !canUnpin];
75
+ return (_jsx("div", { ref: menuRef, className: styles.menu, style: menuStyle, children: COLUMN_HEADER_MENU_ITEMS.map((item, idx) => (_jsx("button", { className: styles.menuItem, onClick: handlers[idx], disabled: disabled[idx], children: item.label }, item.id))) }));
76
+ }
@@ -0,0 +1 @@
1
+ export { ColumnHeaderMenu } from './ColumnHeaderMenu';
@@ -1,13 +1,14 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
3
  import { useMemo, useRef, useEffect, useCallback } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { DataGrid, DataGridHeader, DataGridRow, DataGridHeaderCell, DataGridBody, DataGridCell, createTableColumn, Spinner, Checkbox, Popover, PopoverSurface, } from '@fluentui/react-components';
6
6
  import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
7
+ import { ColumnHeaderMenu } from '../ColumnHeaderMenu';
7
8
  import { InlineCellEditor } from './InlineCellEditor';
8
9
  import { StatusBar } from './StatusBar';
9
10
  import { GridContextMenu } from './GridContextMenu';
10
- import { useDataGridState, useColumnReorder, useVirtualScroll, useLatestRef, getHeaderFilterConfig, getCellRenderDescriptor, buildHeaderRows, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-react';
11
+ import { useDataGridState, useColumnReorder, useVirtualScroll, useLatestRef, getHeaderFilterConfig, getCellRenderDescriptor, buildHeaderRows, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-react';
11
12
  import styles from './DataGridTable.module.css';
12
13
  // Module-scope stable constants (avoid per-render allocations)
13
14
  const gridRootStyle = {
@@ -37,15 +38,17 @@ function DataGridTableInner(props) {
37
38
  const wrapperRef = useRef(null);
38
39
  const tableContainerRef = useRef(null);
39
40
  const state = useDataGridState({ props, wrapperRef });
40
- const { layout, rowSelection: rowSel, editing, interaction, contextMenu: ctxMenu, viewModels } = state;
41
- const { flatColumns, visibleCols, totalColCount, hasCheckboxCol, colOffset, rowIndexByRowId, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides } = layout;
41
+ const { layout, rowSelection: rowSel, editing, interaction, contextMenu: ctxMenu, viewModels, pinning } = state;
42
+ const { flatColumns, visibleCols, totalColCount, hasCheckboxCol, hasRowNumbersCol, colOffset, rowIndexByRowId, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides } = layout;
42
43
  const { selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
43
44
  const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
44
45
  const { activeCell, setActiveCell, handleCellMouseDown, handleSelectAllCells, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange, copyRange, canUndo, canRedo, onUndo, onRedo, isDragging } = interaction;
45
46
  const handlePasteVoid = useCallback(() => { void handlePaste(); }, [handlePaste]);
46
47
  const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
47
48
  const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
48
- const { items, columns, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, visibleColumns, columnOrder, onColumnOrderChange, columnReorder, virtualScroll, pinnedColumns, } = props;
49
+ const { items, columns, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, visibleColumns, columnOrder, onColumnOrderChange, columnReorder, virtualScroll, density = 'normal', pinnedColumns, currentPage = 1, pageSize: propPageSize = 25, } = props;
50
+ // Calculate row number offset for pagination
51
+ const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * propPageSize : 0;
49
52
  // Memoize header rows (recursive tree traversal)
50
53
  const headerRows = useMemo(() => buildHeaderRows(columns, visibleColumns), [columns, visibleColumns]);
51
54
  const hasGroupHeaders = headerRows.length > 1;
@@ -72,6 +75,9 @@ function DataGridTableInner(props) {
72
75
  if (hasCheckboxCol) {
73
76
  acc['__selection__'] = { minWidth: CHECKBOX_COLUMN_WIDTH, defaultWidth: CHECKBOX_COLUMN_WIDTH, idealWidth: CHECKBOX_COLUMN_WIDTH };
74
77
  }
78
+ if (hasRowNumbersCol) {
79
+ acc['__row_number__'] = { minWidth: ROW_NUMBER_COLUMN_WIDTH, defaultWidth: ROW_NUMBER_COLUMN_WIDTH, idealWidth: ROW_NUMBER_COLUMN_WIDTH };
80
+ }
75
81
  visibleCols.forEach((c) => {
76
82
  const minW = c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
77
83
  const defaultW = c.defaultWidth ?? 120;
@@ -85,7 +91,7 @@ function DataGridTableInner(props) {
85
91
  };
86
92
  });
87
93
  return acc;
88
- }, [visibleCols, columnSizingOverrides, hasCheckboxCol]);
94
+ }, [visibleCols, columnSizingOverrides, hasCheckboxCol, hasRowNumbersCol]);
89
95
  const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
90
96
  // Pre-compute column class maps (avoids per-cell .filter(Boolean).join(' '))
91
97
  const { cellClassMap, headerClassMap } = useMemo(() => {
@@ -144,7 +150,10 @@ function DataGridTableInner(props) {
144
150
  const dataCols = visibleCols.map((col, colIdx) => createTableColumn({
145
151
  columnId: col.columnId,
146
152
  compare: col.compare ?? (() => 0),
147
- renderHeaderCell: () => (_jsx("div", { "data-column-id": col.columnId, style: columnReorder ? { cursor: isReorderDraggingRef.current ? 'grabbing' : 'grab' } : undefined, onMouseDown: columnReorder ? (e) => handleHeaderMouseDownRef.current(col.columnId, e) : undefined, children: _jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInputRef.current) }) })),
153
+ renderHeaderCell: () => (_jsx("div", { "data-column-id": col.columnId, style: columnReorder ? { cursor: isReorderDraggingRef.current ? 'grabbing' : 'grab' } : undefined, onMouseDown: columnReorder ? (e) => handleHeaderMouseDownRef.current(col.columnId, e) : undefined, children: _jsxs("div", { className: styles.headerCellContent, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInputRef.current) }), _jsx("button", { className: styles.headerMenuTrigger, onClick: (e) => {
154
+ e.stopPropagation();
155
+ pinning.headerMenu.open(col.columnId, e.currentTarget);
156
+ }, "aria-label": "Column options", title: "Column options", children: "\u22EE" })] }) })),
148
157
  renderCell: (item) => {
149
158
  const rowId = getRowId(item);
150
159
  const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
@@ -195,11 +204,38 @@ function DataGridTableInner(props) {
195
204
  }, "aria-label": `Select row ${rowIndex + 1}` }) }));
196
205
  },
197
206
  });
198
- return [checkboxCol, ...dataCols];
207
+ const cols = [checkboxCol];
208
+ if (hasRowNumbersCol) {
209
+ const rowNumberCol = createTableColumn({
210
+ columnId: '__row_number__',
211
+ compare: () => 0,
212
+ renderHeaderCell: () => (_jsx("div", { className: styles.rowNumberHeaderCell, children: "#" })),
213
+ renderCell: (item) => {
214
+ const rowId = getRowId(item);
215
+ const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
216
+ return (_jsx("div", { className: styles.rowNumberCell, children: rowNumberOffset + rowIndex + 1 }));
217
+ },
218
+ });
219
+ cols.push(rowNumberCol);
220
+ }
221
+ return [...cols, ...dataCols];
222
+ }
223
+ if (hasRowNumbersCol) {
224
+ const rowNumberCol = createTableColumn({
225
+ columnId: '__row_number__',
226
+ compare: () => 0,
227
+ renderHeaderCell: () => (_jsx("div", { className: styles.rowNumberHeaderCell, children: "#" })),
228
+ renderCell: (item) => {
229
+ const rowId = getRowId(item);
230
+ const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
231
+ return (_jsx("div", { className: styles.rowNumberCell, children: rowNumberOffset + rowIndex + 1 }));
232
+ },
233
+ });
234
+ return [rowNumberCol, ...dataCols];
199
235
  }
200
236
  return dataCols;
201
237
  // eslint-disable-next-line react-hooks/exhaustive-deps
202
- }, [visibleCols, hasCheckboxCol, getRowId, setPopoverAnchorEl, columnReorder]); // All volatile state/callbacks read via refs
238
+ }, [visibleCols, hasCheckboxCol, hasRowNumbersCol, getRowId, setPopoverAnchorEl, columnReorder, rowNumberOffset]); // All volatile state/callbacks read via refs
203
239
  // Stable row-click handler
204
240
  const handleSingleRowClick = useCallback((rowId) => {
205
241
  if (rowSelection !== 'single')
@@ -252,7 +288,7 @@ function DataGridTableInner(props) {
252
288
  [String(data.columnId)]: { widthPx: data.width },
253
289
  }));
254
290
  }, [setColumnSizingOverrides]);
255
- return (_jsxs("div", { style: gridRootStyle, children: [_jsxs("div", { ref: wrapperRef, tabIndex: 0, className: `${styles.tableWrapper} ${rowSelection !== 'none' ? styles.selectableGrid : ''}`, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, "data-empty": showEmptyInGrid ? 'true' : undefined, "data-auto-fit": layoutMode === 'fill' && !allowOverflowX ? 'true' : undefined, "data-column-count": totalColCount, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, "data-overflow-x": allowOverflowX ? 'true' : 'false', "data-container-width": containerWidth, "data-min-table-width": Math.round(minTableWidth), "data-has-selection": rowSelection !== 'none' ? 'true' : undefined, onContextMenu: PREVENT_DEFAULT, style: {
291
+ return (_jsxs("div", { style: gridRootStyle, children: [_jsxs("div", { ref: wrapperRef, tabIndex: 0, className: `${styles.tableWrapper} ${rowSelection !== 'none' ? styles.selectableGrid : ''} ${styles[`density-${density}`] || ''}`, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, "data-empty": showEmptyInGrid ? 'true' : undefined, "data-auto-fit": layoutMode === 'fill' && !allowOverflowX ? 'true' : undefined, "data-column-count": totalColCount, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, "data-overflow-x": allowOverflowX ? 'true' : 'false', "data-container-width": containerWidth, "data-min-table-width": Math.round(minTableWidth), "data-has-selection": rowSelection !== 'none' ? 'true' : undefined, onContextMenu: PREVENT_DEFAULT, style: {
256
292
  ['--data-table-column-count']: totalColCount,
257
293
  ['--data-table-width']: showEmptyInGrid
258
294
  ? '100%'
@@ -268,7 +304,7 @@ function DataGridTableInner(props) {
268
304
  : fitToContent
269
305
  ? 'max-content'
270
306
  : '100%',
271
- }, onKeyDown: handleGridKeyDown, children: [_jsx("div", { className: styles.tableScrollContent, children: _jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [virtualScrollEnabled && visibleRange.offsetTop > 0 && (_jsx("div", { style: { height: visibleRange.offsetTop }, "aria-hidden": true })), _jsxs(DataGrid, { items: virtualScrollEnabled ? items.slice(visibleRange.startIndex, visibleRange.endIndex + 1) : items, columns: fluentColumns, resizableColumns: true, resizableColumnsOptions: { autoFitColumns: layoutMode === 'fill' && !allowOverflowX }, columnSizingOptions: columnSizingOptions, onColumnResize: handleColumnResize, getRowId: fluentGetRowId, focusMode: "composite", className: styles.dataGrid, children: [_jsxs(DataGridHeader, { className: styles.stickyHeader, children: [hasGroupHeaders && headerRows.slice(0, -1).map((row, rowIdx) => (_jsxs("tr", { className: styles.groupHeaderRow, children: [rowIdx === 0 && hasCheckboxCol && (_jsx("th", { rowSpan: headerRows.length - 1, style: { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLUMN_WIDTH } })), row.map((cell, cellIdx) => {
307
+ }, onKeyDown: handleGridKeyDown, children: [_jsx("div", { className: styles.tableScrollContent, children: _jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [virtualScrollEnabled && visibleRange.offsetTop > 0 && (_jsx("div", { style: { height: visibleRange.offsetTop }, "aria-hidden": true })), _jsxs(DataGrid, { items: virtualScrollEnabled ? items.slice(visibleRange.startIndex, visibleRange.endIndex + 1) : items, columns: fluentColumns, resizableColumns: true, resizableColumnsOptions: { autoFitColumns: layoutMode === 'fill' && !allowOverflowX }, columnSizingOptions: columnSizingOptions, onColumnResize: handleColumnResize, getRowId: fluentGetRowId, focusMode: "composite", className: styles.dataGrid, children: [_jsxs(DataGridHeader, { className: styles.stickyHeader, children: [hasGroupHeaders && headerRows.slice(0, -1).map((row, rowIdx) => (_jsxs("tr", { className: styles.groupHeaderRow, children: [rowIdx === 0 && hasCheckboxCol && (_jsx("th", { rowSpan: headerRows.length - 1, style: { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLUMN_WIDTH } })), rowIdx === 0 && hasRowNumbersCol && (_jsx("th", { rowSpan: headerRows.length - 1, style: { width: ROW_NUMBER_COLUMN_WIDTH, minWidth: ROW_NUMBER_COLUMN_WIDTH } })), row.map((cell, cellIdx) => {
272
308
  if (cell.isGroup) {
273
309
  return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
274
310
  }
@@ -278,6 +314,6 @@ function DataGridTableInner(props) {
278
314
  const rowIndex = rowIndexByRowId.get(rowId) ?? -1;
279
315
  return (_jsx(GridRow, { item: item, rowId: rowId, rowIndex: rowIndex, isSelected: selectedRowIds.has(rowId), hasCheckboxCol: hasCheckboxCol, cellClassMap: cellClassMap, handleSingleRowClick: handleSingleRowClick, selectionRange: selectionRange, activeCell: activeCell, cutRange: cutRange, copyRange: copyRange, isDragging: isDragging, editingRowId: editingCell?.rowId ?? null }, rowId));
280
316
  } })] }), virtualScrollEnabled && visibleRange.offsetBottom > 0 && (_jsx("div", { style: { height: visibleRange.offsetBottom }, "aria-hidden": true })), isReorderDragging && dropIndicatorX != null && (_jsx("div", { className: styles.dropIndicator, style: { left: dropIndicatorX - (wrapperRef.current?.getBoundingClientRect().left ?? 0) } })), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset }), showEmptyInGrid && emptyState && (_jsx("div", { className: styles.emptyStateInGrid, children: _jsx("div", { className: styles.emptyStateInGridMessageSticky, children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.emptyStateInGridIcon, "aria-hidden": true, children: "\uD83D\uDCCB" }), _jsx("div", { className: styles.emptyStateInGridTitle, children: "No results found" }), _jsx("div", { className: styles.emptyStateInGridMessage, children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx("button", { type: "button", className: styles.emptyStateInGridLink, onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }) }))] }) }) }), menuPosition &&
281
- createPortal(_jsx(GridContextMenu, { x: menuPosition.x, y: menuPosition.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? NOOP, onRedo: onRedo ?? NOOP, onCopy: handleCopy, onCut: handleCut, onPaste: handlePasteVoid, onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }), statusBarConfig && (_jsx(StatusBar, { totalCount: statusBarConfig.totalCount, filteredCount: statusBarConfig.filteredCount, selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size, selectedCellCount: selectionRange ? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1) : undefined, aggregation: statusBarConfig.aggregation, suppressRowCount: statusBarConfig.suppressRowCount })), isLoading && (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx(Spinner, { size: "small" }), _jsx("span", { className: styles.loadingOverlayText, children: loadingMessage })] }) }))] }));
317
+ 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), createPortal(_jsx(ColumnHeaderMenu, { 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 }), document.body)] }), statusBarConfig && (_jsx(StatusBar, { totalCount: statusBarConfig.totalCount, filteredCount: statusBarConfig.filteredCount, selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size, selectedCellCount: selectionRange ? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1) : undefined, aggregation: statusBarConfig.aggregation, suppressRowCount: statusBarConfig.suppressRowCount })), isLoading && (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx(Spinner, { size: "small" }), _jsx("span", { className: styles.loadingOverlayText, children: loadingMessage })] }) }))] }));
282
318
  }
283
319
  export const DataGridTable = React.memo(DataGridTableInner);
@@ -204,6 +204,14 @@
204
204
  min-width: 48px !important;
205
205
  max-width: 48px !important;
206
206
  }
207
+ .tableWrapper {
208
+ /* Row numbers column must stay 50px even in auto-fit mode */
209
+ }
210
+ .tableWrapper[data-auto-fit=true][data-column-count] .rowNumberHeaderCellWrapper, .tableWrapper[data-auto-fit=true][data-column-count] .rowNumberCellWrapper {
211
+ width: 50px !important;
212
+ min-width: 50px !important;
213
+ max-width: 50px !important;
214
+ }
207
215
  .tableWrapper {
208
216
  /* Hide resize handle on last column only when table fits (autoFitColumns); when overflow/scroll, last column can resize (Fluent docs) */
209
217
  }
@@ -246,6 +254,20 @@
246
254
  padding: 0 !important;
247
255
  }
248
256
 
257
+ .rowNumberHeaderCellWrapper {
258
+ width: 50px !important;
259
+ min-width: 50px !important;
260
+ max-width: 50px !important;
261
+ padding: 0 !important;
262
+ }
263
+
264
+ .rowNumberCellWrapper {
265
+ width: 50px !important;
266
+ min-width: 50px !important;
267
+ max-width: 50px !important;
268
+ padding: 0 !important;
269
+ }
270
+
249
271
  .selectionHeaderCell {
250
272
  display: flex;
251
273
  align-items: center;
@@ -262,6 +284,19 @@
262
284
  height: 100%;
263
285
  }
264
286
 
287
+ .rowNumberHeaderCell,
288
+ .rowNumberCell {
289
+ display: flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ width: 100%;
293
+ height: 100%;
294
+ font-weight: 600;
295
+ font-variant-numeric: tabular-nums;
296
+ color: var(--colorNeutralForeground3, #666);
297
+ background: var(--colorNeutralBackground3, #f5f5f5);
298
+ }
299
+
265
300
  .selectedRow :global .fui-DataGridCell {
266
301
  background-color: var(--colorNeutralBackground1Selected, #e6f0fb) !important;
267
302
  }
@@ -308,6 +343,10 @@
308
343
  background-color: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12)) !important;
309
344
  }
310
345
 
346
+ :global([data-drag-anchor]) {
347
+ background-color: var(--colorNeutralBackground1, #fff) !important;
348
+ }
349
+
311
350
  .cellCut {
312
351
  background-color: var(--colorNeutralBackground1Hover, rgba(0, 0, 0, 0.04)) !important;
313
352
  opacity: 0.7;
@@ -601,4 +640,65 @@
601
640
  /* Empty state: no extra bottom border (header row is last row, remove its border-bottom) */
602
641
  .tableWrapper[data-empty=true] :global(.fui-DataGridHeader .fui-DataGridRow) {
603
642
  border-bottom: none !important;
643
+ }
644
+
645
+ .headerCellContent {
646
+ display: flex;
647
+ align-items: center;
648
+ gap: 4px;
649
+ }
650
+
651
+ .headerMenuTrigger {
652
+ background: transparent;
653
+ border: none;
654
+ cursor: pointer;
655
+ padding: 2px 4px;
656
+ font-size: 16px;
657
+ line-height: 1;
658
+ color: var(--colorNeutralForeground3, #666);
659
+ opacity: 0;
660
+ transition: opacity 0.15s, background-color 0.15s;
661
+ border-radius: 3px;
662
+ display: flex;
663
+ align-items: center;
664
+ justify-content: center;
665
+ min-width: 20px;
666
+ height: 20px;
667
+ }
668
+ .headerMenuTrigger:hover {
669
+ background: var(--colorNeutralBackground1Hover, #f3f2f1);
670
+ opacity: 1;
671
+ }
672
+ .headerMenuTrigger:active {
673
+ background: var(--colorNeutralBackground1Pressed, #e1dfdd);
674
+ }
675
+
676
+ .tableWrapper :global(.fui-DataGridHeaderCell:hover) .headerMenuTrigger {
677
+ opacity: 1;
678
+ }
679
+
680
+ .tableWrapper :global(.fui-DataGridHeaderCell.pinnedColLeft),
681
+ .tableWrapper :global(.fui-DataGridCell.pinnedColLeft) {
682
+ border-left: 2px solid var(--colorBrandForeground1, #0078d4) !important;
683
+ }
684
+
685
+ .tableWrapper :global(.fui-DataGridHeaderCell.pinnedColRight),
686
+ .tableWrapper :global(.fui-DataGridCell.pinnedColRight) {
687
+ border-right: 2px solid var(--colorBrandForeground1, #0078d4) !important;
688
+ }
689
+
690
+ .density-compact :global(.fui-DataGridHeaderCell),
691
+ .density-compact :global(.fui-DataGridCell) {
692
+ padding: 4px 8px;
693
+ }
694
+ .density-compact .cellContent {
695
+ padding: 4px 8px;
696
+ }
697
+
698
+ .density-comfortable :global(.fui-DataGridHeaderCell),
699
+ .density-comfortable :global(.fui-DataGridCell) {
700
+ padding: 12px 16px;
701
+ }
702
+ .density-comfortable .cellContent {
703
+ padding: 12px 16px;
604
704
  }
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ export interface ColumnHeaderMenuProps {
3
+ isOpen: boolean;
4
+ anchorElement: HTMLElement | null;
5
+ onClose: () => void;
6
+ onPinLeft: () => void;
7
+ onPinRight: () => void;
8
+ onUnpin: () => void;
9
+ canPinLeft: boolean;
10
+ canPinRight: boolean;
11
+ canUnpin: boolean;
12
+ }
13
+ export declare function ColumnHeaderMenu(props: ColumnHeaderMenuProps): React.ReactElement | null;
@@ -0,0 +1,2 @@
1
+ export { ColumnHeaderMenu } from './ColumnHeaderMenu';
2
+ export type { ColumnHeaderMenuProps } from './ColumnHeaderMenu';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-react-fluent",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "OGrid Fluent UI implementation – DataGrid-powered data table with sorting, filtering, pagination, column chooser, and CSV export.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -40,7 +40,7 @@
40
40
  "node": ">=18"
41
41
  },
42
42
  "dependencies": {
43
- "@alaarab/ogrid-react": "2.0.3"
43
+ "@alaarab/ogrid-react": "2.0.4"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@fluentui/react-components": "^9.0.0",