Skip to content

Commit bdbf0df

Browse files
author
Shipra Gupta
committed
fix: improve touch interaction handling for submenus
1 parent 7af5e8f commit bdbf0df

File tree

1 file changed

+46
-52
lines changed

1 file changed

+46
-52
lines changed

1st-gen/packages/menu/src/MenuItem.ts

Lines changed: 46 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ export class MenuItem extends LikeAnchor(
181181
return this._value || this.itemText;
182182
}
183183

184+
private _lastPointerType?: string;
185+
184186
public set value(value: string) {
185187
if (value === this._value) {
186188
return;
@@ -457,25 +459,15 @@ export class MenuItem extends LikeAnchor(
457459
}
458460
}
459461

460-
private handlePointerdown(event: PointerEvent): void {
461-
if (event.target === this && this.hasSubmenu && this.open) {
462-
this.addEventListener('focus', this.handleSubmenuFocus, {
463-
once: true,
464-
});
465-
this.overlayElement.addEventListener(
466-
'beforetoggle',
467-
this.handleBeforetoggle
468-
);
469-
}
470-
}
471-
472462
protected override firstUpdated(changes: PropertyValues): void {
473463
super.firstUpdated(changes);
474464
this.setAttribute('tabindex', '-1');
475465
this.addEventListener('keydown', this.handleKeydown);
476466
this.addEventListener('mouseover', this.handleMouseover);
477-
this.addEventListener('pointerdown', this.handlePointerdown);
478-
this.addEventListener('pointerenter', this.closeOverlaysForRoot);
467+
// Register pointerenter/leave for ALL menu items (not just those with submenus)
468+
// so items without submenus can close sibling submenus when hovered
469+
this.addEventListener('pointerenter', this.handlePointerenter);
470+
this.addEventListener('pointerleave', this.handlePointerleave);
479471
if (!this.hasAttribute('id')) {
480472
this.id = `sp-menu-item-${randomID()}`;
481473
}
@@ -575,6 +567,7 @@ export class MenuItem extends LikeAnchor(
575567

576568
return false;
577569
}
570+
578571
/**
579572
* forward key info from keydown event to parent menu
580573
*/
@@ -594,11 +587,6 @@ export class MenuItem extends LikeAnchor(
594587
}
595588
};
596589

597-
protected closeOverlaysForRoot(): void {
598-
if (this.open) return;
599-
this.menuData.parentMenu?.closeDescendentOverlays();
600-
}
601-
602590
protected handleFocus(event: FocusEvent): void {
603591
const { target } = event;
604592
if (target === this) {
@@ -613,48 +601,64 @@ export class MenuItem extends LikeAnchor(
613601
}
614602
}
615603

616-
protected handleSubmenuClick(event: Event): void {
604+
protected handleSubmenuTriggerClick(event: Event): void {
617605
if (event.composedPath().includes(this.overlayElement)) {
618606
return;
619607
}
620-
this.openOverlay(true);
621-
}
622608

623-
protected handleSubmenuFocus(): void {
624-
requestAnimationFrame(() => {
625-
// Wait till after `closeDescendentOverlays` has happened in Menu
626-
// to reopen (keep open) the direct descendent of this Menu Item
627-
this.overlayElement.open = this.open;
628-
this.focused = false;
629-
});
609+
// If submenu is already open, toggle it closed
610+
if (this.open && this._lastPointerType === 'touch') {
611+
event.preventDefault();
612+
event.stopPropagation(); // Don't let parent menu handle this
613+
this.open = false;
614+
return;
615+
}
616+
617+
// All: open if closed
618+
if (!this.open) {
619+
event.preventDefault();
620+
event.stopImmediatePropagation();
621+
this.openOverlay(true);
622+
}
630623
}
631624

632-
protected handleBeforetoggle = (event: Event): void => {
633-
if ((event as Event & { newState: string }).newState === 'closed') {
634-
this.open = true;
635-
this.overlayElement.manuallyKeepOpen();
636-
this.overlayElement.removeEventListener(
637-
'beforetoggle',
638-
this.handleBeforetoggle
639-
);
625+
protected handlePointerenter(event: PointerEvent): void {
626+
this._lastPointerType = event.pointerType; // Track pointer type
627+
628+
// For touch: don't handle pointerenter, let click handle it
629+
if (event.pointerType === 'touch') {
630+
return;
640631
}
641-
};
642632

643-
protected handlePointerenter(): void {
633+
// Close sibling submenus before opening this one
634+
this.menuData.parentMenu?.closeDescendentOverlays();
635+
644636
if (this.leaveTimeout) {
645637
clearTimeout(this.leaveTimeout);
646638
delete this.leaveTimeout;
647639
this.recentlyLeftChild = false;
648640
return;
649641
}
650-
this.focus();
642+
643+
// Only focus items with submenus on hover (to show they're interactive)
644+
// Regular items should not show focus styling on hover, only on keyboard navigation
645+
if (this.hasSubmenu) {
646+
this.focus();
647+
}
651648
this.openOverlay();
652649
}
653650

654651
protected leaveTimeout?: ReturnType<typeof setTimeout>;
655652
protected recentlyLeftChild = false;
656653

657-
protected handlePointerleave(): void {
654+
protected handlePointerleave(event: PointerEvent): void {
655+
this._lastPointerType = event.pointerType; // Update on leave too
656+
657+
// For touch: don't handle pointerleave, let click handle it
658+
if (event.pointerType === 'touch') {
659+
return;
660+
}
661+
658662
this._closedViaPointer = true;
659663
if (this.open && !this.recentlyLeftChild) {
660664
this.leaveTimeout = setTimeout(() => {
@@ -782,17 +786,7 @@ export class MenuItem extends LikeAnchor(
782786
const options = { signal: this.abortControllerSubmenu.signal };
783787
this.addEventListener(
784788
'click',
785-
this.handleSubmenuClick,
786-
options
787-
);
788-
this.addEventListener(
789-
'pointerenter',
790-
this.handlePointerenter,
791-
options
792-
);
793-
this.addEventListener(
794-
'pointerleave',
795-
this.handlePointerleave,
789+
this.handleSubmenuTriggerClick,
796790
options
797791
);
798792
this.addEventListener(

0 commit comments

Comments
 (0)