admin管理员组

文章数量:1314442

I'm new to lexical.js and don't fully understand how I can listen to different keystroke binations. 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?

I'm new to lexical.js and don't fully understand how I can listen to different keystroke binations. 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?

Share Improve this question asked Feb 3, 2023 at 16:17 caweidmanncaweidmann 4665 silver badges14 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 6

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

Thank you for the answers they helped me a lot. Below the full solution I ended up using. I created a <GlobalEventsPlugin /> with a SAVE_COMMAND mand which can then be consumed by any plugin:

// GlobalEventsPlugin.tsx
import { useLayoutEffect } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { LexicalCommand, createCommand } from 'lexical'

export const SAVE_COMMAND: LexicalCommand<KeyboardEvent> = createCommand('SAVE_COMMAND')

const GlobalEventsPlugin = () => {
  const [editor] = useLexicalComposerContext()

  useLayoutEffect(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      // "Ctrl" or "Cmd" + "s"
      if ((event.ctrlKey || event.metaKey) && event.which === 83) {
        editor.dispatchCommand(SAVE_COMMAND, event)
      }
    }

    return editor.registerRootListener((rootElement: HTMLElement | null, prevRootElement: HTMLElement | null) => {
      if (prevRootElement !== null) {
        prevRootElement.removeEventListener('keydown', onKeyDown)
      }
      if (rootElement !== null) {
        rootElement.addEventListener('keydown', onKeyDown)
      }
    })
  }, [editor])

  return null
}

export default GlobalEventsPlugin
// Editor.tsx
const initialConfig = { /* ... */ }
function App() {
  return (
    <LexicalComposer initialConfig={initialConfig}>
      <GlobalEventsPlugin />
      <YourEditorHere />
    </LexicalComposer>
  )
}
// Any other context that has access to `editor`
editor.registerCommand(
  SAVE_COMMAND,
  (event) => {
    // Do something with `event`, e.g. `event.preventDefault() && saveData()`
  },
  COMMAND_PRIORITY_HIGH,
)

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

From the intro in the docs:

Commands are the munication system used to wire everything together in Lexical. Custom mands can be created using createCommand() and dispatched to an editor using editor.dispatchCommand(mand, payload). Lexical dispatches mands internally when key presses are triggered and when other important signals occur. Commands can also be handled using editor.registerCommand(handler, priority), and ining mands 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 mands 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 mands 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 bo was used, and, if so, dispatches the relevant mand.

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 mands 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.

There're two mands you can use:

  • KEY_DOWN_COMMAND - Triggered on any key down event.
  • MODIFIER_KEY_COMMAND – Triggered on key press if any modifier key (Cmd/Ctrl/Alt) is also pressed, which makes it a perfect match for shortcuts.

本文标签: javascriptHow can I listen to a keystroke combination with lexicaljsStack Overflow