bo-grid v3.1.0

Editing & selection

.md

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.

keyboard.svelte GitHub ↗

The grid’s keys

KeysAction
↑ ↓ ← →Move the focused cell
Shift + arrowsExtend the selection
Home / EndFirst / last column; with Ctrl/⌘, the first / last cell
PageUp / PageDownMove by a page of rows
EnterEdit the focused cell, or run onRowClick on a read-only one
F2Edit the focused cell
A printable keyStart editing the focused cell with that character
SpaceTick the focused row (with rowSelection)
Ctrl/⌘+A / C / VSelect all / copy / paste
Ctrl/⌘+Z / YUndo / redo an edit, paste or fill
Ctrl/⌘+FOpen the find bar (with findBar)
Alt+↓Open the column menu (with columnMenu)
ContextMenu / Shift+F10Open the row menu (with rowMenu or rowPinning)
→ / ← on a tree rowExpand / collapse it
Enter / Space / → / ← on a group headerToggle / expand / collapse the group
EscapeClear 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 of Shift+, Alt+, Ctrl+, Meta+ and Mod+ (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 false from 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

Grid props from the types
hotkeys Hotkeys Default undefined

Keys 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 undefined

Accessible name for the grid (aria-label on the role="grid" root).

Grid API from the types
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);