w3c / uievents

UI Events
https://w3c.github.io/uievents/
Other
145 stars 52 forks source link

Proposal: Way to determine if any modifier key is pressed #151

Open keithamus opened 7 years ago

keithamus commented 7 years ago

(From whatwg/html#2856)

Use Case

Often times, when adding keyboard event listeners, one would like to determine if a modifier key is pressed (one of ctrlKey, metaKey, altKey, shiftKey) to create keyboard shortcuts in a webapp type scenario. The inherent problem with checking for truthiness of these values is that this becomes "greedy" - one also needs to check the falseness of other keys. As such it becomes quite a lot of code to determine if only a single modifier is pressed:

// Pressing `j` scrolls to next item, ctrl+j scrolls down 2 items
document.addEventListener('keypress', event => {
  if (event.key === 'j' && !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey)) {
    scrollDownOneItem()
  }
  if (event.key === 'j' && event.ctrlKey && !(event.altKey || event.metaKey || event.shiftKey)) {
    scrollDownTwoItems()
  }
})

Reasoning

This is problematic because it is an easy thing to miss while developing, and it is a fair amount of boilerplate code to check a handful of properties to ensure they are false.

Requirements

So to solve this, I believe the solution should keep to these requirements:

jonathantneal commented 1 year ago

I would love to see this addressed in the platform.

Currently, many libraries like CodeMirror must rely on platform detection. https://github.com/codemirror/codemirror5/blob/5.65.9/src/edit/key_events.js#L120

In my own experience, I have noticed smaller libraries appear often unaware of this interop issue and either listen to one modifier or the other. When the issue is brought to their attention by users of apple or non-apple devices, authors quickly patch the issue by listening to both meta and control keys simultaneously. This unfortunately locks users into there only ever being two possible answers on two possible platforms (apple and non-apple).

MDN docs mention navigation.platform as the “least-bad” option “when you need to show users advice about whether the modifier key for keyboard shortcuts is the ⌘ command key (found on Apple systems) rather than the ⌃ control key (on non-Apple systems).”

The accepted StackOverflow answer on this subject begins “Unfortunately at this time there is no JavaScript API for detecting if the host OS uses the Ctrl key or the Cmd key for keyboard shortcuts.

I would suggest adding a new boolean key to KeyboardEvent; modifierKey. It would, effectively, map to the following speculative polyfill:

const isApplePlatform = navigator.platform === 'MacIntel' || navigator.platform === 'iPhone'
const modifierKey = isApplePlatform ? 'metaKey' : 'ctrlKey'
const modifierKeyDescriptor = Object.getOwnPropertyDescriptor(KeyboardEvent.prototype, modifierKey)

Object.defineProperty(KeyboardEvent.prototype, 'modifierKey', modifierKeyDescriptor)
masayuki-nakano commented 1 year ago

@jonathantneal Sounds like it's a different issue. And there was Accel pseudo-key name to check it in the old versions of UI Events, but it was deleted (https://github.com/w3c/uievents/issues/28#issuecomment-192809289). Although Gecko still supports it.

jonathantneal commented 1 year ago

@masayuki-nakano, thank you for the reply.

Sounds like it's a different issue.

Should I open a new issue? Is this issue for detecting the OS-specific modifier (or primary accelerator key?).

When I first looked at this issue, I thought it might be different when read “the solution should be able to determine if one or more modifier keys are truthy” as that seems ‘greedy’, but then it is followed by, “while ensuring rest of the available modifier keys are falsey” which makes me think it is the same as my need.

Which would be best for the working group? This issue? A new issue? ... no issue? 😅

it was deleted

Thank you for that link. I see that they mention that we should open new bugs to track any further issues we encounter. Would my issue and the prior art that I’ve shared qualify?

Although Gecko still supports it.

Upon a brief cross browser testing with the following code, it seems that Firefox and all Chromium browsers support event.getModifierState('Accel'). It turned out that the only browser which did not support this was Safari.

<pre id="log"></pre>
<script>
window.addEventListener('keydown', event => {
    event.preventDefault()

    log.textContent = JSON.stringify({
        key: event.key,
        Accel: event.getModifierState('Accel'),
        Meta: event.getModifierState('Meta'),
        Control: event.getModifierState('Control'),
        Option: event.getModifierState('Option'),
        Alt: event.getModifierState('Alt'),
    }, null, '  ')
})
</script>
masayuki-nakano commented 1 year ago

@jonathantneal In my understanding, this issue requests an API to check multiple modifier stats with a call for making modifier check code simpler.

I think that you should file a new issue in "uievents". Handling only one thing in an issue makes everybody manage issues easier. And I've not known that Accel was supported by Chrome. It sounds like that it's reasonable to resurrect the pseudo modifier.

laughinghan commented 4 months ago

For a real-world example of why the current boolean flag-based API leads to bugs, see: https://medium.engineering/the-curious-case-of-disappearing-polish-s-fa398313d4df

I think the solution is as easy as adding an API to spit out all the modifiers in one string like Java's getKeyModifiersText() which gives eg Ctrl+Shift; any modifiers not included in the string are false. This isn't difficult to do in userland but the API currently affords incorrect usage, and it'd be better to encourage correct usage instead.

I agree that @jonathantneal is talking about something totally different, which is about somehow helping with Ctrl- and Meta- modifiers having different meanings on Mac than they do on Win/Linux; I'm not sure that's something the browser should help with at all. Java, for example, doesn't seem to help with that all, presenting basically the same API as the browser: https://docs.oracle.com/javase/6/docs/api/java/awt/event/InputEvent.html#method_summary