Dragging a block node and leaving it in the same place introduces a replacestep

When dragging a block node, I’d like to be able to know for sure that the location has changed from the original position. If it doesn’t, I would like to not initiate a replace step or transaction of the block node, which then introduces a new diffset event. This happens because we first have a ReplaceStep that deletes the original node, and then another ReplaceStep that adds the original node again.

Is there a way to either:

  • have control of the drag event to know for sure that we havent changed locations
  • modify the original logic to not initiate a step/transaction when the destination is the same as the source positions.

The handleDrop proc should let you implement this.

Gotcha, that’s a helpful starting point!

I guess I’m wondering why moved would equal to true, even though the actual start/end positions of the block node hasn’t changed? Is there a way to be smarter about this differentiation?

moved as opposed to “copied”—the dragged content isn’t being duplicated, only put in the drop position.

So my concern is precisely the behavior of “put in the drop position”. In my view, when the drop position is in between the same two nodes (even if the node has technically “moved” by the mouse), there shouldn’t be any changes w/r/t transaction events. I created a plugin to handle this using your recommended func, but maybe would be nice to see a version that automatically takes that into account? Curious to know your thoughts!

import { Plugin } from 'prosemirror-state';

/** Handle drag events and prevents drops when position is the same */

let originalNode: Node;

export const dragPlugin = new Plugin({
  props: {
    handleDOMEvents: {
      dragstart: (_, event) => {
        if (event.target instanceof Node) {
          originalNode = event.target;
        }
        return false;
      },
    },
    handleDrop: (_, event) => {
      /*
      This drop event.target is the exact final destination of the drag (from where you leave you mouse).
      The original node is where you clicked originally with your mouse.
      
      These values are likely to be different no matter what, even if you barely moved your mouse.
      We can check if the originalNode contains the destination node or in the rare case if it's equal to itself.
      
      Additionally, there is the case where you can drag the destination target to be right next to the originalNode. 
      Either above or below it, the block node has not technically changed position. 
  
      The scenarios below should not contribute to a replacestep when dragging.
  
      Scenario 1: 
        <originalNode>
          <destinationNode>
          </destinationNode>
        </originalNode>
      Scenario 2:
        <destinationNode>
          <originalNode>
          </originalNode>
        </destinationNode>
  
      Scenario 3 (very rare): 
        <originalNode>       <destinationNode>
        </originalNode> ===  </destinationNode>
  
      Scenario 4:
        <originalNode>
        </originalNode>
        <destination node>
        </destination node>
        
      Scenario 5:
        <originalNode>
        </originalNode>
        <someOtherNode>
          <destination node>
          </destination node>
        <someOtherNode>
        
      Scenario 6:
        <destinationNode>
        </destinationNode>
        <originalNode>
        </originalNode>
      Scenario 7:
        <someOtherNode>
          <destination node>
          </destination node>
        <someOtherNode>
        <originalNode>
        </originalNode>
      */

      if (event.target instanceof Node) {
        const isOriginalPosition =
          originalNode.contains(event.target) ||
          event.target.contains(originalNode) ||
          originalNode.isEqualNode(event.target) ||
          originalNode.nextSibling?.isEqualNode(event.target) ||
          originalNode.nextSibling?.contains(event.target) ||
          originalNode.previousSibling?.isEqualNode(event.target) ||
          originalNode.previousSibling?.contains(event.target);
        return !!isOriginalPosition;
      }
      return false;
    },
  },
});

If the user executes an actual drag and drop in the editor, I think firing the transaction is the proper way to do, so you’ll have to change this with a custom handler in your setup.

Is there a way to know the actual position of the element that you’re about to drag and drop into? The handleDrop function only tells me if it moved, but not the expected position of the element about to be dropped.

I’d like to make comparisons between. view.state.selection.from !== newposition

I needed something similar and I cribbed the code from prosemirror-dropcursor:

function dropPos(event: DragEvent, editor: EditorView): number|null {
  let pos = editor.posAtCoords({left: event.clientX, top: event.clientY})

  let node = pos && pos.inside >= 0 && editor.state.doc.nodeAt(pos.inside)
  let disableDropCursor = node && node.type.spec.disableDropCursor
  let disabled = typeof disableDropCursor == "function" ? disableDropCursor(editor, pos, event) : disableDropCursor

  if (pos && !disabled) {
    let target: number | null = pos.pos
    if (editor.dragging && editor.dragging.slice) {
      target = dropPoint(editor.state.doc, target, editor.dragging.slice)
    }
    return target
  }
  return null
}