Differentiating between kyeboard and mouse events during onFocus in React

Viewed 570

I'm was trying to make keyboard navigation focus outline for accessibility. The pseudo :focus-visible class works fine on all elements except input, like text or textarea. It seems input will always have this pseudo class active, since it requires keyboard input. Looking around I couldn't find a way to to fix it, so it would behave like all other interactable elements. So I thought I'd just sort it through onFocus while rendering, by showing/hiding custom css class that will appear and disappear depending on how user focused the input. I've managed to do as much, but the problem is I don't know how I can now further differentiate where the input is coming from, keyboard or mouse. Here's what I got so far. First react hooks:

  const [keyboardFocus, setKeyboardFocus] = useState(false);
  const toggleKeyboardFocusOn = () => setKeyboardFocus(true);
  const toggleKeyboardFocusOff = () => setKeyboardFocus(false);

So we have they keyboardFocus hook, and toggles for onFocus and onBlur which should define weather input should have keyboard focus or not. Then I have the class selector to determine weather the keyboard only outline is active or not:

const inputClassNames = classNames({
    ...
    [css.keyboardOnlyFocus]: keyboardFocus,
  });

And finally in rendering of input I have

      onBlur={toggleKeyboardFocusOff}
      onFocus={toggleKeyboardFocusOn}

So far, focusing in and out works, an will display the outline. What I'm having trouble is understanding how can I now in onFocus determine if the event came from mouse, in which case it should be ignored or if it came from keyboard.

One of the solutions I tried was something like this:

onFocus={(e) => ({
   if(e.key === 'Tab') {
     toggleKeyboardFocusOn;
   }
})

However nothing was happening, and console output of e.key showed as undefined. So what would be the correct way to tell onFocus that the event is coming from mouse or keyboard?

2 Answers

I think the best chance you have is to find out in what order mouse click events and focus events happen, and if they are synchronous (occuring during the same event loop)

Here is a bit code to get you started:

const eventObserved = useRef(false);
const checkOther = () => {
    if (eventObserved.current) {
        console.log('synchronous!');
    } else {
        eventObserved.current = true;
        setTimeout(() => { eventObserved.current = false });
    }
};

return (
  <input
    onClick={() => {
      console.log('clicked');
      checkOther();
    }}
    onFocus={() => {
      console.log('focus');
      checkOther();
    }}
  />
);

And you mights also look into handling MouseDown and MouseUp separately from click. Maybe you need to go down to the bare metal and use native events instead of the react syntetic events.

I ran into a similar kind of situation, the only solution I was able to come up with was:

  • Use onMouseDown to know if it is a mouse event. As it will execute before onChange and onFocus callbacks.

  • Use onKeyDown to know if it is a keyboard event (if required in your case, it was required in my case). onKeyDown callback similar to onMouseDown will execute before onChange and onFocus callback.

Hope this helps.

Related