From 462811cc045ec5a2d089e2b19ee62a59c3d5c3b6 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 25 Oct 2018 19:46:56 +0200 Subject: [PATCH] feat(stepper): add CdkStepHeader directive and fix CdkStepper error on init * 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. --- src/cdk/stepper/public-api.ts | 1 + src/cdk/stepper/step-header.ts | 26 ++++++++++++++++++++++++++ src/cdk/stepper/stepper-module.ts | 19 +++++++++++++++++-- src/cdk/stepper/stepper.ts | 14 +++++++++++--- src/lib/stepper/step-header.ts | 18 ++++++++---------- 5 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 src/cdk/stepper/step-header.ts diff --git a/src/cdk/stepper/public-api.ts b/src/cdk/stepper/public-api.ts index 5ff98ead2f60..b1f825023fd8 100644 --- a/src/cdk/stepper/public-api.ts +++ b/src/cdk/stepper/public-api.ts @@ -10,3 +10,4 @@ export * from './stepper'; export * from './step-label'; export * from './stepper-button'; export * from './stepper-module'; +export * from './step-header'; diff --git a/src/cdk/stepper/step-header.ts b/src/cdk/stepper/step-header.ts new file mode 100644 index 000000000000..450c1062db2d --- /dev/null +++ b/src/cdk/stepper/step-header.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive, ElementRef} from '@angular/core'; +import {FocusableOption} from '@angular/cdk/a11y'; + + +@Directive({ + selector: '[cdkStepHeader]', + host: { + 'role': 'tab', + }, +}) +export class CdkStepHeader implements FocusableOption { + constructor(protected _elementRef: ElementRef) {} + + /** Focuses the step header. */ + focus() { + this._elementRef.nativeElement.focus(); + } +} diff --git a/src/cdk/stepper/stepper-module.ts b/src/cdk/stepper/stepper-module.ts index f0a596c7eb64..b51086e9d73f 100644 --- a/src/cdk/stepper/stepper-module.ts +++ b/src/cdk/stepper/stepper-module.ts @@ -11,11 +11,26 @@ import {CdkStepper, CdkStep} from './stepper'; import {CommonModule} from '@angular/common'; import {CdkStepLabel} from './step-label'; import {CdkStepperNext, CdkStepperPrevious} from './stepper-button'; +import {CdkStepHeader} from './step-header'; import {BidiModule} from '@angular/cdk/bidi'; @NgModule({ imports: [BidiModule, CommonModule], - exports: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious], - declarations: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious] + exports: [ + CdkStep, + CdkStepper, + CdkStepHeader, + CdkStepLabel, + CdkStepperNext, + CdkStepperPrevious, + ], + declarations: [ + CdkStep, + CdkStepper, + CdkStepHeader, + CdkStepLabel, + CdkStepperNext, + CdkStepperPrevious, + ] }) export class CdkStepperModule {} diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index d2af09b354eb..27f4e0d0bae7 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -38,6 +38,7 @@ import {AbstractControl} from '@angular/forms'; import {CdkStepLabel} from './step-label'; import {Observable, Subject, of as obaservableOf} from 'rxjs'; import {startWith, takeUntil} from 'rxjs/operators'; +import {CdkStepHeader} from './step-header'; /** Used to generate unique ID for each stepper component. */ let nextId = 0; @@ -242,8 +243,12 @@ export class CdkStepper implements AfterViewInit, OnDestroy { /** The list of step components that the stepper is holding. */ @ContentChildren(CdkStep) _steps: QueryList; - /** The list of step headers of the steps in the stepper. */ - _stepHeader: QueryList; + /** + * The list of step headers of the steps in the stepper. + * @deprecated Type to be changed to `QueryList`. + * @breaking-change 8.0.0 + */ + @ContentChildren(CdkStepHeader) _stepHeader: QueryList; /** Whether the validity of previous steps should be checked or not. */ @Input() @@ -302,7 +307,10 @@ export class CdkStepper implements AfterViewInit, OnDestroy { } ngAfterViewInit() { - this._keyManager = new FocusKeyManager(this._stepHeader) + // Note that while the step headers are content children by default, any components that + // extend this one might have them as view chidren. We initialize the keyboard handling in + // AfterViewInit so we're guaranteed for both view and content children to be defined. + this._keyManager = new FocusKeyManager(this._stepHeader) .withWrap() .withVerticalOrientation(this._orientation === 'vertical'); diff --git a/src/lib/stepper/step-header.ts b/src/lib/stepper/step-header.ts index b571df051f7d..ae97ba44cdf3 100644 --- a/src/lib/stepper/step-header.ts +++ b/src/lib/stepper/step-header.ts @@ -21,7 +21,8 @@ import {Subscription} from 'rxjs'; import {MatStepLabel} from './step-label'; import {MatStepperIntl} from './stepper-intl'; import {MatStepperIconContext} from './stepper-icon'; -import {StepState} from '@angular/cdk/stepper'; +import {CdkStepHeader, StepState} from '@angular/cdk/stepper'; + @Component({ moduleId: module.id, @@ -35,7 +36,7 @@ import {StepState} from '@angular/cdk/stepper'; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MatStepHeader implements OnDestroy { +export class MatStepHeader extends CdkStepHeader implements OnDestroy { private _intlSubscription: Subscription; /** State of the given step. */ @@ -65,15 +66,16 @@ export class MatStepHeader implements OnDestroy { constructor( public _intl: MatStepperIntl, private _focusMonitor: FocusMonitor, - private _element: ElementRef, + _elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef) { - _focusMonitor.monitor(_element, true); + super(_elementRef); + _focusMonitor.monitor(_elementRef, true); this._intlSubscription = _intl.changes.subscribe(() => changeDetectorRef.markForCheck()); } ngOnDestroy() { this._intlSubscription.unsubscribe(); - this._focusMonitor.stopMonitoring(this._element); + this._focusMonitor.stopMonitoring(this._elementRef); } /** Returns string label of given step if it is a text label. */ @@ -88,7 +90,7 @@ export class MatStepHeader implements OnDestroy { /** Returns the host HTML element. */ _getHostElement() { - return this._element.nativeElement; + return this._elementRef.nativeElement; } /** Template context variables that are exposed to the `matStepperIcon` instances. */ @@ -99,8 +101,4 @@ export class MatStepHeader implements OnDestroy { optional: this.optional }; } - - focus() { - this._getHostElement().focus(); - } }