react-hotkeys-hook
Version:
React hook for using keyboard shortcuts in components. This is a hook version for the [hotkeys] package.
90 lines (72 loc) • 3.77 kB
text/typescript
import hotkeys, { HotkeysEvent, KeyHandler } from 'hotkeys-js';
import React, { useCallback, useEffect, useRef } from 'react';
type AvailableTags = 'INPUT' | 'TEXTAREA' | 'SELECT';
// We implement our own custom filter system.
hotkeys.filter = () => true;
const tagFilter = ({ target }: KeyboardEvent, enableOnTags?: AvailableTags[]) => {
const targetTagName = target && (target as HTMLElement).tagName;
return Boolean((targetTagName && enableOnTags && enableOnTags.includes(targetTagName as AvailableTags)));
};
const isKeyboardEventTriggeredByInput = (ev: KeyboardEvent) => {
return tagFilter(ev, ['INPUT', 'TEXTAREA', 'SELECT']);
};
export type Options = {
enabled?: boolean; // Main setting that determines if the hotkey is enabled or not. (Default: true)
filter?: typeof hotkeys.filter; // A filter function returning whether the callback should get triggered or not. (Default: undefined)
filterPreventDefault?: boolean; // Prevent default browser behavior if the filter function returns false. (Default: true)
enableOnTags?: AvailableTags[]; // Enable hotkeys on a list of tags. (Default: [])
enableOnContentEditable?: boolean; // Enable hotkeys on tags with contentEditable props. (Default: false)
splitKey?: string; // Character to split keys in hotkeys combinations. (Default +)
scope?: string; // Scope. Currently not doing anything.
keyup?: boolean; // Trigger on keyup event? (Default: undefined)
keydown?: boolean; // Trigger on keydown event? (Default: true)
};
export function useHotkeys<T extends Element>(keys: string, callback: KeyHandler, options?: Options): React.MutableRefObject<T | null>;
export function useHotkeys<T extends Element>(keys: string, callback: KeyHandler, deps?: any[]): React.MutableRefObject<T | null>;
export function useHotkeys<T extends Element>(keys: string, callback: KeyHandler, options?: Options, deps?: any[]): React.MutableRefObject<T | null>;
export function useHotkeys<T extends Element>(keys: string, callback: KeyHandler, options?: any[] | Options, deps?: any[]): React.MutableRefObject<T | null> {
if (options instanceof Array) {
deps = options;
options = undefined;
}
const {
enableOnTags,
filter,
keyup,
keydown,
filterPreventDefault = true,
enabled = true,
enableOnContentEditable = false,
} = options as Options || {};
const ref = useRef<T | null>(null);
// The return value of this callback determines if the browsers default behavior is prevented.
const memoisedCallback = useCallback((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => {
if (filter && !filter(keyboardEvent)) {
return !filterPreventDefault;
}
// Check whether the hotkeys was triggered inside an input and that input is enabled or if it was triggered by a content editable tag and it is enabled.
if (
(isKeyboardEventTriggeredByInput(keyboardEvent) && !tagFilter(keyboardEvent, enableOnTags))
|| ((keyboardEvent.target as HTMLElement)?.isContentEditable && !enableOnContentEditable)
) {
return true;
}
if (ref.current === null || document.activeElement === ref.current) {
callback(keyboardEvent, hotkeysEvent);
return true;
}
return false;
}, deps ? [ref, enableOnTags, filter, ...deps] : [ref, enableOnTags, filter]);
useEffect(() => {
if (!enabled) {
return;
}
// In this case keydown is likely undefined, so we set it to false, since hotkeys needs the `keydown` key to have a value.
if (keyup && keydown !== true) {
(options as Options).keydown = false;
}
hotkeys(keys, (options as Options) || {}, memoisedCallback);
return () => hotkeys.unbind(keys, memoisedCallback);
}, [memoisedCallback, keys, enabled]);
return ref;
}