Skip to content

UndoRedoPlugin

Since v0.1.1

Undo/Redo Plugin for tbw-grid

Tracks all cell edits and lets users revert or replay changes with familiar keyboard shortcuts (Ctrl+Z / Ctrl+Y). Maintains an in-memory history stack with configurable depth—perfect for data entry workflows where mistakes happen.

> Required Dependency: This plugin requires EditingPlugin to be loaded first. > UndoRedo tracks the edit history that EditingPlugin creates.

import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';
import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';
ShortcutAction
Ctrl+Z / Cmd+ZUndo last edit
Ctrl+Y / Cmd+Shift+ZRedo last undone edit
OptionTypeDescription
maxHistorySize?numberMaximum number of actions to keep in history. Default: 100
import { queryGrid } from '@toolbox-web/grid';
import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';
import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
columns: [
{ field: 'name', header: 'Name', editable: true },
{ field: 'price', header: 'Price', type: 'number', editable: true },
],
plugins: [
new EditingPlugin({ editOn: 'dblclick' }), // Required - must be first
new UndoRedoPlugin({ maxHistorySize: 50 }),
],
};
  • UndoRedoConfig for configuration options
  • EditingPlugin for the required dependency

Extends BaseGridPlugin

Inherited methods like attach(), detach(), afterRender(), etc. are documented in the base class.

Record a cell edit for undo/redo tracking. Call this when a cell value changes.

The row’s stable ID is captured from the row currently displayed at rowIndex, so call this while rowIndex still points at the edited row (e.g. synchronously inside a cell-commit handler). Undo/redo then target that row by ID even after the view is sorted or filtered. Pass rowId explicitly when the row is not in the view (e.g. cell-commit from an updateRow() on a filtered-out row reports rowIndex: -1) — detail.rowId from cell-commit is the natural source.

recordEdit(rowIndex: number, field: string, oldValue: unknown, newValue: unknown, rowId: string): void
NameTypeDescription
rowIndexnumberThe display (processed-row) index where the edit occurred
fieldstringThe field (column key) that was edited
oldValueunknownThe value before the edit
newValueunknownThe value after the edit
rowIdstringStable ID of the edited row; resolved from rowIndex when omitted (since 3.9.2)

Begin grouping subsequent edits into a single compound action.

While a transaction is active, all recordEdit() calls (both manual and auto-recorded from cell-edit-committed) are buffered instead of pushed to the undo stack. Call endTransaction() to finalize the group.

Nesting: transactions are re-entrant. Calling beginTransaction() while one is already open increments a depth counter rather than throwing; the buffer is finalized only when a matching endTransaction() returns the depth to 0. This lets a synchronous burst of commits (e.g. a multi-cell paste, where each cell-commit handler brackets itself with beginTransaction() + queueMicrotask(endTransaction)) coalesce into a single compound undo entry.

Typical usage — group a user edit with its cascaded side-effects:

grid.on('cell-commit', () => {
const undoRedo = grid.getPluginByName('undoRedo');
undoRedo.beginTransaction();
// Record cascaded updates (these won't auto-record)
const oldB = row.fieldB;
undoRedo.recordEdit(rowIndex, 'fieldB', oldB, computedB);
grid.updateRow(rowId, { fieldB: computedB });
// End after the auto-recorded original edit is captured
queueMicrotask(() => undoRedo.endTransaction());
});
beginTransaction(): void

Finalize the current transaction, wrapping all buffered edits into a single compound action on the undo stack.

  • If the buffer contains multiple edits, they are wrapped in a CompoundEditAction.
  • If the buffer contains a single edit, it is pushed as a regular EditAction.
  • If the buffer is empty, this is a no-op.

Undoing a compound action reverts all edits in reverse order; redoing replays them in forward order.

When transactions are nested, only the outermost endTransaction() (the one that returns the depth to 0) finalizes the buffer; inner calls just decrement.

endTransaction(): void

Programmatically undo the last action.

Behaves exactly like Ctrl+Z: emits undo (with the undone action) and history-change, so app state that replays actions from the undo event stays in sync.

undo(): UndoRedoAction | null

UndoRedoAction | null - The undone action, or null if nothing to undo


Programmatically redo the last undone action.

Behaves exactly like Ctrl+Y: emits redo (with the redone action) and history-change.

redo(): UndoRedoAction | null

UndoRedoAction | null - The redone action, or null if nothing to redo


Check if there are any actions that can be undone.

canUndo(): boolean

Check if there are any actions that can be redone.

canRedo(): boolean

Clear all undo/redo history. Emits history-change when there was history to clear.

clearHistory(): void

Get a copy of the current undo stack.

getUndoStack(): UndoRedoAction[]

Get a copy of the current redo stack.

getRedoStack(): UndoRedoAction[]