Undo and history groups

What one undo step holds, what undo and redo restore, and how to make several of your own commands one undo step.

Every edit the user makes is one undo step. Undo restores the slide and the selection from before the step, and redo restores the slide and the selection from after it. Run editor.exec({ type: 'undo' }) and editor.exec({ type: 'redo' }), or check first with editor.can(command).

What one undo step holds

User actionUndo steps
Typing at a caretOne step for the whole burst. A caret move, a click, a selection change, any command, undo, and redo end the burst.
Backspace, Delete, Enter, or typing over a selected rangeOne step each
Moving, resizing, rotating, or cropping with the pointerOne step for the whole drag, from press to release
Dragging a table grid lineOne step for the whole drag
Arrow keys that nudge the selected shapesOne step for consecutive nudges of the same selection, also with key repeat. Another selection or another command starts a new step.
Any other commandOne step

A command that has nothing to do adds no step. When the browser cancels a pointer drag (pointercancel), the drag ends where it is and keeps one step.

While the pointer moves, a drag that moves, resizes, or rotates shapes shows a preview and does not change the deck. The release writes the drag as one edit, so subscribe listeners and collaboration peers see one change. Press Escape before the release, or move the focus out of the window, to cancel the drag. The deck then does not change. An undo, a redo, or a change to a dragged shape or to its group also cancels the drag. The change can come from a command or from a collaborator. A change to another shape keeps the drag. Crop drags and table grid line drags still change the deck at each pointer move, and Escape does not cancel them. Each of these drags is still one undo step.

Group your own commands

Use a history group to make several commands one undo step. Open one with editor.beginHistoryGroup(), pass it to each command as { historyGroup }, and call end() when the gesture ends:

import type { Editor } from '@pptx-editor.dev/core';

function moveAndTurn(editor: Editor) {
  const group = editor.beginHistoryGroup();
  try {
    const moved = editor.exec({ type: 'nudge', dx: 914400, dy: 0 }, { historyGroup: group });
    if (!moved.ok) return moved;
    return editor.exec({ type: 'rotateObjects', degrees: 90 }, { historyGroup: group });
  } finally {
    group.end();
  }
}
// One `editor.exec({ type: 'undo' })` reverts the move and the turn.

Call end() in a finally block, so a refused command or an exception does not leave the group open. end() keeps the edits already applied, and you can call it more than once.

These rules apply to a group:

  • Undo, redo, and a command with another group or with no group split the step. A later command with the same open group starts a new step.
  • A selection change, and a command that the editor refuses before it writes, do not split the step.
  • Redo restores the selection after the last command of the group.
  • group.state is 'open' until end(). load() and destroy() close every group.
  • exec and can refuse a closed group, a group from another editor, and any other value with errors.unsupported. The document does not change.

Group a slider or a stepper

Toolbar value slots take a group as the last argument. A host slider sends each value while the pointer moves, and one undo step reverts the whole drag. In React:

import { useEditorValueCommand, useHistoryGroup } from '@pptx-editor.dev/react';

export function FontSizeSlider() {
  const size = useEditorValueCommand('font.size');
  const group = useHistoryGroup({ kind: 'range' });
  return (
    <input
      ref={group.ref}
      type="range"
      aria-label="Font size"
      min={8}
      max={96}
      value={size.value ?? 18}
      disabled={!size.isEnabled}
      onChange={(event) => {
        size.execute(Number(event.currentTarget.value), group.options());
      }}
    />
  );
}

font.size takes points. The hook groups pointer and keyboard gestures and releases its binding when the control unmounts.

Vue provides the same useHistoryGroup({ kind: 'range' }) pattern. Bind group.ref to the control and pass group.options() with each write.

For adapter examples, see React hooks and Vue composables.

Collaboration

With the collaboration module from @pptx-editor.dev/pro, undo reverts only your own steps, in the room. A group is one step in the room too, also when a collaborator edits the deck in the middle of your gesture. Without the module, a store transaction with the remote origin clears the local undo and redo history, because undo restores whole slides and would revert that edit.

Next steps