😇

SafariでIME確定時のEnterを上手く制御できなかった話

2022/12/05に公開

はじめに

こんにちは!
スペースマーケットでフロントエンドエンジニアをしておりますk___0122です。
今回は、SafariでIME確定時のEnterの制御が上手くいかずハマったのでまとめてみました!

背景

簡単なサンプルを用意しました。
inputに値を入力し、Enterを押した際にstateを更新したいとします。
最初は以下のような実装をしてました。

import React, { useState } from "react";

export default function App() {
  const [keywords, setKeywords] = useState([]);

  const handleKeydown = (e) => {
    if (e.key === "Enter") {
      setKeywords([...keywords, e.target.value]);
    }
  };

  return (
    <div>
      <input onKeyDown={handleKeydown} />
      <ul>
        {keywords.map((keyword, i) => (
          <li key={i}>{keyword}</li>
        ))}
      </ul>
    </div>
  );
}

ただここで、e.key === 'Enter'で判定すると、IME確定時のEnterも含まれてしまいます。
試しにやきゅうと入力し、Enterで予測変換の確定をするとsetKeyWordsが発火してしまいます。

制御できる方法はないかと調べてたところ、KeyboardEvent.isComposingなるものを見つけました。

イベントが変換セッションの途中、すなわち compositionstart の後かつ compositionend の前に発行されたことを示す論理値を返します。

https://developer.mozilla.org/ja/docs/Web/API/KeyboardEvent/isComposing

isComposingでは文字の変換中はtrueを返し、そうでない場合はfalseを返してくれるそうです。

KeyboardEvent.isComposingを使った実装が以下のようになります。

import React, { useState } from "react";

export default function App() {
  const [keywords, setKeywords] = useState([]);

  const handleKeydown = (e) => {
    if (!e.nativeEvent.isComposing && e.key === "Enter") {
      setKeywords([...keywords, e.target.value]);
    }
  };

  return (
    <div>
      <input onKeyDown={handleKeydown} />
      <ul>
        {keywords.map((keyword, i) => (
          <li key={i}>{keyword}</li>
        ))}
      </ul>
    </div>
  );
}

Chromeで確認したところ上手くできてそう。

問題

ところが上記の実装だと、SafariでIME確定時のEnterを押すたびに、setKeywordsが走ってしまいます。

ブラウザによって挙動が違うようで、Safariの場合IME確定時のEnterを押した際に、isComposingがfalseになってしまうそうです。
こちらの記事参考になりました🙏
https://qiita.com/darai0512/items/fac4f166c23bf2075deb#入力に対するイベント発火の流れ
https://blog.utgw.net/entry/2021/06/29/212256

解決策

非推奨ですが、今回はKeyboardEvent.keyCodeを使いました。
普通のEnterはkeyCodeが13に、IME確定のEnterは229になるようです。

https://developer.mozilla.org/ja/docs/Web/API/KeyboardEvent/keyCode
KeyboardEvent.keyCodeを使った実装が以下のようになります。

import React, { useState } from "react";

export default function App() {
  const [keywords, setKeywords] = useState([]);

  const handleKeydown = (e) => {
    if (e.keyCode === 13) {
      setKeywords([...keywords, e.target.value]);
    }
  };

  return (
    <div>
      <input onKeyDown={handleKeydown} />
      <ul>
        {keywords.map((keyword, i) => (
          <li key={i}>{keyword}</li>
        ))}
      </ul>
    </div>
  );
}

これでSafariでも、IME確定時のEnterと普通のEnterを制御できるようになりました!

isComposingの挙動がブラウザで揃って欲しいです・・・
(他にいい方法あればコメントください🙏)

今回のサンプルです。
https://stackblitz.com/edit/react-wtsvbc?file=src%2FApp.js

最後に

スペースマーケットでは、一緒にサービスを成長させていく仲間を探しています。
とりあえずどんなことしているのか聞いてみたいという方も大歓迎です!
ご興味ありましたら是非ご覧ください!
https://www.wantedly.com/projects/1113570
https://www.wantedly.com/projects/1113544
https://www.wantedly.com/projects/1061116
https://spacemarket.co.jp/recruit/engineer/

スペースマーケット Engineer Blog

Discussion