@alaarab/ogrid-fluent 1.3.2 → 1.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.
- package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.js +6 -1
- package/dist/esm/DataGridTable/DataGridTable.js +65 -93
- package/dist/esm/DataGridTable/DataGridTable.module.css +22 -11
- package/dist/esm/DataGridTable/InlineCellEditor.js +58 -5
- package/dist/esm/FluentDataTable/FluentDataTable.js +3 -3
- package/dist/esm/PaginationControls/PaginationControls.js +2 -2
- package/dist/esm/PaginationControls/PaginationControls.module.css +1 -3
- package/dist/types/ColumnHeaderFilter/ColumnHeaderFilter.d.ts +3 -1
- package/dist/types/DataGridTable/InlineCellEditor.d.ts +1 -1
- package/dist/types/DataGridTable/StatusBar.d.ts +7 -0
- package/dist/types/PaginationControls/PaginationControls.d.ts +1 -0
- package/package.json +6 -6
|
@@ -8,7 +8,7 @@ import { MultiSelectFilterPopover } from './MultiSelectFilterPopover';
|
|
|
8
8
|
import { PeopleFilterPopover } from './PeopleFilterPopover';
|
|
9
9
|
import styles from './ColumnHeaderFilter.module.css';
|
|
10
10
|
export const ColumnHeaderFilter = React.memo((props) => {
|
|
11
|
-
const { columnName, filterType, isSorted = false, isSortedDescending = false, onSort, selectedValues, onFilterChange, options, isLoadingOptions = false, textValue = '', onTextChange, selectedUser, onUserChange, peopleSearch, } = props;
|
|
11
|
+
const { columnName, filterType, isSorted = false, isSortedDescending = false, onSort, selectedValues, onFilterChange, options, isLoadingOptions = false, textValue = '', onTextChange, selectedUser, onUserChange, peopleSearch, dateValue, onDateChange, } = props;
|
|
12
12
|
const state = useColumnHeaderFilterState({
|
|
13
13
|
filterType,
|
|
14
14
|
isSorted,
|
|
@@ -23,6 +23,8 @@ export const ColumnHeaderFilter = React.memo((props) => {
|
|
|
23
23
|
selectedUser,
|
|
24
24
|
onUserChange,
|
|
25
25
|
peopleSearch,
|
|
26
|
+
dateValue,
|
|
27
|
+
onDateChange,
|
|
26
28
|
});
|
|
27
29
|
const { headerRef, popoverRef, peopleInputRef, isFilterOpen, tempSelected, setTempTextValue, searchText, setSearchText, filteredOptions, peopleSuggestions, isPeopleLoading, peopleSearchText, setPeopleSearchText, hasActiveFilter, popoverPosition, handlers, } = state;
|
|
28
30
|
const renderPopoverContent = () => {
|
|
@@ -35,6 +37,9 @@ export const ColumnHeaderFilter = React.memo((props) => {
|
|
|
35
37
|
if (filterType === 'people') {
|
|
36
38
|
return (_jsx(PeopleFilterPopover, { selectedUser: selectedUser, searchText: peopleSearchText, onSearchChange: setPeopleSearchText, suggestions: peopleSuggestions, isLoading: isPeopleLoading, onUserSelect: handlers.handleUserSelect, onClearUser: handlers.handleClearUser, onPopoverClick: handlers.handlePopoverClick, inputRef: peopleInputRef }));
|
|
37
39
|
}
|
|
40
|
+
if (filterType === 'date') {
|
|
41
|
+
return (_jsxs("div", { onClick: handlers.handlePopoverClick, children: [_jsxs("div", { style: { padding: '8px 12px', display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 }, children: ["From:", _jsx("input", { type: "date", value: state.tempDateFrom, onChange: (e) => state.setTempDateFrom(e.target.value), style: { flex: 1 } })] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 }, children: ["To:", _jsx("input", { type: "date", value: state.tempDateTo, onChange: (e) => state.setTempDateTo(e.target.value), style: { flex: 1 } })] })] }), _jsxs("div", { className: styles.popoverActions, children: [_jsx("button", { className: styles.clearButton, onClick: handlers.handleDateClear, disabled: !state.tempDateFrom && !state.tempDateTo, children: "Clear" }), _jsx("button", { className: styles.applyButton, onClick: handlers.handleDateApply, children: "Apply" })] })] }));
|
|
42
|
+
}
|
|
38
43
|
return null;
|
|
39
44
|
};
|
|
40
45
|
return (_jsxs("div", { className: styles.columnHeader, ref: headerRef, children: [_jsx("div", { className: styles.headerContent, children: _jsx(Tooltip, { content: columnName, relationship: "label", withArrow: true, children: _jsx("span", { className: styles.columnNameTooltipTrigger, children: _jsx("span", { className: styles.columnName, "data-header-label": true, children: columnName }) }) }) }), _jsxs("div", { className: styles.headerActions, children: [onSort && (_jsx("button", { type: "button", className: `${styles.sortIcon} ${isSorted ? styles.sortActive : ''}`, onClick: handlers.handleSortClick, "aria-label": `Sort by ${columnName}`, title: isSorted ? (isSortedDescending ? 'Sorted descending' : 'Sorted ascending') : 'Sort', children: isSorted ? (isSortedDescending ? _jsx(ArrowDownRegular, {}) : _jsx(ArrowUpRegular, {})) : (_jsx(ArrowSortRegular, {})) })), filterType !== 'none' && (_jsxs("button", { type: "button", className: `${styles.filterIcon} ${hasActiveFilter ? styles.filterActive : ''} ${isFilterOpen ? styles.filterOpen : ''}`, onClick: handlers.handleFilterIconClick, "aria-label": `Filter ${columnName}`, title: `Filter ${columnName}`, children: [_jsx(FilterRegular, {}), hasActiveFilter && _jsx("span", { className: styles.filterBadge })] }))] }), isFilterOpen && filterType !== 'none' && (_jsxs("div", { className: styles.filterPopover, ref: popoverRef, onClick: handlers.handlePopoverClick, style: popoverPosition
|
|
@@ -7,14 +7,16 @@ 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, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, } from '@alaarab/ogrid-core';
|
|
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
12
|
function DataGridTableInner(props) {
|
|
13
13
|
const wrapperRef = useRef(null);
|
|
14
14
|
const tableContainerRef = useRef(null);
|
|
15
15
|
const state = useDataGridState({ props, wrapperRef });
|
|
16
|
-
const { flatColumns, visibleCols, totalColCount, hasCheckboxCol, rowIndexByRowId, selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, setEditingCell, pendingEditorValue, setPendingEditorValue, activeCell, setActiveCell, handleCellMouseDown, handleSelectAllCells, contextMenu,
|
|
17
|
-
const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
16
|
+
const { flatColumns, visibleCols, totalColCount, hasCheckboxCol, rowIndexByRowId, selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, setEditingCell, pendingEditorValue, setPendingEditorValue, activeCell, setActiveCell, handleCellMouseDown, handleSelectAllCells, contextMenu, handleCellContextMenu, closeContextMenu, canUndo, canRedo, onUndo, onRedo, handleCopy, handleCut, handlePaste, handleGridKeyDown, handleFillHandleMouseDown, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides, statusBarConfig, showEmptyInGrid, hasCellSelection, selectionRange, copyRange, cutRange, colOffset, headerFilterInput, cellDescriptorInput, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl, } = state;
|
|
17
|
+
const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
18
|
+
const headerRows = buildHeaderRows(props.columns, props.visibleColumns);
|
|
19
|
+
const hasGroupHeaders = headerRows.length > 1;
|
|
18
20
|
const fitToContent = layoutMode === 'content';
|
|
19
21
|
const columnSizingOptions = useMemo(() => {
|
|
20
22
|
const acc = {};
|
|
@@ -35,16 +37,7 @@ function DataGridTableInner(props) {
|
|
|
35
37
|
});
|
|
36
38
|
return acc;
|
|
37
39
|
}, [visibleCols, columnSizingOverrides, hasCheckboxCol]);
|
|
38
|
-
const
|
|
39
|
-
const PADDING = 16;
|
|
40
|
-
const checkboxW = hasCheckboxCol ? 48 : 0;
|
|
41
|
-
return visibleCols.reduce((sum, c) => {
|
|
42
|
-
const s = columnSizingOptions[c.columnId];
|
|
43
|
-
const w = s?.idealWidth ?? s?.defaultWidth ?? c.idealWidth ?? c.defaultWidth ?? c.minWidth ?? 80;
|
|
44
|
-
return sum + Math.max(c.minWidth ?? 80, w) + PADDING;
|
|
45
|
-
}, checkboxW);
|
|
46
|
-
}, [visibleCols, columnSizingOptions, hasCheckboxCol]);
|
|
47
|
-
const allowOverflowX = containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
|
|
40
|
+
const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
|
|
48
41
|
const fluentColumns = useMemo(() => {
|
|
49
42
|
const dataCols = visibleCols.map((col, colIdx) => createTableColumn({
|
|
50
43
|
columnId: col.columnId,
|
|
@@ -55,47 +48,18 @@ function DataGridTableInner(props) {
|
|
|
55
48
|
const rowIndex = rowIndexByRowId.get(rowId) ?? -1;
|
|
56
49
|
const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
|
|
57
50
|
if (descriptor.mode === 'editing-inline') {
|
|
58
|
-
return
|
|
51
|
+
return _jsx(InlineCellEditor, { ...buildInlineEditorProps(item, col, descriptor, { commitCellEdit, setEditingCell }) });
|
|
59
52
|
}
|
|
60
53
|
if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
|
|
61
|
-
const
|
|
62
|
-
const displayValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
|
|
54
|
+
const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, { setPendingEditorValue, commitCellEdit, cancelPopoverEdit });
|
|
63
55
|
const CustomEditor = col.cellEditor;
|
|
64
|
-
const editorProps = {
|
|
65
|
-
value: displayValue,
|
|
66
|
-
onValueChange: setPendingEditorValue,
|
|
67
|
-
onCommit: () => {
|
|
68
|
-
const newValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
|
|
69
|
-
commitCellEdit(item, col.columnId, oldValue, newValue, descriptor.rowIndex, descriptor.globalColIndex);
|
|
70
|
-
},
|
|
71
|
-
onCancel: cancelPopoverEdit,
|
|
72
|
-
item,
|
|
73
|
-
column: col,
|
|
74
|
-
cellEditorParams: col.cellEditorParams,
|
|
75
|
-
};
|
|
76
56
|
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: (el) => { if (el)
|
|
77
57
|
setPopoverAnchorEl(el); }, style: { minHeight: '100%', minWidth: 40 }, "aria-hidden": true }), _jsx(Popover, { open: !!popoverAnchorEl, onOpenChange: (_, data) => { if (!data.open)
|
|
78
58
|
cancelPopoverEdit(); }, positioning: { target: popoverAnchorEl ?? undefined }, children: _jsx(PopoverSurface, { children: _jsx(CustomEditor, { ...editorProps }) }) })] }));
|
|
79
59
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
: col.cellStyle
|
|
84
|
-
: undefined;
|
|
85
|
-
let content;
|
|
86
|
-
if (col.renderCell)
|
|
87
|
-
content = col.renderCell(item);
|
|
88
|
-
else {
|
|
89
|
-
const value = descriptor.displayValue;
|
|
90
|
-
if (col.valueFormatter)
|
|
91
|
-
content = col.valueFormatter(value, item);
|
|
92
|
-
else if (value !== null && value !== undefined)
|
|
93
|
-
content = String(value);
|
|
94
|
-
else
|
|
95
|
-
content = null;
|
|
96
|
-
}
|
|
97
|
-
if (cellStyle)
|
|
98
|
-
content = _jsx("span", { style: cellStyle, children: content });
|
|
60
|
+
const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
|
|
61
|
+
const cellStyle = resolveCellStyle(col, item);
|
|
62
|
+
const styledContent = cellStyle ? _jsx("span", { style: cellStyle, children: content }) : content;
|
|
99
63
|
const cellClassNames = [
|
|
100
64
|
styles.cellContent,
|
|
101
65
|
descriptor.isActive && !descriptor.isInRange ? styles.activeCellContent : '',
|
|
@@ -105,10 +69,13 @@ function DataGridTableInner(props) {
|
|
|
105
69
|
]
|
|
106
70
|
.filter(Boolean)
|
|
107
71
|
.join(' ');
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
72
|
+
const colType = col.type;
|
|
73
|
+
const interactionProps = getCellInteractionProps(descriptor, col.columnId, { handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu });
|
|
74
|
+
return (_jsxs("div", { className: cellClassNames, ...interactionProps, style: {
|
|
75
|
+
...(descriptor.canEditAny ? { cursor: 'cell' } : undefined),
|
|
76
|
+
...(colType === 'numeric' ? { justifyContent: 'flex-end', textAlign: 'right' } : undefined),
|
|
77
|
+
...(colType === 'boolean' ? { justifyContent: 'center', textAlign: 'center' } : undefined),
|
|
78
|
+
}, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
|
|
112
79
|
},
|
|
113
80
|
}));
|
|
114
81
|
if (hasCheckboxCol) {
|
|
@@ -217,48 +184,53 @@ function DataGridTableInner(props) {
|
|
|
217
184
|
: fitToContent
|
|
218
185
|
? 'max-content'
|
|
219
186
|
: '100%',
|
|
220
|
-
}, 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 })] }) })),
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
187
|
+
}, 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) => {
|
|
188
|
+
if (cell.isGroup) {
|
|
189
|
+
return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
|
|
190
|
+
}
|
|
191
|
+
return (_jsx("th", { rowSpan: headerRows.length - rowIdx, className: styles.leafHeaderCellSpan, scope: "col", children: cell.columnDef?.name }, cellIdx));
|
|
192
|
+
})] }, `group-${rowIdx}`))), _jsx(DataGridRow, { children: ({ renderHeaderCell, columnId }) => {
|
|
193
|
+
const colIdx = visibleCols.findIndex((c) => c.columnId === columnId);
|
|
194
|
+
const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx >= 0 && colIdx < freezeCols;
|
|
195
|
+
const col = colIdx >= 0 ? visibleCols[colIdx] : undefined;
|
|
196
|
+
const isPinnedLeft = col?.pinned === 'left';
|
|
197
|
+
const isPinnedRight = col?.pinned === 'right';
|
|
198
|
+
return (_jsx(DataGridHeaderCell, { className: [
|
|
199
|
+
columnId === '__selection__' ? styles.selectionHeaderCellWrapper : '',
|
|
200
|
+
isFreezeCol ? styles.freezeCol : '',
|
|
201
|
+
isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
|
|
202
|
+
isPinnedLeft ? styles.pinnedCell : '',
|
|
203
|
+
isPinnedLeft ? styles.pinnedLeft : '',
|
|
204
|
+
isPinnedRight ? styles.pinnedCell : '',
|
|
205
|
+
isPinnedRight ? styles.pinnedRight : '',
|
|
206
|
+
].filter(Boolean).join(' '), children: renderHeaderCell() }));
|
|
207
|
+
} })] }), _jsx(DataGridBody, { children: ({ item }) => {
|
|
208
|
+
const rowId = getRowId(item);
|
|
209
|
+
const isSelected = selectedRowIds.has(rowId);
|
|
210
|
+
return (_jsx(DataGridRow, { className: `${isSelected ? styles.selectedRow : ''} ${activeCell !== null && (rowIndexByRowId.get(rowId) ?? -1) === activeCell.rowIndex
|
|
211
|
+
? styles.activeRow
|
|
212
|
+
: ''}`, onClick: () => {
|
|
213
|
+
if (rowSelection === 'single') {
|
|
214
|
+
const isCurrentlySelected = selectedRowIds.has(rowId);
|
|
215
|
+
updateSelection(isCurrentlySelected ? new Set() : new Set([rowId]));
|
|
216
|
+
}
|
|
217
|
+
}, children: ({ renderCell, columnId }) => {
|
|
218
|
+
const colIdx = visibleCols.findIndex((c) => c.columnId === columnId);
|
|
219
|
+
const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx >= 0 && colIdx < freezeCols;
|
|
220
|
+
const col = colIdx >= 0 ? visibleCols[colIdx] : undefined;
|
|
221
|
+
const isPinnedLeft = col?.pinned === 'left';
|
|
222
|
+
const isPinnedRight = col?.pinned === 'right';
|
|
223
|
+
return (_jsx(DataGridCell, { className: [
|
|
224
|
+
columnId === '__selection__' ? styles.selectionCellWrapper : '',
|
|
225
|
+
isFreezeCol ? styles.freezeCol : '',
|
|
226
|
+
isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
|
|
227
|
+
isPinnedLeft ? styles.pinnedCell : '',
|
|
228
|
+
isPinnedLeft ? styles.pinnedLeft : '',
|
|
229
|
+
isPinnedRight ? styles.pinnedCell : '',
|
|
230
|
+
isPinnedRight ? styles.pinnedRight : '',
|
|
231
|
+
].filter(Boolean).join(' '), children: renderCell(item) }));
|
|
232
|
+
} }, rowId));
|
|
233
|
+
} })] }), _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 }))] }), contextMenu &&
|
|
262
234
|
createPortal(_jsx(GridContextMenu, { x: contextMenu.x, y: contextMenu.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? (() => { }), onRedo: onRedo ?? (() => { }), onCopy: handleCopy, onCut: handleCut, onPaste: () => void handlePaste(), onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }));
|
|
263
235
|
}
|
|
264
236
|
export const DataGridTable = React.memo(DataGridTableInner);
|
|
@@ -12,8 +12,6 @@
|
|
|
12
12
|
position: relative;
|
|
13
13
|
/* When table fits (data-auto-fit): fill 100% so last column gets space. When overflow: size to content for scroll. */
|
|
14
14
|
width: max-content;
|
|
15
|
-
/* No min-width: 100% — anchor sizes to grid content so status bar aligns with the DataGrid border.
|
|
16
|
-
.tableScrollContent provides the full-width background color so no gap is visible. */
|
|
17
15
|
background-color: var(--colorNeutralBackground1, #ffffff);
|
|
18
16
|
}
|
|
19
17
|
|
|
@@ -25,17 +23,19 @@
|
|
|
25
23
|
/* Always use full container width (matches pagination); columns fill the space */
|
|
26
24
|
.tableWrapper {
|
|
27
25
|
position: relative;
|
|
26
|
+
flex: 1;
|
|
27
|
+
min-height: 0;
|
|
28
28
|
/* Default: no horizontal scroll unless we explicitly allow overflow (wide tables). */
|
|
29
29
|
overflow-x: hidden;
|
|
30
|
-
overflow-y:
|
|
30
|
+
overflow-y: auto;
|
|
31
31
|
width: 100%;
|
|
32
32
|
min-width: 0;
|
|
33
33
|
max-width: 100%;
|
|
34
|
-
border-radius: var(--borderRadiusMedium, 4px);
|
|
35
34
|
box-sizing: border-box;
|
|
36
35
|
/* Border is applied to the grid itself so we don't draw an empty bordered area
|
|
37
36
|
when the grid content is narrower than the container. */
|
|
38
37
|
border: none;
|
|
38
|
+
background-color: var(--colorNeutralBackground1, #ffffff);
|
|
39
39
|
-webkit-overflow-scrolling: touch;
|
|
40
40
|
/* Wide tables: allow horizontal scroll */
|
|
41
41
|
}
|
|
@@ -63,9 +63,6 @@
|
|
|
63
63
|
max-width: 100% !important;
|
|
64
64
|
min-width: var(--data-table-min-width, max-content) !important;
|
|
65
65
|
box-sizing: border-box !important;
|
|
66
|
-
/* Visual container border belongs to the grid (not the full-width wrapper). */
|
|
67
|
-
border: 1px solid var(--colorNeutralStroke2, #e0e0e0) !important;
|
|
68
|
-
border-radius: var(--borderRadiusMedium, 4px) !important;
|
|
69
66
|
overflow: hidden;
|
|
70
67
|
}
|
|
71
68
|
.tableWrapper[data-column-count] :global {
|
|
@@ -216,6 +213,23 @@
|
|
|
216
213
|
min-width: 0;
|
|
217
214
|
}
|
|
218
215
|
|
|
216
|
+
.groupHeaderRow th {
|
|
217
|
+
background: var(--ogrid-header-bg, #f5f5f5);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.groupHeaderCell {
|
|
221
|
+
text-align: center;
|
|
222
|
+
font-weight: 600;
|
|
223
|
+
border-bottom: 2px solid var(--ogrid-border, #e0e0e0);
|
|
224
|
+
padding: 6px 10px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.leafHeaderCellSpan {
|
|
228
|
+
font-weight: 600;
|
|
229
|
+
padding: 6px 10px;
|
|
230
|
+
background: var(--ogrid-header-bg, #f5f5f5);
|
|
231
|
+
}
|
|
232
|
+
|
|
219
233
|
.selectionHeaderCellWrapper {
|
|
220
234
|
width: 48px !important;
|
|
221
235
|
min-width: 48px !important;
|
|
@@ -380,9 +394,7 @@
|
|
|
380
394
|
font-size: 12px;
|
|
381
395
|
color: var(--colorNeutralForeground2, #616161);
|
|
382
396
|
background-color: var(--colorSubtleBackgroundSelected, #f3f2f1);
|
|
383
|
-
border: 1px solid var(--colorNeutralStroke2, #e0e0e0);
|
|
384
|
-
border-top: none;
|
|
385
|
-
border-radius: 0 0 var(--borderRadiusMedium, 4px) var(--borderRadiusMedium, 4px);
|
|
397
|
+
border-top: 1px solid var(--colorNeutralStroke2, #e0e0e0);
|
|
386
398
|
min-height: 28px;
|
|
387
399
|
user-select: none;
|
|
388
400
|
}
|
|
@@ -470,7 +482,6 @@
|
|
|
470
482
|
background: rgba(255, 255, 255, 0.7);
|
|
471
483
|
backdrop-filter: blur(1px);
|
|
472
484
|
pointer-events: all;
|
|
473
|
-
border-radius: var(--borderRadiusMedium, 4px);
|
|
474
485
|
}
|
|
475
486
|
|
|
476
487
|
.loadingOverlayContent {
|
|
@@ -1,22 +1,75 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { useInlineCellEditorState } from '@alaarab/ogrid-core';
|
|
3
|
+
import { Select, Checkbox } from '@fluentui/react-components';
|
|
4
|
+
import { useInlineCellEditorState, useRichSelectState } from '@alaarab/ogrid-core';
|
|
5
|
+
// Match cell content layout so column width doesn't shift during editing
|
|
6
|
+
const editorWrapperStyle = {
|
|
7
|
+
width: '100%',
|
|
8
|
+
height: '100%',
|
|
9
|
+
display: 'flex',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
padding: '6px 10px',
|
|
12
|
+
boxSizing: 'border-box',
|
|
13
|
+
overflow: 'hidden',
|
|
14
|
+
minWidth: 0,
|
|
15
|
+
};
|
|
16
|
+
const editorInputStyle = {
|
|
17
|
+
width: '100%',
|
|
18
|
+
padding: 0,
|
|
19
|
+
border: 'none',
|
|
20
|
+
background: 'transparent',
|
|
21
|
+
color: 'inherit',
|
|
22
|
+
font: 'inherit',
|
|
23
|
+
fontSize: '13px',
|
|
24
|
+
outline: 'none',
|
|
25
|
+
minWidth: 0,
|
|
26
|
+
};
|
|
5
27
|
export function InlineCellEditor(props) {
|
|
6
28
|
const { value, column, editorType, onCommit, onCancel } = props;
|
|
7
29
|
const wrapperRef = React.useRef(null);
|
|
8
30
|
const { localValue, setLocalValue, handleKeyDown, handleBlur, commit, cancel } = useInlineCellEditorState({ value, editorType, onCommit, onCancel });
|
|
31
|
+
const richSelectValues = column.cellEditorParams?.values ?? [];
|
|
32
|
+
const richSelectFormatValue = column.cellEditorParams?.formatValue;
|
|
33
|
+
const richSelect = useRichSelectState({
|
|
34
|
+
values: richSelectValues,
|
|
35
|
+
formatValue: richSelectFormatValue,
|
|
36
|
+
initialValue: value,
|
|
37
|
+
onCommit,
|
|
38
|
+
onCancel,
|
|
39
|
+
});
|
|
9
40
|
React.useEffect(() => {
|
|
10
41
|
const input = wrapperRef.current?.querySelector('input');
|
|
11
42
|
input?.focus();
|
|
12
43
|
}, []);
|
|
44
|
+
if (editorType === 'richSelect') {
|
|
45
|
+
return (_jsxs("div", { ref: wrapperRef, style: { ...editorWrapperStyle, position: 'relative' }, children: [_jsx("input", { type: "text", value: richSelect.searchText, onChange: (e) => richSelect.setSearchText(e.target.value), onKeyDown: richSelect.handleKeyDown, placeholder: "Search...", autoFocus: true, style: editorInputStyle }), _jsxs("div", { style: {
|
|
46
|
+
position: 'absolute',
|
|
47
|
+
top: '100%',
|
|
48
|
+
left: 0,
|
|
49
|
+
right: 0,
|
|
50
|
+
maxHeight: 200,
|
|
51
|
+
overflowY: 'auto',
|
|
52
|
+
background: 'var(--ogrid-bg, #fff)',
|
|
53
|
+
border: '1px solid var(--ogrid-border, #ccc)',
|
|
54
|
+
zIndex: 10,
|
|
55
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.2)',
|
|
56
|
+
}, role: "listbox", children: [richSelect.filteredValues.map((v, i) => (_jsx("div", { role: "option", "aria-selected": i === richSelect.highlightedIndex, onClick: () => richSelect.selectValue(v), style: {
|
|
57
|
+
padding: '6px 8px',
|
|
58
|
+
cursor: 'pointer',
|
|
59
|
+
color: 'var(--ogrid-fg, #242424)',
|
|
60
|
+
background: i === richSelect.highlightedIndex ? 'var(--ogrid-bg-hover, #e8f0fe)' : undefined,
|
|
61
|
+
}, children: richSelect.getDisplayText(v) }, String(v)))), richSelect.filteredValues.length === 0 && (_jsx("div", { style: { padding: '6px 8px', color: 'var(--ogrid-muted, #999)' }, children: "No matches" }))] })] }));
|
|
62
|
+
}
|
|
13
63
|
if (editorType === 'checkbox') {
|
|
14
64
|
const checked = value === true;
|
|
15
65
|
return (_jsx(Checkbox, { checked: checked, onChange: (_, data) => commit(data.checked), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), cancel()) }));
|
|
16
66
|
}
|
|
17
67
|
if (editorType === 'select') {
|
|
18
68
|
const values = column.cellEditorParams?.values ?? [];
|
|
19
|
-
return (_jsx(Select, { value: value !== null && value !== undefined ? String(value) : '', onChange: (_, data) => commit(data.value), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), cancel()), children: values.map((v) => (_jsx("option", { value: String(v), children: String(v) }, String(v)))) }));
|
|
69
|
+
return (_jsx("div", { style: editorWrapperStyle, children: _jsx(Select, { value: value !== null && value !== undefined ? String(value) : '', onChange: (_, data) => commit(data.value), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), cancel()), children: values.map((v) => (_jsx("option", { value: String(v), children: String(v) }, String(v)))) }) }));
|
|
70
|
+
}
|
|
71
|
+
if (editorType === 'date') {
|
|
72
|
+
return (_jsx("div", { ref: wrapperRef, style: editorWrapperStyle, children: _jsx("input", { type: "date", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: editorInputStyle, autoFocus: true }) }));
|
|
20
73
|
}
|
|
21
|
-
return (_jsx("div", { ref: wrapperRef, children: _jsx(
|
|
74
|
+
return (_jsx("div", { ref: wrapperRef, style: editorWrapperStyle, children: _jsx("input", { type: "text", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: editorInputStyle, autoFocus: true }) }));
|
|
22
75
|
}
|
|
@@ -6,11 +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, title, toolbar, className, entityLabelPlural, } = useOGrid(props, ref);
|
|
10
|
-
return (_jsx(OGridLayout, { className: className, gap:
|
|
9
|
+
const { dataGridProps, page, pageSize, displayTotalCount, setPage, setPageSize, columnChooserColumns, visibleColumns, handleVisibilityChange, title, toolbar, className, entityLabelPlural, pageSizeOptions, sideBarProps, columnChooserPlacement, } = useOGrid(props, ref);
|
|
10
|
+
return (_jsx(OGridLayout, { className: className, gap: 8, sideBar: sideBarProps, title: title, 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) => {
|
|
11
11
|
setPageSize(size);
|
|
12
12
|
setPage(1);
|
|
13
|
-
}, entityLabelPlural: entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
|
|
13
|
+
}, pageSizeOptions: pageSizeOptions, entityLabelPlural: entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
|
|
14
14
|
});
|
|
15
15
|
OGridInner.displayName = 'OGrid';
|
|
16
16
|
export const OGrid = React.memo(OGridInner);
|
|
@@ -6,9 +6,9 @@ import { ChevronLeftRegular, ChevronRightRegular, ChevronDoubleLeftRegular, Chev
|
|
|
6
6
|
import { getPaginationViewModel } from '@alaarab/ogrid-core';
|
|
7
7
|
import styles from './PaginationControls.module.css';
|
|
8
8
|
export const PaginationControls = React.memo((props) => {
|
|
9
|
-
const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, entityLabelPlural, className } = props;
|
|
9
|
+
const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, pageSizeOptions, entityLabelPlural, className } = props;
|
|
10
10
|
const labelPlural = entityLabelPlural ?? 'items';
|
|
11
|
-
const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount), [currentPage, pageSize, totalCount]);
|
|
11
|
+
const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount, pageSizeOptions ? { pageSizeOptions } : undefined), [currentPage, pageSize, totalCount, pageSizeOptions]);
|
|
12
12
|
const handlePageSizeChange = useCallback((_e, data) => {
|
|
13
13
|
onPageSizeChange(Number(data.value));
|
|
14
14
|
}, [onPageSizeChange]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import type { UserLike, ColumnFilterType } from '@alaarab/ogrid-core';
|
|
2
|
+
import type { UserLike, ColumnFilterType, IDateFilterValue } from '@alaarab/ogrid-core';
|
|
3
3
|
export interface IColumnHeaderFilterProps {
|
|
4
4
|
columnKey: string;
|
|
5
5
|
columnName: string;
|
|
@@ -16,5 +16,7 @@ export interface IColumnHeaderFilterProps {
|
|
|
16
16
|
selectedUser?: UserLike;
|
|
17
17
|
onUserChange?: (user: UserLike | undefined) => void;
|
|
18
18
|
peopleSearch?: (query: string) => Promise<UserLike[]>;
|
|
19
|
+
dateValue?: IDateFilterValue;
|
|
20
|
+
onDateChange?: (value: IDateFilterValue | undefined) => void;
|
|
19
21
|
}
|
|
20
22
|
export declare const ColumnHeaderFilter: React.FC<IColumnHeaderFilterProps>;
|
|
@@ -5,7 +5,7 @@ export interface InlineCellEditorProps<T> {
|
|
|
5
5
|
item: T;
|
|
6
6
|
column: IColumnDef<T>;
|
|
7
7
|
rowIndex: number;
|
|
8
|
-
editorType: 'text' | 'select' | 'checkbox';
|
|
8
|
+
editorType: 'text' | 'select' | 'checkbox' | 'richSelect' | 'date';
|
|
9
9
|
onCommit: (value: unknown) => void;
|
|
10
10
|
onCancel: () => void;
|
|
11
11
|
}
|
|
@@ -4,5 +4,12 @@ export interface StatusBarProps {
|
|
|
4
4
|
filteredCount?: number;
|
|
5
5
|
selectedCount?: number;
|
|
6
6
|
selectedCellCount?: number;
|
|
7
|
+
aggregation?: {
|
|
8
|
+
sum: number;
|
|
9
|
+
avg: number;
|
|
10
|
+
min: number;
|
|
11
|
+
max: number;
|
|
12
|
+
count: number;
|
|
13
|
+
} | null;
|
|
7
14
|
}
|
|
8
15
|
export declare function StatusBar(props: StatusBarProps): React.ReactElement;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-fluent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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",
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@fluentui/react-components": "^9.72.10",
|
|
53
53
|
"@fluentui/react-icons": "^2.0.317",
|
|
54
|
-
"@storybook/react": "
|
|
55
|
-
"@storybook/react-vite": "^8.5.3",
|
|
54
|
+
"@storybook/react-vite": "10.2.8",
|
|
56
55
|
"sass": "^1.83.4",
|
|
57
|
-
"scheduler": "^0.
|
|
58
|
-
"storybook": "
|
|
59
|
-
"
|
|
56
|
+
"scheduler": "^0.27.0",
|
|
57
|
+
"storybook": "10.2.8",
|
|
58
|
+
"eslint-plugin-storybook": "10.2.8",
|
|
59
|
+
"vite": "^7.0.0"
|
|
60
60
|
},
|
|
61
61
|
"publishConfig": {
|
|
62
62
|
"access": "public"
|