FreeGrid한국어

Docs

Getting started

FreeGrid is a framework-free TypeScript grid with thin React and Vue wrappers. Install the core package, or the wrapper for your framework.

npm i @freegridjs/core
# React
npm i @freegridjs/core @freegridjs/react
# Vue
npm i @freegridjs/core @freegridjs/vue

Give the grid a container with a height, import the stylesheet once, and pass columns and rows. Only the rows on screen are rendered, so the data size barely matters.

import { createGrid } from '@freegridjs/core';
import '@freegridjs/core/styles.css';

const grid = createGrid(document.getElementById('grid'), {
  columns: [
    { field: 'name', header: 'Name', pinned: 'left' },
    { field: 'country', header: 'Country', filter: 'set' },
    { field: 'joined', header: 'Joined', type: 'date' },
    { field: 'total', header: 'Total', type: 'number', format: '#,##0' },
  ],
  data: rows,
});

Use it from a CDN

No build step? Load the bundle from npm through unpkg with a script tag. It exposes a global FreeGrid and includes all seven languages. @0 follows the newest 0.x release; pin an exact version such as @freegridjs/[email protected] in production. The same files are on jsDelivr: replace unpkg.com with cdn.jsdelivr.net/npm.

<link rel="stylesheet" href="https://unpkg.com/@freegridjs/core@0/dist/cdn/freegrid.css">
<script src="https://unpkg.com/@freegridjs/core@0/dist/cdn/freegrid.min.js"></script>

<div id="grid" style="height: 480px"></div>
<script>
  const grid = FreeGrid.createGrid(document.getElementById('grid'), {
    columns: [{ field: 'name' }, { field: 'total', type: 'number', format: '#,##0' }],
    data: [{ name: 'Kim', total: 1200 }, { name: 'Lee', total: 3400 }],
    locale: 'ko', // all 7 languages are bundled
  });
</script>

Column definitions

Columns are plain objects. The column id is id if you set one, otherwise field; sorting, filters and column state all refer to columns by that id.

const columns = [
  { field: 'id', header: 'ID', type: 'number', format: '0', width: 80, pinned: 'left' },
  { field: 'name', header: 'Name', flex: 1, minWidth: 160 },
  { field: 'price', header: 'Price', type: 'number', format: 'currency:KRW' },
  { field: 'qty', header: 'Qty', type: 'number' },
  // computed column
  { id: 'amount', header: 'Amount', type: 'number', format: '#,##0', valueGetter: row => row.price * row.qty },
  { field: 'status', header: 'Status', filter: 'set', cellClass: p => `status-${p.value}` },
];
NameTypeDescription
fieldstringProperty of the row object shown in this column.
idstringColumn id. Defaults to field; required when there is no field.
headerstringHeader text. Defaults to field.
type'text' | 'number' | 'date' | 'boolean'Drives alignment, formatting, sorting and the default filter.
formatstring | Intl optionsNumber: '#,##0.00', '0.0%', 'currency:KRW', 'integer'. Date: 'date', 'datetime', 'time', 'short', 'medium', 'long'.
width / minWidth / maxWidthnumberPixel widths. Default width 120, min 40.
flexnumberShare of the leftover width. Resizing turns it into a fixed width.
pinned'left' | 'right'Keep the column visible while scrolling horizontally.
hidebooleanStart hidden. Users can show it from the column chooser.
align'left' | 'center' | 'right'Cell alignment. Numbers default to right.
sortablebooleanDefault true.
resizablebooleanDefault true.
filter'text' | 'number' | 'date' | 'set' | falseFilter UI in the column menu. Default depends on type.
comparator(a, b, rowA, rowB) => numberCustom sort order.
valueGetter(row) => unknownComputed value, e.g. quantity × price. Used for display, sort, filter and summaries.
renderer(params) => string | HTMLElementCustom cell content. Strings are inserted as HTML — escape untrusted data.
cellClassstring | (params) => stringExtra CSS class on cells.
childrenColumnDef[]Makes this a column group header.
mergeboolean | (a, b, rowA, rowB) => booleanMerge consecutive rows with the same value into one tall cell.
wrapHeaderTextbooleanLet a long header label wrap.
chooserbooleanfalse keeps the column out of the column chooser.
lockVisiblebooleanUsers cannot hide this column.
editablebooleanAllow editing and pasting (needs field).
editor{ type: 'select', options } | { type: 'textarea' } | (params) => editorEditor for this column. options can be a function of the row (dependent selects).
requiredbooleanReject blank values.
validate(value, row) => true | stringReturn a message to reject a value.

Grid options

NameTypeDescription
columnsColumnDef[]Required. Column definitions.
dataobject[]Rows.
getRowId(row) => stringStable id; keeps selection across setData.
rowSelection'single' | 'multiple'Adds a checkbox column and click selection.
rowHeight / headerHeightnumberPixels. Default follows the font size (36 / 40 at 13px).
themestring'light', 'dark', 'board', 'erp', 'excel', 'classic' or your own.
fontSize / fontFamilynumber / stringGrid font. Row heights follow the size.
localestring | LocalePackUI language, e.g. 'ko' or an imported pack. Default: the browser's language.
sideBarboolean | { open }Dock the Columns panel on the right.
summarySummaryOptionsSummary rows. See Summary rows.
rangeSelectionbooleanExcel-style cell ranges by drag, Shift+click, Shift+arrows, Ctrl+A. Default true.
statusBarbooleanRow counts plus count/sum/average/min/max of the selected cells.
fillHandlebooleanDrag the selection corner to fill editable cells. Default true.
pinnedTopRows / pinnedBottomRowsRow[]Rows fixed above or below the scrolling rows.
findBar / wrapHeaderTextbooleanCtrl+F find bar (default true); wrap all header labels.
keymapRecord<string, action | false>Remap or disable keys, e.g. { Enter: false, 'Ctrl+E': 'edit' }.
contextMenufalse | (params) => itemsfalse keeps the browser menu; a function customizes the right-click menu.

Data and row ids

Rows are ordinary objects. Without getRowId rows are identified by position, so replacing the data clears the selection. With it, the selection survives setData as long as the ids still exist.

const grid = createGrid(el, {
  columns,
  data: rows,
  getRowId: row => String(row.id), // keeps selection and focus stable across setData
});

grid.setData(await fetch('/api/orders').then(r => r.json()));
grid.getData();               // rows as passed in
grid.getDisplayedRowCount();  // rows after filters

Sorting

Click a header to cycle ascending, descending and unsorted. Shift-click (or Ctrl/⌘-click) adds the column as another sort key; a small number shows its order. Empty values always sort last. Text uses the active language's collation, so Korean, accents and numbers inside text sort naturally.

grid.setSort([{ colId: 'country', dir: 'asc' }, { colId: 'total', dir: 'desc' }]);
grid.getSort();
grid.setSort([]); // clear

// custom order
{ field: 'grade', comparator: (a, b) => ORDER.indexOf(a) - ORDER.indexOf(b) }

Filtering

Each column's ⋮ menu holds a filter that matches its type: text (contains, equals, starts/ends with, blank…), number (=, ≠, <, ≤, >, ≥, between, blank), date (equals, before, after, between) and set (a searchable checklist of the column's values). Text matching ignores case. A filled filter icon marks filtered columns.

grid.setFilter('name', { type: 'text', op: 'contains', value: 'kim' });
grid.setFilter('total', { type: 'number', op: 'between', value: 1000, valueTo: 5000 });
grid.setFilter('joined', { type: 'date', op: 'after', value: '2025-01-01' });
grid.setFilter('country', { type: 'set', values: ['Japan', 'South Korea'] });
grid.setFilter('name', null);   // clear one
grid.setQuickFilter('kim seoul'); // every word must match some visible column
grid.on('filterChange', e => console.log(e.filters));

Pinning and column groups

Pinned columns stay visible while the middle section scrolls horizontally. Users can pin and unpin from the column menu. Columns with children become group headers, nested as deep as you like; a group split across pinned sections shows its header in each.

const columns = [
  { field: 'name', header: 'Name', pinned: 'left' },
  { header: 'Revenue 2026', children: [
    { header: 'First half', children: [{ field: 'q1' }, { field: 'q2' }] },
    { header: 'Second half', children: [{ field: 'q3' }, { field: 'q4' }] },
  ] },
  { field: 'total', header: 'Total', pinned: 'right' },
];

Column customization

Users can drag headers to reorder, drag header edges to resize, and use the column menu to autosize, hide, pin or reset columns. Choose columns opens a chooser with search, select all, the group tree and drag-to-reorder; the same panel can be docked on the right with sideBar. Store getColumnState() to remember each user's layout.

const grid = createGrid(el, {
  columns: [
    { field: 'id', lockVisible: true },       // cannot be hidden
    { field: 'internalCode', chooser: false }, // not listed in the chooser
    ...rest,
  ],
  data,
  sideBar: { open: false }, // docked Columns panel (true = closed, { open: true } = open)
});

grid.openColumnChooser();
grid.setColumnVisible('email', false);
grid.autoSizeColumns();             // all columns
grid.autoSizeColumns(['name']);     // one column

// save / restore the user's layout
localStorage.setItem('layout', JSON.stringify(grid.getColumnState()));
grid.applyColumnState(JSON.parse(localStorage.getItem('layout')));
grid.resetColumnState();

Summary rows

Summary rows show totals, averages and other aggregates. Put them at the top or bottom; keep them pinned outside the scroll area, or let them scroll with the data. They recalculate when data, filters or (with scope: 'selected') the selection change, and use each column's format unless a cell sets its own.

createGrid(el, {
  columns,
  data,
  summary: {
    position: 'bottom',   // 'top' | 'bottom'
    pinned: true,         // true: fixed; false: scrolls with the rows
    scope: 'filtered',    // 'filtered' | 'all' | 'selected'
    rows: [
      { label: 'Total', columns: { qty: 'sum', amount: 'sum', id: 'count' } },
      { label: 'Average', columns: { amount: { agg: 'avg', format: '#,##0.0' } } },
      { columns: {
        amount: { agg: 'max', prefix: 'max' },
        country: values => `${new Set(values).size} countries`, // custom
      } },
    ],
  },
});

grid.setSummary({ position: 'top', rows: [...] }); // change at runtime
grid.setSummary(null);                              // remove
grid.getSummaryValues();                            // [{ qty: 1234, amount: 99000, id: 50 }, …]
  • sum, avg, min, max use numeric values and skip blanks; min/max also compare dates.
  • count is the number of rows in scope.
  • A function receives the column's values and the rows; return a number to have it formatted, or a string to show as is.
  • The row label appears in the first visible column without an aggregate in that row.

Editing

Mark a column editable: true and users can edit its cells: double-click, press Enter or F2, or just start typing. Enter saves and moves down, Tab saves and moves to the next editable cell, Esc cancels. Booleans toggle with Space or a double-click.

The editor follows the column type: text, numbers (1,234 is accepted), dates and booleans. Use a select editor for a fixed list. When its options is a function of the row, you get dependent selects such as cities by country. required and validate keep bad values out; the editor stays open with a message until the value is fixed.

Every edit, paste, insert and removal is tracked and can be undone with Ctrl+Z. Changed cells get a small corner mark. When you're ready to save, read getModifiedRows() or the column-level getChanges(), send them to your server, then call commitChanges().

const COUNTRIES = { KR: ['Seoul', 'Busan'], JP: ['Tokyo', 'Osaka'] };

const grid = createGrid(el, {
  columns: [
    { field: 'id', type: 'number' },                                   // read-only
    { field: 'name', editable: true, required: true },
    { field: 'qty', type: 'number', editable: true, validate: v => v >= 0 || 'Must be 0 or more' },
    { field: 'country', editable: true, editor: { type: 'select', options: Object.keys(COUNTRIES) } },
    { field: 'city', editable: true, editor: { type: 'select', options: row => COUNTRIES[row.country] } },
    { field: 'memo', editable: true, editor: { type: 'textarea' } },
    { field: 'active', type: 'boolean', editable: true },             // Space / double-click toggles
  ],
  data,
});

grid.addRows([{ name: 'New' }], 0);
grid.removeSelectedRows();
grid.undo(); grid.redo();

// send only what changed
const { created, updated, deleted } = grid.getModifiedRows();
await save({ created, updated, deleted, cells: grid.getChanges() });
grid.commitChanges(); // or grid.revertChanges()

Spreadsheet tools

Set merge: true on a column and consecutive rows with the same value become one tall cell, which is handy for reports grouped by region or team. Sort by that column first. statusBar: true adds a bar that shows row counts and the count, sum, average, minimum and maximum of the selected cells. Drag the small square at the corner of a selection to fill editable cells: numbers and dates continue their step, text ending in a number counts up, and anything else repeats.

Use pinnedTopRows and pinnedBottomRows for rows that should never scroll away, such as targets or references. Ctrl+F opens a find bar inside the grid; find() does the same from code.

createGrid(el, {
  columns: [
    { field: 'region', merge: true },                 // identical neighbours become one tall cell
    { field: 'team', merge: (a, b, ra, rb) => a === b && ra.region === rb.region },
    { field: 'q1', type: 'number', editable: true, wrapHeaderText: true },
  ],
  data: rowsSortedByRegion,
  statusBar: true,     // "1,000 rows · Count 12  Sum 48,000  Average 4,000 …"
  fillHandle: true,    // drag the corner: 1, 2 → 3, 4, 5 …; Mon → copies; "item 7" → "item 8"
  pinnedTopRows: [{ region: 'Target', q1: 50000 }],
});

grid.find('Seoul');    // highlights matches, focuses the first; Ctrl+F opens the same as a bar
grid.findNext();

Copy, paste and right-click

Select cells like in Excel: drag across them, Shift-click, use Shift with the arrow keys, or press Ctrl+A. Ctrl+C copies the range in a format Excel and Google Sheets understand: numbers stay numbers, dates become YYYY-MM-DD. Ctrl+V pastes cells copied from Excel starting at the selected cell. A single copied value fills the whole selected range.

Pasting only changes columns with editable: true, so ids and computed columns stay safe. Values are converted to the column type: 1,234, ₩12,000, (5) and 12% become numbers; 2026. 3. 5. and 2026년 3월 5일 become dates. Values that can't be converted are skipped and counted in the paste event.

Right-click a cell for Copy, Copy with headers, Paste, autosize, hide and pin. Right-click a header for the column menu. Turn ranges off with rangeSelection: false, or bring back the browser menu with contextMenu: false.

const grid = createGrid(el, {
  columns: [
    { field: 'id', type: 'number' },                        // read-only
    { field: 'name', editable: true },                      // paste allowed
    { field: 'qty', type: 'number', editable: true },       // "1,234", "(5)", "12%" are parsed
    { field: 'due', type: 'date', editable: true },         // "2026-03-05", "2026. 3. 5." …
  ],
  data,
  rangeSelection: true, // default: drag / Shift+click / Shift+arrows / Ctrl+A
  contextMenu: p => [
    ...p.defaultItems,                                      // Copy, Paste, Pin, Hide, …
    'separator',
    { label: 'Open order', action: () => open(p.row) },
  ],
});

grid.on('cellValueChanged', e => save(e.row));              // persist edits
grid.on('paste', e => console.log(e.changed, 'cells changed'));

grid.setRange({ start: { rowIndex: 0, colId: 'name' }, end: { rowIndex: 9, colId: 'qty' } });
grid.copySelection({ headers: true }); // TSV, also written to the clipboard
grid.pasteText('Kim	1,234
Lee	56');

The menu's Paste item reads the clipboard, which some browsers block or ask about. Ctrl+V always works because it uses the browser's own paste event.

Row selection

With rowSelection the grid adds a checkbox column. Click selects a row, Ctrl/⌘-click toggles, Shift-click selects a range, the header checkbox selects every filtered row, and Space toggles the focused row.

const grid = createGrid(el, { columns, data, rowSelection: 'multiple', getRowId: r => r.id });
grid.on('selectionChange', e => console.log(e.selected.length));
grid.getSelectedRows();
grid.selectAll();   // selects the filtered rows
grid.deselectAll();

Keyboard

Click a cell or tab into the grid, then:

KeyAction
← ↑ → ↓Move the focused cell.
Tab / Shift+TabNext / previous cell, wrapping rows; leaves the grid at the ends.
Home / EndFirst / last column.
Ctrl+Home / Ctrl+EndFirst / last cell.
PageUp / PageDownOne screen up / down.
Shift + ← ↑ → ↓ / Home / End / PageExtend the cell range.
Ctrl+ASelect every cell.
Ctrl+C / Ctrl+VCopy to / paste from Excel (TSV).
Ctrl+FFind in the grid.
Shift+F10 / ContextMenuOpen the right-click menu.
Enter / F2 / typingStart editing an editable cell.
Enter / Tab / Esc (editing)Save and move down / save and move right / cancel.
DeleteClear editable cells.
Ctrl+Z / Ctrl+YUndo / redo.
SpaceToggle the focused row’s selection.
EscClose the column menu or chooser.

Formatting and renderers

Numbers and dates are formatted with the browser's Intl APIs in the grid language, so 1234.5 becomes 1,234.5 in English and 1.234,5 in German. Use renderer for anything else; it gets value, formatted, row, rowIndex and column.

{ field: 'total', type: 'number', format: '#,##0' }       // 1,234
{ field: 'rate', type: 'number', format: '0.0%' }          // 12.5%
{ field: 'price', type: 'number', format: 'currency:KRW' } // ₩12,000
{ field: 'price', type: 'number', format: { maximumSignificantDigits: 3 } } // Intl options
{ field: 'joined', type: 'date', format: 'long' }          // 2026년 3월 5일 (ko)

// element renderer
{ field: 'status', renderer: p => {
    const b = document.createElement('span');
    b.className = `badge badge-${p.value}`;
    b.textContent = p.formatted;
    return b;
} }

A renderer that returns a string is inserted as HTML. Escape anything that comes from users, or return an element.

Themes and fonts

Built-in themes: light, dark, and four simple Korean-style themes — board (bulletin board: thick top rule, horizontal lines), erp (every cell bordered, compact), excel (gridlines, green focus) and classic (navy header). Font size and family are one option each; row heights follow the size unless you set them.

createGrid(el, { columns, data, theme: 'erp', fontSize: 12, fontFamily: 'Pretendard, sans-serif' });
grid.setTheme('board');
grid.setFont({ size: 14 });
grid.setFont({ family: null }); // back to the theme font

Make your own theme

/* any class named fg-theme-<name> is a theme */
.fg-theme-brand {
  --fg-font: "Pretendard", sans-serif;
  --fg-font-size: 13px;
  --fg-bg: #fff; --fg-fg: #1d2433; --fg-muted: #6b7385;
  --fg-header-bg: #f2f5fa; --fg-header-fg: #1d2433;
  --fg-border: #d9dfe8; --fg-row-border: #edf0f5;
  --fg-col-line: inset -1px 0 #edf0f5;
  --fg-row-odd: #fff; --fg-row-hover: #f5f8ff; --fg-row-selected: #e6eeff;
  --fg-accent: #3056d3; --fg-focus: #3056d3;
  --fg-header-text-align: center; --fg-header-label-flex: 1 1 auto;
}
VariableKindControls
--fg-font / --fg-font-sizefontFont family and size.
--fg-bg / --fg-fg / --fg-mutedcolorBackground, text and secondary text.
--fg-header-bg / --fg-header-fg / --fg-header-weightcolor / weightHeader colors and weight.
--fg-border / --fg-row-bordercolorOuter border and row lines.
--fg-col-linebox-shadowVertical cell lines, e.g. 'inset -1px 0 #ddd'. Default none.
--fg-row-odd / --fg-row-hover / --fg-row-selectedcolorZebra, hover and selected rows.
--fg-accent / --fg-focuscolorButtons, sort icons and the focused cell.
--fg-radius / --fg-cell-pxlengthCorner radius and cell padding.
--fg-header-text-align / --fg-top-borderkeyword / borderCenter header labels; thick top rule.

Languages

English is built in. Korean, Simplified Chinese, Japanese, Spanish, French and German are separate imports so you only ship what you use. Without a locale option the grid picks the browser language when that pack is registered, otherwise English.

import ko from '@freegridjs/core/locales/ko';
createGrid(el, { columns, data, locale: ko });

// or register once and use codes
import { registerLocale } from '@freegridjs/core';
import ja from '@freegridjs/core/locales/ja';
registerLocale(ja);
grid.setLocale('ja');

// override a few messages
createGrid(el, { columns, data, locale: { ...ko, noRows: '조회 결과가 없습니다' } });

React

@freegridjs/react exports <FreeGrid>. Every grid option is a prop and every event is an on… prop. The ref is the grid API. Changing data, columns, theme, locale, font or summary updates the grid; keep those objects stable with useMemo.

import { FreeGrid } from '@freegridjs/react';
import type { GridApi } from '@freegridjs/core';
import '@freegridjs/core/styles.css';

export function Orders({ rows }) {
  const grid = useRef<GridApi>(null);
  const columns = useMemo(() => [...], []); // keep props stable
  return (
    <FreeGrid
      ref={grid}
      columns={columns}
      data={rows}
      rowSelection="multiple"
      theme="erp"
      locale="ko"
      summary={summary}
      onCellClick={e => console.log(e.row)}
      onSelectionChange={e => setCount(e.selected.length)}
      style={{ height: 520 }}
    />
  );
}
// grid.current?.setSort([{ colId: 'total', dir: 'desc' }])

Vue

@freegridjs/vue exports <FreeGrid> for Vue 3.3+. Events are kebab-case (@cell-click, @selection-change…), and the component ref exposes getApi().

<script setup lang="ts">
import { FreeGrid } from '@freegridjs/vue';
import '@freegridjs/core/styles.css';

const grid = ref();
const sort = () => grid.value.getApi().setSort([{ colId: 'total', dir: 'desc' }]);
</script>

<template>
  <FreeGrid
    ref="grid"
    :columns="columns"
    :data="rows"
    row-selection="multiple"
    theme="erp"
    locale="ko"
    :height="520"
    :summary="summary"
    @cell-click="e => console.log(e.row)"
    @selection-change="e => (count = e.selected.length)"
  />
</template>

API reference

createGrid(element, options) returns this API. Calls after destroy() do nothing.

MethodReturnsDescription
setData(rows)voidReplace all rows.
getData()Row[]Current rows (unfiltered, unsorted).
setColumns(defs)voidReplace column definitions.
getColumnState() / applyColumnState(state)ColumnState[]Save and restore order, width, visibility and pinning.
resetColumnState()voidBack to the column definitions.
setColumnVisible(colId, visible)voidShow or hide a column.
autoSizeColumns(colIds?)voidFit widths to header and visible cells.
openColumnChooser() / closeColumnChooser()voidFloating column chooser.
toggleSideBar(open?)voidOpen or close the docked Columns panel.
setSort(items) / getSort()SortItem[]e.g. [{ colId: 'total', dir: 'desc' }].
setFilter(colId, model | null) / getFilterModel()FilterModelSet or clear one column filter.
setQuickFilter(text)voidSearch all visible columns.
getSelectedRows() / selectAll() / deselectAll()Row[]Selection helpers. selectAll selects the filtered rows.
setSummary(options | null) / getSummaryValues()object[]Change summary rows; read raw totals.
getRange() / setRange(range | null)GridRangeRead or set the selected cell range.
copySelection({ headers? })stringCopy the range as Excel TSV and return it.
pasteText(text){ changed, skipped }Paste TSV/Excel text at the range or focused cell.
startEdit(rowIndex, colId) / stopEdit(cancel?)boolean / voidOpen or close the editor.
addRows(rows, index?) / removeRows(rows | fn) / removeSelectedRows()Row[]Insert or remove rows (tracked, undoable).
getModifiedRows() / getChanges() / hasChanges()objectCreated/updated/deleted rows and column-level diffs.
commitChanges() / revertChanges()voidAccept changes as the new baseline, or undo all of them.
undo() / redo() / canUndo() / canRedo()void / booleanUndo history for edits, pastes and row changes.
fillRange(target)voidFill from the selection into a larger range (series aware).
find(text) / findNext() / findPrevious() / clearFind()number / voidHighlight and step through matching cells.
setPinnedRows({ top?, bottom? })voidChange pinned rows.
getDisplayedRowCount()numberRows after filtering.
scrollToRow(index)voidScroll a displayed row into view.
setTheme(name)voidSwitch theme.
setFont({ size?, family? })voidChange font; family null restores the theme font.
setLocale(codeOrPack)voidSwitch UI language.
on(event, handler)() => voidSubscribe; returns an unsubscribe function.
destroy()voidRemove the grid and its listeners.

Events

EventPayloadWhen
cellClick / cellDoubleClick{ row, rowIndex, colId, value, event }A data cell was clicked.
selectionChange{ selected: Row[] }Selected rows changed.
sortChange{ sort }Sort changed.
filterChange{ filters }A column filter changed.
columnResize{ colId, width }Resize or autosize finished.
columnMove{ colId, toIndex }A column was dragged to a new position.
columnVisible{ colId, visible }A column was shown or hidden.
rangeChange{ range }The cell range changed.
cellValueChanged{ row, rowIndex, colId, oldValue, newValue }A paste changed a cell.
paste{ changed, skipped }A paste finished.
editStart / editStop{ rowIndex, colId, committed? }An editor opened or closed.
validationFailed{ rowIndex, colId, value, message }An edited value was rejected.
rowsAdded / rowsRemoved{ rows }Rows were inserted or removed.
focusChange{ rowIndex, colId }The focused cell moved.