Drag a node with custom data out of text editor


My goal is to be able to drag some node out of the text editor. For instance, it is possible to select some text, and to drop it in the browser url bar. I want to do something similar, but with more complex data than text/html content.

I’ve defined a custom node type in my schema. The definition is as-is (including comments about things I partially understand):

someCustomNode: {
        // TODO: this prevents editing text within the node?
        atom: true,
        // TODO: not sure if it means draggable within the editor or out of it or both
        draggable: true,
        // TODO: not sure about the difference between group:"inline" and inline: true
        group: "inline",
        inline: true,
        // TODO: not sure of the impact?
        selectable: true,
        attrs: { type: "FOO", },
        toDOM(node) {
               /* some logic to render a web component */
        parseDOM: [/* some logic to retrieve the node type based on the type*/]

I am using a custom element (so a web component) for the display. It defines it’s own “dragstart” event that will fill the drag event with relevant data:

        @dragstart=${(e: DragEvent) => {
                      ev.dataTransfer.effectAllowed = "copy"
                      ev.dataTransfer.setData("SOME_CUSTOM_NODE", JSON.stringify({ someData: "foo" })

Notice the “setData” call. This adds some custom data in the drag event, that you can later test during a drop to detect the kind of data.

However, when the element is dropped out of the text-editor, the type is “text/html” while it should be “SOME_CUSTOM_NODE”. Therefore I guess the drag event is also caught somewhere by prose-mirror, and it overrides my data. This is probably to be able to move nodes around within the text editor, but that prevents dropping out of it.

Any lead on how to prevent that? Calling preventDefault() or stopPropagation seems to totally prevent dragging. I am not sure where to start, the only plugin I use is dropCursor.

You’ll have to create a plugin that handles the drag events before the view does. Check out:

Also, you might have to write a bit more code at either end of the interaction if you plan on dropping content in both a ProseMirror editor and some other external app, but it’s not too bad.

1 Like

Thanks a lot for the directions. I am also encountering a strange behaviour, the drop of atomic elements is lagging one step behind. Meaning if I drag a button 1 and drop, nothing happens. If I drag another button 2 and drop, button 1 is dropped, etc. This will help me dig out this issue.

After more digging, this works pretty well, however I still have some hard time figuring how to get the drop position where I should drop the element. I’ve been using state.tr.selection.anchor but it doesn’t seem to be what I need. I am digging “prosemirror-view” and “prosemirror-dropcursor” to get insights on how they compute the right cursor position for dropping, but it’s complicated.

Edit: it seems that view.posAtCoords({left: e.clientX, top: e.clientY}) gets me the right position based on the cursor X/Y coordinates. Now I need to remove the dragged element, otherwise it copies it instead of dropping.

Edit2: to remove the initial node, the default handler is relying on a “slice”. This is a tad difficult to manage, you have to set the proper selection, get a slice out of it… Instead I’ve opted for a call to state.tr.delete on the initial position of the element and its position +1 (given it’s an atomic block). This part is the hardest to figure imo.

Edit 3: the remove logic is not perfect yet, when getting the position from the mouse it sometimes get the character just after my atomic node instead of the node itself, probably depending on the mouse position. I will try to find something more reliable.

I’m in a similar boat. Looking over the default dragstart handler, I’m not sure why does it clear all dataTransfer data? Why not just text/html and text/plain? @marijn