contentEdiableな要素でのUndoの注意点


モチベーション

忘備録です。

ブラウザやelectronで、contentEdiable=trueな要素を使って文章編集をする場合、windows環境では、Undo/Redoに関するショートカットキーを入力すると、次のことが起こります。

  • 文字入力時にpreventDefaultせず、デフォルトの動作でDOMに文字入力宇した場合のみ、ブラウザの持つデフォルトのUndo履歴に残る。これはショートカットキー(windowsならCtrl+z)でUndoできる。
  • 文字入力時にpreventDefaultし、独自でDOM編集して文字入力した場合は、ブラウザデフォルトのUndo履歴に残らない。ショートカットキーでUndoした場合にはスキップされる。
  • IME入力モードの場合は、原則preventDefaultできない。

リッチテキストエディタを作っており、一部で独自でDOM操作する必要があったので、Undo/Redoも自前で実装していました。そのため、半角入力に関してはブラウザ標準のUndo履歴が残っておらず、ブラウザが勝手にUndoすることはありませんでした。一方で、三番目の問題があり、IMEだけはブラウザ標準のUndo履歴に編集履歴が残っており、ショートカットキーなどによってブラウザが勝手にUndoすることがありました。それに気づくまでは、原因不明の挙動として随分悩みました。。。。

例として、次のような空のdivに、

<div contentEditable="true"></div>

カーソルを合わせて、次の手順で文字を入力します、

  1. IMEモードであああと入力
  2. 半角英数モードでaaaと入力。ただし、keydownでpreventDefault()し、プログラム上から文字aをDOMのTextNodeに3回insertする。
  3. IMEモードでいいいと入力

こうすると、あああいいいの操作だけがブラウザの標準Undo履歴に残っています。ここから、Ctrl+zを二回押してUndoすると、aaaの編集はスキップされて、次のようになります。

<div contentEditable="true">aaa</div>

やっかいですね。

対策

独自DOM操作する場合は、Undo/Redoも独自で実装すると思います。その時忘れずに、Undo/RedoのデフォルトのショートカットキーもpreventDefaultしましょう。windowsの場合は、次のキーをpreventDefault()すればよいです。

  • Undo : Ctrl+z
  • Redo:Ctrl+Shift+zおよびCtrl+y

Redoのキーが二種類挙げてありますが、どちらが有効か(もしくはその両方が有効か)はブラウザに依存します。

最後に

contentEditableに関しては、そもそもブラウザがどこまでを請け負ってくれるのかクリアでないので、結局は全てをpreventDefaultしてしまった方が楽な気がします。文字入力から全てのDOM操作や独自Undo実装をやるなら、そもそもなぜcontentEditable使うんだという気もするのですが、編集カ所にカーソル(キャレット)を表示できる、この一点に尽きるんですよね。カーソルさえ表示できれば、contntEditableなんて動作の曖昧なものを使わずに済むのに、と思います。。。

どなたかcontntEditable=falseでもカーソルを表示できる方法を知っていたら教えてください。