license import { Directionality } from '@angular/cdk/bidi' ; import { CdkStep , CdkStepper , StepContentPositionState , STEPPER_GLOBAL_OPTIONS , StepperOptions } from '@angular/cdk/stepper' ; import { AnimationEvent } from '@angular/animations' ; import { AfterContentInit , ChangeDetectionStrategy , ChangeDetectorRef , Component , ContentChild , ContentChildren , Directive , ElementRef , EventEmitter , forwardRef , Inject , Input , Optional , Output , QueryList , SkipSelf , TemplateRef , ViewChildren , ViewEncapsulation , } from '@angular/core' ; import { FormControl , FormGroupDirective , NgForm } from '@angular/forms' ; import { DOCUMENT } from '@angular/common' ; import { ErrorStateMatcher } from '@angular/material/core' ; import { Subject } from 'rxjs' ; import { takeUntil , distinctUntilChanged } from 'rxjs/operators' ; import { MatStepHeader } from './step-header' ; import { MatStepLabel } from './step-label' ; import { matStepperAnimations } from './stepper-animations' ; import { MatStepperIcon , MatStepperIconContext } from './stepper-icon' ; @ Component ( { moduleId : module . id , selector : 'mat-step' , templateUrl : 'step.html' , providers : [ { provide : ErrorStateMatcher , useExisting : MatStep } ] , encapsulation : ViewEncapsulation . None , exportAs : 'matStep' , changeDetection : ChangeDetectionStrategy . OnPush , } ) export class MatStep extends CdkStep implements ErrorStateMatcher { /** Content for step label given by `<ng-template matStepLabel>`. */ @ ContentChild ( MatStepLabel ) stepLabel : MatStepLabel ; breaking-change constructor ( @ Inject ( forwardRef ( ( ) => MatStepper ) ) stepper , @ SkipSelf ( ) private _errorStateMatcher , @ Optional ( ) @ Inject ( STEPPER_GLOBAL_OPTIONS ) stepperOptions ? ) { super ( stepper , stepperOptions ) ; } /** Custom error state matcher that additionally checks for validity of interacted form. */ isErrorState ( control , form ) { const originalErrorState = this . _errorStateMatcher . isErrorState ( control , form ) ; const customErrorState = ! ! ( control && control . invalid && this . interacted ) ; return originalErrorState || customErrorState ; } } @ Directive ( { selector : '[matStepper]' } ) export class MatStepper extends CdkStepper implements AfterContentInit { /** The list of step headers of the steps in the stepper. */ @ ViewChildren ( MatStepHeader ) _stepHeader : QueryList < MatStepHeader > ; /** Steps that the stepper holds. */ @ ContentChildren ( MatStep ) _steps : QueryList < MatStep > ; /** Custom icon overrides passed in by the consumer. */ @ ContentChildren ( MatStepperIcon ) _icons : QueryList < MatStepperIcon > ; /** Event emitted when the current step is done transitioning in. */ @ Output ( ) readonly animationDone : EventEmitter < void > = new EventEmitter < void > ( ) ; /** Consumer-specified template-refs to be used to override the header icons. */ _iconOverrides : { [ key ] : TemplateRef < MatStepperIconContext > ; } = { } ; /** Stream of animation `done` events when the body expands/collapses. */ _animationDone = new Subject < AnimationEvent > ( ) ; ngAfterContentInit ( ) { this . _icons . forEach ( ( { name , templateRef } ) => this . _iconOverrides [ name ] = templateRef ) ; this . _steps . changes . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => this . _stateChanged ( ) ) ; this . _animationDone . pipe ( distinctUntilChanged ( ( x , y ) => x . fromState === y . fromState && x . toState === y . toState ) , takeUntil ( this . _destroyed ) ) . subscribe ( event => { if ( ( event . toState as StepContentPositionState ) === 'current' ) { this . animationDone . emit ( ) ; } } ) ; } } @ Component ( { moduleId : module . id , selector : 'mat-horizontal-stepper' , exportAs : 'matHorizontalStepper' , templateUrl : 'stepper-horizontal.html' , styleUrls : [ 'stepper.css' ] , inputs : [ 'selectedIndex' ] , host : { 'class' : 'mat-stepper-horizontal' , '[class.mat-stepper-label-position-end]' : 'labelPosition == "end"' , '[class.mat-stepper-label-position-bottom]' : 'labelPosition == "bottom"' , 'aria-orientation' : 'horizontal' , 'role' : 'tablist' , } , animations : [ matStepperAnimations . horizontalStepTransition ] , providers : [ { provide : MatStepper , useExisting : MatHorizontalStepper } ] , encapsulation : ViewEncapsulation . None , changeDetection : ChangeDetectionStrategy . OnPush , } ) export class MatHorizontalStepper extends MatStepper { /** Whether the label should display in bottom or end position. */ @ Input ( ) labelPosition : 'bottom' | 'end' = 'end' ; } @ Component ( { moduleId : module . id , selector : 'mat-vertical-stepper' , exportAs : 'matVerticalStepper' , templateUrl : 'stepper-vertical.html' , styleUrls : [ 'stepper.css' ] , inputs : [ 'selectedIndex' ] , host : { 'class' : 'mat-stepper-vertical' , 'aria-orientation' : 'vertical' , 'role' : 'tablist' , } , animations : [ matStepperAnimations . verticalStepTransition ] , providers : [ { provide : MatStepper , useExisting : MatVerticalStepper } ] , encapsulation : ViewEncapsulation . None , changeDetection : ChangeDetectionStrategy . OnPush , } ) export class MatVerticalStepper extends MatStepper { constructor ( @ Optional ( ) dir , changeDetectorRef , elementRef ? , @ Inject ( DOCUMENT ) _document ? ) { super ( dir , changeDetectorRef , elementRef , _document ) ; this . _orientation = 'vertical' ; } }