Skip to content

⬅️ Back to Table of Contents

📄 useMagicKeys

📊 Analysis Summary

Metric Count
🔧 Functions 6
📦 Imports 10
🔄 Re-exports 1
🟢 Vue Composition API 3
📐 Interfaces 2
📑 Type Aliases 1

📚 Table of Contents

🛠️ File Location:

📂 packages/core/useMagicKeys/index.ts

📦 Imports

Name Source
ComputedRef vue
MaybeRefOrGetter vue
noop @vueuse/shared
computed vue
reactive vue
shallowRef vue
toValue vue
defaultWindow ../_configurable
useEventListener ../useEventListener
DefaultMagicKeysAliasMap ./aliasMap

Re-exports

Type Source Exported Names
named ./aliasMap DefaultMagicKeysAliasMap

Vue Composition API

Name Type Reactive Variables Composables
reactive reactive none none
reactive reactive none none
computed computed none none

Functions

useMagicKeys(options: UseMagicKeysOptions<T>): UseMagicKeysReturn<T>

Reactive keys pressed state, with magical keys combination support.

See: https://vueuse.org/useMagicKeys

Raw JSDoc
/**
 * Reactive keys pressed state, with magical keys combination support.
 *
 * @see https://vueuse.org/useMagicKeys
 */

Calls:

  • reactive (from vue)
  • current.clear
  • setRefs
  • e.getModifierState
  • keys.forEach
  • depsSet.add
  • key[0].toUpperCase
  • key.slice
  • depsMap.get
  • ['shift', 'alt'].includes
  • Array.from
  • depsArray.indexOf
  • depsArray.forEach
  • current.delete
  • deps.clear
  • e.key?.toLowerCase
  • e.code?.toLowerCase
  • [code, key].filter
  • current.add
  • usedKeys.add
  • updateDeps
  • clearDeps
  • metaDeps.forEach
  • metaDeps.clear
  • useEventListener (from ../useEventListener)
  • updateRefs
  • onEventFired
  • Reflect.get
  • prop.toLowerCase
  • /[+_-]/.test
  • prop.split(/[+_-]/g).map
  • i.trim
  • computed (from vue)
  • keys.map(key => toValue(proxy[key])).every
  • shallowRef (from vue)
  • toValue (from vue)

Internal Comments:

// current set
// #1312
// In macOS, keys won't trigger "keyup" event when Meta key is released
// We track it's combination and release manually
// Meta key released (x4)
// #1350 (x3)
// alias
// create new tracking

Code
export function useMagicKeys<T extends boolean = false>(options: UseMagicKeysOptions<T> = {}): UseMagicKeysReturn<T> {
  const {
    reactive: useReactive = false,
    target = defaultWindow,
    aliasMap = DefaultMagicKeysAliasMap,
    passive = true,
    onEventFired = noop,
  } = options
  const current = reactive(new Set<string>())
  const obj = {
    toJSON() { return {} },
    current,
  }
  const refs: Record<string, any> = useReactive ? reactive(obj) : obj
  const metaDeps = new Set<string>()
  const depsMap = new Map<string, Set<string>>([
    ['Meta', metaDeps],
    ['Shift', new Set<string>()],
    ['Alt', new Set<string>()],
  ])
  const usedKeys = new Set<string>()

  function setRefs(key: string, value: boolean) {
    if (key in refs) {
      if (useReactive)
        refs[key] = value
      else
        refs[key].value = value
    }
  }

  function reset() {
    current.clear()
    for (const key of usedKeys)
      setRefs(key, false)
  }

  function updateDeps(value: boolean, e: KeyboardEvent, keys: string[]) {
    if (!value || typeof e.getModifierState !== 'function')
      return
    for (const [modifier, depsSet] of depsMap) {
      if (e.getModifierState(modifier)) {
        keys.forEach(key => depsSet.add(key))
        break
      }
    }
  }

  function clearDeps(value: boolean, key: string) {
    if (value)
      return
    const depsMapKey = `${key[0].toUpperCase()}${key.slice(1)}`
    const deps = depsMap.get(depsMapKey)
    if (!(['shift', 'alt'].includes(key)) || !deps)
      return

    const depsArray = Array.from(deps)
    const depsIndex = depsArray.indexOf(key)
    depsArray.forEach((key, index) => {
      if (index >= depsIndex) {
        current.delete(key)
        setRefs(key, false)
      }
    })
    deps.clear()
  }

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

    if (!key)
      return

    // current set
    if (key) {
      if (value)
        current.add(key)
      else
        current.delete(key)
    }

    for (const key of values) {
      usedKeys.add(key)
      setRefs(key, value)
    }

    updateDeps(value, e, [...current, ...values])
    clearDeps(value, key)

    // #1312
    // In macOS, keys won't trigger "keyup" event when Meta key is released
    // We track it's combination and release manually
    if (key === 'meta' && !value) {
      // Meta key released
      metaDeps.forEach((key) => {
        current.delete(key)
        setRefs(key, false)
      })
      metaDeps.clear()
    }
  }

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

  // #1350
  useEventListener('blur', reset, { passive })
  useEventListener('focus', reset, { passive })

  const proxy = new Proxy(
    refs,
    {
      get(target, prop, rec) {
        if (typeof prop !== 'string')
          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())
            refs[prop] = computed(() => keys.map(key => toValue(proxy[key])).every(Boolean))
          }
          else {
            refs[prop] = shallowRef(false)
          }
        }
        const r = Reflect.get(target, prop, rec)
        return useReactive ? toValue(r) : r
      },
    },
  )

  return proxy as UseMagicKeysReturn<T>
}

Internal helpers

Declared inside another function in this file.

setRefs(key: string, value: boolean): void

Parameters:

  • key string
  • value boolean

Returns: void

Code
function setRefs(key: string, value: boolean) {
    if (key in refs) {
      if (useReactive)
        refs[key] = value
      else
        refs[key].value = value
    }
  }

reset(): void

Returns: void

Calls:

  • current.clear
  • setRefs
Code
function reset() {
    current.clear()
    for (const key of usedKeys)
      setRefs(key, false)
  }

updateDeps(value: boolean, e: KeyboardEvent, keys: string[]): void

Parameters:

  • value boolean
  • e KeyboardEvent
  • keys string[]

Returns: void

Calls:

  • e.getModifierState
  • keys.forEach
  • depsSet.add
Code
function updateDeps(value: boolean, e: KeyboardEvent, keys: string[]) {
    if (!value || typeof e.getModifierState !== 'function')
      return
    for (const [modifier, depsSet] of depsMap) {
      if (e.getModifierState(modifier)) {
        keys.forEach(key => depsSet.add(key))
        break
      }
    }
  }

clearDeps(value: boolean, key: string): void

Parameters:

  • value boolean
  • key string

Returns: void

Calls:

  • key[0].toUpperCase
  • key.slice
  • depsMap.get
  • ['shift', 'alt'].includes
  • Array.from
  • depsArray.indexOf
  • depsArray.forEach
  • current.delete
  • setRefs
  • deps.clear
Code
function clearDeps(value: boolean, key: string) {
    if (value)
      return
    const depsMapKey = `${key[0].toUpperCase()}${key.slice(1)}`
    const deps = depsMap.get(depsMapKey)
    if (!(['shift', 'alt'].includes(key)) || !deps)
      return

    const depsArray = Array.from(deps)
    const depsIndex = depsArray.indexOf(key)
    depsArray.forEach((key, index) => {
      if (index >= depsIndex) {
        current.delete(key)
        setRefs(key, false)
      }
    })
    deps.clear()
  }

updateRefs(e: KeyboardEvent, value: boolean): void

Parameters:

  • e KeyboardEvent
  • value boolean

Returns: void

Calls:

  • e.key?.toLowerCase
  • e.code?.toLowerCase
  • [code, key].filter
  • current.add
  • current.delete
  • usedKeys.add
  • setRefs
  • updateDeps
  • clearDeps
  • metaDeps.forEach
  • metaDeps.clear

Internal Comments:

// current set
// #1312
// In macOS, keys won't trigger "keyup" event when Meta key is released
// We track it's combination and release manually
// Meta key released (x4)

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

    if (!key)
      return

    // current set
    if (key) {
      if (value)
        current.add(key)
      else
        current.delete(key)
    }

    for (const key of values) {
      usedKeys.add(key)
      setRefs(key, value)
    }

    updateDeps(value, e, [...current, ...values])
    clearDeps(value, key)

    // #1312
    // In macOS, keys won't trigger "keyup" event when Meta key is released
    // We track it's combination and release manually
    if (key === 'meta' && !value) {
      // Meta key released
      metaDeps.forEach((key) => {
        current.delete(key)
        setRefs(key, false)
      })
      metaDeps.clear()
    }
  }

Interfaces

UseMagicKeysOptions<Reactive extends boolean>

Interface Code
export interface UseMagicKeysOptions<Reactive extends boolean> {
  /**
   * Returns a reactive object instead of an object of refs
   *
   * @default false
   */
  reactive?: Reactive

  /**
   * Target for listening events
   *
   * @default window
   */
  target?: MaybeRefOrGetter<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
}

Properties

Name Type Optional Description
reactive Reactive not shown
target MaybeRefOrGetter<EventTarget> not shown
aliasMap Record<string, string> not shown
passive boolean not shown
onEventFired (e: KeyboardEvent) => void \| boolean not shown

MagicKeysInternal

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

Properties

Name Type Optional Description
current Set<string> not shown

Type Aliases

UseMagicKeysReturn<Reactive extends boolean>

type UseMagicKeysReturn<Reactive extends boolean> = Readonly<
    Record<
      string,
      Reactive extends true ? boolean : ComputedRef<boolean>
    > & MagicKeysInternal
  >;

Generated by Syntax Scribe