@@ -18,7 +18,9 @@ import {
18
18
Optional ,
19
19
Input ,
20
20
HostListener ,
21
+ NgZone ,
21
22
AfterViewInit ,
23
+ ViewChild ,
22
24
} from '@angular/core' ;
23
25
import {
24
26
CanDisable , CanDisableCtor ,
@@ -63,6 +65,9 @@ export class MatMenuItem extends _MatMenuItemMixinBase
63
65
/** ARIA role for the menu item. */
64
66
@Input ( ) role : 'menuitem' | 'menuitemradio' | 'menuitemcheckbox' = 'menuitem' ;
65
67
68
+ /** Reference to the element wrapping the projected content. */
69
+ @ViewChild ( 'content' ) _content : ElementRef < HTMLElement > | undefined ;
70
+
66
71
private _document : Document ;
67
72
68
73
/** Stream that emits when the menu item is hovered. */
@@ -81,9 +86,11 @@ export class MatMenuItem extends _MatMenuItemMixinBase
81
86
private _elementRef : ElementRef < HTMLElement > ,
82
87
@Inject ( DOCUMENT ) document ?: any ,
83
88
private _focusMonitor ?: FocusMonitor ,
84
- @Inject ( MAT_MENU_PANEL ) @Optional ( ) public _parentMenu ?: MatMenuPanel < MatMenuItem > ) {
89
+ @Inject ( MAT_MENU_PANEL ) @Optional ( ) public _parentMenu ?: MatMenuPanel < MatMenuItem > ,
90
+ private _ngZone ?: NgZone ) {
85
91
86
92
// @breaking -change 8.0.0 make `_focusMonitor` and `document` required params.
93
+ // @breaking -change 11.0.0 make `_ngZone` a required parameter.
87
94
super ( ) ;
88
95
89
96
if ( _parentMenu && _parentMenu . addItem ) {
@@ -111,6 +118,13 @@ export class MatMenuItem extends _MatMenuItemMixinBase
111
118
// mouse or touch interaction.
112
119
this . _focusMonitor . monitor ( this . _elementRef , false ) ;
113
120
}
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
+ }
114
128
}
115
129
116
130
ngOnDestroy ( ) {
@@ -122,6 +136,11 @@ export class MatMenuItem extends _MatMenuItemMixinBase
122
136
this . _parentMenu . removeItem ( this ) ;
123
137
}
124
138
139
+ this . _elementRef . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
140
+ if ( this . _content ) {
141
+ this . _content . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
142
+ }
143
+
125
144
this . _hovered . complete ( ) ;
126
145
this . _focused . complete ( ) ;
127
146
}
@@ -136,20 +155,6 @@ export class MatMenuItem extends _MatMenuItemMixinBase
136
155
return this . _elementRef . nativeElement ;
137
156
}
138
157
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
-
153
158
/** Emits to the hover stream. */
154
159
// We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
155
160
// In Ivy the `host` bindings will be merged when this class is extended, whereas in
@@ -163,7 +168,8 @@ export class MatMenuItem extends _MatMenuItemMixinBase
163
168
164
169
/** Gets the label to be used when determining whether the option should be focused. */
165
170
getLabel ( ) : string {
166
- const element : HTMLElement = this . _elementRef . nativeElement ;
171
+ const element : HTMLElement = this . _content ?
172
+ this . _content . nativeElement : this . _elementRef . nativeElement ;
167
173
const textNodeType = this . _document ? this . _document . TEXT_NODE : 3 ;
168
174
let output = '' ;
169
175
@@ -183,6 +189,26 @@ export class MatMenuItem extends _MatMenuItemMixinBase
183
189
return output . trim ( ) ;
184
190
}
185
191
192
+ /** Binds the click events that prevent the default actions while disabled. */
193
+ private _bindDisabledClickEvents ( ) {
194
+ // We need to bind this event both on the root node and the content wrapper, because browsers
195
+ // won't dispatch events on disabled `button` nodes, but they'll still be dispatched if the
196
+ // user interacts with a non-disabled child of the button. This means that can get regions
197
+ // inside a disabled menu item where clicks land and others where they don't.
198
+ this . _elementRef . nativeElement . addEventListener ( 'click' , this . _preventDisabledClicks ) ;
199
+ if ( this . _content ) {
200
+ this . _content . nativeElement . addEventListener ( 'click' , this . _preventDisabledClicks ) ;
201
+ }
202
+ }
203
+
204
+ /** Prevents the default click action if the menu item is disabled. */
205
+ private _preventDisabledClicks = ( event : Event ) => {
206
+ if ( this . disabled ) {
207
+ event . preventDefault ( ) ;
208
+ event . stopPropagation ( ) ;
209
+ }
210
+ }
211
+
186
212
static ngAcceptInputType_disabled : BooleanInput ;
187
213
static ngAcceptInputType_disableRipple : BooleanInput ;
188
214
}
0 commit comments