Skip to main content

useRangeSelection

A range is an anchor (where the user started) and a focus (where they are now). A single click sets both to the same cell; dragging or Shift+clicking moves only the focus. The hook exposes the normalized rectangle as range and is the input that useFillHandle, useCellClipboard, and useGridFocus consume. It never touches the DOM; you wire the mouse events.

Live demo​

Click a cell, then drag or Shift+click to extend. The anchor cell keeps the solid border
Live

Quick example​

import { useHeadlessGrid, useRangeSelection } from '@alaarab/ogrid-react';

const grid = useHeadlessGrid({ columns, data, getRowId });
const range = useRangeSelection({
rowCount: grid.rows.length,
colCount: grid.columns.length,
});

// Inside the row/column loops (rowIdx, colIdx are positions in grid.rows / grid.columns):
<td
onMouseDown={(e) => (e.shiftKey ? range.extendRange(rowIdx, colIdx) : range.startRange(rowIdx, colIdx))}
onMouseEnter={(e) => e.buttons === 1 && range.extendRange(rowIdx, colIdx)}
style={{ background: range.isInRange(rowIdx, colIdx) ? 'var(--ogrid-range-bg)' : undefined }}
/>

Parameters​

UseRangeSelectionParams

ParamTypeDescription
rowCountnumberVisible row count (the current page). Used by selectAll().
colCountnumberVisible column count. Used by selectAll().

Returns​

UseRangeSelectionResult

FieldTypeDescription
rangeISelectionRange | nullNormalized bounds: startRow <= endRow, startCol <= endCol.
anchorCellCoord | null{ row, col } where the selection started.
focusCellCoord | null{ row, col } where it currently ends.
startRange(row, col) => voidNew single-cell selection (anchor = focus).
extendRange(row, col) => voidMove the focus, keep the anchor. With no anchor yet, behaves like startRange.
setRange(range: ISelectionRange | null) => voidSet the bounds explicitly; null clears.
clearRange() => void
selectAll() => void(0,0) to (rowCount-1, colCount-1). No-op for an empty grid.
isInRange(row, col) => booleanInclusive hit test.
getRangeRows() => number[]Row indices covered, in order.
getRangeCells() => CellCoord[]Every covered cell, row-major.

Behavior notes​

  • Coordinates are positions, not ids. row is an index into the array you render (grid.rows), col an index into grid.columns. Pass the same arrays to the fill and clipboard hooks so all three agree.
  • range is always normalized, so a drag up and to the left still yields startRow <= endRow. Use anchor / focus if you need the drag direction.
  • The range is not clamped when the grid shrinks. Changing page, filter, or sort can leave a stale range pointing past the new rowCount. Call clearRange() in those handlers (or on grid.page change) if that matters to you.
  • Pointer events are yours. Check e.buttons === 1 in onMouseEnter to turn hover into drag, and e.button === 0 in onMouseDown to ignore right clicks. <OGrid> additionally uses the Pointer Events API for touch; the hook does not care which events you use.
  • Ctrl+A is just range.selectAll() bound to a key.

See also​