Skip to main content

useFillHandle

Drag the small square at the bottom-right of a selection and the source block is tiled over the cells you drag across, down or sideways (one axis at a time, like Excel). The hook tracks the drag target, exposes the extended fillRange for highlighting, and on commit calls core's applyFillValues to turn the fill into ICellValueChangedEvent objects. You apply them to your data. Column editable, type, and valueParser are honored, so a text value will not land in a numeric column.

Live demo​

Select cells, then drag the green square at the range's bottom-right corner down or across
Live

Quick example​

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

const grid = useHeadlessGrid({ columns, data, getRowId });
const range = useRangeSelection({ rowCount: grid.rows.length, colCount: grid.columns.length });
const fill = useFillHandle({
rangeSelection: range,
rows: grid.rows,
columns: grid.columns,
onFillCells: (events) => setData((prev) => events.reduce(applyOneEvent, prev)),
});

// Commit even when the mouse is released outside the table.
useEffect(() => {
if (!fill.isFilling) return;
window.addEventListener('mouseup', fill.commitFill);
return () => window.removeEventListener('mouseup', fill.commitFill);
}, [fill.isFilling, fill.commitFill]);

// Inside the cell loop:
const r = range.range;
<td
onMouseEnter={(e) => (fill.isFilling ? fill.updateFill(rowIdx, colIdx) : e.buttons === 1 && range.extendRange(rowIdx, colIdx))}
data-fill={fill.isFilling && fill.isInFillRange(rowIdx, colIdx)}
>
{value}
{r && rowIdx === r.endRow && colIdx === r.endCol && (
<span className="fill-handle" onMouseDown={(e) => { e.stopPropagation(); fill.startFill(); }} />
)}
</td>

Parameters​

UseFillHandleParams<T>

ParamTypeDescription
rangeSelectionUseRangeSelectionResultThe source range. Fill always extends from rangeSelection.range.
rowsT[]The rows you render (grid.rows). Event rowIndex values index into this array.
columnsIColumnDef<T>[]Visible columns, in render order.
onFillCells(events: ICellValueChangedEvent<T>[]) => voidCalled once on commit with every accepted cell. Not called when nothing changed.

Returns​

UseFillHandleResult

FieldTypeDescription
fillTargetCellCoord | nullCell the handle is being dragged toward.
isFillingbooleanfillTarget !== null.
fillRangeISelectionRange | nullSource range extended toward the target along one axis (see behavior notes); equals the source range when idle or when the target is inside it.
startFill() => voidBegin a drag from the range's bottom-right cell. No-op without a range.
updateFill(row, col) => voidMove the target. No-op when not filling.
commitFill() => voidCompute events, call onFillCells, select the filled range, end the drag.
cancelFill() => voidEnd the drag without changes.
isInFillRange(row, col) => booleanHit test against fillRange (includes the source cells).

Behavior notes​

  • Tiling, not series. The source block repeats across the fill range at the same offset modulo the block size (Excel's behavior for a plain drag). Source cells are never overwritten. There is no 1, 2, 3 series detection.
  • One axis at a time, like Excel. The fill range is the source range extended toward the target along a single axis: whichever one the target is farther outside the source on, counted in cells (rows vs columns; an exact tie fills rows). Dragging diagonally therefore fills down or across, never both, and the highlight switches axis as the pointer moves. Dragging up or to the left fills in that direction too. A target inside the source range (including dragging back into it) leaves fillRange equal to the source, so commitFill is a no-op.
  • Releasing without moving is a no-op: when fillRange equals the source range, commitFill ends the drag and onFillCells is not called.
  • The filled range becomes the selection, as in Excel and <OGrid>: after a fill that extends the source, commitFill calls rangeSelection.setRange(fillRange). A no-op fill leaves the selection alone.
  • Validation per cell. Read-only cells are skipped, and each value runs through the target column's valueParser / built-in type parser; rejected cells are dropped from the event list.
  • Stop propagation on the handle. The handle sits inside a cell whose onMouseDown starts a new range; e.stopPropagation() keeps the source range intact.
  • Formula-aware fill (relative reference adjustment) is only available through <OGrid> with formulas enabled; the headless hook passes no formula options to applyFillValues.

See also​