Lattice Grid Buy a licence

api reference

Events

One bus: grid.on, grid.once, grid.off, and the wildcard subscription, with the full event table generated from the declarations.

API reference › Events

Events

One bus. There are no onX configuration properties, and every event is described once, in the generated event table at the end of this page: all 162 of them, with when each fires, the payload interface a handler receives and whether it can be cancelled. That table is generated from the declarations, so it is the release’s own answer rather than a second list to keep in step.

const off = grid.on('cell:changed', e => save(e.row.data));
grid.once('ready', init);
grid.on('*', e => console.log(e.type, e));   // wildcard, for debugging
off();                                       // on() returns its own unsubscribe

What a handler receives. Every payload is a GridEvent: the event’s own fields, plus type, grid and origin. origin is 'api', 'user', 'init' or 'ai' - a host persisting state reads it to ignore its own writes and avoid a feedback loop, and it is how a genuine user gesture is told from a module-driven re-entry.

Cancelling an action. Every user-initiated mutation is gated by a before… event carrying a BeforeEvent: the action’s own context plus preventDefault(reason?), defaultPrevented and reason. Calling preventDefault() (or returning false) cancels the action, and a handler may be async - the mutation is held until every before-handler settles, which is what makes a confirm dialog or a server check a genuine gate. On a veto the paired <action>:cancelled fires with the reason. Host/API writes and remote or router-applied deltas (origin !== 'user') do not fire these.

The declared list is complete, and stays complete: tools/check.js compares every emit() in the grid against the declared event names and fails the build on a mismatch. Each module - charts, the tabbed grid, the kanban board, the KPI panel, the dashboard layout, the Gantt, the AI controller and the Data Router - raises its own events, which are listed on that module’s own surface rather than on this bus.

A declared event is reachable directly, executed

Every name in the event table is a first-class event: grid.on(name, ...) binds it without the unknown-name warning, and each maps to a framework handler prop. Shown for annotation:changed, which promoted from a wildcard-only emission to a declared event. Run headless on every build.

const { createHeadlessGrid } = await import('../packages/core/src/index.js');
const { isKnownEvent } = await import('../packages/core/src/events/index.js');
const { handlerName } = await import('../packages/modules/shared/adapter.js');

const grid = createHeadlessGrid({ columns: [{ field: 'a' }], rows: [] });

// Binding a declared event does not trip the unknown-name warning that an
// undeclared one would - that warning is exactly what removed.
const warnings = [];
const original = console.warn;
console.warn = (...a) => warnings.push(a.join(' '));
const off = grid.on('annotation:changed', () => {});
console.warn = original;
off();
grid.destroy();

return [
  isKnownEvent('annotation:changed'),   // declared at on() time
  warnings.length,                      // 0: no unknown-event warning
  handlerName('annotation:changed'),    // the adapter prop the frameworks expose
].join(' | ');

Cancellable before-events: guarded editing and confirm-before-delete, executed

Every user-initiated mutation has a cancellable before event. A handler cancels the pending action with preventDefault(reason?) and may be async - the mutation is held until it settles, which is what makes a confirm dialog or a server check a genuine gate. On a veto the paired <action>:cancelled fires with the reason. Host/API writes and remote deltas do not fire them. Run headless on every build.

const { createHeadlessGrid } = await import('../packages/core/src/index.js');
const grid = createHeadlessGrid({
  rowKey: 'id',
  columns: [{ field: 'id' }, { field: 'name', edit: { enabled: true } }, { field: 'v', type: 'number', edit: { enabled: true } }],
  rows: [{ id: 'a', name: 'Ann', v: 1 }, { id: 'b', name: 'Bo', v: 2 }],
  selection: 'multiple',
});

const log = [];

// Guarded editing: veto a commit on a locked row, and hear the cancellation.
grid.on('beforeEdit', (e) => { if (e.key === 'a') e.preventDefault('locked'); });
grid.on('edit:cancelled', (e) => log.push('edit ' + e.reason));

// Guard the query and layout surfaces.
grid.on('beforeSort', (e) => e.preventDefault('view-locked'));
grid.on('sort:cancelled', () => log.push('sort'));
grid.on('beforeFilter', (e) => e.preventDefault('no-filter'));
grid.on('filter:cancelled', () => log.push('filter'));
grid.on('beforeColumnMove', (e) => e.preventDefault('fixed'));
grid.on('columnMove:cancelled', () => log.push('colmove'));
grid.on('beforeColumnResize', (e) => e.preventDefault('fixed'));
grid.on('columnResize:cancelled', () => log.push('colresize'));
grid.on('beforeColumnHide', (e) => e.preventDefault('mandatory'));
grid.on('columnHide:cancelled', () => log.push('colhide'));
grid.on('beforeGroup', (e) => e.preventDefault('frozen'));
grid.on('group:cancelled', () => log.push('group'));
grid.on('beforeRowMove', (e) => e.preventDefault('ordered'));
grid.on('rowMove:cancelled', () => log.push('rowmove'));
grid.on('beforeRowAdd', (e) => e.preventDefault('quota'));
grid.on('rowAdd:cancelled', () => log.push('rowadd'));
// A row dropped in from another grid: fires on the receiving grid, naming the
// row under the pointer, so "assign this to that" can veto the insert.
grid.on('beforeRowReceive', (e) => { if (e.overKey !== null) e.preventDefault('assigned'); });
grid.on('rowReceive:cancelled', () => log.push('receive'));
grid.on('beforeSelect', () => {});
grid.on('selection:cancelled', () => log.push('sel'));

// Confirm before delete: an async handler holds the delete until it settles.
grid.on('beforeDelete', async (e) => { await Promise.resolve(); e.preventDefault('user cancelled'); });
grid.on('delete:cancelled', (e) => log.push('delete ' + e.reason));

// Past-tense notifications a host can also observe (not gates): print, remote
// export, and the keyboard-shortcuts overlay.
grid.on('print:before', () => log.push('print-before'));
grid.on('print:after', () => log.push('print-after'));
grid.on('export:request', () => {});
grid.on('export:done', () => {});
grid.on('shortcuts:opened', () => {});
grid.on('shortcuts:closed', () => {});

// A vetoed sort (sync) and an edit blocked on the locked row.
grid.sort.set([{ col: 'v', dir: 'desc' }]);
grid.edit.start('a', 'name');
grid.edit.stop(false, { value: 'Nope' });

return grid.sort.get().length + '|' + grid.rows.byKey('a').data.name + '|' + log.join(',');