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