fix(context-menu): paste via context menu inserts at wrong position (SD-1302)#2110
Merged
harbournick merged 4 commits intomainfrom Feb 20, 2026
Merged
fix(context-menu): paste via context menu inserts at wrong position (SD-1302)#2110harbournick merged 4 commits intomainfrom
harbournick merged 4 commits intomainfrom
Conversation
…SD-1302) When the context menu is open, its hidden search input holds focus, causing the ProseMirror editor's contenteditable to lose focus. The paste action previously called `view.dom.focus()` (raw DOM focus), which restarts ProseMirror's DOMObserver. The observer reads the stale browser selection (collapsed at the document start) and overwrites the PM state, causing content to be pasted at position 0 instead of the cursor location. Fix: use `view.focus()` (ProseMirror-aware) which properly syncs the PM selection to the DOM before restarting the observer. Additionally, save and restore the selection across the async `readClipboardRaw()` gap as a safety net against further async drift.
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a ProseMirror context-menu paste bug where pasted content was inserted at the start of the document by ensuring focus/selection is preserved correctly during the async clipboard read.
Changes:
- Switches paste action focusing from raw DOM focus (
view.dom.focus()) to ProseMirror-aware focus (view.focus()). - Saves the current ProseMirror selection before reading from the clipboard and attempts to restore it after the async clipboard read gap.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Verify the paste action saves the cursor position before focusing, restores it after the async clipboard read, and clamps positions when the document shrinks during the async gap.
Contributor
|
🎉 This PR is included in superdoc v1.15.1-next.1 The release is available on GitHub release |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Bug
Pasting via the right-click context menu inserts content at the beginning of the document instead of at the cursor position.
Steps to reproduce:
Copy, Cut, and Ctrl+V all work correctly — only context-menu Paste is broken.
Root Cause
The paste action in
menuItems.jscalledview.dom.focus()(raw DOM focus) before reading the clipboard. This is subtly different fromview.focus()(ProseMirror-aware focus), and the difference is the root cause:What
view.dom.focus()does (broken)What
view.focus()does (correct)The key insight is that when the context menu is open, its hidden search input holds browser focus. The browser's native selection drifts to position 0. Raw
dom.focus()lets the DOMObserver read this stale position before ProseMirror can sync its own selection back to the DOM.Copy and Cut don't have this problem because they already use
editor.focus()→view.focus()(PM-aware) and are synchronous (no async gap).The Fix
Two changes, both in the paste action inside
menuItems.js:1. Use
view.focus()instead ofview.dom.focus()This prevents the DOMObserver from reading a stale selection during focus.
2. Save/restore selection across the async clipboard read
Even with
view.focus(), there's an async gap duringawait readClipboardRaw()where the DOMObserver could still corrupt the selection (e.g., if the browser fires another focus/blur cycle). As a safety net:The
Math.minclamping handles edge cases where the document might shrink during the async gap (e.g., a collaboration update arrives mid-paste). The defensive guards (doc.contentcheck,typeof create === 'function') ensure graceful degradation in environments with incomplete mock/state objects.Why This Approach
editor.focus()/view.focus(), this aligns Paste with the same patternview.focus()fixes the primary issue, save/restore handles async edge casesRangeErrorif the document changes during the async clipboard readTests
7 new unit tests covering:
view.focus()is called instead ofview.dom.focus()doc.contentorSelectionType.createis unavailablehandleClipboardPasterunsAll 666 test files / 6235 tests pass.
Manual Test Plan