Skip to content

[lexical] Bug Fix: Don't scroll or steal focus when an IME composition is force-committed - #8835

Draft
2wheeh wants to merge 2 commits into
facebook:mainfrom
2wheeh:fix-ime-forced-commit-scroll
Draft

[lexical] Bug Fix: Don't scroll or steal focus when an IME composition is force-committed#8835
2wheeh wants to merge 2 commits into
facebook:mainfrom
2wheeh:fix-ime-forced-commit-scroll

Conversation

@2wheeh

@2wheeh 2wheeh commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Description

Describe the changes in this pull request

Closes #8834

Test plan

Before

Insert relevant screenshots/recordings/automated-tests

After

Insert relevant screenshots/recordings/automated-tests

…n is force-committed

Clicking outside the editor mid-composition makes the browser force-commit and
fire compositionend. Because the commit happens before focus actually moves, the
editor is still the active element, so every activeElement guard in
$updateDOMSelection passes and reconciliation scrolls the caret back into view —
undoing the scrolling the user just did to reach whatever they clicked. A plain
contenteditable keeps its scroll position under the same gesture.

Distinguish keyboard-driven commits from browser-forced ones: a commit typed by
the user (Space/Enter, next-syllable, arrows) is always preceded by a keydown on
the editor, and one caused by clicking away is not. When the keydown is missing,
tag the update with SKIP_SELECTION_FOCUS_TAG and SKIP_SCROLL_INTO_VIEW_TAG,
reusing the lastKeyDownTimeStamp heuristic $handleCompositionStart already uses.
The committed text is still synced and the selection still reconciled; only the
focus grab and the scroll-into-view are suppressed. Android Chrome zeroes
lastKeyDownTimeStamp while composing, so the zero check leaves it unchanged.

Both routes into compositionend are covered: the immediate one and Firefox's,
which defers the commit to the input event that follows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 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 Jul 15, 2026 1:51am
lexical-playground Ready Ready Preview Jul 15, 2026 1:51am

Request Review

@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 Jul 14, 2026

@etrepum etrepum left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

maybe the shape of $onCompositionEndImpl should change a bit (e.g. to take more parameters and handle exit a bit differently)? it looks like all of the new tagging always goes after that function so perhaps it would be easier if that function was responsible for setting the tags

@2wheeh 2wheeh changed the title [lexical] Bug Fix: Don't scroll or steal focus when an IME compositio… [lexical] Bug Fix: Don't scroll or steal focus when an IME composition is force-committed Jul 14, 2026
…ionEndImpl

Follow-up to the force-committed-composition scroll fix, addressing review
feedback that the tag-setting should belong to $onCompositionEndImpl rather
than trail after each of its callers.

Moving the tagging inside the function — right after $setCompositionKey(null),
before any exit — makes the tag follow from ending the composition instead of
from each caller remembering to add it. That closes two gaps the caller-side
tagging had left open, both intended:

- The token-redirect path in $handleInput only tagged when it actually
  redirected onto a token node. An ordinary multi-character commit (the common
  CJK case) ended untagged. It is now tagged.
- Safari's deferred 'ending-safari' commit, processed on the following keydown,
  was never tagged at all. It is now tagged. Because that path finishes the
  commit later, the compositionend timeStamp is stashed in the new
  InputState.compositionEndTimeStamp so the classification reads when the
  composition actually ended, not when we got around to it.

Also separates RECENT_KEYDOWN_WINDOW from ANDROID_COMPOSITION_LATENCY. They
held the same value but answer different questions — one is how late Android's
keyboard delivers events, the other is whether a keystroke is behind a
composition event at all — so tuning one must not silently move the other. No
value change; the $handleCompositionStart site that was already asking the
keydown-recency question is switched to the new name.

The scroll-suppression comment now records that SKIP_SCROLL_INTO_VIEW_TAG only
suppresses Lexical's own reconciliation scroll (the Chromium case), and does
nothing where the browser natively scrolls the caret on commit (observed on
Firefox, where a plain contentEditable jumps identically).

Adds a Firefox unit test for the multi-character-commit path that the
token-redirect gap left untagged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Committing an IME composition with a mouse click scrolls the viewport back to the composed text

2 participants