Skip to content

Commit

Permalink
Components: Add HOF to ignore IME keydowns (WordPress#59081)
Browse files Browse the repository at this point in the history
* Components: Add HOF to ignore IME keydowns

* Replace usages

* Update changelog

* Clean up

Co-authored-by: mirka <[email protected]>
Co-authored-by: t-hamano <[email protected]>
Co-authored-by: tyxla <[email protected]>
  • Loading branch information
4 people authored Feb 19, 2024
1 parent 467aade commit 280c5d7
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 73 deletions.
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- Add lint rules for theme color CSS var usage ([#59022](https://github.com/WordPress/gutenberg/pull/59022)).
- `FormTokenField`: Use `Element.scrollIntoView()` instead of `dom-scroll-into-view` ([#59085](https://github.com/WordPress/gutenberg/pull/59085)).
- Removing `dom-scroll-into-view` as a dependency of the components package ([#59085](https://github.com/WordPress/gutenberg/pull/59085)).
- Add higher-order function to ignore IME keydowns ([#59081](https://github.com/WordPress/gutenberg/pull/59081)).

## 26.0.1 (2024-02-13)

Expand Down
13 changes: 3 additions & 10 deletions packages/components/src/autocomplete/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { isAppleOS } from '@wordpress/keycodes';
*/
import { getAutoCompleterUI } from './autocompleter-ui';
import { escapeRegExp } from '../utils/strings';
import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events';
import type {
AutocompleteProps,
AutocompleterUIProps,
Expand Down Expand Up @@ -183,15 +184,7 @@ export function useAutocomplete( {
return;
}

if (
event.defaultPrevented ||
// Ignore keydowns from IMEs
event.isComposing ||
// Workaround for Mac Safari where the final Enter/Backspace of an IME composition
// is `isComposing=false`, even though it's technically still part of the composition.
// These can only be detected by keyCode.
event.keyCode === 229
) {
if ( event.defaultPrevented ) {
return;
}

Expand Down Expand Up @@ -390,7 +383,7 @@ export function useAutocomplete( {
return {
listBoxId,
activeId,
onKeyDown: handleKeyDown,
onKeyDown: withIgnoreIMEEvents( handleKeyDown ),
popover: hasSelection && AutocompleterUI && (
<AutocompleterUI
className={ className }
Expand Down
74 changes: 33 additions & 41 deletions packages/components/src/combobox-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { normalizeTextString } from '../utils/strings';
import type { ComboboxControlOption, ComboboxControlProps } from './types';
import type { TokenInputProps } from '../form-token-field/types';
import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props';
import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events';

const noop = () => {};

Expand Down Expand Up @@ -186,51 +187,42 @@ function ComboboxControl( props: ComboboxControlProps ) {
setIsExpanded( true );
};

const onKeyDown: React.KeyboardEventHandler< HTMLDivElement > = (
event
) => {
let preventDefault = false;
const onKeyDown: React.KeyboardEventHandler< HTMLDivElement > =
withIgnoreIMEEvents( ( event ) => {
let preventDefault = false;

if (
event.defaultPrevented ||
// Ignore keydowns from IMEs
event.nativeEvent.isComposing ||
// Workaround for Mac Safari where the final Enter/Backspace of an IME composition
// is `isComposing=false`, even though it's technically still part of the composition.
// These can only be detected by keyCode.
event.keyCode === 229
) {
return;
}
if ( event.defaultPrevented ) {
return;
}

switch ( event.code ) {
case 'Enter':
if ( selectedSuggestion ) {
onSuggestionSelected( selectedSuggestion );
switch ( event.code ) {
case 'Enter':
if ( selectedSuggestion ) {
onSuggestionSelected( selectedSuggestion );
preventDefault = true;
}
break;
case 'ArrowUp':
handleArrowNavigation( -1 );
preventDefault = true;
}
break;
case 'ArrowUp':
handleArrowNavigation( -1 );
preventDefault = true;
break;
case 'ArrowDown':
handleArrowNavigation( 1 );
preventDefault = true;
break;
case 'Escape':
setIsExpanded( false );
setSelectedSuggestion( null );
preventDefault = true;
break;
default:
break;
}
break;
case 'ArrowDown':
handleArrowNavigation( 1 );
preventDefault = true;
break;
case 'Escape':
setIsExpanded( false );
setSelectedSuggestion( null );
preventDefault = true;
break;
default:
break;
}

if ( preventDefault ) {
event.preventDefault();
}
};
if ( preventDefault ) {
event.preventDefault();
}
} );

const onBlur = () => {
setInputHasFocus( false );
Expand Down
13 changes: 3 additions & 10 deletions packages/components/src/form-token-field/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from '../base-control/styles/base-control-styles';
import { Spacer } from '../spacer';
import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props';
import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events';

const identity = ( value: string ) => value;

Expand Down Expand Up @@ -194,15 +195,7 @@ export function FormTokenField( props: FormTokenFieldProps ) {
function onKeyDown( event: KeyboardEvent ) {
let preventDefault = false;

if (
event.defaultPrevented ||
// Ignore keydowns from IMEs
event.nativeEvent.isComposing ||
// Workaround for Mac Safari where the final Enter/Backspace of an IME composition
// is `isComposing=false`, even though it's technically still part of the composition.
// These can only be detected by keyCode.
event.keyCode === 229
) {
if ( event.defaultPrevented ) {
return;
}
switch ( event.key ) {
Expand Down Expand Up @@ -689,7 +682,7 @@ export function FormTokenField( props: FormTokenFieldProps ) {

if ( ! disabled ) {
tokenFieldProps = Object.assign( {}, tokenFieldProps, {
onKeyDown,
onKeyDown: withIgnoreIMEEvents( onKeyDown ),
onKeyPress,
onFocus: onFocusHandler,
} );
Expand Down
14 changes: 2 additions & 12 deletions packages/components/src/modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import * as ariaHelper from './aria-helper';
import Button from '../button';
import StyleProvider from '../style-provider';
import type { ModalProps } from './types';
import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events';

// Used to track and dismiss the prior modal when another opens unless nested.
const ModalContext = createContext<
Expand Down Expand Up @@ -196,17 +197,6 @@ function UnforwardedModal(
}, [ isContentScrollable, childrenContainerRef ] );

function handleEscapeKeyDown( event: KeyboardEvent< HTMLDivElement > ) {
if (
// Ignore keydowns from IMEs
event.nativeEvent.isComposing ||
// Workaround for Mac Safari where the final Enter/Backspace of an IME composition
// is `isComposing=false`, even though it's technically still part of the composition.
// These can only be detected by keyCode.
event.keyCode === 229
) {
return;
}

if (
shouldCloseOnEsc &&
( event.code === 'Escape' || event.key === 'Escape' ) &&
Expand Down Expand Up @@ -265,7 +255,7 @@ function UnforwardedModal(
'components-modal__screen-overlay',
overlayClassName
) }
onKeyDown={ handleEscapeKeyDown }
onKeyDown={ withIgnoreIMEEvents( handleEscapeKeyDown ) }
{ ...( shouldCloseOnClickOutside ? overlayPressHandlers : {} ) }
>
<StyleProvider document={ document }>
Expand Down
32 changes: 32 additions & 0 deletions packages/components/src/utils/with-ignore-ime-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* A higher-order function that wraps a keydown event handler to ensure it is not an IME event.
*
* In CJK languages, an IME (Input Method Editor) is used to input complex characters.
* During an IME composition, keydown events (e.g. Enter or Escape) can be fired
* which are intended to control the IME and not the application.
* These events should be ignored by any application logic.
*
* @param keydownHandler The keydown event handler to execute after ensuring it was not an IME event.
*
* @return A wrapped version of the given event handler that ignores IME events.
*/
export function withIgnoreIMEEvents<
E extends React.KeyboardEvent | KeyboardEvent,
>( keydownHandler: ( event: E ) => void ) {
return ( event: E ) => {
const { isComposing } =
'nativeEvent' in event ? event.nativeEvent : event;

if (
isComposing ||
// Workaround for Mac Safari where the final Enter/Backspace of an IME composition
// is `isComposing=false`, even though it's technically still part of the composition.
// These can only be detected by keyCode.
event.keyCode === 229
) {
return;
}

keydownHandler( event );
};
}

0 comments on commit 280c5d7

Please sign in to comment.