Inner workings: From pressing key to an updated model & DOM?

Hi all,

I’m trying to wrap my head around the order of events from the user pressing a key to updating the document and reflecting the change in the DOM.

Let’s say I have an empty document and the user presses the key “A”.

The keypress has to be turned into a Transform object to update the document and the DOM also has to be updated to show the change.

How is this done internally? I.e. where is the transformation created and applied to the state?

My assumptions so far as to how this could be done:

  • PM intercepts keydown, and calls preventDefault(),
  • PM creates a transformation to append the letter “a” and applies it to the document
  • PM updates the DOM to reflect the change so that document and DOM are in sync again

or does it work more like this:

  • PM intercepts keydown, but ignores it
  • Browser inserts the letter “a”
  • PM receives the MutationObserver event “characterData” and creates a transformation and updates it document
  • No DOM update needed.

… and where does this happen in the code?

Thanks a lot in advance!

For keys bound to commands, the key event is indeed preventDefault-ed, and the command fires a transaction itself.

For text input, the native behavior goes through unmolested. The editor has a MutationObserver (in view/src/domobserver.js) on its editable content, and notices that something happens. It then reads the changed region and compares it to the corresponding range of state.doc (in view/src/domchange.js). If there’s a difference, it is converted to some step—usually a replace step, but if the DOM change looks like it just adds or removes a mark it might be a mark step. That is then added to a transaction and dispatched.

The entire new state is computed before any DOM is updated. Once a new state is available, it is passed to updateState, which syncs the DOM to the new state. If the canonical DOM for the new document corresponds to the DOM changes already produced by the typed key, there may indeed not be a need to mutate the DOM at all.

1 Like

Thanks for the quick answer! I think I found it for the above example of typing one letter: prosemirror-view/domchange.js at dfa84353afc7603a250501cbaaf2a684f6d6c2be · ProseMirror/prosemirror-view · GitHub

What do you do if the browser does something unexpected (I know :man_shrugging:), like inserting an element that you would not have expected?

For example:

<div contenteditable><span style="font-style:italic;">italic</span></div>

If you place the cursor at the end and delete all letters (backspace 6 times), then type “a”, you end up with this in Safari:

<div contenteditable><i>a</i></div>

Do you in this case just use the inserted text ignoring the i element?

Depends if there’s a parse rule in the schema that handles <i>. The basic schema has a rule for this on its em mark.

Ok, I think I got it now. Thanks a lot!