ProseMirror / prosemirror

The ProseMirror WYSIWYM editor
http://prosemirror.net/
MIT License
7.61k stars 336 forks source link

Caret stuck in certain views and browsers #1345

Closed schostac closed 1 year ago

schostac commented 1 year ago

Issue description

I'm using ProseMirror and define the paragraph node that renders DOM like this:

<div style="display: flex">
    <div></div>
    <p>Some text</p>
</div>

The toDOM implementation can be found here.

Then I create 3 paragraphs with some text and click, for example, on the beginning of the second paragraph and then press arrow keys left and/or up to go to the first paragraph.

Expected behaviour

The caret moved to the first paragraph.

Actual behaviour

The caret remains in the second paragraph.

Possible root cause

I'm not sure what the root cause is. But pay attention to style="display: flex" and <div></div>; when I remove either of those, the issue is gone. Also note that the issue happens only on some browsers as mentioned below. No errors in console.

Browsers

The issue is present on Chrome 108.0.5359.125 and Microsoft Edge 108.0.1462.54, Windows.

The issues is missing on Firefox 101.0.1, Windows.

Issue reproduction

I created this example repository https://github.com/schostac/prosemirror-caret-issues. In particular, the editor code is here https://github.com/schostac/prosemirror-caret-issues/blob/main/src/editor.js.

Steps:

marijnh commented 1 year ago

Arrow cursor motion is handled by the browser. Does the same issue occur when you create an editable document with this structure that's not managed by ProseMirror?

schostac commented 1 year ago

No. Using that example repository I mentioned above, I copied rendered HTML from the browser and added to the HTML of the page. In the part managed by the editor issues exists, but in the identical part added manually just as static HTML the issues is missing.

marijnh commented 1 year ago

I see. If you can reproduce this without Vue and Tiptap, I can try to debug it.

schostac commented 1 year ago

I'll try to reproduce it only with ProseMirror on this weekend and get back to you.

schostac commented 1 year ago

I've reproduced this issue only with ProseMirror, updated https://github.com/schostac/prosemirror-caret-issues, the issue description and steps to reproduce mentioned above.

schostac commented 1 year ago

The difference I see is that the second condition here in prosemirror-view is false (new and current selections are the same) when the issue is present. But when all is working it's true and we enter that block and go to dispatch. In those cases here we are calling different implementations of localPosFromDOM.

marijnh commented 1 year ago

The browser appears to be moving the cursor into that extra <div>, which isn't a valid cursor position from the perspective of ProseMirror, so that normalizes it back into the content text, causing it to get stuck.

Looks like you can sidestep this particular Chrome issue by making sure the div has content (even if it's just a zero-width space) and is set contenteditable=false. Does that help?

schostac commented 1 year ago

Thanks, that helps indeed.

If I use only contenteditable=false, I can go up without being stuck, and if additionally I use some text I can also go left without being stuck. I don't want to add any visible content in that <div></div> for specific reasons. But I think I can afford putting something like &nbsp; in <div></div>, that works too.

Would you like me to close this issue or you want to have some solution in ProseMirror for this corner case?

marijnh commented 1 year ago

Unfortunately, I don't think ProseMirror can solve this in general (the flexibility of HTML and CSS make the space of potential native cursor misbehavior near infinite), so yeah, let's close.