* Create keyboardShortcuts.jsx Add handler for keyboard shortcuts * Match github's hotkey style Makes objects clearer. * Update keyboardShortcuts.jsx * Allow single character shortcuts update to allow single character shortcuts/hotkeys * Add component documentation Add inline documentation for the component. * Fix example typo * Move keyboardShortcuts to a hook * Fixed useKeyboardShortcuts example * Rename keyboardShortcuts.jsx to useKeyboardShortcuts.jsx * Rebuild * Move isFormField outside render cycle Move the isFormField function outside of the render cycle. * Prevent event listener on empty object Don't add an event listener if the shortcuts property is an empty object or is null * Add tests for useKeyboardShortcuts Initial pass for adding tests to useKeyboardShortcuts * Missed unmount Missed a whole word... * Fix typos * Convert function type * Add new test cases. * Missing brackets * Typo * Update useKeyboardShortcuts.test.jsx * Move event listener to document * Update tests * Use testing-library/user-event * Correct fireEvent * Update keyDown * Slight tidy * Change order, just in case * Swap to render * Make function async * Rerun travis * Add rerender * Fix render function * check event.key for fallback
104 lines
2.8 KiB
JavaScript
104 lines
2.8 KiB
JavaScript
import { useEffect } from "preact/hooks";
|
|
import PropTypes from 'prop-types';
|
|
import { h } from 'preact';
|
|
|
|
/**
|
|
* Checker that return true if element is a form element
|
|
*
|
|
* @param {node} element to be checked
|
|
*
|
|
* @returns {boolean} isFormField
|
|
*/
|
|
function isFormField(element) {
|
|
if ((element instanceof HTMLElement) === false) return false;
|
|
|
|
const name = element.nodeName.toLowerCase();
|
|
const type = (element.getAttribute("type") || "").toLowerCase();
|
|
return (
|
|
name === "select" ||
|
|
name === "textarea" ||
|
|
(name === "input" && ["submit", "reset", "checkbox", "radio"].indexOf(type) < 0) ||
|
|
element.isContentEditable
|
|
);
|
|
};
|
|
|
|
/**
|
|
* hook that can be added to a component to listen
|
|
* for keyboard presses
|
|
*
|
|
* @example
|
|
* const shortcuts = {
|
|
* "ctrl+alt+KeyG": (e) => {
|
|
* e.preventDefault();
|
|
* alert("Control Alt G has been pressed");
|
|
* },
|
|
* "?": (e) => {
|
|
* setIsHelpVisible(true);
|
|
* }
|
|
* }
|
|
*
|
|
* useKeyboardShortcuts(shortcuts, someElementOrWindowObject);
|
|
*
|
|
* @param {object} shortcuts List of keyboard shortcuts/event
|
|
* @param {EventTarget} [eventTarget=window] An event target.
|
|
*
|
|
*/
|
|
export function useKeyboardShortcuts(shortcuts, eventTarget = window) {
|
|
useEffect(() => {
|
|
if (!shortcuts || Object.keys(shortcuts).length === 0) return;
|
|
|
|
const keyEvent = (e) => {
|
|
if (e.defaultPrevented) return;
|
|
|
|
// Get special keys
|
|
const keys = `${e.ctrlKey || e.metaKey ? "ctrl+" : ""}${e.altKey ? "alt+" : ""}${e.shiftKey ? "shift+" : ""}`;
|
|
|
|
// If no special keys are pressed and focus is inside a field return
|
|
if (e.target instanceof Node && isFormField(e.target) && !keys) return;
|
|
|
|
const shortcut = shortcuts[`${keys}${e.code}`] || shortcuts[`${keys}${e.key.toLowerCase()}`];
|
|
if (shortcut) shortcut(e);
|
|
};
|
|
|
|
eventTarget.addEventListener("keydown", keyEvent);
|
|
|
|
return () => {
|
|
eventTarget.removeEventListener("keydown", keyEvent);
|
|
};
|
|
}, [shortcuts, eventTarget]);
|
|
}
|
|
|
|
/**
|
|
* compoent that can be added to a component to listen
|
|
* for keyboard presses using the useKeyboardShortcuts hook
|
|
*
|
|
* @example
|
|
* const shortcuts = {
|
|
* "ctrl+alt+KeyG": (e) => {
|
|
* e.preventDefault();
|
|
* alert("Control Alt G has been pressed")
|
|
* }
|
|
* }
|
|
*
|
|
* <KeyboardShortcuts shortcuts={shortcuts} />
|
|
* <KeyboardShortcuts shortcuts={shortcuts} eventTarget={ref.current} />
|
|
*
|
|
* @param {object} shortcuts List of keyboard shortcuts/event
|
|
* @param {EventTarget} [eventTarget=window] An event target.
|
|
*
|
|
*/
|
|
export function KeyboardShortcuts({ shortcuts, eventTarget }) {
|
|
useKeyboardShortcuts(shortcuts, eventTarget);
|
|
|
|
return null;
|
|
}
|
|
|
|
KeyboardShortcuts.propTypes = {
|
|
shortcuts: PropTypes.object.isRequired,
|
|
eventTarget: PropTypes.instanceOf(Element)
|
|
}
|
|
|
|
KeyboardShortcuts.defaultProps = {
|
|
shortcuts: {},
|
|
eventTarget: window
|
|
}
|