Skip to content

fix(DropdownMenu): light-dismiss + click-toggle race on touch browsers - #2186

Merged
cixzhang merged 1 commit into
mainfrom
fix/dropdown-light-dismiss-race
May 20, 2026
Merged

fix(DropdownMenu): light-dismiss + click-toggle race on touch browsers#2186
cixzhang merged 1 commit into
mainfrom
fix/dropdown-light-dismiss-race

Conversation

@lexs

@lexs lexs commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

On iOS Safari (and other touch browsers where pointerdown fires light-dismiss before the trigger's click event), tapping the XDSDropdownMenu trigger to close an open menu would close and then immediately re-open on the same tap. The hide flows through useXDSPopover's 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 (lines 268, 274–277, 290–296 — same race, same fix) into XDSDropdownMenu: stamp lastHideTimeRef in handleLayerHide, then short-circuit handleButtonClick if it fires within 50ms of that stamp.

Also fixes XDSMoreMenu since it wraps XDSDropdownMenu.

This ports the equivalent internal fix (Meta D104032592 in fbsource, which patches XDSInternalPopoverMenu.react.js — the shared internal layer behind both XDSDropdownMenu and XDSPopoverMenu) to the public xds repo. Here, the internal layer is useXDSPopover, but XDSDropdownMenu calls into it directly with its own toggle logic, so the guard goes alongside that logic. XDSPopover already has the equivalent guard for the same race.

Test Plan

Added a regression test in XDSDropdownMenu.test.tsx that fires open → close → click and asserts the menu does not re-open (showPopover/hidePopover each called exactly once).

$ yarn test packages/core/src/DropdownMenu/XDSDropdownMenu.test.tsx
 Test Files  1 passed (1)
      Tests  29 passed (29)

$ yarn test packages/core/src/Popover/XDSPopover.test.tsx packages/core/src/MoreMenu/XDSMoreMenu.test.tsx
 Test Files  2 passed (2)
      Tests  31 passed (31)

$ yarn workspace @xds/core build
✅ Build success

ESLint clean on the changed files (one pre-existing unused-container warning in an unrelated test left as-is).

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.
@vercel

vercel Bot commented May 18, 2026

Copy link
Copy Markdown

@lexs must be a member of the Meta Open Source team on Vercel to deploy.
- Click here to add @lexs to the team.
- If you initiated this build, request access.

Learn more about collaboration on Vercel and other options here.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label May 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@xds/core 18.3KB 28.3KB 4.2KB

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@cixzhang cixzhang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix. Timestamp guard is the right approach for the iOS event ordering race. Minimal, well-tested, no regression risk.

@cixzhang
cixzhang merged commit b26b1dc into main May 20, 2026
20 of 21 checks passed
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.
@cixzhang
cixzhang deleted the fix/dropdown-light-dismiss-race branch June 21, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants