@@ -53,7 +53,7 @@ import {
53
53
} from '@angular/material/core' ;
54
54
55
55
import { Subject } from 'rxjs' ;
56
- import { takeUntil } from 'rxjs/operators' ;
56
+ import { startWith , takeUntil } from 'rxjs/operators' ;
57
57
58
58
import { MatListAvatarCssMatStyler , MatListIconCssMatStyler } from './list' ;
59
59
@@ -99,7 +99,6 @@ export class MatSelectionListChange {
99
99
'(focus)' : '_handleFocus()' ,
100
100
'(blur)' : '_handleBlur()' ,
101
101
'(click)' : '_handleClick()' ,
102
- 'tabindex' : '-1' ,
103
102
'[class.mat-list-item-disabled]' : 'disabled' ,
104
103
'[class.mat-list-item-with-avatar]' : '_avatar || _icon' ,
105
104
// Manually set the "primary" or "warn" class if the color has been explicitly
@@ -113,6 +112,7 @@ export class MatSelectionListChange {
113
112
'[class.mat-list-single-selected-option]' : 'selected && !selectionList.multiple' ,
114
113
'[attr.aria-selected]' : 'selected' ,
115
114
'[attr.aria-disabled]' : 'disabled' ,
115
+ '[attr.tabindex]' : '-1' ,
116
116
} ,
117
117
templateUrl : 'list-option.html' ,
118
118
encapsulation : ViewEncapsulation . None ,
@@ -323,12 +323,13 @@ export class MatListOption extends _MatListOptionMixinBase implements AfterConte
323
323
inputs : [ 'disableRipple' ] ,
324
324
host : {
325
325
'role' : 'listbox' ,
326
- '[tabIndex]' : 'tabIndex' ,
327
326
'class' : 'mat-selection-list mat-list-base' ,
327
+ '(focus)' : '_onFocus()' ,
328
328
'(blur)' : '_onTouched()' ,
329
329
'(keydown)' : '_keydown($event)' ,
330
330
'[attr.aria-multiselectable]' : 'multiple' ,
331
331
'[attr.aria-disabled]' : 'disabled.toString()' ,
332
+ '[attr.tabindex]' : '_tabIndex' ,
332
333
} ,
333
334
template : '<ng-content></ng-content>' ,
334
335
styleUrls : [ 'list.css' ] ,
@@ -351,7 +352,10 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
351
352
@Output ( ) readonly selectionChange : EventEmitter < MatSelectionListChange > =
352
353
new EventEmitter < MatSelectionListChange > ( ) ;
353
354
354
- /** Tabindex of the selection list. */
355
+ /**
356
+ * Tabindex of the selection list.
357
+ * @breaking -change 11.0.0 Remove `tabIndex` input.
358
+ */
355
359
@Input ( ) tabIndex : number = 0 ;
356
360
357
361
/** Theme color of the selection list. This sets the checkbox color for all list options. */
@@ -398,6 +402,9 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
398
402
/** The currently selected options. */
399
403
selectedOptions = new SelectionModel < MatListOption > ( this . _multiple ) ;
400
404
405
+ /** The tabindex of the selection list. */
406
+ _tabIndex = - 1 ;
407
+
401
408
/** View to model callback that should be called whenever the selected options change. */
402
409
private _onChange : ( value : any ) => void = ( _ : any ) => { } ;
403
410
@@ -413,9 +420,11 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
413
420
/** Whether the list has been destroyed. */
414
421
private _isDestroyed : boolean ;
415
422
416
- constructor ( private _element : ElementRef < HTMLElement > , @Attribute ( 'tabindex' ) tabIndex : string ) {
423
+ constructor ( private _element : ElementRef < HTMLElement > ,
424
+ // @breaking -change 11.0.0 Remove `tabIndex` parameter.
425
+ @Attribute ( 'tabindex' ) tabIndex : string ,
426
+ private _changeDetector : ChangeDetectorRef ) {
417
427
super ( ) ;
418
- this . tabIndex = parseInt ( tabIndex ) || 0 ;
419
428
}
420
429
421
430
ngAfterContentInit ( ) : void {
@@ -433,6 +442,16 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
433
442
this . _setOptionsFromValues ( this . _value ) ;
434
443
}
435
444
445
+ // If the user attempts to tab out of the selection list, allow focus to escape.
446
+ this . _keyManager . tabOut . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => {
447
+ this . _allowFocusEscape ( ) ;
448
+ } ) ;
449
+
450
+ // When the number of options change, update the tabindex of the selection list.
451
+ this . options . changes . pipe ( startWith ( null ) , takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => {
452
+ this . _updateTabIndex ( ) ;
453
+ } ) ;
454
+
436
455
// Sync external changes to the model back to the options.
437
456
this . selectedOptions . changed . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( event => {
438
457
if ( event . added ) {
@@ -560,6 +579,22 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
560
579
this . selectionChange . emit ( new MatSelectionListChange ( this , option ) ) ;
561
580
}
562
581
582
+ /**
583
+ * When the selection list is focused, we want to move focus to an option within the list. Do this
584
+ * by setting the appropriate option to be active.
585
+ */
586
+ _onFocus ( ) : void {
587
+ const activeIndex = this . _keyManager . activeItemIndex ;
588
+
589
+ if ( ! activeIndex || ( activeIndex === - 1 ) ) {
590
+ // If there is no active index, set focus to the first option.
591
+ this . _keyManager . setFirstItemActive ( ) ;
592
+ } else {
593
+ // Otherwise, set focus to the active option.
594
+ this . _keyManager . setActiveItem ( activeIndex ) ;
595
+ }
596
+ }
597
+
563
598
/** Implemented as part of ControlValueAccessor. */
564
599
writeValue ( values : string [ ] ) : void {
565
600
this . _value = values ;
@@ -664,6 +699,25 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
664
699
}
665
700
}
666
701
702
+ /**
703
+ * Removes the tabindex from the selection list and resets it back afterwards, allowing the user
704
+ * to tab out of it. This prevents the list from capturing focus and redirecting it back within
705
+ * the list, creating a focus trap if it user tries to tab away.
706
+ */
707
+ private _allowFocusEscape ( ) {
708
+ this . _tabIndex = - 1 ;
709
+
710
+ setTimeout ( ( ) => {
711
+ this . _tabIndex = 0 ;
712
+ this . _changeDetector . markForCheck ( ) ;
713
+ } ) ;
714
+ }
715
+
716
+ /** Updates the tabindex based upon if the selection list is empty. */
717
+ private _updateTabIndex ( ) : void {
718
+ this . _tabIndex = ( this . options . length === 0 ) ? - 1 : 0 ;
719
+ }
720
+
667
721
static ngAcceptInputType_disabled : BooleanInput ;
668
722
static ngAcceptInputType_disableRipple : BooleanInput ;
669
723
static ngAcceptInputType_multiple : BooleanInput ;
0 commit comments