Skip to content

Commit fbbe463

Browse files
crisbetommalerba
authored andcommitted
feat(stepper): add CdkStepHeader directive and fix CdkStepper error on init (#10614)
* Adds the `CdkStepHeader` directive which is the equivalent of `MatStepHeader` and can be used by consumers when building custom steppers. * Fixes an error that was being thrown on init when using a `cdkStepper` directly. Fixes #10611.
1 parent d2d8a1f commit fbbe463

File tree

5 files changed

+63
-15
lines changed

5 files changed

+63
-15
lines changed

src/cdk/stepper/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './stepper';
1010
export * from './step-label';
1111
export * from './stepper-button';
1212
export * from './stepper-module';
13+
export * from './step-header';

src/cdk/stepper/step-header.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Directive, ElementRef} from '@angular/core';
10+
import {FocusableOption} from '@angular/cdk/a11y';
11+
12+
13+
@Directive({
14+
selector: '[cdkStepHeader]',
15+
host: {
16+
'role': 'tab',
17+
},
18+
})
19+
export class CdkStepHeader implements FocusableOption {
20+
constructor(protected _elementRef: ElementRef<HTMLElement>) {}
21+
22+
/** Focuses the step header. */
23+
focus() {
24+
this._elementRef.nativeElement.focus();
25+
}
26+
}

src/cdk/stepper/stepper-module.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,26 @@ import {CdkStepper, CdkStep} from './stepper';
1111
import {CommonModule} from '@angular/common';
1212
import {CdkStepLabel} from './step-label';
1313
import {CdkStepperNext, CdkStepperPrevious} from './stepper-button';
14+
import {CdkStepHeader} from './step-header';
1415
import {BidiModule} from '@angular/cdk/bidi';
1516

1617
@NgModule({
1718
imports: [BidiModule, CommonModule],
18-
exports: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious],
19-
declarations: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious]
19+
exports: [
20+
CdkStep,
21+
CdkStepper,
22+
CdkStepHeader,
23+
CdkStepLabel,
24+
CdkStepperNext,
25+
CdkStepperPrevious,
26+
],
27+
declarations: [
28+
CdkStep,
29+
CdkStepper,
30+
CdkStepHeader,
31+
CdkStepLabel,
32+
CdkStepperNext,
33+
CdkStepperPrevious,
34+
]
2035
})
2136
export class CdkStepperModule {}

src/cdk/stepper/stepper.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {AbstractControl} from '@angular/forms';
3838
import {CdkStepLabel} from './step-label';
3939
import {Observable, Subject, of as obaservableOf} from 'rxjs';
4040
import {startWith, takeUntil} from 'rxjs/operators';
41+
import {CdkStepHeader} from './step-header';
4142

4243
/** Used to generate unique ID for each stepper component. */
4344
let nextId = 0;
@@ -242,8 +243,12 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
242243
/** The list of step components that the stepper is holding. */
243244
@ContentChildren(CdkStep) _steps: QueryList<CdkStep>;
244245

245-
/** The list of step headers of the steps in the stepper. */
246-
_stepHeader: QueryList<FocusableOption>;
246+
/**
247+
* The list of step headers of the steps in the stepper.
248+
* @deprecated Type to be changed to `QueryList<CdkStepHeader>`.
249+
* @breaking-change 8.0.0
250+
*/
251+
@ContentChildren(CdkStepHeader) _stepHeader: QueryList<FocusableOption>;
247252

248253
/** Whether the validity of previous steps should be checked or not. */
249254
@Input()
@@ -302,7 +307,10 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
302307
}
303308

304309
ngAfterViewInit() {
305-
this._keyManager = new FocusKeyManager(this._stepHeader)
310+
// Note that while the step headers are content children by default, any components that
311+
// extend this one might have them as view chidren. We initialize the keyboard handling in
312+
// AfterViewInit so we're guaranteed for both view and content children to be defined.
313+
this._keyManager = new FocusKeyManager<FocusableOption>(this._stepHeader)
306314
.withWrap()
307315
.withVerticalOrientation(this._orientation === 'vertical');
308316

src/lib/stepper/step-header.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import {Subscription} from 'rxjs';
2121
import {MatStepLabel} from './step-label';
2222
import {MatStepperIntl} from './stepper-intl';
2323
import {MatStepperIconContext} from './stepper-icon';
24-
import {StepState} from '@angular/cdk/stepper';
24+
import {CdkStepHeader, StepState} from '@angular/cdk/stepper';
25+
2526

2627
@Component({
2728
moduleId: module.id,
@@ -35,7 +36,7 @@ import {StepState} from '@angular/cdk/stepper';
3536
encapsulation: ViewEncapsulation.None,
3637
changeDetection: ChangeDetectionStrategy.OnPush,
3738
})
38-
export class MatStepHeader implements OnDestroy {
39+
export class MatStepHeader extends CdkStepHeader implements OnDestroy {
3940
private _intlSubscription: Subscription;
4041

4142
/** State of the given step. */
@@ -65,15 +66,16 @@ export class MatStepHeader implements OnDestroy {
6566
constructor(
6667
public _intl: MatStepperIntl,
6768
private _focusMonitor: FocusMonitor,
68-
private _element: ElementRef<HTMLElement>,
69+
_elementRef: ElementRef<HTMLElement>,
6970
changeDetectorRef: ChangeDetectorRef) {
70-
_focusMonitor.monitor(_element, true);
71+
super(_elementRef);
72+
_focusMonitor.monitor(_elementRef, true);
7173
this._intlSubscription = _intl.changes.subscribe(() => changeDetectorRef.markForCheck());
7274
}
7375

7476
ngOnDestroy() {
7577
this._intlSubscription.unsubscribe();
76-
this._focusMonitor.stopMonitoring(this._element);
78+
this._focusMonitor.stopMonitoring(this._elementRef);
7779
}
7880

7981
/** Returns string label of given step if it is a text label. */
@@ -88,7 +90,7 @@ export class MatStepHeader implements OnDestroy {
8890

8991
/** Returns the host HTML element. */
9092
_getHostElement() {
91-
return this._element.nativeElement;
93+
return this._elementRef.nativeElement;
9294
}
9395

9496
/** Template context variables that are exposed to the `matStepperIcon` instances. */
@@ -99,8 +101,4 @@ export class MatStepHeader implements OnDestroy {
99101
optional: this.optional
100102
};
101103
}
102-
103-
focus() {
104-
this._getHostElement().focus();
105-
}
106104
}

0 commit comments

Comments
 (0)