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.
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.
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?
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)?
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?
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
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.
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.)