@alaarab/ogrid-react 2.0.23 → 2.1.1
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/components/ColumnHeaderFilterContent.js +0 -2
- package/dist/esm/components/MarchingAntsOverlay.js +2 -3
- package/dist/esm/components/SideBar.js +8 -7
- package/dist/esm/components/createOGrid.js +1 -4
- package/dist/esm/hooks/index.js +10 -0
- package/dist/esm/hooks/useActiveCell.js +2 -4
- package/dist/esm/hooks/useCellSelection.js +85 -52
- package/dist/esm/hooks/useClipboard.js +15 -54
- package/dist/esm/hooks/useColumnChooserState.js +25 -13
- package/dist/esm/hooks/useColumnHeaderFilterState.js +22 -11
- package/dist/esm/hooks/useColumnHeaderMenuState.js +1 -1
- package/dist/esm/hooks/useColumnMeta.js +61 -0
- package/dist/esm/hooks/useColumnPinning.js +11 -12
- package/dist/esm/hooks/useColumnReorder.js +8 -1
- package/dist/esm/hooks/useColumnResize.js +6 -2
- package/dist/esm/hooks/useDataGridContextMenu.js +24 -0
- package/dist/esm/hooks/useDataGridEditing.js +56 -0
- package/dist/esm/hooks/useDataGridInteraction.js +109 -0
- package/dist/esm/hooks/useDataGridLayout.js +172 -0
- package/dist/esm/hooks/useDataGridState.js +83 -318
- package/dist/esm/hooks/useDataGridTableOrchestration.js +2 -4
- package/dist/esm/hooks/useFillHandle.js +60 -55
- package/dist/esm/hooks/useFilterOptions.js +21 -6
- package/dist/esm/hooks/useInlineCellEditorState.js +7 -13
- package/dist/esm/hooks/useKeyboardNavigation.js +19 -132
- package/dist/esm/hooks/useMultiSelectFilterState.js +1 -1
- package/dist/esm/hooks/useOGrid.js +158 -301
- package/dist/esm/hooks/useOGridDataFetching.js +74 -0
- package/dist/esm/hooks/useOGridFilters.js +59 -0
- package/dist/esm/hooks/useOGridPagination.js +24 -0
- package/dist/esm/hooks/useOGridSorting.js +24 -0
- package/dist/esm/hooks/usePaginationControls.js +2 -5
- package/dist/esm/hooks/usePeopleFilterState.js +6 -1
- package/dist/esm/hooks/useRichSelectState.js +7 -5
- package/dist/esm/hooks/useRowSelection.js +6 -26
- package/dist/esm/hooks/useSelectState.js +2 -5
- package/dist/esm/hooks/useShallowEqualMemo.js +14 -0
- package/dist/esm/hooks/useTableLayout.js +3 -11
- package/dist/esm/hooks/useUndoRedo.js +16 -10
- package/dist/esm/index.js +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/dist/types/components/ColumnChooserProps.d.ts +2 -0
- package/dist/types/components/ColumnHeaderFilterContent.d.ts +0 -2
- package/dist/types/hooks/index.d.ts +19 -0
- package/dist/types/hooks/useClipboard.d.ts +0 -1
- package/dist/types/hooks/useColumnChooserState.d.ts +2 -0
- package/dist/types/hooks/useColumnHeaderFilterState.d.ts +0 -2
- package/dist/types/hooks/useColumnHeaderMenuState.d.ts +0 -2
- package/dist/types/hooks/useColumnMeta.d.ts +34 -0
- package/dist/types/hooks/useDataGridContextMenu.d.ts +20 -0
- package/dist/types/hooks/useDataGridEditing.d.ts +39 -0
- package/dist/types/hooks/useDataGridInteraction.d.ts +95 -0
- package/dist/types/hooks/useDataGridLayout.d.ts +45 -0
- package/dist/types/hooks/useDataGridState.d.ts +7 -1
- package/dist/types/hooks/useDataGridTableOrchestration.d.ts +1 -2
- package/dist/types/hooks/useOGrid.d.ts +4 -2
- package/dist/types/hooks/useOGridDataFetching.d.ts +28 -0
- package/dist/types/hooks/useOGridFilters.d.ts +24 -0
- package/dist/types/hooks/useOGridPagination.d.ts +18 -0
- package/dist/types/hooks/useOGridSorting.d.ts +23 -0
- package/dist/types/hooks/usePaginationControls.d.ts +1 -1
- package/dist/types/hooks/useRichSelectState.d.ts +2 -0
- package/dist/types/hooks/useShallowEqualMemo.d.ts +7 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/utils/index.d.ts +2 -2
- package/package.json +12 -4
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
import { useMemo, useCallback, useState, useEffect, useRef,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { useMemo, useCallback, useState, useImperativeHandle, useEffect, useRef, } from 'react';
|
|
2
|
+
import { flattenColumns } from '../utils';
|
|
3
|
+
import { validateColumns, validateRowIds } from '@alaarab/ogrid-core';
|
|
4
|
+
import { useOGridPagination } from './useOGridPagination';
|
|
5
|
+
import { useOGridSorting } from './useOGridSorting';
|
|
6
|
+
import { useOGridFilters } from './useOGridFilters';
|
|
7
|
+
import { useOGridDataFetching } from './useOGridDataFetching';
|
|
8
|
+
import { useLatestRef } from './useLatestRef';
|
|
4
9
|
import { useSideBarState } from './useSideBarState';
|
|
5
10
|
const DEFAULT_PAGE_SIZE = 25;
|
|
6
11
|
const EMPTY_LOADING_OPTIONS = {};
|
|
7
12
|
/**
|
|
8
13
|
* Top-level orchestration hook for OGrid: manages pagination, sorting, filtering, column visibility, and sidebar.
|
|
14
|
+
* Delegates to focused sub-hooks for each concern.
|
|
9
15
|
* @param props - All OGrid props (columns, data, callbacks, feature flags).
|
|
10
16
|
* @param ref - Forwarded ref for imperative API (refresh, export, applyColumnState).
|
|
11
17
|
* @returns Grouped props for DataGridTable, pagination controls, column chooser, layout, and filters.
|
|
12
18
|
*/
|
|
13
19
|
export function useOGrid(props, ref) {
|
|
14
20
|
const { columns: columnsProp, getRowId, data, dataSource, page: controlledPage, pageSize: controlledPageSize, sort: controlledSort, filters: controlledFilters, visibleColumns: controlledVisibleColumns, isLoading: controlledLoading, onPageChange, onPageSizeChange, onSortChange, onFiltersChange, onVisibleColumnsChange, columnOrder, onColumnOrderChange, onColumnResized, onColumnPinned, defaultPageSize = DEFAULT_PAGE_SIZE, defaultSortBy, defaultSortDirection = 'asc', toolbar, toolbarBelow, emptyState, entityLabelPlural = 'items', className, layoutMode = 'fill', suppressHorizontalScroll, editable, cellSelection, onCellValueChanged, onUndo, onRedo, canUndo, canRedo, rowSelection = 'none', selectedRows, onSelectionChange, showRowNumbers, statusBar, pageSizeOptions, sideBar, onFirstDataRendered, onError, columnChooser: columnChooserProp, columnReorder, virtualScroll, rowHeight, density = 'normal', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
15
|
-
//
|
|
21
|
+
// --- Derived column state ---
|
|
16
22
|
const columnChooserPlacement = columnChooserProp === false ? 'none'
|
|
17
23
|
: columnChooserProp === 'sidebar' ? 'sidebar'
|
|
18
24
|
: 'toolbar';
|
|
19
25
|
const columns = useMemo(() => flattenColumns(columnsProp), [columnsProp]);
|
|
20
26
|
const isServerSide = dataSource != null;
|
|
21
|
-
|
|
27
|
+
// --- Runtime validation (dev-only, runs once on mount) ---
|
|
28
|
+
const rowIdsValidatedRef = useRef(false);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
validateColumns(columns);
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
|
+
}, []); // intentionally empty — run once at mount
|
|
33
|
+
const defaultSortField = defaultSortBy ?? columns[0]?.columnId ?? '';
|
|
34
|
+
// --- Internal data state (for imperative setRowData/setLoading API) ---
|
|
22
35
|
const [internalData, setInternalData] = useState([]);
|
|
23
36
|
const [internalLoading, setInternalLoading] = useState(false);
|
|
24
37
|
const displayData = data ?? internalData;
|
|
25
38
|
const displayLoading = controlledLoading ?? internalLoading;
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
// --- Sub-hooks ---
|
|
40
|
+
const paginationState = useOGridPagination({
|
|
41
|
+
controlledPage, controlledPageSize, defaultPageSize,
|
|
42
|
+
onPageChange, onPageSizeChange,
|
|
43
|
+
});
|
|
44
|
+
const sortingState = useOGridSorting({
|
|
45
|
+
controlledSort, defaultSortField, defaultSortDirection,
|
|
46
|
+
onSortChange, setPage: paginationState.setPage,
|
|
47
|
+
});
|
|
48
|
+
const filtersState = useOGridFilters({
|
|
49
|
+
controlledFilters, onFiltersChange,
|
|
50
|
+
setPage: paginationState.setPage,
|
|
51
|
+
columns, displayData, dataSource,
|
|
32
52
|
});
|
|
33
|
-
const
|
|
53
|
+
const dataFetchingState = useOGridDataFetching({
|
|
54
|
+
isServerSide, dataSource, displayData, columns,
|
|
55
|
+
stableFilters: filtersState.stableFilters,
|
|
56
|
+
sort: sortingState.sort,
|
|
57
|
+
page: paginationState.page,
|
|
58
|
+
pageSize: paginationState.pageSize,
|
|
59
|
+
onError, onFirstDataRendered,
|
|
60
|
+
});
|
|
61
|
+
// Validate row IDs once on first data render
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const items = dataFetchingState.displayItems;
|
|
64
|
+
if (!rowIdsValidatedRef.current && items.length > 0) {
|
|
65
|
+
rowIdsValidatedRef.current = true;
|
|
66
|
+
validateRowIds(items, getRowId);
|
|
67
|
+
}
|
|
68
|
+
}, [dataFetchingState.displayItems, getRowId]);
|
|
69
|
+
// --- Column visibility ---
|
|
34
70
|
const [internalVisibleColumns, setInternalVisibleColumns] = useState(() => {
|
|
35
71
|
const visible = columns
|
|
36
72
|
.filter((c) => c.defaultVisible !== false)
|
|
37
73
|
.map((c) => c.columnId);
|
|
38
74
|
return new Set(visible.length > 0 ? visible : columns.map((c) => c.columnId));
|
|
39
75
|
});
|
|
40
|
-
const [columnWidthOverrides, setColumnWidthOverrides] = useState({});
|
|
41
|
-
const [pinnedOverrides, setPinnedOverrides] = useState({});
|
|
42
|
-
const page = controlledPage ?? internalPage;
|
|
43
|
-
const pageSize = controlledPageSize ?? internalPageSize;
|
|
44
|
-
const sort = controlledSort ?? internalSort;
|
|
45
|
-
const filters = controlledFilters ?? internalFilters;
|
|
46
76
|
const visibleColumns = controlledVisibleColumns ?? internalVisibleColumns;
|
|
47
|
-
const setPage = useCallback((p) => {
|
|
48
|
-
if (controlledPage === undefined)
|
|
49
|
-
setInternalPage(p);
|
|
50
|
-
onPageChange?.(p);
|
|
51
|
-
}, [controlledPage, onPageChange]);
|
|
52
|
-
const setPageSize = useCallback((size) => {
|
|
53
|
-
if (controlledPageSize === undefined)
|
|
54
|
-
setInternalPageSize(size);
|
|
55
|
-
onPageSizeChange?.(size);
|
|
56
|
-
setPage(1);
|
|
57
|
-
}, [controlledPageSize, onPageSizeChange, setPage]);
|
|
58
|
-
const setSort = useCallback((s) => {
|
|
59
|
-
if (controlledSort === undefined)
|
|
60
|
-
setInternalSort(s);
|
|
61
|
-
onSortChange?.(s);
|
|
62
|
-
setPage(1);
|
|
63
|
-
}, [controlledSort, onSortChange, setPage]);
|
|
64
|
-
const setFilters = useCallback((f) => {
|
|
65
|
-
if (controlledFilters === undefined)
|
|
66
|
-
setInternalFilters(f);
|
|
67
|
-
onFiltersChange?.(f);
|
|
68
|
-
setPage(1);
|
|
69
|
-
}, [controlledFilters, onFiltersChange, setPage]);
|
|
70
77
|
const setVisibleColumns = useCallback((cols) => {
|
|
71
78
|
if (controlledVisibleColumns === undefined)
|
|
72
79
|
setInternalVisibleColumns(cols);
|
|
73
80
|
onVisibleColumnsChange?.(cols);
|
|
74
81
|
}, [controlledVisibleColumns, onVisibleColumnsChange]);
|
|
75
|
-
const handleSort = useCallback((columnKey, direction) => {
|
|
76
|
-
setSort(computeNextSortState(sort, columnKey, direction));
|
|
77
|
-
}, [sort, setSort]);
|
|
78
|
-
/** Single filter change handler — wraps discriminated FilterValue into mergeFilter. */
|
|
79
|
-
const handleFilterChange = useCallback((key, value) => {
|
|
80
|
-
setFilters(mergeFilter(filters, key, value));
|
|
81
|
-
}, [filters, setFilters]);
|
|
82
82
|
const handleVisibilityChange = useCallback((columnKey, isVisible) => {
|
|
83
83
|
const next = new Set(visibleColumns);
|
|
84
84
|
if (isVisible)
|
|
@@ -87,6 +87,7 @@ export function useOGrid(props, ref) {
|
|
|
87
87
|
next.delete(columnKey);
|
|
88
88
|
setVisibleColumns(next);
|
|
89
89
|
}, [visibleColumns, setVisibleColumns]);
|
|
90
|
+
// --- Row selection ---
|
|
90
91
|
const [internalSelectedRows, setInternalSelectedRows] = useState(new Set());
|
|
91
92
|
const effectiveSelectedRows = selectedRows ?? internalSelectedRows;
|
|
92
93
|
const handleSelectionChange = useCallback((event) => {
|
|
@@ -95,117 +96,34 @@ export function useOGrid(props, ref) {
|
|
|
95
96
|
}
|
|
96
97
|
onSelectionChange?.(event);
|
|
97
98
|
}, [selectedRows, onSelectionChange]);
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const stableFiltersRef = useRef(filters);
|
|
111
|
-
const stableFilters = useMemo(() => {
|
|
112
|
-
const prev = stableFiltersRef.current;
|
|
113
|
-
const prevKeys = Object.keys(prev);
|
|
114
|
-
const nextKeys = Object.keys(filters);
|
|
115
|
-
if (prevKeys.length !== nextKeys.length) {
|
|
116
|
-
stableFiltersRef.current = filters;
|
|
117
|
-
return filters;
|
|
118
|
-
}
|
|
119
|
-
for (let i = 0; i < nextKeys.length; i++) {
|
|
120
|
-
if (prev[nextKeys[i]] !== filters[nextKeys[i]]) {
|
|
121
|
-
stableFiltersRef.current = filters;
|
|
122
|
-
return filters;
|
|
99
|
+
// --- Column resize & pin ---
|
|
100
|
+
const [columnWidthOverrides, setColumnWidthOverrides] = useState({});
|
|
101
|
+
const [pinnedOverrides, setPinnedOverrides] = useState({});
|
|
102
|
+
const handleColumnResized = useCallback((columnId, width) => {
|
|
103
|
+
setColumnWidthOverrides((prev) => ({ ...prev, [columnId]: width }));
|
|
104
|
+
onColumnResized?.(columnId, width);
|
|
105
|
+
}, [onColumnResized]);
|
|
106
|
+
const handleColumnPinned = useCallback((columnId, pinned) => {
|
|
107
|
+
setPinnedOverrides((prev) => {
|
|
108
|
+
if (pinned === null) {
|
|
109
|
+
const { [columnId]: _, ...rest } = prev;
|
|
110
|
+
return rest;
|
|
123
111
|
}
|
|
124
|
-
|
|
125
|
-
return prev;
|
|
126
|
-
}, [filters]);
|
|
127
|
-
const clientItemsAndTotal = useMemo(() => {
|
|
128
|
-
if (!isClientSide)
|
|
129
|
-
return null;
|
|
130
|
-
const rows = processClientSideData(displayData, columns, stableFilters, sort.field, sort.direction);
|
|
131
|
-
const total = rows.length;
|
|
132
|
-
const start = (page - 1) * pageSize;
|
|
133
|
-
const paged = rows.slice(start, start + pageSize);
|
|
134
|
-
return { items: paged, totalCount: total };
|
|
135
|
-
}, [
|
|
136
|
-
isClientSide,
|
|
137
|
-
displayData,
|
|
138
|
-
columns,
|
|
139
|
-
stableFilters,
|
|
140
|
-
sort.field,
|
|
141
|
-
sort.direction,
|
|
142
|
-
page,
|
|
143
|
-
pageSize,
|
|
144
|
-
]);
|
|
145
|
-
const [serverItems, setServerItems] = useState([]);
|
|
146
|
-
const [serverTotalCount, setServerTotalCount] = useState(0);
|
|
147
|
-
const [loading, setLoading] = useState(true);
|
|
148
|
-
const fetchIdRef = useRef(0);
|
|
149
|
-
// Ref counter to trigger server-side re-fetches
|
|
150
|
-
const refreshCounterRef = useRef(0);
|
|
151
|
-
const [refreshCounter, setRefreshCounter] = useState(0);
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
if (!isServerSide || !dataSource) {
|
|
154
|
-
if (!isServerSide)
|
|
155
|
-
setLoading(false);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const id = ++fetchIdRef.current;
|
|
159
|
-
setLoading(true);
|
|
160
|
-
dataSource
|
|
161
|
-
.fetchPage({
|
|
162
|
-
page,
|
|
163
|
-
pageSize,
|
|
164
|
-
sort: { field: sort.field, direction: sort.direction },
|
|
165
|
-
filters,
|
|
166
|
-
})
|
|
167
|
-
.then((res) => {
|
|
168
|
-
if (id !== fetchIdRef.current)
|
|
169
|
-
return;
|
|
170
|
-
setServerItems(res.items);
|
|
171
|
-
setServerTotalCount(res.totalCount);
|
|
172
|
-
})
|
|
173
|
-
.catch((err) => {
|
|
174
|
-
if (id !== fetchIdRef.current)
|
|
175
|
-
return;
|
|
176
|
-
onError?.(err);
|
|
177
|
-
setServerItems([]);
|
|
178
|
-
setServerTotalCount(0);
|
|
179
|
-
})
|
|
180
|
-
.finally(() => {
|
|
181
|
-
if (id === fetchIdRef.current)
|
|
182
|
-
setLoading(false);
|
|
112
|
+
return { ...prev, [columnId]: pinned };
|
|
183
113
|
});
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
: serverItems;
|
|
198
|
-
const displayTotalCount = isClientSide && clientItemsAndTotal
|
|
199
|
-
? clientItemsAndTotal.totalCount
|
|
200
|
-
: serverTotalCount;
|
|
201
|
-
// Fire onFirstDataRendered once when the grid first has data
|
|
202
|
-
const firstDataRenderedRef = useRef(false);
|
|
203
|
-
useEffect(() => {
|
|
204
|
-
if (!firstDataRenderedRef.current && displayItems.length > 0) {
|
|
205
|
-
firstDataRenderedRef.current = true;
|
|
206
|
-
onFirstDataRendered?.();
|
|
207
|
-
}
|
|
208
|
-
}, [displayItems.length, onFirstDataRendered]);
|
|
114
|
+
onColumnPinned?.(columnId, pinned);
|
|
115
|
+
}, [onColumnPinned]);
|
|
116
|
+
// --- Imperative handle (stabilized via refs to avoid invalidation on every state change) ---
|
|
117
|
+
const visibleColumnsRef = useLatestRef(visibleColumns);
|
|
118
|
+
const sortRef = useLatestRef(sortingState.sort);
|
|
119
|
+
const columnOrderRef = useLatestRef(columnOrder);
|
|
120
|
+
const columnWidthOverridesRef = useLatestRef(columnWidthOverrides);
|
|
121
|
+
const pinnedOverridesRef = useLatestRef(pinnedOverrides);
|
|
122
|
+
const filtersRef = useLatestRef(filtersState.filters);
|
|
123
|
+
const effectiveSelectedRowsRef = useLatestRef(effectiveSelectedRows);
|
|
124
|
+
const displayItemsRef = useLatestRef(dataFetchingState.displayItems);
|
|
125
|
+
const getRowIdRef = useLatestRef(getRowId);
|
|
126
|
+
const columnsRef = useLatestRef(columns);
|
|
209
127
|
useImperativeHandle(ref, () => ({
|
|
210
128
|
setRowData: (d) => {
|
|
211
129
|
if (!isServerSide)
|
|
@@ -213,75 +131,62 @@ export function useOGrid(props, ref) {
|
|
|
213
131
|
},
|
|
214
132
|
setLoading: setInternalLoading,
|
|
215
133
|
getColumnState: () => ({
|
|
216
|
-
visibleColumns: Array.from(
|
|
217
|
-
sort,
|
|
218
|
-
columnOrder:
|
|
219
|
-
columnWidths: Object.keys(
|
|
220
|
-
filters: Object.keys(
|
|
221
|
-
pinnedColumns: Object.keys(
|
|
134
|
+
visibleColumns: Array.from(visibleColumnsRef.current),
|
|
135
|
+
sort: sortRef.current,
|
|
136
|
+
columnOrder: columnOrderRef.current ?? undefined,
|
|
137
|
+
columnWidths: Object.keys(columnWidthOverridesRef.current).length > 0 ? columnWidthOverridesRef.current : undefined,
|
|
138
|
+
filters: Object.keys(filtersRef.current).length > 0 ? filtersRef.current : undefined,
|
|
139
|
+
pinnedColumns: Object.keys(pinnedOverridesRef.current).length > 0 ? pinnedOverridesRef.current : undefined,
|
|
222
140
|
}),
|
|
223
141
|
applyColumnState: (state) => {
|
|
224
|
-
if (state.visibleColumns)
|
|
142
|
+
if (state.visibleColumns)
|
|
225
143
|
setVisibleColumns(new Set(state.visibleColumns));
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
if (state.columnOrder && onColumnOrderChange) {
|
|
144
|
+
if (state.sort)
|
|
145
|
+
sortingState.setSort(state.sort);
|
|
146
|
+
if (state.columnOrder && onColumnOrderChange)
|
|
231
147
|
onColumnOrderChange(state.columnOrder);
|
|
232
|
-
|
|
233
|
-
if (state.columnWidths) {
|
|
148
|
+
if (state.columnWidths)
|
|
234
149
|
setColumnWidthOverrides(state.columnWidths);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
if (state.pinnedColumns) {
|
|
150
|
+
if (state.filters)
|
|
151
|
+
filtersState.setFilters(state.filters);
|
|
152
|
+
if (state.pinnedColumns)
|
|
240
153
|
setPinnedOverrides(state.pinnedColumns);
|
|
241
|
-
}
|
|
242
154
|
},
|
|
243
|
-
setFilterModel: setFilters,
|
|
244
|
-
getSelectedRows: () => Array.from(
|
|
155
|
+
setFilterModel: filtersState.setFilters,
|
|
156
|
+
getSelectedRows: () => Array.from(effectiveSelectedRowsRef.current),
|
|
245
157
|
setSelectedRows: (rowIds) => {
|
|
246
158
|
if (selectedRows === undefined)
|
|
247
159
|
setInternalSelectedRows(new Set(rowIds));
|
|
248
160
|
},
|
|
249
161
|
selectAll: () => {
|
|
250
|
-
const
|
|
162
|
+
const items = displayItemsRef.current;
|
|
163
|
+
const allIds = new Set(items.map((item) => getRowIdRef.current(item)));
|
|
251
164
|
if (selectedRows === undefined)
|
|
252
165
|
setInternalSelectedRows(allIds);
|
|
253
|
-
onSelectionChange?.({
|
|
254
|
-
selectedRowIds: Array.from(allIds),
|
|
255
|
-
selectedItems: displayItems,
|
|
256
|
-
});
|
|
166
|
+
onSelectionChange?.({ selectedRowIds: Array.from(allIds), selectedItems: items });
|
|
257
167
|
},
|
|
258
168
|
deselectAll: () => {
|
|
259
169
|
if (selectedRows === undefined)
|
|
260
170
|
setInternalSelectedRows(new Set());
|
|
261
|
-
onSelectionChange?.({
|
|
262
|
-
selectedRowIds: [],
|
|
263
|
-
selectedItems: [],
|
|
264
|
-
});
|
|
171
|
+
onSelectionChange?.({ selectedRowIds: [], selectedItems: [] });
|
|
265
172
|
},
|
|
266
|
-
clearFilters: () => setFilters({}),
|
|
267
|
-
clearSort: () => setSort({ field: defaultSortField, direction: defaultSortDirection }),
|
|
173
|
+
clearFilters: () => filtersState.setFilters({}),
|
|
174
|
+
clearSort: () => sortingState.setSort({ field: sortingState.defaultSortField, direction: sortingState.defaultSortDirection }),
|
|
268
175
|
resetGridState: (options) => {
|
|
269
|
-
setFilters({});
|
|
270
|
-
setSort({ field: defaultSortField, direction: defaultSortDirection });
|
|
176
|
+
filtersState.setFilters({});
|
|
177
|
+
sortingState.setSort({ field: sortingState.defaultSortField, direction: sortingState.defaultSortDirection });
|
|
271
178
|
if (!options?.keepSelection) {
|
|
272
179
|
if (selectedRows === undefined)
|
|
273
180
|
setInternalSelectedRows(new Set());
|
|
274
181
|
onSelectionChange?.({ selectedRowIds: [], selectedItems: [] });
|
|
275
182
|
}
|
|
276
183
|
},
|
|
277
|
-
getDisplayedRows: () =>
|
|
184
|
+
getDisplayedRows: () => displayItemsRef.current,
|
|
278
185
|
refreshData: () => {
|
|
279
|
-
if (isServerSide)
|
|
280
|
-
|
|
281
|
-
setRefreshCounter(refreshCounterRef.current);
|
|
282
|
-
}
|
|
186
|
+
if (isServerSide)
|
|
187
|
+
dataFetchingState.refreshData();
|
|
283
188
|
},
|
|
284
|
-
getColumnOrder: () =>
|
|
189
|
+
getColumnOrder: () => columnOrderRef.current ?? columnsRef.current.map((c) => c.columnId),
|
|
285
190
|
setColumnOrder: (order) => {
|
|
286
191
|
onColumnOrderChange?.(order);
|
|
287
192
|
},
|
|
@@ -289,82 +194,38 @@ export function useOGrid(props, ref) {
|
|
|
289
194
|
// No-op at orchestration level — DataGridTable components implement
|
|
290
195
|
// this via useVirtualScroll.scrollToIndex when virtual scrolling is active.
|
|
291
196
|
},
|
|
292
|
-
}),
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
columnWidthOverrides,
|
|
299
|
-
pinnedOverrides,
|
|
300
|
-
filters,
|
|
301
|
-
setFilters,
|
|
302
|
-
setSort,
|
|
303
|
-
setVisibleColumns,
|
|
304
|
-
onColumnOrderChange,
|
|
305
|
-
isServerSide,
|
|
306
|
-
effectiveSelectedRows,
|
|
307
|
-
selectedRows,
|
|
308
|
-
displayItems,
|
|
309
|
-
getRowId,
|
|
310
|
-
onSelectionChange,
|
|
311
|
-
defaultSortField,
|
|
312
|
-
defaultSortDirection,
|
|
197
|
+
}), [
|
|
198
|
+
isServerSide, setVisibleColumns, sortingState, filtersState,
|
|
199
|
+
onColumnOrderChange, selectedRows, onSelectionChange, dataFetchingState,
|
|
200
|
+
columnOrderRef, columnWidthOverridesRef, columnsRef, displayItemsRef,
|
|
201
|
+
effectiveSelectedRowsRef, filtersRef, getRowIdRef, pinnedOverridesRef,
|
|
202
|
+
sortRef, visibleColumnsRef,
|
|
313
203
|
]);
|
|
314
|
-
//
|
|
315
|
-
const hasActiveFilters = useMemo(() => {
|
|
316
|
-
return Object.values(filters).some((v) => v !== undefined);
|
|
317
|
-
}, [filters]);
|
|
318
|
-
const columnChooserColumns = useMemo(() => columns.map((c) => ({
|
|
319
|
-
columnId: c.columnId,
|
|
320
|
-
name: c.name,
|
|
321
|
-
required: c.required === true,
|
|
322
|
-
})), [columns]);
|
|
204
|
+
// --- Status bar ---
|
|
323
205
|
const statusBarConfig = useMemo(() => {
|
|
324
206
|
if (!statusBar)
|
|
325
207
|
return undefined;
|
|
326
208
|
if (typeof statusBar === 'object')
|
|
327
209
|
return statusBar;
|
|
328
|
-
const totalData =
|
|
329
|
-
const filteredData = displayTotalCount;
|
|
210
|
+
const totalData = !isServerSide ? (data?.length ?? 0) : dataFetchingState.displayTotalCount;
|
|
211
|
+
const filteredData = dataFetchingState.displayTotalCount;
|
|
330
212
|
return {
|
|
331
213
|
totalCount: totalData,
|
|
332
|
-
filteredCount: hasActiveFilters ? filteredData : undefined,
|
|
214
|
+
filteredCount: filtersState.hasActiveFilters ? filteredData : undefined,
|
|
333
215
|
selectedCount: effectiveSelectedRows.size,
|
|
334
|
-
suppressRowCount: true,
|
|
216
|
+
suppressRowCount: true,
|
|
335
217
|
};
|
|
336
|
-
}, [
|
|
337
|
-
statusBar,
|
|
338
|
-
isClientSide,
|
|
339
|
-
data,
|
|
340
|
-
serverTotalCount,
|
|
341
|
-
displayTotalCount,
|
|
342
|
-
hasActiveFilters,
|
|
343
|
-
effectiveSelectedRows.size,
|
|
344
|
-
]);
|
|
345
|
-
const handleColumnResized = useCallback((columnId, width) => {
|
|
346
|
-
setColumnWidthOverrides((prev) => ({ ...prev, [columnId]: width }));
|
|
347
|
-
onColumnResized?.(columnId, width);
|
|
348
|
-
}, [onColumnResized]);
|
|
349
|
-
const handleColumnPinned = useCallback((columnId, pinned) => {
|
|
350
|
-
setPinnedOverrides((prev) => {
|
|
351
|
-
if (pinned === null) {
|
|
352
|
-
const { [columnId]: _, ...rest } = prev;
|
|
353
|
-
return rest;
|
|
354
|
-
}
|
|
355
|
-
return { ...prev, [columnId]: pinned };
|
|
356
|
-
});
|
|
357
|
-
onColumnPinned?.(columnId, pinned);
|
|
358
|
-
}, [onColumnPinned]);
|
|
218
|
+
}, [statusBar, isServerSide, data, dataFetchingState.displayTotalCount, filtersState.hasActiveFilters, effectiveSelectedRows.size]);
|
|
359
219
|
// --- Side bar ---
|
|
360
220
|
const sideBarState = useSideBarState({ config: sideBar });
|
|
221
|
+
const columnChooserColumns = useMemo(() => columns.map((c) => ({ columnId: c.columnId, name: c.name, required: c.required === true })), [columns]);
|
|
361
222
|
const filterableColumns = useMemo(() => columns
|
|
362
223
|
.filter((c) => c.filterable && c.filterable.type)
|
|
363
224
|
.map((c) => ({
|
|
364
225
|
columnId: c.columnId,
|
|
365
226
|
name: c.name,
|
|
366
|
-
filterField: c.filterable
|
|
367
|
-
filterType: c.filterable
|
|
227
|
+
filterField: c.filterable?.filterField ?? c.columnId,
|
|
228
|
+
filterType: c.filterable?.type,
|
|
368
229
|
})), [columns]);
|
|
369
230
|
const sideBarProps = useMemo(() => {
|
|
370
231
|
if (!sideBarState.isEnabled)
|
|
@@ -379,34 +240,26 @@ export function useOGrid(props, ref) {
|
|
|
379
240
|
onVisibilityChange: handleVisibilityChange,
|
|
380
241
|
onSetVisibleColumns: setVisibleColumns,
|
|
381
242
|
filterableColumns,
|
|
382
|
-
filters,
|
|
383
|
-
onFilterChange: handleFilterChange,
|
|
384
|
-
filterOptions: clientFilterOptions,
|
|
243
|
+
filters: filtersState.filters,
|
|
244
|
+
onFilterChange: filtersState.handleFilterChange,
|
|
245
|
+
filterOptions: filtersState.clientFilterOptions,
|
|
385
246
|
};
|
|
386
247
|
}, [
|
|
387
|
-
sideBarState.isEnabled,
|
|
388
|
-
sideBarState.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
sideBarState.position,
|
|
392
|
-
columnChooserColumns,
|
|
393
|
-
visibleColumns,
|
|
394
|
-
handleVisibilityChange,
|
|
395
|
-
setVisibleColumns,
|
|
396
|
-
filterableColumns,
|
|
397
|
-
filters,
|
|
398
|
-
handleFilterChange,
|
|
399
|
-
clientFilterOptions,
|
|
248
|
+
sideBarState.isEnabled, sideBarState.activePanel, sideBarState.setActivePanel,
|
|
249
|
+
sideBarState.panels, sideBarState.position,
|
|
250
|
+
columnChooserColumns, visibleColumns, handleVisibilityChange, setVisibleColumns,
|
|
251
|
+
filterableColumns, filtersState.filters, filtersState.handleFilterChange, filtersState.clientFilterOptions,
|
|
400
252
|
]);
|
|
401
|
-
|
|
402
|
-
const
|
|
253
|
+
// --- Assembly ---
|
|
254
|
+
const clearAllFilters = useCallback(() => filtersState.setFilters({}), [filtersState]);
|
|
255
|
+
const isLoadingResolved = (isServerSide && dataFetchingState.serverLoading) || displayLoading;
|
|
403
256
|
const dataGridProps = useMemo(() => ({
|
|
404
|
-
items: displayItems,
|
|
257
|
+
items: dataFetchingState.displayItems,
|
|
405
258
|
columns: columnsProp,
|
|
406
259
|
getRowId,
|
|
407
|
-
sortBy: sort.field,
|
|
408
|
-
sortDirection: sort.direction,
|
|
409
|
-
onColumnSort: handleSort,
|
|
260
|
+
sortBy: sortingState.sort.field,
|
|
261
|
+
sortDirection: sortingState.sort.direction,
|
|
262
|
+
onColumnSort: sortingState.handleSort,
|
|
410
263
|
visibleColumns,
|
|
411
264
|
columnOrder,
|
|
412
265
|
onColumnOrderChange,
|
|
@@ -425,14 +278,14 @@ export function useOGrid(props, ref) {
|
|
|
425
278
|
selectedRows: effectiveSelectedRows,
|
|
426
279
|
onSelectionChange: handleSelectionChange,
|
|
427
280
|
showRowNumbers,
|
|
428
|
-
currentPage: page,
|
|
429
|
-
pageSize,
|
|
281
|
+
currentPage: paginationState.page,
|
|
282
|
+
pageSize: paginationState.pageSize,
|
|
430
283
|
statusBar: statusBarConfig,
|
|
431
284
|
isLoading: isLoadingResolved,
|
|
432
|
-
filters,
|
|
433
|
-
onFilterChange: handleFilterChange,
|
|
434
|
-
filterOptions: clientFilterOptions,
|
|
435
|
-
loadingFilterOptions: dataSource?.fetchFilterOptions ? loadingFilterOptions : EMPTY_LOADING_OPTIONS,
|
|
285
|
+
filters: filtersState.filters,
|
|
286
|
+
onFilterChange: filtersState.handleFilterChange,
|
|
287
|
+
filterOptions: filtersState.clientFilterOptions,
|
|
288
|
+
loadingFilterOptions: dataSource?.fetchFilterOptions ? filtersState.loadingFilterOptions : EMPTY_LOADING_OPTIONS,
|
|
436
289
|
peopleSearch: dataSource?.searchPeople,
|
|
437
290
|
getUserByEmail: dataSource?.getUserByEmail,
|
|
438
291
|
layoutMode,
|
|
@@ -444,37 +297,41 @@ export function useOGrid(props, ref) {
|
|
|
444
297
|
'aria-label': ariaLabel,
|
|
445
298
|
'aria-labelledby': ariaLabelledBy,
|
|
446
299
|
emptyState: {
|
|
447
|
-
hasActiveFilters,
|
|
300
|
+
hasActiveFilters: filtersState.hasActiveFilters,
|
|
448
301
|
onClearAll: clearAllFilters,
|
|
449
302
|
message: emptyState?.message,
|
|
450
303
|
render: emptyState?.render,
|
|
451
304
|
},
|
|
452
305
|
}), [
|
|
453
|
-
displayItems, columnsProp, getRowId,
|
|
306
|
+
dataFetchingState.displayItems, columnsProp, getRowId,
|
|
307
|
+
sortingState.sort.field, sortingState.sort.direction, sortingState.handleSort,
|
|
454
308
|
visibleColumns, columnOrder, onColumnOrderChange, handleColumnResized,
|
|
455
309
|
handleColumnPinned, pinnedOverrides, columnWidthOverrides,
|
|
456
310
|
editable, cellSelection, onCellValueChanged, onUndo, onRedo, canUndo, canRedo,
|
|
457
|
-
rowSelection, effectiveSelectedRows, handleSelectionChange, showRowNumbers,
|
|
458
|
-
|
|
459
|
-
|
|
311
|
+
rowSelection, effectiveSelectedRows, handleSelectionChange, showRowNumbers,
|
|
312
|
+
paginationState.page, paginationState.pageSize, statusBarConfig,
|
|
313
|
+
isLoadingResolved, filtersState.filters, filtersState.handleFilterChange,
|
|
314
|
+
filtersState.clientFilterOptions, dataSource, filtersState.loadingFilterOptions,
|
|
315
|
+
layoutMode, suppressHorizontalScroll, columnReorder, virtualScroll,
|
|
460
316
|
rowHeight, density, ariaLabel, ariaLabelledBy,
|
|
461
|
-
hasActiveFilters, clearAllFilters, emptyState,
|
|
317
|
+
filtersState.hasActiveFilters, clearAllFilters, emptyState,
|
|
462
318
|
]);
|
|
463
319
|
const pagination = useMemo(() => ({
|
|
464
|
-
page,
|
|
465
|
-
pageSize,
|
|
466
|
-
displayTotalCount,
|
|
467
|
-
setPage,
|
|
468
|
-
setPageSize,
|
|
320
|
+
page: paginationState.page,
|
|
321
|
+
pageSize: paginationState.pageSize,
|
|
322
|
+
displayTotalCount: dataFetchingState.displayTotalCount,
|
|
323
|
+
setPage: paginationState.setPage,
|
|
324
|
+
setPageSize: paginationState.setPageSize,
|
|
469
325
|
pageSizeOptions,
|
|
470
326
|
entityLabelPlural,
|
|
471
|
-
}), [page, pageSize, displayTotalCount, setPage, setPageSize, pageSizeOptions, entityLabelPlural]);
|
|
327
|
+
}), [paginationState.page, paginationState.pageSize, dataFetchingState.displayTotalCount, paginationState.setPage, paginationState.setPageSize, pageSizeOptions, entityLabelPlural]);
|
|
472
328
|
const columnChooser = useMemo(() => ({
|
|
473
329
|
columns: columnChooserColumns,
|
|
474
330
|
visibleColumns,
|
|
475
331
|
onVisibilityChange: handleVisibilityChange,
|
|
332
|
+
onSetVisibleColumns: setVisibleColumns,
|
|
476
333
|
placement: columnChooserPlacement,
|
|
477
|
-
}), [columnChooserColumns, visibleColumns, handleVisibilityChange, columnChooserPlacement]);
|
|
334
|
+
}), [columnChooserColumns, visibleColumns, handleVisibilityChange, setVisibleColumns, columnChooserPlacement]);
|
|
478
335
|
const layout = useMemo(() => ({
|
|
479
336
|
toolbar,
|
|
480
337
|
toolbarBelow,
|
|
@@ -483,9 +340,9 @@ export function useOGrid(props, ref) {
|
|
|
483
340
|
sideBarProps,
|
|
484
341
|
}), [toolbar, toolbarBelow, className, emptyState, sideBarProps]);
|
|
485
342
|
const filtersResult = useMemo(() => ({
|
|
486
|
-
hasActiveFilters,
|
|
487
|
-
setFilters,
|
|
488
|
-
}), [hasActiveFilters, setFilters]);
|
|
343
|
+
hasActiveFilters: filtersState.hasActiveFilters,
|
|
344
|
+
setFilters: filtersState.setFilters,
|
|
345
|
+
}), [filtersState.hasActiveFilters, filtersState.setFilters]);
|
|
489
346
|
return {
|
|
490
347
|
dataGridProps,
|
|
491
348
|
pagination,
|