← ./articles-ja

ReactのIME入力バグはcomposition中のkeydownをguardする

Reactのsearch box、command palette、autocomplete、rename fieldが英語入力では動くのに日本語入力でおかしい場合、keyboard handlerを見ます。

IMEユーザーは、最終文字を1キーずつ直接入力していません。まず変換中のtextをcomposeし、その後commitします。そのcomposition中にもbrowserはkeyboard eventを発火します。そこで EnterArrowDownEscape を通常shortcutとして扱うと、未確定textをsubmitしたり、dropdownを誤選択したり、popupを早く閉じたりします。

症状

壊れたautocompleteはこうなりがちです。

function SearchBox() {
  const [selectedIndex, setSelectedIndex] = useState(0);

  function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
    if (event.key === "ArrowDown") {
      setSelectedIndex((index) => index + 1);
      event.preventDefault();
    }

    if (event.key === "Enter") {
      submitSelectedItem();
    }
  }

  return <input onKeyDown={handleKeyDown} />;
}

Latin keyboardでは動きます。しかし日本語IME変換中は、commit前に同じkey eventが来ることがあります。

composition中はguardする

isComposing と互換用の keyCode === 229 を見ます。

function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
  if (event.nativeEvent.isComposing || event.keyCode === 229) {
    return;
  }

  if (event.key === "ArrowDown") {
    setSelectedIndex((index) => Math.min(index + 1, items.length - 1));
    event.preventDefault();
  }

  if (event.key === "Enter") {
    submitSelectedItem();
    event.preventDefault();
  }
}

isComposing が読みやすい現代的なsignalです。keyCode === 229 は、IME関連eventの互換fallbackとしてまだ役に立ちます。

final textはcompositionend後に扱う

commit後にsearchやvalidationを走らせるなら、composition endで処理します。

function SearchBox() {
  const [query, setQuery] = useState("");

  return (
    <input
      value={query}
      onChange={(event) => setQuery(event.currentTarget.value)}
      onCompositionEnd={(event) => {
        setQuery(event.currentTarget.value);
        runSearch(event.currentTarget.value);
      }}
      onKeyDown={handleKeyDown}
    />
  );
}

textのsource of truthをkeydownだけにしないことです。input/changeとcomposition eventがtextを管理し、keydownはcompositionが終わった後のnavigationだけを担当します。

text input周辺のshortcutを全部見る

1つのinputだけ直して、別componentが残ることがよくあります。

確認対象です。

  • search box
  • autocomplete dropdown
  • command palette
  • rename field
  • tag editor
  • chat input
  • editable table cell
  • Enter でsubmitするform

selection、commit、submit、popup closeを行うshortcutは、composition中にskipします。

accessibilityを壊さない

IME対応とkeyboard accessibilityは対立しません。ルールはこうです。

composition中:
  text compositionがkeyを所有する

composition後:
  component keyboard shortcutが効く

commit後は、矢印キーでlistを移動し、Enterで確定できる必要があります。

テストケース

実IMEで確認します。

  1. inputに日本語を入力する
  2. candidate windowが開いている間に矢印キーを押す
  3. app側dropdownが早く動かないことを確認する
  4. IME textをcommitする
  5. もう一度矢印キーを押す
  6. app側dropdownが通常通り動くことを確認する
  7. commit後にEnterを押す
  8. preeditではなくfinal textがsubmitされることを確認する

handler logicは自動テストできますが、この問題はbrowser event orderingが本体なので、実IMEの手動確認も必要です。

参考