Skip to content

Commit

Permalink
feat(useMagicKeys): new options and APIs, close #429
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 8, 2021
1 parent db65464 commit ed5d0cc
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 14 deletions.
28 changes: 27 additions & 1 deletion packages/core/useMagicKeys/demo.vue
@@ -1,8 +1,21 @@
<script setup lang="ts">
import Key from './Key.vue'
import { useMagicKeys } from '.'
import { computed } from 'vue-demi'
import { whenever } from '..'
const { shift, v, u, e, s, v_u_e, u_s_e } = useMagicKeys()
const { shift, v, u, e, s, v_u_e, u_s_e, current } = useMagicKeys()
const keys = computed(() => Array.from(current))
const { option_s } = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.key === 's' && e.metaKey && e.type === 'keydown')
e.preventDefault()
},
})
whenever(option_s, () => console.log('Ctrl+S have been pressed'))
</script>

<template>
Expand Down Expand Up @@ -50,6 +63,19 @@ const { shift, v, u, e, s, v_u_e, u_s_e } = useMagicKeys()
Use
</Key>
</div>

<div class="text-center mt-4">
<Note>Keys Pressed</Note>
<div class="flex mt-2 justify-center space-x-1 min-h-1.5em">
<code
v-for="key in keys"
:key="key"
class="font-mono"
>
{{ key }}
</code>
</div>
</div>
</div>

<img
Expand Down
106 changes: 101 additions & 5 deletions packages/core/useMagicKeys/index.md
Expand Up @@ -26,9 +26,11 @@ watchEffect(() => {
})
```

Check out [all the possible keycodes](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode).

### Combinations

You can magically use combinations (shortcuts/hotkeys) by connecting keys with `+` or `_`
You can magically use combinations (shortcuts/hotkeys) by connecting keys with `+` or `_`.

```ts
import { useMagicKeys } from '@vueuse/core'
Expand All @@ -45,7 +47,7 @@ watch(shiftCtrlA, (v) => {
```ts
import { useMagicKeys } from '@vueuse/core'

const { Ctrl_A_B, space, ctrl_s, /* ... */ } = useMagicKeys()
const { Ctrl_A_B, space, alt_s, /* ... */ } = useMagicKeys()

watch(Ctrl_A_B, (v) => {
if (v)
Expand All @@ -65,9 +67,63 @@ whenever(keys.shift_space, () => {
})
```

### Current Pressed keys

A special property `current` is provided to representing all the keys been pressed currently.

```ts
import { useMagicKeys } from '@vueuse/core'

const { current } = useMagicKeys()

console.log(current) // Set { 'control', 'a' }

whenever(
() => current.has('a') && !current.has('b'),
() => console.log('A is pressed but not B')
)
```

### Key Alias

```ts
import { useMagicKeys, whenever } from '@vueuse/core'

const { shift_cool } = useMagicKeys({
alias: {
cool: 'space'
}
})

whenever(cool, () => console.log('Shift + Space have been pressed'))
```

By default, we have some preconfigured alias for common practices.

For example: `ctrl` -> `control` and `option` -> `meta`.

### Custom Event Handler

```ts
import { useMagicKeys, whenever } from '@vueuse/core'

const { ctrl_s } = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.ctrlKey && e.key === 's' && e.type === 'keydown') {
e.preventDefault()
}
}
})

whenever(ctrl_s, () => console.log('Ctrl+S have been pressed'))
```

> ⚠️ This usage is NOT recommended, please use with caution.
### Reactive Mode

By default, the values of `useMagicKeys()` are `Ref<boolean>`. If you want to use the object in template, you can set it to reactive mode.
By default, the values of `useMagicKeys()` are `Ref<boolean>`. If you want to use the object in the template, you can set it to reactive mode.

```ts
const keys = useMagicKeys({ reactive: true })
Expand Down Expand Up @@ -98,18 +154,58 @@ export interface UseMagicKeysOptions<Reactive extends Boolean> {
* @default window
*/
target?: MaybeRef<EventTarget>
/**
* Alias map for keys, all the keys should be lowercase
* { target: keycode }
*
* @example { ctrl: "control" }
* @default <predefined-map>
*/
aliasMap?: Record<string, string>
/**
* Register passive listener
*
* @default true
*/
passive?: boolean
/**
* Custom event handler for keydown/keyup event.
* Useful when you want to apply custom logic.
*
* When using `e.preventDefault()`, you will need to pass `passive: false` to useMagicKeys().
*/
onEventFired?: (e: KeyboardEvent) => void | boolean
}
export declare const DefaultMagicKeysAliasMap: Readonly<Record<string, string>>
export interface MagicKeysInternal {
/**
* A Set of currently pressed keys,
* Stores raw keyCodes.
*
* @link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
*/
current: Set<string>
}
export declare type MagicKeys<Reactive extends Boolean> = Readonly<
Omit<
Reactive extends true
? Record<string, boolean>
: Record<string, ComputedRef<boolean>>,
keyof MagicKeysInternal
> &
MagicKeysInternal
>
/**
* Reactive keys pressed state, with magical keys combination support.
*
* @link https://vueuse.org/useMagicKeys
*/
export declare function useMagicKeys(
options?: UseMagicKeysOptions<false>
): Readonly<Record<string, ComputedRef<boolean>>>
): MagicKeys<false>
export declare function useMagicKeys(
options: UseMagicKeysOptions<true>
): Readonly<Record<string, boolean>>
): MagicKeys<true>
```

## Source
Expand Down
85 changes: 78 additions & 7 deletions packages/core/useMagicKeys/index.ts
@@ -1,5 +1,5 @@
import { computed, ComputedRef, reactive, ref, unref } from 'vue-demi'
import { MaybeRef } from '@vueuse/shared'
import { MaybeRef, noop } from '@vueuse/shared'
import { useEventListener } from '../useEventListener'
import { defaultWindow } from '../_configurable'

Expand All @@ -17,25 +17,86 @@ export interface UseMagicKeysOptions<Reactive extends Boolean> {
* @default window
*/
target?: MaybeRef<EventTarget>

/**
* Alias map for keys, all the keys should be lowercase
* { target: keycode }
*
* @example { ctrl: "control" }
* @default <predefined-map>
*/
aliasMap?: Record<string, string>

/**
* Register passive listener
*
* @default true
*/
passive?: boolean

/**
* Custom event handler for keydown/keyup event.
* Useful when you want to apply custom logic.
*
* When using `e.preventDefault()`, you will need to pass `passive: false` to useMagicKeys().
*/
onEventFired?: (e: KeyboardEvent) => void | boolean
}

export const DefaultMagicKeysAliasMap: Readonly<Record<string, string>> = {
ctrl: 'control',
option: 'meta',
}

export interface MagicKeysInternal {
/**
* A Set of currently pressed keys,
* Stores raw keyCodes.
*
* @link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
*/
current: Set<string>
}

export type MagicKeys<Reactive extends Boolean> =
Readonly<
Omit<Reactive extends true
? Record<string, boolean>
: Record<string, ComputedRef<boolean>>,
keyof MagicKeysInternal>
& MagicKeysInternal
>

/**
* Reactive keys pressed state, with magical keys combination support.
*
* @link https://vueuse.org/useMagicKeys
*/
export function useMagicKeys(options?: UseMagicKeysOptions<false>): Readonly<Record<string, ComputedRef<boolean>>>
export function useMagicKeys(options: UseMagicKeysOptions<true>): Readonly<Record<string, boolean>>
export function useMagicKeys(options?: UseMagicKeysOptions<false>): MagicKeys<false>
export function useMagicKeys(options: UseMagicKeysOptions<true>): MagicKeys<true>
export function useMagicKeys(options: UseMagicKeysOptions<boolean> = {}): any {
const {
reactive: useReactive = false,
target = defaultWindow,
aliasMap = DefaultMagicKeysAliasMap,
passive = true,
onEventFired = noop,
} = options
const obj = { toJSON() { return {} } }
const current = reactive(new Set<string>())
const obj = { toJSON() { return {} }, current }
const refs: Record<string, any> = useReactive ? reactive(obj) : obj

function updateRefs(e: KeyboardEvent, value: boolean) {
const values = [e.code.toLowerCase(), e.key.toLowerCase()]
const key = e.key.toLowerCase()
const code = e.code.toLowerCase()
const values = [code, key]

// current set
if (value)
current.add(e.code)
else
current.delete(e.code)

for (const key of values) {
if (key in refs) {
if (useReactive)
Expand All @@ -47,8 +108,14 @@ export function useMagicKeys(options: UseMagicKeysOptions<boolean> = {}): any {
}

if (target) {
useEventListener(target, 'keydown', (e: KeyboardEvent) => updateRefs(e, true), { passive: true })
useEventListener(target, 'keyup', (e: KeyboardEvent) => updateRefs(e, false), { passive: true })
useEventListener(target, 'keydown', (e: KeyboardEvent) => {
updateRefs(e, true)
return onEventFired(e)
}, { passive })
useEventListener(target, 'keyup', (e: KeyboardEvent) => {
updateRefs(e, false)
return onEventFired(e)
}, { passive })
}

const proxy = new Proxy(
Expand All @@ -59,6 +126,10 @@ export function useMagicKeys(options: UseMagicKeysOptions<boolean> = {}): any {
return Reflect.get(target, prop, rec)

prop = prop.toLowerCase()
// alias
if (prop in aliasMap)
prop = aliasMap[prop]
// create new tracking
if (!(prop in refs)) {
if (/[+_-]/.test(prop)) {
const keys = prop.split(/[+_-]/g).map(i => i.trim())
Expand Down
5 changes: 4 additions & 1 deletion packages/shared/useDebounce/index.md
Expand Up @@ -38,7 +38,10 @@ console.log(debounced.value) // 'bar'
## Type Declarations

```typescript
export declare function useDebounce<T>(value: Ref<T>, ms?: number): Ref<T>
export declare function useDebounce<T>(
value: Ref<T>,
ms?: number
): Readonly<Ref<T>>
```

## Source
Expand Down

0 comments on commit ed5d0cc

Please sign in to comment.