@@ -31,6 +31,7 @@ import {
3131 TemplateRef ,
3232 ViewChild ,
3333 ViewEncapsulation ,
34+ InjectionToken ,
3435} from '@angular/core' ;
3536import { DOCUMENT } from '@angular/common' ;
3637import { AbstractControl } from '@angular/forms' ;
@@ -65,6 +66,37 @@ export class StepperSelectionEvent {
6566 previouslySelectedStep : CdkStep ;
6667}
6768
69+ /** The state of each step. */
70+ export type StepState = 'number' | 'edit' | 'done' | 'error' | string ;
71+
72+ /** Enum to represent the different states of the steps. */
73+ export const STEP_STATE = {
74+ NUMBER : 'number' ,
75+ EDIT : 'edit' ,
76+ DONE : 'done' ,
77+ ERROR : 'error'
78+ } ;
79+
80+ /** InjectionToken that can be used to specify the global stepper options. */
81+ export const MAT_STEPPER_GLOBAL_OPTIONS =
82+ new InjectionToken < StepperOptions > ( 'mat-stepper-global-options' ) ;
83+
84+ /** Configurable options for stepper. */
85+ export interface StepperOptions {
86+ /**
87+ * Whether the stepper should display an error state or not.
88+ * Default behavior is assumed to be false.
89+ */
90+ showError ?: boolean ;
91+
92+ /**
93+ * Whether the stepper should display the default indicator type
94+ * or not.
95+ * Default behavior is assumed to be true.
96+ */
97+ displayDefaultIndicatorType ?: boolean ;
98+ }
99+
68100@Component ( {
69101 moduleId : module . id ,
70102 selector : 'cdk-step' ,
@@ -74,6 +106,10 @@ export class StepperSelectionEvent {
74106 changeDetection : ChangeDetectionStrategy . OnPush ,
75107} )
76108export class CdkStep implements OnChanges {
109+ private _stepperOptions : StepperOptions ;
110+ _showError : boolean ;
111+ _displayDefaultIndicatorType : boolean ;
112+
77113 /** Template for step label if it exists. */
78114 @ContentChild ( CdkStepLabel ) stepLabel : CdkStepLabel ;
79115
@@ -89,6 +125,9 @@ export class CdkStep implements OnChanges {
89125 /** Plain text label of the step. */
90126 @Input ( ) label : string ;
91127
128+ /** Error message to display when there's an error. */
129+ @Input ( ) errorMessage : string ;
130+
92131 /** Aria label for the tab. */
93132 @Input ( 'aria-label' ) ariaLabel : string ;
94133
@@ -98,6 +137,9 @@ export class CdkStep implements OnChanges {
98137 */
99138 @Input ( 'aria-labelledby' ) ariaLabelledby : string ;
100139
140+ /** State of the step. */
141+ @Input ( ) state : StepState ;
142+
101143 /** Whether the user can return to this step once it has been marked as complted. */
102144 @Input ( )
103145 get editable ( ) : boolean { return this . _editable ; }
@@ -117,18 +159,39 @@ export class CdkStep implements OnChanges {
117159 /** Whether step is marked as completed. */
118160 @Input ( )
119161 get completed ( ) : boolean {
120- return this . _customCompleted == null ? this . _defaultCompleted ( ) : this . _customCompleted ;
162+ return this . _customCompleted == null ? this . _getDefaultCompleted ( ) : this . _customCompleted ;
121163 }
122164 set completed ( value : boolean ) {
123165 this . _customCompleted = coerceBooleanProperty ( value ) ;
124166 }
125167 private _customCompleted : boolean | null = null ;
126168
127- private _defaultCompleted ( ) {
169+ private _getDefaultCompleted ( ) {
128170 return this . stepControl ? this . stepControl . valid && this . interacted : this . interacted ;
129171 }
130172
131- constructor ( @Inject ( forwardRef ( ( ) => CdkStepper ) ) private _stepper : CdkStepper ) { }
173+ /** Whether step has an error. */
174+ @Input ( )
175+ get hasError ( ) : boolean {
176+ return this . _customError || this . _getDefaultError ( ) ;
177+ }
178+ set hasError ( value : boolean ) {
179+ this . _customError = coerceBooleanProperty ( value ) ;
180+ }
181+ private _customError : boolean | null = null ;
182+
183+ private _getDefaultError ( ) {
184+ return this . stepControl && this . stepControl . invalid && this . interacted ;
185+ }
186+
187+ /** @breaking -change 8.0.0 remove the `?` after `stepperOptions` */
188+ constructor (
189+ @Inject ( forwardRef ( ( ) => CdkStepper ) ) private _stepper : CdkStepper ,
190+ @Optional ( ) @Inject ( MAT_STEPPER_GLOBAL_OPTIONS ) stepperOptions ?: StepperOptions ) {
191+ this . _stepperOptions = stepperOptions ? stepperOptions : { } ;
192+ this . _displayDefaultIndicatorType = this . _stepperOptions . displayDefaultIndicatorType !== false ;
193+ this . _showError = ! ! this . _stepperOptions . showError ;
194+ }
132195
133196 /** Selects this step component. */
134197 select ( ) : void {
@@ -143,6 +206,10 @@ export class CdkStep implements OnChanges {
143206 this . _customCompleted = false ;
144207 }
145208
209+ if ( this . _customError != null ) {
210+ this . _customError = false ;
211+ }
212+
146213 if ( this . stepControl ) {
147214 this . stepControl . reset ( ) ;
148215 }
@@ -301,15 +368,46 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
301368 }
302369
303370 /** Returns the type of icon to be displayed. */
304- _getIndicatorType ( index : number ) : 'number' | 'edit' | 'done' {
371+ _getIndicatorType ( index : number , state : StepState = STEP_STATE . NUMBER ) : StepState {
305372 const step = this . _steps . toArray ( ) [ index ] ;
306- if ( ! step . completed || this . _selectedIndex == index ) {
307- return 'number' ;
373+ const isCurrentStep = this . _isCurrentStep ( index ) ;
374+
375+ return step . _displayDefaultIndicatorType
376+ ? this . _getDefaultIndicatorLogic ( step , isCurrentStep )
377+ : this . _getGuidelineLogic ( step , isCurrentStep , state ) ;
378+ }
379+
380+ private _getDefaultIndicatorLogic ( step : CdkStep , isCurrentStep : boolean ) : StepState {
381+ if ( step . _showError && step . hasError && ! isCurrentStep ) {
382+ return STEP_STATE . ERROR ;
383+ } else if ( ! step . completed || isCurrentStep ) {
384+ return STEP_STATE . NUMBER ;
385+ } else {
386+ return step . editable ? STEP_STATE . EDIT : STEP_STATE . DONE ;
387+ }
388+ }
389+
390+ private _getGuidelineLogic (
391+ step : CdkStep ,
392+ isCurrentStep : boolean ,
393+ state : StepState = STEP_STATE . NUMBER ) : StepState {
394+ if ( step . _showError && step . hasError && ! isCurrentStep ) {
395+ return STEP_STATE . ERROR ;
396+ } else if ( step . completed && ! isCurrentStep ) {
397+ return STEP_STATE . DONE ;
398+ } else if ( step . completed && isCurrentStep ) {
399+ return state ;
400+ } else if ( step . editable && isCurrentStep ) {
401+ return STEP_STATE . EDIT ;
308402 } else {
309- return step . editable ? 'edit' : 'done' ;
403+ return state ;
310404 }
311405 }
312406
407+ private _isCurrentStep ( index : number ) {
408+ return this . _selectedIndex === index ;
409+ }
410+
313411 /** Returns the index of the currently-focused step header. */
314412 _getFocusIndex ( ) {
315413 return this . _keyManager ? this . _keyManager . activeItemIndex : this . _selectedIndex ;
0 commit comments