Moving Ranges, Rows & Columns
Since v1.5.0 a selection can be dragged to a new place, as in Excel. Two distinct operations share the gesture:
| Gesture | Operation | Semantics |
|---|---|---|
| Drag the selection border | Move a cell block | Overwrites the destination |
| Drag an already-selected row/column header | Reorder whole rows/columns | Cut-and-insert — nothing is lost |
Available in both editions — moving is basic editing, like auto-fill and cut/paste.
What travels with the cells
The unit of relocation carries everything stored per cell:
value / formula text, rich text, number format, style, cell type, border, comment, and the locked flag.
Note: formulas that reference the moved cells are not rewritten in v1 — the same behavior as the existing cut/paste. A reorder of rows/columns also does not carry conditional-format, validation, table, page-break, outline or filter ranges.
Dragging
- Grab the selection’s border (a ±3px band, inside the selection) — the cursor becomes a move cursor and a preview rectangle follows the pointer. Release to drop.
- The fill handle on the bottom-right corner wins over the border, so auto-fill is unaffected.
- Pressing an already-selected row or column header arms a reorder that only starts after 4px of travel, so a plain click still re-selects the line.
Turn the whole interaction off without touching the API:
grid.worksheet.setRangeMoveEnabled(false)
Programmatic moves
const ws = grid.worksheet
// Relocate a block — its top-left lands on (row, column). Overwrites.
ws.range('A1:C3').moveTo(10, 0)
// Or on the worksheet, with an explicit rectangle
ws.moveRange({ topRow: 0, leftColumn: 0, bottomRow: 2, rightColumn: 2 }, 10, 0)
// Reorder whole lines (cut-and-insert)
ws.moveRows(2, 3, 8) // 3 rows starting at index 2 → before index 8
ws.moveColumns(1, 1, 4) // 1 column at index 1 → before index 4
Each returns true when the move happened. To ask first:
const check = ws.canMoveRange({ topRow: 0, leftColumn: 0, bottomRow: 2, rightColumn: 2 }, 10, 0)
if (!check.ok) console.warn(check.reason)
canMoveRows / canMoveColumns are the line equivalents.
When a move is refused
A move is blocked when it would
- tear a merged cell (partially move one),
- land out of bounds,
- hit a protected cell on a locked sheet, or
- run while a delay-load data source is attached.
Events
onBeforeRangeMove is cancellable — the listener receives a mutable event and setting cancel = true suppresses the default move, so a host can implement its own behavior. It mirrors .NET ReoGrid’s BeforeCopyOrMoveRangeEventArgs.IsCancelled; the listener’s return value is not consulted.
const off = grid.onBeforeRangeMove((event) => {
if (event.kind === 'rows' && event.from.topRow === 0) {
event.cancel = true // pin the header row
}
})
grid.onAfterRangeMove((info) => {
console.log(`${info.kind} moved`, info.from, '→', info.to)
})
- A blocked move still fires
onBeforeRangeMove, withcancelalreadytrueand ablockedReason('merge-source','merge-target','out-of-bounds','protected','data-source','no-op') so you can explain the refusal. Clearing the flag does not force the move through. onAfterRangeMovefires only on a completed move.- Undo/redo emit neither event — they replay the move directly.
Both follow the active sheet, so they keep working after a sheet switch. Each returns an unsubscribe function.
Undo
Every move is a single undo entry. A block move snapshots the source and destination rectangles; a line reorder stores the inverse permutation, so no snapshot is needed.
Related
- Selection & Events — the selection follows the moved block.
- Auto Fill — the fill handle, which takes priority over the move border.
- Clipboard — cut/paste, the other way to relocate data.
- Row & Column — insert/delete, which shifts data rather than reordering it.