Editing & selection
Keyboard & hotkeys
The grid's full keyboard model, and hotkeys of your own that act on the focused row, such as B to buy and Delete to cancel.
The grid is one tab stop: Tab moves focus into it, and keys move within it, as in a spreadsheet. On top of the grid’s own keys, hotkeys binds keys for your app (B to buy, S to sell, Delete to cancel) and hands each binding the row the user is on.
Click a row, then press B, S or Delete; Shift+R brings the removed rows back.
The grid’s keys
| Keys | Action |
|---|---|
| ↑ ↓ ← → | Move the focused cell |
| Shift + arrows | Extend the selection |
| Home / End | First / last column; with Ctrl/⌘, the first / last cell |
| PageUp / PageDown | Move by a page of rows |
| Enter | Edit the focused cell, or run onRowClick on a read-only one |
| F2 | Edit the focused cell |
| A printable key | Start editing the focused cell with that character |
| Space | Tick the focused row (with rowSelection) |
| Ctrl/⌘+A / C / V | Select all / copy / paste |
| Ctrl/⌘+Z / Y | Undo / redo an edit, paste or fill |
| Ctrl/⌘+F | Open the find bar (with findBar) |
| Alt+↓ | Open the column menu (with columnMenu) |
| ContextMenu / Shift+F10 | Open the row menu (with rowMenu or rowPinning) |
| → / ← on a tree row | Expand / collapse it |
| Enter / Space / → / ← on a group header | Toggle / expand / collapse the group |
| Escape | Clear the selection, or close a menu |
While a cell is being edited, keys belong to the editor: Enter saves, Tab saves and moves to the next cell (Shift+Tab: the previous one), and Escape cancels. Each of them gives the keyboard back to the grid. Headers are focusable too: Enter or Space sorts, with Shift to add a sort key. Menus follow the usual menu pattern (arrows move, Enter chooses, Escape closes and returns focus). Screen-reader semantics are in Accessibility.
Hotkeys
hotkeys maps a binding to a handler. Bindings run before the grid’s own keys, and each handler gets the focused row and column (null when there is none) and the event:
<script lang="ts">
import { Grid, type Hotkeys } from 'bo-grid';
const hotkeys: Hotkeys = {
b: ({ row }) => { if (row) openTicket(row, 'buy'); },
s: ({ row }) => { if (row) openTicket(row, 'sell'); },
Delete: ({ row }) => { if (row) cancel(row); },
'Shift+C': () => cancelAll(),
'Mod+K': () => openSearch(), // Ctrl+K, or ⌘K on a Mac
};
</script>
<Grid {rows} {columns} height={480} {hotkeys} /> Writing a binding
- A binding is a key (
'b','Delete','F2','Space','Enter') after any ofShift+,Alt+,Ctrl+,Meta+andMod+(Ctrl or ⌘, whichever is pressed). Case does not matter. - A symbol is bound by the character it types:
'?', not'Shift+/'(which throws). - A letter also fires from its place on the keyboard when the layout types another alphabet there, so B still works on a Cyrillic or Greek layout.
- A binding that cannot be read, or a key bound twice, throws when the grid gets the map.
When a binding runs
- Return
falsefrom a handler to leave the key to the grid, for example to keep the grid’s own Enter on some rows:Enter: ({ row }) => (row?.locked ? undefined : false). - Keys go to the grid, not to a binding, while a cell is being edited, or while an input, button or menu inside the grid has them.
- A held-down key runs its binding once, so holding B opens one ticket.
The focus stays on its row
The selection and an open editor belong to rows, not to positions. When rows are added, removed or reordered (a new rows array, a row arriving above the focused one), the focus, the selected range and the editor move with their rows. A hotkey acts on the row the user is looking at, and an edit is written to the row it was opened on. If the focused row goes away, the selection clears; if the edited row goes away, the edit closes unsaved. (With a server source, rows are where the source puts them.)
api.getFocusedCell() returns the same { row, column } from code, and api.focusCell(rowId, columnKey) moves the focus.
Reference
hotkeys Hotkeys Default undefinedKeys for the app, run before the grid's own. A binding is a key ('b', 'Delete', 'Space'; a symbol by the character it types: '?', not 'Shift+/'), after any of Shift+, Alt+, Ctrl+, Meta+ and Mod+ (Ctrl or ⌘, either). Case does not matter. The handler is given the focused row and column (null when there is none) and the event; return false to leave the key to the grid. Keys go to the grid, not to a binding, while a cell is edited or an input, button or menu inside the grid has them, and a key a binding took runs it once when held down. A binding that cannot be read, or binds a key twice, throws (BG1001–BG1004).
Example
<Grid {rows} {columns} height={480}
hotkeys={{ b: ({ row }) => { if (row) buy(row); }, 'Shift+C': cancelAll, Delete: ({ row }) => { if (row) cancel(row); } }} />onRowClick (row: GridRow, event: MouseEvent | KeyboardEvent) => void Called when a data row is activated by click or Enter (open a detail view, navigate, …). Edit-input and checkbox clicks are excluded.
Example
<Grid {rows} {columns} height={480} {selectedRowId} onRowClick={(row) => (selectedRowId = row.id)} />rowMenu (row: GridRow) => Array<{ label: string; onSelect: () => void }> Right-click row menu. Return the items for a row; an empty array shows no menu. Each item runs onSelect and closes the menu.
Example
<Grid {rows} {columns} height={480}
rowMenu={(row) => [{ label: 'Cancel order', onSelect: () => cancel(row) }, { label: 'Copy id', onSelect: () => copy(String(row.id)) }]} />ariaLabel string Default undefinedAccessible name for the grid (aria-label on the role="grid" root).
getFocusedCell (): { row: GridRow; column: ColumnDef } | null The focused cell's row and column, or null when no data cell has focus (nothing focused yet, or the focus is on a group row). For hotkeys that act on the row the user is on.
Example
const cell = api.getFocusedCell();
if (cell) openTicket(cell.row);focusCell (rowKey: string | number, columnKey: string): boolean Focus (and select) one cell by row id and column key. Returns false when the row or column is not currently visible.
Example
if (!api.focusCell(row.id, 'qty')) api.scrollToRow(row.id);