@selenite/commons
Version:
This typescript package provides a set of frequently used utilities, types and svelte actions for building projects with Typescript and Svelte.
133 lines (132 loc) • 4.51 kB
JavaScript
/**
* Svelte action to add keyboard shortcuts.
*
* @see {@link shortcut}
* @module
*/
import { isArray } from 'lodash-es';
/**
* Returns a listener function that checks if a keyboard shortcut is triggered.
* @param node - node the shortcut is bound to
* @param params - settings of the shortcut
* @returns keyboard listener
*/
function makeShortcutListener(node, params) {
const { shortcuts = [], key, alt, shift, ctrl, action, ignoreElements = ['INPUT', 'TEXTAREA'] } = params;
const shortShortcutDef = key || ctrl || alt || shift
? {
key,
ctrl,
alt,
shift
}
: undefined;
let triggered = false;
return (e) => {
if (params.repeats === false && triggered)
return;
const target = e.target;
if (target instanceof HTMLElement) {
if (ignoreElements.includes(target?.tagName) || target.contentEditable === 'true') {
if (target?.tagName === 'INPUT') {
const input = target;
if (input.type !== 'checkbox' && input.type !== 'radio')
return;
}
else {
return;
}
}
}
let triggeredShortcut = undefined;
if (shortShortcutDef && isShortcutTriggered(e, shortShortcutDef)) {
triggeredShortcut = shortShortcutDef;
}
else if (typeof shortcuts === 'function') {
if (!shortcuts(e))
return;
triggeredShortcut = { key: e.key, ctrl: e.ctrlKey, alt: e.altKey, shift: e.shiftKey };
}
else {
for (const shortcut of isArray(shortcuts) ? shortcuts : [shortcuts]) {
if (isShortcutTriggered(e, shortcut)) {
triggeredShortcut = shortcut;
break;
}
}
}
if (!triggeredShortcut)
return;
// Prevent default behavior if key is defined
// We don't want to prevent default behavior if only
// modifier keys are defined
if (triggeredShortcut.key)
e.preventDefault();
console.debug(`shortcut: ${shortcutToString(triggeredShortcut)}`);
if (action)
action(node, e);
if (triggered === false) {
if (params.endAction) {
function triggerEndAction(e) {
console.debug('Trigger end action.');
params.endAction?.(node, e);
document.removeEventListener('keyup', triggerEndAction);
triggered = false;
}
document.addEventListener('keyup', triggerEndAction);
}
triggered = true;
}
};
}
/**
* Returns whether a shortcut definition has been triggered.
* @param e - keyboard event
* @param shortcut - shortcut definition
* @returns whether shortcut is triggered
*/
function isShortcutTriggered(e, shortcut) {
return ((shortcut.key === undefined ? true : e.key.toLowerCase() === shortcut.key.toLowerCase()) &&
e.ctrlKey === !!shortcut.ctrl &&
e.altKey === !!shortcut.alt &&
e.shiftKey === !!shortcut.shift);
}
/**
* Converts a keyboard shortcut definition to a string.
* @param param0 - Keyboard shortcut definition
* @returns
*/
export function shortcutToString({ ctrl, alt, shift, key }) {
const pieces = [];
if (ctrl)
pieces.push('Ctrl');
if (alt)
pieces.push('Alt');
if (shift)
pieces.push('Shift');
if (key)
pieces.push(key.toUpperCase());
return pieces.join('+');
}
/**
* Action to add a keyboard shortcut.
*
* All keyboard listeners are attached to document.
* @param node - The element the shortcut is bound to
* @param params - Settings for the shortcut
* @returns svelte action to add a keyboard shortcut
*/
export function shortcut(node, params) {
let listener = makeShortcutListener(node, params);
document.addEventListener('keydown', listener, { capture: true });
return {
destroy() {
document.removeEventListener('keydown', listener, { capture: true });
},
update(params) {
document.removeEventListener('keydown', listener, { capture: true });
listener = makeShortcutListener(node, params);
document.addEventListener('keydown', listener, { capture: true });
}
};
}