ブラウザでIME確定時のEnterキー入力をハンドリングしない

IMEまわりのキー入力制御ではDeprecatedなAPIを使わなくちゃいけない、悲しいね。というお話です。

# 背景

入力フォームでEnterキー押下時に、フォームの内容を確定したいことがあります。

素朴に実装すると次のようなソースコードです。

```html
<input id="input">
<span id="message">
<script>
input.addEventListener('keydown', (e) => {
  if(e.key === 'Enter') {
    message.innerText = '確定'
  }
})
</script>
```

[keydown](https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event)イベントを監視し、イベントの[key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key)が`Enter`であれば確定とします。

# 問題
このコードには問題があります。

Firefox（68.0.1）とWindowsのGoogle Chromee（75.0.3770.142）では期待通りに動作します。
MacのGoogle Chrome（75.0.3770.142）とSafari（12.1.2）では、日本語を入力するとき、期待通りに動作しません。
変換確定のEnterを、フォームの確定として扱ってしまいます。

https://codepen.io/ledsun/pen/bXggJN に動作サンプルがあるので試してみてください。

# 原因

前述の2ブラウザでは、IMEの確定時に`keydown`イベントの`key`プロパティが`Enter`になります。

# 対策

必要なのはIME確定時のEnterとそれ以外のEnterを見分ける方法です。

次の作戦はどちらか一方を採用すればよいです。
両方やる必要はありません。

## 作戦1 keyプロパティの代わりにkeyCodeプロパティを使う

keydownイベントの[KeyboardEvent.keyCode](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode)プロパティは、

http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html
> If an Input Method Editor is processing key input and the event is keydown, return 229.

とあるように、IME確定時のEnterは`229`に、それ以外のEnterのときは`13`になります。
keyCodeプロパティの値は、IME確定時のEnterとそれ以外のEnterで変ります。


これを使うと次のようなソースコードになります。

```html
<input id="input">
<span id="message">
<script>
input.addEventListener('keydown', (e) => {
  if(e.keyCode === 13) {
    message.innerText = '確定'
  }
})
</script>
```

気になる点は、keyCodeプロパティがDeprecatedなところです[^1]。　

[^1]: [KeyboardEvent.keyCodeとは何か - WebStudio](https://d-toybox.com/studio/lib/what_is_keyCode.html)によれば、keyCodeプロパティがDeprecatedなのは、ブラウザごとにキーマッピングの扱いが違い、どのキーが押されたのか特定するのが困難なためのようです。もしそうであれば、Enterキーの判定に使う分には問題がないかもしれません。



## 作戦2 keydownイベントの代わりにkeypressイベントを使う

[keypress](https://developer.mozilla.org/en-US/docs/Web/API/Element/keypress_event)イベントは、

> a key that produces a character value is pressed down

とあるように、文字入力がある場合のみ発火します。このため、IME確定時には発火しません。

これを使うと次のようなソースコードになります。

```html
<input id="input">
<span id="message">
<script>
input.addEventListener('keypress', (e) => {
  if(e.key === 'Enter') {
    message.innerText = '確定'
  }
})
</script>
```

やはり、気になる点は、keypressイベントがDeprecatedなところです[^2]。

[^2]: MDNでは、keypressイベントの代わりに`beforeinput`または`keydown`イベントを使うように書かれています。しかし、`beforeinput`はEnterキーの押下では発火しません。`keydown`イベントでは、当初の問題のように`key`プロパティでEnterキー押下がIME確定のためのものかどうか判定できません。

# その他の補足（恨み言）

## keyプロパティ
FirefoxとWindowsのGoogle Chromeは、IMEの確定時に`keydown`イベントの`key`プロパティが`Process`です。MacのGoogle ChromeとSafariも同じように、`key`プロパティが`Process`になってくれればいいのに😩

## isComposingプロパティ
[isComposing](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/isComposing)というプロパティがあります。

> the event is fired after compositionstart and before compositionend

[compositionstart](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event)から[compositionend](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event)の間、つまりIMEの候補が表示されてから確定されるまでの間`true`になります。
すごく良さそうです。

Safariでは、IME確定時のEnter入力の`keydownイベント`は`compositionend`の後に発火します。
つまり、IME確定時のEnter入力のKeyboardEventの`isComposing`は`false`です😩


# 参考資料
- [IME（全角）入力におけるjsイベント現状調査 - Qiita](https://qiita.com/darai0512/items/fac4f166c23bf2075deb)
- [jQueryでテキストボックスの変更を監視/検知する - Qiita](https://qiita.com/maruyam-a/items/cf0168f91d934b449a07)
- [javascriptの日本語入力ハンドリングについてのメモ - Qiita](https://qiita.com/shwld/items/6b6e66ab70b743554267)
- [[Vue.js] 日本語変換の確定でkeyup.enterが発火してしまうのを防ぐ - Qiita](https://qiita.com/h-orito/items/6d99e2256074132d7867)
- [inputの入力値の制御でIME入力とかいろいろ辛かった話 - Qiita](https://qiita.com/yanyan_ys/items/d14ecb3d2e5b7d119d5d)
- [jQueryでIME入力確定時にイベントを発行する - Qiita](https://qiita.com/hrdaya/items/6488d8dd3962cf35c0a0)
- [HTMLElement: beforeinput event - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event)