primitives icon indicating copy to clipboard operation
primitives copied to clipboard

[Select] Selecting an option triggers a touch event on elements positioned behind

Open chrisarnold opened this issue 2 years ago • 68 comments

Bug report

Current Behavior

Using the Select primitive, when selecting an option, the touch event is interacting with any elements positioned underneath the list. This isn't happening on desktop on mouse click but can be replicated in Chrome when using the device simulation

https://user-images.githubusercontent.com/1044655/189703615-f4d8d640-81f4-4263-bc98-a157afce9e0a.mov

Expected behavior

When an option is selected, the menu should close and no events should be triggered on other inputs

Reproducible example

https://codesandbox.io/s/late-snowflake-39o7x7?file=/App.js

Suggested solution

Additional context

Your environment

Software Name(s) Version
Radix Package(s) @radix-ui/react-select 1.0.0
React n/a 17
Browser Chrome 104

chrisarnold avatar Sep 12 '22 16:09 chrisarnold

Hi @chrisarnold,

Whilst I can reproduce it in emulation mode on Chrome, I cannot replicate it on an actual phone (iPhone iOS 16). Are you seeing this issue natively anywhere?

benoitgrelard avatar Sep 20 '22 13:09 benoitgrelard

Hey @benoitgrelard, hmmm that's strange... it's happening natively for me as well. On both Safari and Chrome on an iPhone running iOS 15.6.1

https://user-images.githubusercontent.com/1044655/191274645-f914c5c1-1476-495e-a85e-305c66ccc8eb.MOV

chrisarnold avatar Sep 20 '22 13:09 chrisarnold

I was also able to repro on 15.4.1

andy-hook avatar Sep 20 '22 13:09 andy-hook

Interesting, different behaviour in iOS 16 then?

https://user-images.githubusercontent.com/1539897/191276693-da30a99d-5417-4ca6-89ec-4ac3cf076041.mov

benoitgrelard avatar Sep 20 '22 13:09 benoitgrelard

Interesting, different behaviour in iOS 16 then?

Seems so 🤔 the behaviour I'm seeing matches those posted by @chrisarnold .

andy-hook avatar Sep 20 '22 14:09 andy-hook

That's going to make testing trickier haha, at least we can reliably reproduce in emulation mode, although not sure what to make of that or the change between iOS15 and 16 😅

benoitgrelard avatar Sep 20 '22 15:09 benoitgrelard

I have the same problem on desktop/Firefox right now. The click on any option starts a text selection on the elements behind. I've managed to work around this by using "user-select: none" on the CSS, but this shouldn't be the solution. :sweat_smile:

https://user-images.githubusercontent.com/97403327/191869917-c40cf54a-45cc-4647-ad9b-840d296cc5d6.mp4

cpt-westphalen avatar Sep 22 '22 23:09 cpt-westphalen

@cpt-westphalen There's a workaround for that issue in https://github.com/radix-ui/primitives/issues/1488#issuecomment-1237705742 . I do wonder if the Firefox selection bug is symptomatic of the same underlying issue though 🤔

andy-hook avatar Sep 23 '22 08:09 andy-hook

I am also able to reproduce on select 1.0.0

shyamlohar avatar Sep 26 '22 13:09 shyamlohar

@shyamlohar, just to confirm the theory, are you on iOS 15?

benoitgrelard avatar Sep 26 '22 13:09 benoitgrelard

@benoitgrelard i tried on two android phones one is a Google pixel 5 and one is redmi k20 pro (physical device) on chrome 105. let me know if i can help in any way.

shyamlohar avatar Sep 27 '22 03:09 shyamlohar

The same issue on v1.0.0, can not reproduce on v0.1.1 Mobile chrome v105 and chrome mobile emulator

vitalikda avatar Sep 28 '22 09:09 vitalikda

Same issue, not only on desktop chrome, but also on physical android mobile (Samsung Note 20 Ultra, chrome browser, nextjs development env). This issue makes it, sadly, not fully unusable. (Video from desktop chrome, but same behaviour on phone too)

My environment

Desktop: macOS - 12.6 chrome - 106.0.5249.103

Mobile: samsung note 20 ultra - android 12 chrome - 106.0.5249.118

Main dependencies: react - 18.2.0 @radix-ui/react-select - 1.0.0

Code snippet (tested on):

<SelectPrimitive.Root
    defaultValue={activeLink?.href}
    value={activeLink?.href}
    onValueChange={onChange}
>
    <SelectPrimitive.Trigger asChild aria-label="Food">
        <button>
            <SelectPrimitive.Value>{activeLink?.label}</SelectPrimitive.Value>

            <SelectPrimitive.Icon>
                <ChevronDownIcon />
            </SelectPrimitive.Icon>
        </button>
    </SelectPrimitive.Trigger>

    <SelectPrimitive.Portal>
        <SelectPrimitive.Content className="z-[100]">
            <SelectPrimitive.ScrollUpButton>
                <ChevronUpIcon />
            </SelectPrimitive.ScrollUpButton>

            <SelectPrimitive.Viewport>
                <SelectPrimitive.Group>
                    {links.map(({ label, id, href }) => (
                        <SelectPrimitive.Item
                            key={id}
                            value={href}
                            onPointerUp={(event) => event.stopPropagation()}
                            onClick={(event) => event.stopPropagation()}
                        >
                            <SelectPrimitive.ItemText>{label}</SelectPrimitive.ItemText>

                            <SelectPrimitive.ItemIndicator>
                                <CheckIcon />
                            </SelectPrimitive.ItemIndicator>
                        </SelectPrimitive.Item>
                    ))}
                </SelectPrimitive.Group>
            </SelectPrimitive.Viewport>

            <SelectPrimitive.ScrollDownButton>
                <ChevronDownIcon />
            </SelectPrimitive.ScrollDownButton>
        </SelectPrimitive.Content>
    </SelectPrimitive.Portal>
</SelectPrimitive.Root>

Video (button has click event to change bg color to random)

https://user-images.githubusercontent.com/10235669/196123154-1a93e67c-d626-4120-a039-8f6bd9600c79.mov

Temporary "fix"

I've made a simple mitigation (not a fix) for the time being. It has it's own flaws and changes the behaviour a little bit, but until this issue is addressed, it might help others to temporarily patch and use this select.

  • Added fixed full page overlay (invisible). Rendered only when select is opened
  • Made overlay z-index 100
  • Made select content and trigger z-index 101 (only when select is opened)
  • Added setTimeout of 10ms (just-in-case) for overlay removal upon select closing

veiico avatar Oct 17 '22 08:10 veiico

I ran into same issue in Chrome and Firefox mobile emulation. Also in Native Android Chrome and Firefox. Fortunately, the workaround proposed by @veiico works. Here's the relevant code using styled components:

Introduce an overlay:


const StyledOverlay = styled.div`
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;

  /* Choose whatever z-index makes most sense to you */
  z-index: 1050;
  isolation: isolate;
`
/* Workaround for touch events propagating to underlying elements https://github.com/radix-ui/primitives/issues/1658 */
const Overlay = ({ open }: { open: boolean }) => {
  const [visible, setVisible] = useState(open)
  useEffect(() => {
    if (!open) {
      const timer = setTimeout(() => {
        setVisible(false)
      }, 200)
      return () => {
        clearTimeout(timer)
      }
    }
    setVisible(true)
    return () => {}
  }, [open])

  return enabled ? <StyledOverlay onClick={(e) => e.stopPropagation()} /> : null
}

Add it inside the portal:

        <SelectPrimitive.Portal>
          <>
            <Overlay open={open} />
            <StyledContent>
              ...
            </StyledContent>
          </>
        </SelectPrimitive.Portal>

Edit: Added onClick={(e) => e.stopPropagation()} to the overlay Edit2: Increased the timeout to make the workaround work on Firefox Android

myrjola avatar Nov 08 '22 13:11 myrjola

The same issue here, can we just add a prop stopPropagation for SelectPrimitive.Content to prevent background click?

laozhu avatar Feb 15 '23 04:02 laozhu

Non of the workarounds worked for me. So I "fixed" it by adding a eventlistener to the underlying items and prevent the default event when the select is open:

  const handleOnOpenChange = (open) => {
    // Change `menu__link` to a class that selects the underlying items
    const items = document.querySelectorAll(".menu__link");

    items.forEach((item) => {
      if (open) {
        item.addEventListener("click", function(e){
          e.preventDefault();
          e.stopPropagation();
        });
      } else {
        item.removeEventListener("click", function(e){
          e.preventDefault();
          e.stopPropagation();
        });
      }
    });
  };

And add handleOnOpenChange to the select root:

<Select.Root onOpenChange={handleOnOpenChange} >

Hacky but it works for me!

KoenRijpstra avatar Mar 14 '23 09:03 KoenRijpstra

I came up with this hacky solution, it's kind of similar to Koen's.

const useSelectInteractionFix = (selectors: string) => {
  const timeoutRef = useRef<number | undefined>();
  const root = document.querySelector<HTMLElement>(selectors);

  if (!root) {
    throw new Error("Root was not found");
  }

  const disableClicks = () => {
    root.style.setProperty("pointer-events", "none");
  };

  const enableClicks = () => {
    root.style.removeProperty("pointer-events");
    // or root.removeAttribute("style") to remove empty attribute.
  };

  const openChangeHandler = (open: boolean) => {
    if (open) {
      clearTimeout(timeoutRef.current);
      disableClicks();
    } else {
      // Casting it here because Node is returning `Timeout` and this handler will run in the browser.
      timeoutRef.current = setTimeout(enableClicks, 50) as unknown as number;
    }
  };

  return openChangeHandler;
};
const App = () => {
  // I am using Next.js but it should work with any other root element.
  const handleOpenChange = useSelectInteractionFix("#__next");

  return <Select.Root onOpenChange={handleOpenChange} />
}

demondobf avatar Mar 30 '23 23:03 demondobf

still experience the same issue in Chrome mobile emulator, and in mobile Safari as well. Any plan to provide a fix? 🙏🏻

artekr avatar Apr 08 '23 17:04 artekr

Bug report

Current Behavior

Using the Select primitive, when selecting an option, the touch event is interacting with any elements positioned underneath the list. This isn't happening on desktop on mouse click but can be replicated in Chrome when using the device simulation

Radix_Select_touch_bug.mov

Expected behavior

When an option is selected, the menu should close and no events should be triggered on other inputs

Reproducible example

https://codesandbox.io/s/late-snowflake-39o7x7?file=/App.js

Suggested solution

Additional context

Your environment

Software Name(s) Version Radix Package(s) @radix-ui/react-select 1.0.0 React n/a 17 Browser Chrome 104

hi

After conducting several tests, I believe that I have identified the root of the problem. Essentially, when clicking on an item, the select value changes so quickly that the popper disappears and the touch event simultaneously clicks the element behind it. To address this issue, I implemented a solution that resolved the problem. By using the following code:

<Select.Root open={Open} onOpenChange={() => { setTimeout(() => { setOpen(!Open); }, 100); }}>

The select menu now waits for 100ms to finish the touch event trigger before closing, preventing any accidental clicks on elements behind the select component.

omer-os avatar Apr 21 '23 11:04 omer-os

i hope radix team will solve this issue as soon as possible

omer-os avatar Apr 21 '23 11:04 omer-os

@omer-os Hi there! I tried out your solution using setTimeout and noticed that it worked intermittently and caused a delay in the select. As a result, it may not be the best solution for the problem 😕

https://user-images.githubusercontent.com/15058771/234055067-ccc028ae-5fe6-49c6-9b88-21c896e564b9.mov

drianoaz avatar Apr 24 '23 16:04 drianoaz

@omer-os Hi there! I tried out your solution using setTimeout and noticed that it worked intermittently and caused a delay in the select. As a result, it may not be the best solution for the problem 😕

https://user-images.githubusercontent.com/15058771/234055067-ccc028ae-5fe6-49c6-9b88-21c896e564b9.mov

Hi sir,

I think my solution is not the best but it's simplest, if 200ms effects the UX of your app you need to implement something better.

I'm sure you'll find a better way as we know what's the cause of this problem as i said in my original comment, or dont even use radix UIs select component if you didn't find a good solution.

omer-os avatar Apr 25 '23 01:04 omer-os

this opened PR definitively solved the problem for me 🎉

https://user-images.githubusercontent.com/15058771/234159292-53278d83-127b-40e3-94f5-fc3fcd2e45ab.mov

drianoaz avatar Apr 25 '23 02:04 drianoaz

This 8+ month-old bug renders the whole package useless, why not merge the proposed fix?

myo avatar May 05 '23 17:05 myo

Any progress update on the issue?

fvanharreveld avatar May 09 '23 07:05 fvanharreveld

Any progress update on the issue?

yeah just use this fork (on this branch specifically), works perfectly https://github.com/joaom00/primitives/tree/select-touch

myo avatar May 09 '23 09:05 myo

I sometimes think radix is not developped by pure front end developers, as most complex use case are not covered:

  • mobile touch interractions
  • elements above each other thru different components (dialogs, etc..)

Aarbel avatar Jun 01 '23 10:06 Aarbel

@Aarbel, this isn't a helpful comment. If you have specific difficulties, open issues to discuss things. Otherwise take this behaviour somewhere else, this isn't the kind of community we want to foster here.

benoitgrelard avatar Jun 01 '23 10:06 benoitgrelard

@benoitgrelard

Not sweet to hear i agree, but surely had to be said. I work with production teams that lost weeks fixing / contributing to radix since 2 years, and we are still facing basic bugs which where not existing in the past versions. Thanks again for your work, i know that accessibility on all devices is a big challenge, but please consider that radix contributions / maintenance doesn't look serious to really solve this big problem, example:

  • About the PR linked to this issue https://github.com/radix-ui/primitives/pull/2085 image We can see that it has been approved by @bryanltobing, but absolutely no "domain" comments were written and no test example is added to avoid the problem in the future. So the fix is fully opaque for future developers, and there's a high probability it can cause other bugs.

Suggestions:

  • enforce radix repo reviewing process for new PRs (domain inline comments, video screncaptures, potential side effects overview)
  • add storybook "corner" interfaces for tests (like this example) to be tested by every contributor, otherwise we have no warranty this kind of bug will not re-appear in the next updates.
  • require manual webview touch mobile tests (android and ios simulators) by every contributors

Happy to help if needed.

Aarbel avatar Jun 01 '23 13:06 Aarbel

yeah just use this fork (on this branch specifically), works perfectly

How can this be used as an installed package and consumed in a typescript project? I am trying to use it in a nextjs project. I installed it and I'm importing it successfully, but next is choking on it... it's weirdly having issues with the typescript.

codingwithchris avatar Jun 21 '23 16:06 codingwithchris