Skip to content

feat(material/stepper): add input for controlling the animation duration #17133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components-examples/material/stepper/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {StepperIntlExample} from './stepper-intl/stepper-intl-example';
import {StepperLazyContentExample} from './stepper-lazy-content/stepper-lazy-content-example';
import {StepperResponsiveExample} from './stepper-responsive/stepper-responsive-example';
import {StepperHeaderPositionExample} from './stepper-header-position/stepper-header-position-example';
import {StepperAnimationsExample} from './stepper-animations/stepper-animations-example';

export {
StepperEditableExample,
Expand All @@ -32,6 +33,7 @@ export {
StepperLazyContentExample,
StepperResponsiveExample,
StepperHeaderPositionExample,
StepperAnimationsExample,
};

const EXAMPLES = [
Expand All @@ -47,6 +49,7 @@ const EXAMPLES = [
StepperLazyContentExample,
StepperResponsiveExample,
StepperHeaderPositionExample,
StepperAnimationsExample,
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.example-input-wrapper {
margin-bottom: 16px;
}

label {
margin-right: 4px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<div class="example-input-wrapper">
<label for="duration">Animation duration:</label>
<input id="duration" value="2000" type="number" min="0" step="100" #duration>
</div>

<mat-vertical-stepper [linear]="false" #stepper [animationDuration]="duration.value">
<mat-step [stepControl]="firstFormGroup">
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>Fill out your name</ng-template>
<mat-form-field>
<input matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
</mat-form-field>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>Fill out your address</ng-template>
<mat-form-field>
<input matInput placeholder="Address" formControlName="secondCtrl" required>
</mat-form-field>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step>
<ng-template matStepLabel>Done</ng-template>
You are now done.
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
</mat-step>
</mat-vertical-stepper>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Component} from '@angular/core';
import {FormBuilder, FormGroup} from '@angular/forms';

/**
* @title Stepper animations
*/
@Component({
selector: 'stepper-animations-example',
templateUrl: 'stepper-animations-example.html',
styleUrls: ['stepper-animations-example.css'],
})
export class StepperAnimationsExample {
constructor(private _formBuilder: FormBuilder) {}
firstFormGroup: FormGroup = this._formBuilder.group({firstCtrl: ['']});
secondFormGroup: FormGroup = this._formBuilder.group({secondCtrl: ['']});
}
2 changes: 1 addition & 1 deletion src/material/stepper/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ export * from './stepper';
export * from './stepper-button';
export * from './step-header';
export * from './stepper-intl';
export * from './stepper-animations';
export {matStepperAnimations} from './stepper-animations';
export * from './stepper-icon';
export * from './step-content';
11 changes: 9 additions & 2 deletions src/material/stepper/stepper-animations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
AnimationTriggerMetadata,
} from '@angular/animations';

export const DEFAULT_HORIZONTAL_ANIMATION_DURATION = '500ms';
export const DEFAULT_VERTICAL_ANIMATION_DURATION = '225ms';

/**
* Animations used by the Material steppers.
* @docs-private
Expand All @@ -30,7 +33,9 @@ export const matStepperAnimations: {
// making this element focusable inside of a `hidden` element.
state('current', style({transform: 'none', visibility: 'inherit'})),
state('next', style({transform: 'translate3d(100%, 0, 0)', visibility: 'hidden'})),
transition('* => *', animate('500ms cubic-bezier(0.35, 0, 0.25, 1)')),
transition('* => *', animate('{{animationDuration}} cubic-bezier(0.35, 0, 0.25, 1)'), {
params: {'animationDuration': DEFAULT_HORIZONTAL_ANIMATION_DURATION},
}),
]),

/** Animation that transitions the step along the Y axis in a vertical stepper. */
Expand All @@ -41,6 +46,8 @@ export const matStepperAnimations: {
// because visibility on a child element the one from the parent,
// making this element focusable inside of a `hidden` element.
state('current', style({height: '*', visibility: 'inherit'})),
transition('* <=> current', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
transition('* <=> current', animate('{{animationDuration}} cubic-bezier(0.4, 0.0, 0.2, 1)'), {
params: {'animationDuration': DEFAULT_VERTICAL_ANIMATION_DURATION},
}),
]),
};
10 changes: 8 additions & 2 deletions src/material/stepper/stepper.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
<div class="mat-horizontal-content-container">
<div *ngFor="let step of steps; let i = index"
class="mat-horizontal-stepper-content" role="tabpanel"
[@horizontalStepTransition]="_getAnimationDirection(i)"
[@horizontalStepTransition]="{
'value': _getAnimationDirection(i),
'params': {'animationDuration': _getAnimationDuration()}
}"
(@horizontalStepTransition.done)="_animationDone.next($event)"
[id]="_getStepContentId(i)"
[attr.aria-labelledby]="_getStepLabelId(i)"
Expand All @@ -31,7 +34,10 @@
[ngTemplateOutletContext]="{step: step, i: i}"></ng-container>
<div class="mat-vertical-content-container" [class.mat-stepper-vertical-line]="!isLast">
<div class="mat-vertical-stepper-content" role="tabpanel"
[@verticalStepTransition]="_getAnimationDirection(i)"
[@verticalStepTransition]="{
'value': _getAnimationDirection(i),
'params': {'animationDuration': _getAnimationDuration()}
}"
(@verticalStepTransition.done)="_animationDone.next($event)"
[id]="_getStepContentId(i)"
[attr.aria-labelledby]="_getStepLabelId(i)"
Expand Down
6 changes: 6 additions & 0 deletions src/material/stepper/stepper.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ by placing a `matStepperIcon` for each of the icons that you want to override. T

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

### Controlling the stepper animation
You can control the duration of the stepper's animation using the `animationDuration` input. If you
want to disable the animation completely, you can do so by setting the properties to `0ms`.

<!-- example(stepper-animations) -->

#### Step States
You can set the state of a step to whatever you want. The given state by default maps to an icon.
However, it can be overridden the same way as mentioned above.
Expand Down
9 changes: 9 additions & 0 deletions src/material/stepper/stepper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,15 @@ describe('MatStepper', () => {
const icon = fixture.nativeElement.querySelector('.mat-step-icon span');
expect(icon.getAttribute('aria-hidden')).toBe('true');
});

it('should add units to unit-less values passed in to animationDuration', () => {
const stepperComponent: MatStepper = fixture.debugElement.query(
By.directive(MatStepper),
)!.componentInstance;

stepperComponent.animationDuration = '1337';
expect(stepperComponent.animationDuration).toBe('1337ms');
});
});

describe('basic stepper when attempting to set the selected step too early', () => {
Expand Down
26 changes: 25 additions & 1 deletion src/material/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ import {takeUntil, distinctUntilChanged, map, startWith, switchMap} from 'rxjs/o

import {MatStepHeader} from './step-header';
import {MatStepLabel} from './step-label';
import {matStepperAnimations} from './stepper-animations';
import {
DEFAULT_HORIZONTAL_ANIMATION_DURATION,
DEFAULT_VERTICAL_ANIMATION_DURATION,
matStepperAnimations,
} from './stepper-animations';
import {MatStepperIcon, MatStepperIconContext} from './stepper-icon';
import {MatStepContent} from './step-content';

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

/** Duration for the animation. Will be normalized to milliseconds if no units are set. */
@Input()
get animationDuration(): string {
return this._animationDuration;
}
set animationDuration(value: string) {
this._animationDuration = /^\d+$/.test(value) ? value + 'ms' : value;
}
private _animationDuration = '';

constructor(
@Optional() dir: Directionality,
changeDetectorRef: ChangeDetectorRef,
Expand Down Expand Up @@ -222,4 +236,14 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
_stepIsNavigable(index: number, step: MatStep): boolean {
return step.completed || this.selectedIndex === index || !this.linear;
}

_getAnimationDuration() {
if (this.animationDuration) {
return this.animationDuration;
}

return this.orientation === 'horizontal'
? DEFAULT_HORIZONTAL_ANIMATION_DURATION
: DEFAULT_VERTICAL_ANIMATION_DURATION;
}
}
6 changes: 5 additions & 1 deletion tools/public_api_guard/material/stepper.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,12 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
constructor(dir: Directionality, changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef<HTMLElement>);
readonly animationDone: EventEmitter<void>;
readonly _animationDone: Subject<AnimationEvent_2>;
get animationDuration(): string;
set animationDuration(value: string);
color: ThemePalette;
disableRipple: boolean;
// (undocumented)
_getAnimationDuration(): string;
headerPosition: 'top' | 'bottom';
_iconOverrides: Record<string, TemplateRef<MatStepperIconContext>>;
_icons: QueryList<MatStepperIcon>;
Expand All @@ -144,7 +148,7 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
readonly steps: QueryList<MatStep>;
_steps: QueryList<MatStep>;
// (undocumented)
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>;
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>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatStepper, [{ optional: true; }, null, null]>;
}
Expand Down