@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.
@@ -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, Spinner } from '@fluentui/react-components';
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
- export const MultiSelectFilterPopover = ({ searchText, onSearchChange, options, filteredOptions, selected, onOptionToggle, onSelectAll, onClearSelection, onApply, isLoading, onPopoverClick, onInputFocus, onInputMouseDown, onInputClick, onInputKeyDown, }) => (_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", { className: styles.popoverOptions, onClick: onPopoverClick, children: isLoading ? (_jsx("div", { className: styles.loadingContainer, children: _jsx(Spinner, { size: "small", label: "Loading..." }) })) : filteredOptions.length === 0 ? (_jsx("div", { className: styles.noResults, children: "No options found" })) : (filteredOptions.map((option) => (_jsx("div", { className: styles.popoverOption, children: _jsx(Checkbox, { label: option, checked: selected.has(option), onChange: (ev, data) => {
6
- ev.stopPropagation();
7
- onOptionToggle(option, data.checked === true);
8
- } }) }, option)))) }), _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" })] })] }));
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 { Spinner, Avatar } from '@fluentui/react-components';
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() ? (_jsx("div", { className: styles.loadingContainer, children: _jsx(Spinner, { size: "small", label: "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) => {
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, freezeRows, freezeCols, 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;
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, freezeCols, pinning.pinnedColumns, pinning.leftOffsets, pinning.rightOffsets]);
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-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, onKeyDown: handleGridKeyDown, style: {
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(Spinner, { size: "small" }), _jsx("span", { className: styles.loadingOverlayText, children: message })] }) }));
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.17",
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.17"
43
+ "@alaarab/ogrid-react": "2.0.19"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@fluentui/react-components": "^9.0.0",