nodeViews with contentDOM stops the cursor movement for a node with "text*" content

I made an example at https://glitch.com/edit/#!/sprinkle-hazel-meadowlark?path=index.js%3A18%3A17

You can move the cursor downwards with keyboard (myblock -> mytag -> other stuff). And when you try to move the cursor upwards instead, you will find it stops at mytag.

And if you replace the content of mytag from text* to paragraph, the issue disappears.

But I want to keep the content as text*, while no problem for the cursor movement. How could I make it?

Sorry, the glitch link no longer appears to work, and there’s not enough context in the message to reconstruct what you may be doing. Could you fix/update/recreate the glitch page?

OK, it is now here https://glitch.com/edit/#!/temporal-quixotic-dandelion?path=index.js%3A40%3A0

1 Like

Really sorry, but both those links cause Glitch to fail with “Well, you found a glitch” when I try to open them (both when logged in and when logged out). Other glitches work for some unfathomable reason. Could you paste your code on https://gist.github.com/ or something so that I can get at it?

I came across the same issue. This is my NodeSpec and NodeView

// NodeSpec
const tag = {
  content: "text*",
  parseDOM: [
    {
      tag: 'div.tag',
    },
  ],
  toDOM: () => ['div', {class: 'tag'}, 0],
}
// NodeView for `tag`
class View {
  constructor(node, view, getPos) {
    const dom = document.createElement('div')
    const label = document.createElement('label')
    label.innerText = 'tag'
    this.contentDOM = document.createElement('div')
    dom.appendChild(label)
    dom.appendChild(this.contentDOM)
    this.dom = dom
  }
}

And I have several tag nodes in my document. The cursor cannot be moved upwards.

p.s. gist.github.com or glitch.com is not accessible from my country :sob:

https://codesandbox.io/ is a great alternative to glitch, does that work in your country ?

And if you replace the content of mytag from text* to paragraph, the issue disappears.

I had the exact same issue in this example for my library and changing the text* to paragraph fixed it.

I narrowed the problem to the ignoreMutation handler of the nodeView. From what I understand, this handler tells Prosemirror to handle the mutation event which itself creates an empty text node in response to the mutation event.

Here is my ignoreMutation handler, pay attention to if (mutation.target === this.contentDOM) { clause:

ignoreMutation(mutation) {
    // For PM an atom node is a black box, what happens inside it are of no concern to PM
    // and should be ignored.
    if (this._node.type.isAtom) {
      return true;
    }

    // donot ignore a selection type mutation
    if (mutation.type === 'selection') {
      return false;
    }

    // if a child of this.dom (the one handled by PM)
    // has any mutation, do not ignore it
    if (this.dom.contains(mutation.target)) {
      return false;
    }

    // <!---THIS CONDITION FIXES THE PROBLEM -->
    // if the this.dom itself was the target
    // do not ignore it. This is important for schema where
    // content: 'inline*' and you end up deleting all the content with backspace
    // PM needs to step in and create an empty node for us.
    if (mutation.target === this.contentDOM) {
      return false;
    }

    return true;
  }

you can see full code here

1 Like

Yes, changing the text* to paragraph fixed the issue, with the resulting content wrapped in a paragraph node. Though I can post-process it, I’d like keep it text*.
And then I tried your ignoreMutation, it did not help.

p.s. I made a demo at https://codesandbox.io/s/prosemirror-example-forked-8uixe?file=/src/index.js

Setting contentEditable=false on the label seems to solve the cursor problem. Generally, if you’re going to add non-content DOM structure in a node view, you have to make sure you set it uneditable, or the browser will move the cursor into it (and then ProseMirror will move it out again because it doesn’t consider that position a valid selection endpoint).

Nice to know it. Thank you.

BTW, I can not select all the content by Ctrl+A any more with those labels. Is this an expected behavior of Prosemirror?

As in, the labels don’t show up as selected, or the actual selection doesn’t span the entire document? The latter would be a bug—are you using the selectAll command or relying on some native behavior from a menu? (If so, which platform/menu?)

It does not span the entire document. In fact I tried to copy the whole document after hitting Ctrl-A, nothing copied. I did not use the selectAll command. I am on MacOS with the latest chrome.

The base keymap binds Cmd-A to selectAll. Are you including that binding?

Yes, I can do it after removing those labels.

i modify the demo, let the tag to be inline .then the cursor cannot move left.

demo: ProseMirror Example (forked) - CodeSandbox

and i make another demo, then i find also has the cursor bug, when cursor move right at the end of prosemirror , then next move will move one more position.

demo: ProseMirror Example (forked) - CodeSandbox

Add another mark demo, when cursor move left at the start of https:// ,then next move right ,it stops.

demo: ProseMirror Example (forked) - CodeSandbox

Add an decoration demo,also has the cursor problem

demo: ProseMirror Decoration Example - CodeSandbox

You can probably work around this by making the parent container display: flex and making the label and the content block-level elements.

perfect!, i make the parent container display inline-flex and it works. but i am confused why it works. Doth this handle by prosemirror ,or is the browser defaut behavior ?

This ‘fixes’ the browser’s native behavior, which ProseMirror relies on (it only overrides arrow cursor motion in specific circumstances, since the browser often does a better job than the library can).

I tried your second demo, and found something interesting.

With the original demo, when the cursor is located after https://, hitting backspace will just delete https://.

And then I made the parent container display inline-flex. Now when the cursor is located after https://, hitting backspace will delete https://, and create a new node at the same time, resulting in something like: prosemirrorhttps://.com.

How comes this difference with the display property setting?

The original CodeSandbox is lost , just add a new one below

nodeViews demo: ProseMirror Node View Example (forked) - CodeSandbox

schema demo: ProseMirror Schema Example (forked) - CodeSandbox

decoration demo: ProseMirror Decoration Example (forked) - CodeSandbox

mark demo: ProseMirror Mark Example (forked) - CodeSandbox