@acusti/use-keyboard-events
Version:
React hook that takes keyboard event handlers and attaches them to the document
116 lines • 4.53 kB
JavaScript
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