88
99import { FocusMonitor } from '@angular/cdk/a11y' ;
1010import { SelectionModel } from '@angular/cdk/collections' ;
11+ import { DOWN_ARROW , LEFT_ARROW , RIGHT_ARROW , UP_ARROW , SPACE , ENTER } from '@angular/cdk/keycodes' ;
1112import {
1213 AfterContentInit ,
1314 Attribute ,
@@ -106,8 +107,9 @@ export class MatButtonToggleChange {
106107 { provide : MAT_BUTTON_TOGGLE_GROUP , useExisting : MatButtonToggleGroup } ,
107108 ] ,
108109 host : {
109- 'role' : 'group' ,
110110 'class' : 'mat-button-toggle-group' ,
111+ '(keydown)' : '_keydown($event)' ,
112+ '[role]' : "multiple ? 'group' : 'radiogroup'" ,
111113 '[attr.aria-disabled]' : 'disabled' ,
112114 '[class.mat-button-toggle-vertical]' : 'vertical' ,
113115 '[class.mat-button-toggle-group-appearance-standard]' : 'appearance === "standard"' ,
@@ -231,6 +233,9 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
231233
232234 ngAfterContentInit ( ) {
233235 this . _selectionModel . select ( ...this . _buttonToggles . filter ( toggle => toggle . checked ) ) ;
236+ if ( ! this . multiple ) {
237+ this . _initializeTabIndex ( ) ;
238+ }
234239 }
235240
236241 /**
@@ -257,6 +262,53 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
257262 this . disabled = isDisabled ;
258263 }
259264
265+ /** Handle keydown event calling to single-select button toggle. */
266+ _keydown ( event : KeyboardEvent ) {
267+ if ( this . multiple || this . disabled ) {
268+ return ;
269+ }
270+
271+ const target = event . target as HTMLButtonElement ;
272+ const buttonId = target . id ;
273+ const index = this . _buttonToggles . toArray ( ) . findIndex ( toggle => {
274+ return toggle . buttonId === buttonId ;
275+ } ) ;
276+
277+ let nextButton ;
278+ switch ( event . keyCode ) {
279+ case SPACE :
280+ case ENTER :
281+ nextButton = this . _buttonToggles . get ( index ) ;
282+ break ;
283+ case UP_ARROW :
284+ case LEFT_ARROW :
285+ nextButton = this . _buttonToggles . get ( this . _getNextIndex ( index , - 1 ) ) ;
286+ break ;
287+ case DOWN_ARROW :
288+ case RIGHT_ARROW :
289+ nextButton = this . _buttonToggles . get ( this . _getNextIndex ( index , 1 ) ) ;
290+ break ;
291+ default :
292+ return ;
293+ }
294+
295+ event . preventDefault ( ) ;
296+ nextButton ?. _onButtonClick ( ) ;
297+ nextButton ?. focus ( ) ;
298+ }
299+
300+ /** Obtain the subsequent index to which the focus shifts. */
301+ _getNextIndex ( index : number , offset : number ) : number {
302+ let nextIndex = index + offset ;
303+ if ( nextIndex === this . _buttonToggles . length ) {
304+ nextIndex = 0 ;
305+ }
306+ if ( nextIndex === - 1 ) {
307+ nextIndex = this . _buttonToggles . length - 1 ;
308+ }
309+ return nextIndex ;
310+ }
311+
260312 /** Dispatch change event with current selection and group value. */
261313 _emitChangeEvent ( toggle : MatButtonToggle ) : void {
262314 const event = new MatButtonToggleChange ( toggle , this . value ) ;
@@ -322,6 +374,18 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
322374 return toggle . value === this . _rawValue ;
323375 }
324376
377+ /** Initializes the tabindex attribute using the radio pattern. */
378+ private _initializeTabIndex ( ) {
379+ this . _buttonToggles . forEach ( toggle => {
380+ toggle . tabIndex = - 1 ;
381+ } ) ;
382+ if ( this . selected ) {
383+ ( this . selected as MatButtonToggle ) . tabIndex = 0 ;
384+ } else if ( this . _buttonToggles . length > 0 ) {
385+ this . _buttonToggles . get ( 0 ) ! . tabIndex = 0 ;
386+ }
387+ }
388+
325389 /** Updates the selection state of the toggles in the group based on a value. */
326390 private _setSelectionByValue ( value : any | any [ ] ) {
327391 this . _rawValue = value ;
@@ -346,7 +410,13 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
346410 /** Clears the selected toggles. */
347411 private _clearSelection ( ) {
348412 this . _selectionModel . clear ( ) ;
349- this . _buttonToggles . forEach ( toggle => ( toggle . checked = false ) ) ;
413+ this . _buttonToggles . forEach ( toggle => {
414+ toggle . checked = false ;
415+ // If the button toggle is in single select mode, initialize the tabIndex.
416+ if ( ! this . multiple ) {
417+ toggle . tabIndex = - 1 ;
418+ }
419+ } ) ;
350420 }
351421
352422 /** Selects a value if there's a toggle that corresponds to it. */
@@ -358,6 +428,10 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
358428 if ( correspondingOption ) {
359429 correspondingOption . checked = true ;
360430 this . _selectionModel . select ( correspondingOption ) ;
431+ if ( ! this . multiple ) {
432+ // If the button toggle is in single select mode, reset the tabIndex.
433+ correspondingOption . tabIndex = 0 ;
434+ }
361435 }
362436 }
363437
@@ -437,8 +511,16 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
437511 /** MatButtonToggleGroup reads this to assign its own value. */
438512 @Input ( ) value : any ;
439513
440- /** Tabindex for the toggle. */
441- @Input ( ) tabIndex : number | null ;
514+ /** The tabindex of the button. */
515+ @Input ( )
516+ get tabIndex ( ) : number | null {
517+ return this . _tabIndex ;
518+ }
519+ set tabIndex ( value : number | null ) {
520+ this . _tabIndex = value ;
521+ this . _changeDetectorRef . markForCheck ( ) ;
522+ }
523+ private _tabIndex : number | null ;
442524
443525 /** Whether ripples are disabled on the button toggle. */
444526 @Input ( { transform : booleanAttribute } ) disableRipple : boolean ;
@@ -550,6 +632,19 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
550632 this . buttonToggleGroup . _onTouched ( ) ;
551633 }
552634 }
635+
636+ if ( this . _isSingleSelector ( ) ) {
637+ const focusable = this . buttonToggleGroup . _buttonToggles . find ( toggle => {
638+ return toggle . tabIndex === 0 ;
639+ } ) ;
640+ // Modify the tabindex attribute of the last focusable button toggle to -1.
641+ if ( focusable ) {
642+ focusable . tabIndex = - 1 ;
643+ }
644+ // Modify the tabindex attribute of the presently selected button toggle to 0.
645+ this . tabIndex = 0 ;
646+ }
647+
553648 // Emit a change event when it's the single selector
554649 this . change . emit ( new MatButtonToggleChange ( this , this . value ) ) ;
555650 }
@@ -574,7 +669,7 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
574669 }
575670
576671 /** Whether the toggle is in single selection mode. */
577- private _isSingleSelector ( ) : boolean {
672+ _isSingleSelector ( ) : boolean {
578673 return this . buttonToggleGroup && ! this . buttonToggleGroup . multiple ;
579674 }
580675}
0 commit comments