fix(DropdownMenu): light-dismiss + click-toggle race on touch browsers - #2186
Merged
Conversation
On iOS Safari (and other touch browsers where pointerdown fires light-dismiss before the triggers click event), tapping the dropdown trigger to close an open menu would close and then immediately re-open on the same tap. The hide flows through useXDSPopovers onHide first, then the click hits handleButtonClick, which reads popover.isOpen=false and toggles it back to true. Mirror the existing 50ms hide-time guard in XDSPopover: stamp lastHideTimeRef in handleLayerHide, then short-circuit handleButtonClick if it fires within 50ms of that stamp. Also fixes XDSMoreMenu since it wraps XDSDropdownMenu.
|
@lexs must be a member of the Meta Open Source team on Vercel to deploy. Learn more about collaboration on Vercel and other options here. |
Contributor
PR Analysis Report📚 Storybook PreviewView Storybook for this PR 🧪 Sandbox PreviewView Sandbox for this PR No new or modified components detected. Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
cixzhang
approved these changes
May 19, 2026
cixzhang
left a comment
Contributor
There was a problem hiding this comment.
Clean fix. Timestamp guard is the right approach for the iOS event ordering race. Minimal, well-tested, no regression risk.
cixzhang
pushed a commit
that referenced
this pull request
Jun 21, 2026
#2186) On iOS Safari (and other touch browsers where pointerdown fires light-dismiss before the triggers click event), tapping the dropdown trigger to close an open menu would close and then immediately re-open on the same tap. The hide flows through useXDSPopovers onHide first, then the click hits handleButtonClick, which reads popover.isOpen=false and toggles it back to true. Mirror the existing 50ms hide-time guard in XDSPopover: stamp lastHideTimeRef in handleLayerHide, then short-circuit handleButtonClick if it fires within 50ms of that stamp. Also fixes XDSMoreMenu since it wraps XDSDropdownMenu.
cixzhang
pushed a commit
that referenced
this pull request
Jun 21, 2026
#2186) On iOS Safari (and other touch browsers where pointerdown fires light-dismiss before the triggers click event), tapping the dropdown trigger to close an open menu would close and then immediately re-open on the same tap. The hide flows through useXDSPopovers onHide first, then the click hits handleButtonClick, which reads popover.isOpen=false and toggles it back to true. Mirror the existing 50ms hide-time guard in XDSPopover: stamp lastHideTimeRef in handleLayerHide, then short-circuit handleButtonClick if it fires within 50ms of that stamp. Also fixes XDSMoreMenu since it wraps XDSDropdownMenu.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On iOS Safari (and other touch browsers where
pointerdownfires light-dismiss before the trigger'sclickevent), tapping theXDSDropdownMenutrigger to close an open menu would close and then immediately re-open on the same tap. The hide flows throughuseXDSPopover'sonHidefirst, then the click hitshandleButtonClick, which readspopover.isOpen=falseand toggles it back to true.Mirror the existing 50ms hide-time guard in
XDSPopover(lines 268, 274–277, 290–296 — same race, same fix) intoXDSDropdownMenu: stamplastHideTimeRefinhandleLayerHide, then short-circuithandleButtonClickif it fires within 50ms of that stamp.Also fixes
XDSMoreMenusince it wrapsXDSDropdownMenu.This ports the equivalent internal fix (Meta D104032592 in fbsource, which patches
XDSInternalPopoverMenu.react.js— the shared internal layer behind bothXDSDropdownMenuandXDSPopoverMenu) to the public xds repo. Here, the internal layer isuseXDSPopover, butXDSDropdownMenucalls into it directly with its own toggle logic, so the guard goes alongside that logic.XDSPopoveralready has the equivalent guard for the same race.Test Plan
Added a regression test in
XDSDropdownMenu.test.tsxthat fires open → close → click and asserts the menu does not re-open (showPopover/hidePopovereach called exactly once).ESLint clean on the changed files (one pre-existing unused-
containerwarning in an unrelated test left as-is).