@alaarab/ogrid-react-fluent 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,7 +7,7 @@ import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
|
|
|
7
7
|
import { InlineCellEditor } from './InlineCellEditor';
|
|
8
8
|
import { StatusBar } from './StatusBar';
|
|
9
9
|
import { GridContextMenu } from './GridContextMenu';
|
|
10
|
-
import { useDataGridState, useLatestRef, getHeaderFilterConfig, getCellRenderDescriptor, buildHeaderRows, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-react';
|
|
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
11
|
import styles from './DataGridTable.module.css';
|
|
12
12
|
// Module-scope stable constants (avoid per-render allocations)
|
|
13
13
|
const gridRootStyle = {
|
|
@@ -45,11 +45,28 @@ function DataGridTableInner(props) {
|
|
|
45
45
|
const handlePasteVoid = useCallback(() => { void handlePaste(); }, [handlePaste]);
|
|
46
46
|
const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
|
|
47
47
|
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
|
|
48
|
-
const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
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
49
|
// Memoize header rows (recursive tree traversal)
|
|
50
|
-
const headerRows = useMemo(() => buildHeaderRows(
|
|
50
|
+
const headerRows = useMemo(() => buildHeaderRows(columns, visibleColumns), [columns, visibleColumns]);
|
|
51
51
|
const hasGroupHeaders = headerRows.length > 1;
|
|
52
52
|
const fitToContent = layoutMode === 'content';
|
|
53
|
+
const { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown } = useColumnReorder({
|
|
54
|
+
columns: visibleCols,
|
|
55
|
+
columnOrder,
|
|
56
|
+
onColumnOrderChange,
|
|
57
|
+
enabled: columnReorder === true,
|
|
58
|
+
pinnedColumns,
|
|
59
|
+
wrapperRef,
|
|
60
|
+
});
|
|
61
|
+
const virtualScrollEnabled = virtualScroll?.enabled === true;
|
|
62
|
+
const virtualRowHeight = virtualScroll?.rowHeight ?? 36;
|
|
63
|
+
const { visibleRange } = useVirtualScroll({
|
|
64
|
+
totalRows: items.length,
|
|
65
|
+
rowHeight: virtualRowHeight,
|
|
66
|
+
enabled: virtualScrollEnabled,
|
|
67
|
+
overscan: virtualScroll?.overscan,
|
|
68
|
+
containerRef: wrapperRef,
|
|
69
|
+
});
|
|
53
70
|
const columnSizingOptions = useMemo(() => {
|
|
54
71
|
const acc = {};
|
|
55
72
|
if (hasCheckboxCol) {
|
|
@@ -121,11 +138,13 @@ function DataGridTableInner(props) {
|
|
|
121
138
|
const handleSelectAllRef = useLatestRef(handleSelectAll);
|
|
122
139
|
const handleRowCheckboxChangeRef = useLatestRef(handleRowCheckboxChange);
|
|
123
140
|
const rowIndexByRowIdRef = useLatestRef(rowIndexByRowId);
|
|
141
|
+
const handleHeaderMouseDownRef = useLatestRef(handleHeaderMouseDown);
|
|
142
|
+
const isReorderDraggingRef = useLatestRef(isReorderDragging);
|
|
124
143
|
const fluentColumns = useMemo(() => {
|
|
125
144
|
const dataCols = visibleCols.map((col, colIdx) => createTableColumn({
|
|
126
145
|
columnId: col.columnId,
|
|
127
146
|
compare: col.compare ?? (() => 0),
|
|
128
|
-
renderHeaderCell: () => (_jsx("div", { "data-column-id": col.columnId, children: _jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInputRef.current) }) })),
|
|
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) }) })),
|
|
129
148
|
renderCell: (item) => {
|
|
130
149
|
const rowId = getRowId(item);
|
|
131
150
|
const rowIndex = rowIndexByRowIdRef.current.get(rowId) ?? -1;
|
|
@@ -180,7 +199,7 @@ function DataGridTableInner(props) {
|
|
|
180
199
|
}
|
|
181
200
|
return dataCols;
|
|
182
201
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
183
|
-
}, [visibleCols, hasCheckboxCol, getRowId, setPopoverAnchorEl]); // All volatile state/callbacks read via refs
|
|
202
|
+
}, [visibleCols, hasCheckboxCol, getRowId, setPopoverAnchorEl, columnReorder]); // All volatile state/callbacks read via refs
|
|
184
203
|
// Stable row-click handler
|
|
185
204
|
const handleSingleRowClick = useCallback((rowId) => {
|
|
186
205
|
if (rowSelection !== 'single')
|
|
@@ -249,15 +268,16 @@ function DataGridTableInner(props) {
|
|
|
249
268
|
: fitToContent
|
|
250
269
|
? 'max-content'
|
|
251
270
|
: '100%',
|
|
252
|
-
}, 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: [_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: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLUMN_WIDTH } })), row.map((cell, cellIdx) => {
|
|
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) => {
|
|
253
272
|
if (cell.isGroup) {
|
|
254
273
|
return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
|
|
255
274
|
}
|
|
256
275
|
return (_jsx("th", { rowSpan: headerRows.length - rowIdx, className: styles.leafHeaderCellSpan, scope: "col", children: cell.columnDef?.name }, cellIdx));
|
|
257
276
|
})] }, `group-${rowIdx}`))), _jsx(DataGridRow, { children: ({ renderHeaderCell, columnId }) => (_jsx(DataGridHeaderCell, { className: headerClassMap[String(columnId)] || undefined, children: renderHeaderCell() })) })] }), _jsx(DataGridBody, { children: ({ item }) => {
|
|
258
277
|
const rowId = getRowId(item);
|
|
259
|
-
|
|
260
|
-
|
|
278
|
+
const rowIndex = rowIndexByRowId.get(rowId) ?? -1;
|
|
279
|
+
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
|
+
} })] }), 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 &&
|
|
261
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 })] }) }))] }));
|
|
262
282
|
}
|
|
263
283
|
export const DataGridTable = React.memo(DataGridTableInner);
|
|
@@ -379,7 +379,7 @@
|
|
|
379
379
|
right: -4px;
|
|
380
380
|
bottom: 0;
|
|
381
381
|
width: 4px;
|
|
382
|
-
background: linear-gradient(to right, rgba(0, 0, 0, 0.
|
|
382
|
+
background: linear-gradient(to right, var(--ogrid-pinned-shadow, rgba(0, 0, 0, 0.12)), transparent);
|
|
383
383
|
pointer-events: none;
|
|
384
384
|
}
|
|
385
385
|
|
|
@@ -393,7 +393,7 @@
|
|
|
393
393
|
left: -4px;
|
|
394
394
|
bottom: 0;
|
|
395
395
|
width: 4px;
|
|
396
|
-
background: linear-gradient(to left, rgba(0, 0, 0, 0.
|
|
396
|
+
background: linear-gradient(to left, var(--ogrid-pinned-shadow, rgba(0, 0, 0, 0.12)), transparent);
|
|
397
397
|
pointer-events: none;
|
|
398
398
|
}
|
|
399
399
|
|
|
@@ -493,7 +493,7 @@
|
|
|
493
493
|
display: flex;
|
|
494
494
|
align-items: center;
|
|
495
495
|
justify-content: center;
|
|
496
|
-
background: rgba(255, 255, 255, 0.7);
|
|
496
|
+
background: var(--ogrid-loading-bg, rgba(255, 255, 255, 0.7));
|
|
497
497
|
backdrop-filter: blur(1px);
|
|
498
498
|
pointer-events: all;
|
|
499
499
|
}
|
|
@@ -581,6 +581,18 @@
|
|
|
581
581
|
color: var(--colorBrandForeground1Hover, #115ea3);
|
|
582
582
|
}
|
|
583
583
|
|
|
584
|
+
/* Column reorder drop indicator */
|
|
585
|
+
.dropIndicator {
|
|
586
|
+
position: absolute;
|
|
587
|
+
top: 0;
|
|
588
|
+
bottom: 0;
|
|
589
|
+
width: 3px;
|
|
590
|
+
background: var(--ogrid-primary, #217346);
|
|
591
|
+
pointer-events: none;
|
|
592
|
+
z-index: 100;
|
|
593
|
+
transition: left 0.05s;
|
|
594
|
+
}
|
|
595
|
+
|
|
584
596
|
/* Empty state: hide body, keep header and empty message */
|
|
585
597
|
.tableWrapper[data-empty=true] :global(.fui-DataGrid) tbody {
|
|
586
598
|
display: none;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-react-fluent",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
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.
|
|
43
|
+
"@alaarab/ogrid-react": "2.0.3"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@fluentui/react-components": "^9.0.0",
|