@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.
Files changed (72) hide show
  1. package/dist/esm/index.js +7233 -26
  2. package/package.json +7 -4
  3. package/dist/esm/components/BaseColumnHeaderMenu.js +0 -78
  4. package/dist/esm/components/BaseDropIndicator.js +0 -4
  5. package/dist/esm/components/BaseEmptyState.js +0 -4
  6. package/dist/esm/components/BaseInlineCellEditor.js +0 -167
  7. package/dist/esm/components/BaseLoadingOverlay.js +0 -4
  8. package/dist/esm/components/CellErrorBoundary.js +0 -43
  9. package/dist/esm/components/ColumnChooserProps.js +0 -6
  10. package/dist/esm/components/ColumnHeaderFilterContent.js +0 -33
  11. package/dist/esm/components/ColumnHeaderFilterRenderers.js +0 -67
  12. package/dist/esm/components/EmptyState.js +0 -19
  13. package/dist/esm/components/GridContextMenu.js +0 -35
  14. package/dist/esm/components/MarchingAntsOverlay.js +0 -90
  15. package/dist/esm/components/OGridLayout.js +0 -136
  16. package/dist/esm/components/PaginationControlsProps.js +0 -6
  17. package/dist/esm/components/SideBar.js +0 -123
  18. package/dist/esm/components/StatusBar.js +0 -6
  19. package/dist/esm/components/createOGrid.js +0 -19
  20. package/dist/esm/constants/domHelpers.js +0 -16
  21. package/dist/esm/hooks/index.js +0 -43
  22. package/dist/esm/hooks/useActiveCell.js +0 -75
  23. package/dist/esm/hooks/useCellEditing.js +0 -15
  24. package/dist/esm/hooks/useCellSelection.js +0 -389
  25. package/dist/esm/hooks/useClipboard.js +0 -106
  26. package/dist/esm/hooks/useColumnChooserState.js +0 -74
  27. package/dist/esm/hooks/useColumnHeaderFilterState.js +0 -191
  28. package/dist/esm/hooks/useColumnHeaderMenuState.js +0 -106
  29. package/dist/esm/hooks/useColumnMeta.js +0 -61
  30. package/dist/esm/hooks/useColumnPinning.js +0 -67
  31. package/dist/esm/hooks/useColumnReorder.js +0 -143
  32. package/dist/esm/hooks/useColumnResize.js +0 -127
  33. package/dist/esm/hooks/useContextMenu.js +0 -21
  34. package/dist/esm/hooks/useDataGridContextMenu.js +0 -24
  35. package/dist/esm/hooks/useDataGridEditing.js +0 -56
  36. package/dist/esm/hooks/useDataGridInteraction.js +0 -109
  37. package/dist/esm/hooks/useDataGridLayout.js +0 -172
  38. package/dist/esm/hooks/useDataGridState.js +0 -169
  39. package/dist/esm/hooks/useDataGridTableOrchestration.js +0 -199
  40. package/dist/esm/hooks/useDateFilterState.js +0 -34
  41. package/dist/esm/hooks/useDebounce.js +0 -35
  42. package/dist/esm/hooks/useFillHandle.js +0 -200
  43. package/dist/esm/hooks/useFilterOptions.js +0 -55
  44. package/dist/esm/hooks/useInlineCellEditorState.js +0 -38
  45. package/dist/esm/hooks/useKeyboardNavigation.js +0 -261
  46. package/dist/esm/hooks/useLatestRef.js +0 -11
  47. package/dist/esm/hooks/useListVirtualizer.js +0 -29
  48. package/dist/esm/hooks/useMultiSelectFilterState.js +0 -59
  49. package/dist/esm/hooks/useOGrid.js +0 -371
  50. package/dist/esm/hooks/useOGridDataFetching.js +0 -74
  51. package/dist/esm/hooks/useOGridFilters.js +0 -59
  52. package/dist/esm/hooks/useOGridPagination.js +0 -24
  53. package/dist/esm/hooks/useOGridSorting.js +0 -24
  54. package/dist/esm/hooks/usePaginationControls.js +0 -16
  55. package/dist/esm/hooks/usePeopleFilterState.js +0 -73
  56. package/dist/esm/hooks/useRichSelectState.js +0 -60
  57. package/dist/esm/hooks/useRowSelection.js +0 -69
  58. package/dist/esm/hooks/useSelectState.js +0 -62
  59. package/dist/esm/hooks/useShallowEqualMemo.js +0 -14
  60. package/dist/esm/hooks/useSideBarState.js +0 -39
  61. package/dist/esm/hooks/useTableLayout.js +0 -69
  62. package/dist/esm/hooks/useTextFilterState.js +0 -25
  63. package/dist/esm/hooks/useUndoRedo.js +0 -84
  64. package/dist/esm/hooks/useVirtualScroll.js +0 -69
  65. package/dist/esm/storybook/index.js +0 -1
  66. package/dist/esm/storybook/mockData.js +0 -73
  67. package/dist/esm/types/columnTypes.js +0 -1
  68. package/dist/esm/types/dataGridTypes.js +0 -1
  69. package/dist/esm/types/index.js +0 -1
  70. package/dist/esm/utils/dataGridViewModel.js +0 -54
  71. package/dist/esm/utils/gridRowComparator.js +0 -2
  72. 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
- }