Skip to content

Bug: IME composition: Format/style inherited from previous selection when clicking another paragraph during composition #8098

@yuuki999

Description

@yuuki999

Title

IME composition: Format/style inherited from previous selection when clicking another paragraph during composition

Labels

  • bug
  • IME
  • CJK

Lexical version

0.17.0

Description

When using IME (Input Method Editor) for CJK languages (Japanese, Chinese, Korean), if a user:

  1. Types in a bold text area
  2. Clicks on another paragraph without pressing Enter to confirm the IME composition
  3. Starts typing in the new paragraph

The new text inherits the bold format from the previous paragraph, even though it should use the new paragraph's format.

Steps to reproduce

  1. Create a Lexical editor with two paragraphs:
    • Paragraph A: Contains bold text
    • Paragraph B: Contains normal (non-bold) text
  2. Click on the bold text in Paragraph A
  3. Start typing using IME (e.g., type "あああ" in Japanese IME)
  4. Without pressing Enter to confirm, click on Paragraph B
  5. Start typing in Paragraph B

Expected behavior

The new text in Paragraph B should NOT be bold (should match Paragraph B's existing format).

Actual behavior

The new text in Paragraph B IS bold (inherits from Paragraph A's format).

Root cause analysis

After investigating Lexical's source code, I found two related issues:

1. Selection protection mechanism ($normalizeSelectionPointsForBoundaries)

// Around line 7230 in Lexical.dev.js
if (editor.isComposing() && editor._compositionKey !== anchor.key && $isRangeSelection(lastSelection)) {
  // Restores previous selection, preventing it from moving during composition
  $setPointValues(anchor, lastAnchor.key, lastAnchor.offset, lastAnchor.type);
  $setPointValues(focus, lastFocus.key, lastFocus.offset, lastFocus.type);
}

This protection mechanism prevents selection from moving to a different node during IME composition. While this is intentional to prevent composition issues, it also prevents detecting when the user clicks on a different paragraph.

2. Unconditional format/style inheritance ($internalCreateRangeSelection)

// Around line 7340 in Lexical.dev.js
return new RangeSelection(
  resolvedAnchorPoint,
  resolvedFocusPoint,
  !$isRangeSelection(lastSelection) ? 0 : lastSelection.format,  // Always inherits
  !$isRangeSelection(lastSelection) ? '' : lastSelection.style   // Always inherits
);

When a new RangeSelection is created, it unconditionally copies format and style from lastSelection. This means that after compositionend, when the selection can finally move to the new paragraph, the format/style from the old selection is already inherited.

Attempted workarounds (all failed)

  1. SELECTION_CHANGE_COMMAND: Fires before selection actually moves due to protection
  2. mousedown + setTimeout: Executes before compositionend completes
  3. compositionstart format sync: editor.update() interferes with composition
  4. compositionend correction: Text already inserted with wrong format
  5. $setCompositionKey(null): No effect on format inheritance

Proposed solution

Consider one of the following:

  1. Add an option to disable format inheritance when creating RangeSelection
  2. Don't inherit format/style when paragraph changes after compositionend
  3. Provide a hook to customize format/style during selection creation
  4. Reset format/style to anchor node's format when selection moves to a different paragraph after composition

Environment

  • Browser: Chrome/Safari/Firefox (all affected)
  • OS: macOS/Windows (all affected)
  • IME: Any CJK IME (Japanese, Chinese, Korean)

Additional context

This issue significantly affects usability for CJK language users who frequently use IME. The workaround of "press Enter before clicking another paragraph" is not intuitive and breaks the natural editing flow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    compositionRelating to IME inputs

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions