← ブログ

useKeymapによるキーボードショートカット

useKeymapでキー割り当てを一度宣言すると、Text.jsがキー入力を振り分け、?ヘルプ画面に掲載し、フッターに表示し、Ctrl+Kコマンドパレットに追加します。

Adam

これまで、Text.jsアプリでは useInput でキーを処理し、同じキーを <KeyHint> フッターにも手作業で記載していました。useKeymap は、この両方を1つの宣言に置き換えます。

import { CommandPalette, KeyHelp, KeyHints, StatusBar, Text, useKeymap } from "@textjs/react";export default function Sessions() {  useKeymap(    {      "j, down": { label: "Next", run: next, hint: true },      "k, up": { label: "Previous", run: previous },      "g g": { label: "Go to top", run: top },      "ctrl+s": { label: "Save", run: save, hint: true },    },    { group: "Navigation" },  );  return (    <>      <StatusBar right={<Text color="subtle">main</Text>}>        <KeyHints />      </StatusBar>      <CommandPalette commands={commands} />      <KeyHelp />    </>  );}

j を押すと next が実行されます。macOSでは、これらのバインディングに hint が設定されているため、<KeyHints> がフッターに j/↓ Next と ⌃S Save を表示します。? は4つすべてをNavigationグループにまとめたヘルプ画面を開き、Ctrl+K はそれらを検索できるパレットを開きます。

キーの構文

構文意味
ctrl+k修飾キー:ctrl、alt、shift、meta、mod
mod+k⌘ または Ctrl のうち押されている方
j, downいずれかのキー
g gキー間の間隔が最大1秒のシーケンス
G大文字は Shift を含む指定
?入力可能なキーは文字と照合

バインディングは、ハンドラー、または label、group、hint、command、disabled、noRepeat を持つオブジェクトです。command: false を指定すると、ラベル付きのバインディングがパレットから除外されます。ラベルのないバインディングも動作しますが、ヘルプ画面とパレットには表示されません。

ルーティング

各キーマップは1つの useInput ハンドラーを登録するため、既存の入力ルールが適用されます。エディターは優先度10、キーマップは0で入力を受け取るため、フォーカスされた <Input> が先にキーを処理します。一致したキーは伝播を停止します。<Modal> が開いていると、その外側のバインディングは無効になります。ただし、キーマップが global: true を渡している場合は除きます。

シーケンスは、そのハンドラー内で照合されます。シーケンスを開始するキーは保留状態で保持され、伝播を停止し、次のキーが sequenceTimeout(デフォルトで1,000ms)を超えてから届くとシーケンスがリセットされます。

if (p.events.length > 0 && now - p.at > sequenceTimeout) p.events = [];

ルートごとに1つのレジストリー

ラベル付きのバインディングは、コンソールのルートに属するレジストリーに公開されます。<KeyHelp>、<KeyHints>、<CommandPalette> はそこから読み取り、独自のコンポーネントでも useKeymapBindings() を通じて読み取れます。モーダルが開いている間、レジストリーはその内側のバインディングだけを返すため、ヘルプ画面にはその時点で使えるキーが表示されます。

キーマップは通常インラインで記述するため、レンダーのたびに新しいオブジェクトが作られます。このコストを抑えるため、キーマップは表示内容が変わったときだけラベルを再公開します。

// Publish labels only when the visible shape changes (inline keymap objects are fine).const signature = compiled  .map((c) => `${c.keys}\u0000${c.binding.label ?? ""}\u0000${c.binding.group ?? ""}\u0000${c.binding.hint ? 1 : 0}${c.binding.command === false ? 0 : 1}\u0000${c.binding.disabled ? 1 : 0}`)  .join("\u0001");

キーが届いた時点でrefからハンドラーを読み取るため、レンダーごとに作られる新しいクロージャーが古くなることはありません。

ヘルプ画面とパレット

<KeyHelp> は ? で開き、有効なラベル付きバインディングをすべてグループ別に表示します。keys と title でトリガーと見出しを変更できます。

<CommandPalette> は Ctrl+K または Ctrl+P で開きます。commands とすべてのラベル付きバインディングをあいまい検索し、一致した文字を強調表示します。コマンドを実行する前に閉じるため、コマンドから独自のモーダルを開けます。

formatKeys は、バインディングを表示用に整形します。formatKeys("ctrl+shift+p") はmacOSでは ⌃⇧P、それ以外では ctrl+shift+P を返し、<Kbd> は同じ形式で単一のキーを表示します。

useKeymap、<KeyHelp>、<KeyHints>、<StatusBar>、<CommandPalette> はText.js 1.0に含まれています。リファレンスはキーボードにあります。