@@ -34,6 +34,7 @@ import {
34
34
import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations' ;
35
35
import { ThemePalette , RippleAnimationConfig } from '@angular/material-experimental/mdc-core' ;
36
36
import { numbers } from '@material/ripple' ;
37
+ import { FocusMonitor } from '@angular/cdk/a11y' ;
37
38
import {
38
39
MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS ,
39
40
MatSlideToggleDefaultOptions ,
@@ -71,7 +72,8 @@ export class MatSlideToggleChange {
71
72
host : {
72
73
'class' : 'mat-mdc-slide-toggle' ,
73
74
'[id]' : 'id' ,
74
- '[attr.tabindex]' : 'null' ,
75
+ // Needs to be `-1` so it can still receive programmatic focus.
76
+ '[attr.tabindex]' : 'disabled ? null : -1' ,
75
77
'[attr.aria-label]' : 'null' ,
76
78
'[attr.aria-labelledby]' : 'null' ,
77
79
'[class.mat-primary]' : 'color === "primary"' ,
@@ -80,7 +82,6 @@ export class MatSlideToggleChange {
80
82
'[class.mat-mdc-slide-toggle-focused]' : '_focused' ,
81
83
'[class.mat-mdc-slide-toggle-checked]' : 'checked' ,
82
84
'[class._mat-animation-noopable]' : '_animationMode === "NoopAnimations"' ,
83
- '(focus)' : '_inputElement.nativeElement.focus()' ,
84
85
} ,
85
86
exportAs : 'matSlideToggle' ,
86
87
encapsulation : ViewEncapsulation . None ,
@@ -194,7 +195,9 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe
194
195
/** Reference to the MDC switch element. */
195
196
@ViewChild ( 'switch' ) _switchElement : ElementRef < HTMLElement > ;
196
197
197
- constructor ( private _changeDetectorRef : ChangeDetectorRef ,
198
+ constructor ( private _elementRef : ElementRef ,
199
+ private _focusMonitor : FocusMonitor ,
200
+ private _changeDetectorRef : ChangeDetectorRef ,
198
201
@Attribute ( 'tabindex' ) tabIndex : string ,
199
202
@Inject ( MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS )
200
203
public defaults : MatSlideToggleDefaultOptions ,
@@ -206,9 +209,35 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe
206
209
const foundation = this . _foundation = new MDCSwitchFoundation ( this . _adapter ) ;
207
210
foundation . setDisabled ( this . disabled ) ;
208
211
foundation . setChecked ( this . checked ) ;
212
+
213
+ this . _focusMonitor
214
+ . monitor ( this . _elementRef , true )
215
+ . subscribe ( focusOrigin => {
216
+ // Only forward focus manually when it was received programmatically or through the
217
+ // keyboard. We should not do this for mouse/touch focus for two reasons:
218
+ // 1. It can prevent clicks from landing in Chrome (see #18269).
219
+ // 2. They're already handled by the wrapping `label` element.
220
+ if ( focusOrigin === 'keyboard' || focusOrigin === 'program' ) {
221
+ this . _inputElement . nativeElement . focus ( ) ;
222
+ this . _focused = true ;
223
+ } else if ( ! focusOrigin ) {
224
+ // When a focused element becomes disabled, the browser *immediately* fires a blur event.
225
+ // Angular does not expect events to be raised during change detection, so any state
226
+ // change (such as a form control's ng-touched) will cause a changed-after-checked error.
227
+ // See https://github.com/angular/angular/issues/17793. To work around this, we defer
228
+ // telling the form control it has been touched until the next tick.
229
+ Promise . resolve ( ) . then ( ( ) => {
230
+ this . _focused = false ;
231
+ this . _onTouched ( ) ;
232
+ this . _changeDetectorRef . markForCheck ( ) ;
233
+ } ) ;
234
+ }
235
+ } ) ;
209
236
}
210
237
211
238
ngOnDestroy ( ) {
239
+ this . _focusMonitor . stopMonitoring ( this . _elementRef ) ;
240
+
212
241
if ( this . _foundation ) {
213
242
this . _foundation . destroy ( ) ;
214
243
}
@@ -285,20 +314,6 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe
285
314
this . _onChange ( this . checked ) ;
286
315
}
287
316
288
- /** Handles blur events on the native input. */
289
- _onBlur ( ) {
290
- // When a focused element becomes disabled, the browser *immediately* fires a blur event.
291
- // Angular does not expect events to be raised during change detection, so any state change
292
- // (such as a form control's 'ng-touched') will cause a changed-after-checked error.
293
- // See https://github.com/angular/angular/issues/17793. To work around this, we defer
294
- // telling the form control it has been touched until the next tick.
295
- Promise . resolve ( ) . then ( ( ) => {
296
- this . _focused = false ;
297
- this . _onTouched ( ) ;
298
- this . _changeDetectorRef . markForCheck ( ) ;
299
- } ) ;
300
- }
301
-
302
317
static ngAcceptInputType_tabIndex : NumberInput ;
303
318
static ngAcceptInputType_required : BooleanInput ;
304
319
static ngAcceptInputType_checked : BooleanInput ;
0 commit comments