@@ -19,6 +19,7 @@ import {
19
19
ContentChildren ,
20
20
Directive ,
21
21
EventEmitter ,
22
+ ElementRef ,
22
23
forwardRef ,
23
24
Inject ,
24
25
Input ,
@@ -31,6 +32,7 @@ import {
31
32
ViewChild ,
32
33
ViewEncapsulation ,
33
34
} from '@angular/core' ;
35
+ import { DOCUMENT } from '@angular/common' ;
34
36
import { AbstractControl } from '@angular/forms' ;
35
37
import { CdkStepLabel } from './step-label' ;
36
38
import { Observable , Subject , of as obaservableOf } from 'rxjs' ;
@@ -164,6 +166,12 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
164
166
/** Used for managing keyboard focus. */
165
167
private _keyManager : FocusKeyManager < FocusableOption > ;
166
168
169
+ /**
170
+ * @breaking -change 8.0.0 Remove `| undefined` once the `_document`
171
+ * constructor param is required.
172
+ */
173
+ private _document : Document | undefined ;
174
+
167
175
/** The list of step components that the stepper is holding. */
168
176
@ContentChildren ( CdkStep ) _steps : QueryList < CdkStep > ;
169
177
@@ -218,8 +226,12 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
218
226
219
227
constructor (
220
228
@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 ) {
222
233
this . _groupId = nextId ++ ;
234
+ this . _document = _document ;
223
235
}
224
236
225
237
ngAfterViewInit ( ) {
@@ -305,7 +317,14 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
305
317
selectedStep : stepsArray [ newIndex ] ,
306
318
previouslySelectedStep : stepsArray [ this . _selectedIndex ] ,
307
319
} ) ;
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
+
309
328
this . _selectedIndex = newIndex ;
310
329
this . _stateChanged ( ) ;
311
330
}
@@ -348,4 +367,15 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
348
367
private _layoutDirection ( ) : Direction {
349
368
return this . _dir && this . _dir . value === 'rtl' ? 'rtl' : 'ltr' ;
350
369
}
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
+ }
351
381
}
0 commit comments