/** * Listen for keyboard event and trigger `shortcut` {@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent | CustomEvent } * @example Typical usage * * ```svelte * * * * ``` * * * * As with any svelte action, `shortcut` should be use with element and not component. * * ```html * <-- correct usage--> *
* * <-- incorrect usage--> * * ``` * * You can either: * * - pass multiple callbacks to their associated triggers, or * * - pass one single handler to the `on:shortcut` event, in which case you should * provide an ID to each trigger to be able to distinguish what trigger was activated * in the event handler. * * Either way, only use `callback` or `on:shortcut` and not both to * avoid handler duplication. * @param {HTMLElement} node - HTMLElement to add event listener to * @param {import('./public').ShortcutParameter} param - svelte action parameters * @returns {import('./public').ShortcutActionReturn} */ export function shortcut(node, param) { let { enabled = true, trigger, type = 'keydown' } = param; /** * @param {KeyboardEvent} event */ function handler(event) { const normalizedTriggers = Array.isArray(trigger) ? trigger : [trigger]; /** @type {Record} */ const modifiedMap = { alt: event.altKey, ctrl: event.ctrlKey, shift: event.shiftKey, meta: event.metaKey, }; for (const trigger of normalizedTriggers) { const mergedTrigger = { modifier: [], preventDefault: false, enabled: true, ...trigger, }; const { modifier, key, callback, preventDefault, enabled: triggerEnabled } = mergedTrigger; if (triggerEnabled) { if (modifier.length) { const modifierDefs = (Array.isArray(modifier) ? modifier : [modifier]).map((def) => typeof def === 'string' ? [def] : def, ); const modified = modifierDefs.some((def) => def.every((modifier) => modifiedMap[modifier]), ); if (!modified) continue; } if (event.key === key) { if (preventDefault) event.preventDefault(); /** @type {import('./public').ShortcutEventDetail} */ const detail = { node, trigger: mergedTrigger, originalEvent: event, }; node.dispatchEvent(new CustomEvent('shortcut', { detail })); callback?.(detail); } } } } if (enabled) node.addEventListener(type, handler); return { update: (update) => { const { enabled: newEnabled = true, type: newType = 'keydown' } = update; if (enabled && (!newEnabled || type !== newType)) { node.removeEventListener(type, handler); } else if (!enabled && newEnabled) { node.addEventListener(newType, handler); } enabled = newEnabled; type = newType; trigger = update.trigger; }, destroy: () => { node.removeEventListener(type, handler); }, }; }