UNPKG

@acusti/use-keyboard-events

Version:

React hook that takes keyboard event handlers and attaches them to the document

116 lines 4.53 kB
export const handlersData = { config: new Map(), keydown: [], keypress: [], keyup: [], }; const PRIORITY_MIN = -50; const PRIORITY_MAX = 50; const PRIORITY_MODIFIER = PRIORITY_MIN * -1; const noop = () => { }; // eslint-disable-line @typescript-eslint/no-empty-function export function addHandler({ eventType, handler, ignoreUsedKeyboardEvents = true, priority: _priority, }) { var _a; if (!handler) return noop; // normalize priority to be zero-based between min and max (for use as an array index) const priority = Math.min(PRIORITY_MAX + PRIORITY_MODIFIER, Math.max(0, (_priority !== null && _priority !== void 0 ? _priority : 0) + PRIORITY_MODIFIER)); const handlersByPriority = handlersData[eventType]; (_a = handlersByPriority[priority]) !== null && _a !== void 0 ? _a : (handlersByPriority[priority] = new Set()); handlersByPriority[priority].add(handler); handlersData.config.set(handler, { ignoreUsedKeyboardEvents, priority }); return () => { handlersData.config.delete(handler); if (handlersByPriority[priority] == null) return; handlersByPriority[priority].delete(handler); if (!handlersByPriority[priority].size) { delete handlersByPriority[priority]; // if no other handlers have a higher priority, resize the array for (let index = priority; index > -1 && handlersByPriority[index] == null && handlersByPriority.length === index + 1; index--) { handlersByPriority.length = index; } } }; } const IS_APPLE_REGEXP = /mac|iphone|ipad|ipod/i; export function isPrimaryModifierPressed(event) { var _a, _b; // eslint-disable-next-line @typescript-eslint/no-deprecated const platform = (_b = (_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.platform) !== null && _b !== void 0 ? _b : ''; return IS_APPLE_REGEXP.test(platform) ? event.metaKey : event.ctrlKey; } const NON_TEXT_INPUT_TYPES = new Set([ 'button', 'checkbox', 'color', 'file', 'hidden', 'image', 'radio', 'range', 'reset', 'submit', ]); const NON_TEXT_INPUT_USES_KEYS = new Set([ ' ', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Enter', ]); export const usesKeyEvents = (target) => target.isContentEditable || target.tagName === 'TEXTAREA' || target.tagName === 'INPUT'; export const isEventTargetUsingKeyEvent = (event) => { const target = event.target; if (target == null || !usesKeyEvents(target)) return false; // Restrict special handling to only non-text <input> elements if (target.tagName !== 'INPUT') return true; if (!NON_TEXT_INPUT_TYPES.has(target.type)) return true; // Non-text inputs can use arrow keys, escape, the spacebar, and return / enter return NON_TEXT_INPUT_USES_KEYS.has(event.key); }; export function addHandlers(doc) { if (!(doc === null || doc === void 0 ? void 0 : doc.defaultView) || doc.defaultView.__useKeyboardEventsAttached__) { return; } doc.defaultView.__useKeyboardEventsAttached__ = true; doc.addEventListener('keydown', handleKeyboardEvent); doc.addEventListener('keypress', handleKeyboardEvent); doc.addEventListener('keyup', handleKeyboardEvent); return () => { if (doc.defaultView) { doc.defaultView.__useKeyboardEventsAttached__ = false; } doc.removeEventListener('keydown', handleKeyboardEvent); doc.removeEventListener('keypress', handleKeyboardEvent); doc.removeEventListener('keyup', handleKeyboardEvent); }; } function handleKeyboardEvent(event) { const eventType = event.type; const handlersByPriority = handlersData[eventType]; let index = handlersByPriority.length; const targetUsesKeyEvents = isEventTargetUsingKeyEvent(event); while (index--) { const handlers = handlersByPriority[index]; if (handlers != null) { for (const handler of handlers) { const config = handlersData.config.get(handler); if (!targetUsesKeyEvents || (config === null || config === void 0 ? void 0 : config.ignoreUsedKeyboardEvents) === false) { const shouldPropagate = handler(event); if (shouldPropagate === false) return; } } } } } //# sourceMappingURL=handlers.js.map