@@ -51,7 +51,7 @@ import {
5151 ThemePalette ,
5252} from '@angular/material/core' ;
5353import { Subject } from 'rxjs' ;
54- import { takeUntil } from 'rxjs/operators' ;
54+ import { startWith , takeUntil } from 'rxjs/operators' ;
5555
5656import { MatListAvatarCssMatStyler , MatListIconCssMatStyler } from './list' ;
5757
@@ -97,7 +97,6 @@ export class MatSelectionListChange {
9797 '(focus)' : '_handleFocus()' ,
9898 '(blur)' : '_handleBlur()' ,
9999 '(click)' : '_handleClick()' ,
100- 'tabindex' : '-1' ,
101100 '[class.mat-list-item-disabled]' : 'disabled' ,
102101 '[class.mat-list-item-with-avatar]' : '_avatar || _icon' ,
103102 // Manually set the "primary" or "warn" class if the color has been explicitly
@@ -110,6 +109,7 @@ export class MatSelectionListChange {
110109 '[class.mat-warn]' : 'color === "warn"' ,
111110 '[attr.aria-selected]' : 'selected' ,
112111 '[attr.aria-disabled]' : 'disabled' ,
112+ '[attr.tabindex]' : '-1' ,
113113 } ,
114114 templateUrl : 'list-option.html' ,
115115 encapsulation : ViewEncapsulation . None ,
@@ -320,12 +320,13 @@ export class MatListOption extends _MatListOptionMixinBase implements AfterConte
320320 inputs : [ 'disableRipple' ] ,
321321 host : {
322322 'role' : 'listbox' ,
323- '[tabIndex]' : 'tabIndex' ,
324323 'class' : 'mat-selection-list mat-list-base' ,
324+ '(focus)' : '_onFocus()' ,
325325 '(blur)' : '_onTouched()' ,
326326 '(keydown)' : '_keydown($event)' ,
327327 'aria-multiselectable' : 'true' ,
328328 '[attr.aria-disabled]' : 'disabled.toString()' ,
329+ '[attr.tabindex]' : '_tabIndex' ,
329330 } ,
330331 template : '<ng-content></ng-content>' ,
331332 styleUrls : [ 'list.css' ] ,
@@ -346,7 +347,10 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
346347 @Output ( ) readonly selectionChange : EventEmitter < MatSelectionListChange > =
347348 new EventEmitter < MatSelectionListChange > ( ) ;
348349
349- /** Tabindex of the selection list. */
350+ /**
351+ * Tabindex of the selection list.
352+ * @breaking -change 11.0.0 Remove `tabIndex` input.
353+ */
350354 @Input ( ) tabIndex : number = 0 ;
351355
352356 /** Theme color of the selection list. This sets the checkbox color for all list options. */
@@ -376,6 +380,9 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
376380 /** The currently selected options. */
377381 selectedOptions : SelectionModel < MatListOption > = new SelectionModel < MatListOption > ( true ) ;
378382
383+ /** The tabindex of the selection list. */
384+ _tabIndex = - 1 ;
385+
379386 /** View to model callback that should be called whenever the selected options change. */
380387 private _onChange : ( value : any ) => void = ( _ : any ) => { } ;
381388
@@ -391,9 +398,11 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
391398 /** Whether the list has been destroyed. */
392399 private _isDestroyed : boolean ;
393400
394- constructor ( private _element : ElementRef < HTMLElement > , @Attribute ( 'tabindex' ) tabIndex : string ) {
401+ constructor ( private _element : ElementRef < HTMLElement > ,
402+ // @breaking -change 11.0.0 Remove `tabIndex` parameter.
403+ @Attribute ( 'tabindex' ) tabIndex : string ,
404+ private _changeDetector : ChangeDetectorRef ) {
395405 super ( ) ;
396- this . tabIndex = parseInt ( tabIndex ) || 0 ;
397406 }
398407
399408 ngAfterContentInit ( ) : void {
@@ -409,6 +418,16 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
409418 this . _setOptionsFromValues ( this . _value ) ;
410419 }
411420
421+ // If the user attempts to tab out of the selection list, allow focus to escape.
422+ this . _keyManager . tabOut . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => {
423+ this . _allowFocusEscape ( ) ;
424+ } ) ;
425+
426+ // When the number of options change, update the tabindex of the selection list.
427+ this . options . changes . pipe ( startWith ( null ) , takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => {
428+ this . _updateTabIndex ( ) ;
429+ } ) ;
430+
412431 // Sync external changes to the model back to the options.
413432 this . selectedOptions . changed . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( event => {
414433 if ( event . added ) {
@@ -536,6 +555,22 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
536555 this . selectionChange . emit ( new MatSelectionListChange ( this , option ) ) ;
537556 }
538557
558+ /**
559+ * When the selection list is focused, we want to move focus to an option within the list. Do this
560+ * by setting the appropriate option to be active.
561+ */
562+ _onFocus ( ) : void {
563+ const activeIndex = this . _keyManager . activeItemIndex ;
564+
565+ if ( ! activeIndex || ( activeIndex === - 1 ) ) {
566+ // If there is no active index, set focus to the first option.
567+ this . _keyManager . setFirstItemActive ( ) ;
568+ } else {
569+ // Otherwise, set focus to the active option.
570+ this . _keyManager . setActiveItem ( activeIndex ) ;
571+ }
572+ }
573+
539574 /** Implemented as part of ControlValueAccessor. */
540575 writeValue ( values : string [ ] ) : void {
541576 this . _value = values ;
@@ -640,6 +675,25 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
640675 }
641676 }
642677
678+ /**
679+ * Removes the tabindex from the selection list and resets it back afterwards, allowing the user
680+ * to tab out of it. This prevents the list from capturing focus and redirecting it back within
681+ * the list, creating a focus trap if it user tries to tab away.
682+ */
683+ private _allowFocusEscape ( ) {
684+ this . _tabIndex = - 1 ;
685+
686+ setTimeout ( ( ) => {
687+ this . _tabIndex = 0 ;
688+ this . _changeDetector . markForCheck ( ) ;
689+ } ) ;
690+ }
691+
692+ /** Updates the tabindex based upon if the selection list is empty. */
693+ private _updateTabIndex ( ) : void {
694+ this . _tabIndex = ( this . options . length === 0 ) ? - 1 : 0 ;
695+ }
696+
643697 static ngAcceptInputType_disabled : BooleanInput ;
644698 static ngAcceptInputType_disableRipple : BooleanInput ;
645699}
0 commit comments