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,53 @@ 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
+ _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
+
260
312
/** Dispatch change event with current selection and group value. */
261
313
_emitChangeEvent ( toggle : MatButtonToggle ) : void {
262
314
const event = new MatButtonToggleChange ( toggle , this . value ) ;
@@ -322,6 +374,18 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
322
374
return toggle . value === this . _rawValue ;
323
375
}
324
376
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
+
325
389
/** Updates the selection state of the toggles in the group based on a value. */
326
390
private _setSelectionByValue ( value : any | any [ ] ) {
327
391
this . _rawValue = value ;
@@ -346,7 +410,13 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
346
410
/** Clears the selected toggles. */
347
411
private _clearSelection ( ) {
348
412
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
+ } ) ;
350
420
}
351
421
352
422
/** Selects a value if there's a toggle that corresponds to it. */
@@ -358,6 +428,10 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
358
428
if ( correspondingOption ) {
359
429
correspondingOption . checked = true ;
360
430
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
+ }
361
435
}
362
436
}
363
437
@@ -437,8 +511,16 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
437
511
/** MatButtonToggleGroup reads this to assign its own value. */
438
512
@Input ( ) value : any ;
439
513
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 ;
442
524
443
525
/** Whether ripples are disabled on the button toggle. */
444
526
@Input ( { transform : booleanAttribute } ) disableRipple : boolean ;
@@ -550,6 +632,19 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
550
632
this . buttonToggleGroup . _onTouched ( ) ;
551
633
}
552
634
}
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
+
553
648
// Emit a change event when it's the single selector
554
649
this . change . emit ( new MatButtonToggleChange ( this , this . value ) ) ;
555
650
}
@@ -574,7 +669,7 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
574
669
}
575
670
576
671
/** Whether the toggle is in single selection mode. */
577
- private _isSingleSelector ( ) : boolean {
672
+ _isSingleSelector ( ) : boolean {
578
673
return this . buttonToggleGroup && ! this . buttonToggleGroup . multiple ;
579
674
}
580
675
}
0 commit comments