8000 feat(useKeybinds): add useKeybinds by veselj43 · Pull Request #4756 · vueuse/vueuse · GitHub
[go: up one dir, main page]

Skip to content

feat(useKeybinds): add useKeybinds #4756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export * from './useImage'
export * from './useInfiniteScroll'
export * from './useIntersectionObserver'
export * from './useKeyModifier'
export * from './useKeybinds'
export * from './useLocalStorage'
export * from './useMagicKeys'
export * from './useManualRefHistory'
Expand Down
89 changes: 89 additions & 0 deletions packages/core/useKeybinds/demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<script setup lang="ts">
import { useKeybinds } from '@vueuse/core'
import { onMounted, shallowReactive, useTemplateRef } from 'vue'

const state = shallowReactive({
action1: false,
action2: false,
action3: false,
})

const inputRef = useTemplateRef('input')

function reset() {
state.action1 = false
state.action2 = false
state.action3 = false

if (inputRef.value) {
inputRef.value.value = ''
}
}

onMounted(() => {
useKeybinds({
'KeyA-KeyS': () => {
state.action1 = true
},
'KeyA-KeyD': () => {
state.action2 = true
},
'alt_KeyA-alt_KeyF': () => {
state.action3 = true
},
'KeyT': ({ lastEvent }) => {
lastEvent.preventDefault()
inputRef.value?.focus()
},
})
})
</script>

<template>
<div class="flex flex-col gap-4">
<div class="flex flex-col">
<div>
<button type="button" :disabled="state.action1" @click="state.action1 = true">
Action 1 <span class="mx-1"><kbd>a</kbd> <kbd>s</kbd></span> <span v-if="state.action1">&check;</span>
</button>
</div>

<div>
<button type="button" :disabled="state.action2" @click="state.action2 = true">
Action 2 <span class="mx-1"><kbd>a</kbd> <kbd>d</kbd></span> <span v-if="state.action2">&check;</span>
</button>
</div>

<div>
<button type="button" :disabled="state.action3" @click="state.action3 = true">
Action 3 <span class="mx-1"><kbd>Alt/Option + a</kbd> <kbd>Alt/Option + f</kbd></span> <span v-if="state.action3">&check;</span>
</button>
</div>
</div>

<div>
<label for="use-keybinds-demo">Input events are ignored by default</label>
<input id="use-keybinds-demo" ref="input" type="text" placeholder="Press T to focus">
</div>

<div>
<button @click="reset()">
Reset
</button>
</div>
</div>
</template>

<style scoped>
kbd {
background-color: #000;
border-radius: 4px;
border: 1px #ddd solid;
color: #ddd;
font-family: inherit;
font-size: 12px;
font-weight: 700;
padding: 1px 4px;
text-shadow: none;
}
</style>
145 changes: 145 additions & 0 deletions packages/core/useKeybinds/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
category: Sensors
---

# useKeybinds

Register keybinds/shortcuts.

## Usage

Register keybinds represented by key string and handler.

```ts
import { useKeybinds } from '@vueuse/core'
import { onMounted } from 'vue'

onMounted(() => {
useKeybinds({
KeyG: () => { /* do something */ },
})
})
```

### Modifier keys

Key string supports also modifiers and sequences.

Modifiers are separated by `_` (`alt_KeyG`) and can be combined in alphabetical order (`alt_ctrl_meta_shift_KeyD`).

Note:

- `meta` key is `Command` key for MacOS or `Windows` key for Windows
- `Windows` key is usually not used for keybinds in applications
- `Command` keybinds are usually equal to `Ctrl` on Windows (and other OS)
- Like copy: `Command + c` on MacOS is `Ctrl + c`
- This convention is not specific to system commands and is used in apps as well

For convenience `meta` represents `Command` for MacOS and `Ctrl` for other OS.

Consider using `meta` instead of `ctrl` for more consistent experience across platforms.

Don't use both `ctrl` and `meta` modifiers as it won't work on all platforms.

```ts
import { useKeybinds } from '@vueuse/core'
import { onMounted } from 'vue'

onMounted(() => {
useKeybinds({
KeyG: () => {},
meta_KeyK: () => {},
alt_KeyG: () => {},
alt_shift_KeyA: () => {},
})
})
```

You can also customize this behavior with `normalizeCombinedKeyCode` option.

```ts
import { useKeybinds } from '@vueuse/core'
import { onMounted } from 'vue'

onMounted(() => {
useKeybinds(
{
meta_KeyK: () => {},
},
{
normalizeCombinedKeyCode: combinedKeyCode => combinedKeyCode // exact modifier keys
}
)
})
```

### Key sequences

Sequences are separated by `-` (`KeyM-KeyR`) and can be chained multiple times.

Modifiers and sequences can be also combined.

```ts
import { useKeybinds } from '@vueuse/core'
import { onMounted } from 'vue'

onMounted(() => {
useKeybinds({
'KeyM-KeyR': () => {},
'ctrl_KeyK-ctrl_KeyT': () => {},
})
})
```

### Key codes

Key is represented by key code, not the actual character, so it is easier to define.

If it was by key value, you would have to consider different key values with different modifiers.
For example is `s` with `shift` would become `shift_S` (uppercase) and `.` with `alt` would become `alt_>` (also depending on keyboard layout).

You can customize this behavior by passing options `getKeyCodeWithModifiers` or `getKeyCode`.

```ts
import { useKeybinds } from '@vueuse/core'
import { onMounted } from 'vue'

onMounted(() => {
useKeybinds(
{
'KeyG': () => {},
'alt_KeyG': () => {},
'KeyM-KeyR': () => {},
'ctrl_KeyK-ctrl_KeyT': () => {},
},
{
getKeyCode: e => e.code // default implementation
}
)
})
```

### What events are suitable for keybinds

Not every key event should be considered for keybinds. Events from inputs and content editable elements are excluded by default.

If you need to customize these exceptions you can define which events are included by `includeEvent` option.

```ts
import { useKeybinds } from '@vueuse/core'
import { onMounted } from 'vue'

onMounted(() => {
useKeybinds(
{
KeyG: () => {},
},
{
includeEvent: (e) => {
// not the actual default implementation
return e.target?.tagName !== 'INPUT'
}
}
)
})
```
Loading
0