Nested draggable editors behave differently in all browsers

I try to use a nested editor instance within a draggable node view. This is useful for some kind of CMS like seen below (used in a Craft CMS plugin):

My goal is to be able to drag the node and edit the text in it.

However, Chrome seems to be the only browser that works as expected:

In Safari, I’m not able to click within the nested editor at all:

In Firefox, I’m able to click within the nested editor but the cursor always jumps to the start of the node:

There are no issues in Safari and Firefox if I change draggable to false. But I would like to make them draggable :sweat_smile:

I created a glitch demo here:

1 Like

This got me interested in how Prosemirror implements dragging of nodes that are editable since this is similar to that in a way. PM adds a draggable attribute on demand to such nodes (compare that to the example in glitch with a permanent draggable attribute), when it deems the user might be interested in drag. This makes me feel that a draggable attribute messes with an editable element and to solve the problem we might need to do something similar to what pm-view has done src code.

1 Like

I filed the original issue with the Tiptap crew on this one Unable to focus in nested tiptap editor inside custom node view on Safari · Issue #1403 · ueberdosis/tiptap · GitHub and wondering if there’s an update, or any ideas I should pursue?

We’ve also found a similar issue with Firefox Draggable custom node view - unable to focus cursor in `textarea` or `input` elements in Firefox · Issue #1668 · ueberdosis/tiptap · GitHub, once again linked to settings draggable.

Sorry to reference Tiptap issues here, but I believe they might be ProseMirror issues, all related to dragging. In both bug cases, if we disable dragging on our custom node views, the issue is resolved. Please also feel free to direct me to this being a Tiptap issue, and not a ProseMirror one if you feel that’s the cause.

I’d argue they are browser issues—there’s no consensus on how draggable+editable text should work, and browsers do different things (even Chrome’s behavior isn’t ideal I think). It’s usually better to use drag handles separate from the editable text.

1 Like

Yep, certainly seems like (wonderful) browser bugs. In our case, the drag handles are separate to the editable text, the OP video probably doesn’t show that very well.

Here’s some videos showing ProseMirror (via Tiptap) with custom node views that are draggable, but contain other elements that are clickable/selectable (a <textarea> element). You’ll note that we have a separate drag handle to control how to drag “blocks” of content. But in Firefox, as soon as we enable the custom node to be draggable it prevents any clicking on the inner <textarea>. Something tells me that it’s a browser quirk about the mouse-click event, potentially when listening for draggability?

This works as expected with Chrome (and Safari): Screen Capture on 2021-11-09 at 10-53-22.mp4 - Droplr

Firefox is seemingly the only one with an issue: Screen Capture on 2021-11-09 at 10-57-14.mov - Droplr

Hope that illustrates the issue a little?

Are you making the entire node draggable, or only the handle?

I think the entire node, if this is anything to go by:

Node.create({
    name: 'vizyBlock',
    isBlock: true,
    inline: false,
    group: 'block',
    draggable: true,
    defining: true,
    selectable: true,

Without draggable: true, the drag handle doesn’t work - but I imagine that’s up to Tiptap’s implementation of how their drag handle works?

But yes, it wouldn’t be desirable to have the entire node draggable, dragging should only be allowed when click-dragging on the drag handle.

The draghandles in tiptap work in such a way that within stopEvent (tiptap/NodeView.ts at 26303f3f076896e605f2f41e7c736d2153852d08 · ueberdosis/tiptap · GitHub) it is looked whether the dragging is started within the drag handle. Otherwise the dragging is blocked. It’s based on the ProseMirror draggable option.

Thanks for the clarification @philippkuehn do you think this would be an issue with Tiptap draggable handling? Sorry for my lack of understanding, but from looking at your link, that seems to be where the logic for handling dragging takes place (replacing rather than extending ProseMirror)?

No, I think it’s a browser or ProseMirror thing. Because of that I started this thread with a plain ProseMirror demo.

I did forget that, but I also note that the entire node view is draggable (which is buggy as the video shows), but your demo doesn’t use a drag handle?

Refer to the original recreation with Tiptap - tiptap-firefox-inputs - CodeSandbox

I’ve tried to recreate it with ProseMirror (I can’t figure out how to remove the draggable="true" from the nested NodeView), but that seems to work? https://carpal-flawless-alto.glitch.me

Alright, so this is entirely a browser issue, as illustrated by this extremely simple example without ProseMirror - Edit fiddle - JSFiddle - Code Playground

I’m wondering if we’re able to disable draggable on the entire node view and only have it enabled for the drag handle - which is actually what we want anyway. But I think that’s more of a Tiptap thing.

Feel free to mark as closed, or maybe something to configure with Tiptap @philippkuehn .

I’ve kept the custom view using draggable: true as without it, we’d lose dragging functionality. Instead, after initialization, I’m removing the draggable="true" attribute from the DOM element. This doesn’t seem to affect draggability, but does fix Firefox’s inability to select text.

1 Like

Reviving this, as we’re running into the Firefox issue. On clicking an input child of a draggable node, Firefox can only put the cursor at the start of the input.

When I set the node to non-selectable and non-draggable, it works. But we want nodes to be selectable and not draggable.

I’m curious about this line in ProseMirror. Is there a way that we could suppress it? The docs and this code say that selectable nodes are always draggable when selected.

if (this.contentDOM || !this.node.type.spec.draggable) (this.dom as HTMLElement).draggable = true

Edit: node spec selectNode should override that, but MouseDown still adds draggable on mouse down. I don’t see a way to override that.

Edit: workaround. By keeping the node selectable and draggable, I can remove draggable attribute just in time to make the Firefox glitch bearable.

In node spec:

        stopEvent(event: Event) {
          if (event.type === "mousedown" || event.type === "pointerdown") {
            const target = event?.target as HTMLElement;
            if (withinNodeThatStopsSelection(target)) {
              // Still has a momentary glitch with the initial selection.
              // ProseMirror will add draggable again if dragging from elsewhere
              // in the node.
              dom.removeAttribute("draggable");
              // DocEditor doesn't need to handle this event
              return true;
            }
          }
          return false;
        },
const disallowSelectionNodeNames = ["SELECT", "INPUT", "BUTTON"];
const withinNodeThatStopsSelection = (target: HTMLElement) => {
  let current: HTMLElement | null = target;
  while (current) {
    if (disallowSelectionNodeNames.includes(current.nodeName)) {
      return true;
    }
    current = current.parentElement;
  }
  return false;
};

What’s an ‘input child’? If the node view renders as an <input> element it won’t have a contentDOM and won’t activate the line of code that you link, right?

I might have missed where the draggable attribute is being added on node selection vs mouse down. I think I have a workaround, setting the nodes to draggable: false, and using stopEvent to ignore all events that don’t target a specific drag handle. (Instead of my withinNodeThatStopsSelection above.)