From a67f691a0066e4d017f580388df31b22d1c45372 Mon Sep 17 00:00:00 2001 From: Sylvain Marroufin Date: Thu, 15 Jun 2023 11:39:03 +0200 Subject: [PATCH] feat(defineShortcuts): chained shortcuts + docs update (#282) Co-authored-by: Benjamin Canac --- docs/content/1.getting-started/4.shortcuts.md | 15 ++++ src/runtime/composables/defineShortcuts.ts | 70 ++++++++++++++++--- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/docs/content/1.getting-started/4.shortcuts.md b/docs/content/1.getting-started/4.shortcuts.md index 7f326541f2..2982cce3c5 100644 --- a/docs/content/1.getting-started/4.shortcuts.md +++ b/docs/content/1.getting-started/4.shortcuts.md @@ -48,6 +48,21 @@ defineShortcuts({ ``` +Shortcuts keys are written as the literal keyboard key value. Combinations are made with `_` separator. Chained shortcuts are made with `-` separator. + +Modifiers are also available: +- `meta`: acts as `Command` for MacOS and `Control` for others +- `ctrl`: acts as `Control` +- `shift`: acts as `Shift` and is only necessary for alphabetic keys + +Examples of keys: +- `escape`: will trigger by hitting `Esc` +- `meta_k`: will trigger by hitting `⌘` and `K` at the same time on MacOS, and `Ctrl` and `K` on Windows and Linux +- `ctrl_k`: will trigger by hitting `Ctrl` and `K` at the same time on MacOS, Windows and Linux +- `shift_e`: will trigger by hitting `Shift` and `E` at the same time on MacOS, Windows and Linux +- `?`: will trigger by hitting `?` on some keyboard layouts, or for example `Shift` and `/`, which results in `?` on US Mac keyboards +- `g-d`: will trigger by hitting `g` then `d` with a maximum delay of 800ms by default + ### `usingInput` Prop: `usingInput?: string | boolean` diff --git a/src/runtime/composables/defineShortcuts.ts b/src/runtime/composables/defineShortcuts.ts index b4be4fc91b..d07538c1bb 100644 --- a/src/runtime/composables/defineShortcuts.ts +++ b/src/runtime/composables/defineShortcuts.ts @@ -14,9 +14,14 @@ export interface ShortcutsConfig { [key: string]: ShortcutConfig | Function } +export interface ShortcutsOptions { + chainDelay?: number +} + interface Shortcut { handler: Function condition: ComputedRef + chained: boolean // KeyboardEvent attributes key: string ctrlKey: boolean @@ -27,18 +32,43 @@ interface Shortcut { // keyCode?: number } -export const defineShortcuts = (config: ShortcutsConfig) => { +export const defineShortcuts = (config: ShortcutsConfig, options: ShortcutsOptions = {}) => { const { macOS, usingInput } = useShortcuts() let shortcuts: Shortcut[] = [] + const chainedInputs = ref([]) + const clearChainedInput = () => { + chainedInputs.value.splice(0, chainedInputs.value.length) + } + const debouncedClearChainedInput = useDebounceFn(clearChainedInput, options.chainDelay ?? 800) + const onKeyDown = (e: KeyboardEvent) => { // Input autocomplete triggers a keydown event if (!e.key) { return } const alphabeticalKey = /^[a-z]{1}$/i.test(e.key) - for (const shortcut of shortcuts) { + let chainedKey + chainedInputs.value.push(e.key) + // try matching a chained shortcut + if (chainedInputs.value.length >= 2) { + chainedKey = chainedInputs.value.slice(-2).join('-') + + for (const shortcut of shortcuts.filter(s => s.chained)) { + if (shortcut.key !== chainedKey) { continue } + + if (shortcut.condition.value) { + e.preventDefault() + shortcut.handler() + } + clearChainedInput() + return + } + } + + // try matching a standard shortcut + for (const shortcut of shortcuts.filter(s => !s.chained)) { if (e.key.toLowerCase() !== shortcut.key) { continue } if (e.metaKey !== shortcut.metaKey) { continue } if (e.ctrlKey !== shortcut.ctrlKey) { continue } @@ -52,8 +82,11 @@ export const defineShortcuts = (config: ShortcutsConfig) => { e.preventDefault() shortcut.handler() } + clearChainedInput() return } + + debouncedClearChainedInput() } // Map config to full detailled shortcuts @@ -63,14 +96,33 @@ export const defineShortcuts = (config: ShortcutsConfig) => { } // Parse key and modifiers - const keySplit = key.toLowerCase().split('_').map(k => k) - let shortcut: Partial = { - key: keySplit.filter(k => !['meta', 'ctrl', 'shift', 'alt'].includes(k)).join('_'), - metaKey: keySplit.includes('meta'), - ctrlKey: keySplit.includes('ctrl'), - shiftKey: keySplit.includes('shift'), - altKey: keySplit.includes('alt') + let shortcut: Partial + + if (key.includes('-') && key.includes('_')) { + console.trace('[Shortcut] Invalid key') + return null + } + + const chained = key.includes('-') + if (chained) { + shortcut = { + key: key.toLowerCase(), + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: false + } + } else { + const keySplit = key.toLowerCase().split('_').map(k => k) + shortcut = { + key: keySplit.filter(k => !['meta', 'ctrl', 'shift', 'alt'].includes(k)).join('_'), + metaKey: keySplit.includes('meta'), + ctrlKey: keySplit.includes('ctrl'), + shiftKey: keySplit.includes('shift'), + altKey: keySplit.includes('alt') + } } + shortcut.chained = chained // Convert Meta to Ctrl for non-MacOS if (!macOS.value && shortcut.metaKey && !shortcut.ctrlKey) {