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 usingeditor.dispatchCommand(command, payload)
. Lexical dispatches commands internally when key presses are triggered and when other important signals occur. Commands can also be handled usingeditor.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
payload
s are typed via thecreateCommand(...)
API, but they're usually a DOMevent
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.