Make only part of a NodeView Draggable

How can I make only part of my NodeView draggable?

I have a text field in my NodeView and when I try to select the text using click and drag it drags the entire cards NodeView.

I have a drag handle at the edge of my NodeView for dragging but I am unable to make it the only way to drag the NodeView.

Is there some way to set a flag on sub elements that makes them not start the drag action?

Thanks

Did you set draggable to false in the node spec? That’ll prevent ProseMirror from adding a draggable attribute to the wrapping node, which sounds like it might be the problem here.

Hi Marijin,

Thanks for the quick reply!

I tried as you suggested and removed the draggable from the node spec and the node is no longer draggable but that doesn’t let me drag using my drag handle inside the component.

Below you can see the handle is at the left side

Is there a way to make only part of the node view draggable but still drag the entire node view when just the drag handle is being dragged?

1 Like

Hi,

did you every find a solution to this? I saw various examples where that actually did work, but was not able to figure out the exact reason why. Here are some examples:

  1. TipTap drag todos but still able to select the text: https://tiptap.scrumpy.io/todo-list
  2. Nuclino offers a drag handle for list items: https://www.nuclino.com/

It would be great to have some indicator inside the NodeView that would trigger the whole Node to be dragged, like:

<div class="my-parent-node">
   <span data-pm-draggable="true" contenteditable="false">X</span>
   TEXT
</div>

Thanks for your help.

Yeah I would also like to know a solution for this one!

I’m looking at this exact problem at the moment. From what I can tell it looks like you’d need to override stopEvent on your NodeView and return false if the drag originated from the draggable part and true if not.

Any help on this would be much appreciated.

Ok, to make this work you need to return false from stopCapture when you get a mouseDown event inside the “draggable handle” of your component. Then continue returning false until the end of the drag event.

This is roughly what I’ve come up with:

let captureEvents = true;

function resetCapture() {
	captureEvents = true;
	document.removeEventListener('dragend', resetCapture, false);
}

stopEvent(event) {
	const delegateToProsemirror = event.target.getAttribute('delegateevent');
	if (delegateToProsemirror && event.type === 'mousedown') {
		captureEvents = false;
		document.addEventListener('dragend', resetCapture, false);
	}
	return captureEvents;
}

So I add a delegateevent attribute to the part of the NodeView dom that I want to be the “draggable handle”.

you can maybe also check whether the target element is the node’s contentDOM, and only allow the appropriate events to pass (i.e. non-mousedown / dragstart events)

ex:

stopEvent() { 
    const { draggable } = this.node.type.spec
    if (/dragstart|dragover|drangend|drop/.test(e.type)) return false
    return !(draggable && /mousedown|drag|drop/.test(e.type))
}

edit: might be better to only allow mousedown event in grippable areas

if (/drag|drop/.test(e.type)) return false
return !(grippable && /mousedown/.test(e.type))