UNPKG

ahooks

Version:
234 lines (233 loc) 7.25 kB
import useLatest from '../useLatest'; import { isFunction, isNumber, isString } from '../utils'; import { getTargetElement } from '../utils/domTarget'; import useDeepCompareEffectWithTarget from '../utils/useDeepCompareWithTarget'; import isAppleDevice from '../utils/isAppleDevice'; // 键盘事件 keyCode 别名 const aliasKeyCodeMap = { '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, '8': 56, '9': 57, backspace: 8, tab: 9, enter: 13, shift: 16, // Legacy alias kept for compatibility; standard name is "control". ctrl: 17, control: 17, alt: 18, // Legacy alias kept for compatibility; standard name is "pause". pausebreak: 19, pause: 19, capslock: 20, // Legacy alias kept for compatibility; standard name is "escape". esc: 27, escape: 27, // Legacy alias kept for compatibility; standard name is "spacebar" (non-standard but widely used). space: 32, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36, // Legacy aliases kept for compatibility; standard names are "arrowleft/arrowup/arrowright/arrowdown". leftarrow: 37, arrowleft: 37, uparrow: 38, arrowup: 38, rightarrow: 39, arrowright: 39, downarrow: 40, arrowdown: 40, insert: 45, delete: 46, a: 65, b: 66, c: 67, d: 68, e: 69, f: 70, g: 71, h: 72, i: 73, j: 74, k: 75, l: 76, m: 77, n: 78, o: 79, p: 80, q: 81, r: 82, s: 83, t: 84, u: 85, v: 86, w: 87, x: 88, y: 89, z: 90, leftwindowkey: 91, rightwindowkey: 92, meta: isAppleDevice ? [91, 93] : [91, 92], // Legacy alias kept for compatibility; standard name is "contextmenu". selectkey: 93, contextmenu: 93, numpad0: 96, numpad1: 97, numpad2: 98, numpad3: 99, numpad4: 100, numpad5: 101, numpad6: 102, numpad7: 103, numpad8: 104, numpad9: 105, multiply: 106, add: 107, subtract: 109, decimalpoint: 110, divide: 111, f1: 112, f2: 113, f3: 114, f4: 115, f5: 116, f6: 117, f7: 118, f8: 119, f9: 120, f10: 121, f11: 122, f12: 123, numlock: 144, scrolllock: 145, semicolon: 186, equalsign: 187, comma: 188, dash: 189, period: 190, forwardslash: 191, graveaccent: 192, openbracket: 219, backslash: 220, closebracket: 221, singlequote: 222, }; // 修饰键 const modifierKey = { ctrl: (event) => event.ctrlKey || (event.type === 'keyup' && event.keyCode === 17), shift: (event) => event.shiftKey || (event.type === 'keyup' && event.keyCode === 16), alt: (event) => event.altKey || (event.type === 'keyup' && event.keyCode === 18), meta: (event) => event.metaKey || (event.type === 'keyup' && aliasKeyCodeMap.meta.includes(event.keyCode)), }; // 判断合法的按键类型 function isValidKeyType(value) { return isString(value) || isNumber(value); } // 根据 event 计算激活键数量 function countKeyByEvent(event) { const countOfModifier = Object.keys(modifierKey).reduce((total, key) => { if (modifierKey[key](event)) { return total + 1; } return total; }, 0); // 16 17 18 91 92 是修饰键的 keyCode,如果 keyCode 是修饰键,那么激活数量就是修饰键的数量,如果不是,那么就需要 +1 return [16, 17, 18, 91, 92].includes(event.keyCode) ? countOfModifier : countOfModifier + 1; } /** * 判断按键是否激活 * @param [event: KeyboardEvent]键盘事件 * @param [keyFilter: any] 当前键 * @returns string | number | boolean */ function genFilterKey(event, keyFilter, exactMatch) { // 浏览器自动补全 input 的时候,会触发 keyDown、keyUp 事件,但此时 event.key 等为空 if (!event.key) { return false; } // 数字类型直接匹配事件的 keyCode if (isNumber(keyFilter)) { return event.keyCode === keyFilter ? keyFilter : false; } // 字符串依次判断是否有组合键 const genArr = keyFilter.split('.'); let genLen = 0; for (const key of genArr) { // 组合键 const genModifier = modifierKey[key]; // keyCode 别名 const aliasKeyCode = aliasKeyCodeMap[key.toLowerCase()]; if ((genModifier && genModifier(event)) || (aliasKeyCode && aliasKeyCode === event.keyCode)) { genLen++; } } /** * 需要判断触发的键位和监听的键位完全一致,判断方法就是触发的键位里有且等于监听的键位 * genLen === genArr.length 能判断出来触发的键位里有监听的键位 * countKeyByEvent(event) === genArr.length 判断出来触发的键位数量里有且等于监听的键位数量 * 主要用来防止按组合键其子集也会触发的情况,例如监听 ctrl+a 会触发监听 ctrl 和 a 两个键的事件。 */ if (exactMatch) { return genLen === genArr.length && countKeyByEvent(event) === genArr.length ? keyFilter : false; } return genLen === genArr.length ? keyFilter : false; } /** * 键盘输入预处理方法 * @param [keyFilter: any] 当前键 * @returns () => Boolean */ function genKeyFormatter(keyFilter, exactMatch) { if (isFunction(keyFilter)) { return keyFilter; } if (isValidKeyType(keyFilter)) { return (event) => genFilterKey(event, keyFilter, exactMatch); } if (Array.isArray(keyFilter)) { return (event) => keyFilter.find((item) => genFilterKey(event, item, exactMatch)); } return () => Boolean(keyFilter); } const defaultEvents = ['keydown']; function useKeyPress(keyFilter, eventHandler, option) { const { events = defaultEvents, target, exactMatch = false, useCapture = false } = option || {}; const eventHandlerRef = useLatest(eventHandler); const keyFilterRef = useLatest(keyFilter); useDeepCompareEffectWithTarget(() => { var _a; const el = getTargetElement(target, window); if (!el) { return; } const callbackHandler = (event) => { var _a; const keyEvent = event; const genGuard = genKeyFormatter(keyFilterRef.current, exactMatch); const keyGuard = genGuard(keyEvent); const firedKey = isValidKeyType(keyGuard) ? keyGuard : keyEvent.key; if (keyGuard) { return (_a = eventHandlerRef.current) === null || _a === void 0 ? void 0 : _a.call(eventHandlerRef, keyEvent, firedKey); } }; for (const eventName of events) { (_a = el === null || el === void 0 ? void 0 : el.addEventListener) === null || _a === void 0 ? void 0 : _a.call(el, eventName, callbackHandler, useCapture); } return () => { var _a; for (const eventName of events) { (_a = el === null || el === void 0 ? void 0 : el.removeEventListener) === null || _a === void 0 ? void 0 : _a.call(el, eventName, callbackHandler, useCapture); } }; }, [events], target); } export default useKeyPress;