Skip to content

fix: prevent FloatingFocusManager from resetting editor selection (#2525)#2664

Open
YousefED wants to merge 3 commits intomainfrom
fix/ai-selection-reset-2525
Open

fix: prevent FloatingFocusManager from resetting editor selection (#2525)#2664
YousefED wants to merge 3 commits intomainfrom
fix/ai-selection-reset-2525

Conversation

@YousefED
Copy link
Copy Markdown
Collaborator

@YousefED YousefED commented Apr 20, 2026

Summary

Fixes a regression where AI operations apply to the whole page instead of the user's selection, caused by FloatingFocusManager resetting the ProseMirror editor selection when the AI menu opens.

Rationale

After PR #2591 (portaling all floating UI elements to document.body), floating-ui's FloatingFocusManager began inserting a hidden focus-return <span> element inside the ProseMirror contenteditable DOM via insertAdjacentElement('afterend', ...) on the reference element. This triggers PM's MutationObserver, which resets the editor selection — causing the AI menu to see no selection and show whole-page operations instead of selection-specific ones.

Changes

  • packages/react/src/components/Popovers/GenericPopover.tsx: Skip calling refs.setReference(element) when FloatingFocusManager is active (disabled !== false) and the reference element is inside the editor DOM (editor.isWithinEditor(element)). Position is still handled correctly via the separate refs.setPositionReference() call.
  • tests/src/end-to-end/ai/ai-selection.test.ts: New Playwright e2e test that selects text, clicks the AI toolbar button with real mouse coordinates, and verifies PM selection is preserved after the AI menu opens.

Impact

  • Only affects popovers that use FloatingFocusManager with a reference element inside the editor (currently: AI menu, floating comment composer).
  • Popovers with focusManagerProps.disabled: true (formatting toolbar, suggestion menu, side menu, etc.) are unaffected.
  • Positioning behavior unchanged since setPositionReference is always called.

Testing

  • Playwright test confirmed: fails without fix (selection shifts from 679→1018), passes with fix across Chromium, Firefox, and WebKit.
  • Existing AI scroll regression test continues to pass.

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Additional Notes

Root cause: floating-ui/react@0.27.19FloatingFocusManager line 2057-2058 inserts a fallback element when isInsidePortal && domReference. By not setting domReference for in-editor references, we prevent the insertion entirely.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Popover reference handling updated so editor text selection is preserved correctly depending on focus-manager settings and whether the popover target is inside the editor.
  • Tests

    • Added an end-to-end test that verifies text selection remains intact when interacting with the AI toolbar button and opening the AI menu.

)

When FloatingFocusManager is used inside a FloatingPortal, floating-ui
inserts a hidden <span> after the domReference via insertAdjacentElement.
If the reference element is inside the ProseMirror contenteditable, this
triggers PM's MutationObserver and resets the editor selection.

Skip setting domReference when FloatingFocusManager is active and the
reference is within the editor DOM. Positioning still works via the
separate setPositionReference call.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 20, 2026

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

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Apr 20, 2026 5:50pm
blocknote-website Ready Ready Preview Apr 20, 2026 5:50pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 20, 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: 614170e1-c9cb-4142-a093-7c675fb7e77d

📥 Commits

Reviewing files that changed from the base of the PR and between b086c13 and a56afee.

📒 Files selected for processing (1)
  • tests/src/end-to-end/ai/ai-selection.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/src/end-to-end/ai/ai-selection.test.ts

📝 Walkthrough

Walkthrough

Updated GenericPopover to only set the floating reference when a real reference element is present and either FloatingFocusManager is disabled or the element is outside the editor. Added an end-to-end Playwright test verifying ProseMirror selection persists after clicking the AI toolbar button.

Changes

Cohort / File(s) Summary
Popover Reference Management
packages/react/src/components/Popovers/GenericPopover.tsx
Modified useEffect to call refs.setReference(element) only when reference.element exists and either props.focusManagerProps?.disabled is true or !editor.isWithinEditor(element). Added props.focusManagerProps?.disabled and editor to the dependency array.
AI Selection Testing
tests/src/end-to-end/ai/ai-selection.test.ts
Added a Playwright E2E test that focuses the editor, captures ProseMirror selection (from/to) via page.evaluate, clicks the AI toolbar button using mouse coordinates, waits for the AI menu input, and asserts the editor selection is unchanged.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Page
  participant Editor
  participant ToolbarButton as AI_Button
  participant AI_Menu

  User->>Page: focus editor (keyboard)
  Page->>Editor: set selection (Home, Shift+End)
  Page->>Page: capture selection via page.evaluate
  User->>AI_Button: click (mouse at bounding-box center)
  AI_Button->>Page: open AI menu
  Page->>AI_Menu: wait for input visible
  Page->>Editor: read selection via page.evaluate
  Editor-->>Page: return selection (from/to)
  Page-->>User: assert selection unchanged
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through code with careful cheer,
where popovers learn when to appear,
selections held with a gentle click,
tests that prove the trick so slick,
a tiny hop, a tidy cheer.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: preventing FloatingFocusManager from resetting editor selection when the AI menu opens, which is the core objective of this PR.
Description check ✅ Passed The description follows the template structure and provides comprehensive information: clear summary, detailed rationale, specific changes, impact analysis, testing results, and a populated checklist addressing the regression and its solution.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ai-selection-reset-2525

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/src/end-to-end/ai/ai-selection.test.ts (1)

42-45: Align the regression note with this PR’s fix.

The assertion is right, but the comment points to a preventDefault toolbar fix instead of the FloatingFocusManager/refs.setReference behavior this PR changes.

📝 Proposed comment update
-    // Without the preventDefault fix on toolbar buttons, the browser
-    // moves focus to the portaled button on mousedown, which blurs the
-    // editor and clears the selection.
+    // Without skipping refs.setReference for editor references while
+    // FloatingFocusManager is active, floating-ui inserts focus-return
+    // elements near the ProseMirror reference and PM resets the selection.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/src/end-to-end/ai/ai-selection.test.ts` around lines 42 - 45, Update
the regression comment to reference the actual fix introduced in this PR:
replace mention of the toolbar buttons' preventDefault behavior with the
FloatingFocusManager/refs.setReference change; specifically, state that without
the FloatingFocusManager and proper refs.setReference handling the browser moves
focus on mousedown to the portaled button which blurs the editor and clears the
selection, and keep the assertion text unchanged in ai-selection.test.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react/src/components/Popovers/GenericPopover.tsx`:
- Around line 158-171: The conditional that decides whether to call
refs.setReference currently treats props.focusManagerProps?.disabled ===
undefined as "disabled" (because it checks !== false) and thus still sets the
DOM reference; change the guard to only set the reference when the focus manager
is explicitly disabled (props.focusManagerProps?.disabled === true) or when the
reference element is outside the editor (use (props.focusManagerProps?.disabled
=== true || !editor.isWithinEditor(element)) in the refs.setReference branch),
and apply the same explicit-true logic in the other related block that mirrors
this behavior around FloatingFocusManager rendering.

In `@tests/src/end-to-end/ai/ai-selection.test.ts`:
- Line 6: The test's AI_BUTTON_SELECTOR (`AI_BUTTON_SELECTOR =
[data-test="editwithAI"]`) doesn't match the actual rendered AIToolbarButton,
causing failures; either add the missing data-test="editwithAI" attribute to the
AIToolbarButton component (where it's defined) or update `AI_BUTTON_SELECTOR` to
use the selector that the component actually renders (e.g., the component's real
data-test value, role, aria-label, or text content); make the same change for
the other occurrences referenced around lines 32-35 so all selectors in this
file target the rendered element.

---

Nitpick comments:
In `@tests/src/end-to-end/ai/ai-selection.test.ts`:
- Around line 42-45: Update the regression comment to reference the actual fix
introduced in this PR: replace mention of the toolbar buttons' preventDefault
behavior with the FloatingFocusManager/refs.setReference change; specifically,
state that without the FloatingFocusManager and proper refs.setReference
handling the browser moves focus on mousedown to the portaled button which blurs
the editor and clears the selection, and keep the assertion text unchanged in
ai-selection.test.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e528995-4170-4d77-886e-8d3451ddbe6b

📥 Commits

Reviewing files that changed from the base of the PR and between 46355c0 and 611c900.

📒 Files selected for processing (2)
  • packages/react/src/components/Popovers/GenericPopover.tsx
  • tests/src/end-to-end/ai/ai-selection.test.ts

Comment thread packages/react/src/components/Popovers/GenericPopover.tsx
import { AI_URL } from "../../utils/const.js";
import { focusOnEditor } from "../../utils/editor.js";

const AI_BUTTON_SELECTOR = `[data-test="editwithAI"]`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the AI button selector match the rendered component.

AIToolbarButton does not expose data-test="editwithAI" in the provided component snippet, so this test can fail before reaching the regression assertion. Add the attribute to the button component or switch the test to a selector that exists.

Also applies to: 32-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/src/end-to-end/ai/ai-selection.test.ts` at line 6, The test's
AI_BUTTON_SELECTOR (`AI_BUTTON_SELECTOR = [data-test="editwithAI"]`) doesn't
match the actual rendered AIToolbarButton, causing failures; either add the
missing data-test="editwithAI" attribute to the AIToolbarButton component (where
it's defined) or update `AI_BUTTON_SELECTOR` to use the selector that the
component actually renders (e.g., the component's real data-test value, role,
aria-label, or text content); make the same change for the other occurrences
referenced around lines 32-35 so all selectors in this file target the rendered
element.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 20, 2026

Open in StackBlitz

@blocknote/ariakit

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

@blocknote/code-block

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

@blocknote/core

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

@blocknote/mantine

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

@blocknote/react

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

@blocknote/server-util

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

@blocknote/shadcn

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

@blocknote/xl-ai

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

@blocknote/xl-docx-exporter

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

@blocknote/xl-email-exporter

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

@blocknote/xl-multi-column

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

@blocknote/xl-odt-exporter

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

@blocknote/xl-pdf-exporter

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

commit: a56afee

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