@@ -195,6 +195,8 @@ export class MenuItem extends LikeAnchor(
195195
196196 private _value = '' ;
197197
198+ private _lastPointerType ?: string ;
199+
198200 /**
199201 * @private
200202 * text content of the menu item minus whitespace
@@ -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 }
@@ -594,11 +586,6 @@ export class MenuItem extends LikeAnchor(
594586 }
595587 } ;
596588
597- protected closeOverlaysForRoot ( ) : void {
598- if ( this . open ) return ;
599- this . menuData . parentMenu ?. closeDescendentOverlays ( ) ;
600- }
601-
602589 protected handleFocus ( event : FocusEvent ) : void {
603590 const { target } = event ;
604591 if ( target === this ) {
@@ -613,48 +600,64 @@ export class MenuItem extends LikeAnchor(
613600 }
614601 }
615602
616- protected handleSubmenuClick ( event : Event ) : void {
603+ protected handleSubmenuTriggerClick ( event : Event ) : void {
617604 if ( event . composedPath ( ) . includes ( this . overlayElement ) ) {
618605 return ;
619606 }
620- this . openOverlay ( true ) ;
621- }
622607
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- } ) ;
608+ // If submenu is already open, toggle it closed
609+ if ( this . open && this . _lastPointerType === 'touch' ) {
610+ event . preventDefault ( ) ;
611+ event . stopPropagation ( ) ; // Don't let parent menu handle this
612+ this . open = false ;
613+ return ;
614+ }
615+
616+ // All: open if closed
617+ if ( ! this . open ) {
618+ event . preventDefault ( ) ;
619+ event . stopImmediatePropagation ( ) ;
620+ this . openOverlay ( true ) ;
621+ }
630622 }
631623
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- ) ;
624+ protected handlePointerenter ( event : PointerEvent ) : void {
625+ this . _lastPointerType = event . pointerType ; // Track pointer type
626+
627+ // For touch: don't handle pointerenter, let click handle it
628+ if ( event . pointerType === 'touch' ) {
629+ return ;
640630 }
641- } ;
642631
643- protected handlePointerenter ( ) : void {
632+ // Close sibling submenus before opening this one
633+ this . menuData . parentMenu ?. closeDescendentOverlays ( ) ;
634+
644635 if ( this . leaveTimeout ) {
645636 clearTimeout ( this . leaveTimeout ) ;
646637 delete this . leaveTimeout ;
647638 this . recentlyLeftChild = false ;
648639 return ;
649640 }
650- this . focus ( ) ;
641+
642+ // Only focus items with submenus on hover (to show they're interactive)
643+ // Regular items should not show focus styling on hover, only on keyboard navigation
644+ if ( this . hasSubmenu ) {
645+ this . focus ( ) ;
646+ }
651647 this . openOverlay ( ) ;
652648 }
653649
654650 protected leaveTimeout ?: ReturnType < typeof setTimeout > ;
655651 protected recentlyLeftChild = false ;
656652
657- protected handlePointerleave ( ) : void {
653+ protected handlePointerleave ( event : PointerEvent ) : void {
654+ this . _lastPointerType = event . pointerType ; // Update on leave too
655+
656+ // For touch: don't handle pointerleave, let click handle it
657+ if ( event . pointerType === 'touch' ) {
658+ return ;
659+ }
660+
658661 this . _closedViaPointer = true ;
659662 if ( this . open && ! this . recentlyLeftChild ) {
660663 this . leaveTimeout = setTimeout ( ( ) => {
@@ -782,17 +785,7 @@ export class MenuItem extends LikeAnchor(
782785 const options = { signal : this . abortControllerSubmenu . signal } ;
783786 this . addEventListener (
784787 '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 ,
788+ this . handleSubmenuTriggerClick ,
796789 options
797790 ) ;
798791 this . addEventListener (
0 commit comments