今回は emacs keybindings などを実現するための下準備として キーボードショートカットへの対応方法を調べます。
たとえば、 Ctrl + S したら検索(または保存処理)を実行、などという 機能を実現するために、そのキーイベントに反応させるには どうしたらいいかを調べます。
そして https://github.com/facebook/lexical からソースをダウンロードして次のコードを読む。
これ(https://lexical.dev/docs/concepts/commands)を理解すべき。
コマンドを作成して( createCommand )、 editor.dispatchCommand() したり、 dispatchCommand されたコマンドを editor.registerCommand() に登録した関数で受け取るしくみが lexical にはあらかじめ備わっている。
このコード。
function onKeyDown(event: KeyboardEvent, editor: LexicalEditor): void {
lastKeyDownTimeStamp = event.timeStamp;
lastKeyCode = event.keyCode;
if (editor.isComposing()) {
return;
}
const {keyCode, shiftKey, ctrlKey, metaKey, altKey} = event;
if (dispatchCommand(editor, KEY_DOWN_COMMAND, event)) {
return;
}
// 以下、デフォルト(というかビルトイン済み)のショートカットの処理が記述されている。
onKeyDown 関数の先頭部分 if (dispatchCommand(editor, KEY_DOWN_COMMAND, event)) { return ; } で、 KEY_DOWN_COMMAND が dispatchCommand() されて、 もし dispatchCommnd() の戻り値が true ならば、 onKeyDown 関数はそこで終了する。
したがって キーイベントが起きたときに、何かする処理を入れたい場合、 この KEY_DOWN_COMMAND に反応する処理を editor.registerCommand() で 登録しておけばよいことになる。
それでは、この KEY_DOWN_COMMAND が dispatch されてきたら、そのイベントを受け取り、処理を追加するプラグイン MyKeyEventsPlugin を書きます。
src/Editor.js
import { KEY_DOWN_COMMAND } from 'lexical';
...
function MyKeyEventsPlugin() {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerCommand(
KEY_DOWN_COMMAND,
(keyboardEvent)=>{
console.log(keyboardEvent);
keyboardEvent.preventDefault();
return true;
},
COMMAND_PRIORITY_EDITOR,
);
}, [editor]);
}
...
return (
<LexicalComposer initialConfig={initialConfig}>
<PlainTextPlugin ... />
<MyKeyEventsPlugin />
...
</LexicalComposer>
);
MyKeyEventsPlugin を作成して、 editor.registerCommand() を記述します。
KEY_DOWN_COMMAND を受け取るように指定した上で、処理内容を次のように記述しています。
(keyboardEvent)=>{
console.log(keyboardEvent);
keyboardEvent.preventDefault();
return true;
},
preventDefault() してデフォルトの作動を抑止した上で true を返しているだけです。 これで、 Editor で何を入力(どんなキー入力)しても、無反応になります。 これで lexical のエディタに対するキー入力を乗っ取ることができました。
もちろん、エディタで何もキーが受け付けされなかったら無意味なので、 ここで本当に実装したいことは、 自分が対処したいキー入力に対してのみ処理を追加して true を返す、 それ以外は全部 false を返すことです。
たとえば、Ctrl + S だけ乗っ取りたければ、次のように実装します。
function MyKeyEventsPlugin() {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerCommand(
KEY_DOWN_COMMAND,
(keyboardEvent)=>{
console.log(keyboardEvent);
if( keyboardEvent.ctrlKey && keyboardEvent.keyCode === 83 ){
// Ctrl + S:
keyboardEvent.preventDefault();
// TODO: do something.
return true;
} else {
return false;
}
},
COMMAND_PRIORITY_EDITOR,
);
}, [editor]);
}
keyboardEvent.keyCode でキー入力を取得していますが、keyboardEvent.which でとるべきとかその辺は本題からはずれるので、省略。 ここでは keyboardEvent.keyCode だけで処理しています。
lexical 神か。 ただし、これは数時間調べて把握したことなので、何か勘違いなどがあるかもしれない。
これでも一部のキーイベントをとることはできたのだが、 Ctrl + A など あらかじめ lexical のエディタにビルトインされているショートカットキーを乗っ取ることはできない。
何かの役に立つかも知れないのでメモしておきます。
src/Editor.js
function GlobalEventsPlugin() {
const [editor] = useLexicalComposerContext();
useEffect(() => {
const onKeyDown = (keyboardEvent)=> {
console.log(keyboardEvent.keyCode);
if( keyboardEvent.ctrlKey && keyboardEvent.which === 83 ){
// Ctrl + S:
keyboardEvent.preventDefault();
// TODO: do something.
}
};
const eventType = "keydown";
return editor.registerRootListener((rootElement, prevRootElement) => {
if (rootElement) {
rootElement.addEventListener(eventType, onKeyDown);
}
if (prevRootElement) {
prevRootElement.removeEventListener(eventType, onKeyDown);
}
});
}, [editor]);
}
この GlobalEventsPlugin をセットすれば Ctrl+S については意図通作動した。
以上です。