diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index ca795d00b3da..83841e032c27 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -315,11 +315,16 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { /** Used to track unique ID for each stepper component. */ _groupId: number; - // Note that this isn't an `Input` so it doesn't bleed into the Material stepper. /** Orientation of the stepper. */ + @Input() get orientation(): StepperOrientation { return this._orientation; } set orientation(value: StepperOrientation) { - this._updateOrientation(value); + // This is a protected method so that `MatSteppter` can hook into it. + this._orientation = value; + + if (this._keyManager) { + this._keyManager.withVerticalOrientation(value === 'vertical'); + } } /** @@ -432,16 +437,6 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { this._getGuidelineLogic(step, isCurrentStep, state); } - /** Updates the stepper orientation. */ - protected _updateOrientation(value: StepperOrientation) { - // This is a protected method so that `MatSteppter` can hook into it. - this._orientation = value; - - if (this._keyManager) { - this._keyManager.withVerticalOrientation(value === 'vertical'); - } - } - private _getDefaultIndicatorLogic(step: CdkStep, isCurrentStep: boolean): StepState { if (step._showError && step.hasError && !isCurrentStep) { return STEP_STATE.ERROR; diff --git a/src/dev-app/stepper/stepper-demo.html b/src/dev-app/stepper/stepper-demo.html index 6970e76cf078..55e977e25010 100644 --- a/src/dev-app/stepper/stepper-demo.html +++ b/src/dev-app/stepper/stepper-demo.html @@ -4,6 +4,9 @@

Disable header ripple

+

+ Vertical +

- + - +

Linear Horizontal Stepper Demo using a different form for each step

diff --git a/src/dev-app/stepper/stepper-demo.ts b/src/dev-app/stepper/stepper-demo.ts index 4df50cef9d65..bfe34ecd446a 100644 --- a/src/dev-app/stepper/stepper-demo.ts +++ b/src/dev-app/stepper/stepper-demo.ts @@ -21,6 +21,7 @@ export class StepperDemo implements OnInit { isNonEditable = false; disableRipple = false; showLabelBottom = false; + isVertical = false; nameFormGroup: FormGroup; emailFormGroup: FormGroup; diff --git a/src/material/stepper/public-api.ts b/src/material/stepper/public-api.ts index fb1e0c2d4b40..07fa772b37be 100644 --- a/src/material/stepper/public-api.ts +++ b/src/material/stepper/public-api.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +export {StepperOrientation} from '@angular/cdk/stepper'; export * from './stepper-module'; export * from './step-label'; export * from './stepper'; diff --git a/src/material/stepper/stepper-animations.ts b/src/material/stepper/stepper-animations.ts index 930e8fbea7f6..063ebfda7386 100644 --- a/src/material/stepper/stepper-animations.ts +++ b/src/material/stepper/stepper-animations.ts @@ -23,7 +23,7 @@ export const matStepperAnimations: { readonly verticalStepTransition: AnimationTriggerMetadata; } = { /** Animation that transitions the step along the X axis in a horizontal stepper. */ - horizontalStepTransition: trigger('stepTransition', [ + horizontalStepTransition: trigger('horizontalStepTransition', [ state('previous', style({transform: 'translate3d(-100%, 0, 0)', visibility: 'hidden'})), // Transition to '', rather than `visible`, because visibility on a child element overrides // the one from the parent, making this element focusable inside of a `hidden` element. @@ -33,7 +33,7 @@ export const matStepperAnimations: { ]), /** Animation that transitions the step along the Y axis in a vertical stepper. */ - verticalStepTransition: trigger('stepTransition', [ + verticalStepTransition: trigger('verticalStepTransition', [ state('previous', style({height: '0px', visibility: 'hidden'})), state('next', style({height: '0px', visibility: 'hidden'})), // Transition to '', rather than `visible`, because visibility on a child element overrides diff --git a/src/material/stepper/stepper-horizontal.html b/src/material/stepper/stepper-horizontal.html deleted file mode 100644 index bf004776064c..000000000000 --- a/src/material/stepper/stepper-horizontal.html +++ /dev/null @@ -1,39 +0,0 @@ -
- - - -
-
-
- -
-
- -
-
diff --git a/src/material/stepper/stepper-module.ts b/src/material/stepper/stepper-module.ts index 4e97efb2bd40..31df34462223 100644 --- a/src/material/stepper/stepper-module.ts +++ b/src/material/stepper/stepper-module.ts @@ -34,8 +34,6 @@ import {MatStepContent} from './step-content'; ], exports: [ MatCommonModule, - MatHorizontalStepper, - MatVerticalStepper, MatStep, MatStepLabel, MatStepper, diff --git a/src/material/stepper/stepper-vertical.html b/src/material/stepper/stepper-vertical.html deleted file mode 100644 index 36a5db980a56..000000000000 --- a/src/material/stepper/stepper-vertical.html +++ /dev/null @@ -1,37 +0,0 @@ -
- - - -
-
-
- -
-
-
-
diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html new file mode 100644 index 000000000000..8ecae97166f4 --- /dev/null +++ b/src/material/stepper/stepper.html @@ -0,0 +1,74 @@ + + + +
+ + +
+
+
+ +
+
+ +
+
+
+ + + +
+ +
+
+
+ +
+
+
+
+
+ +
+ + + + + diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index 8a51c344fd67..981abc46e8c5 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -81,7 +81,7 @@ describe('MatStepper', () => { }); it('should throw when a negative `selectedIndex` is assigned', () => { - const stepperComponent: MatVerticalStepper = fixture.debugElement + const stepperComponent: MatStepper = fixture.debugElement .query(By.css('mat-vertical-stepper'))!.componentInstance; expect(() => { @@ -91,7 +91,7 @@ describe('MatStepper', () => { }); it('should throw when an out-of-bounds `selectedIndex` is assigned', () => { - const stepperComponent: MatVerticalStepper = fixture.debugElement + const stepperComponent: MatStepper = fixture.debugElement .query(By.css('mat-vertical-stepper'))!.componentInstance; expect(() => { @@ -360,7 +360,7 @@ describe('MatStepper', () => { }); it('should adjust the index when removing a step before the current one', () => { - const stepperComponent: MatVerticalStepper = fixture.debugElement + const stepperComponent: MatStepper = fixture.debugElement .query(By.css('mat-vertical-stepper'))!.componentInstance; stepperComponent.selectedIndex = 2; @@ -395,20 +395,12 @@ describe('MatStepper', () => { .every(element => element.classList.contains('mat-focus-indicator'))).toBe(true); }); - it('should throw when trying to change the orientation of a stepper', () => { - const stepperComponent: MatStepper = fixture.debugElement - .query(By.css('mat-vertical-stepper'))!.componentInstance; - - expect(() => stepperComponent.orientation = 'horizontal') - .toThrowError('Updating the orientation of a Material stepper is not supported.'); - }); - }); describe('basic stepper when attempting to set the selected step too early', () => { it('should not throw', () => { const fixture = createComponent(SimpleMatVerticalStepperApp); - const stepperComponent: MatVerticalStepper = fixture.debugElement + const stepperComponent: MatStepper = fixture.debugElement .query(By.css('mat-vertical-stepper'))!.componentInstance; expect(() => stepperComponent.selected).not.toThrow(); @@ -418,7 +410,7 @@ describe('MatStepper', () => { describe('basic stepper when attempting to set the selected step too early', () => { it('should not throw', () => { const fixture = createComponent(SimpleMatVerticalStepperApp); - const stepperComponent: MatVerticalStepper = fixture.debugElement + const stepperComponent: MatStepper = fixture.debugElement .query(By.css('mat-vertical-stepper'))!.componentInstance; expect(() => stepperComponent.selected = null!).not.toThrow(); @@ -529,7 +521,7 @@ describe('MatStepper', () => { describe('linear stepper', () => { let fixture: ComponentFixture; let testComponent: LinearMatVerticalStepperApp; - let stepperComponent: MatVerticalStepper; + let stepperComponent: MatStepper; beforeEach(() => { fixture = createComponent(LinearMatVerticalStepperApp); @@ -769,13 +761,13 @@ describe('MatStepper', () => { describe('linear stepper with a pre-defined selectedIndex', () => { let preselectedFixture: ComponentFixture; - let stepper: MatHorizontalStepper; + let stepper: MatStepper; beforeEach(() => { preselectedFixture = createComponent(SimplePreselectedMatHorizontalStepperApp); preselectedFixture.detectChanges(); stepper = preselectedFixture.debugElement - .query(By.directive(MatHorizontalStepper))!.componentInstance; + .query(By.directive(MatStepper))!.componentInstance; }); it('should not throw', () => { @@ -798,8 +790,8 @@ describe('MatStepper', () => { noStepControlFixture.detectChanges(); }); it('should not move to the next step if the current one is not completed ', () => { - const stepper: MatHorizontalStepper = noStepControlFixture.debugElement - .query(By.directive(MatHorizontalStepper))!.componentInstance; + const stepper: MatStepper = noStepControlFixture.debugElement + .query(By.directive(MatStepper))!.componentInstance; const headers = noStepControlFixture.debugElement .queryAll(By.css('.mat-horizontal-stepper-header')); @@ -825,8 +817,8 @@ describe('MatStepper', () => { expect(controlAndBindingFixture.componentInstance.steps[0].control.valid).toBe(true); expect(controlAndBindingFixture.componentInstance.steps[0].completed).toBe(false); - const stepper: MatHorizontalStepper = controlAndBindingFixture.debugElement - .query(By.directive(MatHorizontalStepper))!.componentInstance; + const stepper: MatStepper = controlAndBindingFixture.debugElement + .query(By.directive(MatStepper))!.componentInstance; const headers = controlAndBindingFixture.debugElement .queryAll(By.css('.mat-horizontal-stepper-header')); @@ -841,6 +833,12 @@ describe('MatStepper', () => { }); describe('vertical stepper', () => { + it('should be able to use the legacy classes in queries', () => { + let fixture = createComponent(SimpleMatVerticalStepperApp); + fixture.detectChanges(); + expect(fixture.componentInstance.legacyTokenStepper).toBeTruthy(); + }); + it('should set the aria-orientation to "vertical"', () => { let fixture = createComponent(SimpleMatVerticalStepperApp); fixture.detectChanges(); @@ -942,6 +940,12 @@ describe('MatStepper', () => { }); describe('horizontal stepper', () => { + it('should be able to use the legacy classes in queries', () => { + let fixture = createComponent(SimpleMatHorizontalStepperApp); + fixture.detectChanges(); + expect(fixture.componentInstance.legacyTokenStepper).toBeTruthy(); + }); + it('should set the aria-orientation to "horizontal"', () => { let fixture = createComponent(SimpleMatHorizontalStepperApp); fixture.detectChanges(); @@ -1597,6 +1601,7 @@ class MatHorizontalStepperWithErrorsApp implements OnInit { ` }) class SimpleMatHorizontalStepperApp { + @ViewChild(MatHorizontalStepper) legacyTokenStepper: MatHorizontalStepper; inputLabel = 'Step 3'; disableRipple = false; stepperTheme: ThemePalette; @@ -1633,6 +1638,7 @@ class SimpleMatHorizontalStepperApp { ` }) class SimpleMatVerticalStepperApp { + @ViewChild(MatVerticalStepper) legacyTokenStepper: MatVerticalStepper; inputLabel = 'Step 3'; showStepTwo = true; disableRipple = false; diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts index a3c9129ca491..6699f9463e0c 100644 --- a/src/material/stepper/stepper.ts +++ b/src/material/stepper/stepper.ts @@ -13,7 +13,7 @@ import { CdkStepper, StepContentPositionState, STEPPER_GLOBAL_OPTIONS, - StepperOptions + StepperOptions, } from '@angular/cdk/stepper'; import {AnimationEvent} from '@angular/animations'; import { @@ -115,8 +115,65 @@ export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentI } } +/** + * Proxies the public APIs from `MatStepper` to the deprecated `MatHorizontalStepper` and + * `MatVerticalStepper`. + * @deprecated Use `MatStepper` instead. + * @breaking-change 13.0.0 + * @docs-private + */ +@Directive() +abstract class _MatProxyStepperBase extends CdkStepper { + readonly steps: QueryList; + readonly animationDone: EventEmitter; + disableRipple: boolean; + color: ThemePalette; + labelPosition: 'bottom' | 'end'; +} + +/** + * @deprecated Use `MatStepper` instead. + * @breaking-change 13.0.0 + */ +@Directive({selector: 'mat-horizontal-stepper'}) +export class MatHorizontalStepper extends _MatProxyStepperBase {} + +/** + * @deprecated Use `MatStepper` instead. + * @breaking-change 13.0.0 + */ +@Directive({selector: 'mat-vertical-stepper'}) +export class MatVerticalStepper extends _MatProxyStepperBase {} + -@Directive({selector: '[matStepper]', providers: [{provide: CdkStepper, useExisting: MatStepper}]}) +@Component({ + selector: 'mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]', + exportAs: 'matStepper, matVerticalStepper, matHorizontalStepper', + templateUrl: 'stepper.html', + styleUrls: ['stepper.css'], + inputs: ['selectedIndex'], + host: { + '[class.mat-stepper-horizontal]': 'orientation === "horizontal"', + '[class.mat-stepper-vertical]': 'orientation === "vertical"', + '[class.mat-stepper-label-position-end]': + 'orientation === "horizontal" && labelPosition == "end"', + '[class.mat-stepper-label-position-bottom]': + 'orientation === "horizontal" && labelPosition == "bottom"', + '[attr.aria-orientation]': 'orientation', + 'role': 'tablist', + }, + animations: [ + matStepperAnimations.horizontalStepTransition, + matStepperAnimations.verticalStepTransition, + ], + providers: [ + {provide: CdkStepper, useExisting: MatStepper}, + {provide: MatHorizontalStepper, useExisting: MatStepper}, + {provide: MatVerticalStepper, useExisting: MatStepper}, + ], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) export class MatStepper extends CdkStepper implements AfterContentInit { /** The list of step headers of the steps in the stepper. */ @ViewChildren(MatStepHeader) _stepHeader: QueryList; @@ -139,12 +196,29 @@ export class MatStepper extends CdkStepper implements AfterContentInit { /** Theme color for all of the steps in stepper. */ @Input() color: ThemePalette; + /** + * Whether the label should display in bottom or end position. + * Only applies in the `horizontal` orientation. + */ + @Input() + labelPosition: 'bottom' | 'end' = 'end'; + /** Consumer-specified template-refs to be used to override the header icons. */ - _iconOverrides: {[key: string]: TemplateRef} = {}; + _iconOverrides: Record> = {}; /** Stream of animation `done` events when the body expands/collapses. */ _animationDone = new Subject(); + constructor( + @Optional() dir: Directionality, + changeDetectorRef: ChangeDetectorRef, + elementRef: ElementRef, + @Inject(DOCUMENT) _document: any) { + super(dir, changeDetectorRef, elementRef, _document); + const nodeName = elementRef.nativeElement.nodeName.toLowerCase(); + this.orientation = nodeName === 'mat-vertical-stepper' ? 'vertical' : 'horizontal'; + } + ngAfterContentInit() { super.ngAfterContentInit(); this._icons.forEach(({name, templateRef}) => this._iconOverrides[name] = templateRef); @@ -167,79 +241,6 @@ export class MatStepper extends CdkStepper implements AfterContentInit { }); } - protected _updateOrientation() { - if ((typeof ngDevMode === 'undefined' || ngDevMode)) { - throw Error('Updating the orientation of a Material stepper is not supported.'); - } - } - - static ngAcceptInputType_editable: BooleanInput; - static ngAcceptInputType_optional: BooleanInput; - static ngAcceptInputType_completed: BooleanInput; - static ngAcceptInputType_hasError: BooleanInput; -} - -@Component({ - selector: 'mat-horizontal-stepper', - exportAs: 'matHorizontalStepper', - templateUrl: 'stepper-horizontal.html', - styleUrls: ['stepper.css'], - inputs: ['selectedIndex'], - host: { - 'class': 'mat-stepper-horizontal', - '[class.mat-stepper-label-position-end]': 'labelPosition == "end"', - '[class.mat-stepper-label-position-bottom]': 'labelPosition == "bottom"', - 'aria-orientation': 'horizontal', - 'role': 'tablist', - }, - animations: [matStepperAnimations.horizontalStepTransition], - providers: [ - {provide: MatStepper, useExisting: MatHorizontalStepper}, - {provide: CdkStepper, useExisting: MatHorizontalStepper} - ], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MatHorizontalStepper extends MatStepper { - /** Whether the label should display in bottom or end position. */ - @Input() - labelPosition: 'bottom' | 'end' = 'end'; - - static ngAcceptInputType_editable: BooleanInput; - static ngAcceptInputType_optional: BooleanInput; - static ngAcceptInputType_completed: BooleanInput; - static ngAcceptInputType_hasError: BooleanInput; -} - -@Component({ - selector: 'mat-vertical-stepper', - exportAs: 'matVerticalStepper', - templateUrl: 'stepper-vertical.html', - styleUrls: ['stepper.css'], - inputs: ['selectedIndex'], - host: { - 'class': 'mat-stepper-vertical', - 'aria-orientation': 'vertical', - 'role': 'tablist', - }, - animations: [matStepperAnimations.verticalStepTransition], - providers: [ - {provide: MatStepper, useExisting: MatVerticalStepper}, - {provide: CdkStepper, useExisting: MatVerticalStepper} - ], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MatVerticalStepper extends MatStepper { - constructor( - @Optional() dir: Directionality, - changeDetectorRef: ChangeDetectorRef, - elementRef: ElementRef, - @Inject(DOCUMENT) _document: any) { - super(dir, changeDetectorRef, elementRef, _document); - this._orientation = 'vertical'; - } - static ngAcceptInputType_editable: BooleanInput; static ngAcceptInputType_optional: BooleanInput; static ngAcceptInputType_completed: BooleanInput; diff --git a/tools/public_api_guard/cdk/stepper.d.ts b/tools/public_api_guard/cdk/stepper.d.ts index f1988d9aa19a..f8f99172b0f1 100644 --- a/tools/public_api_guard/cdk/stepper.d.ts +++ b/tools/public_api_guard/cdk/stepper.d.ts @@ -71,7 +71,6 @@ export declare class CdkStepper implements AfterContentInit, AfterViewInit, OnDe _getStepLabelId(i: number): string; _onKeydown(event: KeyboardEvent): void; _stateChanged(): void; - protected _updateOrientation(value: StepperOrientation): void; next(): void; ngAfterContentInit(): void; ngAfterViewInit(): void; @@ -84,7 +83,7 @@ export declare class CdkStepper implements AfterContentInit, AfterViewInit, OnDe static ngAcceptInputType_linear: BooleanInput; static ngAcceptInputType_optional: BooleanInput; static ngAcceptInputType_selectedIndex: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/stepper.d.ts b/tools/public_api_guard/material/stepper.d.ts index 961d356a389f..01abf23e5dd3 100644 --- a/tools/public_api_guard/material/stepper.d.ts +++ b/tools/public_api_guard/material/stepper.d.ts @@ -6,13 +6,8 @@ export declare const MAT_STEPPER_INTL_PROVIDER: { export declare function MAT_STEPPER_INTL_PROVIDER_FACTORY(parentIntl: MatStepperIntl): MatStepperIntl; -export declare class MatHorizontalStepper extends MatStepper { - labelPosition: 'bottom' | 'end'; - static ngAcceptInputType_completed: BooleanInput; - static ngAcceptInputType_editable: BooleanInput; - static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_optional: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; +export declare class MatHorizontalStepper extends _MatProxyStepperBase { + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -69,24 +64,23 @@ export declare class MatStepLabel extends CdkStepLabel { export declare class MatStepper extends CdkStepper implements AfterContentInit { _animationDone: Subject; - _iconOverrides: { - [key: string]: TemplateRef; - }; + _iconOverrides: Record>; _icons: QueryList; _stepHeader: QueryList; _steps: QueryList; readonly animationDone: EventEmitter; color: ThemePalette; disableRipple: boolean; + labelPosition: 'bottom' | 'end'; readonly steps: QueryList; - protected _updateOrientation(): void; + constructor(dir: Directionality, changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef, _document: any); ngAfterContentInit(): void; static ngAcceptInputType_completed: BooleanInput; static ngAcceptInputType_editable: BooleanInput; static ngAcceptInputType_hasError: BooleanInput; static ngAcceptInputType_optional: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; - static ɵfac: i0.ɵɵFactoryDef; + static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵfac: i0.ɵɵFactoryDef; } export declare const matStepperAnimations: { @@ -117,7 +111,7 @@ export declare class MatStepperIntl { export declare class MatStepperModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class MatStepperNext extends CdkStepperNext { @@ -130,12 +124,7 @@ export declare class MatStepperPrevious extends CdkStepperPrevious { static ɵfac: i0.ɵɵFactoryDef; } -export declare class MatVerticalStepper extends MatStepper { - constructor(dir: Directionality, changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef, _document: any); - static ngAcceptInputType_completed: BooleanInput; - static ngAcceptInputType_editable: BooleanInput; - static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_optional: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; - static ɵfac: i0.ɵɵFactoryDef; +export declare class MatVerticalStepper extends _MatProxyStepperBase { + static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵfac: i0.ɵɵFactoryDef; }