@alaarab/ogrid-core 1.9.0 → 2.0.0-beta

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 (87) hide show
  1. package/README.md +6 -20
  2. package/dist/esm/index.js +5 -15
  3. package/dist/esm/types/index.js +2 -1
  4. package/dist/esm/utils/clientSideData.js +1 -1
  5. package/dist/esm/utils/gridRowComparator.js +11 -1
  6. package/dist/esm/utils/index.js +1 -2
  7. package/dist/types/index.d.ts +3 -23
  8. package/dist/types/types/columnTypes.d.ts +3 -6
  9. package/dist/types/types/dataGridTypes.d.ts +1 -150
  10. package/dist/types/types/index.d.ts +3 -3
  11. package/dist/types/utils/gridRowComparator.d.ts +8 -0
  12. package/dist/types/utils/index.d.ts +1 -3
  13. package/package.json +6 -24
  14. package/dist/esm/components/BaseInlineCellEditor.js +0 -112
  15. package/dist/esm/components/CellErrorBoundary.js +0 -43
  16. package/dist/esm/components/EmptyState.js +0 -19
  17. package/dist/esm/components/GridContextMenu.js +0 -35
  18. package/dist/esm/components/MarchingAntsOverlay.js +0 -110
  19. package/dist/esm/components/OGridLayout.js +0 -91
  20. package/dist/esm/components/SideBar.js +0 -122
  21. package/dist/esm/components/StatusBar.js +0 -6
  22. package/dist/esm/hooks/index.js +0 -25
  23. package/dist/esm/hooks/useActiveCell.js +0 -62
  24. package/dist/esm/hooks/useCellEditing.js +0 -15
  25. package/dist/esm/hooks/useCellSelection.js +0 -324
  26. package/dist/esm/hooks/useClipboard.js +0 -162
  27. package/dist/esm/hooks/useColumnChooserState.js +0 -62
  28. package/dist/esm/hooks/useColumnHeaderFilterState.js +0 -180
  29. package/dist/esm/hooks/useColumnResize.js +0 -92
  30. package/dist/esm/hooks/useContextMenu.js +0 -21
  31. package/dist/esm/hooks/useDataGridState.js +0 -314
  32. package/dist/esm/hooks/useDateFilterState.js +0 -34
  33. package/dist/esm/hooks/useDebounce.js +0 -35
  34. package/dist/esm/hooks/useFillHandle.js +0 -196
  35. package/dist/esm/hooks/useFilterOptions.js +0 -40
  36. package/dist/esm/hooks/useInlineCellEditorState.js +0 -44
  37. package/dist/esm/hooks/useKeyboardNavigation.js +0 -420
  38. package/dist/esm/hooks/useLatestRef.js +0 -11
  39. package/dist/esm/hooks/useMultiSelectFilterState.js +0 -59
  40. package/dist/esm/hooks/useOGrid.js +0 -467
  41. package/dist/esm/hooks/usePeopleFilterState.js +0 -68
  42. package/dist/esm/hooks/useRichSelectState.js +0 -58
  43. package/dist/esm/hooks/useRowSelection.js +0 -78
  44. package/dist/esm/hooks/useSideBarState.js +0 -39
  45. package/dist/esm/hooks/useTableLayout.js +0 -77
  46. package/dist/esm/hooks/useTextFilterState.js +0 -25
  47. package/dist/esm/hooks/useUndoRedo.js +0 -83
  48. package/dist/esm/storybook/index.js +0 -1
  49. package/dist/esm/storybook/mockData.js +0 -73
  50. package/dist/esm/utils/dataGridViewModel.js +0 -233
  51. package/dist/types/components/BaseInlineCellEditor.d.ts +0 -33
  52. package/dist/types/components/CellErrorBoundary.d.ts +0 -25
  53. package/dist/types/components/EmptyState.d.ts +0 -26
  54. package/dist/types/components/GridContextMenu.d.ts +0 -18
  55. package/dist/types/components/MarchingAntsOverlay.d.ts +0 -15
  56. package/dist/types/components/OGridLayout.d.ts +0 -37
  57. package/dist/types/components/SideBar.d.ts +0 -30
  58. package/dist/types/components/StatusBar.d.ts +0 -24
  59. package/dist/types/hooks/index.d.ts +0 -48
  60. package/dist/types/hooks/useActiveCell.d.ts +0 -13
  61. package/dist/types/hooks/useCellEditing.d.ts +0 -16
  62. package/dist/types/hooks/useCellSelection.d.ts +0 -22
  63. package/dist/types/hooks/useClipboard.d.ts +0 -30
  64. package/dist/types/hooks/useColumnChooserState.d.ts +0 -27
  65. package/dist/types/hooks/useColumnHeaderFilterState.d.ts +0 -73
  66. package/dist/types/hooks/useColumnResize.d.ts +0 -23
  67. package/dist/types/hooks/useContextMenu.d.ts +0 -19
  68. package/dist/types/hooks/useDataGridState.d.ts +0 -137
  69. package/dist/types/hooks/useDateFilterState.d.ts +0 -19
  70. package/dist/types/hooks/useDebounce.d.ts +0 -9
  71. package/dist/types/hooks/useFillHandle.d.ts +0 -33
  72. package/dist/types/hooks/useFilterOptions.d.ts +0 -16
  73. package/dist/types/hooks/useInlineCellEditorState.d.ts +0 -24
  74. package/dist/types/hooks/useKeyboardNavigation.d.ts +0 -47
  75. package/dist/types/hooks/useLatestRef.d.ts +0 -6
  76. package/dist/types/hooks/useMultiSelectFilterState.d.ts +0 -24
  77. package/dist/types/hooks/useOGrid.d.ts +0 -52
  78. package/dist/types/hooks/usePeopleFilterState.d.ts +0 -25
  79. package/dist/types/hooks/useRichSelectState.d.ts +0 -22
  80. package/dist/types/hooks/useRowSelection.d.ts +0 -22
  81. package/dist/types/hooks/useSideBarState.d.ts +0 -20
  82. package/dist/types/hooks/useTableLayout.d.ts +0 -27
  83. package/dist/types/hooks/useTextFilterState.d.ts +0 -16
  84. package/dist/types/hooks/useUndoRedo.d.ts +0 -23
  85. package/dist/types/storybook/index.d.ts +0 -2
  86. package/dist/types/storybook/mockData.d.ts +0 -37
  87. package/dist/types/utils/dataGridViewModel.d.ts +0 -169
@@ -1,467 +0,0 @@
1
- import { useMemo, useCallback, useState, useEffect, useRef, useImperativeHandle, } from 'react';
2
- import { mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, } from '../utils/ogridHelpers';
3
- import { flattenColumns, processClientSideData } from '../utils';
4
- import { useFilterOptions } from './useFilterOptions';
5
- import { useSideBarState } from './useSideBarState';
6
- const DEFAULT_PAGE_SIZE = 25;
7
- const EMPTY_LOADING_OPTIONS = {};
8
- /**
9
- * Top-level orchestration hook for OGrid: manages pagination, sorting, filtering, column visibility, and sidebar.
10
- * @param props - All OGrid props (columns, data, callbacks, feature flags).
11
- * @param ref - Forwarded ref for imperative API (refresh, export, applyColumnState).
12
- * @returns Grouped props for DataGridTable, pagination controls, column chooser, layout, and filters.
13
- */
14
- export function useOGrid(props, ref) {
15
- 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, freezeRows, freezeCols, 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, statusBar, pageSizeOptions, sideBar, onFirstDataRendered, onError, columnChooser: columnChooserProp, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
16
- // Resolve column chooser placement
17
- const columnChooserPlacement = columnChooserProp === false ? 'none'
18
- : columnChooserProp === 'sidebar' ? 'sidebar'
19
- : 'toolbar';
20
- const columns = useMemo(() => flattenColumns(columnsProp), [columnsProp]);
21
- const isServerSide = dataSource != null;
22
- const isClientSide = !isServerSide;
23
- const [internalData, setInternalData] = useState([]);
24
- const [internalLoading, setInternalLoading] = useState(false);
25
- const displayData = data ?? internalData;
26
- const displayLoading = controlledLoading ?? internalLoading;
27
- const defaultSortField = defaultSortBy ?? columns[0]?.columnId ?? '';
28
- const [internalPage, setInternalPage] = useState(1);
29
- const [internalPageSize, setInternalPageSize] = useState(defaultPageSize);
30
- const [internalSort, setInternalSort] = useState({
31
- field: defaultSortField,
32
- direction: defaultSortDirection,
33
- });
34
- const [internalFilters, setInternalFilters] = useState({});
35
- const [internalVisibleColumns, setInternalVisibleColumns] = useState(() => {
36
- const visible = columns
37
- .filter((c) => c.defaultVisible !== false)
38
- .map((c) => c.columnId);
39
- return new Set(visible.length > 0 ? visible : columns.map((c) => c.columnId));
40
- });
41
- const [columnWidthOverrides, setColumnWidthOverrides] = useState({});
42
- const [pinnedOverrides, setPinnedOverrides] = useState({});
43
- const page = controlledPage ?? internalPage;
44
- const pageSize = controlledPageSize ?? internalPageSize;
45
- const sort = controlledSort ?? internalSort;
46
- const filters = controlledFilters ?? internalFilters;
47
- const visibleColumns = controlledVisibleColumns ?? internalVisibleColumns;
48
- const setPage = useCallback((p) => {
49
- if (controlledPage === undefined)
50
- setInternalPage(p);
51
- onPageChange?.(p);
52
- }, [controlledPage, onPageChange]);
53
- const setPageSize = useCallback((size) => {
54
- if (controlledPageSize === undefined)
55
- setInternalPageSize(size);
56
- onPageSizeChange?.(size);
57
- setPage(1);
58
- }, [controlledPageSize, onPageSizeChange, setPage]);
59
- const setSort = useCallback((s) => {
60
- if (controlledSort === undefined)
61
- setInternalSort(s);
62
- onSortChange?.(s);
63
- setPage(1);
64
- }, [controlledSort, onSortChange, setPage]);
65
- const setFilters = useCallback((f) => {
66
- if (controlledFilters === undefined)
67
- setInternalFilters(f);
68
- onFiltersChange?.(f);
69
- setPage(1);
70
- }, [controlledFilters, onFiltersChange, setPage]);
71
- const setVisibleColumns = useCallback((cols) => {
72
- if (controlledVisibleColumns === undefined)
73
- setInternalVisibleColumns(cols);
74
- onVisibleColumnsChange?.(cols);
75
- }, [controlledVisibleColumns, onVisibleColumnsChange]);
76
- const handleSort = useCallback((columnKey) => {
77
- setSort({
78
- field: columnKey,
79
- direction: sort.field === columnKey && sort.direction === 'asc' ? 'desc' : 'asc',
80
- });
81
- }, [sort, setSort]);
82
- /** Single filter change handler — wraps discriminated FilterValue into mergeFilter. */
83
- const handleFilterChange = useCallback((key, value) => {
84
- setFilters(mergeFilter(filters, key, value));
85
- }, [filters, setFilters]);
86
- const handleVisibilityChange = useCallback((columnKey, isVisible) => {
87
- const next = new Set(visibleColumns);
88
- if (isVisible)
89
- next.add(columnKey);
90
- else
91
- next.delete(columnKey);
92
- setVisibleColumns(next);
93
- }, [visibleColumns, setVisibleColumns]);
94
- const [internalSelectedRows, setInternalSelectedRows] = useState(new Set());
95
- const effectiveSelectedRows = selectedRows ?? internalSelectedRows;
96
- const handleSelectionChange = useCallback((event) => {
97
- if (selectedRows === undefined) {
98
- setInternalSelectedRows(new Set(event.selectedRowIds));
99
- }
100
- onSelectionChange?.(event);
101
- }, [selectedRows, onSelectionChange]);
102
- const multiSelectFilterFields = useMemo(() => getMultiSelectFilterFields(columns), [columns]);
103
- const filterOptionsSource = useMemo(() => dataSource ?? { fetchFilterOptions: undefined }, [dataSource]);
104
- const { filterOptions: serverFilterOptions, loadingOptions: loadingFilterOptions } = useFilterOptions(filterOptionsSource, multiSelectFilterFields);
105
- const hasServerFilterOptions = dataSource?.fetchFilterOptions != null;
106
- const clientFilterOptions = useMemo(() => {
107
- if (hasServerFilterOptions)
108
- return serverFilterOptions;
109
- return deriveFilterOptionsFromData(displayData, columns);
110
- }, [hasServerFilterOptions, displayData, columns, serverFilterOptions]);
111
- // --- Client-side filtering & sorting ---
112
- const clientItemsAndTotal = useMemo(() => {
113
- if (!isClientSide)
114
- return null;
115
- const rows = processClientSideData(displayData, columns, filters, sort.field, sort.direction);
116
- const total = rows.length;
117
- const start = (page - 1) * pageSize;
118
- const paged = rows.slice(start, start + pageSize);
119
- return { items: paged, totalCount: total };
120
- }, [
121
- isClientSide,
122
- displayData,
123
- columns,
124
- filters,
125
- sort.field,
126
- sort.direction,
127
- page,
128
- pageSize,
129
- ]);
130
- const [serverItems, setServerItems] = useState([]);
131
- const [serverTotalCount, setServerTotalCount] = useState(0);
132
- const [loading, setLoading] = useState(true);
133
- const fetchIdRef = useRef(0);
134
- // Ref counter to trigger server-side re-fetches
135
- const refreshCounterRef = useRef(0);
136
- const [refreshCounter, setRefreshCounter] = useState(0);
137
- useEffect(() => {
138
- if (!isServerSide || !dataSource) {
139
- if (!isServerSide)
140
- setLoading(false);
141
- return;
142
- }
143
- const id = ++fetchIdRef.current;
144
- setLoading(true);
145
- dataSource
146
- .fetchPage({
147
- page,
148
- pageSize,
149
- sort: { field: sort.field, direction: sort.direction },
150
- filters,
151
- })
152
- .then((res) => {
153
- if (id !== fetchIdRef.current)
154
- return;
155
- setServerItems(res.items);
156
- setServerTotalCount(res.totalCount);
157
- })
158
- .catch((err) => {
159
- if (id !== fetchIdRef.current)
160
- return;
161
- onError?.(err);
162
- setServerItems([]);
163
- setServerTotalCount(0);
164
- })
165
- .finally(() => {
166
- if (id === fetchIdRef.current)
167
- setLoading(false);
168
- });
169
- // eslint-disable-next-line react-hooks/exhaustive-deps
170
- }, [
171
- isServerSide,
172
- dataSource,
173
- page,
174
- pageSize,
175
- sort.field,
176
- sort.direction,
177
- filters,
178
- onError,
179
- refreshCounter,
180
- ]);
181
- const displayItems = isClientSide && clientItemsAndTotal
182
- ? clientItemsAndTotal.items
183
- : serverItems;
184
- const displayTotalCount = isClientSide && clientItemsAndTotal
185
- ? clientItemsAndTotal.totalCount
186
- : serverTotalCount;
187
- // Fire onFirstDataRendered once when the grid first has data
188
- const firstDataRenderedRef = useRef(false);
189
- useEffect(() => {
190
- if (!firstDataRenderedRef.current && displayItems.length > 0) {
191
- firstDataRenderedRef.current = true;
192
- onFirstDataRendered?.();
193
- }
194
- }, [displayItems.length, onFirstDataRendered]);
195
- useImperativeHandle(ref, () => ({
196
- setRowData: (d) => {
197
- if (!isServerSide)
198
- setInternalData(d);
199
- },
200
- setLoading: setInternalLoading,
201
- getColumnState: () => ({
202
- visibleColumns: Array.from(visibleColumns),
203
- sort,
204
- columnOrder: columnOrder ?? undefined,
205
- columnWidths: Object.keys(columnWidthOverrides).length > 0 ? columnWidthOverrides : undefined,
206
- filters: Object.keys(filters).length > 0 ? filters : undefined,
207
- pinnedColumns: Object.keys(pinnedOverrides).length > 0 ? pinnedOverrides : undefined,
208
- }),
209
- applyColumnState: (state) => {
210
- if (state.visibleColumns) {
211
- setVisibleColumns(new Set(state.visibleColumns));
212
- }
213
- if (state.sort) {
214
- setSort(state.sort);
215
- }
216
- if (state.columnOrder && onColumnOrderChange) {
217
- onColumnOrderChange(state.columnOrder);
218
- }
219
- if (state.columnWidths) {
220
- setColumnWidthOverrides(state.columnWidths);
221
- }
222
- if (state.filters) {
223
- setFilters(state.filters);
224
- }
225
- if (state.pinnedColumns) {
226
- setPinnedOverrides(state.pinnedColumns);
227
- }
228
- },
229
- setFilterModel: setFilters,
230
- getSelectedRows: () => Array.from(effectiveSelectedRows),
231
- setSelectedRows: (rowIds) => {
232
- if (selectedRows === undefined)
233
- setInternalSelectedRows(new Set(rowIds));
234
- },
235
- selectAll: () => {
236
- const allIds = new Set(displayItems.map((item) => getRowId(item)));
237
- if (selectedRows === undefined)
238
- setInternalSelectedRows(allIds);
239
- onSelectionChange?.({
240
- selectedRowIds: Array.from(allIds),
241
- selectedItems: displayItems,
242
- });
243
- },
244
- deselectAll: () => {
245
- if (selectedRows === undefined)
246
- setInternalSelectedRows(new Set());
247
- onSelectionChange?.({
248
- selectedRowIds: [],
249
- selectedItems: [],
250
- });
251
- },
252
- clearFilters: () => setFilters({}),
253
- clearSort: () => setSort({ field: defaultSortField, direction: defaultSortDirection }),
254
- resetGridState: (options) => {
255
- setFilters({});
256
- setSort({ field: defaultSortField, direction: defaultSortDirection });
257
- if (!options?.keepSelection) {
258
- if (selectedRows === undefined)
259
- setInternalSelectedRows(new Set());
260
- onSelectionChange?.({ selectedRowIds: [], selectedItems: [] });
261
- }
262
- },
263
- getDisplayedRows: () => displayItems,
264
- refreshData: () => {
265
- if (isServerSide) {
266
- refreshCounterRef.current += 1;
267
- setRefreshCounter(refreshCounterRef.current);
268
- }
269
- },
270
- }), [
271
- visibleColumns,
272
- sort,
273
- columnOrder,
274
- columnWidthOverrides,
275
- pinnedOverrides,
276
- filters,
277
- setFilters,
278
- setSort,
279
- setVisibleColumns,
280
- onColumnOrderChange,
281
- isServerSide,
282
- effectiveSelectedRows,
283
- selectedRows,
284
- displayItems,
285
- getRowId,
286
- onSelectionChange,
287
- defaultSortField,
288
- defaultSortDirection,
289
- ]);
290
- // With discriminated union, any defined value is active (mergeFilter already strips empties)
291
- const hasActiveFilters = useMemo(() => {
292
- return Object.values(filters).some((v) => v !== undefined);
293
- }, [filters]);
294
- const columnChooserColumns = useMemo(() => columns.map((c) => ({
295
- columnId: c.columnId,
296
- name: c.name,
297
- required: c.required === true,
298
- })), [columns]);
299
- const statusBarConfig = useMemo(() => {
300
- if (!statusBar)
301
- return undefined;
302
- if (typeof statusBar === 'object')
303
- return statusBar;
304
- const totalData = isClientSide ? (data?.length ?? 0) : serverTotalCount;
305
- const filteredData = displayTotalCount;
306
- return {
307
- totalCount: totalData,
308
- filteredCount: hasActiveFilters ? filteredData : undefined,
309
- selectedCount: effectiveSelectedRows.size,
310
- suppressRowCount: true, // OGrid always has pagination which shows the total
311
- };
312
- }, [
313
- statusBar,
314
- isClientSide,
315
- data,
316
- serverTotalCount,
317
- displayTotalCount,
318
- hasActiveFilters,
319
- effectiveSelectedRows.size,
320
- ]);
321
- const handleColumnResized = useCallback((columnId, width) => {
322
- setColumnWidthOverrides((prev) => ({ ...prev, [columnId]: width }));
323
- onColumnResized?.(columnId, width);
324
- }, [onColumnResized]);
325
- const handleColumnPinned = useCallback((columnId, pinned) => {
326
- setPinnedOverrides((prev) => {
327
- if (pinned === null) {
328
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
329
- const { [columnId]: _, ...rest } = prev;
330
- return rest;
331
- }
332
- return { ...prev, [columnId]: pinned };
333
- });
334
- onColumnPinned?.(columnId, pinned);
335
- }, [onColumnPinned]);
336
- // --- Side bar ---
337
- const sideBarState = useSideBarState({ config: sideBar });
338
- const filterableColumns = useMemo(() => columns
339
- .filter((c) => c.filterable && c.filterable.type)
340
- .map((c) => ({
341
- columnId: c.columnId,
342
- name: c.name,
343
- filterField: c.filterable.filterField ?? c.columnId,
344
- filterType: c.filterable.type,
345
- })), [columns]);
346
- const sideBarProps = useMemo(() => {
347
- if (!sideBarState.isEnabled)
348
- return null;
349
- return {
350
- activePanel: sideBarState.activePanel,
351
- onPanelChange: sideBarState.setActivePanel,
352
- panels: sideBarState.panels,
353
- position: sideBarState.position,
354
- columns: columnChooserColumns,
355
- visibleColumns,
356
- onVisibilityChange: handleVisibilityChange,
357
- onSetVisibleColumns: setVisibleColumns,
358
- filterableColumns,
359
- filters,
360
- onFilterChange: handleFilterChange,
361
- filterOptions: clientFilterOptions,
362
- };
363
- }, [
364
- sideBarState.isEnabled,
365
- sideBarState.activePanel,
366
- sideBarState.setActivePanel,
367
- sideBarState.panels,
368
- sideBarState.position,
369
- columnChooserColumns,
370
- visibleColumns,
371
- handleVisibilityChange,
372
- setVisibleColumns,
373
- filterableColumns,
374
- filters,
375
- handleFilterChange,
376
- clientFilterOptions,
377
- ]);
378
- const clearAllFilters = useCallback(() => setFilters({}), [setFilters]);
379
- const isLoadingResolved = (isServerSide && loading) || displayLoading;
380
- const dataGridProps = useMemo(() => ({
381
- items: displayItems,
382
- columns: columnsProp,
383
- getRowId,
384
- sortBy: sort.field,
385
- sortDirection: sort.direction,
386
- onColumnSort: handleSort,
387
- visibleColumns,
388
- columnOrder,
389
- onColumnOrderChange,
390
- onColumnResized: handleColumnResized,
391
- onColumnPinned: handleColumnPinned,
392
- pinnedColumns: pinnedOverrides,
393
- initialColumnWidths: columnWidthOverrides,
394
- freezeRows,
395
- freezeCols,
396
- editable,
397
- cellSelection,
398
- onCellValueChanged,
399
- onUndo,
400
- onRedo,
401
- canUndo,
402
- canRedo,
403
- rowSelection,
404
- selectedRows: effectiveSelectedRows,
405
- onSelectionChange: handleSelectionChange,
406
- statusBar: statusBarConfig,
407
- isLoading: isLoadingResolved,
408
- filters,
409
- onFilterChange: handleFilterChange,
410
- filterOptions: clientFilterOptions,
411
- loadingFilterOptions: dataSource?.fetchFilterOptions ? loadingFilterOptions : EMPTY_LOADING_OPTIONS,
412
- peopleSearch: dataSource?.searchPeople,
413
- getUserByEmail: dataSource?.getUserByEmail,
414
- layoutMode,
415
- suppressHorizontalScroll,
416
- 'aria-label': ariaLabel,
417
- 'aria-labelledby': ariaLabelledBy,
418
- emptyState: {
419
- hasActiveFilters,
420
- onClearAll: clearAllFilters,
421
- message: emptyState?.message,
422
- render: emptyState?.render,
423
- },
424
- }), [
425
- displayItems, columnsProp, getRowId, sort.field, sort.direction, handleSort,
426
- visibleColumns, columnOrder, onColumnOrderChange, handleColumnResized,
427
- handleColumnPinned, pinnedOverrides, columnWidthOverrides, freezeRows, freezeCols,
428
- editable, cellSelection, onCellValueChanged, onUndo, onRedo, canUndo, canRedo,
429
- rowSelection, effectiveSelectedRows, handleSelectionChange, statusBarConfig,
430
- isLoadingResolved, filters, handleFilterChange, clientFilterOptions, dataSource,
431
- loadingFilterOptions, layoutMode, suppressHorizontalScroll, ariaLabel, ariaLabelledBy,
432
- hasActiveFilters, clearAllFilters, emptyState,
433
- ]);
434
- const pagination = useMemo(() => ({
435
- page,
436
- pageSize,
437
- displayTotalCount,
438
- setPage,
439
- setPageSize,
440
- pageSizeOptions,
441
- entityLabelPlural,
442
- }), [page, pageSize, displayTotalCount, setPage, setPageSize, pageSizeOptions, entityLabelPlural]);
443
- const columnChooser = useMemo(() => ({
444
- columns: columnChooserColumns,
445
- visibleColumns,
446
- onVisibilityChange: handleVisibilityChange,
447
- placement: columnChooserPlacement,
448
- }), [columnChooserColumns, visibleColumns, handleVisibilityChange, columnChooserPlacement]);
449
- const layout = useMemo(() => ({
450
- toolbar,
451
- toolbarBelow,
452
- className,
453
- emptyState,
454
- sideBarProps,
455
- }), [toolbar, toolbarBelow, className, emptyState, sideBarProps]);
456
- const filtersResult = useMemo(() => ({
457
- hasActiveFilters,
458
- setFilters,
459
- }), [hasActiveFilters, setFilters]);
460
- return {
461
- dataGridProps,
462
- pagination,
463
- columnChooser,
464
- layout,
465
- filters: filtersResult,
466
- };
467
- }
@@ -1,68 +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
- const PEOPLE_SEARCH_DEBOUNCE_MS = 300;
8
- export function usePeopleFilterState(params) {
9
- const { selectedUser, onUserChange, peopleSearch, isFilterOpen, filterType } = params;
10
- const peopleInputRef = useRef(null);
11
- const peopleSearchTimeoutRef = useRef(undefined);
12
- const [peopleSuggestions, setPeopleSuggestions] = useState([]);
13
- const [isPeopleLoading, setIsPeopleLoading] = useState(false);
14
- const [peopleSearchText, setPeopleSearchText] = useState('');
15
- // Sync temp state when popover opens
16
- useEffect(() => {
17
- if (isFilterOpen) {
18
- setPeopleSearchText('');
19
- setPeopleSuggestions([]);
20
- if (filterType === 'people') {
21
- setTimeout(() => peopleInputRef.current?.focus(), 50);
22
- }
23
- }
24
- }, [isFilterOpen, filterType]);
25
- // People search with debounce
26
- useEffect(() => {
27
- if (!peopleSearch || !isFilterOpen || filterType !== 'people')
28
- return;
29
- if (peopleSearchTimeoutRef.current)
30
- window.clearTimeout(peopleSearchTimeoutRef.current);
31
- if (!peopleSearchText.trim()) {
32
- setPeopleSuggestions([]);
33
- return;
34
- }
35
- setIsPeopleLoading(true);
36
- peopleSearchTimeoutRef.current = window.setTimeout(async () => {
37
- try {
38
- const results = await peopleSearch(peopleSearchText);
39
- setPeopleSuggestions(results.slice(0, 10));
40
- }
41
- catch {
42
- setPeopleSuggestions([]);
43
- }
44
- finally {
45
- setIsPeopleLoading(false);
46
- }
47
- }, PEOPLE_SEARCH_DEBOUNCE_MS);
48
- return () => {
49
- if (peopleSearchTimeoutRef.current)
50
- window.clearTimeout(peopleSearchTimeoutRef.current);
51
- };
52
- }, [peopleSearchText, peopleSearch, isFilterOpen, filterType]);
53
- const handleUserSelect = useCallback((user) => {
54
- onUserChange?.(user);
55
- }, [onUserChange]);
56
- const handleClearUser = useCallback(() => {
57
- onUserChange?.(undefined);
58
- }, [onUserChange]);
59
- return {
60
- peopleSuggestions,
61
- isPeopleLoading,
62
- peopleSearchText,
63
- setPeopleSearchText,
64
- peopleInputRef,
65
- handleUserSelect,
66
- handleClearUser,
67
- };
68
- }
@@ -1,58 +0,0 @@
1
- import { useState, useCallback, useMemo } from 'react';
2
- /**
3
- * Manages searchable rich select editor state with keyboard navigation (arrow keys, enter, escape).
4
- * @param params - Values, format function, initial value, and commit/cancel callbacks.
5
- * @returns Search text, filtered values, highlighted index, keyboard handler, and select function.
6
- */
7
- export function useRichSelectState(params) {
8
- const { values, formatValue, onCommit, onCancel } = params;
9
- const [searchText, setSearchText] = useState('');
10
- const [highlightedIndex, setHighlightedIndex] = useState(0);
11
- const getDisplayText = useCallback((value) => {
12
- if (formatValue)
13
- return formatValue(value);
14
- return value != null ? String(value) : '';
15
- }, [formatValue]);
16
- const filteredValues = useMemo(() => {
17
- if (!searchText.trim())
18
- return values;
19
- const lower = searchText.toLowerCase();
20
- return values.filter((v) => getDisplayText(v).toLowerCase().includes(lower));
21
- }, [values, searchText, getDisplayText]);
22
- const selectValue = useCallback((value) => {
23
- onCommit(value);
24
- }, [onCommit]);
25
- const handleKeyDown = useCallback((e) => {
26
- switch (e.key) {
27
- case 'ArrowDown':
28
- e.preventDefault();
29
- setHighlightedIndex((prev) => Math.min(prev + 1, filteredValues.length - 1));
30
- break;
31
- case 'ArrowUp':
32
- e.preventDefault();
33
- setHighlightedIndex((prev) => Math.max(prev - 1, 0));
34
- break;
35
- case 'Enter':
36
- e.preventDefault();
37
- e.stopPropagation();
38
- if (filteredValues.length > 0 && highlightedIndex < filteredValues.length) {
39
- selectValue(filteredValues[highlightedIndex]);
40
- }
41
- break;
42
- case 'Escape':
43
- e.preventDefault();
44
- e.stopPropagation();
45
- onCancel();
46
- break;
47
- }
48
- }, [filteredValues, highlightedIndex, selectValue, onCancel]);
49
- return {
50
- searchText,
51
- setSearchText,
52
- filteredValues,
53
- highlightedIndex,
54
- handleKeyDown,
55
- selectValue,
56
- getDisplayText,
57
- };
58
- }
@@ -1,78 +0,0 @@
1
- import { useState, useCallback, useRef, useMemo } from 'react';
2
- import { useLatestRef } from './useLatestRef';
3
- /**
4
- * Manages row selection state for single or multiple selection modes with shift-click range support.
5
- * @param params - Items, getRowId, selection mode, controlled state, and selection change callback.
6
- * @returns Selected row IDs, update function, checkbox handlers, and selection state booleans.
7
- */
8
- export function useRowSelection(params) {
9
- const { items, getRowId, rowSelection, controlledSelectedRows, onSelectionChange, } = params;
10
- const [internalSelectedRows, setInternalSelectedRows] = useState(new Set());
11
- const lastClickedRowRef = useRef(-1);
12
- // Defensive: convert to Set if caller passes an array (e.g. from JSON state)
13
- const selectedRowIds = useMemo(() => controlledSelectedRows != null
14
- ? controlledSelectedRows instanceof Set
15
- ? controlledSelectedRows
16
- : new Set(controlledSelectedRows)
17
- : internalSelectedRows, [controlledSelectedRows, internalSelectedRows]);
18
- const updateSelection = useCallback((newSelectedIds) => {
19
- if (controlledSelectedRows === undefined) {
20
- setInternalSelectedRows(newSelectedIds);
21
- }
22
- onSelectionChange?.({
23
- selectedRowIds: Array.from(newSelectedIds),
24
- selectedItems: items.filter((item) => newSelectedIds.has(getRowId(item))),
25
- });
26
- }, [controlledSelectedRows, onSelectionChange, items, getRowId]);
27
- // Read selectedRowIds via ref to avoid recreating this callback on every selection change
28
- const selectedRowIdsRef = useLatestRef(selectedRowIds);
29
- const itemsRef = useLatestRef(items);
30
- const handleRowCheckboxChange = useCallback((rowId, checked, rowIndex, shiftKey) => {
31
- if (rowSelection === 'single') {
32
- updateSelection(checked ? new Set([rowId]) : new Set());
33
- lastClickedRowRef.current = rowIndex;
34
- return;
35
- }
36
- const next = new Set(selectedRowIdsRef.current);
37
- const currentItems = itemsRef.current;
38
- if (shiftKey && lastClickedRowRef.current >= 0 && lastClickedRowRef.current !== rowIndex) {
39
- const start = Math.min(lastClickedRowRef.current, rowIndex);
40
- const end = Math.max(lastClickedRowRef.current, rowIndex);
41
- for (let i = start; i <= end; i++) {
42
- if (i < currentItems.length) {
43
- const id = getRowId(currentItems[i]);
44
- if (checked)
45
- next.add(id);
46
- else
47
- next.delete(id);
48
- }
49
- }
50
- }
51
- else {
52
- if (checked)
53
- next.add(rowId);
54
- else
55
- next.delete(rowId);
56
- }
57
- lastClickedRowRef.current = rowIndex;
58
- updateSelection(next);
59
- }, [rowSelection, getRowId, updateSelection]);
60
- const handleSelectAll = useCallback((checked) => {
61
- if (checked) {
62
- updateSelection(new Set(items.map((item) => getRowId(item))));
63
- }
64
- else {
65
- updateSelection(new Set());
66
- }
67
- }, [items, getRowId, updateSelection]);
68
- const allSelected = useMemo(() => items.length > 0 && items.every((item) => selectedRowIds.has(getRowId(item))), [items, selectedRowIds, getRowId]);
69
- const someSelected = useMemo(() => !allSelected && items.some((item) => selectedRowIds.has(getRowId(item))), [allSelected, items, selectedRowIds, getRowId]);
70
- return {
71
- selectedRowIds,
72
- updateSelection,
73
- handleRowCheckboxChange,
74
- handleSelectAll,
75
- allSelected,
76
- someSelected,
77
- };
78
- }