Skip to content

Conversation

@shipg22
Copy link
Collaborator

@shipg22 shipg22 commented Nov 8, 2025

Description

Submenu interaction is fixed for touch devices.

Submenu should open on hover for desktop and on click for touch devices.
First click should open, and second click should close the submenu on touch devices.

Motivation and context

Related issue(s)

[CCEX-244009]

  • fixes [Issue Number]

Screenshots (if appropriate)


Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Descriptive Test Statement

Storybook

In dev tool select any touch screen device, for eg; iPad mini
open menu
Touch submenu under menu
Touch any submenu item
it should open menu under submenu if available, otherwise would select that item
Touch any nested menu item, it should open submenu if available, otherwise would select that item
Subsequent touch should open and close submenus

image

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

@shipg22 shipg22 requested a review from a team as a code owner November 8, 2025 02:15
@changeset-bot
Copy link

changeset-bot bot commented Nov 8, 2025

🦋 Changeset detected

Latest commit: e8cf182

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 78 packages
Name Type
@spectrum-web-components/menu Patch
@spectrum-web-components/breadcrumbs Patch
@spectrum-web-components/combobox Patch
@spectrum-web-components/picker Patch
@spectrum-web-components/bundle Patch
@spectrum-web-components/action-menu Patch
@spectrum-web-components/accordion Patch
@spectrum-web-components/action-bar Patch
@spectrum-web-components/action-button Patch
@spectrum-web-components/action-group Patch
@spectrum-web-components/alert-banner Patch
@spectrum-web-components/alert-dialog Patch
@spectrum-web-components/asset Patch
@spectrum-web-components/avatar Patch
@spectrum-web-components/badge Patch
@spectrum-web-components/button-group Patch
@spectrum-web-components/button Patch
@spectrum-web-components/card Patch
@spectrum-web-components/checkbox Patch
@spectrum-web-components/clear-button Patch
@spectrum-web-components/close-button Patch
@spectrum-web-components/coachmark Patch
@spectrum-web-components/color-area Patch
@spectrum-web-components/color-field Patch
@spectrum-web-components/color-handle Patch
@spectrum-web-components/color-loupe Patch
@spectrum-web-components/color-slider Patch
@spectrum-web-components/color-wheel Patch
@spectrum-web-components/contextual-help Patch
@spectrum-web-components/dialog Patch
@spectrum-web-components/divider Patch
@spectrum-web-components/dropzone Patch
@spectrum-web-components/field-group Patch
@spectrum-web-components/field-label Patch
@spectrum-web-components/help-text Patch
@spectrum-web-components/icon Patch
@spectrum-web-components/icons-ui Patch
@spectrum-web-components/icons-workflow Patch
@spectrum-web-components/icons Patch
@spectrum-web-components/iconset Patch
@spectrum-web-components/illustrated-message Patch
@spectrum-web-components/infield-button Patch
@spectrum-web-components/link Patch
@spectrum-web-components/meter Patch
@spectrum-web-components/modal Patch
@spectrum-web-components/number-field Patch
@spectrum-web-components/overlay Patch
@spectrum-web-components/picker-button Patch
@spectrum-web-components/popover Patch
@spectrum-web-components/progress-bar Patch
@spectrum-web-components/progress-circle Patch
@spectrum-web-components/radio Patch
@spectrum-web-components/search Patch
@spectrum-web-components/sidenav Patch
@spectrum-web-components/slider Patch
@spectrum-web-components/split-view Patch
@spectrum-web-components/status-light Patch
@spectrum-web-components/swatch Patch
@spectrum-web-components/switch Patch
@spectrum-web-components/table Patch
@spectrum-web-components/tabs Patch
@spectrum-web-components/tags Patch
@spectrum-web-components/textfield Patch
@spectrum-web-components/thumbnail Patch
@spectrum-web-components/toast Patch
@spectrum-web-components/tooltip Patch
@spectrum-web-components/top-nav Patch
@spectrum-web-components/tray Patch
@spectrum-web-components/underlay Patch
@spectrum-web-components/base Patch
@spectrum-web-components/grid Patch
@spectrum-web-components/opacity-checkerboard Patch
@spectrum-web-components/reactive-controllers Patch
@spectrum-web-components/shared Patch
@spectrum-web-components/styles Patch
@spectrum-web-components/theme Patch
@spectrum-web-components/truncated Patch
@spectrum-web-components/eslint-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Nov 8, 2025

📚 Branch Preview

🔍 Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-5867

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@shipg22 shipg22 changed the title Shiprag/fix touch submenu interactions v2 fix: improve touch interaction handling for submenus Nov 8, 2025
@shipg22 shipg22 self-assigned this Nov 8, 2025
@shipg22 shipg22 force-pushed the shiprag/fix-touch-submenu-interactions-v2 branch from 934adf0 to a8163fd Compare November 8, 2025 02:56
@Rajdeepc
Copy link
Contributor

I am unable to open any submenu. Can you please check your logic once again.

submenu.mov

Copy link
Contributor

@caseyisonit caseyisonit left a comment

Choose a reason for hiding this comment

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

Ran through the manual tests and this proposed fix is not working. submenus do not open at all. This needs more work on the logic to be considered.

Shipra Gupta added 2 commits November 11, 2025 14:36
…lose and reopen

- Add _touchListenerActive flag to prevent multiple pointerup listeners from being registered
- Reset flag in handleTouchSubmenuToggle after action completes
- Prevents rapid close/reopen behavior when tapping on menu items with open submenus
@shipg22 shipg22 force-pushed the shiprag/fix-touch-submenu-interactions-v2 branch from 7147e73 to 85cb5a8 Compare November 12, 2025 17:16
- Add click event dispatch after touch tap to cover handleSubmenuClick touch prevention
- Add touch pointerleave test to cover early return for touch devices
- Add test for pointerdown on open submenu followed by focus to cover handleSubmenuFocus

These targeted test enhancements improve coverage without adding entirely new test cases.
@Rajdeepc
Copy link
Contributor

Rajdeepc commented Nov 14, 2025

@shipg22 On my initial first pass the solution is not working for nested submenus.Please check and verify so that I can look into your logic more deeply. Attached video for reference.
https://github.com/user-attachments/assets/eccd8430-bd4d-463a-b082-f161027658ad

Copy link
Contributor

@Rajdeepc Rajdeepc left a comment

Choose a reason for hiding this comment

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

The approach is good, but the implementation needs some hardening. Let me know if you would like to discuss on any of these!

if (
event.pointerType === 'touch' &&
this.hasSubmenu &&
event.target === this &&
Copy link
Contributor

@Rajdeepc Rajdeepc Nov 14, 2025

Choose a reason for hiding this comment

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

event.target === this is brittle in shadow DOM. When the element has a shadow DOM or children inside the component) pointer events often have event.target equal to an inner element, not the host element (this). The current checks (event.target === this) will often fail and skip the intended logic.
You can use event.composedPath() or event.currentTarget instead

event.target === this &&
!this._touchListenerActive
) {
event.preventDefault(); // Prevent click suppression
Copy link
Contributor

@Rajdeepc Rajdeepc Nov 14, 2025

Choose a reason for hiding this comment

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

preventDefault on pointerdown may suppress subsequent click or change expected browser behavior. This comment is also bit misleading. Can you please explain why this is required here?

If the intent is to prevent a synthetic click from triggering the overlay twice, then you should ensure cleanup via pointerup/pointercancel and ignore click in handleSubmenuClick based on pointerType


protected handlePointerenter(): void {
protected handlePointerenter(event: PointerEvent): void {
this._lastPointerType = event.pointerType;
Copy link
Contributor

@Rajdeepc Rajdeepc Nov 14, 2025

Choose a reason for hiding this comment

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

_lastPointerType can become stale across interactions when you logic grows large and it should be scoped to the active pointer. You can use pointerId instead.

event.stopPropagation(); // Prevent bubbling to parent menu items
this._touchListenerActive = true;
this.addEventListener('pointerup', this.handleTouchSubmenuToggle, {
once: true,
Copy link
Contributor

@Rajdeepc Rajdeepc Nov 14, 2025

Choose a reason for hiding this comment

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

pointerup listener attached to this with once:true can never fire or not fire in some touch scenarios like when finger moves off the element which will result in _touchListenerActive stuck true.
You can use pointerup/pointercancel handlers on window/document or the host root to ensure cleanup, and clear them in the handler.

}

protected handleSubmenuClick(event: Event): void {
const pointerEvent = event as PointerEvent;
Copy link
Contributor

@Rajdeepc Rajdeepc Nov 14, 2025

Choose a reason for hiding this comment

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

This is unsafe casting. You are using pointerEvent.pointerType but click handlers typically receive MouseEvent or generic Event. pointerType will be undefined

Comment on lines +507 to +513
// Toggle the submenu
if (this.open) {
this.open = false;
} else {
this.openOverlay(true);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

you are doing if (this.open) this.open = false; else this.openOverlay(true); Elsewhere you call this.openOverlay(true). Please ensure that closing/opening is consistently done via one API to avoid confusion and mismatched cleanup.

@Rajdeepc Rajdeepc added the Status: Ready for review PR ready for review or re-review. label Nov 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants