@@ -18,6 +18,9 @@ import {
18
18
Optional ,
19
19
Input ,
20
20
HostListener ,
21
+ NgZone ,
22
+ AfterViewInit ,
23
+ ViewChild ,
21
24
} from '@angular/core' ;
22
25
import {
23
26
CanDisable , CanDisableCtor ,
@@ -57,11 +60,14 @@ const _MatMenuItemMixinBase: CanDisableRippleCtor & CanDisableCtor & typeof MatM
57
60
templateUrl : 'menu-item.html' ,
58
61
} )
59
62
export class MatMenuItem extends _MatMenuItemMixinBase
60
- implements FocusableOption , CanDisable , CanDisableRipple , OnDestroy {
63
+ implements FocusableOption , CanDisable , CanDisableRipple , AfterViewInit , OnDestroy {
61
64
62
65
/** ARIA role for the menu item. */
63
66
@Input ( ) role : 'menuitem' | 'menuitemradio' | 'menuitemcheckbox' = 'menuitem' ;
64
67
68
+ /** Reference to the element wrapping the projected content. */
69
+ @ViewChild ( 'content' ) _content : ElementRef < HTMLElement > | undefined ;
70
+
65
71
private _document : Document ;
66
72
67
73
/** Stream that emits when the menu item is hovered. */
@@ -80,9 +86,11 @@ export class MatMenuItem extends _MatMenuItemMixinBase
80
86
private _elementRef : ElementRef < HTMLElement > ,
81
87
@Inject ( DOCUMENT ) document ?: any ,
82
88
private _focusMonitor ?: FocusMonitor ,
83
- @Inject ( MAT_MENU_PANEL ) @Optional ( ) public _parentMenu ?: MatMenuPanel < MatMenuItem > ) {
89
+ @Inject ( MAT_MENU_PANEL ) @Optional ( ) public _parentMenu ?: MatMenuPanel < MatMenuItem > ,
90
+ private _ngZone ?: NgZone ) {
84
91
85
92
// @breaking -change 8.0.0 make `_focusMonitor` and `document` required params.
93
+ // @breaking -change 11.0.0 make `_ngZone` a required parameter.
86
94
super ( ) ;
87
95
88
96
if ( _focusMonitor ) {
@@ -110,6 +118,15 @@ export class MatMenuItem extends _MatMenuItemMixinBase
110
118
this . _focused . next ( this ) ;
111
119
}
112
120
121
+ ngAfterViewInit ( ) {
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
+ }
128
+ }
129
+
113
130
ngOnDestroy ( ) {
114
131
if ( this . _focusMonitor ) {
115
132
this . _focusMonitor . stopMonitoring ( this . _elementRef ) ;
@@ -119,6 +136,11 @@ export class MatMenuItem extends _MatMenuItemMixinBase
119
136
this . _parentMenu . removeItem ( this ) ;
120
137
}
121
138
139
+ this . _elementRef . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
140
+ if ( this . _content ) {
141
+ this . _content . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
142
+ }
143
+
122
144
this . _hovered . complete ( ) ;
123
145
this . _focused . complete ( ) ;
124
146
}
@@ -133,20 +155,6 @@ export class MatMenuItem extends _MatMenuItemMixinBase
133
155
return this . _elementRef . nativeElement ;
134
156
}
135
157
136
- /** Prevents the default element actions if it is disabled. */
137
- // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
138
- // In Ivy the `host` bindings will be merged when this class is extended, whereas in
139
- // ViewEngine they're overwritten.
140
- // TODO(crisbeto): we move this back into `host` once Ivy is turned on by default.
141
- // tslint:disable-next-line:no-host-decorator-in-concrete
142
- @HostListener ( 'click' , [ '$event' ] )
143
- _checkDisabled ( event : Event ) : void {
144
- if ( this . disabled ) {
145
- event . preventDefault ( ) ;
146
- event . stopPropagation ( ) ;
147
- }
148
- }
149
-
150
158
/** Emits to the hover stream. */
151
159
// We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
152
160
// In Ivy the `host` bindings will be merged when this class is extended, whereas in
@@ -160,7 +168,8 @@ export class MatMenuItem extends _MatMenuItemMixinBase
160
168
161
169
/** Gets the label to be used when determining whether the option should be focused. */
162
170
getLabel ( ) : string {
163
- const element : HTMLElement = this . _elementRef . nativeElement ;
171
+ const element : HTMLElement = this . _content ?
172
+ this . _content . nativeElement : this . _elementRef . nativeElement ;
164
173
const textNodeType = this . _document ? this . _document . TEXT_NODE : 3 ;
165
174
let output = '' ;
166
175
@@ -180,6 +189,26 @@ export class MatMenuItem extends _MatMenuItemMixinBase
180
189
return output . trim ( ) ;
181
190
}
182
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
+
183
212
static ngAcceptInputType_disabled : BooleanInput ;
184
213
static ngAcceptInputType_disableRipple : BooleanInput ;
185
214
}
0 commit comments