Skip to content

[lexical-rich-text] Bug Fix: Don't deselect a selected node when clicking inside it - #8910

Merged
etrepum merged 1 commit into
facebook:mainfrom
alvorithm:fix/rich-text-click-deselect-selected-node
Aug 5, 2026
Merged

[lexical-rich-text] Bug Fix: Don't deselect a selected node when clicking inside it#8910
etrepum merged 1 commit into
facebook:mainfrom
alvorithm:fix/rich-text-click-deselect-selected-node

Conversation

@alvorithm

@alvorithm alvorithm commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Context

Fixes #8907. A NodeSelection set by an inline DecoratorNode's own click handler (useLexicalNodeSelection + onClick, or any programmatic $setSelection in the click event) is immediately cleared by @lexical/rich-text's CLICK_COMMAND handler, which deselects on any click. The mechanism is verified in the issue thread, starting from the request to submit a PR: React renders decorator content in a root inside the editor root, so the decorator's onClick runs before Lexical's click handler; the click then reaches registerRichText's deselect handler, which clears whatever NodeSelection was just committed. The deselect contract only makes sense for clicks away from the selected node; a click on a selected decorator is an interaction with that node (its own click handler may select it, open an editor, ...), not a deselect gesture.

Base: origin/main (a933222c4). Standalone repro used for the analysis: https://gist.github.com/alvorithm/73199ba7f08c65b9cf1586593a72345c

One finding from investigating e2e coverage (see Verification): the playground never hits this bug because every playground decorator consumes CLICK_COMMAND at a priority above rich-text's deselect. Command listeners run in descending priority order (COMMAND_PRIORITY_CRITICAL = 4 first, COMMAND_PRIORITY_EDITOR = 0 last; LexicalUpdates.ts dispatches for (let i = 4; i >= 0; i--)), and a listener that returns true stops propagation. ImageComponent, EquationComponent, PollComponent, ExcalidrawComponent and PageBreakNode all register CLICK_COMMAND at COMMAND_PRIORITY_LOW or higher and return true for clicks on their own node, so rich-text's deselect handler never runs for those clicks. The deselect only fires when no higher-priority consumer handled the click, which is exactly the #8907 pattern: selection set from a React onClick inside decorator content, with no CLICK_COMMAND consumer registered.

Changes

  • 6b7a8bebe [lexical-rich-text] Bug Fix: Don't deselect a selected node when clicking inside it (#8907)
    • In registerRichText's CLICK_COMMAND handler, when the selection is a NodeSelection, skip the deselect if the click target is an HTMLElement contained in any selected node's DOM (editor.getElementByKey(...).contains(target)), and return false so lower-priority handlers (e.g. the playground's decorator click handlers at COMMAND_PRIORITY_LOW) still run. Clicks anywhere else keep the existing deselect behavior.
    • Adds RichTextNodeSelectionClick.test.ts (4 unit tests): click inside a selected inline decorator keeps the selection; same for a block decorator; click outside still deselects; a non-element target still deselects.

Verification

  • npx vitest --project unit --run packages/lexical-rich-text; 13 files, 121 tests passed.
  • Regression test proven: with the source change reverted, RichTextNodeSelectionClick.test.ts fails exactly the two "click inside keeps the selection" cases; with the fix, 4/4 pass.
  • npx tsc --noEmit -p tsconfig.json clean for both changed files.
  • npx eslint clean on both changed files.
  • Not verified: browser e2e suites (playwright) and the standalone vite repro against this branch. The fix logic is identical to the patch validated against the repro on 0.47.0 (patched/pristine dists in the repro), and main's handler had the same shape; unit tests exercise the same code path with a real DOM root element.
  • Playground e2e coverage was investigated and deliberately not added. A spec was prototyped (two images, shift-click multi-select, then shift-click the already-selected image again) and ran green both with and without the fix: the image's own CLICK_COMMAND handler at COMMAND_PRIORITY_LOW consumes the click before rich-text's deselect runs (see Context). Any e2e for this fix would need a new playground decorator component that selects from a React onClick and does not consume CLICK_COMMAND; that component does not exist today. The unit tests cover the same handler contract deterministically.

@meta-cla

meta-cla Bot commented Aug 3, 2026

Copy link
Copy Markdown

Hi @alvorithm!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview Aug 3, 2026 3:23pm
lexical-playground Ready Ready Preview Aug 3, 2026 3:23pm

Request Review

@etrepum

etrepum commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Will review after the CLA is signed

@meta-cla

meta-cla Bot commented Aug 3, 2026

Copy link
Copy Markdown

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 3, 2026
@alvorithm
alvorithm marked this pull request as ready for review August 3, 2026 17:19
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Aug 3, 2026
@etrepum
etrepum added this pull request to the merge queue Aug 5, 2026
Merged via the queue into facebook:main with commit 5dff953 Aug 5, 2026
46 checks passed
@etrepum etrepum mentioned this pull request Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Click-to-select an inline decorator is undone by rich-text's CLICK_COMMAND deselect handler

2 participants