Conditionally add classes to the DOM on hover

I’d like to do the following:

  • on right click, context menu appear inside of editor → This is already done with floating-ui library
  • in context menu, there is a button Select fragment which after click, closes context menu and adds to the editor dom two event listenres: First when hovering over any child DOM node inside editor.view, adds a certain classes to its dom (and removes from previously hovered). Second which on click disable the mouseover listener and copy the outerHTML of the last hovered dom to clipboard.

How can I do this? Should I write a plugin with a state which control whether listeners should be active, define these listeneres in handleDOMEvents and adds classes to the nodes by using decorations?

EDIT Currently I have the following:

const contextMenuPlugin = new Plugin<IContextMenuState>({
      state: {
        init(config, instance) {
          return {
            active: false,
            pos: 0,
        apply(tr, value, oldState, newState) {
          const newPluginState =
            tr.getMeta(CONTEXT_MENU_PLUGIN_KEY) || tr.getMeta('contextMenu');
          if (newPluginState) return newPluginState;
          return value;

      props: {
        decorations(state) {
          const pluginState = CONTEXT_MENU_PLUGIN_KEY.getState(state);
          if (!pluginState?.active) return null;

          const pos = pluginState.pos;
          const node = state.doc.nodeAt(pos);

          const Deco =
            node? === 'text' ? Decoration.inline : Decoration.node;

          const highlightClasses = [

          const decoration = Deco(pos, pos + node?.nodeSize!, {
            class: highlightClasses.join(' '),

          return DecorationSet.create(state.doc, [decoration]);

        handleDOMEvents: {
          mouseover: (view, event) => {
            var state = CONTEXT_MENU_PLUGIN_KEY.getState(view.state);
            if (!state?.active) return;

            let target = as HTMLElement;
            let selectableTarget: HTMLElement | null = target.hasAttribute(
              ? target
              : null;

            while (!selectableTarget && target.parentElement) {
              target = target.parentElement;
              selectableTarget = target.hasAttribute('data-copyable')
                ? target
                : null;

            if (!selectableTarget) return;

            const parent = selectableTarget.parentElement;
            let children = Array.from(parent?.childNodes || []);
            let offset = children.indexOf(selectableTarget);

            const pos = view.posAtDOM(parent!, offset);
            const newState = {
    , newState)
          mousedown: (view, event) => {
            var state = CONTEXT_MENU_PLUGIN_KEY.getState(view.state);
            if (!state?.active) return false;
            const newState = { ...state, active: false };
            // const tr =;
            // tr.setSelection(NodeSelection.create(tr.doc, newState.pos))
              // .focus()
              .setMeta('contextMenu', newState)

Decorations are properly added on hover but there is a problem with mousedown event. In DevTools everthing works fine, i.e. node is selected and ProseMIrror.selectedNode class is added. However when I close devtools, for some reason the ProseMIrror.selectedNode class disappear immidiately, i.e. code works fine because it selects a node but then it unselect it immidiately. Any suggestions what might be the reason?

EDIT For some reason switching from mousedown to click event solves the problem, why?