fix(clipboard): use ProseMirror selection state to fix copy/cut inside Shadow DOM#2676
fix(clipboard): use ProseMirror selection state to fix copy/cut inside Shadow DOM#2676wielinde wants to merge 1 commit intoTypeCellOS:mainfrom
Conversation
…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' }).
|
@wielinde is attempting to deploy a commit to the TypeCell Team on Vercel. A member of the Team first needs to authorize it. |
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Problem
window.getSelection()returnsnullor a collapsed selection when a BlockNote editor is mounted inside a Shadow DOM (attachShadow({ mode: 'open' })). This causescheckIfSelectionInNonEditableBlockto always returntrue, which silently skipscopyToClipboard. The browser's default copy handler then fires instead, which uses ProseMirror'sDOMSerializer— producing<div class="bn-block-content">wrappers with no<ul>/<ol>— so list bullets, heading levels, bold, and italic are all lost when pasting into external apps (Google Docs, LibreOffice Writer, Gmail, etc.).Affected browsers: Firefox (all versions), Safari ≤ 16.3, Chromium edge cases.
Reproduction: mount a BlockNote editor inside
attachShadow({ mode: 'open' }), select a bullet list, copy, and paste into any external app — formatting is gone.This is not a theoretical concern: OpenProject embeds BlockNote inside a Shadow DOM to isolate it from the host Angular application. This bug breaks clipboard formatting for all their users.
Root cause
window.getSelection()is not Shadow-DOM-aware and cannot see selections that live inside a shadow root.Fix
Use
view.state.selection.emptyas the primary empty-selection guard. ProseMirror's internal state is always accurate regardless of DOM mode. The DOM-level non-editable-island traversal is retained as a secondary check, but only runs whenwindow.getSelection()actually returns a non-collapsed selection (i.e. in non-Shadow-DOM contexts where it still works).Both
copyandcutcall sites are updated to passview.Checklist
view.state.selection.emptyreplaceswindow.getSelection()for the empty-selection guardcopyandcuthandlers updated