Skip to content

Commit 20a7f1b

Browse files
committed
feat(material/stepper): add input for controlling the animation duration
Adds a new input called `animationDuration` that allows users to control the animation duration of a stepper, similarly to other components. Fixes #17130.
1 parent fc5f8b0 commit 20a7f1b

File tree

10 files changed

+120
-5
lines changed

10 files changed

+120
-5
lines changed

src/components-examples/material/stepper/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {StepperIntlExample} from './stepper-intl/stepper-intl-example';
1818
import {StepperLazyContentExample} from './stepper-lazy-content/stepper-lazy-content-example';
1919
import {StepperResponsiveExample} from './stepper-responsive/stepper-responsive-example';
2020
import {StepperHeaderPositionExample} from './stepper-header-position/stepper-header-position-example';
21+
import {StepperAnimationsExample} from './stepper-animations/stepper-animations-example';
2122

2223
export {
2324
StepperEditableExample,
@@ -32,6 +33,7 @@ export {
3233
StepperLazyContentExample,
3334
StepperResponsiveExample,
3435
StepperHeaderPositionExample,
36+
StepperAnimationsExample,
3537
};
3638

3739
const EXAMPLES = [
@@ -47,6 +49,7 @@ const EXAMPLES = [
4749
StepperLazyContentExample,
4850
StepperResponsiveExample,
4951
StepperHeaderPositionExample,
52+
StepperAnimationsExample,
5053
];
5154

5255
@NgModule({
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.example-input-wrapper {
2+
margin-bottom: 16px;
3+
}
4+
5+
label {
6+
margin-right: 4px;
7+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<div class="example-input-wrapper">
2+
<label for="duration">Animation duration:</label>
3+
<input id="duration" value="2000" type="number" min="0" step="100" #duration>
4+
</div>
5+
6+
<mat-vertical-stepper [linear]="false" #stepper [animationDuration]="duration.value">
7+
<mat-step [stepControl]="firstFormGroup">
8+
<form [formGroup]="firstFormGroup">
9+
<ng-template matStepLabel>Fill out your name</ng-template>
10+
<mat-form-field>
11+
<input matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
12+
</mat-form-field>
13+
<div>
14+
<button mat-button matStepperNext>Next</button>
15+
</div>
16+
</form>
17+
</mat-step>
18+
<mat-step [stepControl]="secondFormGroup">
19+
<form [formGroup]="secondFormGroup">
20+
<ng-template matStepLabel>Fill out your address</ng-template>
21+
<mat-form-field>
22+
<input matInput placeholder="Address" formControlName="secondCtrl" required>
23+
</mat-form-field>
24+
<div>
25+
<button mat-button matStepperPrevious>Back</button>
26+
<button mat-button matStepperNext>Next</button>
27+
</div>
28+
</form>
29+
</mat-step>
30+
<mat-step>
31+
<ng-template matStepLabel>Done</ng-template>
32+
You are now done.
33+
<div>
34+
<button mat-button matStepperPrevious>Back</button>
35+
<button mat-button (click)="stepper.reset()">Reset</button>
36+
</div>
37+
</mat-step>
38+
</mat-vertical-stepper>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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 {Component} from '@angular/core';
10+
import {FormBuilder, FormGroup} from '@angular/forms';
11+
12+
/**
13+
* @title Stepper animations
14+
*/
15+
@Component({
16+
selector: 'stepper-animations-example',
17+
templateUrl: 'stepper-animations-example.html',
18+
styleUrls: ['stepper-animations-example.css'],
19+
})
20+
export class StepperAnimationsExample {
21+
constructor(private _formBuilder: FormBuilder) {}
22+
firstFormGroup: FormGroup = this._formBuilder.group({firstCtrl: ['']});
23+
secondFormGroup: FormGroup = this._formBuilder.group({secondCtrl: ['']});
24+
}

src/material/stepper/stepper-animations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const matStepperAnimations: {
3030
// making this element focusable inside of a `hidden` element.
3131
state('current', style({transform: 'none', visibility: 'inherit'})),
3232
state('next', style({transform: 'translate3d(100%, 0, 0)', visibility: 'hidden'})),
33-
transition('* => *', animate('500ms cubic-bezier(0.35, 0, 0.25, 1)')),
33+
transition('* => *', animate('{{animationDuration}} cubic-bezier(0.35, 0, 0.25, 1)')),
3434
]),
3535

3636
/** Animation that transitions the step along the Y axis in a vertical stepper. */
@@ -41,6 +41,6 @@ export const matStepperAnimations: {
4141
// because visibility on a child element the one from the parent,
4242
// making this element focusable inside of a `hidden` element.
4343
state('current', style({height: '*', visibility: 'inherit'})),
44-
transition('* <=> current', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
44+
transition('* <=> current', animate('{{animationDuration}} cubic-bezier(0.4, 0.0, 0.2, 1)')),
4545
]),
4646
};

src/material/stepper/stepper.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
<div class="mat-horizontal-content-container">
1414
<div *ngFor="let step of steps; let i = index"
1515
class="mat-horizontal-stepper-content" role="tabpanel"
16-
[@horizontalStepTransition]="_getAnimationDirection(i)"
16+
[@horizontalStepTransition]="{
17+
value: _getAnimationDirection(i),
18+
params: {animationDuration: _getAnimationDuration()}
19+
}"
1720
(@horizontalStepTransition.done)="_animationDone.next($event)"
1821
[id]="_getStepContentId(i)"
1922
[attr.aria-labelledby]="_getStepLabelId(i)"
@@ -31,7 +34,10 @@
3134
[ngTemplateOutletContext]="{step: step, i: i}"></ng-container>
3235
<div class="mat-vertical-content-container" [class.mat-stepper-vertical-line]="!isLast">
3336
<div class="mat-vertical-stepper-content" role="tabpanel"
34-
[@verticalStepTransition]="_getAnimationDirection(i)"
37+
[@verticalStepTransition]="{
38+
value: _getAnimationDirection(i),
39+
params: {animationDuration: _getAnimationDuration()}
40+
}"
3541
(@verticalStepTransition.done)="_animationDone.next($event)"
3642
[id]="_getStepContentId(i)"
3743
[attr.aria-labelledby]="_getStepLabelId(i)"

src/material/stepper/stepper.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ by placing a `matStepperIcon` for each of the icons that you want to override. T
137137

138138
Note that you aren't limited to using the `mat-icon` component when providing custom icons.
139139

140+
### Controlling the stepper animation
141+
You can control the duration of the stepper's animation using the `animationDuration` input. If you
142+
want to disable the animation completely, you can do so by setting the properties to `0ms`.
143+
144+
<!-- example(stepper-animations) -->
145+
140146
#### Step States
141147
You can set the state of a step to whatever you want. The given state by default maps to an icon.
142148
However, it can be overridden the same way as mentioned above.

src/material/stepper/stepper.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,15 @@ describe('MatStepper', () => {
449449
const icon = fixture.nativeElement.querySelector('.mat-step-icon span');
450450
expect(icon.getAttribute('aria-hidden')).toBe('true');
451451
});
452+
453+
it('should add units to unit-less values passed in to animationDuration', () => {
454+
const stepperComponent: MatStepper = fixture.debugElement.query(
455+
By.directive(MatStepper),
456+
)!.componentInstance;
457+
458+
stepperComponent.animationDuration = '1337';
459+
expect(stepperComponent.animationDuration).toBe('1337ms');
460+
});
452461
});
453462

454463
describe('basic stepper when attempting to set the selected step too early', () => {

src/material/stepper/stepper.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,16 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
185185
/** Stream of animation `done` events when the body expands/collapses. */
186186
readonly _animationDone = new Subject<AnimationEvent>();
187187

188+
/** Duration for the animation. Will be normalized to milliseconds if no units are set. */
189+
@Input()
190+
get animationDuration(): string {
191+
return this._animationDuration;
192+
}
193+
set animationDuration(value: string) {
194+
this._animationDuration = /^\d+$/.test(value) ? value + 'ms' : value;
195+
}
196+
private _animationDuration = '';
197+
188198
constructor(
189199
@Optional() dir: Directionality,
190200
changeDetectorRef: ChangeDetectorRef,
@@ -222,4 +232,12 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
222232
_stepIsNavigable(index: number, step: MatStep): boolean {
223233
return step.completed || this.selectedIndex === index || !this.linear;
224234
}
235+
236+
_getAnimationDuration() {
237+
if (this.animationDuration) {
238+
return this.animationDuration;
239+
}
240+
241+
return this.orientation === 'horizontal' ? '500ms' : '225ms';
242+
}
225243
}

tools/public_api_guard/material/stepper.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,12 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
130130
constructor(dir: Directionality, changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef<HTMLElement>);
131131
readonly animationDone: EventEmitter<void>;
132132
readonly _animationDone: Subject<AnimationEvent_2>;
133+
get animationDuration(): string;
134+
set animationDuration(value: string);
133135
color: ThemePalette;
134136
disableRipple: boolean;
137+
// (undocumented)
138+
_getAnimationDuration(): string;
135139
headerPosition: 'top' | 'bottom';
136140
_iconOverrides: Record<string, TemplateRef<MatStepperIconContext>>;
137141
_icons: QueryList<MatStepperIcon>;
@@ -144,7 +148,7 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
144148
readonly steps: QueryList<MatStep>;
145149
_steps: QueryList<MatStep>;
146150
// (undocumented)
147-
static ɵcmp: i0.ɵɵComponentDeclaration<MatStepper, "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]", ["matStepper", "matVerticalStepper", "matHorizontalStepper"], { "selectedIndex": "selectedIndex"; "disableRipple": "disableRipple"; "color": "color"; "labelPosition": "labelPosition"; "headerPosition": "headerPosition"; }, { "animationDone": "animationDone"; }, ["_steps", "_icons"], never>;
151+
static ɵcmp: i0.ɵɵComponentDeclaration<MatStepper, "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]", ["matStepper", "matVerticalStepper", "matHorizontalStepper"], { "selectedIndex": "selectedIndex"; "disableRipple": "disableRipple"; "color": "color"; "labelPosition": "labelPosition"; "headerPosition": "headerPosition"; "animationDuration": "animationDuration"; }, { "animationDone": "animationDone"; }, ["_steps", "_icons"], never>;
148152
// (undocumented)
149153
static ɵfac: i0.ɵɵFactoryDeclaration<MatStepper, [{ optional: true; }, null, null]>;
150154
}

0 commit comments

Comments
 (0)