88
99import { Direction , Directionality } from '@angular/cdk/bidi' ;
1010import { coerceNumberProperty } from '@angular/cdk/coercion' ;
11- import { END , ENTER , HOME , LEFT_ARROW , RIGHT_ARROW , SPACE } from '@angular/cdk/keycodes' ;
11+ import { END , ENTER , HOME , SPACE } from '@angular/cdk/keycodes' ;
1212import { ViewportRuler } from '@angular/cdk/scrolling' ;
1313import {
1414 AfterContentChecked ,
@@ -31,6 +31,7 @@ import {CanDisableRipple, mixinDisableRipple} from '@angular/material/core';
3131import { merge , of as observableOf , Subscription } from 'rxjs' ;
3232import { MatInkBar } from './ink-bar' ;
3333import { MatTabLabelWrapper } from './tab-label-wrapper' ;
34+ import { FocusKeyManager } from '@angular/cdk/a11y' ;
3435
3536
3637/**
@@ -80,9 +81,6 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
8081 @ViewChild ( 'tabListContainer' ) _tabListContainer : ElementRef ;
8182 @ViewChild ( 'tabList' ) _tabList : ElementRef ;
8283
83- /** The tab index that is focused. */
84- private _focusIndex : number = 0 ;
85-
8684 /** The distance in pixels that the tab labels should be translated to the left. */
8785 private _scrollDistance = 0 ;
8886
@@ -110,6 +108,9 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
110108 /** Whether the scroll distance has changed and should be applied after the view is checked. */
111109 private _scrollDistanceChanged : boolean ;
112110
111+ /** Used to manage focus between the tabs. */
112+ private _keyManager : FocusKeyManager < MatTabLabelWrapper > ;
113+
113114 private _selectedIndex : number = 0 ;
114115
115116 /** The index of the active tab. */
@@ -119,7 +120,10 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
119120 value = coerceNumberProperty ( value ) ;
120121 this . _selectedIndexChanged = this . _selectedIndex != value ;
121122 this . _selectedIndex = value ;
122- this . _focusIndex = value ;
123+
124+ if ( this . _keyManager ) {
125+ this . _keyManager . updateActiveItemIndex ( value ) ;
126+ }
123127 }
124128
125129 /** Event emitted when the option is selected. */
@@ -164,25 +168,21 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
164168
165169 _handleKeydown ( event : KeyboardEvent ) {
166170 switch ( event . keyCode ) {
167- case RIGHT_ARROW :
168- this . _focusNextTab ( ) ;
169- break ;
170- case LEFT_ARROW :
171- this . _focusPreviousTab ( ) ;
172- break ;
173171 case HOME :
174- this . _focusFirstTab ( ) ;
172+ this . _keyManager . setFirstItemActive ( ) ;
175173 event . preventDefault ( ) ;
176174 break ;
177175 case END :
178- this . _focusLastTab ( ) ;
176+ this . _keyManager . setLastItemActive ( ) ;
179177 event . preventDefault ( ) ;
180178 break ;
181179 case ENTER :
182180 case SPACE :
183181 this . selectFocusedIndex . emit ( this . focusIndex ) ;
184182 event . preventDefault ( ) ;
185183 break ;
184+ default :
185+ this . _keyManager . onKeydown ( event ) ;
186186 }
187187 }
188188
@@ -197,10 +197,19 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
197197 this . _alignInkBarToSelectedTab ( ) ;
198198 } ;
199199
200+ this . _keyManager = new FocusKeyManager ( this . _labelWrappers )
201+ . withHorizontalOrientation ( this . _getLayoutDirection ( ) ) ;
202+
203+ this . _keyManager . updateActiveItemIndex ( 0 ) ;
204+
200205 // Defer the first call in order to allow for slower browsers to lay out the elements.
201206 // This helps in cases where the user lands directly on a page with paginated tabs.
202207 typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame ( realign ) : realign ( ) ;
203- this . _realignInkBar = merge ( dirChange , resize ) . subscribe ( realign ) ;
208+
209+ this . _realignInkBar = merge ( dirChange , resize ) . subscribe ( ( ) => {
210+ realign ( ) ;
211+ this . _keyManager . withHorizontalOrientation ( this . _getLayoutDirection ( ) ) ;
212+ } ) ;
204213 }
205214
206215 ngOnDestroy ( ) {
@@ -227,14 +236,14 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
227236
228237 /** Tracks which element has focus; used for keyboard navigation */
229238 get focusIndex ( ) : number {
230- return this . _focusIndex ;
239+ return this . _keyManager ? this . _keyManager . activeItemIndex ! : 0 ;
231240 }
232241
233242 /** When the focus index is set, we must manually send focus to the correct label */
234243 set focusIndex ( value : number ) {
235- if ( ! this . _isValidIndex ( value ) || this . _focusIndex == value ) { return ; }
244+ if ( ! this . _isValidIndex ( value ) || this . focusIndex == value || ! this . _keyManager ) { return ; }
236245
237- this . _focusIndex = value ;
246+ this . _keyManager . setActiveItem ( value ) ;
238247 this . indexFocused . emit ( value ) ;
239248 this . _setTabFocus ( value ) ;
240249 }
@@ -276,53 +285,6 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
276285 }
277286 }
278287
279- /**
280- * Moves the focus towards the beginning or the end of the list depending on the offset provided.
281- * Valid offsets are 1 and -1.
282- */
283- _moveFocus ( offset : number ) {
284- if ( this . _labelWrappers ) {
285- const tabs : MatTabLabelWrapper [ ] = this . _labelWrappers . toArray ( ) ;
286-
287- for ( let i = this . focusIndex + offset ; i < tabs . length && i >= 0 ; i += offset ) {
288- if ( this . _isValidIndex ( i ) ) {
289- this . focusIndex = i ;
290- return ;
291- }
292- }
293- }
294- }
295-
296- /** Increment the focus index by 1 until a valid tab is found. */
297- _focusNextTab ( ) : void {
298- this . _moveFocus ( this . _getLayoutDirection ( ) == 'ltr' ? 1 : - 1 ) ;
299- }
300-
301- /** Decrement the focus index by 1 until a valid tab is found. */
302- _focusPreviousTab ( ) : void {
303- this . _moveFocus ( this . _getLayoutDirection ( ) == 'ltr' ? - 1 : 1 ) ;
304- }
305-
306- /** Focuses the first tab. */
307- private _focusFirstTab ( ) : void {
308- for ( let i = 0 ; i < this . _labelWrappers . length ; i ++ ) {
309- if ( this . _isValidIndex ( i ) ) {
310- this . focusIndex = i ;
311- break ;
312- }
313- }
314- }
315-
316- /** Focuses the last tab. */
317- private _focusLastTab ( ) : void {
318- for ( let i = this . _labelWrappers . length - 1 ; i > - 1 ; i -- ) {
319- if ( this . _isValidIndex ( i ) ) {
320- this . focusIndex = i ;
321- break ;
322- }
323- }
324- }
325-
326288 /** The layout direction of the containing app. */
327289 _getLayoutDirection ( ) : Direction {
328290 return this . _dir && this . _dir . value === 'rtl' ? 'rtl' : 'ltr' ;
0 commit comments