javascripttypescriptlexicaljs

How can I listen to a keystroke combination with lexical.js?


I'm new to lexical.js and don't fully understand how I can listen to different keystroke combinations. I want to listen to "Ctrl/Cmd+S" and then fire off a callback (to save data). How can I do that?

With a textarea I can do something like:

const onKeyDown = (event) => {
  // "Ctrl" or "Cmd" + "s"
  if ((event.ctrlKey || event.metaKey) && event.which === 83) {
    // save data
  }
}

<textarea onKeyDown={onKeyDown} />

With lexical I've tried to do something like:

const MY_SAVE_COMMAND: LexicalCommand<string> = createCommand('MY_SAVE_COMMAND')

editor.registerCommand<KeyboardEvent>(
  MY_SAVE_COMMAND,
  (event) => {
    console.log('[MY_SAVE_COMMAND] event', event)
    return true
  },
  COMMAND_PRIORITY_HIGH,
)

which does not work. Where do I insert the part where I listen to the keystrokes, or is there a different way of doing this altogether?


Solution

  • Custom commands are more like global event types. Lexical provides a lot out of the box you can listen to, but for things like regular event listeners, what you probably want is to attach the event to the rootElement via editor.registerRootListener.

    You can use a custom plugin to manage attaching and removing this event listener. In the example below, I don't implement onKeyDown, but you would just like any other normal event handler.

    import {useLayoutEffect} from 'react';
    import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
    
    function CommandSPlugin() {
      const [editor] = useLexicalComposerContext();
      useLayoutEffect(() => {
        const onKeyDown = () => { /* Your handler logic here */ };
    
        return editor.registerRootListener(
          (
            rootElement: null | HTMLElement,
            prevRootElement: null | HTMLElement,
          ) => {
            if (prevRootElement !== null) {
              prevRootElement.removeEventListener('keydown', onKeyDown);
            }
            if (rootElement !== null) {
              rootElement.addEventListener('keydown', onKeyDown);
            }
          }
        );
      }, [editor]);
    }
    
    // Then later...
    
    const initialConfig = { /* ... */ };
    function App() {
      return (
        <LexicalComposer initialConfig={initialConfig}>
          <CommandSPlugin />
          <YourEditorHere />
        </LexicalComposer>
      );
    }