Editing & validation
Enable editing grid-wide with editable: true or per column. editable can be a function:
{ field: 'notes', editable: row => row?.status !== 'done' }
Calculated columns (formula) are read-only unless you set editable on them. Editing one then
creates a cell formula or value for that row only.
Keyboard & mouse
| Action | Keys |
|---|---|
| Move the active cell | arrows, Tab / Shift+Tab, Ctrl/Cmd+arrow to the edge of the data |
| Start editing | double-click, Enter, F2, or just start typing (replaces the content, like Excel) |
| Commit | Enter (moves down), Shift+Enter (up), Tab / Shift+Tab (sideways), clicking elsewhere |
| Cancel | Esc |
| Clear a cell or a range | Delete / Backspace (one undo step) |
| New line in a textarea editor | Shift+Enter or Alt+Enter |
| Toggle a boolean | click the checkbox |
| Copy | Ctrl/Cmd+C: the cell range, the selected rows (tab separated, as displayed) or the active cell |
| Paste | Ctrl/Cmd+V: a tab separated block from the top left of the range; one value fills the whole range |
| Undo / redo | Ctrl/Cmd+Z, Ctrl/Cmd+Y or Ctrl/Cmd+Shift+Z |
Ranges, the fill handle, find / replace and the rest of the shortcuts are in Spreadsheet features.
The change pipeline
Every change goes through the same steps: typing, pasting, setCellValue and checkbox toggles alike.
- Formula? If the text starts with
=and the column allows formulas, it becomes a cell formula. - Parse (edits and pastes): text → typed value using the column type and locale, or
column.parse. - Validate:
column.validaterules. column.beforeChange(detail): returnfalseto cancel or a new value to store instead.beforeCellChangeevent:preventDefault()cancels; you can rewriteevent.detail.value.- Store in SorlaDB, recalculate dependent formulas, re-render the affected rows and footers.
column.onChange(detail)and thecellChangeevent.
A value that fails steps 2–3 keeps the editor open, marks the cell red with the message as tooltip
and emits validationError.
setCellValue(id, field, value) from code skips parsing (pass typed values) but still validates
and fires events. Pass { source: 'edit' } to parse text as if typed. updateRow(id, patch)
writes several fields directly, with no validation and no cell* events (it emits rowChange).
Parsing
| Type | Accepts |
|---|---|
| number types | locale numbers (1.234,56 pt-BR, 1,234.56 en-US), currency symbols, %, 1.5 in a comma locale |
integer |
whole numbers only |
percent |
15 and 15% both store 0.15 |
date / datetime |
ISO, or dd/mm/yyyy (mm/dd/yyyy in en-US) with optional hh:mm; impossible dates are rejected |
boolean |
true/false, 1/0, yes/no, sim/não, x |
select |
option value or label (case-insensitive) |
| empty text | blank (null) |
Validation
validate: { required: true, min: 0, max: 100, minLength: 2, maxLength: 40, pattern: '^[A-Z]', message: 'Custom message' }
validate: (value, row, column) => value !== 13 || 'Unlucky number' // true / undefined = valid, false / string = invalid
validate: [{ required: true }, (value, row) => value <= row.limit || 'Over the limit']
min / max compare numbers, or dates when given ISO strings (min: '2026-01-01'). Blank values
skip every rule except required. Built-in messages are translated (see i18n).
Editors
Built-in: text, number, date, datetime, select, checkbox, textarea. The default comes
from the type. A custom editor is an object with create(context):
const ratingEditor: CustomEditor = {
create({ value, text, column, row, grid, commit, cancel }) {
const input = document.createElement('input');
input.type = 'range';
input.min = '1';
input.max = '5';
input.value = String(value ?? 3);
input.className = 'ig-editor';
return {
element: input,
getValue: () => Number(input.value), // a typed value skips parsing; a string is parsed
focus: () => input.focus(),
destroy: () => {} // optional cleanup
};
}
};
{ field: 'rating', type: 'integer', editor: ratingEditor }
The grid wires Enter/Tab/Esc on element. Call commit() / cancel() yourself for editors with
their own buttons. A plain function (context) => EditorInstance works too.
Rows
const id = grid.addRow({ product: 'New', qty: 1 }, { position: 'start' }); // beforeRowAdd / rowAdd
grid.updateRow(id, { qty: 2, price: 10 }); // rowChange
grid.deleteRows(grid.getSelectedIds()); // beforeRowDelete / rowDelete
grid.setData(rows); // replace everything (dataChange)
The navbar can show Add row (navbar.add: true or a function returning default values) and
Delete (deletes the selected rows after a confirmation modal, unless confirmDelete: false).