Skip to content

Commit ed09a73

Browse files
authored
feat(material/stepper): allow for orientation to be changed dynamically (#22139)
Combines `mat-vertical-stepper` and `mat-horizontal-stepper` into a single `mat-stepper` class in order to allow for the orientation to be changed dynamically. Also deprecates `MatVerticalStepper` and `MatHorizontalStepper`. This is a reimplementation of #9173, however this time I took a different approach which should make it easier to maintain and eventually remove the two separate steppers. It should result in a smaller bundle as well. The main differences are: 1. Rather than have 3 components (`MatStepper`, `MatVerticalStepper` and `MatHorizontalStepper`), these changes combine everything into `MatStepper` while `MatVerticalStepper` and `MatHorizontalStepper` are only used as injection tokens for backwards compatibility. The `selector` and `exportAs` of `MatStepper` is changed to match the two individual steppers and the orientation is inferred from the tag name. This will make it much easier to remove the deprecated directives. Furthermore, it should result in a smaller bundle since the template and styles only need to be inlined in one place. 2. `MatVerticalStepper` and `MatHorizontalStepper` are turned into very basic directives that have the same public API as `MatStepper` and they proxy everything to it. This is primarily so that if somebody managed to get a hold of a `MatVerticalStepper` or `MatHorizontalStepper` instance, or they used the old classes to type their own code, it wouldn't result in a breaking change. Relates to #7700.
1 parent 2427abe commit ed09a73

File tree

13 files changed

+213
-217
lines changed

13 files changed

+213
-217
lines changed

src/cdk/stepper/stepper.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -315,11 +315,16 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
315315
/** Used to track unique ID for each stepper component. */
316316
_groupId: number;
317317

318-
// Note that this isn't an `Input` so it doesn't bleed into the Material stepper.
319318
/** Orientation of the stepper. */
319+
@Input()
320320
get orientation(): StepperOrientation { return this._orientation; }
321321
set orientation(value: StepperOrientation) {
322-
this._updateOrientation(value);
322+
// This is a protected method so that `MatSteppter` can hook into it.
323+
this._orientation = value;
324+
325+
if (this._keyManager) {
326+
this._keyManager.withVerticalOrientation(value === 'vertical');
327+
}
323328
}
324329

325330
/**
@@ -432,16 +437,6 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
432437
this._getGuidelineLogic(step, isCurrentStep, state);
433438
}
434439

435-
/** Updates the stepper orientation. */
436-
protected _updateOrientation(value: StepperOrientation) {
437-
// This is a protected method so that `MatSteppter` can hook into it.
438-
this._orientation = value;
439-
440-
if (this._keyManager) {
441-
this._keyManager.withVerticalOrientation(value === 'vertical');
442-
}
443-
}
444-
445440
private _getDefaultIndicatorLogic(step: CdkStep, isCurrentStep: boolean): StepState {
446441
if (step._showError && step.hasError && !isCurrentStep) {
447442
return STEP_STATE.ERROR;

src/dev-app/stepper/stepper-demo.html

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
<p>
55
<mat-checkbox [(ngModel)]="disableRipple">Disable header ripple</mat-checkbox>
66
</p>
7+
<p>
8+
<mat-checkbox [(ngModel)]="isVertical">Vertical</mat-checkbox>
9+
</p>
710
<p>
811
<button mat-stroked-button (click)="showLabelBottom = !showLabelBottom">
912
Toggle label position
@@ -18,10 +21,15 @@
1821
</mat-form-field>
1922
</p>
2023

21-
<h3>Linear Vertical Stepper Demo using a single form</h3>
24+
<h3>Linear Stepper Demo using a single form</h3>
2225
<form [formGroup]="formGroup">
23-
<mat-vertical-stepper #linearVerticalStepper="matVerticalStepper" formArrayName="formArray"
24-
[linear]="!isNonLinear" [disableRipple]="disableRipple" [color]="theme">
26+
<mat-stepper
27+
#linearStepper="matVerticalStepper"
28+
formArrayName="formArray"
29+
[orientation]="isVertical ? 'vertical' : 'horizontal'"
30+
[linear]="!isNonLinear"
31+
[disableRipple]="disableRipple"
32+
[color]="theme">
2533
<mat-step formGroupName="0" [stepControl]="formArray?.get([0]) === null ? undefined! : formArray?.get([0])!">
2634
<ng-template matStepLabel>Fill out your name</ng-template>
2735
<mat-form-field>
@@ -61,10 +69,10 @@ <h3>Linear Vertical Stepper Demo using a single form</h3>
6169
Everything seems correct.
6270
<div>
6371
<button mat-button>Done</button>
64-
<button type="button" mat-button (click)="linearVerticalStepper.reset()">Reset</button>
72+
<button type="button" mat-button (click)="linearStepper.reset()">Reset</button>
6573
</div>
6674
</mat-step>
67-
</mat-vertical-stepper>
75+
</mat-stepper>
6876
</form>
6977

7078
<h3>Linear Horizontal Stepper Demo using a different form for each step</h3>

src/dev-app/stepper/stepper-demo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class StepperDemo implements OnInit {
2121
isNonEditable = false;
2222
disableRipple = false;
2323
showLabelBottom = false;
24+
isVertical = false;
2425

2526
nameFormGroup: FormGroup;
2627
emailFormGroup: FormGroup;

src/material/stepper/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
export {StepperOrientation} from '@angular/cdk/stepper';
910
export * from './stepper-module';
1011
export * from './step-label';
1112
export * from './stepper';

src/material/stepper/stepper-animations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const matStepperAnimations: {
2323
readonly verticalStepTransition: AnimationTriggerMetadata;
2424
} = {
2525
/** Animation that transitions the step along the X axis in a horizontal stepper. */
26-
horizontalStepTransition: trigger('stepTransition', [
26+
horizontalStepTransition: trigger('horizontalStepTransition', [
2727
state('previous', style({transform: 'translate3d(-100%, 0, 0)', visibility: 'hidden'})),
2828
// Transition to `inherit`, rather than `visible`,
2929
// because visibility on a child element the one from the parent,
@@ -34,7 +34,7 @@ export const matStepperAnimations: {
3434
]),
3535

3636
/** Animation that transitions the step along the Y axis in a vertical stepper. */
37-
verticalStepTransition: trigger('stepTransition', [
37+
verticalStepTransition: trigger('verticalStepTransition', [
3838
state('previous', style({height: '0px', visibility: 'hidden'})),
3939
state('next', style({height: '0px', visibility: 'hidden'})),
4040
// Transition to `inherit`, rather than `visible`,

src/material/stepper/stepper-horizontal.html

Lines changed: 0 additions & 39 deletions
This file was deleted.

src/material/stepper/stepper-module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ import {MatStepContent} from './step-content';
3434
],
3535
exports: [
3636
MatCommonModule,
37-
MatHorizontalStepper,
38-
MatVerticalStepper,
3937
MatStep,
4038
MatStepLabel,
4139
MatStepper,

src/material/stepper/stepper-vertical.html

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/material/stepper/stepper.html

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<ng-container [ngSwitch]="orientation">
2+
<!-- Horizontal stepper -->
3+
<ng-container *ngSwitchCase="'horizontal'">
4+
<div class="mat-horizontal-stepper-header-container">
5+
<ng-container *ngFor="let step of steps; let i = index; let isLast = last">
6+
<ng-container
7+
[ngTemplateOutlet]="stepTemplate"
8+
[ngTemplateOutletContext]="{step: step, i: i}"></ng-container>
9+
<div *ngIf="!isLast" class="mat-stepper-horizontal-line"></div>
10+
</ng-container>
11+
</div>
12+
13+
<div class="mat-horizontal-content-container">
14+
<div *ngFor="let step of steps; let i = index"
15+
class="mat-horizontal-stepper-content" role="tabpanel"
16+
[@horizontalStepTransition]="_getAnimationDirection(i)"
17+
(@horizontalStepTransition.done)="_animationDone.next($event)"
18+
[id]="_getStepContentId(i)"
19+
[attr.aria-labelledby]="_getStepLabelId(i)"
20+
[attr.aria-expanded]="selectedIndex === i">
21+
<ng-container [ngTemplateOutlet]="step.content"></ng-container>
22+
</div>
23+
</div>
24+
</ng-container>
25+
26+
<!-- Vertical stepper -->
27+
<ng-container *ngSwitchCase="'vertical'">
28+
<div class="mat-step" *ngFor="let step of steps; let i = index; let isLast = last">
29+
<ng-container
30+
[ngTemplateOutlet]="stepTemplate"
31+
[ngTemplateOutletContext]="{step: step, i: i}"></ng-container>
32+
<div class="mat-vertical-content-container" [class.mat-stepper-vertical-line]="!isLast">
33+
<div class="mat-vertical-stepper-content" role="tabpanel"
34+
[@verticalStepTransition]="_getAnimationDirection(i)"
35+
(@verticalStepTransition.done)="_animationDone.next($event)"
36+
[id]="_getStepContentId(i)"
37+
[attr.aria-labelledby]="_getStepLabelId(i)"
38+
[attr.aria-expanded]="selectedIndex === i">
39+
<div class="mat-vertical-content">
40+
<ng-container [ngTemplateOutlet]="step.content"></ng-container>
41+
</div>
42+
</div>
43+
</div>
44+
</div>
45+
</ng-container>
46+
47+
</ng-container>
48+
49+
<!-- Common step templating -->
50+
<ng-template let-step="step" let-i="i" #stepTemplate>
51+
<mat-step-header
52+
[class.mat-horizontal-stepper-header]="orientation === 'horizontal'"
53+
[class.mat-vertical-stepper-header]="orientation === 'vertical'"
54+
(click)="step.select()"
55+
(keydown)="_onKeydown($event)"
56+
[tabIndex]="_getFocusIndex() === i ? 0 : -1"
57+
[id]="_getStepLabelId(i)"
58+
[attr.aria-posinset]="i + 1"
59+
[attr.aria-setsize]="steps.length"
60+
[attr.aria-controls]="_getStepContentId(i)"
61+
[attr.aria-selected]="selectedIndex == i"
62+
[attr.aria-label]="step.ariaLabel || null"
63+
[attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null"
64+
[index]="i"
65+
[state]="_getIndicatorType(i, step.state)"
66+
[label]="step.stepLabel || step.label"
67+
[selected]="selectedIndex === i"
68+
[active]="step.completed || selectedIndex === i || !linear"
69+
[optional]="step.optional"
70+
[errorMessage]="step.errorMessage"
71+
[iconOverrides]="_iconOverrides"
72+
[disableRipple]="disableRipple"
73+
[color]="step.color || color"></mat-step-header>
74+
</ng-template>

0 commit comments

Comments
 (0)