codedsaif-react-hooks
Version:
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
168 lines (157 loc) • 5.7 kB
JavaScript
import { useState, useEffect } from 'react';
/**
* useKeyPress
*
* A powerful custom React hook for handling keyboard interactions.
*
* Features:
* - Detect and respond to individual key presses
* - Match specific target key (e.g. 'a')
* - Maintain history of pressed keys
* - Detect modifier key states: Shift, Alt, Ctrl, Meta
* - Detect multi-key combos like 'ctrl+s', 'cmd+z'
* - Pause and resume key tracking
* - Optionally ignore key presses in input, textarea, or contentEditable fields
*
* @param {Object} options - Configuration options
* @param {Function} [options.onKeyPress] - Function to run when a valid key is pressed
* @param {string} [options.targetKey] - Key to match for triggering `keyMatched` (case-insensitive)
* @param {boolean} [options.enableHistory=true] - Whether to track history of pressed keys
* @param {number} [options.maxHistory=10] - Max number of keys to store in history
* @param {boolean} [options.ignoreInput=false] - If true, ignores key presses from editable elements (input, textarea, contentEditable)
* @param {string[]} [options.combos=[]] - List of keyboard combos to detect (e.g. ['ctrl+s', 'cmd+z'])
*
* @returns {Object} Hook output
* @returns {string|null} keyPressed - The current key being pressed (null if none)
* @returns {boolean} keyMatched - True if the most recent key matched `targetKey`
* @returns {string[]} keyHistory - Array of previously pressed keys (up to `maxHistory`)
* @returns {string|null} comboMatched - The matched combo string, if any (e.g. 'ctrl+s')
* @returns {Object} modifiers - Current modifier key states { shift, alt, ctrl, meta }
* @returns {Function} reset - Clears the keyMatched, keyHistory, and comboMatched states
* @returns {Function} setPaused - Function to pause/resume the hook (use `setPaused(true)` to pause)
*
* @example
* // Basic key detection and match tracking
* const { keyPressed, keyMatched, keyHistory } = useKeyPress({
* onKeyPress: (key) => console.log('You pressed:', key),
* targetKey: 'a',
* enableHistory: true,
* maxHistory: 5,
* });
*
* @example
* // Full usage with modifiers, combo detection, pause and reset
* const {
* keyPressed,
* keyMatched,
* keyHistory,
* comboMatched,
* modifiers,
* reset,
* setPaused,
* } = useKeyPress({
* onKeyPress: (key) => console.log('Key:', key),
* targetKey: 's',
* combos: ['ctrl+s', 'cmd+z'],
* ignoreInput: true,
* });
*
* useEffect(() => {
* if (comboMatched === 'ctrl+s') {
* console.log('Save triggered!');
* }
* }, [comboMatched]);
*/
const useKeyPress = ({
onKeyPress,
targetKey,
enableHistory = true,
maxHistory = 10,
ignoreInput = false,
combos = [],
} = {}) => {
const [keyPressed, setKeyPressed] = useState(null);
const [keyHistory, setKeyHistory] = useState([]);
const [keyMatched, setKeyMatched] = useState(false);
const [comboMatched, setComboMatched] = useState(null);
const [paused, setPaused] = useState(false);
const [modifiers, setModifiers] = useState({
shift: false,
alt: false,
ctrl: false,
meta: false,
});
const reset = () => {
setKeyHistory([]);
setKeyMatched(false);
setComboMatched(null);
};
const isEditableElement = (el) => {
const tag = el?.tagName?.toLowerCase();
return (
tag === 'input' ||
tag === 'textarea' ||
el?.isContentEditable
);
};
useEffect(() => {
const handleKeyDown = (e) => {
if (paused) return;
if (ignoreInput && isEditableElement(document?.activeElement)) return;
const key = e?.key?.toLowerCase();
setModifiers({
shift: e?.shiftKey,
alt: e?.altKey,
ctrl: e?.ctrlKey,
meta: e?.metaKey,
});
// Debounce repeated key presses
if (keyPressed === key) return;
setKeyPressed(key);
if (onKeyPress) onKeyPress(key);
// Handle history
if (enableHistory) {
setKeyHistory((prev) => {
const updated = [...prev, key];
return updated.slice(-maxHistory);
});
}
// Match single key
if (key === targetKey?.toLowerCase()) {
setKeyMatched(true);
}
// Match combo
const comboKey = [
e.ctrlKey ? 'ctrl' : '',
e.metaKey ? 'cmd' : '',
e.altKey ? 'alt' : '',
e.shiftKey ? 'shift' : '',
key,
]
.filter(Boolean)
.join('+');
if (combos.includes(comboKey)) {
setComboMatched(comboKey);
}
};
const handleKeyUp = () => {
setKeyPressed(null);
};
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keyup', handleKeyUp);
return () => {
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('keyup', handleKeyUp);
};
}, [keyPressed, paused, onKeyPress, targetKey, enableHistory, maxHistory, combos, ignoreInput]);
return {
keyPressed,
keyMatched,
keyHistory,
comboMatched,
modifiers,
reset,
setPaused,
};
};
export default useKeyPress;