[w3c/editing] Ability to rollback changes after IME, so that editors that manage their own incremental rendering can assume the same DOM as before the IME (Issue #510)

michael created an issue (w3c/editing#510)

Professional web-based rich text editors maintain their own model to represent their document state. They do not rely on the DOM as a source of truth. So the principle is that whenever you want to make a change to the document, you first update the model, and then the change propagates to the UI.

Unfortunately (and this is a big deal) contenteditable can't guarantee us editor developers that DOM changes are only made through our model, and not directly through contenteditable's native behavior. During character composition (IME), the native implementation of the browser will change the DOM as you compose a character or word, violating our "model change triggers rerenders" paradigm.

I understand that IMEs and hard-wired with the OS, and you need a way to display the state of the composition until it is finished.

However, can't there be a way for editor developers to "reset the DOM state" to what it was before, at the compositionend stage?

Here's some pseudocode illustrating how this could look like:

```js
let before_input_selection;

function onbeforeinput(event) {
  if (event.isComposing) {
    // skip: handled by oncompositionstart/end
  } else {
    // handle any other inputs, including replacements using event.getTargetRanges()
  }
}

function oncompositionstart(event) {
  before_input_selection = __getModelSelectionFromTargetRange(event.getTargetRanges()[0]);
}

function oncompositionend(event) {
  // Step 1 - I need a way to roll back the DOM changes to have a clean slate for step 3
  // NOTE: I tried document.execCommand('undo') here, which would basically do the
  // same thing, it works in Chrome but breaks in weird ways in Safari/Firefox.
  event.resetDOM(); 
  // Step 2 - Set the model selection to the state right after the composition started 
  // (where no changes were applied yet - e.g. to the word the composition started from)
  doc.selection = before_input_selection;
  // Step 3 - Apply the final composition to (this will replace the contents of before_input_selection)
  const tr = doc.tr;
  tr.insert_text(event.data);
  // this applies the change to the model, and triggers an incremental rerender of the affected part of the document
  doc.apply(tr);
  // Now my incremental reactive rendering (triggered by doc.apply(tr)) can happen reliably, knowing that the DOM is in the same state as before the composition started.
}
```

My approach here treats the complete composition as a single input to be applied to my internal model. My model would not reflect the in-between inputs during composition. That means I'd let the browser change the DOM during composition to visualize the current composition state. However, at oncompositionend stage, I'd roll back the DOM changes to have a clean slate again. Then i can apply the change to my model, and incremental rerender (in my case implemented by Svelte's reactivity system) can do its usual job.

NOTE: resetDOM should only undo the DOM changes caused by the composition that just ended, all other DOM changes that might have happened in the meanwhile are not considered. I think this approach should be solid, even in a collaborative editing scenario, because character composition is something that happens locally (only you see it) until committed/confirmed, then the change should be distributed to collaborators.

Also see ???.

-- 
Reply to this email directly or view it on GitHub:
https://github.com/w3c/editing/issues/510
You are receiving this because you are subscribed to this thread.

Message ID: <w3c/editing/issues/510@github.com>

Received on Monday, 17 November 2025 14:19:38 UTC