Focus issue in Chrome when the first child node of the node view has contenteditable=false


#1

When a ProseMirror editor is focused using the keyboard (i.e. by pressing Tab), it usually gets the ProseMirror-focused class and the cursor is positioned at the start of the editable content.

However, in Chrome (but not Firefox, where the cursor is positioned as expected), if the first editable node has a node view that adds an element with contenteditable=false at the start of the view, the editor is still focused but the cursor isn’t visible and the editable node isn’t focused.

I’ve made a minimal example that shows this behaviour.

ProseMirror’s behaviour is actually better than native contenteditable in any browser, but it would be nice if it could always focus the contentDOM in Chrome in the same way it does in Firefox.


#2

Opening a bug with Chrome would be a good idea here, since focusing an editable field should definitely put the selection in it.

I’m not quite sure how to work around this in the general case—we could force a selection when the editor receives focus, but I’m not sure whether that has any undesirable side effects (such as a race condition where, when the editor is focused by clicking on it, our code overrides the newly created selection with a default or current selection).

Have you tried adding a focus event handler that calls .focus() on the editor? Does that produce any strange side effects?


#3

That seems to be exactly what was needed - thanks!


#4

This may be a reasonable thing for the editor to do itself—could you try this in production for a bit and report back whether it causes any problems for you?


#5

One issue I’ve found is that view.focus() should only be called when the editor is focused with the Tab key (where restoring the previous selection makes sense), not when clicking with the mouse (where the selection should be set to the clicked position).

I’m trying something like this, which seems to work:

let isMouseDown = false

const view = new EditorView(undefined, {
  handleDOMEvents: {
    focus: () => {
      if (!isMouseDown) {
        view.focus()
      }

      return false
    },
    mousedown: () => {
      isMouseDown = true

      return false
    },
    mouseup: () => {
      isMouseDown = false

      return false
    }
  }
})