@@ -195,8 +195,6 @@ export class MenuItem extends LikeAnchor(
195195
196196 private _value = '' ;
197197
198- private _lastPointerType ?: string ;
199-
200198 /**
201199 * @private
202200 * text content of the menu item minus whitespace
@@ -459,15 +457,25 @@ export class MenuItem extends LikeAnchor(
459457 }
460458 }
461459
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+
462472 protected override firstUpdated ( changes : PropertyValues ) : void {
463473 super . firstUpdated ( changes ) ;
464474 this . setAttribute ( 'tabindex' , '-1' ) ;
465475 this . addEventListener ( 'keydown' , this . handleKeydown ) ;
466476 this . addEventListener ( 'mouseover' , this . handleMouseover ) ;
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 ) ;
477+ this . addEventListener ( 'pointerdown' , this . handlePointerdown ) ;
478+ this . addEventListener ( 'pointerenter' , this . closeOverlaysForRoot ) ;
471479 if ( ! this . hasAttribute ( 'id' ) ) {
472480 this . id = `sp-menu-item-${ randomID ( ) } ` ;
473481 }
@@ -586,6 +594,11 @@ export class MenuItem extends LikeAnchor(
586594 }
587595 } ;
588596
597+ protected closeOverlaysForRoot ( ) : void {
598+ if ( this . open ) return ;
599+ this . menuData . parentMenu ?. closeDescendentOverlays ( ) ;
600+ }
601+
589602 protected handleFocus ( event : FocusEvent ) : void {
590603 const { target } = event ;
591604 if ( target === this ) {
@@ -600,64 +613,48 @@ export class MenuItem extends LikeAnchor(
600613 }
601614 }
602615
603- protected handleSubmenuTriggerClick ( event : Event ) : void {
616+ protected handleSubmenuClick ( event : Event ) : void {
604617 if ( event . composedPath ( ) . includes ( this . overlayElement ) ) {
605618 return ;
606619 }
607-
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- }
620+ this . openOverlay ( true ) ;
622621 }
623622
624- protected handlePointerenter ( event : PointerEvent ) : void {
625- this . _lastPointerType = event . pointerType ; // Track pointer type
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+ } ) ;
630+ }
626631
627- // For touch: don't handle pointerenter, let click handle it
628- if ( event . pointerType === 'touch' ) {
629- return ;
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+ ) ;
630640 }
641+ } ;
631642
632- // Close sibling submenus before opening this one
633- this . menuData . parentMenu ?. closeDescendentOverlays ( ) ;
634-
643+ protected handlePointerenter ( ) : void {
635644 if ( this . leaveTimeout ) {
636645 clearTimeout ( this . leaveTimeout ) ;
637646 delete this . leaveTimeout ;
638647 this . recentlyLeftChild = false ;
639648 return ;
640649 }
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- }
650+ this . focus ( ) ;
647651 this . openOverlay ( ) ;
648652 }
649653
650654 protected leaveTimeout ?: ReturnType < typeof setTimeout > ;
651655 protected recentlyLeftChild = false ;
652656
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-
657+ protected handlePointerleave ( ) : void {
661658 this . _closedViaPointer = true ;
662659 if ( this . open && ! this . recentlyLeftChild ) {
663660 this . leaveTimeout = setTimeout ( ( ) => {
@@ -785,7 +782,17 @@ export class MenuItem extends LikeAnchor(
785782 const options = { signal : this . abortControllerSubmenu . signal } ;
786783 this . addEventListener (
787784 'click' ,
788- this . handleSubmenuTriggerClick ,
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 ,
789796 options
790797 ) ;
791798 this . addEventListener (
0 commit comments