Skip to content

[Feature request] Imperative undo/redo API for iOS & Android (with canUndo/canRedo state) #675

Description

@yagiz2000

[Feature request] Imperative undo/redo API for iOS & Android (with canUndo/canRedo state)

Is your feature request related to a problem? Please describe.

I'm building a note-taking app on EnrichedTextInput and there is currently no way to offer undo/redo toolbar buttons on mobile:

  • EnrichedTextInputInstance exposes no undo() / redo() methods.
  • OnChangeStateEvent has no canUndo / canRedo flags, so a toolbar can't enable/disable the buttons.

Web already supports undo/redo since #622 via @tiptap/extension-history, but only through keyboard shortcuts — there is no imperative API on any platform, and mobile has no history at all. #429 asked for this earlier but was closed for inactivity, so I'd like to pick it up properly.

Today the only workaround is an app-level snapshot stack (debounced getHTML() + setValue() restore), which loses selection, fights the keyboard, and duplicates work the platform can do natively.

Describe the solution you'd like

I'd like to contribute this myself and want to align on the API before opening PRs. Proposal:

1. Imperative methods on EnrichedTextInputInstance:

/** Undoes the last user edit (typing, formatting, link/image insertion). No-op when history is empty. */
undo: () => void;
/** Re-applies the most recently undone edit. No-op when there is nothing to redo. */
redo: () => void;

2. History state on OnChangeStateEvent:

canUndo: boolean;
canRedo: boolean;

Flat booleans rather than the { isActive, isConflicting, isBlocking } shape, since undo isn't a text style — alignment: string is the existing flat-field precedent. Happy to emit these via a separate event instead if you prefer to keep OnChangeStateEvent style-only.

3. Semantics:

  • All user-originated edits are undoable: typing, toggle* formatting, setLink / setImage / setMention.
  • Programmatic setValue() clears the history stack — loading content into the editor must not be undoable into an empty editor.

4. Platform notes:

  • iOS: plan to build on the text view's native NSUndoManager, so shake-to-undo, the three-finger gestures, and hardware-keyboard Cmd+Z stay consistent with the imperative API for free. If the editor's attributed-string mutations turn out not to register cleanly with the undo manager, fall back to a snapshot/command stack.
  • Android: no built-in undo manager on EditText, so this would be a Kotlin-side history stack.
  • Web: thin wiring of the same API onto the already-merged TipTap history (editor.commands.undo() / editor.can().undo()), closing the parity gap with the shortcuts added in feat(web): undo, redo #622.

Delivery: split by platform following the repo's existing pattern (feat(ios): …, then feat(android): …), each with Maestro flows (undo after typing, undo after formatting, setValue reset) and example-app toolbar buttons.

Describe alternatives you've considered

  • App-level snapshot undo (getHTML() + setValue()): works but loses selection/caret, creates large string churn on every keystroke, and can't integrate with OS-level undo gestures.
  • Keyboard-shortcut-only parity with web: hardware Cmd+Z already partially works on iOS, but on-screen toolbar buttons are the primary mobile UX and need both the imperative calls and the canUndo/canRedo state.
  • Exposing only methods without state flags: forces apps to track dirty state themselves and still mis-renders button enablement after undo-to-initial-state.

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions