diff --git a/packages/core/useMagicKeys/index.test.ts b/packages/core/useMagicKeys/index.test.ts index 642b1a956a3..bd7939800c5 100644 --- a/packages/core/useMagicKeys/index.test.ts +++ b/packages/core/useMagicKeys/index.test.ts @@ -63,4 +63,48 @@ describe('useMagicKeys', () => { })) expect(Ctrl_Shift_Period.value).toBe(true) }) + it('macos command key combinations with double keydown', async () => { + // #3026: doesn't trigger on releasing and pressing again the second key in a combination + const { Meta_Z } = useMagicKeys({ target }) + expect(Meta_Z.value).toBe(false) + + // Simulate first keydown of command key + target.dispatchEvent(new KeyboardEvent('keydown', { + key: 'Meta', + metaKey: true, + })) + expect(Meta_Z.value).toBe(false) + + // Simulate first keydown of Z + target.dispatchEvent(new KeyboardEvent('keydown', { + key: 'z', + metaKey: true, + })) + expect(Meta_Z.value).toBe(true) // true because of the first keydown of command key + z + await new Promise(resolve => setTimeout(resolve, 1)) + expect(Meta_Z.value).toBe(true) // after 1ms, it's still true + + // Simulate second keydown of Z without keyup (macOS behavior) + target.dispatchEvent(new KeyboardEvent('keydown', { + key: 'z', + metaKey: true, + })) + expect(Meta_Z.value).toBe(false) // false because it has to manually trigger keyup event + await new Promise(resolve => setTimeout(resolve, 1)) + expect(Meta_Z.value).toBe(true) // after 1ms, it's true again + + // Simulate keyup of Z + target.dispatchEvent(new KeyboardEvent('keyup', { + key: 'z', + metaKey: true, + })) + expect(Meta_Z.value).toBe(false) + + // Simulate keyup of command key + target.dispatchEvent(new KeyboardEvent('keyup', { + key: 'Meta', + metaKey: false, + })) + expect(Meta_Z.value).toBe(false) + }) }) diff --git a/packages/core/useMagicKeys/index.ts b/packages/core/useMagicKeys/index.ts index f4245cce80d..5890f08e5e8 100644 --- a/packages/core/useMagicKeys/index.ts +++ b/packages/core/useMagicKeys/index.ts @@ -1,6 +1,6 @@ import type { ComputedRef, MaybeRefOrGetter } from 'vue' import { noop } from '@vueuse/shared' -import { computed, reactive, shallowRef, toValue } from 'vue' +import { computed, nextTick, reactive, shallowRef, toValue } from 'vue' import { defaultWindow } from '../_configurable' import { useEventListener } from '../useEventListener' import { DefaultMagicKeysAliasMap } from './aliasMap' @@ -137,7 +137,18 @@ export function useMagicKeys(options: UseMagicKeysOptions = {}): any { } useEventListener(target, 'keydown', (e: KeyboardEvent) => { - updateRefs(e, true) + const key = e.key?.toLowerCase() + // #3026: doesn't trigger on releasing and pressing again the second key in a combination + // Solution: Trigger "keyup" event manually when "keydown" event is fired without "keyup" + if (key && e.getModifierState('Meta') && current.has(key)) { + updateRefs(e, false) + nextTick(() => { + updateRefs(e, true) + }) + } + else { + updateRefs(e, true) + } return onEventFired(e) }, { passive }) useEventListener(target, 'keyup', (e: KeyboardEvent) => {