@@ -18,8 +18,10 @@ import {
1818 Optional ,
1919 Input ,
2020 HostListener ,
21+ NgZone ,
2122 AfterViewInit ,
2223 ChangeDetectorRef ,
24+ ViewChild ,
2325} from '@angular/core' ;
2426import {
2527 CanDisable ,
@@ -62,6 +64,9 @@ export class MatMenuItem extends _MatMenuItemBase
6264 /** ARIA role for the menu item. */
6365 @Input ( ) role : 'menuitem' | 'menuitemradio' | 'menuitemcheckbox' = 'menuitem' ;
6466
67+ /** Reference to the element wrapping the projected content. */
68+ @ViewChild ( 'content' ) _content : ElementRef < HTMLElement > | undefined ;
69+
6570 /** Stream that emits when the menu item is hovered. */
6671 readonly _hovered : Subject < MatMenuItem > = new Subject < MatMenuItem > ( ) ;
6772
@@ -83,13 +88,15 @@ export class MatMenuItem extends _MatMenuItemBase
8388 @Inject ( DOCUMENT ) _document ?: any ,
8489 private _focusMonitor ?: FocusMonitor ,
8590 @Inject ( MAT_MENU_PANEL ) @Optional ( ) public _parentMenu ?: MatMenuPanel < MatMenuItem > ,
91+ private _ngZone ?: NgZone ,
8692 /**
8793 * @deprecated `_changeDetectorRef` to become a required parameter.
8894 * @breaking -change 14.0.0
8995 */
90- private _changeDetectorRef ?: ChangeDetectorRef ) {
96+ private _changeDetectorRef ?: ChangeDetectorRef ) {
9197
9298 // @breaking -change 8.0.0 make `_focusMonitor` and `document` required params.
99+ // @breaking -change 11.0.0 make `_ngZone` a required parameter.
93100 super ( ) ;
94101
95102 if ( _parentMenu && _parentMenu . addItem ) {
@@ -115,6 +122,13 @@ export class MatMenuItem extends _MatMenuItemBase
115122 // mouse or touch interaction.
116123 this . _focusMonitor . monitor ( this . _elementRef , false ) ;
117124 }
125+
126+ // @breaking -change 11.0.0 Remove null check for `_ngZone`.
127+ if ( this . _ngZone ) {
128+ this . _ngZone . runOutsideAngular ( ( ) => this . _bindDisabledClickEvents ( ) ) ;
129+ } else {
130+ this . _bindDisabledClickEvents ( ) ;
131+ }
118132 }
119133
120134 ngOnDestroy ( ) {
@@ -126,6 +140,11 @@ export class MatMenuItem extends _MatMenuItemBase
126140 this . _parentMenu . removeItem ( this ) ;
127141 }
128142
143+ this . _elementRef . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
144+ if ( this . _content ) {
145+ this . _content . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
146+ }
147+
129148 this . _hovered . complete ( ) ;
130149 this . _focused . complete ( ) ;
131150 }
@@ -140,20 +159,6 @@ export class MatMenuItem extends _MatMenuItemBase
140159 return this . _elementRef . nativeElement ;
141160 }
142161
143- /** Prevents the default element actions if it is disabled. */
144- // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
145- // In Ivy the `host` bindings will be merged when this class is extended, whereas in
146- // ViewEngine they're overwritten.
147- // TODO(crisbeto): we move this back into `host` once Ivy is turned on by default.
148- // tslint:disable-next-line:no-host-decorator-in-concrete
149- @HostListener ( 'click' , [ '$event' ] )
150- _checkDisabled ( event : Event ) : void {
151- if ( this . disabled ) {
152- event . preventDefault ( ) ;
153- event . stopPropagation ( ) ;
154- }
155- }
156-
157162 /** Emits to the hover stream. */
158163 // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
159164 // In Ivy the `host` bindings will be merged when this class is extended, whereas in
@@ -188,6 +193,26 @@ export class MatMenuItem extends _MatMenuItemBase
188193 this . _changeDetectorRef ?. markForCheck ( ) ;
189194 }
190195
196+ /** Binds the click events that prevent the default actions while disabled. */
197+ private _bindDisabledClickEvents ( ) {
198+ // We need to bind this event both on the root node and the content wrapper, because browsers
199+ // won't dispatch events on disabled `button` nodes, but they'll still be dispatched if the
200+ // user interacts with a non-disabled child of the button. This means that can get regions
201+ // inside a disabled menu item where clicks land and others where they don't.
202+ this . _elementRef . nativeElement . addEventListener ( 'click' , this . _preventDisabledClicks ) ;
203+ if ( this . _content ) {
204+ this . _content . nativeElement . addEventListener ( 'click' , this . _preventDisabledClicks ) ;
205+ }
206+ }
207+
208+ /** Prevents the default click action if the menu item is disabled. */
209+ private _preventDisabledClicks = ( event : Event ) => {
210+ if ( this . disabled ) {
211+ event . preventDefault ( ) ;
212+ event . stopPropagation ( ) ;
213+ }
214+ }
215+
191216 static ngAcceptInputType_disabled : BooleanInput ;
192217 static ngAcceptInputType_disableRipple : BooleanInput ;
193218}
0 commit comments