@@ -32,6 +32,8 @@ import {
3232 booleanAttribute ,
3333 numberAttribute ,
3434 inject ,
35+ signal ,
36+ computed ,
3537} from '@angular/core' ;
3638import {
3739 ControlContainer ,
@@ -168,26 +170,47 @@ export class CdkStep implements OnChanges {
168170 /** Whether step is marked as completed. */
169171 @Input ( { transform : booleanAttribute } )
170172 get completed ( ) : boolean {
171- return this . _completedOverride == null ? this . _getDefaultCompleted ( ) : this . _completedOverride ;
173+ if ( this . _completedOverride != null ) {
174+ return this . _completedOverride ;
175+ }
176+
177+ return this . stepControl ? this . stepControl . valid && this . interacted : this . interacted ;
172178 }
173179 set completed ( value : boolean ) {
174180 this . _completedOverride = value ;
175181 }
176182 _completedOverride : boolean | null = null ;
177183
178- private _getDefaultCompleted ( ) {
179- return this . stepControl ? this . stepControl . valid && this . interacted : this . interacted ;
180- }
184+ /** Current index of the step within the stepper. */
185+ readonly index = signal ( - 1 ) ;
186+
187+ /** Whether the step is selected. */
188+ readonly isSelected = computed ( ( ) => this . _stepper . selectedIndex === this . index ( ) ) ;
189+
190+ /** Type of indicator that should be shown for the step. */
191+ readonly indicatorType = computed ( ( ) => {
192+ const isCurrentStep = this . isSelected ( ) ;
193+ return this . _displayDefaultIndicatorType
194+ ? this . _getDefaultIndicatorLogic ( this , isCurrentStep )
195+ : this . _getGuidelineLogic ( this , isCurrentStep , this . state ) ;
196+ } ) ;
197+
198+ /** Whether the user can navigate to the step. */
199+ readonly isNavigable = computed ( ( ) => {
200+ const isSelected = this . isSelected ( ) ;
201+ return this . completed || isSelected || ! this . _stepper . linear ;
202+ } ) ;
181203
182204 /** Whether step has an error. */
183205 @Input ( { transform : booleanAttribute } )
184206 get hasError ( ) : boolean {
185- return this . _customError == null ? this . _getDefaultError ( ) : this . _customError ;
207+ const customError = this . _customError ( ) ;
208+ return customError == null ? this . _getDefaultError ( ) : customError ;
186209 }
187210 set hasError ( value : boolean ) {
188- this . _customError = value ;
211+ this . _customError . set ( value ) ;
189212 }
190- private _customError : boolean | null = null ;
213+ private _customError = signal < boolean | null > ( null ) ;
191214
192215 private _getDefaultError ( ) {
193216 return this . stepControl && this . stepControl . invalid && this . interacted ;
@@ -214,8 +237,8 @@ export class CdkStep implements OnChanges {
214237 this . _completedOverride = false ;
215238 }
216239
217- if ( this . _customError != null ) {
218- this . _customError = false ;
240+ if ( this . _customError ( ) != null ) {
241+ this . _customError . set ( false ) ;
219242 }
220243
221244 if ( this . stepControl ) {
@@ -244,7 +267,35 @@ export class CdkStep implements OnChanges {
244267 _showError ( ) : boolean {
245268 // We want to show the error state either if the user opted into/out of it using the
246269 // global options, or if they've explicitly set it through the `hasError` input.
247- return this . _stepperOptions . showError ?? this . _customError != null ;
270+ return this . _stepperOptions . showError ?? this . _customError ( ) != null ;
271+ }
272+
273+ private _getDefaultIndicatorLogic ( step : CdkStep , isCurrentStep : boolean ) : StepState {
274+ if ( step . _showError ( ) && step . hasError && ! isCurrentStep ) {
275+ return STEP_STATE . ERROR ;
276+ } else if ( ! step . completed || isCurrentStep ) {
277+ return STEP_STATE . NUMBER ;
278+ } else {
279+ return step . editable ? STEP_STATE . EDIT : STEP_STATE . DONE ;
280+ }
281+ }
282+
283+ private _getGuidelineLogic (
284+ step : CdkStep ,
285+ isCurrentStep : boolean ,
286+ state : StepState = STEP_STATE . NUMBER ,
287+ ) : StepState {
288+ if ( step . _showError ( ) && step . hasError && ! isCurrentStep ) {
289+ return STEP_STATE . ERROR ;
290+ } else if ( step . completed && ! isCurrentStep ) {
291+ return STEP_STATE . DONE ;
292+ } else if ( step . completed && isCurrentStep ) {
293+ return state ;
294+ } else if ( step . editable && isCurrentStep ) {
295+ return STEP_STATE . EDIT ;
296+ } else {
297+ return state ;
298+ }
248299 }
249300}
250301
@@ -281,7 +332,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
281332 /** The index of the selected step. */
282333 @Input ( { transform : numberAttribute } )
283334 get selectedIndex ( ) : number {
284- return this . _selectedIndex ;
335+ return this . _selectedIndex ( ) ;
285336 }
286337 set selectedIndex ( index : number ) {
287338 if ( this . _steps ) {
@@ -290,21 +341,21 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
290341 throw Error ( 'cdkStepper: Cannot assign out-of-bounds value to `selectedIndex`.' ) ;
291342 }
292343
293- if ( this . _selectedIndex !== index ) {
344+ if ( this . selectedIndex !== index ) {
294345 this . selected ?. _markAsInteracted ( ) ;
295346
296347 if (
297348 ! this . _anyControlsInvalidOrPending ( index ) &&
298- ( index >= this . _selectedIndex || this . steps . toArray ( ) [ index ] . editable )
349+ ( index >= this . selectedIndex || this . steps . toArray ( ) [ index ] . editable )
299350 ) {
300351 this . _updateSelectedItemIndex ( index ) ;
301352 }
302353 }
303354 } else {
304- this . _selectedIndex = index ;
355+ this . _selectedIndex . set ( index ) ;
305356 }
306357 }
307- private _selectedIndex = 0 ;
358+ private _selectedIndex = signal ( 0 ) ;
308359
309360 /** The step that is selected. */
310361 @Input ( )
@@ -347,6 +398,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
347398 . pipe ( startWith ( this . _steps ) , takeUntil ( this . _destroyed ) )
348399 . subscribe ( ( steps : QueryList < CdkStep > ) => {
349400 this . steps . reset ( steps . filter ( step => step . _stepper === this ) ) ;
401+ this . steps . forEach ( ( step , index ) => step . index . set ( index ) ) ;
350402 this . steps . notifyOnChanges ( ) ;
351403 } ) ;
352404 }
@@ -393,26 +445,26 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
393445 . pipe ( startWith ( this . _layoutDirection ( ) ) , takeUntil ( this . _destroyed ) )
394446 . subscribe ( direction => this . _keyManager ?. withHorizontalOrientation ( direction ) ) ;
395447
396- this . _keyManager . updateActiveItem ( this . _selectedIndex ) ;
448+ this . _keyManager . updateActiveItem ( this . selectedIndex ) ;
397449
398450 // No need to `takeUntil` here, because we're the ones destroying `steps`.
399451 this . steps . changes . subscribe ( ( ) => {
400452 if ( ! this . selected ) {
401- this . _selectedIndex = Math . max ( this . _selectedIndex - 1 , 0 ) ;
453+ this . _selectedIndex . set ( Math . max ( this . selectedIndex - 1 , 0 ) ) ;
402454 }
403455 } ) ;
404456
405457 // The logic which asserts that the selected index is within bounds doesn't run before the
406458 // steps are initialized, because we don't how many steps there are yet so we may have an
407459 // invalid index on init. If that's the case, auto-correct to the default so we don't throw.
408- if ( ! this . _isValidIndex ( this . _selectedIndex ) ) {
409- this . _selectedIndex = 0 ;
460+ if ( ! this . _isValidIndex ( this . selectedIndex ) ) {
461+ this . _selectedIndex . set ( 0 ) ;
410462 }
411463
412464 // For linear step and selected index is greater than zero,
413465 // set all the previous steps to interacted so that we can navigate to previous steps.
414- if ( this . linear && this . _selectedIndex > 0 ) {
415- const visitedSteps = this . steps . toArray ( ) . slice ( 0 , this . _selectedIndex ) ;
466+ if ( this . linear && this . selectedIndex > 0 ) {
467+ const visitedSteps = this . steps . toArray ( ) . slice ( 0 , this . _selectedIndex ( ) ) ;
416468
417469 for ( const step of visitedSteps ) {
418470 step . _markAsInteracted ( ) ;
@@ -430,12 +482,12 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
430482
431483 /** Selects and focuses the next step in list. */
432484 next ( ) : void {
433- this . selectedIndex = Math . min ( this . _selectedIndex + 1 , this . steps . length - 1 ) ;
485+ this . selectedIndex = Math . min ( this . _selectedIndex ( ) + 1 , this . steps . length - 1 ) ;
434486 }
435487
436488 /** Selects and focuses the previous step in list. */
437489 previous ( ) : void {
438- this . selectedIndex = Math . max ( this . _selectedIndex - 1 , 0 ) ;
490+ this . selectedIndex = Math . max ( this . _selectedIndex ( ) - 1 , 0 ) ;
439491 }
440492
441493 /** Resets the stepper to its initial state. Note that this includes clearing form data. */
@@ -462,7 +514,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
462514
463515 /** Returns position state of the step with the given index. */
464516 _getAnimationDirection ( index : number ) : StepContentPositionState {
465- const position = index - this . _selectedIndex ;
517+ const position = index - this . _selectedIndex ( ) ;
466518 if ( position < 0 ) {
467519 return this . _layoutDirection ( ) === 'rtl' ? 'next' : 'previous' ;
468520 } else if ( position > 0 ) {
@@ -471,60 +523,20 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
471523 return 'current' ;
472524 }
473525
474- /** Returns the type of icon to be displayed. */
475- _getIndicatorType ( index : number , state : StepState = STEP_STATE . NUMBER ) : StepState {
476- const step = this . steps . toArray ( ) [ index ] ;
477- const isCurrentStep = this . _isCurrentStep ( index ) ;
478-
479- return step . _displayDefaultIndicatorType
480- ? this . _getDefaultIndicatorLogic ( step , isCurrentStep )
481- : this . _getGuidelineLogic ( step , isCurrentStep , state ) ;
482- }
483-
484- private _getDefaultIndicatorLogic ( step : CdkStep , isCurrentStep : boolean ) : StepState {
485- if ( step . _showError ( ) && step . hasError && ! isCurrentStep ) {
486- return STEP_STATE . ERROR ;
487- } else if ( ! step . completed || isCurrentStep ) {
488- return STEP_STATE . NUMBER ;
489- } else {
490- return step . editable ? STEP_STATE . EDIT : STEP_STATE . DONE ;
491- }
492- }
493-
494- private _getGuidelineLogic (
495- step : CdkStep ,
496- isCurrentStep : boolean ,
497- state : StepState = STEP_STATE . NUMBER ,
498- ) : StepState {
499- if ( step . _showError ( ) && step . hasError && ! isCurrentStep ) {
500- return STEP_STATE . ERROR ;
501- } else if ( step . completed && ! isCurrentStep ) {
502- return STEP_STATE . DONE ;
503- } else if ( step . completed && isCurrentStep ) {
504- return state ;
505- } else if ( step . editable && isCurrentStep ) {
506- return STEP_STATE . EDIT ;
507- } else {
508- return state ;
509- }
510- }
511-
512- private _isCurrentStep ( index : number ) {
513- return this . _selectedIndex === index ;
514- }
515-
516526 /** Returns the index of the currently-focused step header. */
517- _getFocusIndex ( ) {
518- return this . _keyManager ? this . _keyManager . activeItemIndex : this . _selectedIndex ;
527+ _getFocusIndex ( ) : number | null {
528+ return this . _keyManager ? this . _keyManager . activeItemIndex : this . _selectedIndex ( ) ;
519529 }
520530
521531 private _updateSelectedItemIndex ( newIndex : number ) : void {
522532 const stepsArray = this . steps . toArray ( ) ;
533+ const selectedIndex = this . _selectedIndex ( ) ;
534+
523535 this . selectionChange . emit ( {
524536 selectedIndex : newIndex ,
525- previouslySelectedIndex : this . _selectedIndex ,
537+ previouslySelectedIndex : selectedIndex ,
526538 selectedStep : stepsArray [ newIndex ] ,
527- previouslySelectedStep : stepsArray [ this . _selectedIndex ] ,
539+ previouslySelectedStep : stepsArray [ selectedIndex ] ,
528540 } ) ;
529541
530542 // If focus is inside the stepper, move it to the next header, otherwise it may become
@@ -537,8 +549,8 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
537549 : this . _keyManager . updateActiveItem ( newIndex ) ;
538550 }
539551
540- this . _selectedIndex = newIndex ;
541- this . selectedIndexChange . emit ( this . _selectedIndex ) ;
552+ this . _selectedIndex . set ( newIndex ) ;
553+ this . selectedIndexChange . emit ( newIndex ) ;
542554 this . _stateChanged ( ) ;
543555 }
544556
0 commit comments