Skip to content

Commit e2dd016

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 45fae71 commit e2dd016

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
@@ -17,6 +17,7 @@ import {StepperHarnessExample} from './stepper-harness/stepper-harness-example';
1717
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';
20+
import {StepperAnimationsExample} from './stepper-animations/stepper-animations-example';
2021

2122
export {
2223
StepperEditableExample,
@@ -30,6 +31,7 @@ export {
3031
StepperVerticalExample,
3132
StepperLazyContentExample,
3233
StepperResponsiveExample,
34+
StepperAnimationsExample,
3335
};
3436

3537
const EXAMPLES = [
@@ -44,6 +46,7 @@ const EXAMPLES = [
4446
StepperVerticalExample,
4547
StepperLazyContentExample,
4648
StepperResponsiveExample,
49+
StepperAnimationsExample,
4750
];
4851

4952
@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
@@ -130,6 +130,12 @@ by placing a `matStepperIcon` for each of the icons that you want to override. T
130130

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

133+
### Controlling the stepper animation
134+
You can control the duration of the stepper's animation using the `animationDuration` input. If you
135+
want to disable the animation completely, you can do so by setting the properties to `0ms`.
136+
137+
<!-- example(stepper-animations) -->
138+
133139
#### Step States
134140
You can set the state of a step to whatever you want. The given state by default maps to an icon.
135141
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
@@ -177,6 +177,16 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
177177
/** Stream of animation `done` events when the body expands/collapses. */
178178
readonly _animationDone = new Subject<AnimationEvent>();
179179

180+
/** Duration for the animation. Will be normalized to milliseconds if no units are set. */
181+
@Input()
182+
get animationDuration(): string {
183+
return this._animationDuration;
184+
}
185+
set animationDuration(value: string) {
186+
this._animationDuration = /^\d+$/.test(value) ? value + 'ms' : value;
187+
}
188+
private _animationDuration = '';
189+
180190
constructor(
181191
@Optional() dir: Directionality,
182192
changeDetectorRef: ChangeDetectorRef,
@@ -214,4 +224,12 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
214224
_stepIsNavigable(index: number, step: MatStep): boolean {
215225
return step.completed || this.selectedIndex === index || !this.linear;
216226
}
227+
228+
_getAnimationDuration() {
229+
if (this.animationDuration) {
230+
return this.animationDuration;
231+
}
232+
233+
return this.orientation === 'horizontal' ? '500ms' : '225ms';
234+
}
217235
}

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
_iconOverrides: Record<string, TemplateRef<MatStepperIconContext>>;
136140
_icons: QueryList<MatStepperIcon>;
137141
labelPosition: 'bottom' | 'end';
@@ -143,7 +147,7 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
143147
readonly steps: QueryList<MatStep>;
144148
_steps: QueryList<MatStep>;
145149
// (undocumented)
146-
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"; }, { "animationDone": "animationDone"; }, ["_steps", "_icons"], never>;
150+
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"; "animationDuration": "animationDuration"; }, { "animationDone": "animationDone"; }, ["_steps", "_icons"], never>;
147151
// (undocumented)
148152
static ɵfac: i0.ɵɵFactoryDeclaration<MatStepper, [{ optional: true; }, null, null]>;
149153
}

0 commit comments

Comments
 (0)