Skip to content

Undo / Redo ​

The SDK ships with an UndoManager that records state changes across the Engine, Timeline, Layers, Clips, Library, and Subtitles. It is disabled by default. Enable it when initializing the Engine:

typescript
await Engine.getInstance().init({
    enableUndoRedo: true,
    ...
  });

Basic Usage ​

typescript
const undoManager = Engine.getInstance().getUndoManager();

undoManager.undo();
undoManager.redo();
undoManager.canUndo();
undoManager.canRedo();
undoManager.clear();

Grouping Changes ​

By default, each recorded action is its own undo step. To group multiple changes into a single undoable operation, wrap them in a group:

typescript
undoManager.startGroup("Resize and rotate");
clip.style.setScale(0.8, 0.8);
clip.style.setRotation(Math.PI / 6);
undoManager.endGroup();

Call cancelGroup() to discard a group that is still open.

Pausing Recording ​

If you need to apply changes that should not be undoable (for example, temporary previews while a user drags), pause the recorder:

typescript
undoManager.setPause(true);
// ... non-undoable changes ...
undoManager.setPause(false);

For nested pausing (recommended when the same flow can be entered from multiple places), use pushPause() and popPause().

typescript
undoManager.pushPause();
// ... work ...
undoManager.popPause();

You can also inspect the pause stack and clear it when needed:

typescript
const stack = undoManager.getPauseStack();
undoManager.clearPauseStack();

Enabling / Disabling at Runtime ​

typescript
undoManager.setEnabled(false);

Disabling the manager also clears its internal stacks.

Serialization ​

The undo/redo history can be serialized and restored alongside the project. This allows users to continue editing after closing the tab:

typescript
const undoData = undoManager.serialize();
await undoManager.deserialize(undoData);

Events ​

Every undo/redo mutation emits an event. Use these events to update your UI state (for example, to enable/disable the Undo and Redo buttons):

typescript
Engine.getInstance().events.on("undo:changed", ({ canUndo, canRedo }) => {
  undoButton.disabled = !canUndo;
  redoButton.disabled = !canRedo;
});

Engine.getInstance().events.on("undo:performed", ({ group }) => {
  console.log("Undid:", group.name);
});

Engine.getInstance().events.on("redo:performed", ({ group }) => {
  console.log("Redid:", group.name);
});

Custom Undo Actions ​

If you are building on top of the SDK (for example with a Custom Clip), you can record your own undo entries through recordCustomUndo. The action name must not collide with any value in UndoActionEnum (those are reserved for the SDK itself).

typescript
undoManager.recordCustomUndo("my-app:rename", {
  prev: previousName,
  next: nextName,
});

The undo:process:custom event fires when the custom record is undone or redone, giving you a chance to apply the state change yourself:

typescript
Engine.getInstance().events.on(
  "undo:process:custom",
  ({ undoRecord, redoGroup }) => {
    if (undoRecord.action === "my-app:rename") {
      // Add a redo on undo
      redoGroup.records.push(
        new UndoRecord("my-app:rename", {
          prev: undoRecord.data.next,
          next: undoRecord.data.prev,
        }),
      );
    
      applyName(undoRecord.data.prev);
    }
  },
);

Inspecting History ​

Surface the pending undo/redo groups in your UI (for example, to show "Undo: Move clip" in a menu):

typescript
undoManager.canUndo(); // boolean
undoManager.canRedo(); // boolean
undoManager.getUndoHistory(); // string[] of group names, newest last
undoManager.getRedoHistory(); // string[]
undoManager.getPerforming(); // true while an undo/redo is in flight

undo:redo:changed fires on every mutation, so you can drive the UI reactively without polling.

History Size ​

The default history holds ~100 groups. Adjust it with setMaxUndoHistory:

typescript
undoManager.setMaxUndoHistory(500);
const cap = undoManager.getMaxUndoHistory();

Older entries are dropped once the cap is exceeded.

Enable / Disable at Runtime ​

typescript
undoManager.setEnabled(false);
const isOn = undoManager.getEnabled();

Disabling clears both stacks. Useful when importing a project, you don't want the import operations to show up in the user's undo history.