UNPKG

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
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;