@@ -18,7 +18,9 @@ import {
1818 Optional ,
1919 Input ,
2020 HostListener ,
21+ NgZone ,
2122 AfterViewInit ,
23+ ViewChild ,
2224} from '@angular/core' ;
2325import {
2426 CanDisable , CanDisableCtor ,
@@ -63,6 +65,9 @@ export class MatMenuItem extends _MatMenuItemMixinBase
6365 /** ARIA role for the menu item. */
6466 @Input ( ) role : 'menuitem' | 'menuitemradio' | 'menuitemcheckbox' = 'menuitem' ;
6567
68+ /** Reference to the element wrapping the projected content. */
69+ @ViewChild ( 'content' ) _content : ElementRef < HTMLElement > | undefined ;
70+
6671 /** Stream that emits when the menu item is hovered. */
6772 readonly _hovered : Subject < MatMenuItem > = new Subject < MatMenuItem > ( ) ;
6873
@@ -83,9 +88,11 @@ export class MatMenuItem extends _MatMenuItemMixinBase
8388 */
8489 @Inject ( DOCUMENT ) _document ?: any ,
8590 private _focusMonitor ?: FocusMonitor ,
86- @Inject ( MAT_MENU_PANEL ) @Optional ( ) public _parentMenu ?: MatMenuPanel < MatMenuItem > ) {
91+ @Inject ( MAT_MENU_PANEL ) @Optional ( ) public _parentMenu ?: MatMenuPanel < MatMenuItem > ,
92+ private _ngZone ?: NgZone ) {
8793
8894 // @breaking -change 8.0.0 make `_focusMonitor` and `document` required params.
95+ // @breaking -change 11.0.0 make `_ngZone` a required parameter.
8996 super ( ) ;
9097
9198 if ( _parentMenu && _parentMenu . addItem ) {
@@ -111,6 +118,13 @@ export class MatMenuItem extends _MatMenuItemMixinBase
111118 // mouse or touch interaction.
112119 this . _focusMonitor . monitor ( this . _elementRef , false ) ;
113120 }
121+
122+ // @breaking -change 11.0.0 Remove null check for `_ngZone`.
123+ if ( this . _ngZone ) {
124+ this . _ngZone . runOutsideAngular ( ( ) => this . _bindDisabledClickEvents ( ) ) ;
125+ } else {
126+ this . _bindDisabledClickEvents ( ) ;
127+ }
114128 }
115129
116130 ngOnDestroy ( ) {
@@ -122,6 +136,11 @@ export class MatMenuItem extends _MatMenuItemMixinBase
122136 this . _parentMenu . removeItem ( this ) ;
123137 }
124138
139+ this . _elementRef . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
140+ if ( this . _content ) {
141+ this . _content . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
142+ }
143+
125144 this . _hovered . complete ( ) ;
126145 this . _focused . complete ( ) ;
127146 }
@@ -136,20 +155,6 @@ export class MatMenuItem extends _MatMenuItemMixinBase
136155 return this . _elementRef . nativeElement ;
137156 }
138157
139- /** Prevents the default element actions if it is disabled. */
140- // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
141- // In Ivy the `host` bindings will be merged when this class is extended, whereas in
142- // ViewEngine they're overwritten.
143- // TODO(crisbeto): we move this back into `host` once Ivy is turned on by default.
144- // tslint:disable-next-line:no-host-decorator-in-concrete
145- @HostListener ( 'click' , [ '$event' ] )
146- _checkDisabled ( event : Event ) : void {
147- if ( this . disabled ) {
148- event . preventDefault ( ) ;
149- event . stopPropagation ( ) ;
150- }
151- }
152-
153158 /** Emits to the hover stream. */
154159 // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
155160 // In Ivy the `host` bindings will be merged when this class is extended, whereas in
@@ -175,6 +180,26 @@ export class MatMenuItem extends _MatMenuItemMixinBase
175180 return clone . textContent ?. trim ( ) || '' ;
176181 }
177182
183+ /** Binds the click events that prevent the default actions while disabled. */
184+ private _bindDisabledClickEvents ( ) {
185+ // We need to bind this event both on the root node and the content wrapper, because browsers
186+ // won't dispatch events on disabled `button` nodes, but they'll still be dispatched if the
187+ // user interacts with a non-disabled child of the button. This means that can get regions
188+ // inside a disabled menu item where clicks land and others where they don't.
189+ this . _elementRef . nativeElement . addEventListener ( 'click' , this . _preventDisabledClicks ) ;
190+ if ( this . _content ) {
191+ this . _content . nativeElement . addEventListener ( 'click' , this . _preventDisabledClicks ) ;
192+ }
193+ }
194+
195+ /** Prevents the default click action if the menu item is disabled. */
196+ private _preventDisabledClicks = ( event : Event ) => {
197+ if ( this . disabled ) {
198+ event . preventDefault ( ) ;
199+ event . stopPropagation ( ) ;
200+ }
201+ }
202+
178203 static ngAcceptInputType_disabled : BooleanInput ;
179204 static ngAcceptInputType_disableRipple : BooleanInput ;
180205}
0 commit comments