@@ -31,6 +31,7 @@ import {
31
31
TemplateRef ,
32
32
ViewChild ,
33
33
ViewEncapsulation ,
34
+ InjectionToken ,
34
35
} from '@angular/core' ;
35
36
import { DOCUMENT } from '@angular/common' ;
36
37
import { AbstractControl } from '@angular/forms' ;
@@ -65,6 +66,37 @@ export class StepperSelectionEvent {
65
66
previouslySelectedStep : CdkStep ;
66
67
}
67
68
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
+
68
100
@Component ( {
69
101
moduleId : module . id ,
70
102
selector : 'cdk-step' ,
@@ -74,6 +106,10 @@ export class StepperSelectionEvent {
74
106
changeDetection : ChangeDetectionStrategy . OnPush ,
75
107
} )
76
108
export class CdkStep implements OnChanges {
109
+ private _stepperOptions : StepperOptions ;
110
+ _showError : boolean ;
111
+ _displayDefaultIndicatorType : boolean ;
112
+
77
113
/** Template for step label if it exists. */
78
114
@ContentChild ( CdkStepLabel ) stepLabel : CdkStepLabel ;
79
115
@@ -89,6 +125,9 @@ export class CdkStep implements OnChanges {
89
125
/** Plain text label of the step. */
90
126
@Input ( ) label : string ;
91
127
128
+ /** Error message to display when there's an error. */
129
+ @Input ( ) errorMessage : string ;
130
+
92
131
/** Aria label for the tab. */
93
132
@Input ( 'aria-label' ) ariaLabel : string ;
94
133
@@ -98,6 +137,9 @@ export class CdkStep implements OnChanges {
98
137
*/
99
138
@Input ( 'aria-labelledby' ) ariaLabelledby : string ;
100
139
140
+ /** State of the step. */
141
+ @Input ( ) state : StepState ;
142
+
101
143
/** Whether the user can return to this step once it has been marked as complted. */
102
144
@Input ( )
103
145
get editable ( ) : boolean { return this . _editable ; }
@@ -117,18 +159,39 @@ export class CdkStep implements OnChanges {
117
159
/** Whether step is marked as completed. */
118
160
@Input ( )
119
161
get completed ( ) : boolean {
120
- return this . _customCompleted == null ? this . _defaultCompleted ( ) : this . _customCompleted ;
162
+ return this . _customCompleted == null ? this . _getDefaultCompleted ( ) : this . _customCompleted ;
121
163
}
122
164
set completed ( value : boolean ) {
123
165
this . _customCompleted = coerceBooleanProperty ( value ) ;
124
166
}
125
167
private _customCompleted : boolean | null = null ;
126
168
127
- private _defaultCompleted ( ) {
169
+ private _getDefaultCompleted ( ) {
128
170
return this . stepControl ? this . stepControl . valid && this . interacted : this . interacted ;
129
171
}
130
172
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
+ }
132
195
133
196
/** Selects this step component. */
134
197
select ( ) : void {
@@ -143,6 +206,10 @@ export class CdkStep implements OnChanges {
143
206
this . _customCompleted = false ;
144
207
}
145
208
209
+ if ( this . _customError != null ) {
210
+ this . _customError = false ;
211
+ }
212
+
146
213
if ( this . stepControl ) {
147
214
this . stepControl . reset ( ) ;
148
215
}
@@ -301,15 +368,46 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
301
368
}
302
369
303
370
/** 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 {
305
372
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 ;
308
402
} else {
309
- return step . editable ? 'edit' : 'done' ;
403
+ return state ;
310
404
}
311
405
}
312
406
407
+ private _isCurrentStep ( index : number ) {
408
+ return this . _selectedIndex === index ;
409
+ }
410
+
313
411
/** Returns the index of the currently-focused step header. */
314
412
_getFocusIndex ( ) {
315
413
return this . _keyManager ? this . _keyManager . activeItemIndex : this . _selectedIndex ;
0 commit comments