31

On Mac browsers, javascript does not receive keyup events for most keys (other modifier keys seem to be an exception) when the metakey is down. Use this jsfiddle to demonstrate (focus the result area and try something like cmd + x, the x will not receive a keyup event): http://jsfiddle.net/mUEaV/

I've reproduced this in stable releases for Chrome, FF, Safari and Opera. The same thing does not seem to happen with the control key in Windows 7.

Is the OS hijacking the keyup event? This seems especially strange since commands that use the metakey such as save, find, cut, copy, etcetera all activate on keydown not on keyup, and can be hijacked by the javascript just fine.

valentinas
  • 4,277
  • 1
  • 20
  • 27
anonymouse
  • 468
  • 6
  • 9
  • Sorry to ask a clearly-stupid question, but as a non-Mac user: which one's the meta key? (I have a Mac keyboard, but it's attached to an Ubuntu PC.) – David Thomas Aug 05 '12 at 21:01
  • @DavidThomas I use keyboards the other way around: a windows keyboard with a mac :) For me, the meta key is the windows key, **with the default settings**. (On a mac, you can change that, *to keep things consistent!* :O) – tomsmeding Aug 23 '12 at 13:16
  • 2
    This is expected behavior for OS X: http://stackoverflow.com/questions/4001565/missing-keyup-events-on-meaningful-key-combinations-e-g-select-till-beginning – NateS Mar 08 '13 at 12:38

5 Answers5

6

It's simply not possible to get the onKeyUp events when meta is used, I learned today. Very unfortunate and difficult to work around. You'll have to emulate them some other way.

Edit: To clarify, this is only on Mac and occurs due to OS level handling of the event. It cannot be overridden. Sorry to be the bearer of bad news.

Slbox
  • 10,957
  • 15
  • 54
  • 106
3

Although event.metaKey returns false, event.keyCode and event.key are still populated.

document.addEventListener('keyup', function(e) {
    console.log(e.metaKey || e.key);
});
Click here then press the Command, Control, or Option keys.
AaronJ
  • 1,060
  • 10
  • 24
  • This is not the problem, the problem is that there is no keyup event fired when you let go of the X if you press command (the meta key) first – Jasper Aug 07 '23 at 12:15
2

Is the browser window retaining the focus when you press those keys? In windows you can get similar result when pressing windows+R or CTRL+ESC and similar key combinations that make browser to loose focus and that results in missed events.

valentinas
  • 4,277
  • 1
  • 20
  • 27
  • The browser is retaining focus. There is no visual indication of lost focus and you can continue pressing keys and capturing other key events. I noticed that while testing in Windows as well, and there it makes sense because the Windows key is expected to cause OS specific behavior, and it cannot be hijacked by the browser afaik. – anonymouse Aug 06 '12 at 15:18
1

While keyup events are indeed not available when the meta key is pressed, you can still get keydown events for all keys, as well as keyup events for the meta key itself.

This allows us to just simply keep track of the state of the meta key ourselves, like so:

let metaKeyDown = false;

window.addEventListener("keydown", event => {
    if (event.key == 'Meta') { metaKeyDown = true; }
});

window.addEventListener("keyup", event => {
    if (event.key == 'Meta') { metaKeyDown = false; }
});

By now additionally checking for the main key, plus cancelling the default behavior with Event.preventDefault() we can easily listen for key combinations (like here e.g. CMD+K) and prevent the browser from handling them:

let metaKeyDown = false;

window.addEventListener("keydown", event => {
    if (event.key == 'Meta') { metaKeyDown = true; }

    if (event.key == 'k' && metaKeyDown) {
        event.preventDefault();
        console.log('CMD+K pressed!');
    }
});

window.addEventListener("keyup", event => {
    if (event.key == 'Meta') { metaKeyDown = false; }
});

(Note the observation of the k key taking place already on keydown.)

Also, please be aware that when used incorrectly, this can break standard browser functionality (e.g. like CMD+C or CMD+R), and lead to poor user experience.

max
  • 1,509
  • 1
  • 19
  • 24
0

You can create an artificial keyup event by waiting for a certain period after the last keydown event. The only caveat is people will have different repeat rates on their os. https://jsfiddle.net/u7t43coz/10/

const metaKeyCodes = ["MetaLeft", "MetaRight"];
const shiftKeyCodes = ["ShiftLeft", "ShiftRight"];
const ctrlKeyCodes = ["ControlLeft", "ControlRight"];
const altKeyCodes = ["AltLeft", "AltRight"];
const modifierKeyCodes = [
  ...metaKeyCodes,
  ...shiftKeyCodes,
  ...ctrlKeyCodes,
  ...altKeyCodes
];

// record which keys are down
const downKeys = new Set()
const artificialKeyUpTimes = {}

function onKeydown(e) {
    downKeys.add(e.code);

    // do other keydown stuff here
    console.log("meta", e.metaKey, e.code, "down")

    // check if metaKey is down
    if (metaKeyCodes.some(k => downKeys.has(k))) {
      downKeys.forEach(dk => {
        // we want to exclude modifier keys has they dont repeat
        if (!modifierKeyCodes.includes(dk)) {
          // fire artificial keyup on timeout
          if (!artificialKeyUpTimes[dk])
            setTimeout(
              () => fireArtificialKeyUp(dk, e),
              500
            );
          artificialKeyUpTimes[dk] = Date.now();
        }
      });
    }
  }

 function fireArtificialKeyUp(code, e) {
    // if enough time has passed fire keyup
    if (Date.now() - artificialKeyUpTimes[code] > 100) {
      delete artificialKeyUpTimes[code];
      //if key is still down, fire keyup
      if (downKeys.has(code)) {
        const eCode = isNaN(code) ? { code: code } : { keyCode: code };
        document.dispatchEvent(
          new KeyboardEvent("keyup", { ...e, ...eCode })
        );
      }
    } else {
      setTimeout(() => fireArtificialKeyUp(code, e), 100);
    }
  }

  function onKeyup(e) {
    downKeys.delete(e.code);

    // do keyup stuff here
    console.log("meta", e.metaKey, e.code, "up")
  }

  document.addEventListener("keydown", onKeydown)
  document.addEventListener("keyup", onKeyup)
Ryan King
  • 3,538
  • 12
  • 48
  • 72