primitives icon indicating copy to clipboard operation
primitives copied to clipboard

[Tooltip] Tooltip is opened on active buttons when the browser tab is switched

Open gdorsi opened this issue 3 years ago • 8 comments

Bug report

Current Behavior

Steps:

  • Go to https://www.radix-ui.com/docs/primitives/components/tooltip
  • Click on the button
  • Change browser tab
  • Go back to the tooltip tab
  • The tooltip state is open

This happens also when the browser window is minimized/maximized.

Expected behavior

The tooltip shouldn't be open when the tab becomes active again

Reproducible example

https://www.radix-ui.com/docs/primitives/components/tooltip

Suggested solution

The issue is triggered by the onFocus handler because the browser triggers a focus event when the page becomes visible.

I've applied this workaround on my tooltips:

  const prevActiveElement = useRef<EventTarget | null>(null);

  useEffect(() => {
    const handleFocus = (evt: Event) => {
      const { target } = evt;

      prevActiveElement.current = target;
    };

    window.addEventListener('focusin', handleFocus);

    return () => {
      window.removeEventListener('focusin', handleFocus);
    };
  }, []);

  return (
    <Tooltip.Root>
      <Tooltip.Trigger
        onFocus={(evt) => {
          // Blocks the tooltip open during the page visibility change
          // based on the assumption that we want to show the tooltip only when the focus
          // is triggered on a new element
          if (prevActiveElement.current === evt.target) {
            evt.preventDefault();
          }
        }}

To make the example small I've placed everything in the component, but it's better to track the previous active element inside a provider.

I'm migrating from tippy.js and while they handle focus they don't have the same issue. I would also check there to find a possible solution.

Your environment

Software Name(s) Version
Radix Package(s) @radix-ui/react-tooltip 1.0.2
React n/a 18.2
Browser Chrome 105
Assistive tech n/a
Node n/a
npm/yarn
Operating System Linux & MacOS

gdorsi avatar Nov 23 '22 14:11 gdorsi

Reproducing and experiencing the same issue.

itsmingjie avatar Dec 04 '22 04:12 itsmingjie

are you getting a memory leak warning for this?

ctaylr13 avatar Jan 03 '23 15:01 ctaylr13

There are some other similar cases where this experience happens.

From looking at the code, it's anytime the trigger regains focus but the pointer is not down.

Another example case would be closing a dialog or other modal experience with the escape button. This returns focus to the trigger, but the pointer is not pressed. Alternatively, a click event only fires after the pointer up, so any button click in a dialog that closes the dialog and returns the focus will likely have this same issue.

sfrieson avatar Jan 03 '23 19:01 sfrieson

are you getting a memory leak warning for this?

Nope

gdorsi avatar Jan 10 '23 01:01 gdorsi

There are some other similar cases where this experience happens.

From looking at the code, it's anytime the trigger regains focus but the pointer is not down.

Another example case would be closing a dialog or other modal experience with the escape button. This returns focus to the trigger, but the pointer is not pressed. Alternatively, a click event only fires after the pointer up, so any button click in a dialog that closes the dialog and returns the focus will likely have this same issue.

Seems a different issue to me.

The one I've reported is caused by the browsers behavior of triggering a focus event when the tab/window goes back to the active state.

gdorsi avatar Jan 10 '23 01:01 gdorsi

There are some other similar cases where this experience happens.

From looking at the code, it's anytime the trigger regains focus but the pointer is not down.

Another example case would be closing a dialog or other modal experience with the escape button. This returns focus to the trigger, but the pointer is not pressed. Alternatively, a click event only fires after the pointer up, so any button click in a dialog that closes the dialog and returns the focus will likely have this same issue.

We've experienced this behaviour as well. We commonly have Icon Buttons with Tooltips that activate Dialogs. Once the Dialog is closed, the Tooltip is shown because focus is returned back to the Icon Button. It strikes me that this is not ideal behaviour. It's a bit confusing and annoying as a user, but I will also admit I'm not intimately familiar with all the accessibility guidelines for Tooltips. Perhaps this is intended behaviour?

If it's not intended behaviour it would be useful to get a fix for it because I expect it's not uncommon to use these components together so it seems like a common issue users will run into.

As I currently understand it, it seems plausible that the issues @gdorsi and @sfrieson have described are the same root cause.

What if there was an extra check added here that checked if the target of the tooltip has focus-visible rather than focus? I'm not actually sure if there's a direct way to query focus-visible for element in javascript. I don't believe there is a focus-visible (reference: https://w3c.github.io/uievents/#event-type-focus) but you could perhaps do something along the lines of:

onFocus={composeEventHandlers(props.onFocus, (event) => {
    if (!isPointerDownRef.current && document.querySelector("*:focus-visible") === event.currentTarget) context.onOpen();
})}

sanbornhilland avatar Jun 22 '23 18:06 sanbornhilland

Same problem here, but it only happens on my end after the state of the trigger changes. It works again without any issues, after I click somewhere to hide the tooltip.

Zerebokep avatar Jun 18 '24 06:06 Zerebokep

See pull request https://github.com/radix-ui/primitives/pull/2919 It would be wonderful if someone had time to review and merge

MortenHofft avatar Sep 30 '24 20:09 MortenHofft