@alaarab/ogrid-react 2.1.3 → 2.1.4
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/index.js +7233 -26
- package/package.json +7 -4
- package/dist/esm/components/BaseColumnHeaderMenu.js +0 -78
- package/dist/esm/components/BaseDropIndicator.js +0 -4
- package/dist/esm/components/BaseEmptyState.js +0 -4
- package/dist/esm/components/BaseInlineCellEditor.js +0 -167
- package/dist/esm/components/BaseLoadingOverlay.js +0 -4
- package/dist/esm/components/CellErrorBoundary.js +0 -43
- package/dist/esm/components/ColumnChooserProps.js +0 -6
- package/dist/esm/components/ColumnHeaderFilterContent.js +0 -33
- package/dist/esm/components/ColumnHeaderFilterRenderers.js +0 -67
- package/dist/esm/components/EmptyState.js +0 -19
- package/dist/esm/components/GridContextMenu.js +0 -35
- package/dist/esm/components/MarchingAntsOverlay.js +0 -90
- package/dist/esm/components/OGridLayout.js +0 -136
- package/dist/esm/components/PaginationControlsProps.js +0 -6
- package/dist/esm/components/SideBar.js +0 -123
- package/dist/esm/components/StatusBar.js +0 -6
- package/dist/esm/components/createOGrid.js +0 -19
- package/dist/esm/constants/domHelpers.js +0 -16
- package/dist/esm/hooks/index.js +0 -43
- package/dist/esm/hooks/useActiveCell.js +0 -75
- package/dist/esm/hooks/useCellEditing.js +0 -15
- package/dist/esm/hooks/useCellSelection.js +0 -389
- package/dist/esm/hooks/useClipboard.js +0 -106
- package/dist/esm/hooks/useColumnChooserState.js +0 -74
- package/dist/esm/hooks/useColumnHeaderFilterState.js +0 -191
- package/dist/esm/hooks/useColumnHeaderMenuState.js +0 -106
- package/dist/esm/hooks/useColumnMeta.js +0 -61
- package/dist/esm/hooks/useColumnPinning.js +0 -67
- package/dist/esm/hooks/useColumnReorder.js +0 -143
- package/dist/esm/hooks/useColumnResize.js +0 -127
- package/dist/esm/hooks/useContextMenu.js +0 -21
- package/dist/esm/hooks/useDataGridContextMenu.js +0 -24
- package/dist/esm/hooks/useDataGridEditing.js +0 -56
- package/dist/esm/hooks/useDataGridInteraction.js +0 -109
- package/dist/esm/hooks/useDataGridLayout.js +0 -172
- package/dist/esm/hooks/useDataGridState.js +0 -169
- package/dist/esm/hooks/useDataGridTableOrchestration.js +0 -199
- package/dist/esm/hooks/useDateFilterState.js +0 -34
- package/dist/esm/hooks/useDebounce.js +0 -35
- package/dist/esm/hooks/useFillHandle.js +0 -200
- package/dist/esm/hooks/useFilterOptions.js +0 -55
- package/dist/esm/hooks/useInlineCellEditorState.js +0 -38
- package/dist/esm/hooks/useKeyboardNavigation.js +0 -261
- package/dist/esm/hooks/useLatestRef.js +0 -11
- package/dist/esm/hooks/useListVirtualizer.js +0 -29
- package/dist/esm/hooks/useMultiSelectFilterState.js +0 -59
- package/dist/esm/hooks/useOGrid.js +0 -371
- package/dist/esm/hooks/useOGridDataFetching.js +0 -74
- package/dist/esm/hooks/useOGridFilters.js +0 -59
- package/dist/esm/hooks/useOGridPagination.js +0 -24
- package/dist/esm/hooks/useOGridSorting.js +0 -24
- package/dist/esm/hooks/usePaginationControls.js +0 -16
- package/dist/esm/hooks/usePeopleFilterState.js +0 -73
- package/dist/esm/hooks/useRichSelectState.js +0 -60
- package/dist/esm/hooks/useRowSelection.js +0 -69
- package/dist/esm/hooks/useSelectState.js +0 -62
- package/dist/esm/hooks/useShallowEqualMemo.js +0 -14
- package/dist/esm/hooks/useSideBarState.js +0 -39
- package/dist/esm/hooks/useTableLayout.js +0 -69
- package/dist/esm/hooks/useTextFilterState.js +0 -25
- package/dist/esm/hooks/useUndoRedo.js +0 -84
- package/dist/esm/hooks/useVirtualScroll.js +0 -69
- package/dist/esm/storybook/index.js +0 -1
- package/dist/esm/storybook/mockData.js +0 -73
- package/dist/esm/types/columnTypes.js +0 -1
- package/dist/esm/types/dataGridTypes.js +0 -1
- package/dist/esm/types/index.js +0 -1
- package/dist/esm/utils/dataGridViewModel.js +0 -54
- package/dist/esm/utils/gridRowComparator.js +0 -2
- package/dist/esm/utils/index.js +0 -5
|
@@ -1,371 +0,0 @@
|
|
|
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';
|
|
9
|
-
import { useSideBarState } from './useSideBarState';
|
|
10
|
-
const DEFAULT_PAGE_SIZE = 25;
|
|
11
|
-
const EMPTY_LOADING_OPTIONS = {};
|
|
12
|
-
/**
|
|
13
|
-
* Top-level orchestration hook for OGrid: manages pagination, sorting, filtering, column visibility, and sidebar.
|
|
14
|
-
* Delegates to focused sub-hooks for each concern.
|
|
15
|
-
* @param props - All OGrid props (columns, data, callbacks, feature flags).
|
|
16
|
-
* @param ref - Forwarded ref for imperative API (refresh, export, applyColumnState).
|
|
17
|
-
* @returns Grouped props for DataGridTable, pagination controls, column chooser, layout, and filters.
|
|
18
|
-
*/
|
|
19
|
-
export function useOGrid(props, ref) {
|
|
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, stickyHeader, fullScreen, onFirstDataRendered, onError, columnChooser: columnChooserProp, columnReorder, virtualScroll, rowHeight, density = 'normal', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
21
|
-
// --- Derived column state ---
|
|
22
|
-
const columnChooserPlacement = columnChooserProp === false ? 'none'
|
|
23
|
-
: columnChooserProp === 'sidebar' ? 'sidebar'
|
|
24
|
-
: 'toolbar';
|
|
25
|
-
const columns = useMemo(() => flattenColumns(columnsProp), [columnsProp]);
|
|
26
|
-
const isServerSide = dataSource != null;
|
|
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) ---
|
|
35
|
-
const [internalData, setInternalData] = useState([]);
|
|
36
|
-
const [internalLoading, setInternalLoading] = useState(false);
|
|
37
|
-
const displayData = data ?? internalData;
|
|
38
|
-
const displayLoading = controlledLoading ?? internalLoading;
|
|
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,
|
|
52
|
-
});
|
|
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 ---
|
|
70
|
-
const [internalVisibleColumns, setInternalVisibleColumns] = useState(() => {
|
|
71
|
-
const visible = columns
|
|
72
|
-
.filter((c) => c.defaultVisible !== false)
|
|
73
|
-
.map((c) => c.columnId);
|
|
74
|
-
return new Set(visible.length > 0 ? visible : columns.map((c) => c.columnId));
|
|
75
|
-
});
|
|
76
|
-
// Re-initialize when columns arrive after starting empty (common pattern: columns
|
|
77
|
-
// depend on async data, so the initial render passes columns=[] then re-renders
|
|
78
|
-
// with actual columns once data loads).
|
|
79
|
-
const prevColumnsLengthRef = useRef(columns.length);
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
const prev = prevColumnsLengthRef.current;
|
|
82
|
-
prevColumnsLengthRef.current = columns.length;
|
|
83
|
-
if (controlledVisibleColumns !== undefined)
|
|
84
|
-
return; // controlled — skip
|
|
85
|
-
if (prev === 0 && columns.length > 0 && internalVisibleColumns.size === 0) {
|
|
86
|
-
const visible = columns
|
|
87
|
-
.filter((c) => c.defaultVisible !== false)
|
|
88
|
-
.map((c) => c.columnId);
|
|
89
|
-
setInternalVisibleColumns(new Set(visible.length > 0 ? visible : columns.map((c) => c.columnId)));
|
|
90
|
-
}
|
|
91
|
-
}, [columns, controlledVisibleColumns, internalVisibleColumns.size]);
|
|
92
|
-
const visibleColumns = controlledVisibleColumns ?? internalVisibleColumns;
|
|
93
|
-
const setVisibleColumns = useCallback((cols) => {
|
|
94
|
-
if (controlledVisibleColumns === undefined)
|
|
95
|
-
setInternalVisibleColumns(cols);
|
|
96
|
-
onVisibleColumnsChange?.(cols);
|
|
97
|
-
}, [controlledVisibleColumns, onVisibleColumnsChange]);
|
|
98
|
-
const handleVisibilityChange = useCallback((columnKey, isVisible) => {
|
|
99
|
-
const next = new Set(visibleColumns);
|
|
100
|
-
if (isVisible)
|
|
101
|
-
next.add(columnKey);
|
|
102
|
-
else
|
|
103
|
-
next.delete(columnKey);
|
|
104
|
-
setVisibleColumns(next);
|
|
105
|
-
}, [visibleColumns, setVisibleColumns]);
|
|
106
|
-
// --- Row selection ---
|
|
107
|
-
const [internalSelectedRows, setInternalSelectedRows] = useState(new Set());
|
|
108
|
-
const effectiveSelectedRows = selectedRows ?? internalSelectedRows;
|
|
109
|
-
const handleSelectionChange = useCallback((event) => {
|
|
110
|
-
if (selectedRows === undefined) {
|
|
111
|
-
setInternalSelectedRows(new Set(event.selectedRowIds));
|
|
112
|
-
}
|
|
113
|
-
onSelectionChange?.(event);
|
|
114
|
-
}, [selectedRows, onSelectionChange]);
|
|
115
|
-
// --- Column resize & pin ---
|
|
116
|
-
const [columnWidthOverrides, setColumnWidthOverrides] = useState({});
|
|
117
|
-
const [pinnedOverrides, setPinnedOverrides] = useState({});
|
|
118
|
-
const handleColumnResized = useCallback((columnId, width) => {
|
|
119
|
-
setColumnWidthOverrides((prev) => ({ ...prev, [columnId]: width }));
|
|
120
|
-
onColumnResized?.(columnId, width);
|
|
121
|
-
}, [onColumnResized]);
|
|
122
|
-
const handleColumnPinned = useCallback((columnId, pinned) => {
|
|
123
|
-
setPinnedOverrides((prev) => {
|
|
124
|
-
if (pinned === null) {
|
|
125
|
-
const { [columnId]: _, ...rest } = prev;
|
|
126
|
-
return rest;
|
|
127
|
-
}
|
|
128
|
-
return { ...prev, [columnId]: pinned };
|
|
129
|
-
});
|
|
130
|
-
onColumnPinned?.(columnId, pinned);
|
|
131
|
-
}, [onColumnPinned]);
|
|
132
|
-
// --- Imperative handle (stabilized via refs to avoid invalidation on every state change) ---
|
|
133
|
-
const visibleColumnsRef = useLatestRef(visibleColumns);
|
|
134
|
-
const sortRef = useLatestRef(sortingState.sort);
|
|
135
|
-
const columnOrderRef = useLatestRef(columnOrder);
|
|
136
|
-
const columnWidthOverridesRef = useLatestRef(columnWidthOverrides);
|
|
137
|
-
const pinnedOverridesRef = useLatestRef(pinnedOverrides);
|
|
138
|
-
const filtersRef = useLatestRef(filtersState.filters);
|
|
139
|
-
const effectiveSelectedRowsRef = useLatestRef(effectiveSelectedRows);
|
|
140
|
-
const displayItemsRef = useLatestRef(dataFetchingState.displayItems);
|
|
141
|
-
const getRowIdRef = useLatestRef(getRowId);
|
|
142
|
-
const columnsRef = useLatestRef(columns);
|
|
143
|
-
useImperativeHandle(ref, () => ({
|
|
144
|
-
setRowData: (d) => {
|
|
145
|
-
if (!isServerSide)
|
|
146
|
-
setInternalData(d);
|
|
147
|
-
},
|
|
148
|
-
setLoading: setInternalLoading,
|
|
149
|
-
getColumnState: () => ({
|
|
150
|
-
visibleColumns: Array.from(visibleColumnsRef.current),
|
|
151
|
-
sort: sortRef.current,
|
|
152
|
-
columnOrder: columnOrderRef.current ?? undefined,
|
|
153
|
-
columnWidths: Object.keys(columnWidthOverridesRef.current).length > 0 ? columnWidthOverridesRef.current : undefined,
|
|
154
|
-
filters: Object.keys(filtersRef.current).length > 0 ? filtersRef.current : undefined,
|
|
155
|
-
pinnedColumns: Object.keys(pinnedOverridesRef.current).length > 0 ? pinnedOverridesRef.current : undefined,
|
|
156
|
-
}),
|
|
157
|
-
applyColumnState: (state) => {
|
|
158
|
-
if (state.visibleColumns)
|
|
159
|
-
setVisibleColumns(new Set(state.visibleColumns));
|
|
160
|
-
if (state.sort)
|
|
161
|
-
sortingState.setSort(state.sort);
|
|
162
|
-
if (state.columnOrder && onColumnOrderChange)
|
|
163
|
-
onColumnOrderChange(state.columnOrder);
|
|
164
|
-
if (state.columnWidths)
|
|
165
|
-
setColumnWidthOverrides(state.columnWidths);
|
|
166
|
-
if (state.filters)
|
|
167
|
-
filtersState.setFilters(state.filters);
|
|
168
|
-
if (state.pinnedColumns)
|
|
169
|
-
setPinnedOverrides(state.pinnedColumns);
|
|
170
|
-
},
|
|
171
|
-
setFilterModel: filtersState.setFilters,
|
|
172
|
-
getSelectedRows: () => Array.from(effectiveSelectedRowsRef.current),
|
|
173
|
-
setSelectedRows: (rowIds) => {
|
|
174
|
-
if (selectedRows === undefined)
|
|
175
|
-
setInternalSelectedRows(new Set(rowIds));
|
|
176
|
-
},
|
|
177
|
-
selectAll: () => {
|
|
178
|
-
const items = displayItemsRef.current;
|
|
179
|
-
const allIds = new Set(items.map((item) => getRowIdRef.current(item)));
|
|
180
|
-
if (selectedRows === undefined)
|
|
181
|
-
setInternalSelectedRows(allIds);
|
|
182
|
-
onSelectionChange?.({ selectedRowIds: Array.from(allIds), selectedItems: items });
|
|
183
|
-
},
|
|
184
|
-
deselectAll: () => {
|
|
185
|
-
if (selectedRows === undefined)
|
|
186
|
-
setInternalSelectedRows(new Set());
|
|
187
|
-
onSelectionChange?.({ selectedRowIds: [], selectedItems: [] });
|
|
188
|
-
},
|
|
189
|
-
clearFilters: () => filtersState.setFilters({}),
|
|
190
|
-
clearSort: () => sortingState.setSort({ field: sortingState.defaultSortField, direction: sortingState.defaultSortDirection }),
|
|
191
|
-
resetGridState: (options) => {
|
|
192
|
-
filtersState.setFilters({});
|
|
193
|
-
sortingState.setSort({ field: sortingState.defaultSortField, direction: sortingState.defaultSortDirection });
|
|
194
|
-
if (!options?.keepSelection) {
|
|
195
|
-
if (selectedRows === undefined)
|
|
196
|
-
setInternalSelectedRows(new Set());
|
|
197
|
-
onSelectionChange?.({ selectedRowIds: [], selectedItems: [] });
|
|
198
|
-
}
|
|
199
|
-
},
|
|
200
|
-
getDisplayedRows: () => displayItemsRef.current,
|
|
201
|
-
refreshData: () => {
|
|
202
|
-
if (isServerSide)
|
|
203
|
-
dataFetchingState.refreshData();
|
|
204
|
-
},
|
|
205
|
-
getColumnOrder: () => columnOrderRef.current ?? columnsRef.current.map((c) => c.columnId),
|
|
206
|
-
setColumnOrder: (order) => {
|
|
207
|
-
onColumnOrderChange?.(order);
|
|
208
|
-
},
|
|
209
|
-
scrollToRow: () => {
|
|
210
|
-
// No-op at orchestration level — DataGridTable components implement
|
|
211
|
-
// this via useVirtualScroll.scrollToIndex when virtual scrolling is active.
|
|
212
|
-
},
|
|
213
|
-
}), [
|
|
214
|
-
isServerSide, setVisibleColumns, sortingState, filtersState,
|
|
215
|
-
onColumnOrderChange, selectedRows, onSelectionChange, dataFetchingState,
|
|
216
|
-
columnOrderRef, columnWidthOverridesRef, columnsRef, displayItemsRef,
|
|
217
|
-
effectiveSelectedRowsRef, filtersRef, getRowIdRef, pinnedOverridesRef,
|
|
218
|
-
sortRef, visibleColumnsRef,
|
|
219
|
-
]);
|
|
220
|
-
// --- Status bar ---
|
|
221
|
-
const statusBarConfig = useMemo(() => {
|
|
222
|
-
if (!statusBar)
|
|
223
|
-
return undefined;
|
|
224
|
-
if (typeof statusBar === 'object')
|
|
225
|
-
return statusBar;
|
|
226
|
-
const totalData = !isServerSide ? (data?.length ?? 0) : dataFetchingState.displayTotalCount;
|
|
227
|
-
const filteredData = dataFetchingState.displayTotalCount;
|
|
228
|
-
return {
|
|
229
|
-
totalCount: totalData,
|
|
230
|
-
filteredCount: filtersState.hasActiveFilters ? filteredData : undefined,
|
|
231
|
-
selectedCount: effectiveSelectedRows.size,
|
|
232
|
-
suppressRowCount: true,
|
|
233
|
-
};
|
|
234
|
-
}, [statusBar, isServerSide, data, dataFetchingState.displayTotalCount, filtersState.hasActiveFilters, effectiveSelectedRows.size]);
|
|
235
|
-
// --- Side bar ---
|
|
236
|
-
const sideBarState = useSideBarState({ config: sideBar });
|
|
237
|
-
const columnChooserColumns = useMemo(() => columns.map((c) => ({ columnId: c.columnId, name: c.name, required: c.required === true })), [columns]);
|
|
238
|
-
const filterableColumns = useMemo(() => columns
|
|
239
|
-
.filter((c) => c.filterable && c.filterable.type)
|
|
240
|
-
.map((c) => ({
|
|
241
|
-
columnId: c.columnId,
|
|
242
|
-
name: c.name,
|
|
243
|
-
filterField: c.filterable?.filterField ?? c.columnId,
|
|
244
|
-
filterType: c.filterable?.type,
|
|
245
|
-
})), [columns]);
|
|
246
|
-
const sideBarProps = useMemo(() => {
|
|
247
|
-
if (!sideBarState.isEnabled)
|
|
248
|
-
return null;
|
|
249
|
-
return {
|
|
250
|
-
activePanel: sideBarState.activePanel,
|
|
251
|
-
onPanelChange: sideBarState.setActivePanel,
|
|
252
|
-
panels: sideBarState.panels,
|
|
253
|
-
position: sideBarState.position,
|
|
254
|
-
columns: columnChooserColumns,
|
|
255
|
-
visibleColumns,
|
|
256
|
-
onVisibilityChange: handleVisibilityChange,
|
|
257
|
-
onSetVisibleColumns: setVisibleColumns,
|
|
258
|
-
filterableColumns,
|
|
259
|
-
filters: filtersState.filters,
|
|
260
|
-
onFilterChange: filtersState.handleFilterChange,
|
|
261
|
-
filterOptions: filtersState.clientFilterOptions,
|
|
262
|
-
};
|
|
263
|
-
}, [
|
|
264
|
-
sideBarState.isEnabled, sideBarState.activePanel, sideBarState.setActivePanel,
|
|
265
|
-
sideBarState.panels, sideBarState.position,
|
|
266
|
-
columnChooserColumns, visibleColumns, handleVisibilityChange, setVisibleColumns,
|
|
267
|
-
filterableColumns, filtersState.filters, filtersState.handleFilterChange, filtersState.clientFilterOptions,
|
|
268
|
-
]);
|
|
269
|
-
// --- Assembly ---
|
|
270
|
-
const clearAllFilters = useCallback(() => filtersState.setFilters({}), [filtersState]);
|
|
271
|
-
const isLoadingResolved = (isServerSide && dataFetchingState.serverLoading) || displayLoading;
|
|
272
|
-
const dataGridProps = useMemo(() => ({
|
|
273
|
-
items: dataFetchingState.displayItems,
|
|
274
|
-
columns: columnsProp,
|
|
275
|
-
getRowId,
|
|
276
|
-
sortBy: sortingState.sort.field,
|
|
277
|
-
sortDirection: sortingState.sort.direction,
|
|
278
|
-
onColumnSort: sortingState.handleSort,
|
|
279
|
-
visibleColumns,
|
|
280
|
-
columnOrder,
|
|
281
|
-
onColumnOrderChange,
|
|
282
|
-
onColumnResized: handleColumnResized,
|
|
283
|
-
onColumnPinned: handleColumnPinned,
|
|
284
|
-
pinnedColumns: pinnedOverrides,
|
|
285
|
-
initialColumnWidths: columnWidthOverrides,
|
|
286
|
-
editable,
|
|
287
|
-
cellSelection,
|
|
288
|
-
onCellValueChanged,
|
|
289
|
-
onUndo,
|
|
290
|
-
onRedo,
|
|
291
|
-
canUndo,
|
|
292
|
-
canRedo,
|
|
293
|
-
rowSelection,
|
|
294
|
-
selectedRows: effectiveSelectedRows,
|
|
295
|
-
onSelectionChange: handleSelectionChange,
|
|
296
|
-
showRowNumbers,
|
|
297
|
-
currentPage: paginationState.page,
|
|
298
|
-
pageSize: paginationState.pageSize,
|
|
299
|
-
statusBar: statusBarConfig,
|
|
300
|
-
isLoading: isLoadingResolved,
|
|
301
|
-
filters: filtersState.filters,
|
|
302
|
-
onFilterChange: filtersState.handleFilterChange,
|
|
303
|
-
filterOptions: filtersState.clientFilterOptions,
|
|
304
|
-
loadingFilterOptions: dataSource?.fetchFilterOptions ? filtersState.loadingFilterOptions : EMPTY_LOADING_OPTIONS,
|
|
305
|
-
peopleSearch: dataSource?.searchPeople,
|
|
306
|
-
getUserByEmail: dataSource?.getUserByEmail,
|
|
307
|
-
layoutMode,
|
|
308
|
-
suppressHorizontalScroll,
|
|
309
|
-
stickyHeader: stickyHeader ?? true,
|
|
310
|
-
columnReorder,
|
|
311
|
-
virtualScroll,
|
|
312
|
-
rowHeight,
|
|
313
|
-
density,
|
|
314
|
-
'aria-label': ariaLabel,
|
|
315
|
-
'aria-labelledby': ariaLabelledBy,
|
|
316
|
-
emptyState: {
|
|
317
|
-
hasActiveFilters: filtersState.hasActiveFilters,
|
|
318
|
-
onClearAll: clearAllFilters,
|
|
319
|
-
message: emptyState?.message,
|
|
320
|
-
render: emptyState?.render,
|
|
321
|
-
},
|
|
322
|
-
}), [
|
|
323
|
-
dataFetchingState.displayItems, columnsProp, getRowId,
|
|
324
|
-
sortingState.sort.field, sortingState.sort.direction, sortingState.handleSort,
|
|
325
|
-
visibleColumns, columnOrder, onColumnOrderChange, handleColumnResized,
|
|
326
|
-
handleColumnPinned, pinnedOverrides, columnWidthOverrides,
|
|
327
|
-
editable, cellSelection, onCellValueChanged, onUndo, onRedo, canUndo, canRedo,
|
|
328
|
-
rowSelection, effectiveSelectedRows, handleSelectionChange, showRowNumbers,
|
|
329
|
-
paginationState.page, paginationState.pageSize, statusBarConfig,
|
|
330
|
-
isLoadingResolved, filtersState.filters, filtersState.handleFilterChange,
|
|
331
|
-
filtersState.clientFilterOptions, dataSource, filtersState.loadingFilterOptions,
|
|
332
|
-
layoutMode, suppressHorizontalScroll, stickyHeader, columnReorder, virtualScroll,
|
|
333
|
-
rowHeight, density, ariaLabel, ariaLabelledBy,
|
|
334
|
-
filtersState.hasActiveFilters, clearAllFilters, emptyState,
|
|
335
|
-
]);
|
|
336
|
-
const pagination = useMemo(() => ({
|
|
337
|
-
page: paginationState.page,
|
|
338
|
-
pageSize: paginationState.pageSize,
|
|
339
|
-
displayTotalCount: dataFetchingState.displayTotalCount,
|
|
340
|
-
setPage: paginationState.setPage,
|
|
341
|
-
setPageSize: paginationState.setPageSize,
|
|
342
|
-
pageSizeOptions,
|
|
343
|
-
entityLabelPlural,
|
|
344
|
-
}), [paginationState.page, paginationState.pageSize, dataFetchingState.displayTotalCount, paginationState.setPage, paginationState.setPageSize, pageSizeOptions, entityLabelPlural]);
|
|
345
|
-
const columnChooser = useMemo(() => ({
|
|
346
|
-
columns: columnChooserColumns,
|
|
347
|
-
visibleColumns,
|
|
348
|
-
onVisibilityChange: handleVisibilityChange,
|
|
349
|
-
onSetVisibleColumns: setVisibleColumns,
|
|
350
|
-
placement: columnChooserPlacement,
|
|
351
|
-
}), [columnChooserColumns, visibleColumns, handleVisibilityChange, setVisibleColumns, columnChooserPlacement]);
|
|
352
|
-
const layout = useMemo(() => ({
|
|
353
|
-
toolbar,
|
|
354
|
-
toolbarBelow,
|
|
355
|
-
className,
|
|
356
|
-
emptyState,
|
|
357
|
-
sideBarProps,
|
|
358
|
-
fullScreen,
|
|
359
|
-
}), [toolbar, toolbarBelow, className, emptyState, sideBarProps, fullScreen]);
|
|
360
|
-
const filtersResult = useMemo(() => ({
|
|
361
|
-
hasActiveFilters: filtersState.hasActiveFilters,
|
|
362
|
-
setFilters: filtersState.setFilters,
|
|
363
|
-
}), [filtersState.hasActiveFilters, filtersState.setFilters]);
|
|
364
|
-
return {
|
|
365
|
-
dataGridProps,
|
|
366
|
-
pagination,
|
|
367
|
-
columnChooser,
|
|
368
|
-
layout,
|
|
369
|
-
filters: filtersResult,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
2
|
-
import { processClientSideData } from '../utils';
|
|
3
|
-
/**
|
|
4
|
-
* Manages data fetching (server-side) and client-side filtering/sorting/pagination.
|
|
5
|
-
* Fires onFirstDataRendered once when items first appear.
|
|
6
|
-
*/
|
|
7
|
-
export function useOGridDataFetching(params) {
|
|
8
|
-
const { isServerSide, dataSource, displayData, columns, stableFilters, sort, page, pageSize, onError, onFirstDataRendered, } = params;
|
|
9
|
-
const isClientSide = !isServerSide;
|
|
10
|
-
// --- Client-side filtering & sorting ---
|
|
11
|
-
const clientItemsAndTotal = useMemo(() => {
|
|
12
|
-
if (!isClientSide)
|
|
13
|
-
return null;
|
|
14
|
-
const rows = processClientSideData(displayData, columns, stableFilters, sort.field, sort.direction);
|
|
15
|
-
const total = rows.length;
|
|
16
|
-
const start = (page - 1) * pageSize;
|
|
17
|
-
const paged = rows.slice(start, start + pageSize);
|
|
18
|
-
return { items: paged, totalCount: total };
|
|
19
|
-
}, [isClientSide, displayData, columns, stableFilters, sort.field, sort.direction, page, pageSize]);
|
|
20
|
-
// --- Server-side data fetching ---
|
|
21
|
-
const [serverItems, setServerItems] = useState([]);
|
|
22
|
-
const [serverTotalCount, setServerTotalCount] = useState(0);
|
|
23
|
-
const [serverLoading, setServerLoading] = useState(true);
|
|
24
|
-
const fetchIdRef = useRef(0);
|
|
25
|
-
const [refreshCounter, setRefreshCounter] = useState(0);
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (!isServerSide || !dataSource) {
|
|
28
|
-
if (!isServerSide)
|
|
29
|
-
setServerLoading(false);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const id = ++fetchIdRef.current;
|
|
33
|
-
setServerLoading(true);
|
|
34
|
-
dataSource
|
|
35
|
-
.fetchPage({
|
|
36
|
-
page, pageSize,
|
|
37
|
-
sort: { field: sort.field, direction: sort.direction },
|
|
38
|
-
filters: stableFilters,
|
|
39
|
-
})
|
|
40
|
-
.then((res) => {
|
|
41
|
-
if (id !== fetchIdRef.current)
|
|
42
|
-
return;
|
|
43
|
-
setServerItems(res.items);
|
|
44
|
-
setServerTotalCount(res.totalCount);
|
|
45
|
-
})
|
|
46
|
-
.catch((err) => {
|
|
47
|
-
if (id !== fetchIdRef.current)
|
|
48
|
-
return;
|
|
49
|
-
onError?.(err);
|
|
50
|
-
setServerItems([]);
|
|
51
|
-
setServerTotalCount(0);
|
|
52
|
-
})
|
|
53
|
-
.finally(() => {
|
|
54
|
-
if (id === fetchIdRef.current)
|
|
55
|
-
setServerLoading(false);
|
|
56
|
-
});
|
|
57
|
-
}, [isServerSide, dataSource, page, pageSize, sort.field, sort.direction, stableFilters, onError, refreshCounter]);
|
|
58
|
-
const displayItems = isClientSide && clientItemsAndTotal ? clientItemsAndTotal.items : serverItems;
|
|
59
|
-
const displayTotalCount = isClientSide && clientItemsAndTotal ? clientItemsAndTotal.totalCount : serverTotalCount;
|
|
60
|
-
// Fire onFirstDataRendered once when the grid first has data
|
|
61
|
-
const firstDataRenderedRef = useRef(false);
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (!firstDataRenderedRef.current && displayItems.length > 0) {
|
|
64
|
-
firstDataRenderedRef.current = true;
|
|
65
|
-
onFirstDataRendered?.();
|
|
66
|
-
}
|
|
67
|
-
}, [displayItems.length, onFirstDataRendered]);
|
|
68
|
-
return {
|
|
69
|
-
displayItems,
|
|
70
|
-
displayTotalCount,
|
|
71
|
-
serverLoading,
|
|
72
|
-
refreshData: () => setRefreshCounter((prev) => prev + 1),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useMemo } from 'react';
|
|
2
|
-
import { mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, } from '../utils';
|
|
3
|
-
import { useFilterOptions } from './useFilterOptions';
|
|
4
|
-
import { useShallowEqualMemo } from './useShallowEqualMemo';
|
|
5
|
-
/** Deep-equal check for filter objects (shallow key+value comparison). */
|
|
6
|
-
function filtersEqual(a, b) {
|
|
7
|
-
const aKeys = Object.keys(a);
|
|
8
|
-
const bKeys = Object.keys(b);
|
|
9
|
-
if (aKeys.length !== bKeys.length)
|
|
10
|
-
return false;
|
|
11
|
-
for (let i = 0; i < bKeys.length; i++) {
|
|
12
|
-
if (a[bKeys[i]] !== b[bKeys[i]])
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
const EMPTY_LOADING_OPTIONS = {};
|
|
18
|
-
const EMPTY_DATA_SOURCE = { fetchFilterOptions: undefined };
|
|
19
|
-
/**
|
|
20
|
-
* Manages filter state, filter options (client + server), and stabilized filter reference.
|
|
21
|
-
* Resets to page 1 on filter change.
|
|
22
|
-
*/
|
|
23
|
-
export function useOGridFilters(params) {
|
|
24
|
-
const { controlledFilters, onFiltersChange, setPage, columns, displayData, dataSource } = params;
|
|
25
|
-
const [internalFilters, setInternalFilters] = useState({});
|
|
26
|
-
const filters = controlledFilters ?? internalFilters;
|
|
27
|
-
const setFilters = useCallback((f) => {
|
|
28
|
-
if (controlledFilters === undefined)
|
|
29
|
-
setInternalFilters(f);
|
|
30
|
-
onFiltersChange?.(f);
|
|
31
|
-
setPage(1);
|
|
32
|
-
}, [controlledFilters, onFiltersChange, setPage]);
|
|
33
|
-
const handleFilterChange = useCallback((key, value) => {
|
|
34
|
-
setFilters(mergeFilter(filters, key, value));
|
|
35
|
-
}, [filters, setFilters]);
|
|
36
|
-
// Stabilize filters via shallow comparison so processClientSideData useMemo
|
|
37
|
-
// doesn't re-run when the filter object reference changes but values are identical.
|
|
38
|
-
const stableFilters = useShallowEqualMemo(filters, (a, b) => filtersEqual(a, b));
|
|
39
|
-
const hasActiveFilters = useMemo(() => Object.values(filters).some((v) => v !== undefined), [filters]);
|
|
40
|
-
// --- Filter options (server or client-derived) ---
|
|
41
|
-
const multiSelectFilterFields = useMemo(() => getMultiSelectFilterFields(columns), [columns]);
|
|
42
|
-
const filterOptionsSource = dataSource ?? EMPTY_DATA_SOURCE;
|
|
43
|
-
const { filterOptions: serverFilterOptions, loadingOptions: loadingFilterOptions } = useFilterOptions(filterOptionsSource, multiSelectFilterFields);
|
|
44
|
-
const hasServerFilterOptions = dataSource?.fetchFilterOptions != null;
|
|
45
|
-
const clientFilterOptions = useMemo(() => {
|
|
46
|
-
if (hasServerFilterOptions)
|
|
47
|
-
return serverFilterOptions;
|
|
48
|
-
return deriveFilterOptionsFromData(displayData, columns);
|
|
49
|
-
}, [hasServerFilterOptions, displayData, columns, serverFilterOptions]);
|
|
50
|
-
return {
|
|
51
|
-
filters,
|
|
52
|
-
setFilters,
|
|
53
|
-
handleFilterChange,
|
|
54
|
-
stableFilters,
|
|
55
|
-
hasActiveFilters,
|
|
56
|
-
clientFilterOptions,
|
|
57
|
-
loadingFilterOptions: dataSource?.fetchFilterOptions ? loadingFilterOptions : EMPTY_LOADING_OPTIONS,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
/**
|
|
3
|
-
* Manages pagination state with controlled/uncontrolled dual-mode support.
|
|
4
|
-
* Resets to page 1 when page size changes.
|
|
5
|
-
*/
|
|
6
|
-
export function useOGridPagination(params) {
|
|
7
|
-
const { controlledPage, controlledPageSize, defaultPageSize, onPageChange, onPageSizeChange } = params;
|
|
8
|
-
const [internalPage, setInternalPage] = useState(1);
|
|
9
|
-
const [internalPageSize, setInternalPageSize] = useState(defaultPageSize);
|
|
10
|
-
const page = controlledPage ?? internalPage;
|
|
11
|
-
const pageSize = controlledPageSize ?? internalPageSize;
|
|
12
|
-
const setPage = useCallback((p) => {
|
|
13
|
-
if (controlledPage === undefined)
|
|
14
|
-
setInternalPage(p);
|
|
15
|
-
onPageChange?.(p);
|
|
16
|
-
}, [controlledPage, onPageChange]);
|
|
17
|
-
const setPageSize = useCallback((size) => {
|
|
18
|
-
if (controlledPageSize === undefined)
|
|
19
|
-
setInternalPageSize(size);
|
|
20
|
-
onPageSizeChange?.(size);
|
|
21
|
-
setPage(1);
|
|
22
|
-
}, [controlledPageSize, onPageSizeChange, setPage]);
|
|
23
|
-
return { page, pageSize, setPage, setPageSize };
|
|
24
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
import { computeNextSortState } from '../utils';
|
|
3
|
-
/**
|
|
4
|
-
* Manages sort state with controlled/uncontrolled dual-mode support.
|
|
5
|
-
* Resets to page 1 on sort change.
|
|
6
|
-
*/
|
|
7
|
-
export function useOGridSorting(params) {
|
|
8
|
-
const { controlledSort, defaultSortField, defaultSortDirection, onSortChange, setPage } = params;
|
|
9
|
-
const [internalSort, setInternalSort] = useState({
|
|
10
|
-
field: defaultSortField,
|
|
11
|
-
direction: defaultSortDirection,
|
|
12
|
-
});
|
|
13
|
-
const sort = controlledSort ?? internalSort;
|
|
14
|
-
const setSort = useCallback((s) => {
|
|
15
|
-
if (controlledSort === undefined)
|
|
16
|
-
setInternalSort(s);
|
|
17
|
-
onSortChange?.(s);
|
|
18
|
-
setPage(1);
|
|
19
|
-
}, [controlledSort, onSortChange, setPage]);
|
|
20
|
-
const handleSort = useCallback((columnKey, direction) => {
|
|
21
|
-
setSort(computeNextSortState(sort, columnKey, direction));
|
|
22
|
-
}, [sort, setSort]);
|
|
23
|
-
return { sort, setSort, handleSort, defaultSortField, defaultSortDirection };
|
|
24
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { getPaginationViewModel } from '../utils';
|
|
3
|
-
/**
|
|
4
|
-
* Shared pagination controls logic for React UI packages.
|
|
5
|
-
* Computes pagination view model and provides standardized handlers.
|
|
6
|
-
*/
|
|
7
|
-
export function usePaginationControls(props) {
|
|
8
|
-
const { currentPage, pageSize, totalCount, onPageSizeChange, pageSizeOptions, entityLabelPlural } = props;
|
|
9
|
-
const labelPlural = entityLabelPlural ?? 'items';
|
|
10
|
-
const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount, pageSizeOptions ? { pageSizeOptions } : undefined), [currentPage, pageSize, totalCount, pageSizeOptions]);
|
|
11
|
-
return {
|
|
12
|
-
labelPlural,
|
|
13
|
-
vm,
|
|
14
|
-
handlePageSizeChange: onPageSizeChange,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* People filter state sub-hook for column header filters.
|
|
3
|
-
* Manages people search text, suggestions, loading state, input ref, and user select/clear handlers.
|
|
4
|
-
* Includes debounced people search effect.
|
|
5
|
-
*/
|
|
6
|
-
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
7
|
-
import { PEOPLE_SEARCH_DEBOUNCE_MS } from '@alaarab/ogrid-core';
|
|
8
|
-
export function usePeopleFilterState(params) {
|
|
9
|
-
const { onUserChange, peopleSearch, isFilterOpen, filterType } = params;
|
|
10
|
-
const peopleInputRef = useRef(null);
|
|
11
|
-
const focusTimeoutRef = useRef(undefined);
|
|
12
|
-
const peopleSearchTimeoutRef = useRef(undefined);
|
|
13
|
-
const [peopleSuggestions, setPeopleSuggestions] = useState([]);
|
|
14
|
-
const [isPeopleLoading, setIsPeopleLoading] = useState(false);
|
|
15
|
-
const [peopleSearchText, setPeopleSearchText] = useState('');
|
|
16
|
-
// Sync temp state when popover opens
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
if (isFilterOpen) {
|
|
19
|
-
setPeopleSearchText('');
|
|
20
|
-
setPeopleSuggestions([]);
|
|
21
|
-
if (filterType === 'people') {
|
|
22
|
-
focusTimeoutRef.current = window.setTimeout(() => peopleInputRef.current?.focus(), 50);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return () => {
|
|
26
|
-
if (focusTimeoutRef.current)
|
|
27
|
-
window.clearTimeout(focusTimeoutRef.current);
|
|
28
|
-
};
|
|
29
|
-
}, [isFilterOpen, filterType]);
|
|
30
|
-
// People search with debounce
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
if (!peopleSearch || !isFilterOpen || filterType !== 'people')
|
|
33
|
-
return;
|
|
34
|
-
if (peopleSearchTimeoutRef.current)
|
|
35
|
-
window.clearTimeout(peopleSearchTimeoutRef.current);
|
|
36
|
-
if (!peopleSearchText.trim()) {
|
|
37
|
-
setPeopleSuggestions([]);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
setIsPeopleLoading(true);
|
|
41
|
-
peopleSearchTimeoutRef.current = window.setTimeout(async () => {
|
|
42
|
-
try {
|
|
43
|
-
const results = await peopleSearch(peopleSearchText);
|
|
44
|
-
setPeopleSuggestions(results.slice(0, 10));
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
setPeopleSuggestions([]);
|
|
48
|
-
}
|
|
49
|
-
finally {
|
|
50
|
-
setIsPeopleLoading(false);
|
|
51
|
-
}
|
|
52
|
-
}, PEOPLE_SEARCH_DEBOUNCE_MS);
|
|
53
|
-
return () => {
|
|
54
|
-
if (peopleSearchTimeoutRef.current)
|
|
55
|
-
window.clearTimeout(peopleSearchTimeoutRef.current);
|
|
56
|
-
};
|
|
57
|
-
}, [peopleSearchText, peopleSearch, isFilterOpen, filterType]);
|
|
58
|
-
const handleUserSelect = useCallback((user) => {
|
|
59
|
-
onUserChange?.(user);
|
|
60
|
-
}, [onUserChange]);
|
|
61
|
-
const handleClearUser = useCallback(() => {
|
|
62
|
-
onUserChange?.(undefined);
|
|
63
|
-
}, [onUserChange]);
|
|
64
|
-
return {
|
|
65
|
-
peopleSuggestions,
|
|
66
|
-
isPeopleLoading,
|
|
67
|
-
peopleSearchText,
|
|
68
|
-
setPeopleSearchText,
|
|
69
|
-
peopleInputRef,
|
|
70
|
-
handleUserSelect,
|
|
71
|
-
handleClearUser,
|
|
72
|
-
};
|
|
73
|
-
}
|