5

I'm working on keyboard shortcuts for a web application and need to check if a keypress should trigger the shortcut or if it is just the user typing and should therefore not trigger the shortcut.

For example, a common pattern is to use the / or s keys to open the global search bar. Obviously this should not open the search bar if the user is typing into another input.

The ideal logic would go something like this: On keypress, check the currently focused element. If the element accepts keyboard input (can be typed into), then do nothing. If the element does not accept keyboard input, run the shortcut.

Note that checking for focusability is not enough because links and buttons are focusable, but do not accept keyboard input (in the way I mean here).

Here's what I have so far:

function acceptsKeyboardInput(element) {
    return (
        element.tagName === "INPUT" ||
        element.tagName === "TEXTAREA" ||
        element.isContentEditable
    );
}

Does this approach catch every case or is there a better way to tell if an HTML element accepts keyboard input?

Henry Woody
  • 14,024
  • 7
  • 39
  • 56
  • It seems like a good non verbose check tough, why are you looking for an alternative solution, is it not working as intended ? – Cesare Polonara May 15 '22 at 20:01
  • Basically, any element that is focus-able should be able to receive keyboard input. You can read the official docs about that here: [Keyboard - Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Keyboard). You can have a look at [this question](https://stackoverflow.com/questions/18261595/how-to-check-if-a-dom-element-is-focusable) on how to detect if an element is focus-able or not. – icecub May 15 '22 at 20:02
  • 1
    @CesarePolonara I wrote that code while writing this question, so I may have figured it out. But now just want to check if there is either a built-in API for doing this (or more direct way) or if there is a case I am missing. – Henry Woody May 15 '22 at 20:04
  • @icecub I made a small update to the question, but focusability isn't what I'm after here as links and buttons are focusable but you can't type into them. – Henry Woody May 15 '22 at 20:05
  • @icecub but you can make an element focusable by setting the `tabindex` attribute, so that could make an element focusable but not editable. – Cesare Polonara May 15 '22 at 20:07
  • I believe only form elements have the `readOnly` attribute. I know that doesn't answer your question but maybe it can help – Spencer May May 15 '22 at 20:08
  • According to how JQuery has implemented [focusable](https://github.com/jquery/jquery-ui/blob/70dae67b73dfea9126f126f516fe8286f1e73417/ui/focusable.js), it looks like there isn't any straightforward way to check whether an element can receive inputs. But chances are that your solution already covers all your needs – Christian Vincenzo Traina May 15 '22 at 20:10
  • @Henry I see, I think you should use `Document.activeElement` to check the current focused element on `window.onkeydown` and perform the check you exposed, if it's not an input/textarea/contenteditable you're good to go. – Cesare Polonara May 15 '22 at 20:10
  • @CesarePolonara That's like saying that your car is running because you fired it up. If you specifically choose to make an element focus-able, you shouldn't complain that it is detected as focus-able. – icecub May 16 '22 at 07:37
  • 1
    @HenryWoody Afaik, there is no standard way to detect if an element accepts keyboard input as in "can be typed into". You'll probably have to make a list of standard elements and / or attributes that can be typed into and finally check for the global attribute `.isContentEditable;` for any other element that you've given the `contenteditable` attribute. – icecub May 16 '22 at 07:48

2 Answers2

1

From what I've seen, the suggestion given in the question seems to be the best approach—with some adjustments.

The first improvement is to blacklist a set of input types that don't accept keyboard input (e.g. checkbox or radio). I find it easier and better to use a blacklist rather than a whitelist for two reasons. The first is future-proofing against newly supported input types and in case you miss one. Second and more importantly, an invalid input type defaults back to type text, which means there are an infinite number of input types that accept keyboard input.

The second change is to also include <select> elements since they can be typed into as a sort of search/quick-select functionality.

Here's the full function:

const nonTypingInputTypes = new Set([
    "checkbox",
    "radio",
    "button",
    "reset",
    "submit",
    "file",
]);

export function acceptsKeyboardInput(element) {
    return (
        (element.tagName === "INPUT" && !nonTypingInputTypes.has(element.type)) ||
        element.tagName === "TEXTAREA" ||
        element.tagName === "SELECT" ||
        element.isContentEditable
    );
}

There are a few other inputs that don't accept keyboard input but that are less widely supported by browsers (e.g. color), so I've tried to keep it to the more commonly used and widely implemented input types.

Henry Woody
  • 14,024
  • 7
  • 39
  • 56
0

Will all shortcuts be more than one key? If so you can listen for input and prevent a shortcut from running with a boolean value.

var is_input = false
window.addEventListener('keydown', function (e) {
    console.log(is_input)
    is_input = false
})
window.addEventListener('input', function (e) {
    is_input = e.constructor.name == 'InputEvent'
})

Expected output for /s while able to type would be true or false (depending on the previous is_input value) at / keypress then true at s keypress and all keys following.

Expected output for /s while not able to type would be true or false (depending on the previous is_input value) at / keypress then false at s keypress and all keys following

Spencer May
  • 4,266
  • 9
  • 28
  • 48
  • Note: this works for all inputs including content editable HTML. However, I did notice keydown isn't called when type searching in a standard `select` element – Spencer May May 15 '22 at 21:35
  • Interesting approach. I think the problem with this would be the complexity since you can't rely on the order in which the event listeners are called (implemented differently in different browsers) and this would require running the actual logic in a separate way (possibly with a timeout) which would further introduce concurrency issues. – Henry Woody Jun 28 '22 at 00:31
  • @HenryWoody is there ever a case where `input` wont follow `keydown`? I believe keyboard events will always fire before a dom change does – Spencer May Jun 29 '22 at 20:37
  • @HenryWoody I decided to try and make my own library using this method, and I had to opt for the timeout method incase a single character shortcut is set. I'm still looking into DOM firing order but can't find a use case where input wont immediately follow keydown. You should check it out! `github.com/kapenike/OmniKeys` – Spencer May Jun 30 '22 at 16:57