8
8
9
9
import { FocusMonitor } from '@angular/cdk/a11y' ;
10
10
import { SelectionModel } from '@angular/cdk/collections' ;
11
+ import { DOWN_ARROW , LEFT_ARROW , RIGHT_ARROW , UP_ARROW , SPACE , ENTER } from '@angular/cdk/keycodes' ;
11
12
import {
12
13
AfterContentInit ,
13
14
Attribute ,
@@ -106,8 +107,9 @@ export class MatButtonToggleChange {
106
107
{ provide : MAT_BUTTON_TOGGLE_GROUP , useExisting : MatButtonToggleGroup } ,
107
108
] ,
108
109
host : {
109
- 'role' : 'group' ,
110
110
'class' : 'mat-button-toggle-group' ,
111
+ '(keydown)' : '_keydown($event)' ,
112
+ '[role]' : "multiple ? 'group' : 'radiogroup'" ,
111
113
'[attr.aria-disabled]' : 'disabled' ,
112
114
'[class.mat-button-toggle-vertical]' : 'vertical' ,
113
115
'[class.mat-button-toggle-group-appearance-standard]' : 'appearance === "standard"' ,
@@ -231,6 +233,9 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
231
233
232
234
ngAfterContentInit ( ) {
233
235
this . _selectionModel . select ( ...this . _buttonToggles . filter ( toggle => toggle . checked ) ) ;
236
+ if ( ! this . multiple ) {
237
+ this . _initializeTabIndex ( ) ;
238
+ }
234
239
}
235
240
236
241
/**
@@ -257,6 +262,41 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
257
262
this . disabled = isDisabled ;
258
263
}
259
264
265
+ /** Handle keydown event calling to single-select button toggle. */
266
+ protected _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
+
260
300
/** Dispatch change event with current selection and group value. */
261
301
_emitChangeEvent ( toggle : MatButtonToggle ) : void {
262
302
const event = new MatButtonToggleChange ( toggle , this . value ) ;
@@ -322,6 +362,31 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
322
362
return toggle . value === this . _rawValue ;
323
363
}
324
364
365
+ /** Initializes the tabindex attribute using the radio pattern. */
366
+ private _initializeTabIndex ( ) {
367
+ this . _buttonToggles . forEach ( toggle => {
368
+ toggle . tabIndex = - 1 ;
369
+ } ) ;
370
+ if ( this . selected ) {
371
+ ( this . selected as MatButtonToggle ) . tabIndex = 0 ;
372
+ } else if ( this . _buttonToggles . length > 0 ) {
373
+ this . _buttonToggles . get ( 0 ) ! . tabIndex = 0 ;
374
+ }
375
+ this . _markButtonsForCheck ( ) ;
376
+ }
377
+
378
+ /** Obtain the subsequent index to which the focus shifts. */
379
+ private _getNextIndex ( index : number , offset : number ) : number {
380
+ let nextIndex = index + offset ;
381
+ if ( nextIndex === this . _buttonToggles . length ) {
382
+ nextIndex = 0 ;
383
+ }
384
+ if ( nextIndex === - 1 ) {
385
+ nextIndex = this . _buttonToggles . length - 1 ;
386
+ }
387
+ return nextIndex ;
388
+ }
389
+
325
390
/** Updates the selection state of the toggles in the group based on a value. */
326
391
private _setSelectionByValue ( value : any | any [ ] ) {
327
392
this . _rawValue = value ;
@@ -346,7 +411,13 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
346
411
/** Clears the selected toggles. */
347
412
private _clearSelection ( ) {
348
413
this . _selectionModel . clear ( ) ;
349
- this . _buttonToggles . forEach ( toggle => ( toggle . checked = false ) ) ;
414
+ this . _buttonToggles . forEach ( toggle => {
415
+ toggle . checked = false ;
416
+ // If the button toggle is in single select mode, initialize the tabIndex.
417
+ if ( ! this . multiple ) {
418
+ toggle . tabIndex = - 1 ;
419
+ }
420
+ } ) ;
350
421
}
351
422
352
423
/** Selects a value if there's a toggle that corresponds to it. */
@@ -358,6 +429,10 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
358
429
if ( correspondingOption ) {
359
430
correspondingOption . checked = true ;
360
431
this . _selectionModel . select ( correspondingOption ) ;
432
+ if ( ! this . multiple ) {
433
+ // If the button toggle is in single select mode, reset the tabIndex.
434
+ correspondingOption . tabIndex = 0 ;
435
+ }
361
436
}
362
437
}
363
438
@@ -437,8 +512,16 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
437
512
/** MatButtonToggleGroup reads this to assign its own value. */
438
513
@Input ( ) value : any ;
439
514
440
- /** Tabindex for the toggle. */
441
- @Input ( ) tabIndex : number | null ;
515
+ /** Tabindex of the toggle. */
516
+ @Input ( )
517
+ get tabIndex ( ) : number | null {
518
+ return this . _tabIndex ;
519
+ }
520
+ set tabIndex ( value : number | null ) {
521
+ this . _tabIndex = value ;
522
+ this . _markForCheck ( ) ;
523
+ }
524
+ private _tabIndex : number | null ;
442
525
443
526
/** Whether ripples are disabled on the button toggle. */
444
527
@Input ( { transform : booleanAttribute } ) disableRipple : boolean ;
@@ -550,6 +633,19 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
550
633
this . buttonToggleGroup . _onTouched ( ) ;
551
634
}
552
635
}
636
+
637
+ if ( this . _isSingleSelector ( ) ) {
638
+ const focusable = this . buttonToggleGroup . _buttonToggles . find ( toggle => {
639
+ return toggle . tabIndex === 0 ;
640
+ } ) ;
641
+ // Modify the tabindex attribute of the last focusable button toggle to -1.
642
+ if ( focusable ) {
643
+ focusable . tabIndex = - 1 ;
644
+ }
645
+ // Modify the tabindex attribute of the presently selected button toggle to 0.
646
+ this . tabIndex = 0 ;
647
+ }
648
+
553
649
// Emit a change event when it's the single selector
554
650
this . change . emit ( new MatButtonToggleChange ( this , this . value ) ) ;
555
651
}
@@ -574,7 +670,7 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
574
670
}
575
671
576
672
/** Whether the toggle is in single selection mode. */
577
- private _isSingleSelector ( ) : boolean {
673
+ _isSingleSelector ( ) : boolean {
578
674
return this . buttonToggleGroup && ! this . buttonToggleGroup . multiple ;
579
675
}
580
676
}
0 commit comments