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