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

Time:02-04

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?

CodePudding user response:

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>
  );
}

CodePudding user response:

Custom commands do not have automatic triggers: you must 'dispatch' them.

From the intro in the docs:

Commands are the communication system used to wire everything together in Lexical. Custom commands can be created using createCommand() and dispatched to an editor using editor.dispatchCommand(command, payload). Lexical dispatches commands internally when key presses are triggered and when other important signals occur. Commands can also be handled using editor.registerCommand(handler, priority), and incoming commands are propagated through all handlers by priority until a handler stops the propagation (in a similar way to event propagation in the browser).

The built-in commands are dispatched in the same way. If you look at the LexicalEvents.ts file (referenced frequently in the docs), you can see in the function onKeyDown how their keyboard-triggered commands are implemented:

function onKeyDown(event: KeyboardEvent, editor: LexicalEditor): void {
  ...

  const {keyCode, shiftKey, ctrlKey, metaKey, altKey} = event;

  if (isMoveForward(keyCode, ctrlKey, altKey, metaKey)) {
    dispatchCommand(editor, KEY_ARROW_RIGHT_COMMAND, event);
  } else if (isMoveToEnd(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
    dispatchCommand(editor, MOVE_TO_END, event);
  } ...

It uses helper functions to check the key and some of the modifier keys to determine if a certain combo was used, and, if so, dispatches the relevant command.

The pattern of including the original event is noted in the docs for dispatchCommand:

The payloads are typed via the createCommand(...) API, but they're usually a DOM event for commands dispatched from an event listener.

Note: if you use Ctrl S, you'll need to use preventDefault() to prevent the browser trying to download the page.

  • Related