@alaarab/ogrid-react 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 (88) hide show
  1. package/README.md +55 -0
  2. package/dist/esm/components/BaseInlineCellEditor.js +112 -0
  3. package/dist/esm/components/CellErrorBoundary.js +43 -0
  4. package/dist/esm/components/EmptyState.js +19 -0
  5. package/dist/esm/components/GridContextMenu.js +35 -0
  6. package/dist/esm/components/MarchingAntsOverlay.js +110 -0
  7. package/dist/esm/components/OGridLayout.js +91 -0
  8. package/dist/esm/components/SideBar.js +122 -0
  9. package/dist/esm/components/StatusBar.js +6 -0
  10. package/dist/esm/hooks/index.js +25 -0
  11. package/dist/esm/hooks/useActiveCell.js +62 -0
  12. package/dist/esm/hooks/useCellEditing.js +15 -0
  13. package/dist/esm/hooks/useCellSelection.js +327 -0
  14. package/dist/esm/hooks/useClipboard.js +161 -0
  15. package/dist/esm/hooks/useColumnChooserState.js +62 -0
  16. package/dist/esm/hooks/useColumnHeaderFilterState.js +180 -0
  17. package/dist/esm/hooks/useColumnResize.js +92 -0
  18. package/dist/esm/hooks/useContextMenu.js +21 -0
  19. package/dist/esm/hooks/useDataGridState.js +313 -0
  20. package/dist/esm/hooks/useDateFilterState.js +34 -0
  21. package/dist/esm/hooks/useDebounce.js +35 -0
  22. package/dist/esm/hooks/useFillHandle.js +195 -0
  23. package/dist/esm/hooks/useFilterOptions.js +40 -0
  24. package/dist/esm/hooks/useInlineCellEditorState.js +44 -0
  25. package/dist/esm/hooks/useKeyboardNavigation.js +419 -0
  26. package/dist/esm/hooks/useLatestRef.js +11 -0
  27. package/dist/esm/hooks/useMultiSelectFilterState.js +59 -0
  28. package/dist/esm/hooks/useOGrid.js +465 -0
  29. package/dist/esm/hooks/usePeopleFilterState.js +68 -0
  30. package/dist/esm/hooks/useRichSelectState.js +58 -0
  31. package/dist/esm/hooks/useRowSelection.js +80 -0
  32. package/dist/esm/hooks/useSideBarState.js +39 -0
  33. package/dist/esm/hooks/useTableLayout.js +77 -0
  34. package/dist/esm/hooks/useTextFilterState.js +25 -0
  35. package/dist/esm/hooks/useUndoRedo.js +83 -0
  36. package/dist/esm/index.js +16 -0
  37. package/dist/esm/storybook/index.js +1 -0
  38. package/dist/esm/storybook/mockData.js +73 -0
  39. package/dist/esm/types/columnTypes.js +1 -0
  40. package/dist/esm/types/dataGridTypes.js +1 -0
  41. package/dist/esm/types/index.js +1 -0
  42. package/dist/esm/utils/dataGridViewModel.js +220 -0
  43. package/dist/esm/utils/gridRowComparator.js +2 -0
  44. package/dist/esm/utils/index.js +5 -0
  45. package/dist/types/components/BaseInlineCellEditor.d.ts +33 -0
  46. package/dist/types/components/CellErrorBoundary.d.ts +25 -0
  47. package/dist/types/components/EmptyState.d.ts +26 -0
  48. package/dist/types/components/GridContextMenu.d.ts +18 -0
  49. package/dist/types/components/MarchingAntsOverlay.d.ts +15 -0
  50. package/dist/types/components/OGridLayout.d.ts +37 -0
  51. package/dist/types/components/SideBar.d.ts +30 -0
  52. package/dist/types/components/StatusBar.d.ts +24 -0
  53. package/dist/types/hooks/index.d.ts +48 -0
  54. package/dist/types/hooks/useActiveCell.d.ts +13 -0
  55. package/dist/types/hooks/useCellEditing.d.ts +16 -0
  56. package/dist/types/hooks/useCellSelection.d.ts +22 -0
  57. package/dist/types/hooks/useClipboard.d.ts +30 -0
  58. package/dist/types/hooks/useColumnChooserState.d.ts +27 -0
  59. package/dist/types/hooks/useColumnHeaderFilterState.d.ts +73 -0
  60. package/dist/types/hooks/useColumnResize.d.ts +23 -0
  61. package/dist/types/hooks/useContextMenu.d.ts +19 -0
  62. package/dist/types/hooks/useDataGridState.d.ts +137 -0
  63. package/dist/types/hooks/useDateFilterState.d.ts +19 -0
  64. package/dist/types/hooks/useDebounce.d.ts +9 -0
  65. package/dist/types/hooks/useFillHandle.d.ts +33 -0
  66. package/dist/types/hooks/useFilterOptions.d.ts +16 -0
  67. package/dist/types/hooks/useInlineCellEditorState.d.ts +24 -0
  68. package/dist/types/hooks/useKeyboardNavigation.d.ts +47 -0
  69. package/dist/types/hooks/useLatestRef.d.ts +6 -0
  70. package/dist/types/hooks/useMultiSelectFilterState.d.ts +24 -0
  71. package/dist/types/hooks/useOGrid.d.ts +52 -0
  72. package/dist/types/hooks/usePeopleFilterState.d.ts +25 -0
  73. package/dist/types/hooks/useRichSelectState.d.ts +22 -0
  74. package/dist/types/hooks/useRowSelection.d.ts +22 -0
  75. package/dist/types/hooks/useSideBarState.d.ts +20 -0
  76. package/dist/types/hooks/useTableLayout.d.ts +27 -0
  77. package/dist/types/hooks/useTextFilterState.d.ts +16 -0
  78. package/dist/types/hooks/useUndoRedo.d.ts +23 -0
  79. package/dist/types/index.d.ts +23 -0
  80. package/dist/types/storybook/index.d.ts +2 -0
  81. package/dist/types/storybook/mockData.d.ts +37 -0
  82. package/dist/types/types/columnTypes.d.ts +25 -0
  83. package/dist/types/types/dataGridTypes.d.ts +152 -0
  84. package/dist/types/types/index.d.ts +3 -0
  85. package/dist/types/utils/dataGridViewModel.d.ts +161 -0
  86. package/dist/types/utils/gridRowComparator.d.ts +2 -0
  87. package/dist/types/utils/index.d.ts +6 -0
  88. package/package.json +46 -0
@@ -0,0 +1,419 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import { normalizeSelectionRange } from '../types';
3
+ import { getCellValue, parseValue } from '../utils';
4
+ /**
5
+ * Excel-style Ctrl+Arrow: find the target position along a 1D axis.
6
+ * - Non-empty current + non-empty next → scan through non-empties, stop at last before empty/edge.
7
+ * - Otherwise → skip empties, land on next non-empty or edge.
8
+ */
9
+ function findCtrlTarget(pos, edge, step, isEmpty) {
10
+ if (pos === edge)
11
+ return pos;
12
+ const next = pos + step;
13
+ if (!isEmpty(pos) && !isEmpty(next)) {
14
+ let p = next;
15
+ while (p !== edge) {
16
+ if (isEmpty(p + step))
17
+ return p;
18
+ p += step;
19
+ }
20
+ return edge;
21
+ }
22
+ let p = next;
23
+ while (p !== edge) {
24
+ if (!isEmpty(p))
25
+ return p;
26
+ p += step;
27
+ }
28
+ return edge;
29
+ }
30
+ /**
31
+ * Handles all keyboard navigation, shortcuts, and cell editing triggers for the grid.
32
+ * @param params - Grouped data, state, handlers, and feature flags for keyboard interactions.
33
+ * @returns Keyboard event handler for the grid wrapper.
34
+ */
35
+ export function useKeyboardNavigation(params) {
36
+ // Store latest params in a ref so handleGridKeyDown is a stable callback
37
+ const paramsRef = useRef(params);
38
+ paramsRef.current = params;
39
+ const handleGridKeyDown = useCallback((e) => {
40
+ const { data, state, handlers, features } = paramsRef.current;
41
+ const { items, visibleCols, colOffset, hasCheckboxCol, visibleColumnCount, getRowId } = data;
42
+ const { activeCell, selectionRange, editingCell, selectedRowIds } = state;
43
+ const { setActiveCell, setSelectionRange, setEditingCell, handleRowCheckboxChange, handleCopy, handleCut, handlePaste, setContextMenu, onUndo, onRedo, clearClipboardRanges } = handlers;
44
+ const { editable, onCellValueChanged, rowSelection, wrapperRef } = features;
45
+ const maxRowIndex = items.length - 1;
46
+ const maxColIndex = visibleColumnCount - 1 + colOffset;
47
+ if (items.length === 0)
48
+ return;
49
+ if (activeCell === null) {
50
+ if ([
51
+ 'ArrowDown',
52
+ 'ArrowUp',
53
+ 'ArrowLeft',
54
+ 'ArrowRight',
55
+ 'Tab',
56
+ 'Enter',
57
+ 'Home',
58
+ 'End',
59
+ ].includes(e.key)) {
60
+ setActiveCell({ rowIndex: 0, columnIndex: colOffset });
61
+ e.preventDefault();
62
+ }
63
+ return;
64
+ }
65
+ const { rowIndex, columnIndex } = activeCell;
66
+ const dataColIndex = columnIndex - colOffset;
67
+ const shift = e.shiftKey;
68
+ const isEmptyAt = (r, c) => {
69
+ if (r < 0 || r >= items.length || c < 0 || c >= visibleCols.length)
70
+ return true;
71
+ const v = getCellValue(items[r], visibleCols[c]);
72
+ return v == null || v === '';
73
+ };
74
+ switch (e.key) {
75
+ case 'c':
76
+ if (e.ctrlKey || e.metaKey) {
77
+ if (editingCell != null)
78
+ break; // let the input handle copy
79
+ e.preventDefault();
80
+ handleCopy();
81
+ }
82
+ break;
83
+ case 'x':
84
+ if (e.ctrlKey || e.metaKey) {
85
+ if (editingCell != null)
86
+ break; // let the input handle cut
87
+ e.preventDefault();
88
+ handleCut();
89
+ }
90
+ break;
91
+ case 'v':
92
+ if (e.ctrlKey || e.metaKey) {
93
+ if (editingCell != null)
94
+ break; // let the input handle paste
95
+ e.preventDefault();
96
+ void handlePaste();
97
+ }
98
+ break;
99
+ case 'ArrowDown': {
100
+ e.preventDefault();
101
+ const ctrl = e.ctrlKey || e.metaKey;
102
+ const newRow = ctrl
103
+ ? findCtrlTarget(rowIndex, maxRowIndex, 1, (r) => isEmptyAt(r, Math.max(0, dataColIndex)))
104
+ : Math.min(rowIndex + 1, maxRowIndex);
105
+ if (shift) {
106
+ setSelectionRange(normalizeSelectionRange({
107
+ startRow: selectionRange?.startRow ?? rowIndex,
108
+ startCol: selectionRange?.startCol ?? dataColIndex,
109
+ endRow: newRow,
110
+ endCol: selectionRange?.endCol ?? dataColIndex,
111
+ }));
112
+ }
113
+ else {
114
+ setSelectionRange({
115
+ startRow: newRow,
116
+ startCol: dataColIndex,
117
+ endRow: newRow,
118
+ endCol: dataColIndex,
119
+ });
120
+ }
121
+ setActiveCell({ rowIndex: newRow, columnIndex });
122
+ break;
123
+ }
124
+ case 'ArrowUp': {
125
+ e.preventDefault();
126
+ const ctrl = e.ctrlKey || e.metaKey;
127
+ const newRowUp = ctrl
128
+ ? findCtrlTarget(rowIndex, 0, -1, (r) => isEmptyAt(r, Math.max(0, dataColIndex)))
129
+ : Math.max(rowIndex - 1, 0);
130
+ if (shift) {
131
+ setSelectionRange(normalizeSelectionRange({
132
+ startRow: selectionRange?.startRow ?? rowIndex,
133
+ startCol: selectionRange?.startCol ?? dataColIndex,
134
+ endRow: newRowUp,
135
+ endCol: selectionRange?.endCol ?? dataColIndex,
136
+ }));
137
+ }
138
+ else {
139
+ setSelectionRange({
140
+ startRow: newRowUp,
141
+ startCol: dataColIndex,
142
+ endRow: newRowUp,
143
+ endCol: dataColIndex,
144
+ });
145
+ }
146
+ setActiveCell({ rowIndex: newRowUp, columnIndex });
147
+ break;
148
+ }
149
+ case 'ArrowRight': {
150
+ e.preventDefault();
151
+ const ctrl = e.ctrlKey || e.metaKey;
152
+ let newCol;
153
+ if (ctrl && dataColIndex >= 0) {
154
+ newCol = findCtrlTarget(dataColIndex, visibleCols.length - 1, 1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
155
+ }
156
+ else {
157
+ newCol = Math.min(columnIndex + 1, maxColIndex);
158
+ }
159
+ const newDataCol = newCol - colOffset;
160
+ if (shift) {
161
+ setSelectionRange(normalizeSelectionRange({
162
+ startRow: selectionRange?.startRow ?? rowIndex,
163
+ startCol: selectionRange?.startCol ?? dataColIndex,
164
+ endRow: selectionRange?.endRow ?? rowIndex,
165
+ endCol: newDataCol,
166
+ }));
167
+ }
168
+ else {
169
+ setSelectionRange({
170
+ startRow: rowIndex,
171
+ startCol: newDataCol,
172
+ endRow: rowIndex,
173
+ endCol: newDataCol,
174
+ });
175
+ }
176
+ setActiveCell({ rowIndex, columnIndex: newCol });
177
+ break;
178
+ }
179
+ case 'ArrowLeft': {
180
+ e.preventDefault();
181
+ const ctrl = e.ctrlKey || e.metaKey;
182
+ let newColLeft;
183
+ if (ctrl && dataColIndex >= 0) {
184
+ newColLeft = findCtrlTarget(dataColIndex, 0, -1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
185
+ }
186
+ else {
187
+ newColLeft = Math.max(columnIndex - 1, colOffset);
188
+ }
189
+ const newDataColLeft = newColLeft - colOffset;
190
+ if (shift) {
191
+ setSelectionRange(normalizeSelectionRange({
192
+ startRow: selectionRange?.startRow ?? rowIndex,
193
+ startCol: selectionRange?.startCol ?? dataColIndex,
194
+ endRow: selectionRange?.endRow ?? rowIndex,
195
+ endCol: newDataColLeft,
196
+ }));
197
+ }
198
+ else {
199
+ setSelectionRange({
200
+ startRow: rowIndex,
201
+ startCol: newDataColLeft,
202
+ endRow: rowIndex,
203
+ endCol: newDataColLeft,
204
+ });
205
+ }
206
+ setActiveCell({ rowIndex, columnIndex: newColLeft });
207
+ break;
208
+ }
209
+ case 'Tab': {
210
+ e.preventDefault();
211
+ let newRowTab = rowIndex;
212
+ let newColTab = columnIndex;
213
+ if (e.shiftKey) {
214
+ if (columnIndex > colOffset) {
215
+ newColTab = columnIndex - 1;
216
+ }
217
+ else if (rowIndex > 0) {
218
+ newRowTab = rowIndex - 1;
219
+ newColTab = maxColIndex;
220
+ }
221
+ }
222
+ else {
223
+ if (columnIndex < maxColIndex) {
224
+ newColTab = columnIndex + 1;
225
+ }
226
+ else if (rowIndex < maxRowIndex) {
227
+ newRowTab = rowIndex + 1;
228
+ newColTab = colOffset;
229
+ }
230
+ }
231
+ const newDataColTab = newColTab - colOffset;
232
+ setSelectionRange({
233
+ startRow: newRowTab,
234
+ startCol: newDataColTab,
235
+ endRow: newRowTab,
236
+ endCol: newDataColTab,
237
+ });
238
+ setActiveCell({ rowIndex: newRowTab, columnIndex: newColTab });
239
+ break;
240
+ }
241
+ case 'Home': {
242
+ e.preventDefault();
243
+ const newRowHome = e.ctrlKey ? 0 : rowIndex;
244
+ setSelectionRange({
245
+ startRow: newRowHome,
246
+ startCol: 0,
247
+ endRow: newRowHome,
248
+ endCol: 0,
249
+ });
250
+ setActiveCell({ rowIndex: newRowHome, columnIndex: colOffset });
251
+ break;
252
+ }
253
+ case 'End': {
254
+ e.preventDefault();
255
+ const newRowEnd = e.ctrlKey ? maxRowIndex : rowIndex;
256
+ setSelectionRange({
257
+ startRow: newRowEnd,
258
+ startCol: visibleColumnCount - 1,
259
+ endRow: newRowEnd,
260
+ endCol: visibleColumnCount - 1,
261
+ });
262
+ setActiveCell({ rowIndex: newRowEnd, columnIndex: maxColIndex });
263
+ break;
264
+ }
265
+ case 'Enter':
266
+ case 'F2': {
267
+ e.preventDefault();
268
+ if (dataColIndex >= 0 && dataColIndex < visibleCols.length) {
269
+ const col = visibleCols[dataColIndex];
270
+ const item = items[rowIndex];
271
+ if (item && col) {
272
+ const colEditable = col.editable === true ||
273
+ (typeof col.editable === 'function' && col.editable(item));
274
+ if (editable !== false &&
275
+ colEditable &&
276
+ onCellValueChanged != null) {
277
+ setEditingCell({ rowId: getRowId(item), columnId: col.columnId });
278
+ }
279
+ }
280
+ }
281
+ break;
282
+ }
283
+ case 'Escape':
284
+ e.preventDefault();
285
+ if (editingCell != null) {
286
+ setEditingCell(null);
287
+ }
288
+ else {
289
+ clearClipboardRanges?.();
290
+ setActiveCell(null);
291
+ setSelectionRange(null);
292
+ }
293
+ break;
294
+ case ' ':
295
+ if (rowSelection !== 'none' &&
296
+ columnIndex === 0 &&
297
+ hasCheckboxCol) {
298
+ e.preventDefault();
299
+ const item = items[rowIndex];
300
+ if (item) {
301
+ const id = getRowId(item);
302
+ const isSelected = selectedRowIds.has(id);
303
+ handleRowCheckboxChange(id, !isSelected, rowIndex, e.shiftKey);
304
+ }
305
+ }
306
+ break;
307
+ case 'z':
308
+ if (e.ctrlKey || e.metaKey) {
309
+ if (editingCell == null) {
310
+ if (e.shiftKey && onRedo) {
311
+ e.preventDefault();
312
+ onRedo();
313
+ }
314
+ else if (!e.shiftKey && onUndo) {
315
+ e.preventDefault();
316
+ onUndo();
317
+ }
318
+ }
319
+ }
320
+ break;
321
+ case 'y':
322
+ if (e.ctrlKey || e.metaKey) {
323
+ if (editingCell == null && onRedo) {
324
+ e.preventDefault();
325
+ onRedo();
326
+ }
327
+ }
328
+ break;
329
+ case 'a':
330
+ if (e.ctrlKey || e.metaKey) {
331
+ if (editingCell != null)
332
+ break; // let the input handle select-all
333
+ e.preventDefault();
334
+ if (items.length > 0 && visibleColumnCount > 0) {
335
+ setSelectionRange({
336
+ startRow: 0,
337
+ startCol: 0,
338
+ endRow: items.length - 1,
339
+ endCol: visibleColumnCount - 1,
340
+ });
341
+ setActiveCell({ rowIndex: 0, columnIndex: colOffset });
342
+ }
343
+ }
344
+ break;
345
+ case 'Delete':
346
+ case 'Backspace': {
347
+ if (editingCell != null)
348
+ break;
349
+ if (editable === false)
350
+ break;
351
+ if (onCellValueChanged == null)
352
+ break;
353
+ const range = selectionRange ??
354
+ (activeCell != null
355
+ ? {
356
+ startRow: activeCell.rowIndex,
357
+ startCol: activeCell.columnIndex - colOffset,
358
+ endRow: activeCell.rowIndex,
359
+ endCol: activeCell.columnIndex - colOffset,
360
+ }
361
+ : null);
362
+ if (range == null)
363
+ break;
364
+ e.preventDefault();
365
+ const norm = normalizeSelectionRange(range);
366
+ for (let r = norm.startRow; r <= norm.endRow; r++) {
367
+ for (let c = norm.startCol; c <= norm.endCol; c++) {
368
+ if (r >= items.length || c >= visibleCols.length)
369
+ continue;
370
+ const item = items[r];
371
+ const col = visibleCols[c];
372
+ const colEditable = col.editable === true ||
373
+ (typeof col.editable === 'function' && col.editable(item));
374
+ if (!colEditable)
375
+ continue;
376
+ const oldValue = getCellValue(item, col);
377
+ const result = parseValue('', oldValue, item, col);
378
+ if (!result.valid)
379
+ continue;
380
+ onCellValueChanged({
381
+ item,
382
+ columnId: col.columnId,
383
+ oldValue,
384
+ newValue: result.value,
385
+ rowIndex: r,
386
+ });
387
+ }
388
+ }
389
+ break;
390
+ }
391
+ case 'F10':
392
+ if (e.shiftKey) {
393
+ e.preventDefault();
394
+ if (activeCell != null && wrapperRef.current) {
395
+ const sel = `[data-row-index="${activeCell.rowIndex}"][data-col-index="${activeCell.columnIndex}"]`;
396
+ const cell = wrapperRef.current.querySelector(sel);
397
+ if (cell) {
398
+ const rect = cell.getBoundingClientRect();
399
+ setContextMenu({
400
+ x: rect.left + rect.width / 2,
401
+ y: rect.top + rect.height / 2,
402
+ });
403
+ }
404
+ else {
405
+ setContextMenu({ x: 100, y: 100 });
406
+ }
407
+ }
408
+ else {
409
+ setContextMenu({ x: 100, y: 100 });
410
+ }
411
+ }
412
+ break;
413
+ default:
414
+ break;
415
+ }
416
+ }, [] // stable — reads latest values from paramsRef
417
+ );
418
+ return { handleGridKeyDown };
419
+ }
@@ -0,0 +1,11 @@
1
+ import { useRef } from 'react';
2
+ /**
3
+ * Returns a ref that always holds the latest value.
4
+ * Useful for capturing volatile state in stable callbacks
5
+ * without adding the value to dependency arrays.
6
+ */
7
+ export function useLatestRef(value) {
8
+ const ref = useRef(value);
9
+ ref.current = value;
10
+ return ref;
11
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Multi-select filter state sub-hook for column header filters.
3
+ * Manages temporary selection set, search text, debounced search, filtered options, and select/clear handlers.
4
+ */
5
+ import { useState, useCallback, useEffect, useMemo } from 'react';
6
+ import { useDebounce } from './useDebounce';
7
+ const SEARCH_DEBOUNCE_MS = 150;
8
+ const EMPTY_OPTIONS = [];
9
+ export function useMultiSelectFilterState(params) {
10
+ const { selectedValues, onFilterChange, options, isFilterOpen } = params;
11
+ const safeSelectedValues = selectedValues ?? EMPTY_OPTIONS;
12
+ const safeOptions = options ?? EMPTY_OPTIONS;
13
+ const [tempSelected, setTempSelected] = useState(() => new Set(safeSelectedValues));
14
+ const [searchText, setSearchText] = useState('');
15
+ const debouncedSearchText = useDebounce(searchText, SEARCH_DEBOUNCE_MS);
16
+ // Sync temp state when popover opens
17
+ useEffect(() => {
18
+ if (isFilterOpen) {
19
+ setTempSelected(new Set(safeSelectedValues));
20
+ setSearchText('');
21
+ }
22
+ }, [isFilterOpen, safeSelectedValues]);
23
+ // Filtered options for multiSelect (search within options)
24
+ const filteredOptions = useMemo(() => {
25
+ if (!debouncedSearchText.trim())
26
+ return safeOptions;
27
+ const searchLower = debouncedSearchText.toLowerCase().trim();
28
+ return safeOptions.filter((opt) => opt.toLowerCase().includes(searchLower));
29
+ }, [safeOptions, debouncedSearchText]);
30
+ const handleCheckboxChange = useCallback((option, checked) => {
31
+ setTempSelected((prev) => {
32
+ const next = new Set(prev);
33
+ if (checked)
34
+ next.add(option);
35
+ else
36
+ next.delete(option);
37
+ return next;
38
+ });
39
+ }, []);
40
+ const handleSelectAll = useCallback(() => {
41
+ setTempSelected(new Set(filteredOptions));
42
+ }, [filteredOptions]);
43
+ const handleClearSelection = useCallback(() => setTempSelected(new Set()), []);
44
+ const handleApplyMultiSelect = useCallback(() => {
45
+ onFilterChange?.(Array.from(tempSelected));
46
+ }, [onFilterChange, tempSelected]);
47
+ return {
48
+ tempSelected,
49
+ setTempSelected,
50
+ searchText,
51
+ setSearchText,
52
+ debouncedSearchText,
53
+ filteredOptions,
54
+ handleCheckboxChange,
55
+ handleSelectAll,
56
+ handleClearSelection,
57
+ handleApplyMultiSelect,
58
+ };
59
+ }