@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,60 +0,0 @@
1
- import { useState, useCallback, useMemo } from 'react';
2
- /** Shared display text formatter for select and rich-select editors. */
3
- export function getSelectDisplayText(value, formatValue) {
4
- if (formatValue)
5
- return formatValue(value);
6
- return value != null ? String(value) : '';
7
- }
8
- /**
9
- * Manages searchable rich select editor state with keyboard navigation (arrow keys, enter, escape).
10
- * @param params - Values, format function, initial value, and commit/cancel callbacks.
11
- * @returns Search text, filtered values, highlighted index, keyboard handler, and select function.
12
- */
13
- export function useRichSelectState(params) {
14
- const { values, formatValue, onCommit, onCancel } = params;
15
- const [searchText, setSearchText] = useState('');
16
- const [highlightedIndex, setHighlightedIndex] = useState(0);
17
- const getDisplayText = useCallback((value) => getSelectDisplayText(value, formatValue), [formatValue]);
18
- const filteredValues = useMemo(() => {
19
- if (!searchText.trim())
20
- return values;
21
- const lower = searchText.toLowerCase();
22
- return values.filter((v) => getDisplayText(v).toLowerCase().includes(lower));
23
- }, [values, searchText, getDisplayText]);
24
- const selectValue = useCallback((value) => {
25
- onCommit(value);
26
- }, [onCommit]);
27
- const handleKeyDown = useCallback((e) => {
28
- switch (e.key) {
29
- case 'ArrowDown':
30
- e.preventDefault();
31
- setHighlightedIndex((prev) => Math.min(prev + 1, filteredValues.length - 1));
32
- break;
33
- case 'ArrowUp':
34
- e.preventDefault();
35
- setHighlightedIndex((prev) => Math.max(prev - 1, 0));
36
- break;
37
- case 'Enter':
38
- e.preventDefault();
39
- e.stopPropagation();
40
- if (filteredValues.length > 0 && highlightedIndex < filteredValues.length) {
41
- selectValue(filteredValues[highlightedIndex]);
42
- }
43
- break;
44
- case 'Escape':
45
- e.preventDefault();
46
- e.stopPropagation();
47
- onCancel();
48
- break;
49
- }
50
- }, [filteredValues, highlightedIndex, selectValue, onCancel]);
51
- return {
52
- searchText,
53
- setSearchText,
54
- filteredValues,
55
- highlightedIndex,
56
- handleKeyDown,
57
- selectValue,
58
- getDisplayText,
59
- };
60
- }
@@ -1,69 +0,0 @@
1
- import { useState, useCallback, useRef, useMemo } from 'react';
2
- import { useLatestRef } from './useLatestRef';
3
- import { applyRangeRowSelection, computeRowSelectionState } from '../utils';
4
- /**
5
- * Manages row selection state for single or multiple selection modes with shift-click range support.
6
- * @param params - Items, getRowId, selection mode, controlled state, and selection change callback.
7
- * @returns Selected row IDs, update function, checkbox handlers, and selection state booleans.
8
- */
9
- export function useRowSelection(params) {
10
- const { items, getRowId, rowSelection, controlledSelectedRows, onSelectionChange, } = params;
11
- const [internalSelectedRows, setInternalSelectedRows] = useState(new Set());
12
- const lastClickedRowRef = useRef(-1);
13
- // Defensive: convert to Set if caller passes an array (e.g. from JSON state)
14
- const selectedRowIds = useMemo(() => controlledSelectedRows != null
15
- ? controlledSelectedRows instanceof Set
16
- ? controlledSelectedRows
17
- : new Set(controlledSelectedRows)
18
- : internalSelectedRows, [controlledSelectedRows, internalSelectedRows]);
19
- const updateSelection = useCallback((newSelectedIds) => {
20
- if (controlledSelectedRows === undefined) {
21
- setInternalSelectedRows(newSelectedIds);
22
- }
23
- onSelectionChange?.({
24
- selectedRowIds: Array.from(newSelectedIds),
25
- selectedItems: items.filter((item) => newSelectedIds.has(getRowId(item))),
26
- });
27
- }, [controlledSelectedRows, onSelectionChange, items, getRowId]);
28
- // Read selectedRowIds via ref to avoid recreating this callback on every selection change
29
- const selectedRowIdsRef = useLatestRef(selectedRowIds);
30
- const itemsRef = useLatestRef(items);
31
- const handleRowCheckboxChange = useCallback((rowId, checked, rowIndex, shiftKey) => {
32
- if (rowSelection === 'single') {
33
- updateSelection(checked ? new Set([rowId]) : new Set());
34
- lastClickedRowRef.current = rowIndex;
35
- return;
36
- }
37
- const currentItems = itemsRef.current;
38
- let next;
39
- if (shiftKey && lastClickedRowRef.current >= 0 && lastClickedRowRef.current !== rowIndex) {
40
- next = applyRangeRowSelection(lastClickedRowRef.current, rowIndex, checked, currentItems, getRowId, selectedRowIdsRef.current);
41
- }
42
- else {
43
- next = new Set(selectedRowIdsRef.current);
44
- if (checked)
45
- next.add(rowId);
46
- else
47
- next.delete(rowId);
48
- }
49
- lastClickedRowRef.current = rowIndex;
50
- updateSelection(next);
51
- }, [rowSelection, getRowId, updateSelection, itemsRef, selectedRowIdsRef]);
52
- const handleSelectAll = useCallback((checked) => {
53
- if (checked) {
54
- updateSelection(new Set(items.map((item) => getRowId(item))));
55
- }
56
- else {
57
- updateSelection(new Set());
58
- }
59
- }, [items, getRowId, updateSelection]);
60
- const { allSelected, someSelected } = useMemo(() => computeRowSelectionState(selectedRowIds, items, getRowId), [items, selectedRowIds, getRowId]);
61
- return {
62
- selectedRowIds,
63
- updateSelection,
64
- handleRowCheckboxChange,
65
- handleSelectAll,
66
- allSelected,
67
- someSelected,
68
- };
69
- }
@@ -1,62 +0,0 @@
1
- import { useState, useCallback, useEffect, useRef } from 'react';
2
- import { getSelectDisplayText } from './useRichSelectState';
3
- /**
4
- * Manages select editor state with keyboard navigation (arrow keys, enter, escape).
5
- * Simpler than useRichSelectState — no search, just a dropdown list.
6
- */
7
- export function useSelectState(params) {
8
- const { values, formatValue, initialValue, onCommit, onCancel } = params;
9
- const dropdownRef = useRef(null);
10
- const getDisplayText = useCallback((value) => getSelectDisplayText(value, formatValue), [formatValue]);
11
- // Start highlighted on current value
12
- const initialIndex = values.findIndex((v) => String(v) === String(initialValue));
13
- const [highlightedIndex, setHighlightedIndex] = useState(Math.max(initialIndex, 0));
14
- // Scroll highlighted option into view
15
- useEffect(() => {
16
- const dropdown = dropdownRef.current;
17
- if (!dropdown)
18
- return;
19
- const highlighted = dropdown.children[highlightedIndex];
20
- highlighted?.scrollIntoView({ block: 'nearest' });
21
- }, [highlightedIndex]);
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, values.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 (values.length > 0 && highlightedIndex < values.length) {
39
- selectValue(values[highlightedIndex]);
40
- }
41
- break;
42
- case 'Tab':
43
- e.preventDefault();
44
- if (values.length > 0 && highlightedIndex < values.length) {
45
- selectValue(values[highlightedIndex]);
46
- }
47
- break;
48
- case 'Escape':
49
- e.preventDefault();
50
- e.stopPropagation();
51
- onCancel();
52
- break;
53
- }
54
- }, [values, highlightedIndex, selectValue, onCancel]);
55
- return {
56
- highlightedIndex,
57
- handleKeyDown,
58
- selectValue,
59
- getDisplayText,
60
- dropdownRef,
61
- };
62
- }
@@ -1,14 +0,0 @@
1
- import { useRef } from 'react';
2
- /**
3
- * Returns a referentially stable value as long as the comparator considers
4
- * the new value equal to the previous one. Unlike the broken
5
- * `useMemo` + `useRef` + `useEffect` pattern, this works correctly because
6
- * it compares *before* React's own reference check on the dependency array.
7
- */
8
- export function useShallowEqualMemo(value, isEqual) {
9
- const ref = useRef(value);
10
- if (!isEqual(value, ref.current)) {
11
- ref.current = value;
12
- }
13
- return ref.current;
14
- }
@@ -1,39 +0,0 @@
1
- import { useState, useCallback, useMemo } from 'react';
2
- const DEFAULT_PANELS = ['columns', 'filters'];
3
- /**
4
- * Manages side bar panel state: enabled panels, active panel, position, and toggle/close handlers.
5
- * @param params - Side bar config (boolean, ISideBarDef, or undefined).
6
- * @returns Enabled flag, active panel, setters, panel list, position, open state, toggle, and close.
7
- */
8
- export function useSideBarState(params) {
9
- const { config } = params;
10
- const isEnabled = config != null && config !== false;
11
- const parsed = useMemo(() => {
12
- if (!isEnabled || config === true) {
13
- return { panels: DEFAULT_PANELS, position: 'right', defaultPanel: null };
14
- }
15
- const def = config;
16
- return {
17
- panels: def.panels ?? DEFAULT_PANELS,
18
- position: def.position ?? 'right',
19
- defaultPanel: def.defaultPanel ?? null,
20
- };
21
- }, [isEnabled, config]);
22
- const [activePanel, setActivePanel] = useState(parsed.defaultPanel);
23
- const toggle = useCallback((panel) => {
24
- setActivePanel((prev) => (prev === panel ? null : panel));
25
- }, []);
26
- const close = useCallback(() => {
27
- setActivePanel(null);
28
- }, []);
29
- return {
30
- isEnabled,
31
- activePanel,
32
- setActivePanel,
33
- panels: parsed.panels,
34
- position: parsed.position,
35
- isOpen: activePanel !== null,
36
- toggle,
37
- close,
38
- };
39
- }
@@ -1,69 +0,0 @@
1
- import { useState, useEffect, useMemo } from 'react';
2
- import { CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING } from '@alaarab/ogrid-core';
3
- /**
4
- * Manages table layout: container width measurement, column sizing overrides,
5
- * min/desired table width calculations.
6
- */
7
- export function useTableLayout(params) {
8
- const { wrapperRef, visibleCols, flatColumns, hasCheckboxCol, initialColumnWidths, onColumnResized, } = params;
9
- // --- Container width measurement via ResizeObserver ---
10
- const [containerWidth, setContainerWidth] = useState(0);
11
- useEffect(() => {
12
- const el = wrapperRef.current;
13
- if (!el)
14
- return;
15
- const measure = () => {
16
- const rect = el.getBoundingClientRect();
17
- const cs = window.getComputedStyle(el);
18
- const borderX = (parseFloat(cs.borderLeftWidth || '0') || 0) +
19
- (parseFloat(cs.borderRightWidth || '0') || 0);
20
- setContainerWidth(Math.max(0, rect.width - borderX));
21
- };
22
- const ro = new ResizeObserver(measure);
23
- ro.observe(el);
24
- measure();
25
- return () => ro.disconnect();
26
- }, [wrapperRef]);
27
- // --- Column sizing overrides state ---
28
- const [columnSizingOverrides, setColumnSizingOverrides] = useState(() => {
29
- if (!initialColumnWidths)
30
- return {};
31
- const result = {};
32
- for (const [id, width] of Object.entries(initialColumnWidths)) {
33
- result[id] = { widthPx: width };
34
- }
35
- return result;
36
- });
37
- // --- Minimum table width calculation ---
38
- const minTableWidth = useMemo(() => {
39
- const checkboxW = hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0;
40
- return visibleCols.reduce((sum, c) => sum + (c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH) + CELL_PADDING, checkboxW);
41
- }, [visibleCols, hasCheckboxCol]);
42
- // --- Cleanup effect: remove overrides for columns that no longer exist ---
43
- useEffect(() => {
44
- const colIds = new Set(flatColumns.map((c) => c.columnId));
45
- setColumnSizingOverrides((prev) => {
46
- const kept = Object.fromEntries(Object.entries(prev).filter(([id]) => colIds.has(id)));
47
- return Object.keys(kept).length !== Object.keys(prev).length ? kept : prev;
48
- });
49
- }, [flatColumns]);
50
- // --- Desired table width calculation ---
51
- const desiredTableWidth = useMemo(() => {
52
- const checkboxW = hasCheckboxCol ? CHECKBOX_COLUMN_WIDTH : 0;
53
- return visibleCols.reduce((sum, c) => {
54
- const override = columnSizingOverrides[c.columnId];
55
- const w = override
56
- ? override.widthPx
57
- : (c.idealWidth ?? c.defaultWidth ?? c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH);
58
- return sum + Math.max(c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH, w) + CELL_PADDING;
59
- }, checkboxW);
60
- }, [visibleCols, columnSizingOverrides, hasCheckboxCol]);
61
- return {
62
- containerWidth,
63
- minTableWidth,
64
- desiredTableWidth,
65
- columnSizingOverrides,
66
- setColumnSizingOverrides,
67
- onColumnResized,
68
- };
69
- }
@@ -1,25 +0,0 @@
1
- /**
2
- * Text filter state sub-hook for column header filters.
3
- * Manages temporary text value and apply/clear handlers.
4
- */
5
- import { useState, useCallback, useEffect } from 'react';
6
- export function useTextFilterState(params) {
7
- const { textValue = '', onTextChange, isFilterOpen } = params;
8
- const [tempTextValue, setTempTextValue] = useState(textValue);
9
- // Sync temp state when popover opens
10
- useEffect(() => {
11
- if (isFilterOpen) {
12
- setTempTextValue(textValue);
13
- }
14
- }, [isFilterOpen, textValue]);
15
- const handleTextApply = useCallback(() => {
16
- onTextChange?.(tempTextValue.trim());
17
- }, [onTextChange, tempTextValue]);
18
- const handleTextClear = useCallback(() => setTempTextValue(''), []);
19
- return {
20
- tempTextValue,
21
- setTempTextValue,
22
- handleTextApply,
23
- handleTextClear,
24
- };
25
- }
@@ -1,84 +0,0 @@
1
- import { useCallback, useRef, useState } from 'react';
2
- import { UndoRedoStack } from '../utils';
3
- /**
4
- * Wraps onCellValueChanged with an undo/redo history stack.
5
- * Supports batch operations: changes between beginBatch/endBatch are one undo step.
6
- */
7
- export function useUndoRedo(params) {
8
- const { onCellValueChanged, maxUndoDepth = 100 } = params;
9
- const stackRef = useRef(null);
10
- if (stackRef.current === null) {
11
- stackRef.current = new UndoRedoStack(maxUndoDepth);
12
- }
13
- const [historyLength, setHistoryLength] = useState(0);
14
- const [redoLength, setRedoLength] = useState(0);
15
- const getStack = useCallback(() => {
16
- const s = stackRef.current;
17
- if (!s)
18
- throw new Error('UndoRedoStack not initialized');
19
- return s;
20
- }, []);
21
- const wrapped = useCallback((event) => {
22
- if (!onCellValueChanged)
23
- return;
24
- const stack = getStack();
25
- stack.record(event);
26
- if (!stack.isBatching) {
27
- setHistoryLength(stack.historyLength);
28
- setRedoLength(stack.redoLength);
29
- }
30
- onCellValueChanged(event);
31
- }, [onCellValueChanged, getStack]);
32
- const beginBatch = useCallback(() => {
33
- getStack().beginBatch();
34
- }, [getStack]);
35
- const endBatch = useCallback(() => {
36
- const stack = getStack();
37
- stack.endBatch();
38
- setHistoryLength(stack.historyLength);
39
- setRedoLength(stack.redoLength);
40
- }, [getStack]);
41
- const undo = useCallback(() => {
42
- if (!onCellValueChanged)
43
- return;
44
- const stack = getStack();
45
- const lastBatch = stack.undo();
46
- if (!lastBatch)
47
- return;
48
- setHistoryLength(stack.historyLength);
49
- setRedoLength(stack.redoLength);
50
- // Revert in reverse order so multi-cell undo is applied correctly
51
- for (let i = lastBatch.length - 1; i >= 0; i--) {
52
- const ev = lastBatch[i];
53
- onCellValueChanged({
54
- ...ev,
55
- oldValue: ev.newValue,
56
- newValue: ev.oldValue,
57
- });
58
- }
59
- }, [onCellValueChanged, getStack]);
60
- const redo = useCallback(() => {
61
- if (!onCellValueChanged)
62
- return;
63
- const stack = getStack();
64
- const nextBatch = stack.redo();
65
- if (!nextBatch)
66
- return;
67
- setHistoryLength(stack.historyLength);
68
- setRedoLength(stack.redoLength);
69
- // Replay in original order
70
- for (const ev of nextBatch) {
71
- onCellValueChanged(ev);
72
- }
73
- }, [onCellValueChanged, getStack]);
74
- return {
75
- onCellValueChanged: onCellValueChanged ? wrapped : undefined,
76
- undo,
77
- redo,
78
- canUndo: historyLength > 0,
79
- canRedo: redoLength > 0,
80
- beginBatch,
81
- endBatch,
82
- maxUndoDepth,
83
- };
84
- }
@@ -1,69 +0,0 @@
1
- import { useMemo, useCallback, useRef } from 'react';
2
- import { useVirtualizer } from '@tanstack/react-virtual';
3
- /** Threshold below which virtual scrolling is a no-op (all rows rendered). */
4
- const PASSTHROUGH_THRESHOLD = 100;
5
- /**
6
- * Wraps TanStack Virtual for row virtualization.
7
- * When disabled or when totalRows < threshold, returns a pass-through (all rows visible).
8
- * @param params - Total rows, row height, enabled flag, overscan, and container ref.
9
- * @returns Virtualizer instance, total height, visible range, and scrollToIndex helper.
10
- */
11
- export function useVirtualScroll(params) {
12
- const { totalRows, rowHeight, enabled, overscan = 5, containerRef, } = params;
13
- const isActive = enabled && totalRows >= PASSTHROUGH_THRESHOLD;
14
- const getScrollElement = useCallback(() => containerRef.current, [containerRef]);
15
- const virtualizer = useVirtualizer({
16
- count: isActive ? totalRows : 0,
17
- getScrollElement,
18
- estimateSize: () => rowHeight,
19
- overscan,
20
- enabled: isActive,
21
- });
22
- const passthroughRange = useMemo(() => ({
23
- startIndex: 0,
24
- endIndex: Math.max(0, totalRows - 1),
25
- offsetTop: 0,
26
- offsetBottom: 0,
27
- }), [totalRows]);
28
- const activeRange = useMemo(() => {
29
- if (!isActive)
30
- return passthroughRange;
31
- const virtualItems = virtualizer.getVirtualItems();
32
- if (virtualItems.length === 0) {
33
- return { startIndex: 0, endIndex: -1, offsetTop: 0, offsetBottom: 0 };
34
- }
35
- const first = virtualItems[0];
36
- const last = virtualItems[virtualItems.length - 1];
37
- const totalSize = virtualizer.getTotalSize();
38
- return {
39
- startIndex: first.index,
40
- endIndex: last.index,
41
- offsetTop: first.start,
42
- offsetBottom: Math.max(0, totalSize - last.end),
43
- };
44
- }, [isActive, virtualizer, passthroughRange]);
45
- const totalHeight = isActive
46
- ? virtualizer.getTotalSize()
47
- : totalRows * rowHeight;
48
- const scrollToIndexRef = useRef(virtualizer);
49
- scrollToIndexRef.current = virtualizer;
50
- const scrollToIndex = useCallback((index) => {
51
- if (isActive) {
52
- scrollToIndexRef.current?.scrollToIndex(index, { align: 'auto' });
53
- }
54
- else {
55
- // When not virtualized, scroll the container directly
56
- const container = containerRef.current;
57
- if (container) {
58
- const top = index * rowHeight;
59
- container.scrollTo({ top, behavior: 'auto' });
60
- }
61
- }
62
- }, [isActive, containerRef, rowHeight]);
63
- return {
64
- virtualizer: isActive ? virtualizer : null,
65
- totalHeight,
66
- visibleRange: activeRange,
67
- scrollToIndex,
68
- };
69
- }
@@ -1 +0,0 @@
1
- export { storyRows, storyGetRowId, noop, editableInitialRows, editableColumns, NotesPopupEditor, spreadsheetRows, spreadsheetColumns, departmentFilterOptions, statusFilterOptions, } from './mockData';
@@ -1,73 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import * as React from 'react';
3
- export const storyRows = [
4
- { id: '1', name: 'Alpha', status: 'Active', owner: 'alice@test.com' },
5
- { id: '2', name: 'Beta', status: 'Closed', owner: 'bob@test.com' },
6
- { id: '3', name: 'Gamma', status: 'Active', owner: 'carol@test.com' },
7
- { id: '4', name: 'Delta', status: 'Planning', owner: 'dave@test.com' },
8
- ];
9
- export const storyGetRowId = (r) => r.id;
10
- export const noop = () => { };
11
- export const editableInitialRows = [
12
- { id: '1', name: 'Alpha', status: 'Active', approved: false },
13
- { id: '2', name: 'Beta', status: 'Closed', approved: true },
14
- { id: '3', name: 'Gamma', status: 'Planning', approved: false },
15
- ];
16
- export const editableColumns = [
17
- {
18
- columnId: 'name',
19
- name: 'Name',
20
- editable: true,
21
- cellEditor: 'text',
22
- },
23
- {
24
- columnId: 'status',
25
- name: 'Status',
26
- editable: true,
27
- cellEditor: 'select',
28
- cellEditorParams: { values: ['Active', 'Closed', 'Planning'] },
29
- },
30
- {
31
- columnId: 'approved',
32
- name: 'Approved',
33
- editable: true,
34
- cellEditor: 'checkbox',
35
- valueFormatter: (v) => (v === true ? 'Yes' : 'No'),
36
- },
37
- ];
38
- export function NotesPopupEditor({ value, onValueChange, onCommit, onCancel }) {
39
- const [local, setLocal] = React.useState(String(value ?? ''));
40
- return (_jsxs("div", { style: { padding: 8, minWidth: 200 }, children: [_jsx("textarea", { value: local, onChange: (e) => setLocal(e.target.value), onBlur: () => {
41
- onValueChange(local);
42
- onCommit();
43
- }, rows: 3, style: { width: '100%', marginBottom: 8 }, "data-testid": "notes-editor" }), _jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx("button", { type: "button", onClick: () => { onValueChange(local); onCommit(); }, children: "Save" }), _jsx("button", { type: "button", onClick: onCancel, children: "Cancel" })] })] }));
44
- }
45
- export const spreadsheetRows = [
46
- { id: '1', name: 'Alice Johnson', department: 'Engineering', salary: 125000, startDate: '2021-03-15', status: 'Active', email: 'alice@company.com' },
47
- { id: '2', name: 'Bob Smith', department: 'Marketing', salary: 95000, startDate: '2020-07-01', status: 'Active', email: 'bob@company.com' },
48
- { id: '3', name: 'Carol Williams', department: 'Engineering', salary: 140000, startDate: '2019-11-20', status: 'Active', email: 'carol@company.com' },
49
- { id: '4', name: 'Dave Brown', department: 'Sales', salary: 85000, startDate: '2022-01-10', status: 'On Leave', email: 'dave@company.com' },
50
- { id: '5', name: 'Eve Davis', department: 'Engineering', salary: 155000, startDate: '2018-05-22', status: 'Active', email: 'eve@company.com' },
51
- { id: '6', name: 'Frank Miller', department: 'Marketing', salary: 88000, startDate: '2023-02-14', status: 'Active', email: 'frank@company.com' },
52
- { id: '7', name: 'Grace Lee', department: 'Sales', salary: 92000, startDate: '2021-08-30', status: 'Active', email: 'grace@company.com' },
53
- { id: '8', name: 'Henry Wilson', department: 'Engineering', salary: 130000, startDate: '2020-04-12', status: 'Inactive', email: 'henry@company.com' },
54
- { id: '9', name: 'Iris Taylor', department: 'HR', salary: 78000, startDate: '2022-09-05', status: 'Active', email: 'iris@company.com' },
55
- { id: '10', name: 'Jack Anderson', department: 'Engineering', salary: 145000, startDate: '2019-06-18', status: 'Active', email: 'jack@company.com' },
56
- ];
57
- export const spreadsheetColumns = [
58
- { columnId: 'name', name: 'Employee Name', sortable: true, minWidth: 160, filterable: { type: 'text' } },
59
- { columnId: 'department', name: 'Department', sortable: true, filterable: { type: 'multiSelect' } },
60
- {
61
- columnId: 'salary',
62
- name: 'Salary',
63
- sortable: true,
64
- minWidth: 100,
65
- valueFormatter: (v) => typeof v === 'number' ? `$${v.toLocaleString()}` : '',
66
- cellStyle: { textAlign: 'right', fontVariantNumeric: 'tabular-nums' },
67
- },
68
- { columnId: 'startDate', name: 'Start Date', sortable: true, minWidth: 110 },
69
- { columnId: 'status', name: 'Status', sortable: true, filterable: { type: 'multiSelect' } },
70
- { columnId: 'email', name: 'Email', sortable: true, minWidth: 180 },
71
- ];
72
- export const departmentFilterOptions = ['Engineering', 'Marketing', 'Sales', 'HR'];
73
- export const statusFilterOptions = ['Active', 'On Leave', 'Inactive'];
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export { toUserLike, isInSelectionRange, normalizeSelectionRange } from '@alaarab/ogrid-core';
@@ -1 +0,0 @@
1
- export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './dataGridTypes';
@@ -1,54 +0,0 @@
1
- /**
2
- * View model helpers for DataGridTable.
3
- * Pure functions are now in @alaarab/ogrid-core. This file re-exports them
4
- * and adds React-specific helpers / type narrowing.
5
- */
6
- import { resolveCellDisplayContent as coreResolveCellDisplayContent, resolveCellStyle as coreResolveCellStyle, buildInlineEditorProps as coreBuildInlineEditorProps, buildPopoverEditorProps as coreBuildPopoverEditorProps, } from '@alaarab/ogrid-core';
7
- // Re-export pure functions from core (no type narrowing needed)
8
- export { getHeaderFilterConfig, getCellRenderDescriptor, } from '@alaarab/ogrid-core';
9
- // --- React-typed wrappers for functions that need type narrowing ---
10
- /**
11
- * Resolves display content for a cell in display mode.
12
- * Returns React.ReactNode for JSX compatibility.
13
- */
14
- export function resolveCellDisplayContent(col, item, displayValue) {
15
- return coreResolveCellDisplayContent(col, item, displayValue);
16
- }
17
- /**
18
- * Resolves the cellStyle from a column def.
19
- * Returns React.CSSProperties for JSX compatibility.
20
- */
21
- export function resolveCellStyle(col, item) {
22
- return coreResolveCellStyle(col, item);
23
- }
24
- /**
25
- * Builds props for InlineCellEditor with React-specific IColumnDef.
26
- */
27
- export function buildInlineEditorProps(item, col, descriptor, callbacks) {
28
- const result = coreBuildInlineEditorProps(item, col, descriptor, callbacks);
29
- return { ...result, column: col };
30
- }
31
- /**
32
- * Builds ICellEditorProps for custom popover editors with React-specific IColumnDef.
33
- */
34
- export function buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, callbacks) {
35
- const result = coreBuildPopoverEditorProps(item, col, descriptor, pendingEditorValue, callbacks);
36
- return { ...result, column: col };
37
- }
38
- export function getCellInteractionProps(descriptor, columnId, handlers) {
39
- return {
40
- 'data-row-index': descriptor.rowIndex,
41
- 'data-col-index': descriptor.globalColIndex,
42
- ...(descriptor.isInRange ? { 'data-in-range': 'true' } : {}),
43
- tabIndex: descriptor.isActive ? 0 : -1,
44
- onMouseDown: (e) => handlers.handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex),
45
- onClick: () => handlers.setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }),
46
- onContextMenu: handlers.handleCellContextMenu,
47
- ...(descriptor.canEditAny
48
- ? {
49
- role: 'button',
50
- onDoubleClick: () => handlers.setEditingCell({ rowId: descriptor.rowId, columnId }),
51
- }
52
- : {}),
53
- };
54
- }
@@ -1,2 +0,0 @@
1
- // Re-export from core — no React-specific changes
2
- export { areGridRowPropsEqual, isRowInRange } from '@alaarab/ogrid-core';
@@ -1,5 +0,0 @@
1
- // Shared utilities re-exported from core
2
- export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, computeNextSortState, measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, findCtrlArrowTarget, computeTabNavigation, rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, applyFillValues, computeArrowNavigation, applyCellDeletion, applyRangeRowSelection, computeRowSelectionState, UndoRedoStack, } from '@alaarab/ogrid-core';
3
- // View model utilities (re-exported from core + React-specific getCellInteractionProps)
4
- export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './dataGridViewModel';
5
- export { areGridRowPropsEqual, isRowInRange } from './gridRowComparator';