-
Matiss Janis Aboltins authoredMatiss Janis Aboltins authored
KeyHandlers.tsx 2.07 KiB
// @ts-strict-ignore
import React, { createContext, useEffect, useContext } from 'react';
import hotkeys, { type KeyHandler as HotKeyHandler } from 'hotkeys-js';
const KeyScopeContext = createContext('app');
hotkeys.filter = event => {
const target = (event.target || event.srcElement) as HTMLElement;
const tagName = target.tagName;
// This is the default behavior of hotkeys, except we only suppress
// key presses if the meta key is not pressed
if (
!event.metaKey &&
(target.isContentEditable ||
((tagName === 'INPUT' ||
tagName === 'TEXTAREA' ||
tagName === 'SELECT') &&
!target['readOnly']))
) {
return false;
}
return true;
};
type KeyHandlerProps = {
keyName: string;
eventType?: string;
handler: HotKeyHandler;
};
function KeyHandler({
keyName,
eventType = 'keydown',
handler,
}: KeyHandlerProps) {
const scope = useContext(KeyScopeContext);
if (eventType !== 'keyup' && eventType !== 'keydown') {
throw new Error('KeyHandler: unknown event type: ' + eventType);
}
useEffect(() => {
function _handler(event, hk) {
// Right now it always overrides the default behavior, but in
// the future we can make this customizable
event.preventDefault();
if (event.type === eventType && handler) {
return handler(event, hk);
}
}
hotkeys(keyName, { scope, keyup: true }, _handler);
return () => {
// @ts-expect-error unbind args typedef does not expect an object
hotkeys.unbind({
key: keyName,
scope,
method: _handler,
});
};
}, [keyName, handler, scope]);
return null;
}
type KeyHandlersProps = {
eventType?: string;
keys: Record<string, HotKeyHandler>;
};
export function KeyHandlers({ eventType, keys = {} }: KeyHandlersProps) {
const handlers = Object.keys(keys).map(key => {
return (
<KeyHandler
key={key}
keyName={key}
eventType={eventType}
handler={keys[key]}
/>
);
});
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{handlers}</>;
}