Skip to content

[audit] BottomSheet: standalone Escape lacks an IME composition guard (I19) #5302

Description

@ernestt

Bug: the standalone Bottom Sheet dismisses on Escape without guarding against IME composition, so a CJK (Korean/Japanese/Chinese) user pressing Escape to cancel an in-progress composition dismisses the sheet instead.

When an IME is composing, the browser fires a keydown for Escape (cancel) / Enter (commit) before compositionend. A handler that reads a bare event.key === 'Escape' misreads the composition-cancel as an app command. utils/ime.ts names closing a dialog mid-composition as exactly this bug, and the fix is isImeKeyEvent(event.nativeEvent) early-return.

The base Dialog guards it (Dialog.tsx, in its Escape keydown handler) and so does the sibling BottomSheetSwitcher (its handleKeyDown calls isImeKeyEvent(event.nativeEvent), with a dedicated test). The standalone BottomSheet does not — neither its handleKeyDown nor its native onCancel (handleCancel) guards.

Where: packages/core/src/BottomSheet/BottomSheet.tsxhandleKeyDown and handleCancel in StandaloneBottomSheet.

Reachable path: the documented purpose="form" and mobile-keyboard (height="tall") use cases put a text field in a standalone sheet. A CJK user composing in that field who presses Escape to cancel the candidate loses the whole sheet.

Why lint doesn't catch it: @astryx/no-unguarded-ime-keydown only flags onKeyDown on an editable surface (input/textarea/contentEditable/role=textbox). The handler here is on the <dialog>, which the rule's heuristic treats as non-editable, so this is a manual finding.

Fix: early-return on isImeKeyEvent(event.nativeEvent) at the top of both handleKeyDown and handleCancel, mirroring Dialog, and add a standalone IME-Escape test (the switcher already has one). The switcher path is already correct.


Filed from a full Component Audit Rubric v1.6 grading of BottomSheet (mode O). Rule: §9 I19 (IME composition guard). Component ledger entry: core/BottomSheet — 89.1 / C (this is the one open BLOCK; the C cap lifts and the projection is A once it's fixed). Rubric: https://github.com/facebook/astryx/wiki/Component-Audit-Rubric

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions