Auto-disappearing menu and editor focus: approaches?

We got a design brief stating that the menu should only appear while a user is interacting with the editor.

We have a custom toolbar component, and using React to render everything.

The DOM structure is like this:

  • Wrapper component
    • Editor
    • Toolbar

(in reverse for tab-ordering issues, the toolbar renders on top of the editor via flex-reverse).

The main issue we’re facing is that the EditorView constantly loses focus when the user is interacting with the menu. The solution to that, so far, is to fastidiously call editorView.focus() after each dispatch or command application.

However, this approach breaks in two cases:

  • more complex UI, e.g. dropdowns for text alignment, you have to keep track whether the dropdown is open, and then keep showing the toolbar, even if the editor has lost focus.
  • general annoyance, e.g. the toolbar has a bit of margin around it, and then clicking on the margin makes the editor lose focus. You have to make sure that the toolbar traps those clicks and either re-focuses the view or stops propagation etc.

There’s ways around all of that of course, like keeping track of open popups and dialogs, trapping clicks etc etc, but it feels like going against the grain.

I wonder if there’s any other solution for this, e.g: could I somehow put the toolbar inside the content-editable component so that interacting with it wouldn’t steal the focus from the editor? Would that even work? Any other approaches that come to mind?

What about using blur inside view’s handleDOMEvents ? You can come up with something inside there that will solve all the issues you are having

The blur event is not cancellable, so not sure what’s there to do other than calling focus on the view. Not sure how would the logic look to detect when this is warranted or not.

Why would you want to cancel it? Your goal is when someone miss clicks on the margin or the area or next to the focused view, inside blur refocus the editor. For dropdowns etc you can use onMouseDown

 onMouseDown={event => {
          event.preventDefault();
          setIsOpen(!isOpen);
        }}

Sure, that’s what I’m doing now in various places. I was wondering if there’s a different DOM structure that I could use to avoid guarding against blurs in the first place.

As I understand your problem, the template text editor available on Prosemirror’s home page avoids your issues: https://prosemirror.net/

Maybe you’ll see something in the source? I noticed that that text editor uses divs for the menu items, and you maybe use buttons (which are focusable, and grabbing focus away from the editor). You can make them divs on your side and use role=“button” for ARIA?

1 Like

Divs vs buttons are a clever trick. I will investigate further.

Don’t think so as it’s native browser behavior for things with tab index to take focus when clicked.

For example within a todo item node view that has a non editable checkbox, I use the mouse down prevent default trick to stop cursor from disappearing on checkbox click, but still toggling state.