@alaarab/ogrid-react 2.0.22 → 2.1.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/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 +2 -4
- 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 +159 -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 +29 -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,85 @@
|
|
|
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
|
+
filters: filtersState.filters,
|
|
57
|
+
sort: sortingState.sort,
|
|
58
|
+
page: paginationState.page,
|
|
59
|
+
pageSize: paginationState.pageSize,
|
|
60
|
+
onError, onFirstDataRendered,
|
|
61
|
+
});
|
|
62
|
+
// Validate row IDs once on first data render
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const items = dataFetchingState.displayItems;
|
|
65
|
+
if (!rowIdsValidatedRef.current && items.length > 0) {
|
|
66
|
+
rowIdsValidatedRef.current = true;
|
|
67
|
+
validateRowIds(items, getRowId);
|
|
68
|
+
}
|
|
69
|
+
}, [dataFetchingState.displayItems, getRowId]);
|
|
70
|
+
// --- Column visibility ---
|
|
34
71
|
const [internalVisibleColumns, setInternalVisibleColumns] = useState(() => {
|
|
35
72
|
const visible = columns
|
|
36
73
|
.filter((c) => c.defaultVisible !== false)
|
|
37
74
|
.map((c) => c.columnId);
|
|
38
75
|
return new Set(visible.length > 0 ? visible : columns.map((c) => c.columnId));
|
|
39
76
|
});
|
|
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
77
|
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
78
|
const setVisibleColumns = useCallback((cols) => {
|
|
71
79
|
if (controlledVisibleColumns === undefined)
|
|
72
80
|
setInternalVisibleColumns(cols);
|
|
73
81
|
onVisibleColumnsChange?.(cols);
|
|
74
82
|
}, [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
83
|
const handleVisibilityChange = useCallback((columnKey, isVisible) => {
|
|
83
84
|
const next = new Set(visibleColumns);
|
|
84
85
|
if (isVisible)
|
|
@@ -87,6 +88,7 @@ export function useOGrid(props, ref) {
|
|
|
87
88
|
next.delete(columnKey);
|
|
88
89
|
setVisibleColumns(next);
|
|
89
90
|
}, [visibleColumns, setVisibleColumns]);
|
|
91
|
+
// --- Row selection ---
|
|
90
92
|
const [internalSelectedRows, setInternalSelectedRows] = useState(new Set());
|
|
91
93
|
const effectiveSelectedRows = selectedRows ?? internalSelectedRows;
|
|
92
94
|
const handleSelectionChange = useCallback((event) => {
|
|
@@ -95,117 +97,34 @@ export function useOGrid(props, ref) {
|
|
|
95
97
|
}
|
|
96
98
|
onSelectionChange?.(event);
|
|
97
99
|
}, [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;
|
|
100
|
+
// --- Column resize & pin ---
|
|
101
|
+
const [columnWidthOverrides, setColumnWidthOverrides] = useState({});
|
|
102
|
+
const [pinnedOverrides, setPinnedOverrides] = useState({});
|
|
103
|
+
const handleColumnResized = useCallback((columnId, width) => {
|
|
104
|
+
setColumnWidthOverrides((prev) => ({ ...prev, [columnId]: width }));
|
|
105
|
+
onColumnResized?.(columnId, width);
|
|
106
|
+
}, [onColumnResized]);
|
|
107
|
+
const handleColumnPinned = useCallback((columnId, pinned) => {
|
|
108
|
+
setPinnedOverrides((prev) => {
|
|
109
|
+
if (pinned === null) {
|
|
110
|
+
const { [columnId]: _, ...rest } = prev;
|
|
111
|
+
return rest;
|
|
123
112
|
}
|
|
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);
|
|
113
|
+
return { ...prev, [columnId]: pinned };
|
|
183
114
|
});
|
|
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]);
|
|
115
|
+
onColumnPinned?.(columnId, pinned);
|
|
116
|
+
}, [onColumnPinned]);
|
|
117
|
+
// --- Imperative handle (stabilized via refs to avoid invalidation on every state change) ---
|
|
118
|
+
const visibleColumnsRef = useLatestRef(visibleColumns);
|
|
119
|
+
const sortRef = useLatestRef(sortingState.sort);
|
|
120
|
+
const columnOrderRef = useLatestRef(columnOrder);
|
|
121
|
+
const columnWidthOverridesRef = useLatestRef(columnWidthOverrides);
|
|
122
|
+
const pinnedOverridesRef = useLatestRef(pinnedOverrides);
|
|
123
|
+
const filtersRef = useLatestRef(filtersState.filters);
|
|
124
|
+
const effectiveSelectedRowsRef = useLatestRef(effectiveSelectedRows);
|
|
125
|
+
const displayItemsRef = useLatestRef(dataFetchingState.displayItems);
|
|
126
|
+
const getRowIdRef = useLatestRef(getRowId);
|
|
127
|
+
const columnsRef = useLatestRef(columns);
|
|
209
128
|
useImperativeHandle(ref, () => ({
|
|
210
129
|
setRowData: (d) => {
|
|
211
130
|
if (!isServerSide)
|
|
@@ -213,75 +132,62 @@ export function useOGrid(props, ref) {
|
|
|
213
132
|
},
|
|
214
133
|
setLoading: setInternalLoading,
|
|
215
134
|
getColumnState: () => ({
|
|
216
|
-
visibleColumns: Array.from(
|
|
217
|
-
sort,
|
|
218
|
-
columnOrder:
|
|
219
|
-
columnWidths: Object.keys(
|
|
220
|
-
filters: Object.keys(
|
|
221
|
-
pinnedColumns: Object.keys(
|
|
135
|
+
visibleColumns: Array.from(visibleColumnsRef.current),
|
|
136
|
+
sort: sortRef.current,
|
|
137
|
+
columnOrder: columnOrderRef.current ?? undefined,
|
|
138
|
+
columnWidths: Object.keys(columnWidthOverridesRef.current).length > 0 ? columnWidthOverridesRef.current : undefined,
|
|
139
|
+
filters: Object.keys(filtersRef.current).length > 0 ? filtersRef.current : undefined,
|
|
140
|
+
pinnedColumns: Object.keys(pinnedOverridesRef.current).length > 0 ? pinnedOverridesRef.current : undefined,
|
|
222
141
|
}),
|
|
223
142
|
applyColumnState: (state) => {
|
|
224
|
-
if (state.visibleColumns)
|
|
143
|
+
if (state.visibleColumns)
|
|
225
144
|
setVisibleColumns(new Set(state.visibleColumns));
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
if (state.columnOrder && onColumnOrderChange) {
|
|
145
|
+
if (state.sort)
|
|
146
|
+
sortingState.setSort(state.sort);
|
|
147
|
+
if (state.columnOrder && onColumnOrderChange)
|
|
231
148
|
onColumnOrderChange(state.columnOrder);
|
|
232
|
-
|
|
233
|
-
if (state.columnWidths) {
|
|
149
|
+
if (state.columnWidths)
|
|
234
150
|
setColumnWidthOverrides(state.columnWidths);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
if (state.pinnedColumns) {
|
|
151
|
+
if (state.filters)
|
|
152
|
+
filtersState.setFilters(state.filters);
|
|
153
|
+
if (state.pinnedColumns)
|
|
240
154
|
setPinnedOverrides(state.pinnedColumns);
|
|
241
|
-
}
|
|
242
155
|
},
|
|
243
|
-
setFilterModel: setFilters,
|
|
244
|
-
getSelectedRows: () => Array.from(
|
|
156
|
+
setFilterModel: filtersState.setFilters,
|
|
157
|
+
getSelectedRows: () => Array.from(effectiveSelectedRowsRef.current),
|
|
245
158
|
setSelectedRows: (rowIds) => {
|
|
246
159
|
if (selectedRows === undefined)
|
|
247
160
|
setInternalSelectedRows(new Set(rowIds));
|
|
248
161
|
},
|
|
249
162
|
selectAll: () => {
|
|
250
|
-
const
|
|
163
|
+
const items = displayItemsRef.current;
|
|
164
|
+
const allIds = new Set(items.map((item) => getRowIdRef.current(item)));
|
|
251
165
|
if (selectedRows === undefined)
|
|
252
166
|
setInternalSelectedRows(allIds);
|
|
253
|
-
onSelectionChange?.({
|
|
254
|
-
selectedRowIds: Array.from(allIds),
|
|
255
|
-
selectedItems: displayItems,
|
|
256
|
-
});
|
|
167
|
+
onSelectionChange?.({ selectedRowIds: Array.from(allIds), selectedItems: items });
|
|
257
168
|
},
|
|
258
169
|
deselectAll: () => {
|
|
259
170
|
if (selectedRows === undefined)
|
|
260
171
|
setInternalSelectedRows(new Set());
|
|
261
|
-
onSelectionChange?.({
|
|
262
|
-
selectedRowIds: [],
|
|
263
|
-
selectedItems: [],
|
|
264
|
-
});
|
|
172
|
+
onSelectionChange?.({ selectedRowIds: [], selectedItems: [] });
|
|
265
173
|
},
|
|
266
|
-
clearFilters: () => setFilters({}),
|
|
267
|
-
clearSort: () => setSort({ field: defaultSortField, direction: defaultSortDirection }),
|
|
174
|
+
clearFilters: () => filtersState.setFilters({}),
|
|
175
|
+
clearSort: () => sortingState.setSort({ field: sortingState.defaultSortField, direction: sortingState.defaultSortDirection }),
|
|
268
176
|
resetGridState: (options) => {
|
|
269
|
-
setFilters({});
|
|
270
|
-
setSort({ field: defaultSortField, direction: defaultSortDirection });
|
|
177
|
+
filtersState.setFilters({});
|
|
178
|
+
sortingState.setSort({ field: sortingState.defaultSortField, direction: sortingState.defaultSortDirection });
|
|
271
179
|
if (!options?.keepSelection) {
|
|
272
180
|
if (selectedRows === undefined)
|
|
273
181
|
setInternalSelectedRows(new Set());
|
|
274
182
|
onSelectionChange?.({ selectedRowIds: [], selectedItems: [] });
|
|
275
183
|
}
|
|
276
184
|
},
|
|
277
|
-
getDisplayedRows: () =>
|
|
185
|
+
getDisplayedRows: () => displayItemsRef.current,
|
|
278
186
|
refreshData: () => {
|
|
279
|
-
if (isServerSide)
|
|
280
|
-
|
|
281
|
-
setRefreshCounter(refreshCounterRef.current);
|
|
282
|
-
}
|
|
187
|
+
if (isServerSide)
|
|
188
|
+
dataFetchingState.refreshData();
|
|
283
189
|
},
|
|
284
|
-
getColumnOrder: () =>
|
|
190
|
+
getColumnOrder: () => columnOrderRef.current ?? columnsRef.current.map((c) => c.columnId),
|
|
285
191
|
setColumnOrder: (order) => {
|
|
286
192
|
onColumnOrderChange?.(order);
|
|
287
193
|
},
|
|
@@ -289,82 +195,38 @@ export function useOGrid(props, ref) {
|
|
|
289
195
|
// No-op at orchestration level — DataGridTable components implement
|
|
290
196
|
// this via useVirtualScroll.scrollToIndex when virtual scrolling is active.
|
|
291
197
|
},
|
|
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,
|
|
198
|
+
}), [
|
|
199
|
+
isServerSide, setVisibleColumns, sortingState, filtersState,
|
|
200
|
+
onColumnOrderChange, selectedRows, onSelectionChange, dataFetchingState,
|
|
201
|
+
columnOrderRef, columnWidthOverridesRef, columnsRef, displayItemsRef,
|
|
202
|
+
effectiveSelectedRowsRef, filtersRef, getRowIdRef, pinnedOverridesRef,
|
|
203
|
+
sortRef, visibleColumnsRef,
|
|
313
204
|
]);
|
|
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]);
|
|
205
|
+
// --- Status bar ---
|
|
323
206
|
const statusBarConfig = useMemo(() => {
|
|
324
207
|
if (!statusBar)
|
|
325
208
|
return undefined;
|
|
326
209
|
if (typeof statusBar === 'object')
|
|
327
210
|
return statusBar;
|
|
328
|
-
const totalData =
|
|
329
|
-
const filteredData = displayTotalCount;
|
|
211
|
+
const totalData = !isServerSide ? (data?.length ?? 0) : dataFetchingState.displayTotalCount;
|
|
212
|
+
const filteredData = dataFetchingState.displayTotalCount;
|
|
330
213
|
return {
|
|
331
214
|
totalCount: totalData,
|
|
332
|
-
filteredCount: hasActiveFilters ? filteredData : undefined,
|
|
215
|
+
filteredCount: filtersState.hasActiveFilters ? filteredData : undefined,
|
|
333
216
|
selectedCount: effectiveSelectedRows.size,
|
|
334
|
-
suppressRowCount: true,
|
|
217
|
+
suppressRowCount: true,
|
|
335
218
|
};
|
|
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]);
|
|
219
|
+
}, [statusBar, isServerSide, data, dataFetchingState.displayTotalCount, filtersState.hasActiveFilters, effectiveSelectedRows.size]);
|
|
359
220
|
// --- Side bar ---
|
|
360
221
|
const sideBarState = useSideBarState({ config: sideBar });
|
|
222
|
+
const columnChooserColumns = useMemo(() => columns.map((c) => ({ columnId: c.columnId, name: c.name, required: c.required === true })), [columns]);
|
|
361
223
|
const filterableColumns = useMemo(() => columns
|
|
362
224
|
.filter((c) => c.filterable && c.filterable.type)
|
|
363
225
|
.map((c) => ({
|
|
364
226
|
columnId: c.columnId,
|
|
365
227
|
name: c.name,
|
|
366
|
-
filterField: c.filterable
|
|
367
|
-
filterType: c.filterable
|
|
228
|
+
filterField: c.filterable?.filterField ?? c.columnId,
|
|
229
|
+
filterType: c.filterable?.type,
|
|
368
230
|
})), [columns]);
|
|
369
231
|
const sideBarProps = useMemo(() => {
|
|
370
232
|
if (!sideBarState.isEnabled)
|
|
@@ -379,34 +241,26 @@ export function useOGrid(props, ref) {
|
|
|
379
241
|
onVisibilityChange: handleVisibilityChange,
|
|
380
242
|
onSetVisibleColumns: setVisibleColumns,
|
|
381
243
|
filterableColumns,
|
|
382
|
-
filters,
|
|
383
|
-
onFilterChange: handleFilterChange,
|
|
384
|
-
filterOptions: clientFilterOptions,
|
|
244
|
+
filters: filtersState.filters,
|
|
245
|
+
onFilterChange: filtersState.handleFilterChange,
|
|
246
|
+
filterOptions: filtersState.clientFilterOptions,
|
|
385
247
|
};
|
|
386
248
|
}, [
|
|
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,
|
|
249
|
+
sideBarState.isEnabled, sideBarState.activePanel, sideBarState.setActivePanel,
|
|
250
|
+
sideBarState.panels, sideBarState.position,
|
|
251
|
+
columnChooserColumns, visibleColumns, handleVisibilityChange, setVisibleColumns,
|
|
252
|
+
filterableColumns, filtersState.filters, filtersState.handleFilterChange, filtersState.clientFilterOptions,
|
|
400
253
|
]);
|
|
401
|
-
|
|
402
|
-
const
|
|
254
|
+
// --- Assembly ---
|
|
255
|
+
const clearAllFilters = useCallback(() => filtersState.setFilters({}), [filtersState]);
|
|
256
|
+
const isLoadingResolved = (isServerSide && dataFetchingState.serverLoading) || displayLoading;
|
|
403
257
|
const dataGridProps = useMemo(() => ({
|
|
404
|
-
items: displayItems,
|
|
258
|
+
items: dataFetchingState.displayItems,
|
|
405
259
|
columns: columnsProp,
|
|
406
260
|
getRowId,
|
|
407
|
-
sortBy: sort.field,
|
|
408
|
-
sortDirection: sort.direction,
|
|
409
|
-
onColumnSort: handleSort,
|
|
261
|
+
sortBy: sortingState.sort.field,
|
|
262
|
+
sortDirection: sortingState.sort.direction,
|
|
263
|
+
onColumnSort: sortingState.handleSort,
|
|
410
264
|
visibleColumns,
|
|
411
265
|
columnOrder,
|
|
412
266
|
onColumnOrderChange,
|
|
@@ -425,14 +279,14 @@ export function useOGrid(props, ref) {
|
|
|
425
279
|
selectedRows: effectiveSelectedRows,
|
|
426
280
|
onSelectionChange: handleSelectionChange,
|
|
427
281
|
showRowNumbers,
|
|
428
|
-
currentPage: page,
|
|
429
|
-
pageSize,
|
|
282
|
+
currentPage: paginationState.page,
|
|
283
|
+
pageSize: paginationState.pageSize,
|
|
430
284
|
statusBar: statusBarConfig,
|
|
431
285
|
isLoading: isLoadingResolved,
|
|
432
|
-
filters,
|
|
433
|
-
onFilterChange: handleFilterChange,
|
|
434
|
-
filterOptions: clientFilterOptions,
|
|
435
|
-
loadingFilterOptions: dataSource?.fetchFilterOptions ? loadingFilterOptions : EMPTY_LOADING_OPTIONS,
|
|
286
|
+
filters: filtersState.filters,
|
|
287
|
+
onFilterChange: filtersState.handleFilterChange,
|
|
288
|
+
filterOptions: filtersState.clientFilterOptions,
|
|
289
|
+
loadingFilterOptions: dataSource?.fetchFilterOptions ? filtersState.loadingFilterOptions : EMPTY_LOADING_OPTIONS,
|
|
436
290
|
peopleSearch: dataSource?.searchPeople,
|
|
437
291
|
getUserByEmail: dataSource?.getUserByEmail,
|
|
438
292
|
layoutMode,
|
|
@@ -444,37 +298,41 @@ export function useOGrid(props, ref) {
|
|
|
444
298
|
'aria-label': ariaLabel,
|
|
445
299
|
'aria-labelledby': ariaLabelledBy,
|
|
446
300
|
emptyState: {
|
|
447
|
-
hasActiveFilters,
|
|
301
|
+
hasActiveFilters: filtersState.hasActiveFilters,
|
|
448
302
|
onClearAll: clearAllFilters,
|
|
449
303
|
message: emptyState?.message,
|
|
450
304
|
render: emptyState?.render,
|
|
451
305
|
},
|
|
452
306
|
}), [
|
|
453
|
-
displayItems, columnsProp, getRowId,
|
|
307
|
+
dataFetchingState.displayItems, columnsProp, getRowId,
|
|
308
|
+
sortingState.sort.field, sortingState.sort.direction, sortingState.handleSort,
|
|
454
309
|
visibleColumns, columnOrder, onColumnOrderChange, handleColumnResized,
|
|
455
310
|
handleColumnPinned, pinnedOverrides, columnWidthOverrides,
|
|
456
311
|
editable, cellSelection, onCellValueChanged, onUndo, onRedo, canUndo, canRedo,
|
|
457
|
-
rowSelection, effectiveSelectedRows, handleSelectionChange, showRowNumbers,
|
|
458
|
-
|
|
459
|
-
|
|
312
|
+
rowSelection, effectiveSelectedRows, handleSelectionChange, showRowNumbers,
|
|
313
|
+
paginationState.page, paginationState.pageSize, statusBarConfig,
|
|
314
|
+
isLoadingResolved, filtersState.filters, filtersState.handleFilterChange,
|
|
315
|
+
filtersState.clientFilterOptions, dataSource, filtersState.loadingFilterOptions,
|
|
316
|
+
layoutMode, suppressHorizontalScroll, columnReorder, virtualScroll,
|
|
460
317
|
rowHeight, density, ariaLabel, ariaLabelledBy,
|
|
461
|
-
hasActiveFilters, clearAllFilters, emptyState,
|
|
318
|
+
filtersState.hasActiveFilters, clearAllFilters, emptyState,
|
|
462
319
|
]);
|
|
463
320
|
const pagination = useMemo(() => ({
|
|
464
|
-
page,
|
|
465
|
-
pageSize,
|
|
466
|
-
displayTotalCount,
|
|
467
|
-
setPage,
|
|
468
|
-
setPageSize,
|
|
321
|
+
page: paginationState.page,
|
|
322
|
+
pageSize: paginationState.pageSize,
|
|
323
|
+
displayTotalCount: dataFetchingState.displayTotalCount,
|
|
324
|
+
setPage: paginationState.setPage,
|
|
325
|
+
setPageSize: paginationState.setPageSize,
|
|
469
326
|
pageSizeOptions,
|
|
470
327
|
entityLabelPlural,
|
|
471
|
-
}), [page, pageSize, displayTotalCount, setPage, setPageSize, pageSizeOptions, entityLabelPlural]);
|
|
328
|
+
}), [paginationState.page, paginationState.pageSize, dataFetchingState.displayTotalCount, paginationState.setPage, paginationState.setPageSize, pageSizeOptions, entityLabelPlural]);
|
|
472
329
|
const columnChooser = useMemo(() => ({
|
|
473
330
|
columns: columnChooserColumns,
|
|
474
331
|
visibleColumns,
|
|
475
332
|
onVisibilityChange: handleVisibilityChange,
|
|
333
|
+
onSetVisibleColumns: setVisibleColumns,
|
|
476
334
|
placement: columnChooserPlacement,
|
|
477
|
-
}), [columnChooserColumns, visibleColumns, handleVisibilityChange, columnChooserPlacement]);
|
|
335
|
+
}), [columnChooserColumns, visibleColumns, handleVisibilityChange, setVisibleColumns, columnChooserPlacement]);
|
|
478
336
|
const layout = useMemo(() => ({
|
|
479
337
|
toolbar,
|
|
480
338
|
toolbarBelow,
|
|
@@ -483,9 +341,9 @@ export function useOGrid(props, ref) {
|
|
|
483
341
|
sideBarProps,
|
|
484
342
|
}), [toolbar, toolbarBelow, className, emptyState, sideBarProps]);
|
|
485
343
|
const filtersResult = useMemo(() => ({
|
|
486
|
-
hasActiveFilters,
|
|
487
|
-
setFilters,
|
|
488
|
-
}), [hasActiveFilters, setFilters]);
|
|
344
|
+
hasActiveFilters: filtersState.hasActiveFilters,
|
|
345
|
+
setFilters: filtersState.setFilters,
|
|
346
|
+
}), [filtersState.hasActiveFilters, filtersState.setFilters]);
|
|
489
347
|
return {
|
|
490
348
|
dataGridProps,
|
|
491
349
|
pagination,
|