adobe / adobe/spectrum-web-components

[Bug]: Action Menu (mobile Tray) doesn't reopen after being dismissed by an outside tap

Open Beginner friendly
#6,678 3 comments 0 reactions 0 assignees View on GitHub
bug needs-jira-ticket triage
Dominant language
TypeScript
Stars
1.5k
Forks
262
Avg merge
3d 10h
Merged PRs (30d)
68

Description

### Code of conduct

- [x] I agree to follow this project's code of conduct.

### Impacted component(s)

Action Menu (`sp-action-menu`)

### Library version

1.12.2

### Expected behavior

On a mobile/touch device, tapping the action button should reopen the menu, even if the previous time it was open, it was dismissed by tapping outside of it rather than by selecting a menu item or moving focus elsewhere.

### Actual behavior

On a mobile device (or Chrome DevTools device emulation), opening the Action Menu renders it in a tray. If the tray is dismissed by tapping outside of it (instead of selecting a menu item), tapping the action button again does **not** reopen the tray. The button becomes unresponsive to taps until focus is moved to some other element and back.

### Screenshots

See linked Slack thread / screen recording (internal): reproduces directly on the SWC docs site action-menu example.

### What browsers are you seeing the problem in?

Chrome

### How can we reproduce this issue?

1. Go to https://opensource.adobe.com/spectrum-web-components/components/action-menu/
2. Open the page with Chrome DevTools device emulation using an iPhone 16 Pro Max (or any touch/mobile emulation).
3. Tap the action button. The menu opens in a tray.
4. Tap outside the tray to dismiss it (without selecting a menu item).
5. Tap the action button again.
6. **Expected:** the tray reopens. **Actual:** nothing happens; the tray does not reopen until focus is moved elsewhere first.

### Sample code or abstract reproduction which illustrates the problem

```html

Deselect
Select Inverse

```

No special markup is required; this reproduces with the default Action Menu example on the docs site, as long as the mobile/touch `MobileController` code path is active (viewport matches the mobile media query).

### Severity

SEV 4

### Root cause (found during investigation)

In `packages/picker/src/Picker.ts`, `ExpandableElement.handleBeforetoggle` handles the overlay's native `beforetoggle` event when it closes. When the close is triggered externally (e.g. tapping outside the mobile Tray, which closes via the Tray's underlay `close` event rather than a click on the trigger button) while `strategy.preventNextToggle === 'no'`, the handler does:

```ts
} else if (this.strategy?.preventNextToggle === 'no') {
this.open = false;
}
```

This sets the host's own `open` property (so the Tray visually closes) but never syncs `this.strategy.open`, unlike the sibling branch just above it (`if (!this.open) { ... this.strategy.open = false; }`), which does sync both.

Because of that, `MobileController`'s internally tracked `open` state (`InteractionController.open`, which is separate from `host.open`) stays stale at `true`. On the very next tap, `MobileController.handlePointerdown()` reads that stale value and sets `preventNextToggle = 'yes'` (believing the menu is still open), which causes `MobileController.handleClick()` to skip calling `toggle()` entirely - so the tap does nothing.

This doesn't affect desktop because `DesktopController.handleActivate()` unconditionally calls `host.toggle()`, which recomputes fresh from the host's own `open` property and resyncs `strategy.open` as a side effect (self-healing). `MobileController.handleClick()` instead gates the `toggle()` call itself on the stale controller state, so it never reaches that self-healing path.

**Suggested fix:** also sync `this.strategy.open = false;` in that `else if` branch, mirroring the branch above it.

### Would you like to track this issue in Jira?

- [x] Yes, please tell me the ticket number!

Contributor guide

Open the contributing guide

Research direction

Start in packages/picker/src/Picker.ts, reading ExpandableElement.handleBeforetoggle and the MobileController open-state flow. Reproduce with the Action Menu docs example in Chrome DevTools iPhone emulation, then verify that dismissing the tray by tapping outside allows the action button to reopen it immediately.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
88/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.