Skip to content

Commit

Permalink
feat(defineShortcuts): chained shortcuts + docs update (#282)
Browse files Browse the repository at this point in the history
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
  • Loading branch information
smarroufin and benjamincanac committed Jun 21, 2023
1 parent dfccbcf commit a67f691
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 9 deletions.
15 changes: 15 additions & 0 deletions docs/content/1.getting-started/4.shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ defineShortcuts({
</script>
```

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`
Expand Down
70 changes: 61 additions & 9 deletions src/runtime/composables/defineShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ export interface ShortcutsConfig {
[key: string]: ShortcutConfig | Function
}

export interface ShortcutsOptions {
chainDelay?: number
}

interface Shortcut {
handler: Function
condition: ComputedRef<Boolean>
chained: boolean
// KeyboardEvent attributes
key: string
ctrlKey: boolean
Expand All @@ -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 }
Expand All @@ -52,8 +82,11 @@ export const defineShortcuts = (config: ShortcutsConfig) => {
e.preventDefault()
shortcut.handler()
}
clearChainedInput()
return
}

debouncedClearChainedInput()
}

// Map config to full detailled shortcuts
Expand All @@ -63,14 +96,33 @@ export const defineShortcuts = (config: ShortcutsConfig) => {
}

// Parse key and modifiers
const keySplit = key.toLowerCase().split('_').map(k => k)
let shortcut: Partial<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')
let shortcut: Partial<Shortcut>

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) {
Expand Down

0 comments on commit a67f691

Please sign in to comment.