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>
| Param | Type | Description |
|---|---|---|
rangeSelection | UseRangeSelectionResult | The source range. Fill always extends from rangeSelection.range. |
rows | T[] | The rows you render (grid.rows). Event rowIndex values index into this array. |
columns | IColumnDef<T>[] | Visible columns, in render order. |
onFillCells | (events: ICellValueChangedEvent<T>[]) => void | Called once on commit with every accepted cell. Not called when nothing changed. |
Returns
UseFillHandleResult
| Field | Type | Description |
|---|---|---|
fillTarget | CellCoord | null | Cell the handle is being dragged toward. |
isFilling | boolean | fillTarget !== null. |
fillRange | ISelectionRange | null | Source 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 | () => void | Begin a drag from the range's bottom-right cell. No-op without a range. |
updateFill | (row, col) => void | Move the target. No-op when not filling. |
commitFill | () => void | Compute events, call onFillCells, select the filled range, end the drag. |
cancelFill | () => void | End the drag without changes. |
isInFillRange | (row, col) => boolean | Hit 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
fillRangeequal to the source, socommitFillis a no-op. - Releasing without moving is a no-op: when
fillRangeequals the source range,commitFillends the drag andonFillCellsis not called. - The filled range becomes the selection, as in Excel and
<OGrid>: after a fill that extends the source,commitFillcallsrangeSelection.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
onMouseDownstarts a new range;e.stopPropagation()keeps the source range intact. - Formula-aware fill (relative reference adjustment) is only available
through
<OGrid>withformulasenabled; the headless hook passes no formula options toapplyFillValues.
See also
useRangeSelectionprovides the source rangeuseUndoRedowithbeginBatch/endBatchto undo a whole fill in one step- Editing & Clipboard: fill handle inside
<OGrid>