@alaarab/ogrid-fluent 1.6.0 → 1.7.2

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.
@@ -9,6 +9,20 @@ import { StatusBar } from './StatusBar';
9
9
  import { GridContextMenu } from './GridContextMenu';
10
10
  import { useDataGridState, getHeaderFilterConfig, getCellRenderDescriptor, buildHeaderRows, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from '@alaarab/ogrid-core';
11
11
  import styles from './DataGridTable.module.css';
12
+ // Module-scope stable constants (avoid per-render allocations)
13
+ const gridRootStyle = {
14
+ position: 'relative',
15
+ flex: 1,
16
+ minHeight: 0,
17
+ display: 'flex',
18
+ flexDirection: 'column',
19
+ };
20
+ const CURSOR_CELL_STYLE = { cursor: 'cell' };
21
+ const NUMERIC_STYLE = { justifyContent: 'flex-end', textAlign: 'right' };
22
+ const BOOLEAN_STYLE = { justifyContent: 'center', textAlign: 'center' };
23
+ const EDITABLE_NUMERIC_STYLE = { cursor: 'cell', justifyContent: 'flex-end', textAlign: 'right' };
24
+ const EDITABLE_BOOLEAN_STYLE = { cursor: 'cell', justifyContent: 'center', textAlign: 'center' };
25
+ const PREVENT_DEFAULT = (e) => { e.preventDefault(); };
12
26
  function DataGridTableInner(props) {
13
27
  const wrapperRef = useRef(null);
14
28
  const tableContainerRef = useRef(null);
@@ -21,7 +35,8 @@ function DataGridTableInner(props) {
21
35
  const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
22
36
  const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid } = viewModels;
23
37
  const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
24
- const headerRows = buildHeaderRows(props.columns, props.visibleColumns);
38
+ // Memoize header rows (recursive tree traversal)
39
+ const headerRows = useMemo(() => buildHeaderRows(props.columns, props.visibleColumns), [props.columns, props.visibleColumns]);
25
40
  const hasGroupHeaders = headerRows.length > 1;
26
41
  const fitToContent = layoutMode === 'content';
27
42
  const columnSizingOptions = useMemo(() => {
@@ -44,6 +59,52 @@ function DataGridTableInner(props) {
44
59
  return acc;
45
60
  }, [visibleCols, columnSizingOverrides, hasCheckboxCol]);
46
61
  const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
62
+ // Pre-compute column class maps (avoids per-cell .filter(Boolean).join(' '))
63
+ const { cellClassMap, headerClassMap, colIndexMap } = useMemo(() => {
64
+ const cm = {};
65
+ const hm = {};
66
+ const im = new Map();
67
+ for (let i = 0; i < visibleCols.length; i++) {
68
+ const col = visibleCols[i];
69
+ const isFreezeCol = freezeCols != null && freezeCols >= 1 && i < freezeCols;
70
+ const isPinnedLeft = col.pinned === 'left';
71
+ const isPinnedRight = col.pinned === 'right';
72
+ const parts = [];
73
+ if (isFreezeCol)
74
+ parts.push(styles.freezeCol);
75
+ if (isFreezeCol && i === 0)
76
+ parts.push(styles.freezeColFirst);
77
+ if (isPinnedLeft) {
78
+ parts.push(styles.pinnedCell);
79
+ parts.push(styles.pinnedLeft);
80
+ }
81
+ if (isPinnedRight) {
82
+ parts.push(styles.pinnedCell);
83
+ parts.push(styles.pinnedRight);
84
+ }
85
+ cm[col.columnId] = parts.join(' ');
86
+ hm[col.columnId] = parts.join(' ');
87
+ im.set(col.columnId, i);
88
+ }
89
+ cm['__selection__'] = styles.selectionCellWrapper;
90
+ hm['__selection__'] = styles.selectionHeaderCellWrapper;
91
+ return { cellClassMap: cm, headerClassMap: hm, colIndexMap: im };
92
+ }, [visibleCols, freezeCols]);
93
+ // Refs for volatile state (read inside fluentColumns render closures without adding to deps)
94
+ const cellDescriptorInputRef = useRef(cellDescriptorInput);
95
+ cellDescriptorInputRef.current = cellDescriptorInput;
96
+ const selectedRowIdsRef = useRef(selectedRowIds);
97
+ selectedRowIdsRef.current = selectedRowIds;
98
+ const activeCellRef = useRef(activeCell);
99
+ activeCellRef.current = activeCell;
100
+ const pendingEditorValueRef = useRef(pendingEditorValue);
101
+ pendingEditorValueRef.current = pendingEditorValue;
102
+ const popoverAnchorElRef = useRef(popoverAnchorEl);
103
+ popoverAnchorElRef.current = popoverAnchorEl;
104
+ const allSelectedRef = useRef(allSelected);
105
+ allSelectedRef.current = allSelected;
106
+ const someSelectedRef = useRef(someSelected);
107
+ someSelectedRef.current = someSelected;
47
108
  const fluentColumns = useMemo(() => {
48
109
  const dataCols = visibleCols.map((col, colIdx) => createTableColumn({
49
110
  columnId: col.columnId,
@@ -52,16 +113,16 @@ function DataGridTableInner(props) {
52
113
  renderCell: (item) => {
53
114
  const rowId = getRowId(item);
54
115
  const rowIndex = rowIndexByRowId.get(rowId) ?? -1;
55
- const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
116
+ const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInputRef.current);
56
117
  if (descriptor.mode === 'editing-inline') {
57
118
  return _jsx(InlineCellEditor, { ...buildInlineEditorProps(item, col, descriptor, { commitCellEdit, setEditingCell }) });
58
119
  }
59
120
  if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
60
- const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, { setPendingEditorValue, commitCellEdit, cancelPopoverEdit });
121
+ const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValueRef.current, { setPendingEditorValue, commitCellEdit, cancelPopoverEdit });
61
122
  const CustomEditor = col.cellEditor;
62
123
  return (_jsxs(_Fragment, { children: [_jsx("div", { ref: (el) => { if (el)
63
- setPopoverAnchorEl(el); }, style: { minHeight: '100%', minWidth: 40 }, "aria-hidden": true }), _jsx(Popover, { open: !!popoverAnchorEl, onOpenChange: (_, data) => { if (!data.open)
64
- cancelPopoverEdit(); }, positioning: { target: popoverAnchorEl ?? undefined }, children: _jsx(PopoverSurface, { children: _jsx(CustomEditor, { ...editorProps }) }) })] }));
124
+ setPopoverAnchorEl(el); }, style: { minHeight: '100%', minWidth: 40 }, "aria-hidden": true }), _jsx(Popover, { open: !!popoverAnchorElRef.current, onOpenChange: (_, data) => { if (!data.open)
125
+ cancelPopoverEdit(); }, positioning: { target: popoverAnchorElRef.current ?? undefined }, children: _jsx(PopoverSurface, { children: _jsx(CustomEditor, { ...editorProps }) }) })] }));
65
126
  }
66
127
  const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
67
128
  const cellStyle = resolveCellStyle(col, item);
@@ -77,23 +138,24 @@ function DataGridTableInner(props) {
77
138
  .join(' ');
78
139
  const colType = col.type;
79
140
  const interactionProps = getCellInteractionProps(descriptor, col.columnId, { handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu });
80
- return (_jsxs("div", { className: cellClassNames, ...interactionProps, style: {
81
- ...(descriptor.canEditAny ? { cursor: 'cell' } : undefined),
82
- ...(colType === 'numeric' ? { justifyContent: 'flex-end', textAlign: 'right' } : undefined),
83
- ...(colType === 'boolean' ? { justifyContent: 'center', textAlign: 'center' } : undefined),
84
- }, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
141
+ // Select stable style constant by type + editability
142
+ const computedStyle = descriptor.canEditAny
143
+ ? (colType === 'numeric' ? EDITABLE_NUMERIC_STYLE : colType === 'boolean' ? EDITABLE_BOOLEAN_STYLE : CURSOR_CELL_STYLE)
144
+ : (colType === 'numeric' ? NUMERIC_STYLE : colType === 'boolean' ? BOOLEAN_STYLE : undefined);
145
+ return (_jsxs("div", { className: cellClassNames, ...interactionProps, style: computedStyle, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
85
146
  },
86
147
  }));
87
148
  if (hasCheckboxCol) {
88
149
  const checkboxCol = createTableColumn({
89
150
  columnId: '__selection__',
90
151
  compare: () => 0,
91
- renderHeaderCell: () => (_jsx("div", { className: styles.selectionHeaderCell, children: _jsx(Checkbox, { checked: allSelected ? true : someSelected ? 'mixed' : false, onChange: (_, data) => handleSelectAll(!!data.checked), "aria-label": "Select all rows" }) })),
152
+ renderHeaderCell: () => (_jsx("div", { className: styles.selectionHeaderCell, children: _jsx(Checkbox, { checked: allSelectedRef.current ? true : someSelectedRef.current ? 'mixed' : false, onChange: (_, data) => handleSelectAll(!!data.checked), "aria-label": "Select all rows" }) })),
92
153
  renderCell: (item) => {
93
154
  const rowId = getRowId(item);
94
155
  const rowIndex = rowIndexByRowId.get(rowId) ?? -1;
95
- const isChecked = selectedRowIds.has(rowId);
96
- const isActive = activeCell?.rowIndex === rowIndex && activeCell?.columnIndex === 0;
156
+ const isChecked = selectedRowIdsRef.current.has(rowId);
157
+ const ac = activeCellRef.current;
158
+ const isActive = ac?.rowIndex === rowIndex && ac?.columnIndex === 0;
97
159
  return (_jsx("div", { className: `${styles.selectionCell} ${isActive ? styles.activeCellContent : ''}`, "data-row-index": rowIndex, "data-col-index": 0, onClick: (e) => {
98
160
  e.stopPropagation();
99
161
  setActiveCell({ rowIndex, columnIndex: 0 });
@@ -108,18 +170,11 @@ function DataGridTableInner(props) {
108
170
  }, [
109
171
  visibleCols,
110
172
  headerFilterInput,
111
- cellDescriptorInput,
112
173
  getRowId,
113
174
  rowIndexByRowId,
114
- pendingEditorValue,
115
- popoverAnchorEl,
116
175
  hasCheckboxCol,
117
- allSelected,
118
- someSelected,
119
- selectedRowIds,
120
176
  handleSelectAll,
121
177
  handleRowCheckboxChange,
122
- activeCell,
123
178
  handleCellMouseDown,
124
179
  handleFillHandleMouseDown,
125
180
  handleCellContextMenu,
@@ -130,6 +185,15 @@ function DataGridTableInner(props) {
130
185
  commitCellEdit,
131
186
  cancelPopoverEdit,
132
187
  ]);
188
+ // Stable row-click handler
189
+ const handleSingleRowClick = useCallback((rowId) => {
190
+ if (rowSelection !== 'single')
191
+ return;
192
+ const ids = selectedRowIdsRef.current;
193
+ updateSelection(ids.has(rowId) ? new Set() : new Set([rowId]));
194
+ }, [rowSelection, updateSelection]);
195
+ // Stable getRowId wrapper for Fluent DataGrid
196
+ const fluentGetRowId = useCallback((item) => String(getRowId(item)), [getRowId]);
133
197
  // Double-click to auto-fit column width
134
198
  useEffect(() => {
135
199
  const root = wrapperRef.current;
@@ -172,71 +236,35 @@ function DataGridTableInner(props) {
172
236
  [String(data.columnId)]: { widthPx: data.width },
173
237
  }));
174
238
  }, [setColumnSizingOverrides]);
175
- return (_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: (e) => {
176
- e.preventDefault();
177
- }, style: {
178
- ['--data-table-column-count']: totalColCount,
179
- ['--data-table-width']: showEmptyInGrid
180
- ? '100%'
181
- : allowOverflowX
182
- ? 'fit-content'
183
- : fitToContent
184
- ? 'fit-content'
185
- : '100%',
186
- ['--data-table-min-width']: showEmptyInGrid
187
- ? '100%'
188
- : allowOverflowX
189
- ? 'max-content'
190
- : fitToContent
191
- ? 'max-content'
192
- : '100%',
193
- }, onKeyDown: handleGridKeyDown, children: [isLoading && items.length > 0 && (_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 })] }) })), _jsxs("div", { className: styles.tableScrollContent, children: [_jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [_jsxs(DataGrid, { items: items, columns: fluentColumns, resizableColumns: true, resizableColumnsOptions: { autoFitColumns: layoutMode === 'fill' && !allowOverflowX }, columnSizingOptions: columnSizingOptions, onColumnResize: handleColumnResize, getRowId: (item) => String(getRowId(item)), 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: 48, minWidth: 48 } })), row.map((cell, cellIdx) => {
194
- if (cell.isGroup) {
195
- return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
196
- }
197
- return (_jsx("th", { rowSpan: headerRows.length - rowIdx, className: styles.leafHeaderCellSpan, scope: "col", children: cell.columnDef?.name }, cellIdx));
198
- })] }, `group-${rowIdx}`))), _jsx(DataGridRow, { children: ({ renderHeaderCell, columnId }) => {
199
- const colIdx = visibleCols.findIndex((c) => c.columnId === columnId);
200
- const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx >= 0 && colIdx < freezeCols;
201
- const col = colIdx >= 0 ? visibleCols[colIdx] : undefined;
202
- const isPinnedLeft = col?.pinned === 'left';
203
- const isPinnedRight = col?.pinned === 'right';
204
- return (_jsx(DataGridHeaderCell, { className: [
205
- columnId === '__selection__' ? styles.selectionHeaderCellWrapper : '',
206
- isFreezeCol ? styles.freezeCol : '',
207
- isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
208
- isPinnedLeft ? styles.pinnedCell : '',
209
- isPinnedLeft ? styles.pinnedLeft : '',
210
- isPinnedRight ? styles.pinnedCell : '',
211
- isPinnedRight ? styles.pinnedRight : '',
212
- ].filter(Boolean).join(' '), children: renderHeaderCell() }));
213
- } })] }), _jsx(DataGridBody, { children: ({ item }) => {
214
- const rowId = getRowId(item);
215
- const isSelected = selectedRowIds.has(rowId);
216
- return (_jsx(DataGridRow, { className: `${isSelected ? styles.selectedRow : ''} ${activeCell !== null && (rowIndexByRowId.get(rowId) ?? -1) === activeCell.rowIndex
217
- ? styles.activeRow
218
- : ''}`, onClick: () => {
219
- if (rowSelection === 'single') {
220
- const isCurrentlySelected = selectedRowIds.has(rowId);
221
- updateSelection(isCurrentlySelected ? new Set() : new Set([rowId]));
222
- }
223
- }, children: ({ renderCell, columnId }) => {
224
- const colIdx = visibleCols.findIndex((c) => c.columnId === columnId);
225
- const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx >= 0 && colIdx < freezeCols;
226
- const col = colIdx >= 0 ? visibleCols[colIdx] : undefined;
227
- const isPinnedLeft = col?.pinned === 'left';
228
- const isPinnedRight = col?.pinned === 'right';
229
- return (_jsx(DataGridCell, { className: [
230
- columnId === '__selection__' ? styles.selectionCellWrapper : '',
231
- isFreezeCol ? styles.freezeCol : '',
232
- isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
233
- isPinnedLeft ? styles.pinnedCell : '',
234
- isPinnedLeft ? styles.pinnedLeft : '',
235
- isPinnedRight ? styles.pinnedCell : '',
236
- isPinnedRight ? styles.pinnedRight : '',
237
- ].filter(Boolean).join(' '), children: renderCell(item) }));
238
- } }, rowId));
239
- } })] }), _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.') })] })) }) }))] }) }), 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 }))] }), menuPosition &&
240
- createPortal(_jsx(GridContextMenu, { x: menuPosition.x, y: menuPosition.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? (() => { }), onRedo: onRedo ?? (() => { }), onCopy: handleCopy, onCut: handleCut, onPaste: () => void handlePaste(), onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }));
239
+ 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: {
240
+ ['--data-table-column-count']: totalColCount,
241
+ ['--data-table-width']: showEmptyInGrid
242
+ ? '100%'
243
+ : allowOverflowX
244
+ ? 'fit-content'
245
+ : fitToContent
246
+ ? 'fit-content'
247
+ : '100%',
248
+ ['--data-table-min-width']: showEmptyInGrid
249
+ ? '100%'
250
+ : allowOverflowX
251
+ ? 'max-content'
252
+ : fitToContent
253
+ ? 'max-content'
254
+ : '100%',
255
+ }, onKeyDown: handleGridKeyDown, children: [_jsxs("div", { className: styles.tableScrollContent, children: [_jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [_jsxs(DataGrid, { items: 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: 48, minWidth: 48 } })), row.map((cell, cellIdx) => {
256
+ if (cell.isGroup) {
257
+ return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
258
+ }
259
+ return (_jsx("th", { rowSpan: headerRows.length - rowIdx, className: styles.leafHeaderCellSpan, scope: "col", children: cell.columnDef?.name }, cellIdx));
260
+ })] }, `group-${rowIdx}`))), _jsx(DataGridRow, { children: ({ renderHeaderCell, columnId }) => (_jsx(DataGridHeaderCell, { className: headerClassMap[String(columnId)] || undefined, children: renderHeaderCell() })) })] }), _jsx(DataGridBody, { children: ({ item }) => {
261
+ const rowId = getRowId(item);
262
+ const isSelected = selectedRowIdsRef.current.has(rowId);
263
+ const ac = activeCellRef.current;
264
+ return (_jsx(DataGridRow, { className: `${isSelected ? styles.selectedRow : ''} ${ac !== null && (rowIndexByRowId.get(rowId) ?? -1) === ac.rowIndex
265
+ ? styles.activeRow
266
+ : ''}`, onClick: () => handleSingleRowClick(rowId), children: ({ renderCell, columnId }) => (_jsx(DataGridCell, { className: cellClassMap[String(columnId)] || undefined, children: renderCell(item) })) }, rowId));
267
+ } })] }), _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.') })] })) }) }))] }) }), 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 }))] }), menuPosition &&
268
+ createPortal(_jsx(GridContextMenu, { x: menuPosition.x, y: menuPosition.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? (() => { }), onRedo: onRedo ?? (() => { }), onCopy: handleCopy, onCut: handleCut, onPaste: () => void handlePaste(), onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }), isLoading && items.length > 0 && (_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 })] }) }))] }));
241
269
  }
242
270
  export const DataGridTable = React.memo(DataGridTableInner);
@@ -38,6 +38,7 @@
38
38
  border: none;
39
39
  background-color: var(--colorNeutralBackground1, #ffffff);
40
40
  -webkit-overflow-scrolling: touch;
41
+ will-change: scroll-position;
41
42
  /* Wide tables: allow horizontal scroll */
42
43
  }
43
44
  .tableWrapper[data-overflow-x=true] {
@@ -330,7 +331,7 @@
330
331
  .stickyHeader {
331
332
  position: sticky;
332
333
  top: 0;
333
- z-index: 2;
334
+ z-index: 6;
334
335
  background-color: var(--colorSubtleBackgroundSelected, #f3f2f1);
335
336
  }
336
337
 
@@ -340,6 +341,7 @@
340
341
  left: 0;
341
342
  z-index: 2;
342
343
  background-color: var(--colorNeutralBackground1, #ffffff);
344
+ will-change: transform;
343
345
  }
344
346
 
345
347
  :global(.fui-DataGridHeader) .freezeColFirst {
@@ -353,7 +355,7 @@
353
355
  .stickyHeader .pinnedLeft,
354
356
  .stickyHeader .pinnedRight {
355
357
  top: 0;
356
- z-index: 3;
358
+ z-index: 7;
357
359
  }
358
360
 
359
361
  .activeRow :global .fui-DataGridCell:not(:has(.activeCellContent)) {
@@ -364,6 +366,7 @@
364
366
  position: sticky;
365
367
  z-index: 2;
366
368
  background-color: inherit;
369
+ will-change: transform;
367
370
  }
368
371
 
369
372
  .pinnedLeft {
@@ -6,13 +6,11 @@ import { ColumnChooser } from '../ColumnChooser/ColumnChooser';
6
6
  import { PaginationControls } from '../PaginationControls/PaginationControls';
7
7
  import { useOGrid, OGridLayout, } from '@alaarab/ogrid-core';
8
8
  const OGridInner = forwardRef(function OGridInner(props, ref) {
9
- const { dataGridProps, page, pageSize, displayTotalCount, setPage, setPageSize, columnChooserColumns, visibleColumns, handleVisibilityChange, toolbar, className, entityLabelPlural, pageSizeOptions, sideBarProps, columnChooserPlacement, } = useOGrid(props, ref);
10
- return (_jsx(OGridLayout, { className: className, sideBar: sideBarProps, toolbar: toolbar, toolbarEnd: columnChooserPlacement === 'toolbar' ? (_jsx(ColumnChooser, { columns: columnChooserColumns, visibleColumns: visibleColumns, onVisibilityChange: handleVisibilityChange })) : undefined, pagination: _jsx(PaginationControls, { currentPage: page, pageSize: pageSize, totalCount: displayTotalCount, onPageChange: setPage, onPageSizeChange: (size) => {
9
+ const { dataGridProps, page, pageSize, displayTotalCount, setPage, setPageSize, columnChooserColumns, visibleColumns, handleVisibilityChange, toolbar, toolbarBelow, className, entityLabelPlural, pageSizeOptions, sideBarProps, columnChooserPlacement, } = useOGrid(props, ref);
10
+ return (_jsx(OGridLayout, { className: className, sideBar: sideBarProps, toolbar: toolbar, toolbarBelow: toolbarBelow, toolbarEnd: columnChooserPlacement === 'toolbar' ? (_jsx(ColumnChooser, { columns: columnChooserColumns, visibleColumns: visibleColumns, onVisibilityChange: handleVisibilityChange })) : undefined, pagination: _jsx(PaginationControls, { currentPage: page, pageSize: pageSize, totalCount: displayTotalCount, onPageChange: setPage, onPageSizeChange: (size) => {
11
11
  setPageSize(size);
12
12
  setPage(1);
13
13
  }, pageSizeOptions: pageSizeOptions, entityLabelPlural: entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
14
14
  });
15
15
  OGridInner.displayName = 'OGrid';
16
16
  export const OGrid = React.memo(OGridInner);
17
- /** @deprecated Use OGrid and IOGridProps. Kept for backward compatibility. */
18
- export const FluentDataTable = OGrid;
@@ -1 +1 @@
1
- export { OGrid, FluentDataTable } from './FluentDataTable';
1
+ export { OGrid } from './FluentDataTable';
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Components
2
- export { OGrid, FluentDataTable } from './FluentDataTable';
2
+ export { OGrid } from './FluentDataTable';
3
3
  export { DataGridTable } from './DataGridTable/DataGridTable';
4
4
  export { ColumnChooser } from './ColumnChooser/ColumnChooser';
5
5
  export { ColumnHeaderFilter } from './ColumnHeaderFilter/ColumnHeaderFilter';
@@ -1,7 +1,5 @@
1
1
  import * as React from 'react';
2
2
  import type { IOGridDataGridProps } from '@alaarab/ogrid-core';
3
- /** @deprecated Use IOGridDataGridProps from @alaarab/ogrid-core for new code. */
4
- export type IDataGridTableProps<T> = IOGridDataGridProps<T>;
5
3
  declare function DataGridTableInner<T>(props: IOGridDataGridProps<T>): React.ReactElement;
6
4
  export declare const DataGridTable: typeof DataGridTableInner;
7
5
  export {};
@@ -3,7 +3,3 @@ import { type IOGridProps, type IOGridApi } from '@alaarab/ogrid-core';
3
3
  export type { IOGridProps } from '@alaarab/ogrid-core';
4
4
  declare const OGridInner: React.ForwardRefExoticComponent<IOGridProps<unknown> & React.RefAttributes<IOGridApi<unknown>>>;
5
5
  export declare const OGrid: typeof OGridInner;
6
- /** @deprecated Use OGrid and IOGridProps. Kept for backward compatibility. */
7
- export declare const FluentDataTable: React.ForwardRefExoticComponent<IOGridProps<unknown> & React.RefAttributes<IOGridApi<unknown>>>;
8
- /** @deprecated Use IOGridProps. Kept for backward compatibility. */
9
- export type IFluentDataTableProps<T> = IOGridProps<T>;
@@ -1 +1 @@
1
- export { OGrid, FluentDataTable, type IOGridProps, type IFluentDataTableProps } from './FluentDataTable';
1
+ export { OGrid, type IOGridProps } from './FluentDataTable';
@@ -1,5 +1,5 @@
1
- export { OGrid, type IOGridProps, FluentDataTable, type IFluentDataTableProps } from './FluentDataTable';
2
- export { DataGridTable, type IDataGridTableProps } from './DataGridTable/DataGridTable';
1
+ export { OGrid, type IOGridProps } from './FluentDataTable';
2
+ export { DataGridTable } from './DataGridTable/DataGridTable';
3
3
  export { ColumnChooser, type IColumnChooserProps } from './ColumnChooser/ColumnChooser';
4
4
  export { ColumnHeaderFilter, type IColumnHeaderFilterProps } from './ColumnHeaderFilter/ColumnHeaderFilter';
5
5
  export { PaginationControls, type IPaginationControlsProps } from './PaginationControls/PaginationControls';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-fluent",
3
- "version": "1.6.0",
3
+ "version": "1.7.2",
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-core": "^1.6.0"
43
+ "@alaarab/ogrid-core": "^1.7.2"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@fluentui/react-components": "^9.0.0",