Skip to content

fix(clipboard): use ProseMirror selection state for Shadow DOM compatibility#2677

Open
wielinde wants to merge 2 commits intoTypeCellOS:mainfrom
opf:main
Open

fix(clipboard): use ProseMirror selection state for Shadow DOM compatibility#2677
wielinde wants to merge 2 commits intoTypeCellOS:mainfrom
opf:main

Conversation

@wielinde
Copy link
Copy Markdown

@wielinde wielinde commented Apr 25, 2026

Problem

BlockNote embeds ProseMirror inside editors that may be mounted in a Shadow DOM (e.g. OpenProject uses attachShadow({ mode: 'open' }) to isolate the editor from its Angular host). In this setup, window.getSelection() returns null or a collapsed selection even when text is visually selected — this affects Firefox (all versions), Safari ≤16.3, and Chromium edge cases.

checkIfSelectionInNonEditableBlock used window.getSelection() as its primary empty-selection guard. Because getSelection() misfires in Shadow DOM, the guard always returned true, causing both the copy and cut handlers to bail out early without writing to the clipboard. The browser's default copy then ran, which uses ProseMirror's DOMSerializer without BlockNote's semantic wrappers — losing list formatting, headings, and bold/italic on paste into external apps.

Fix

Use view.state.selection.empty as the primary guard. ProseMirror's internal selection state is updated via its own reconciliation loop and is always accurate regardless of DOM mode.

The non-editable-block DOM walk (which does need window.getSelection()) is kept as a secondary guard, but only runs when getSelection() actually returns a non-collapsed selection — so it's a no-op in Shadow DOM environments where getSelection() is unreliable.

Test

Verified in OpenProject (Shadow DOM setup) with a bullet list: the clipboard now contains all three expected types — blocknote/html, text/html (with <ul>/<li>), and text/plain (markdown * item).

Summary by CodeRabbit

  • Bug Fixes
    • Improved clipboard handling for copy and cut operations in non-editable sections and shadow DOM scenarios, ensuring more reliable selection detection across different browser conditions.

…ibility

OpenProject embeds BlockNote inside a Shadow DOM (attachShadow({ mode: 'open' }))
to isolate it from the host Angular application. In this setup,
window.getSelection() returns null or a collapsed selection even when text is
selected (Firefox all versions, Safari ≤16.3, Chromium edge cases), causing
checkIfSelectionInNonEditableBlock to always return true and skip the
clipboard write entirely. The browser's default copy then fires, which uses
ProseMirror's DOMSerializer without semantic wrappers — so list formatting,
headings, and bold/italic are lost on paste into external apps.

Fix: use view.state.selection.empty as the primary empty-selection guard.
ProseMirror's internal state is always accurate regardless of DOM mode. The
DOM-level non-editable-island check is kept as a secondary guard, but only
when window.getSelection() actually returns a non-collapsed selection.

Fixes copy/cut for editors mounted inside attachShadow({ mode: 'open' }).
fix(clipboard): use ProseMirror selection state for Shadow DOM compatibility
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 25, 2026

@wielinde is attempting to deploy a commit to the TypeCell Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bfcebbd6-24ce-4efd-a443-487861b8fda4

📥 Commits

Reviewing files that changed from the base of the PR and between 210b499 and a9992bc.

📒 Files selected for processing (1)
  • packages/core/src/api/clipboard/toClipboard/copyExtension.ts

📝 Walkthrough

Walkthrough

The change refactors the checkIfSelectionInNonEditableBlock function to accept the ProseMirror EditorView and use view.state.selection.empty for determining empty selections instead of window.getSelection(), which improves reliability in various DOM and shadow DOM scenarios. The copy and cut event handlers are updated accordingly.

Changes

Cohort / File(s) Summary
Clipboard Selection Detection
packages/core/src/api/clipboard/toClipboard/copyExtension.ts
Refactored checkIfSelectionInNonEditableBlock to accept ProseMirror EditorView as parameter and use view.state.selection.empty instead of relying on window.getSelection(). Non-editable block detection still traverses DOM nodes via window.getSelection().focusNode, but only after confirming selection is non-empty. Updated copy and cut event handlers to pass the new function signature.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit hops through EditorView's state,
No more does DOM Selection dictate!
In shadow lands where selection hides,
ProseMirror's truth becomes our guide—
Clipboard magic flows more true,
Through editable plains we hop on through! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: using ProseMirror selection state instead of window.getSelection() to fix Shadow DOM compatibility issues with clipboard operations.
Description check ✅ Passed The PR description provides a clear problem statement, detailed explanation of the fix, and verification steps, but is missing some template sections like formal testing details and checklist items.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant