Skip to content

Commit 79aa133

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 dc020c7 commit 79aa133

File tree

11 files changed

+135
-7
lines changed

11 files changed

+135
-7
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/public-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ export * from './stepper';
1313
export * from './stepper-button';
1414
export * from './step-header';
1515
export * from './stepper-intl';
16-
export * from './stepper-animations';
16+
export {matStepperAnimations} from './stepper-animations';
1717
export * from './stepper-icon';
1818
export * from './step-content';

src/material/stepper/stepper-animations.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import {
1414
AnimationTriggerMetadata,
1515
} from '@angular/animations';
1616

17+
export const DEFAULT_HORIZONTAL_ANIMATION_DURATION = '500ms';
18+
export const DEFAULT_VERTICAL_ANIMATION_DURATION = '225ms';
19+
1720
/**
1821
* Animations used by the Material steppers.
1922
* @docs-private
@@ -30,7 +33,9 @@ export const matStepperAnimations: {
3033
// making this element focusable inside of a `hidden` element.
3134
state('current', style({transform: 'none', visibility: 'inherit'})),
3235
state('next', style({transform: 'translate3d(100%, 0, 0)', visibility: 'hidden'})),
33-
transition('* => *', animate('500ms cubic-bezier(0.35, 0, 0.25, 1)')),
36+
transition('* => *', animate('{{animationDuration}} cubic-bezier(0.35, 0, 0.25, 1)'), {
37+
params: {'animationDuration': DEFAULT_HORIZONTAL_ANIMATION_DURATION},
38+
}),
3439
]),
3540

3641
/** Animation that transitions the step along the Y axis in a vertical stepper. */
@@ -41,6 +46,8 @@ export const matStepperAnimations: {
4146
// because visibility on a child element the one from the parent,
4247
// making this element focusable inside of a `hidden` element.
4348
state('current', style({height: '*', visibility: 'inherit'})),
44-
transition('* <=> current', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
49+
transition('* <=> current', animate('{{animationDuration}} cubic-bezier(0.4, 0.0, 0.2, 1)'), {
50+
params: {'animationDuration': DEFAULT_VERTICAL_ANIMATION_DURATION},
51+
}),
4552
]),
4653
};

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: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ import {takeUntil, distinctUntilChanged, map, startWith, switchMap} from 'rxjs/o
4545

4646
import {MatStepHeader} from './step-header';
4747
import {MatStepLabel} from './step-label';
48-
import {matStepperAnimations} from './stepper-animations';
48+
import {
49+
DEFAULT_HORIZONTAL_ANIMATION_DURATION,
50+
DEFAULT_VERTICAL_ANIMATION_DURATION,
51+
matStepperAnimations,
52+
} from './stepper-animations';
4953
import {MatStepperIcon, MatStepperIconContext} from './stepper-icon';
5054
import {MatStepContent} from './step-content';
5155

@@ -185,6 +189,16 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
185189
/** Stream of animation `done` events when the body expands/collapses. */
186190
readonly _animationDone = new Subject<AnimationEvent>();
187191

192+
/** Duration for the animation. Will be normalized to milliseconds if no units are set. */
193+
@Input()
194+
get animationDuration(): string {
195+
return this._animationDuration;
196+
}
197+
set animationDuration(value: string) {
198+
this._animationDuration = /^\d+$/.test(value) ? value + 'ms' : value;
199+
}
200+
private _animationDuration = '';
201+
188202
constructor(
189203
@Optional() dir: Directionality,
190204
changeDetectorRef: ChangeDetectorRef,
@@ -222,4 +236,14 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
222236
_stepIsNavigable(index: number, step: MatStep): boolean {
223237
return step.completed || this.selectedIndex === index || !this.linear;
224238
}
239+
240+
_getAnimationDuration() {
241+
if (this.animationDuration) {
242+
return this.animationDuration;
243+
}
244+
245+
return this.orientation === 'horizontal'
246+
? DEFAULT_HORIZONTAL_ANIMATION_DURATION
247+
: DEFAULT_VERTICAL_ANIMATION_DURATION;
248+
}
225249
}

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)