← ./articles-ja

TauriアプリのCtrl+Sはsave handlerの前にpreventDefaultする

TauriアプリのfrontendはWebView内で動きます。つまり、desktop appでもブラウザ由来のkeyboard behaviorが残ります。

よくあるのが Ctrl+S です。save handlerを実装したのに、押すとnativeの「Save Page As」dialogが出る。アプリがshortcutを完全には奪えていません。

多くの場合、足りないのは event.preventDefault() です。

症状: desktop appなのにSave Pageが出る

症状はこうです。

  • Ctrl+S でprojectを保存したい
  • handler自体は呼ばれていることもある
  • それでもWebViewがsave-page dialogを出す
  • ユーザーにはブラウザっぽい挙動に見える

これは小さな技術問題ではなく、editor系desktop appの品質問題です。

shortcutを捕まえてdefaultを止める

app全体のshortcut handlerを置きます。

document.addEventListener("keydown", (event) => {
  const mod = event.ctrlKey || event.metaKey;

  if (mod && event.key.toLowerCase() === "s") {
    event.preventDefault();
    saveCurrentDocument();
  }
});

Windows/Linuxは ctrlKey、macOSは metaKey です。

重要なのは、ブラウザ標準挙動を置き換えるshortcut branchでは必ず preventDefault() を呼ぶことです。

handlerだけ呼んでも不十分

これは不十分です。

if ((event.ctrlKey || event.metaKey) && event.key === "s") {
  saveCurrentDocument();
}

app handlerが走っても、browser defaultも走ります。結果として「保存はされたがSave Page dialogも出る」という二重挙動になります。

関連shortcutも同じ方針にする

file actionは同じ形で扱います。

document.addEventListener("keydown", (event) => {
  const mod = event.ctrlKey || event.metaKey;
  const key = event.key.toLowerCase();

  if (mod && key === "s" && !event.shiftKey) {
    event.preventDefault();
    saveCurrentDocument();
  }

  if (mod && key === "o") {
    event.preventDefault();
    openProject();
  }

  if (mod && event.shiftKey && key === "s") {
    event.preventDefault();
    saveAs();
  }
});

shortcut mapは明示的にします。隠れたshortcutはテストしにくく、ユーザーにも伝わりません。

UIにshortcutを表示する

shortcutがあるactionは、近くに表示します。

<button onClick={saveCurrentDocument}>
  Save <kbd>Ctrl</kbd><kbd>S</kbd>
</button>

macOS向けには Cmd 表示へ切り替えると自然です。

pluginを使う場合

少数のapp shortcutなら、frontendの keydown handlerで十分なことが多いです。

広範囲のbrowser shortcutをまとめて抑止したいなら、tauri-plugin-prevent-default のようなpluginを使えます。ただし、app固有のcommandは明示しておくほうが、UIとtestが安定します。

検証チェック

  • Ctrl+S で保存され、Save Page dialogが出ない
  • macOS対応なら Cmd+S も動く
  • input focus中の編集shortcutを壊していない
  • React component内で登録したhandlerはunmount時に外す
  • UI表示と実際のshortcutが一致する
  • native dialog regressionを手動または自動で確認する

参考