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
| Param | Type | Description |
|---|---|---|
rowCount | number | Visible row count (the current page). Used by selectAll(). |
colCount | number | Visible column count. Used by selectAll(). |
Returns
UseRangeSelectionResult
| Field | Type | Description |
|---|---|---|
range | ISelectionRange | null | Normalized bounds: startRow <= endRow, startCol <= endCol. |
anchor | CellCoord | null | { row, col } where the selection started. |
focus | CellCoord | null | { row, col } where it currently ends. |
startRange | (row, col) => void | New single-cell selection (anchor = focus). |
extendRange | (row, col) => void | Move the focus, keep the anchor. With no anchor yet, behaves like startRange. |
setRange | (range: ISelectionRange | null) => void | Set 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) => boolean | Inclusive hit test. |
getRangeRows | () => number[] | Row indices covered, in order. |
getRangeCells | () => CellCoord[] | Every covered cell, row-major. |
Behavior notes
- Coordinates are positions, not ids.
rowis an index into the array you render (grid.rows),colan index intogrid.columns. Pass the same arrays to the fill and clipboard hooks so all three agree. rangeis always normalized, so a drag up and to the left still yieldsstartRow <= endRow. Useanchor/focusif 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. CallclearRange()in those handlers (or ongrid.pagechange) if that matters to you. - Pointer events are yours. Check
e.buttons === 1inonMouseEnterto turn hover into drag, ande.button === 0inonMouseDownto 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
useFillHandleanduseCellClipboardconsume this rangeuseGridFocusextends it with Shift+Arrow- Spreadsheet Selection for the same model inside
<OGrid>