Skip to content

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

Merged
matthewlipski merged 2 commits intoTypeCellOS:mainfrom
opf:main
Apr 29, 2026
Merged

fix(clipboard): use ProseMirror selection state for Shadow DOM compatibility#2677
matthewlipski merged 2 commits intoTypeCellOS:mainfrom
opf:main

Conversation

@wielinde
Copy link
Copy Markdown
Contributor

@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.

@matthewlipski
Copy link
Copy Markdown
Collaborator

Hi @wielinde, this looks good! Could you just approve this PR? It just adds a regression test - I don't have permissions to edit your branch directly so this should be fine.

@nperez0111
Copy link
Copy Markdown
Contributor

@matthewlipski, let's just merge this PR & you can fix up in a follow up.

For next time, it would be nice to give edit rights to the PR @wielinde

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 29, 2026

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2677

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2677

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2677

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2677

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2677

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2677

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2677

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2677

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2677

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2677

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2677

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2677

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2677

commit: a9992bc

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Apr 29, 2026 3:57pm
blocknote-website Error Error Apr 29, 2026 3:57pm

Request Review

@matthewlipski matthewlipski merged commit 130a916 into TypeCellOS:main Apr 29, 2026
21 of 23 checks passed
matthewlipski added a commit that referenced this pull request Apr 29, 2026
* fix(clipboard): use ProseMirror selection state for Shadow DOM compatibility

* Added e2e test and example for test

---------

Co-authored-by: Wieland Lindenthal <w.lindenthal@forkmerge.com>
@wielinde
Copy link
Copy Markdown
Contributor Author

Thank you 🙏 That is amazing!

...and yes, note taken for next time!

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.

3 participants