@@ -19,6 +19,7 @@ import {
1919 ContentChildren ,
2020 Directive ,
2121 EventEmitter ,
22+ ElementRef ,
2223 forwardRef ,
2324 Inject ,
2425 Input ,
@@ -31,6 +32,7 @@ import {
3132 ViewChild ,
3233 ViewEncapsulation ,
3334} from '@angular/core' ;
35+ import { DOCUMENT } from '@angular/common' ;
3436import { AbstractControl } from '@angular/forms' ;
3537import { CdkStepLabel } from './step-label' ;
3638import { Observable , Subject , of as obaservableOf } from 'rxjs' ;
@@ -164,6 +166,12 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
164166 /** Used for managing keyboard focus. */
165167 private _keyManager : FocusKeyManager < FocusableOption > ;
166168
169+ /**
170+ * @breaking -change 8.0.0 Remove `| undefined` once the `_document`
171+ * constructor param is required.
172+ */
173+ private _document : Document | undefined ;
174+
167175 /** The list of step components that the stepper is holding. */
168176 @ContentChildren ( CdkStep ) _steps : QueryList < CdkStep > ;
169177
@@ -218,8 +226,12 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
218226
219227 constructor (
220228 @Optional ( ) private _dir : Directionality ,
221- private _changeDetectorRef : ChangeDetectorRef ) {
229+ private _changeDetectorRef : ChangeDetectorRef ,
230+ // @breaking -change 8.0.0 `_elementRef` and `_document` parameters to become required.
231+ private _elementRef ?: ElementRef < HTMLElement > ,
232+ @Inject ( DOCUMENT ) _document ?: any ) {
222233 this . _groupId = nextId ++ ;
234+ this . _document = _document ;
223235 }
224236
225237 ngAfterViewInit ( ) {
@@ -305,7 +317,14 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
305317 selectedStep : stepsArray [ newIndex ] ,
306318 previouslySelectedStep : stepsArray [ this . _selectedIndex ] ,
307319 } ) ;
308- this . _keyManager . updateActiveItemIndex ( newIndex ) ;
320+
321+ // If focus is inside the stepper, move it to the next header, otherwise it may become
322+ // lost when the active step content is hidden. We can't be more granular with the check
323+ // (e.g. checking whether focus is inside the active step), because we don't have a
324+ // reference to the elements that are rendering out the content.
325+ this . _containsFocus ( ) ? this . _keyManager . setActiveItem ( newIndex ) :
326+ this . _keyManager . updateActiveItemIndex ( newIndex ) ;
327+
309328 this . _selectedIndex = newIndex ;
310329 this . _stateChanged ( ) ;
311330 }
@@ -348,4 +367,15 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
348367 private _layoutDirection ( ) : Direction {
349368 return this . _dir && this . _dir . value === 'rtl' ? 'rtl' : 'ltr' ;
350369 }
370+
371+ /** Checks whether the stepper contains the focused element. */
372+ private _containsFocus ( ) : boolean {
373+ if ( ! this . _document || ! this . _elementRef ) {
374+ return false ;
375+ }
376+
377+ const stepperElement = this . _elementRef . nativeElement ;
378+ const focusedElement = this . _document . activeElement ;
379+ return stepperElement === focusedElement || stepperElement . contains ( focusedElement ) ;
380+ }
351381}
0 commit comments