Skip to content

Bug: LexicalTypeaheadMenuPlugin closes typeahead menu during IME composition #7985

@kykim00

Description

@kykim00

Lexical version: 0.38.2

Steps To Reproduce

  1. Open any plugin that uses LexicalTypeaheadMenuPlugin. (e.g. CopmponentPickerPlugin, EmojiPickerPlugin ...)
  2. Modify the plugin to always render menu (remove options.length check)
  3. Add "No results found" UI for empty results
  4. Type Korean characters ㄱㄱㄱㄱㄱ rapidly
  5. Observe "No results found" UI flickering on/off
  6. Check console logs showing onCloseonOpen cycles with editor.isComposing() === true

Link to code example:

 <LexicalTypeaheadMenuPlugin<ComponentPickerOption>
        onQueryChange={setQueryString}
        onSelectOption={onSelectOption}
        triggerFn={checkForTriggerMatch}
        options={options}
        onOpen={() => {
          console.log('Menu opened');
        }}
        onClose={() => {
          console.log('Menu closed, isComposing:', editor.isComposing());
        }}
        menuRenderFn={(
          anchorElementRef,
          {selectedIndex, selectOptionAndCleanUp, setHighlightedIndex},
        ) =>
          anchorElementRef.current
            ? ReactDOM.createPortal(
                <div className="typeahead-popover component-picker-menu">
                  <ul>
                    {options.map((option, i: number) => (
                      <ComponentPickerMenuItem
                        index={i}
                        isSelected={selectedIndex === i}
                        onClick={() => {
                          setHighlightedIndex(i);
                          selectOptionAndCleanUp(option);
                        }}
                        onMouseEnter={() => {
                          setHighlightedIndex(i);
                        }}
                        key={option.key}
                        option={option}
                      />
                    ))}

                    {options.length === 0 && (
                      <li>
                        <span className="text">No results found</span>
                      </li>
                    )}
                  </ul>
                </div>,
                anchorElementRef.current,
              )
            : null
        }
      />
Image

The current behavior

During IME composition for Korean, Japanese, or Chinese input, the typeahead menu closes intermittently while typing, even though the trigger character (e.g., @, /)

Root Cause:

During IME composition, the browser creates a range selection (non-collapsed) to display the composing text. Lexical synchronizes this DOM selection state, causing selection.isCollapsed() to return false. The plugin's updateListener doesn't check for composition state before evaluating selection, leading to premature menu closure.

The expected behavior

During IME composition, the typeahead menu should remain stable:

No unexpected onClose → onOpen cycles
"No results found" UI should not flicker
Menu state should only update after composition ends (compositionend event)

Impact of fix

Severity: Medium - Affects user experience but doesn't cause data loss

Frequency: Occurs consistently when using IME input (Korean, Japanese, Chinese, etc.) with any typeahead plugin

Affected users:

  • All users typing in CJK (Chinese, Japanese, Korean) languages
  • Approximately 1.5+ billion people use these languages globally
  • Affects all plugins using LexicalTypeaheadMenuPlugin

Proposed Solution:

// LexicalTypeaheadMenuPlugin.tsx
const updateListener = () => {  
  editor.getEditorState().read(() => {  
    if (!editor.isEditable()) {  
      closeTypeahead();  
      return;  
    }  
  
    // Skip checks during IME composition  
    if (editor.isComposing()) {  
      return;  
    }  
  
    // ... existing logic  
  });  
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    compositionRelating to IME inputspopoverIssues related to popover/menu code in the playground

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions