@@ -181,8 +181,6 @@ export class MenuItem extends LikeAnchor(
181181 return this . _value || this . itemText ;
182182 }
183183
184- private _lastPointerType ?: string ;
185-
186184 public set value ( value : string ) {
187185 if ( value === this . _value ) {
188186 return ;
@@ -322,6 +320,13 @@ export class MenuItem extends LikeAnchor(
322320 */
323321 private _closedViaPointer = false ;
324322
323+ /**
324+ * Touch interaction state for submenu toggling
325+ */
326+ private _activePointerId ?: number ;
327+ private _touchHandledViaPointerup = false ;
328+ private _touchAbortController ?: AbortController ;
329+
325330 private handleClickCapture ( event : Event ) : void | boolean {
326331 if ( this . disabled ) {
327332 event . preventDefault ( ) ;
@@ -459,57 +464,74 @@ export class MenuItem extends LikeAnchor(
459464 }
460465 }
461466
462- private _touchListenerActive = false ;
463-
464467 private handlePointerdown ( event : PointerEvent ) : void {
465- // Track pointer type for touch detection
466- this . _lastPointerType = event . pointerType ;
468+ const path = event . composedPath ( ) ;
469+ const targetIsInOverlay =
470+ this . overlayElement && path . includes ( this . overlayElement ) ;
467471
468- // For touch devices with submenus, handle on pointerup instead of click
469- // Only if the touch is directly on this menu item (not on overlay or child elements)
470472 if (
471473 event . pointerType === 'touch' &&
472474 this . hasSubmenu &&
473- event . target === this &&
474- ! this . _touchListenerActive
475+ ! targetIsInOverlay &&
476+ this . _activePointerId === undefined
475477 ) {
476- event . preventDefault ( ) ; // Prevent click suppression
477- event . stopPropagation ( ) ; // Prevent bubbling to parent menu items
478- this . _touchListenerActive = true ;
479- this . addEventListener ( 'pointerup' , this . handleTouchSubmenuToggle , {
478+ this . _activePointerId = event . pointerId ;
479+ this . _touchAbortController = new AbortController ( ) ;
480+
481+ window . addEventListener (
482+ 'pointerup' ,
483+ this . handleTouchSubmenuToggle ,
484+ { once : true , signal : this . _touchAbortController . signal }
485+ ) ;
486+ window . addEventListener ( 'pointercancel' , this . handleTouchCleanup , {
480487 once : true ,
488+ signal : this . _touchAbortController . signal ,
481489 } ) ;
482490 }
483491
484492 if (
485- event . target === this &&
493+ ! targetIsInOverlay &&
486494 this . hasSubmenu &&
487495 this . open &&
488496 event . pointerType !== 'touch'
489497 ) {
490498 this . addEventListener ( 'focus' , this . handleSubmenuFocus , {
491499 once : true ,
492500 } ) ;
493- this . overlayElement . addEventListener (
501+ this . overlayElement ? .addEventListener (
494502 'beforetoggle' ,
495503 this . handleBeforetoggle
496504 ) ;
497505 }
498506 }
499507
500508 private handleTouchSubmenuToggle = ( event : PointerEvent ) : void => {
501- event . preventDefault ( ) ;
502- event . stopPropagation ( ) ;
509+ if ( event . pointerId !== this . _activePointerId ) {
510+ return ;
511+ }
503512
504- // Reset the listener flag
505- this . _touchListenerActive = false ;
513+ this . _touchAbortController ?. abort ( ) ;
514+ this . _touchHandledViaPointerup = true ;
515+ this . _activePointerId = undefined ;
506516
507- // Toggle the submenu
508517 if ( this . open ) {
509518 this . open = false ;
510519 } else {
511- this . openOverlay ( true ) ;
520+ this . openOverlay ( ) ;
512521 }
522+
523+ setTimeout ( ( ) => {
524+ this . _touchHandledViaPointerup = false ;
525+ } , 0 ) ;
526+ } ;
527+
528+ private handleTouchCleanup = ( event : PointerEvent ) : void => {
529+ if ( event . pointerId !== this . _activePointerId ) {
530+ return ;
531+ }
532+ this . _touchAbortController ?. abort ( ) ;
533+ this . _activePointerId = undefined ;
534+ this . _touchHandledViaPointerup = false ;
513535 } ;
514536
515537 protected override firstUpdated ( changes : PropertyValues ) : void {
@@ -657,14 +679,7 @@ export class MenuItem extends LikeAnchor(
657679 }
658680
659681 protected handleSubmenuClick ( event : Event ) : void {
660- const pointerEvent = event as PointerEvent ;
661-
662- const isTouchEvent =
663- pointerEvent . pointerType === 'touch' ||
664- this . _lastPointerType === 'touch' ;
665-
666- // For touch events, completely ignore click
667- if ( isTouchEvent ) {
682+ if ( this . _touchHandledViaPointerup ) {
668683 event . stopPropagation ( ) ;
669684 event . preventDefault ( ) ;
670685 return ;
@@ -698,8 +713,6 @@ export class MenuItem extends LikeAnchor(
698713 } ;
699714
700715 protected handlePointerenter ( event : PointerEvent ) : void {
701- this . _lastPointerType = event . pointerType ;
702-
703716 // For touch devices, don't open on pointerenter - let click handle it
704717 if ( event . pointerType === 'touch' ) {
705718 return ;
@@ -719,8 +732,6 @@ export class MenuItem extends LikeAnchor(
719732 protected recentlyLeftChild = false ;
720733
721734 protected handlePointerleave ( event : PointerEvent ) : void {
722- this . _lastPointerType = event . pointerType ;
723-
724735 // For touch devices, don't close on pointerleave - let click handle it
725736 if ( event . pointerType === 'touch' ) {
726737 return ;
@@ -892,6 +903,12 @@ export class MenuItem extends LikeAnchor(
892903 selectionRoot : undefined ,
893904 cleanupSteps : [ ] ,
894905 } ;
906+
907+ // Clean up any active touch listeners
908+ this . _touchAbortController ?. abort ( ) ;
909+ this . _activePointerId = undefined ;
910+ this . _touchHandledViaPointerup = false ;
911+
895912 super . disconnectedCallback ( ) ;
896913 }
897914
0 commit comments