@alaarab/ogrid-core 1.7.2 → 1.8.0

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.
@@ -33,10 +33,9 @@ const toolbarSectionStyle = {
33
33
  alignItems: 'center',
34
34
  gap: 8,
35
35
  };
36
+ /** Secondary toolbar row — minimal wrapper. Consumer content handles its own padding/background. */
36
37
  const toolbarBelowStyle = {
37
- padding: '6px 12px',
38
38
  borderBottom: '1px solid var(--ogrid-border, #e0e0e0)',
39
- background: 'var(--ogrid-header-bg, #f5f5f5)',
40
39
  };
41
40
  const footerStripStyle = {
42
41
  borderTop: '1px solid var(--ogrid-border, #e0e0e0)',
@@ -23,6 +23,10 @@ export function useCellSelection(params) {
23
23
  const liveDragRangeRef = useRef(null);
24
24
  /** Auto-scroll interval during drag. */
25
25
  const autoScrollRef = useRef(null);
26
+ // Ref mirror of selectionRange — lets handleCellMouseDown read current value
27
+ // without adding selectionRange to its useCallback deps (keeps it stable).
28
+ const selectionRangeRef = useRef(selectionRange);
29
+ selectionRangeRef.current = selectionRange;
26
30
  const handleCellMouseDown = useCallback((e, rowIndex, globalColIndex) => {
27
31
  // Only handle primary (left) button — let middle-click scroll and right-click context menu work natively
28
32
  if (e.button !== 0)
@@ -32,10 +36,11 @@ export function useCellSelection(params) {
32
36
  // Prevent native text selection during cell drag
33
37
  e.preventDefault();
34
38
  const dataColIndex = globalColIndex - colOffset;
35
- if (e.shiftKey && selectionRange != null) {
39
+ const currentRange = selectionRangeRef.current;
40
+ if (e.shiftKey && currentRange != null) {
36
41
  setSelectionRange(normalizeSelectionRange({
37
- startRow: selectionRange.startRow,
38
- startCol: selectionRange.startCol,
42
+ startRow: currentRange.startRow,
43
+ startCol: currentRange.startCol,
39
44
  endRow: rowIndex,
40
45
  endCol: dataColIndex,
41
46
  }));
@@ -55,7 +60,7 @@ export function useCellSelection(params) {
55
60
  isDraggingRef.current = true;
56
61
  setIsDragging(true);
57
62
  }
58
- }, [colOffset, selectionRange, setActiveCell]);
63
+ }, [colOffset, setActiveCell]);
59
64
  const handleSelectAllCells = useCallback(() => {
60
65
  if (rowCount === 0 || visibleColCount === 0)
61
66
  return;
@@ -348,6 +348,7 @@ export function useDataGridState(params) {
348
348
  canRedo: undoRedo.canRedo,
349
349
  onUndo: undoRedo.undo,
350
350
  onRedo: undoRedo.redo,
351
+ isDragging: cellSelection ? isDragging : false,
351
352
  },
352
353
  contextMenu: {
353
354
  menuPosition: cellSelection ? contextMenu : null,
@@ -173,15 +173,19 @@ export function useFillHandle(params) {
173
173
  beginBatch,
174
174
  endBatch,
175
175
  ]);
176
+ // Ref mirror — keeps handleFillHandleMouseDown stable across selection changes
177
+ const selectionRangeRef = useRef(selectionRange);
178
+ selectionRangeRef.current = selectionRange;
176
179
  const handleFillHandleMouseDown = useCallback((e) => {
177
180
  e.preventDefault();
178
181
  e.stopPropagation();
179
- if (!selectionRange)
182
+ const range = selectionRangeRef.current;
183
+ if (!range)
180
184
  return;
181
185
  setFillDrag({
182
- startRow: selectionRange.startRow,
183
- startCol: selectionRange.startCol,
186
+ startRow: range.startRow,
187
+ startCol: range.startCol,
184
188
  });
185
- }, [selectionRange]);
189
+ }, []);
186
190
  return { fillDrag, setFillDrag, handleFillHandleMouseDown };
187
191
  }
package/dist/esm/index.js CHANGED
@@ -8,4 +8,4 @@ export { GridContextMenu } from './components/GridContextMenu';
8
8
  export { MarchingAntsOverlay } from './components/MarchingAntsOverlay';
9
9
  export { SideBar } from './components/SideBar';
10
10
  // Utilities
11
- export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, } from './utils';
11
+ export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, isRowInRange, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, } from './utils';
@@ -54,6 +54,18 @@ export function getHeaderFilterConfig(col, input) {
54
54
  }
55
55
  return base;
56
56
  }
57
+ // --- Row-level range utility (used by memoized GridRow comparators) ---
58
+ /**
59
+ * Checks whether a given row index falls within a selection range.
60
+ * O(1) — used by React.memo comparators to skip unchanged rows.
61
+ */
62
+ export function isRowInRange(range, rowIndex) {
63
+ if (!range)
64
+ return false;
65
+ const minR = Math.min(range.startRow, range.endRow);
66
+ const maxR = Math.max(range.startRow, range.endRow);
67
+ return rowIndex >= minR && rowIndex <= maxR;
68
+ }
57
69
  /**
58
70
  * Returns a descriptor for rendering a cell. UI uses this to decide editing-inline vs editing-popover vs display
59
71
  * and to apply isActive, isInRange, etc. without duplicating the boolean logic.
@@ -6,7 +6,7 @@ export { getStatusBarParts } from './statusBarHelpers';
6
6
  export { getDataGridStatusBarConfig } from './dataGridStatusBar';
7
7
  export { getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, } from './paginationHelpers';
8
8
  export { GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut } from './gridContextMenuHelpers';
9
- export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './dataGridViewModel';
9
+ export { getHeaderFilterConfig, getCellRenderDescriptor, isRowInRange, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './dataGridViewModel';
10
10
  export { parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, } from './valueParsers';
11
11
  export { computeAggregations } from './aggregationUtils';
12
12
  export { processClientSideData } from './clientSideData';
@@ -93,6 +93,8 @@ export interface DataGridCellInteractionState {
93
93
  canRedo: boolean;
94
94
  onUndo?: () => void;
95
95
  onRedo?: () => void;
96
+ /** True while user is drag-selecting cells (mousedown → mouseup). */
97
+ isDragging: boolean;
96
98
  }
97
99
  /** Context menu position and handlers. */
98
100
  export interface DataGridContextMenuState {
@@ -12,5 +12,5 @@ export { MarchingAntsOverlay } from './components/MarchingAntsOverlay';
12
12
  export type { MarchingAntsOverlayProps } from './components/MarchingAntsOverlay';
13
13
  export { SideBar } from './components/SideBar';
14
14
  export type { SideBarProps, SideBarFilterColumn } from './components/SideBar';
15
- export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, } from './utils';
15
+ export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, isRowInRange, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, } from './utils';
16
16
  export type { CsvColumn, StatusBarPart, StatusBarPartsInput, GridContextMenuItem, GridContextMenuHandlerProps, PaginationViewModel, HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, CellInteractionHandlers, ParseValueResult, AggregationResult, } from './utils';
@@ -40,6 +40,14 @@ export interface HeaderFilterConfig {
40
40
  * Use in Fluent/Material/Radix DataGridTable instead of createHeaderWithFilter.
41
41
  */
42
42
  export declare function getHeaderFilterConfig<T>(col: IColumnDef<T>, input: HeaderFilterConfigInput): HeaderFilterConfig;
43
+ /**
44
+ * Checks whether a given row index falls within a selection range.
45
+ * O(1) — used by React.memo comparators to skip unchanged rows.
46
+ */
47
+ export declare function isRowInRange(range: {
48
+ startRow: number;
49
+ endRow: number;
50
+ } | null, rowIndex: number): boolean;
43
51
  export type CellRenderMode = 'editing-inline' | 'editing-popover' | 'display';
44
52
  export interface CellRenderDescriptorInput<T> {
45
53
  editingCell: {
@@ -7,7 +7,7 @@ export { getDataGridStatusBarConfig } from './dataGridStatusBar';
7
7
  export { getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, } from './paginationHelpers';
8
8
  export type { PaginationViewModel } from './paginationHelpers';
9
9
  export { GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut } from './gridContextMenuHelpers';
10
- export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './dataGridViewModel';
10
+ export { getHeaderFilterConfig, getCellRenderDescriptor, isRowInRange, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './dataGridViewModel';
11
11
  export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, CellInteractionHandlers, } from './dataGridViewModel';
12
12
  export type { CsvColumn } from './exportToCsv';
13
13
  export type { StatusBarPart, StatusBarPartsInput } from './statusBarHelpers';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-core",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "OGrid core – framework-agnostic types, hooks, and utilities for OGrid data tables.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",