Skip to content

Text Editor History

Problem

Per-keystroke history creates too many undo entries. Keyboard handlers must also catch asynchronous Ledger failures.

Solution

Capture the value before each debounced edit, then submit one reversible command.

ts
import { createKeymap } from '@vielzeug/keymap';
import { createLedger } from '@vielzeug/ledger';

const ledger = createLedger();
const textarea = document.getElementById('editor') as HTMLTextAreaElement;
let committed = textarea.value;

function recordEdit(next: string): Promise<void> {
  const before = committed;

  return ledger.do({
    apply: () => { textarea.value = next; committed = next; },
    label: 'Type',
    revert: () => { textarea.value = before; committed = before; },
  });
}

const reportHistoryError = (error: unknown): void => recordHistoryFailure(error);
const map = createKeymap([
  { id: 'undo', shortcut: 'ctrl+z', handler: () => void ledger.undo().catch(reportHistoryError) },
  { id: 'redo', shortcut: 'ctrl+shift+z', handler: () => void ledger.redo().catch(reportHistoryError) },
]);

map.mount(textarea);

function disposeEditor(): void {
  map.dispose();
  ledger.dispose();
}

Pitfalls

  • Capture the prior value before submitting the command.
  • Keep debouncing outside Ledger; Ledger records the reversible transition, not input timing.
  • Dispose the keymap and ledger with the editor owner.