@@ -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 ( isCurrentStep )
195+ : this . _getGuidelineLogic ( isCurrentStep ) ;
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,31 @@ 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 ( isCurrentStep : boolean ) : StepState {
274+ if ( this . _showError ( ) && this . hasError && ! isCurrentStep ) {
275+ return STEP_STATE . ERROR ;
276+ } else if ( ! this . completed || isCurrentStep ) {
277+ return STEP_STATE . NUMBER ;
278+ }
279+ return this . editable ? STEP_STATE . EDIT : STEP_STATE . DONE ;
280+ }
281+
282+ private _getGuidelineLogic ( isCurrentStep : boolean ) : StepState {
283+ const defaultState = this . state || STEP_STATE . NUMBER ;
284+
285+ if ( this . _showError ( ) && this . hasError && ! isCurrentStep ) {
286+ return STEP_STATE . ERROR ;
287+ } else if ( this . completed && ! isCurrentStep ) {
288+ return STEP_STATE . DONE ;
289+ } else if ( this . completed && isCurrentStep ) {
290+ return defaultState ;
291+ } else if ( this . editable && isCurrentStep ) {
292+ return STEP_STATE . EDIT ;
293+ }
294+ return defaultState ;
248295 }
249296}
250297
@@ -281,7 +328,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
281328 /** The index of the selected step. */
282329 @Input ( { transform : numberAttribute } )
283330 get selectedIndex ( ) : number {
284- return this . _selectedIndex ;
331+ return this . _selectedIndex ( ) ;
285332 }
286333 set selectedIndex ( index : number ) {
287334 if ( this . _steps ) {
@@ -290,21 +337,21 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
290337 throw Error ( 'cdkStepper: Cannot assign out-of-bounds value to `selectedIndex`.' ) ;
291338 }
292339
293- if ( this . _selectedIndex !== index ) {
340+ if ( this . selectedIndex !== index ) {
294341 this . selected ?. _markAsInteracted ( ) ;
295342
296343 if (
297344 ! this . _anyControlsInvalidOrPending ( index ) &&
298- ( index >= this . _selectedIndex || this . steps . toArray ( ) [ index ] . editable )
345+ ( index >= this . selectedIndex || this . steps . toArray ( ) [ index ] . editable )
299346 ) {
300347 this . _updateSelectedItemIndex ( index ) ;
301348 }
302349 }
303350 } else {
304- this . _selectedIndex = index ;
351+ this . _selectedIndex . set ( index ) ;
305352 }
306353 }
307- private _selectedIndex = 0 ;
354+ private _selectedIndex = signal ( 0 ) ;
308355
309356 /** The step that is selected. */
310357 @Input ( )
@@ -347,6 +394,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
347394 . pipe ( startWith ( this . _steps ) , takeUntil ( this . _destroyed ) )
348395 . subscribe ( ( steps : QueryList < CdkStep > ) => {
349396 this . steps . reset ( steps . filter ( step => step . _stepper === this ) ) ;
397+ this . steps . forEach ( ( step , index ) => step . index . set ( index ) ) ;
350398 this . steps . notifyOnChanges ( ) ;
351399 } ) ;
352400 }
@@ -393,26 +441,26 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
393441 . pipe ( startWith ( this . _layoutDirection ( ) ) , takeUntil ( this . _destroyed ) )
394442 . subscribe ( direction => this . _keyManager ?. withHorizontalOrientation ( direction ) ) ;
395443
396- this . _keyManager . updateActiveItem ( this . _selectedIndex ) ;
444+ this . _keyManager . updateActiveItem ( this . selectedIndex ) ;
397445
398446 // No need to `takeUntil` here, because we're the ones destroying `steps`.
399447 this . steps . changes . subscribe ( ( ) => {
400448 if ( ! this . selected ) {
401- this . _selectedIndex = Math . max ( this . _selectedIndex - 1 , 0 ) ;
449+ this . _selectedIndex . set ( Math . max ( this . selectedIndex - 1 , 0 ) ) ;
402450 }
403451 } ) ;
404452
405453 // The logic which asserts that the selected index is within bounds doesn't run before the
406454 // steps are initialized, because we don't how many steps there are yet so we may have an
407455 // 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 ;
456+ if ( ! this . _isValidIndex ( this . selectedIndex ) ) {
457+ this . _selectedIndex . set ( 0 ) ;
410458 }
411459
412460 // For linear step and selected index is greater than zero,
413461 // 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 ) ;
462+ if ( this . linear && this . selectedIndex > 0 ) {
463+ const visitedSteps = this . steps . toArray ( ) . slice ( 0 , this . _selectedIndex ( ) ) ;
416464
417465 for ( const step of visitedSteps ) {
418466 step . _markAsInteracted ( ) ;
@@ -430,12 +478,12 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
430478
431479 /** Selects and focuses the next step in list. */
432480 next ( ) : void {
433- this . selectedIndex = Math . min ( this . _selectedIndex + 1 , this . steps . length - 1 ) ;
481+ this . selectedIndex = Math . min ( this . _selectedIndex ( ) + 1 , this . steps . length - 1 ) ;
434482 }
435483
436484 /** Selects and focuses the previous step in list. */
437485 previous ( ) : void {
438- this . selectedIndex = Math . max ( this . _selectedIndex - 1 , 0 ) ;
486+ this . selectedIndex = Math . max ( this . _selectedIndex ( ) - 1 , 0 ) ;
439487 }
440488
441489 /** Resets the stepper to its initial state. Note that this includes clearing form data. */
@@ -462,7 +510,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
462510
463511 /** Returns position state of the step with the given index. */
464512 _getAnimationDirection ( index : number ) : StepContentPositionState {
465- const position = index - this . _selectedIndex ;
513+ const position = index - this . _selectedIndex ( ) ;
466514 if ( position < 0 ) {
467515 return this . _layoutDirection ( ) === 'rtl' ? 'next' : 'previous' ;
468516 } else if ( position > 0 ) {
@@ -471,60 +519,20 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
471519 return 'current' ;
472520 }
473521
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-
516522 /** Returns the index of the currently-focused step header. */
517- _getFocusIndex ( ) {
518- return this . _keyManager ? this . _keyManager . activeItemIndex : this . _selectedIndex ;
523+ _getFocusIndex ( ) : number | null {
524+ return this . _keyManager ? this . _keyManager . activeItemIndex : this . _selectedIndex ( ) ;
519525 }
520526
521527 private _updateSelectedItemIndex ( newIndex : number ) : void {
522528 const stepsArray = this . steps . toArray ( ) ;
529+ const selectedIndex = this . _selectedIndex ( ) ;
530+
523531 this . selectionChange . emit ( {
524532 selectedIndex : newIndex ,
525- previouslySelectedIndex : this . _selectedIndex ,
533+ previouslySelectedIndex : selectedIndex ,
526534 selectedStep : stepsArray [ newIndex ] ,
527- previouslySelectedStep : stepsArray [ this . _selectedIndex ] ,
535+ previouslySelectedStep : stepsArray [ selectedIndex ] ,
528536 } ) ;
529537
530538 // If focus is inside the stepper, move it to the next header, otherwise it may become
@@ -537,8 +545,8 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
537545 : this . _keyManager . updateActiveItem ( newIndex ) ;
538546 }
539547
540- this . _selectedIndex = newIndex ;
541- this . selectedIndexChange . emit ( this . _selectedIndex ) ;
548+ this . _selectedIndex . set ( newIndex ) ;
549+ this . selectedIndexChange . emit ( newIndex ) ;
542550 this . _stateChanged ( ) ;
543551 }
544552
0 commit comments