@alaarab/ogrid-react-fluent 2.0.17 → 2.0.19
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.module.css +18 -1
- package/dist/esm/ColumnHeaderFilter/MultiSelectFilterPopover.js +13 -5
- package/dist/esm/ColumnHeaderFilter/PeopleFilterPopover.js +2 -2
- package/dist/esm/DataGridTable/DataGridTable.js +3 -8
- package/dist/esm/DataGridTable/DataGridTable.module.css +0 -16
- package/dist/esm/DataGridTable/LoadingOverlay.js +1 -2
- package/package.json +2 -2
|
@@ -270,10 +270,27 @@
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
.loadingContainer {
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
justify-content: center;
|
|
276
|
+
gap: 8px;
|
|
273
277
|
padding: 20px;
|
|
274
|
-
text-align: center;
|
|
275
278
|
}
|
|
276
279
|
|
|
280
|
+
.filterSpinner {
|
|
281
|
+
width: 20px;
|
|
282
|
+
height: 20px;
|
|
283
|
+
border: 2px solid var(--colorNeutralStroke1, #d1d1d1);
|
|
284
|
+
border-top-color: var(--colorBrandBackground, #0f6cbd);
|
|
285
|
+
border-radius: 50%;
|
|
286
|
+
animation: ogrid-filter-spin 0.8s linear infinite;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
@keyframes ogrid-filter-spin {
|
|
290
|
+
to {
|
|
291
|
+
transform: rotate(360deg);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
277
294
|
.noResults {
|
|
278
295
|
padding: 16px;
|
|
279
296
|
text-align: center;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Input, Checkbox
|
|
2
|
+
import { Input, Checkbox } from '@fluentui/react-components';
|
|
3
3
|
import { SearchRegular } from '@fluentui/react-icons';
|
|
4
|
+
import { useListVirtualizer } from '@alaarab/ogrid-react';
|
|
4
5
|
import styles from './ColumnHeaderFilter.module.css';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const ITEM_HEIGHT = 40;
|
|
7
|
+
export const MultiSelectFilterPopover = ({ searchText, onSearchChange, options, filteredOptions, selected, onOptionToggle, onSelectAll, onClearSelection, onApply, isLoading, onPopoverClick, onInputFocus, onInputMouseDown, onInputClick, onInputKeyDown, }) => {
|
|
8
|
+
const virt = useListVirtualizer({ count: filteredOptions.length, itemHeight: ITEM_HEIGHT });
|
|
9
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.popoverSearch, onClick: onPopoverClick, children: [_jsx(Input, { placeholder: "Search...", value: searchText, onChange: (e, data) => onSearchChange(data.value ?? ''), onFocus: onInputFocus, onMouseDown: onInputMouseDown, onClick: onInputClick, onKeyDown: onInputKeyDown, autoComplete: "off", className: styles.searchInput, contentBefore: _jsx(SearchRegular, {}) }), _jsxs("div", { className: styles.resultCount, children: [filteredOptions.length, " of ", options.length, " options"] })] }), _jsxs("div", { className: styles.selectAllRow, onClick: onPopoverClick, children: [_jsxs("button", { type: "button", className: styles.selectAllButton, onClick: onSelectAll, children: ["Select All (", filteredOptions.length, ")"] }), _jsx("button", { type: "button", className: styles.selectAllButton, onClick: onClearSelection, children: "Clear" })] }), _jsx("div", { ref: virt.containerRef, onScroll: virt.onScroll, className: styles.popoverOptions, onClick: onPopoverClick, children: isLoading ? (_jsxs("div", { className: styles.loadingContainer, children: [_jsx("div", { className: styles.filterSpinner }), _jsx("span", { style: { fontSize: 12, color: 'var(--colorNeutralForeground2, #616161)' }, children: "Loading..." })] })) : filteredOptions.length === 0 ? (_jsx("div", { className: styles.noResults, children: "No options found" })) : (_jsx("div", { style: { height: virt.totalHeight, position: 'relative' }, children: virt.visibleItems.map(({ index, offsetTop }) => {
|
|
10
|
+
const option = filteredOptions[index];
|
|
11
|
+
return (_jsx("div", { className: styles.popoverOption, style: { position: 'absolute', top: offsetTop, width: '100%', height: ITEM_HEIGHT, boxSizing: 'border-box', display: 'flex', alignItems: 'center' }, children: _jsx(Checkbox, { label: option, checked: selected.has(option), onChange: (ev, data) => {
|
|
12
|
+
ev.stopPropagation();
|
|
13
|
+
onOptionToggle(option, data.checked === true);
|
|
14
|
+
} }) }, option));
|
|
15
|
+
}) })) }), _jsxs("div", { className: styles.popoverActions, onClick: onPopoverClick, children: [_jsx("button", { type: "button", className: styles.clearButton, onClick: onClearSelection, children: "Clear" }), _jsx("button", { type: "button", className: styles.applyButton, onClick: onApply, children: "Apply" })] })] }));
|
|
16
|
+
};
|
|
9
17
|
MultiSelectFilterPopover.displayName = 'MultiSelectFilterPopover';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Avatar } from '@fluentui/react-components';
|
|
3
3
|
import { SearchRegular, FilterRegular } from '@fluentui/react-icons';
|
|
4
4
|
import styles from './ColumnHeaderFilter.module.css';
|
|
5
|
-
export const PeopleFilterPopover = ({ selectedUser, searchText, onSearchChange, suggestions, isLoading, onUserSelect, onClearUser, onPopoverClick, inputRef, }) => (_jsxs(_Fragment, { children: [selectedUser && (_jsxs("div", { className: styles.selectedUserSection, onClick: onPopoverClick, children: [_jsx("div", { className: styles.selectedUserLabel, children: "Currently filtered by:" }), _jsxs("div", { className: styles.selectedUser, children: [_jsxs("div", { className: styles.userInfo, children: [_jsx(Avatar, { name: selectedUser.displayName, image: { src: selectedUser.photo }, size: 32 }), _jsxs("div", { className: styles.userText, children: [_jsx("div", { children: selectedUser.displayName }), _jsx("div", { className: styles.userSecondary, children: selectedUser.email })] })] }), _jsx("button", { type: "button", className: styles.removeUserButton, onClick: onClearUser, "aria-label": "Remove filter", children: _jsx(FilterRegular, {}) })] })] })), _jsx("div", { className: styles.popoverSearch, onClick: onPopoverClick, children: _jsxs("div", { className: styles.nativeInputWrapper, children: [_jsx(SearchRegular, { className: styles.nativeInputIcon }), _jsx("input", { ref: inputRef, type: "text", placeholder: "Search for a person...", value: searchText, onChange: (e) => onSearchChange(e.target.value), onFocus: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), autoComplete: "off", className: styles.nativeInput })] }) }), _jsx("div", { className: styles.popoverOptions, onClick: onPopoverClick, children: isLoading && searchText.trim() ? (
|
|
5
|
+
export const PeopleFilterPopover = ({ selectedUser, searchText, onSearchChange, suggestions, isLoading, onUserSelect, onClearUser, onPopoverClick, inputRef, }) => (_jsxs(_Fragment, { children: [selectedUser && (_jsxs("div", { className: styles.selectedUserSection, onClick: onPopoverClick, children: [_jsx("div", { className: styles.selectedUserLabel, children: "Currently filtered by:" }), _jsxs("div", { className: styles.selectedUser, children: [_jsxs("div", { className: styles.userInfo, children: [_jsx(Avatar, { name: selectedUser.displayName, image: { src: selectedUser.photo }, size: 32 }), _jsxs("div", { className: styles.userText, children: [_jsx("div", { children: selectedUser.displayName }), _jsx("div", { className: styles.userSecondary, children: selectedUser.email })] })] }), _jsx("button", { type: "button", className: styles.removeUserButton, onClick: onClearUser, "aria-label": "Remove filter", children: _jsx(FilterRegular, {}) })] })] })), _jsx("div", { className: styles.popoverSearch, onClick: onPopoverClick, children: _jsxs("div", { className: styles.nativeInputWrapper, children: [_jsx(SearchRegular, { className: styles.nativeInputIcon }), _jsx("input", { ref: inputRef, type: "text", placeholder: "Search for a person...", value: searchText, onChange: (e) => onSearchChange(e.target.value), onFocus: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), autoComplete: "off", className: styles.nativeInput })] }) }), _jsx("div", { className: styles.popoverOptions, onClick: onPopoverClick, children: isLoading && searchText.trim() ? (_jsxs("div", { className: styles.loadingContainer, children: [_jsx("div", { className: styles.filterSpinner }), _jsx("span", { style: { fontSize: 12, color: 'var(--colorNeutralForeground2, #616161)' }, children: "Searching..." })] })) : suggestions.length === 0 && searchText.trim() ? (_jsx("div", { className: styles.noResults, children: "No results found" })) : searchText.trim() ? (suggestions.map((user) => (_jsx("div", { className: styles.personOption, onClick: (e) => {
|
|
6
6
|
e.stopPropagation();
|
|
7
7
|
onUserSelect(user);
|
|
8
8
|
}, children: _jsxs("div", { className: styles.userInfo, children: [_jsx(Avatar, { name: user.displayName, image: { src: user.photo }, size: 32 }), _jsxs("div", { className: styles.userText, children: [_jsx("div", { children: user.displayName }), _jsx("div", { className: styles.userSecondary, children: user.email })] })] }) }, user.id ?? user.email ?? user.displayName)))) : (_jsx("div", { className: styles.noResults, children: "Type to search..." })) }), selectedUser && (_jsx("div", { className: styles.popoverActions, onClick: onPopoverClick, children: _jsx("button", { type: "button", className: styles.clearButton, onClick: onClearUser, children: "Clear Filter" }) }))] }));
|
|
@@ -22,7 +22,7 @@ function GridRowInner(props) {
|
|
|
22
22
|
const GridRow = React.memo(GridRowInner, areGridRowPropsEqual);
|
|
23
23
|
function DataGridTableInner(props) {
|
|
24
24
|
const o = useDataGridTableOrchestration({ props });
|
|
25
|
-
const { wrapperRef, tableContainerRef, lastMouseShiftRef, interaction, pinning, handleResizeStart, getColumnWidth, isReorderDragging, dropIndicatorX, handleHeaderMouseDown, virtualScrollEnabled, visibleRange, items, getRowId, emptyState, rowSelection,
|
|
25
|
+
const { wrapperRef, tableContainerRef, lastMouseShiftRef, interaction, pinning, handleResizeStart, getColumnWidth, isReorderDragging, dropIndicatorX, handleHeaderMouseDown, virtualScrollEnabled, visibleRange, items, getRowId, emptyState, rowSelection, isLoading, loadingMessage, ariaLabel, ariaLabelledBy, visibleColumns, columnOrder, columnReorder, density, rowNumberOffset, headerRows, allowOverflowX, fitToContent, editCallbacks, interactionHandlers, cellDescriptorInputRef, pendingEditorValueRef, popoverAnchorElRef, handleSingleRowClick, handlePasteVoid, visibleCols, totalColCount, hasCheckboxCol, hasRowNumbersCol, colOffset, containerWidth, minTableWidth, columnSizingOverrides, measuredColumnWidths, selectedRowIds, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, editingCell, setPopoverAnchorEl, cancelPopoverEdit, setActiveCell, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, cutRange, copyRange, canUndo, canRedo, onUndo, onRedo, isDragging, menuPosition, closeContextMenu, headerFilterInput, statusBarConfig, showEmptyInGrid, onCellError, headerMenu, } = o;
|
|
26
26
|
// Pre-compute column styles and classNames (avoids per-cell object creation in the row loop)
|
|
27
27
|
const columnMeta = useMemo(() => {
|
|
28
28
|
const cellStyles = {};
|
|
@@ -33,7 +33,6 @@ function DataGridTableInner(props) {
|
|
|
33
33
|
const col = visibleCols[i];
|
|
34
34
|
const columnWidth = getColumnWidth(col);
|
|
35
35
|
const hasExplicitWidth = !!(columnSizingOverrides[col.columnId] || col.idealWidth != null || col.defaultWidth != null);
|
|
36
|
-
const isFreezeCol = freezeCols != null && freezeCols >= 1 && i < freezeCols;
|
|
37
36
|
const isPinnedLeft = pinning.pinnedColumns[col.columnId] === 'left';
|
|
38
37
|
const isPinnedRight = pinning.pinnedColumns[col.columnId] === 'right';
|
|
39
38
|
const hasResizeOverride = !!columnSizingOverrides[col.columnId];
|
|
@@ -63,10 +62,6 @@ function DataGridTableInner(props) {
|
|
|
63
62
|
...(isPinnedRight && pinning.rightOffsets[col.columnId] != null ? { right: pinning.rightOffsets[col.columnId] } : undefined),
|
|
64
63
|
};
|
|
65
64
|
const parts = [];
|
|
66
|
-
if (isFreezeCol)
|
|
67
|
-
parts.push(styles.freezeCol);
|
|
68
|
-
if (isFreezeCol && i === 0)
|
|
69
|
-
parts.push(styles.freezeColFirst);
|
|
70
65
|
if (isPinnedLeft)
|
|
71
66
|
parts.push(styles.pinnedColLeft);
|
|
72
67
|
if (isPinnedRight)
|
|
@@ -76,7 +71,7 @@ function DataGridTableInner(props) {
|
|
|
76
71
|
hdrClasses[col.columnId] = cn;
|
|
77
72
|
}
|
|
78
73
|
return { cellStyles, cellClasses, hdrStyles, hdrClasses };
|
|
79
|
-
}, [visibleCols, getColumnWidth, columnSizingOverrides, measuredColumnWidths,
|
|
74
|
+
}, [visibleCols, getColumnWidth, columnSizingOverrides, measuredColumnWidths, pinning.pinnedColumns, pinning.leftOffsets, pinning.rightOffsets]);
|
|
80
75
|
// renderCellContent reads volatile state from refs -- keeps function identity stable so
|
|
81
76
|
// GridRow's React.memo comparator can skip rows whose selection state hasn't changed.
|
|
82
77
|
const renderCellContent = useCallback((item, col, rowIndex, colIdx) => {
|
|
@@ -105,7 +100,7 @@ function DataGridTableInner(props) {
|
|
|
105
100
|
},
|
|
106
101
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- *Ref vars are stable refs from useLatestRef
|
|
107
102
|
[editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit, getRowId, onCellError]);
|
|
108
|
-
return (_jsxs("div", { style: GRID_ROOT_STYLE, children: [_jsxs("div", { ref: wrapperRef, tabIndex: 0, onMouseDown: (e) => { lastMouseShiftRef.current = e.shiftKey; }, 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-loading": isLoading && items.length === 0 ? 'true' : undefined, "data-column-count": totalColCount, "data-
|
|
103
|
+
return (_jsxs("div", { style: GRID_ROOT_STYLE, children: [_jsxs("div", { ref: wrapperRef, tabIndex: 0, onMouseDown: (e) => { lastMouseShiftRef.current = e.shiftKey; }, 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-loading": isLoading && items.length === 0 ? 'true' : undefined, "data-column-count": totalColCount, "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, onKeyDown: handleGridKeyDown, style: {
|
|
109
104
|
['--data-table-column-count']: totalColCount,
|
|
110
105
|
['--data-table-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'fit-content' : fitToContent ? 'fit-content' : '100%',
|
|
111
106
|
['--data-table-min-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'max-content' : fitToContent ? 'max-content' : '100%',
|
|
@@ -159,13 +159,6 @@
|
|
|
159
159
|
pointer-events: none;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
.freezeColFirst {
|
|
163
|
-
position: sticky;
|
|
164
|
-
left: 0;
|
|
165
|
-
z-index: 6;
|
|
166
|
-
background: var(--ogrid-bg, #ffffff);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
162
|
.cellContent {
|
|
170
163
|
width: 100%;
|
|
171
164
|
height: 100%;
|
|
@@ -661,15 +654,6 @@
|
|
|
661
654
|
background-color: var(--colorSubtleBackgroundSelected, var(--ogrid-header-bg, #f3f2f1));
|
|
662
655
|
}
|
|
663
656
|
|
|
664
|
-
.freezeColFirst {
|
|
665
|
-
background-color: var(--colorNeutralBackground1, #ffffff);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
.dataTable :global(.fui-TableHeader) .freezeColFirst {
|
|
669
|
-
background-color: var(--colorSubtleBackgroundSelected, var(--ogrid-header-bg, #f3f2f1));
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
.stickyHeader .freezeColFirst,
|
|
673
657
|
.stickyHeader .pinnedColLeft,
|
|
674
658
|
.stickyHeader .pinnedColRight {
|
|
675
659
|
top: 0;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Spinner } from '@fluentui/react-components';
|
|
3
2
|
import styles from './DataGridTable.module.css';
|
|
4
3
|
export function LoadingOverlay({ message }) {
|
|
5
|
-
return (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx(
|
|
4
|
+
return (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx("div", { className: styles.spinner }), _jsx("span", { className: styles.loadingOverlayText, children: message })] }) }));
|
|
6
5
|
}
|
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.19",
|
|
4
4
|
"description": "OGrid React Fluent 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.19"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@fluentui/react-components": "^9.0.0",
|