primitives
primitives copied to clipboard
[Select] Selecting an option triggers a touch event on elements positioned behind
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 |
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?
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
I was also able to repro on 15.4.1
Interesting, different behaviour in iOS 16 then?
https://user-images.githubusercontent.com/1539897/191276693-da30a99d-5417-4ca6-89ec-4ac3cf076041.mov
Interesting, different behaviour in iOS 16 then?
Seems so 🤔 the behaviour I'm seeing matches those posted by @chrisarnold .
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 😅
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 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 🤔
I am also able to reproduce on select 1.0.0
@shyamlohar, just to confirm the theory, are you on iOS 15?
@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.
The same issue on v1.0.0, can not reproduce on v0.1.1 Mobile chrome v105 and chrome mobile emulator
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
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
The same issue here, can we just add a prop stopPropagation
for SelectPrimitive.Content
to prevent background click?
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!
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} />
}
still experience the same issue in Chrome mobile emulator, and in mobile Safari as well. Any plan to provide a fix? 🙏🏻
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.
i hope radix team will solve this issue as soon as possible
@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
@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.
this opened PR definitively solved the problem for me 🎉
https://user-images.githubusercontent.com/15058771/234159292-53278d83-127b-40e3-94f5-fc3fcd2e45ab.mov
This 8+ month-old bug renders the whole package useless, why not merge the proposed fix?
Any progress update on the issue?
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
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, 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
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
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.
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.