diff --git a/goldens/cdk/stepper/index.api.md b/goldens/cdk/stepper/index.api.md index 9295ce4417e2..a26d32c85c00 100644 --- a/goldens/cdk/stepper/index.api.md +++ b/goldens/cdk/stepper/index.api.md @@ -28,16 +28,22 @@ export class CdkStep implements OnChanges { get completed(): boolean; set completed(value: boolean); // (undocumented) - _completedOverride: boolean | null; + _completedOverride: i0.WritableSignal<boolean | null>; content: TemplateRef<any>; // (undocumented) _displayDefaultIndicatorType: boolean; - editable: boolean; + get editable(): boolean; + set editable(value: boolean); errorMessage: string; get hasError(): boolean; set hasError(value: boolean); - interacted: boolean; + readonly index: i0.WritableSignal<number>; + readonly indicatorType: i0.Signal<string>; + get interacted(): boolean; + set interacted(value: boolean); readonly interactedStream: EventEmitter<CdkStep>; + readonly isNavigable: i0.Signal<boolean>; + readonly isSelected: i0.Signal<boolean>; label: string; // (undocumented) _markAsInteracted(): void; @@ -55,7 +61,8 @@ export class CdkStep implements OnChanges { reset(): void; select(): void; _showError(): boolean; - state: StepState; + get state(): StepState; + set state(value: StepState); stepControl: AbstractControl; stepLabel: CdkStepLabel; // (undocumented) @@ -97,7 +104,6 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { protected _elementRef: ElementRef<HTMLElement>; _getAnimationDirection(index: number): StepContentPositionState; _getFocusIndex(): number | null; - _getIndicatorType(index: number, state?: StepState): StepState; _getStepContentId(i: number): string; _getStepLabelId(i: number): string; linear: boolean; diff --git a/goldens/material/stepper/index.api.md b/goldens/material/stepper/index.api.md index c4ecf404a356..b511e57b2aae 100644 --- a/goldens/material/stepper/index.api.md +++ b/goldens/material/stepper/index.api.md @@ -82,7 +82,6 @@ export class MatStepHeader extends CdkStepHeader implements AfterViewInit, OnDes // (undocumented) _getDefaultTextForState(state: StepState): string; _getHostElement(): HTMLElement; - _getIconContext(): MatStepperIconContext; iconOverrides: { [key: string]: TemplateRef<MatStepperIconContext>; }; @@ -138,8 +137,6 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten // (undocumented) ngOnDestroy(): void; _stepHeader: QueryList<MatStepHeader>; - // (undocumented) - _stepIsNavigable(index: number, step: MatStep): boolean; readonly steps: QueryList<MatStep>; _steps: QueryList<MatStep>; // (undocumented) diff --git a/src/cdk/a11y/key-manager/list-key-manager.ts b/src/cdk/a11y/key-manager/list-key-manager.ts index 55796cbe1d01..c52ebb9697e8 100644 --- a/src/cdk/a11y/key-manager/list-key-manager.ts +++ b/src/cdk/a11y/key-manager/list-key-manager.ts @@ -39,7 +39,7 @@ export type ListKeyManagerModifierKey = 'altKey' | 'ctrlKey' | 'metaKey' | 'shif * of items, it will set the active item correctly when arrow events occur. */ export class ListKeyManager<T extends ListKeyManagerOption> { - private _activeItemIndex = -1; + private _activeItemIndex = signal(-1); private _activeItem = signal<T | null>(null); private _wrap = false; private _typeaheadSubscription = Subscription.EMPTY; @@ -209,7 +209,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> { this.updateActiveItem(item); if (this._activeItem() !== previousActiveItem) { - this.change.next(this._activeItemIndex); + this.change.next(this._activeItemIndex()); } } @@ -279,7 +279,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> { case PAGE_UP: if (this._pageUpAndDown.enabled && isModifierAllowed) { - const targetIndex = this._activeItemIndex - this._pageUpAndDown.delta; + const targetIndex = this._activeItemIndex() - this._pageUpAndDown.delta; this._setActiveItemByIndex(targetIndex > 0 ? targetIndex : 0, 1); break; } else { @@ -288,7 +288,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> { case PAGE_DOWN: if (this._pageUpAndDown.enabled && isModifierAllowed) { - const targetIndex = this._activeItemIndex + this._pageUpAndDown.delta; + const targetIndex = this._activeItemIndex() + this._pageUpAndDown.delta; const itemsLength = this._getItemsArray().length; this._setActiveItemByIndex(targetIndex < itemsLength ? targetIndex : itemsLength - 1, -1); break; @@ -312,7 +312,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> { /** Index of the currently active item. */ get activeItemIndex(): number | null { - return this._activeItemIndex; + return this._activeItemIndex(); } /** The active item. */ @@ -337,12 +337,12 @@ export class ListKeyManager<T extends ListKeyManagerOption> { /** Sets the active item to the next enabled item in the list. */ setNextItemActive(): void { - this._activeItemIndex < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1); + this._activeItemIndex() < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1); } /** Sets the active item to a previous enabled item in the list. */ setPreviousItemActive(): void { - this._activeItemIndex < 0 && this._wrap + this._activeItemIndex() < 0 && this._wrap ? this.setLastItemActive() : this._setActiveItemByDelta(-1); } @@ -366,7 +366,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> { // Explicitly check for `null` and `undefined` because other falsy values are valid. this._activeItem.set(activeItem == null ? null : activeItem); - this._activeItemIndex = index; + this._activeItemIndex.set(index); this._typeahead?.setCurrentSelectedItemIndex(index); } @@ -398,7 +398,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> { const items = this._getItemsArray(); for (let i = 1; i <= items.length; i++) { - const index = (this._activeItemIndex + delta * i + items.length) % items.length; + const index = (this._activeItemIndex() + delta * i + items.length) % items.length; const item = items[index]; if (!this._skipPredicateFn(item)) { @@ -414,7 +414,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> { * it encounters either end of the list, it will stop and not wrap. */ private _setActiveInDefaultMode(delta: -1 | 1): void { - this._setActiveItemByIndex(this._activeItemIndex + delta, delta); + this._setActiveItemByIndex(this._activeItemIndex() + delta, delta); } /** @@ -456,8 +456,8 @@ export class ListKeyManager<T extends ListKeyManagerOption> { if (activeItem) { const newIndex = newItems.indexOf(activeItem); - if (newIndex > -1 && newIndex !== this._activeItemIndex) { - this._activeItemIndex = newIndex; + if (newIndex > -1 && newIndex !== this._activeItemIndex()) { + this._activeItemIndex.set(newIndex); this._typeahead?.setCurrentSelectedItemIndex(newIndex); } } diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index 66f99022eea9..06d82f8af560 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -32,6 +32,8 @@ import { booleanAttribute, numberAttribute, inject, + signal, + computed, } from '@angular/core'; import { ControlContainer, @@ -135,7 +137,13 @@ export class CdkStep implements OnChanges { @Input() stepControl: AbstractControl; /** Whether user has attempted to move away from the step. */ - interacted = false; + get interacted(): boolean { + return this._interacted(); + } + set interacted(value: boolean) { + this._interacted.set(value); + } + private _interacted = signal(false); /** Emits when the user has attempted to move away from the step. */ @Output('interacted') @@ -157,10 +165,24 @@ export class CdkStep implements OnChanges { @Input('aria-labelledby') ariaLabelledby: string; /** State of the step. */ - @Input() state: StepState; + @Input() + get state(): StepState { + return this._state()!; + } + set state(value: StepState) { + this._state.set(value); + } + private _state = signal<StepState | undefined>(undefined); /** Whether the user can return to this step once it has been marked as completed. */ - @Input({transform: booleanAttribute}) editable: boolean = true; + @Input({transform: booleanAttribute}) + get editable(): boolean { + return this._editable()!; + } + set editable(value: boolean) { + this._editable.set(value); + } + private _editable = signal(true); /** Whether the completion of step is optional. */ @Input({transform: booleanAttribute}) optional: boolean = false; @@ -168,29 +190,72 @@ export class CdkStep implements OnChanges { /** Whether step is marked as completed. */ @Input({transform: booleanAttribute}) get completed(): boolean { - return this._completedOverride == null ? this._getDefaultCompleted() : this._completedOverride; + const override = this._completedOverride(); + const interacted = this._interacted(); + + if (override != null) { + return override; + } + + return interacted && (!this.stepControl || this.stepControl.valid); } set completed(value: boolean) { - this._completedOverride = value; + this._completedOverride.set(value); } - _completedOverride: boolean | null = null; + _completedOverride = signal<boolean | null>(null); - private _getDefaultCompleted() { - return this.stepControl ? this.stepControl.valid && this.interacted : this.interacted; - } + /** Current index of the step within the stepper. */ + readonly index = signal(-1); + + /** Whether the step is selected. */ + readonly isSelected = computed<boolean>(() => this._stepper.selectedIndex === this.index()); + + /** Type of indicator that should be shown for the step. */ + readonly indicatorType = computed<StepState>(() => { + const selected = this.isSelected(); + const completed = this.completed; + const defaultState = this._state() ?? STEP_STATE.NUMBER; + const editable = this._editable(); + + if (this._showError() && this.hasError && !selected) { + return STEP_STATE.ERROR; + } + + if (this._displayDefaultIndicatorType) { + if (!completed || selected) { + return STEP_STATE.NUMBER; + } + return editable ? STEP_STATE.EDIT : STEP_STATE.DONE; + } else { + if (completed && !selected) { + return STEP_STATE.DONE; + } else if (completed && selected) { + return defaultState; + } + return editable && selected ? STEP_STATE.EDIT : defaultState; + } + }); + + /** Whether the user can navigate to the step. */ + readonly isNavigable = computed<boolean>(() => { + const isSelected = this.isSelected(); + const isCompleted = this.completed; + return isCompleted || isSelected || !this._stepper.linear; + }); /** Whether step has an error. */ @Input({transform: booleanAttribute}) get hasError(): boolean { - return this._customError == null ? this._getDefaultError() : this._customError; + const customError = this._customError(); + return customError == null ? this._getDefaultError() : customError; } set hasError(value: boolean) { - this._customError = value; + this._customError.set(value); } - private _customError: boolean | null = null; + private _customError = signal<boolean | null>(null); private _getDefaultError() { - return this.stepControl && this.stepControl.invalid && this.interacted; + return this.interacted && !!this.stepControl?.invalid; } constructor(...args: unknown[]); @@ -208,14 +273,14 @@ export class CdkStep implements OnChanges { /** Resets the step to its initial state. Note that this includes resetting form data. */ reset(): void { - this.interacted = false; + this._interacted.set(false); - if (this._completedOverride != null) { - this._completedOverride = false; + if (this._completedOverride() != null) { + this._completedOverride.set(false); } - if (this._customError != null) { - this._customError = false; + if (this._customError() != null) { + this._customError.set(false); } if (this.stepControl) { @@ -234,8 +299,8 @@ export class CdkStep implements OnChanges { } _markAsInteracted() { - if (!this.interacted) { - this.interacted = true; + if (!this._interacted()) { + this._interacted.set(true); this.interactedStream.emit(this); } } @@ -244,7 +309,7 @@ export class CdkStep implements OnChanges { _showError(): boolean { // We want to show the error state either if the user opted into/out of it using the // global options, or if they've explicitly set it through the `hasError` input. - return this._stepperOptions.showError ?? this._customError != null; + return this._stepperOptions.showError ?? this._customError() != null; } } @@ -281,7 +346,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { /** The index of the selected step. */ @Input({transform: numberAttribute}) get selectedIndex(): number { - return this._selectedIndex; + return this._selectedIndex(); } set selectedIndex(index: number) { if (this._steps) { @@ -290,21 +355,21 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { throw Error('cdkStepper: Cannot assign out-of-bounds value to `selectedIndex`.'); } - if (this._selectedIndex !== index) { + if (this.selectedIndex !== index) { this.selected?._markAsInteracted(); if ( !this._anyControlsInvalidOrPending(index) && - (index >= this._selectedIndex || this.steps.toArray()[index].editable) + (index >= this.selectedIndex || this.steps.toArray()[index].editable) ) { this._updateSelectedItemIndex(index); } } } else { - this._selectedIndex = index; + this._selectedIndex.set(index); } } - private _selectedIndex = 0; + private _selectedIndex = signal(0); /** The step that is selected. */ @Input() @@ -347,6 +412,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { .pipe(startWith(this._steps), takeUntil(this._destroyed)) .subscribe((steps: QueryList<CdkStep>) => { this.steps.reset(steps.filter(step => step._stepper === this)); + this.steps.forEach((step, index) => step.index.set(index)); this.steps.notifyOnChanges(); }); } @@ -393,26 +459,26 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { .pipe(startWith(this._layoutDirection()), takeUntil(this._destroyed)) .subscribe(direction => this._keyManager?.withHorizontalOrientation(direction)); - this._keyManager.updateActiveItem(this._selectedIndex); + this._keyManager.updateActiveItem(this.selectedIndex); // No need to `takeUntil` here, because we're the ones destroying `steps`. this.steps.changes.subscribe(() => { if (!this.selected) { - this._selectedIndex = Math.max(this._selectedIndex - 1, 0); + this._selectedIndex.set(Math.max(this.selectedIndex - 1, 0)); } }); // The logic which asserts that the selected index is within bounds doesn't run before the // steps are initialized, because we don't how many steps there are yet so we may have an // invalid index on init. If that's the case, auto-correct to the default so we don't throw. - if (!this._isValidIndex(this._selectedIndex)) { - this._selectedIndex = 0; + if (!this._isValidIndex(this.selectedIndex)) { + this._selectedIndex.set(0); } // For linear step and selected index is greater than zero, // set all the previous steps to interacted so that we can navigate to previous steps. - if (this.linear && this._selectedIndex > 0) { - const visitedSteps = this.steps.toArray().slice(0, this._selectedIndex); + if (this.linear && this.selectedIndex > 0) { + const visitedSteps = this.steps.toArray().slice(0, this._selectedIndex()); for (const step of visitedSteps) { step._markAsInteracted(); @@ -430,12 +496,12 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { /** Selects and focuses the next step in list. */ next(): void { - this.selectedIndex = Math.min(this._selectedIndex + 1, this.steps.length - 1); + this.selectedIndex = Math.min(this._selectedIndex() + 1, this.steps.length - 1); } /** Selects and focuses the previous step in list. */ previous(): void { - this.selectedIndex = Math.max(this._selectedIndex - 1, 0); + this.selectedIndex = Math.max(this._selectedIndex() - 1, 0); } /** Resets the stepper to its initial state. Note that this includes clearing form data. */ @@ -462,7 +528,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { /** Returns position state of the step with the given index. */ _getAnimationDirection(index: number): StepContentPositionState { - const position = index - this._selectedIndex; + const position = index - this._selectedIndex(); if (position < 0) { return this._layoutDirection() === 'rtl' ? 'next' : 'previous'; } else if (position > 0) { @@ -471,60 +537,20 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { return 'current'; } - /** Returns the type of icon to be displayed. */ - _getIndicatorType(index: number, state: StepState = STEP_STATE.NUMBER): StepState { - const step = this.steps.toArray()[index]; - const isCurrentStep = this._isCurrentStep(index); - - return step._displayDefaultIndicatorType - ? this._getDefaultIndicatorLogic(step, isCurrentStep) - : this._getGuidelineLogic(step, isCurrentStep, state); - } - - private _getDefaultIndicatorLogic(step: CdkStep, isCurrentStep: boolean): StepState { - if (step._showError() && step.hasError && !isCurrentStep) { - return STEP_STATE.ERROR; - } else if (!step.completed || isCurrentStep) { - return STEP_STATE.NUMBER; - } else { - return step.editable ? STEP_STATE.EDIT : STEP_STATE.DONE; - } - } - - private _getGuidelineLogic( - step: CdkStep, - isCurrentStep: boolean, - state: StepState = STEP_STATE.NUMBER, - ): StepState { - if (step._showError() && step.hasError && !isCurrentStep) { - return STEP_STATE.ERROR; - } else if (step.completed && !isCurrentStep) { - return STEP_STATE.DONE; - } else if (step.completed && isCurrentStep) { - return state; - } else if (step.editable && isCurrentStep) { - return STEP_STATE.EDIT; - } else { - return state; - } - } - - private _isCurrentStep(index: number) { - return this._selectedIndex === index; - } - /** Returns the index of the currently-focused step header. */ - _getFocusIndex() { - return this._keyManager ? this._keyManager.activeItemIndex : this._selectedIndex; + _getFocusIndex(): number | null { + return this._keyManager ? this._keyManager.activeItemIndex : this._selectedIndex(); } private _updateSelectedItemIndex(newIndex: number): void { const stepsArray = this.steps.toArray(); + const selectedIndex = this._selectedIndex(); + this.selectionChange.emit({ selectedIndex: newIndex, - previouslySelectedIndex: this._selectedIndex, + previouslySelectedIndex: selectedIndex, selectedStep: stepsArray[newIndex], - previouslySelectedStep: stepsArray[this._selectedIndex], + previouslySelectedStep: stepsArray[selectedIndex], }); // If focus is inside the stepper, move it to the next header, otherwise it may become @@ -537,8 +563,8 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { : this._keyManager.updateActiveItem(newIndex); } - this._selectedIndex = newIndex; - this.selectedIndexChange.emit(this._selectedIndex); + this._selectedIndex.set(newIndex); + this.selectedIndexChange.emit(newIndex); this._stateChanged(); } @@ -569,7 +595,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { const isIncomplete = control ? control.invalid || control.pending || !step.interacted : !step.completed; - return isIncomplete && !step.optional && !step._completedOverride; + return isIncomplete && !step.optional && !step._completedOverride(); }); } diff --git a/src/material/list/list-base.ts b/src/material/list/list-base.ts index eaa942ed0043..401ba7856318 100644 --- a/src/material/list/list-base.ts +++ b/src/material/list/list-base.ts @@ -19,6 +19,7 @@ import { OnDestroy, QueryList, Injector, + signal, } from '@angular/core'; import { _animationsDisabled, @@ -64,12 +65,12 @@ export abstract class MatListBase { */ @Input() get disabled(): boolean { - return this._disabled; + return this._disabled(); } set disabled(value: BooleanInput) { - this._disabled = coerceBooleanProperty(value); + this._disabled.set(coerceBooleanProperty(value)); } - private _disabled = false; + private _disabled = signal(false); protected _defaultOptions = inject(MAT_LIST_CONFIG, {optional: true}); } @@ -149,12 +150,12 @@ export abstract class MatListItemBase implements AfterViewInit, OnDestroy, Rippl /** Whether the list-item is disabled. */ @Input() get disabled(): boolean { - return this._disabled || !!this._listBase?.disabled; + return this._disabled() || !!this._listBase?.disabled; } set disabled(value: BooleanInput) { - this._disabled = coerceBooleanProperty(value); + this._disabled.set(coerceBooleanProperty(value)); } - private _disabled = false; + private _disabled = signal(false); private _subscriptions = new Subscription(); private _rippleRenderer: RippleRenderer | null = null; diff --git a/src/material/list/selection-list.spec.ts b/src/material/list/selection-list.spec.ts index 911dd94d077d..d83c25fa6589 100644 --- a/src/material/list/selection-list.spec.ts +++ b/src/material/list/selection-list.spec.ts @@ -10,7 +10,6 @@ import { ChangeDetectionStrategy, Component, DebugElement, - provideCheckNoChangesConfig, QueryList, ViewChildren, } from '@angular/core'; @@ -43,21 +42,6 @@ describe('MatSelectionList without forms', () => { let listOptions: DebugElement[]; let selectionList: DebugElement; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - providers: [provideCheckNoChangesConfig({exhaustive: false})], - imports: [ - MatListModule, - SelectionListWithListOptions, - SelectionListWithCheckboxPositionAfter, - SelectionListWithListDisabled, - SelectionListWithOnlyOneOption, - SelectionListWithIndirectChildOptions, - SelectionListWithSelectedOptionAndValue, - ], - }); - })); - beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SelectionListWithListOptions); fixture.detectChanges(); @@ -1277,23 +1261,6 @@ describe('MatSelectionList without forms', () => { }); describe('MatSelectionList with forms', () => { - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - providers: [provideCheckNoChangesConfig({exhaustive: false})], - imports: [ - MatListModule, - FormsModule, - ReactiveFormsModule, - SelectionListWithModel, - SelectionListWithFormControl, - SelectionListWithPreselectedOption, - SelectionListWithPreselectedOptionAndModel, - SelectionListWithPreselectedFormControlOnPush, - SelectionListWithCustomComparator, - ], - }); - })); - describe('and ngModel', () => { let fixture: ComponentFixture<SelectionListWithModel>; let selectionListDebug: DebugElement; diff --git a/src/material/list/selection-list.ts b/src/material/list/selection-list.ts index ca819bd7e0f4..9a8d186c4cea 100644 --- a/src/material/list/selection-list.ts +++ b/src/material/list/selection-list.ts @@ -30,6 +30,7 @@ import { ViewEncapsulation, forwardRef, inject, + signal, } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {ThemePalette} from '../core'; @@ -265,18 +266,18 @@ export class MatSelectionList */ @Input() override get disabled(): boolean { - return this._selectionListDisabled; + return this._selectionListDisabled(); } override set disabled(value: BooleanInput) { // Update the disabled state of this list. Write to `this._selectionListDisabled` instead of // `super.disabled`. That is to avoid closure compiler compatibility issues with assigning to // a super property. - this._selectionListDisabled = coerceBooleanProperty(value); - if (this._selectionListDisabled) { + this._selectionListDisabled.set(coerceBooleanProperty(value)); + if (this._selectionListDisabled()) { this._keyManager?.setActiveItem(-1); } } - private _selectionListDisabled = false; + private _selectionListDisabled = signal(false); /** Implemented as part of ControlValueAccessor. */ registerOnChange(fn: (value: any) => void): void { diff --git a/src/material/menu/menu.spec.ts b/src/material/menu/menu.spec.ts index b2f150e3d139..2dd79464239a 100644 --- a/src/material/menu/menu.spec.ts +++ b/src/material/menu/menu.spec.ts @@ -22,7 +22,6 @@ import { Input, OnDestroy, Output, - provideCheckNoChangesConfig, QueryList, signal, TemplateRef, @@ -1744,7 +1743,6 @@ describe('MatMenu', () => { direction = 'ltr'; TestBed.resetTestingModule().configureTestingModule({ providers: [ - provideCheckNoChangesConfig({exhaustive: false}), { provide: Directionality, useValue: { @@ -1907,7 +1905,7 @@ describe('MatMenu', () => { .withContext('Expected two open menus') .toBe(2); - items[1].componentInstance.disabled = true; + fixture.componentInstance.secondItemDisabled = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1932,7 +1930,7 @@ describe('MatMenu', () => { const item = fixture.debugElement.query(By.directive(MatMenuItem))!; - item.componentInstance.disabled = true; + fixture.componentInstance.firstItemDisabled = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -2728,8 +2726,9 @@ class CustomMenu { <button mat-menu-item id="level-one-trigger" [matMenuTriggerFor]="levelOne" + [disabled]="firstItemDisabled" #levelOneTrigger="matMenuTrigger">One</button> - <button mat-menu-item>Two</button> + <button mat-menu-item [disabled]="secondItemDisabled">Two</button> @if (showLazy) { <button mat-menu-item id="lazy-trigger" @@ -2779,6 +2778,9 @@ class NestedMenu { @ViewChild('lazy') lazyMenu: MatMenu; @ViewChild('lazyTrigger') lazyTrigger: MatMenuTrigger; showLazy = false; + + firstItemDisabled = false; + secondItemDisabled = false; } @Component({ diff --git a/src/material/stepper/step-header.html b/src/material/stepper/step-header.html index 19ade872e7a4..99b65a5643a4 100644 --- a/src/material/stepper/step-header.html +++ b/src/material/stepper/step-header.html @@ -7,7 +7,7 @@ @if (iconOverrides && iconOverrides[state]) { <ng-container [ngTemplateOutlet]="iconOverrides[state]" - [ngTemplateOutletContext]="_getIconContext()"></ng-container> + [ngTemplateOutletContext]="{index, active, optional}"></ng-container> } @else { @switch (state) { @case ('number') { diff --git a/src/material/stepper/step-header.ts b/src/material/stepper/step-header.ts index db4bac260578..998e5d3610ad 100644 --- a/src/material/stepper/step-header.ts +++ b/src/material/stepper/step-header.ts @@ -128,15 +128,6 @@ export class MatStepHeader extends CdkStepHeader implements AfterViewInit, OnDes return this._elementRef.nativeElement; } - /** Template context variables that are exposed to the `matStepperIcon` instances. */ - _getIconContext(): MatStepperIconContext { - return { - index: this.index, - active: this.active, - optional: this.optional, - }; - } - _getDefaultTextForState(state: StepState): string { if (state == 'number') { return `${this.index + 1}`; diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index a75e48522600..0f75e58c012b 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -15,7 +15,7 @@ @for (step of steps; track step) { <ng-container [ngTemplateOutlet]="stepTemplate" - [ngTemplateOutletContext]="{step, i: $index}"/> + [ngTemplateOutletContext]="{step}"/> @if (!$last) { <div class="mat-stepper-horizontal-line"></div> } @@ -44,7 +44,7 @@ <div class="mat-step"> <ng-container [ngTemplateOutlet]="stepTemplate" - [ngTemplateOutletContext]="{step, i: $index}"/> + [ngTemplateOutletContext]="{step}"/> <div #animatedContainer class="mat-vertical-content-container" @@ -66,29 +66,29 @@ } <!-- Common step templating --> -<ng-template let-step="step" let-i="i" #stepTemplate> +<ng-template let-step="step" #stepTemplate> <mat-step-header [class.mat-horizontal-stepper-header]="orientation === 'horizontal'" [class.mat-vertical-stepper-header]="orientation === 'vertical'" (click)="step.select()" (keydown)="_onKeydown($event)" - [tabIndex]="_getFocusIndex() === i ? 0 : -1" - [id]="_getStepLabelId(i)" - [attr.aria-posinset]="i + 1" + [tabIndex]="_getFocusIndex() === step.index() ? 0 : -1" + [id]="_getStepLabelId(step.index())" + [attr.aria-posinset]="step.index() + 1" [attr.aria-setsize]="steps.length" - [attr.aria-controls]="_getStepContentId(i)" - [attr.aria-selected]="selectedIndex == i" + [attr.aria-controls]="_getStepContentId(step.index())" + [attr.aria-selected]="step.isSelected()" [attr.aria-label]="step.ariaLabel || null" [attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null" - [attr.aria-disabled]="_stepIsNavigable(i, step) ? null : true" - [index]="i" - [state]="_getIndicatorType(i, step.state)" + [attr.aria-disabled]="step.isNavigable() ? null : true" + [index]="step.index()" + [state]="step.indicatorType()" [label]="step.stepLabel || step.label" - [selected]="selectedIndex === i" - [active]="_stepIsNavigable(i, step)" + [selected]="step.isSelected()" + [active]="step.isNavigable()" [optional]="step.optional" [errorMessage]="step.errorMessage" [iconOverrides]="_iconOverrides" - [disableRipple]="disableRipple || !_stepIsNavigable(i, step)" + [disableRipple]="disableRipple || !step.isNavigable()" [color]="step.color || color"/> </ng-template> diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index c640438ae327..b66beebe99f4 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -33,7 +33,6 @@ import { ViewEncapsulation, WritableSignal, inject, - provideCheckNoChangesConfig, signal, } from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; @@ -75,59 +74,45 @@ describe('MatStepper', () => { }); it('should default to the first step', () => { - const stepperComponent: MatStepper = fixture.debugElement.query( - By.css('mat-stepper'), - )!.componentInstance; - - expect(stepperComponent.selectedIndex).toBe(0); + expect(fixture.componentInstance.stepper.selectedIndex).toBe(0); }); it('should throw when a negative `selectedIndex` is assigned', () => { - const stepperComponent: MatStepper = fixture.debugElement.query( - By.css('mat-stepper'), - )!.componentInstance; - expect(() => { - stepperComponent.selectedIndex = -10; + fixture.componentInstance.stepper.selectedIndex = -10; fixture.detectChanges(); }).toThrowError(/Cannot assign out-of-bounds/); }); it('should throw when an out-of-bounds `selectedIndex` is assigned', () => { - const stepperComponent: MatStepper = fixture.debugElement.query( - By.css('mat-stepper'), - )!.componentInstance; - expect(() => { - stepperComponent.selectedIndex = 1337; + fixture.componentInstance.stepper.selectedIndex = 1337; fixture.detectChanges(); }).toThrowError(/Cannot assign out-of-bounds/); }); it('should change selected index on header click', () => { const stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header')); - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; - expect(stepperComponent.selectedIndex).toBe(0); - expect(stepperComponent.selected instanceof MatStep).toBe(true); + expect(stepper.selectedIndex).toBe(0); + expect(stepper.selected instanceof MatStep).toBe(true); // select the second step let stepHeaderEl = stepHeaders[1].nativeElement; stepHeaderEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); - expect(stepperComponent.selected instanceof MatStep).toBe(true); + expect(stepper.selectedIndex).toBe(1); + expect(stepper.selected instanceof MatStep).toBe(true); // select the third step stepHeaderEl = stepHeaders[2].nativeElement; stepHeaderEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(2); - expect(stepperComponent.selected instanceof MatStep).toBe(true); + expect(stepper.selectedIndex).toBe(2); + expect(stepper.selected instanceof MatStep).toBe(true); }); it('should set the "tablist" role on stepper', () => { @@ -136,13 +121,10 @@ describe('MatStepper', () => { }); it('should display the correct label', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; let selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]'); expect(selectedLabel.textContent).toMatch('Step 1'); - stepperComponent.selectedIndex = 2; + fixture.componentInstance.stepper.selectedIndex = 2; fixture.detectChanges(); selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]'); @@ -156,32 +138,30 @@ describe('MatStepper', () => { }); it('should go to next available step when the next button is clicked', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); let nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0] .nativeElement; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[1] .nativeElement; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(2); + expect(stepper.selectedIndex).toBe(2); nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[2] .nativeElement; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(2); + expect(stepper.selectedIndex).toBe(2); }); it('should set the next stepper button type to "submit"', () => { @@ -192,34 +172,32 @@ describe('MatStepper', () => { }); it('should go to previous available step when the previous button is clicked', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); - stepperComponent.selectedIndex = 2; + stepper.selectedIndex = 2; let previousButtonNativeEl = fixture.debugElement.queryAll( By.directive(MatStepperPrevious), )[2].nativeElement; previousButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); previousButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperPrevious))[1] .nativeElement; previousButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); previousButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperPrevious))[0] .nativeElement; previousButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); }); it('should set the previous stepper button type to "button"', () => { @@ -230,33 +208,29 @@ describe('MatStepper', () => { }); it('should set the correct step position for animation', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; - expect(stepperComponent._getAnimationDirection(0)).toBe('current'); - expect(stepperComponent._getAnimationDirection(1)).toBe('next'); - expect(stepperComponent._getAnimationDirection(2)).toBe('next'); + expect(stepper._getAnimationDirection(0)).toBe('current'); + expect(stepper._getAnimationDirection(1)).toBe('next'); + expect(stepper._getAnimationDirection(2)).toBe('next'); - stepperComponent.selectedIndex = 1; + stepper.selectedIndex = 1; fixture.detectChanges(); - expect(stepperComponent._getAnimationDirection(0)).toBe('previous'); - expect(stepperComponent._getAnimationDirection(2)).toBe('next'); - expect(stepperComponent._getAnimationDirection(1)).toBe('current'); + expect(stepper._getAnimationDirection(0)).toBe('previous'); + expect(stepper._getAnimationDirection(2)).toBe('next'); + expect(stepper._getAnimationDirection(1)).toBe('current'); - stepperComponent.selectedIndex = 2; + stepper.selectedIndex = 2; fixture.detectChanges(); - expect(stepperComponent._getAnimationDirection(0)).toBe('previous'); - expect(stepperComponent._getAnimationDirection(1)).toBe('previous'); - expect(stepperComponent._getAnimationDirection(2)).toBe('current'); + expect(stepper._getAnimationDirection(0)).toBe('previous'); + expect(stepper._getAnimationDirection(1)).toBe('previous'); + expect(stepper._getAnimationDirection(2)).toBe('current'); }); it('should not set focus on header of selected step if header is not clicked', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; const stepHeaderEl = fixture.debugElement.queryAll(By.css('mat-step-header'))[1] .nativeElement; const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0] @@ -265,14 +239,12 @@ describe('MatStepper', () => { nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); expect(stepHeaderEl.focus).not.toHaveBeenCalled(); }); it('should focus next step header if focus is inside the stepper', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; const stepHeaderEl = fixture.debugElement.queryAll(By.css('mat-step-header'))[1] .nativeElement; const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0] @@ -282,7 +254,7 @@ describe('MatStepper', () => { nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); expect(stepHeaderEl.focus).toHaveBeenCalled(); }); @@ -296,9 +268,7 @@ describe('MatStepper', () => { fixture = createComponent(SimpleMatVerticalStepperApp, [], [], ViewEncapsulation.ShadowDom); fixture.detectChanges(); - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; const stepHeaderEl = fixture.debugElement.queryAll(By.css('mat-step-header'))[1] .nativeElement; const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0] @@ -308,62 +278,56 @@ describe('MatStepper', () => { nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); expect(stepHeaderEl.focus).toHaveBeenCalled(); }); it('should only be able to return to a previous step if it is editable', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; - stepperComponent.selectedIndex = 1; - stepperComponent.steps.toArray()[0].editable = false; + stepper.selectedIndex = 1; + stepper.steps.toArray()[0].editable = false; const previousButtonNativeEl = fixture.debugElement.queryAll( By.directive(MatStepperPrevious), )[1].nativeElement; previousButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); - stepperComponent.steps.toArray()[0].editable = true; + stepper.steps.toArray()[0].editable = true; previousButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); }); it('should set create icon if step is editable and completed', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0] .nativeElement; - expect(stepperComponent._getIndicatorType(0)).toBe('number'); - stepperComponent.steps.toArray()[0].editable = true; + expect(stepper.steps.first.indicatorType()).toBe('number'); + stepper.steps.toArray()[0].editable = true; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent._getIndicatorType(0)).toBe('edit'); + expect(stepper.steps.first.indicatorType()).toBe('edit'); }); it('should set done icon if step is not editable and is completed', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0] .nativeElement; - expect(stepperComponent._getIndicatorType(0)).toBe('number'); - stepperComponent.steps.toArray()[0].editable = false; + expect(stepper.steps.first.indicatorType()).toBe('number'); + stepper.steps.toArray()[0].editable = false; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent._getIndicatorType(0)).toBe('done'); + expect(stepper.steps.first.indicatorType()).toBe('done'); }); it('should emit an event when the enter animation is done', () => { - const stepper = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance; + const stepper = fixture.componentInstance.stepper; const selectionChangeSpy = jasmine.createSpy('selectionChange spy'); const animationDoneSpy = jasmine.createSpy('animationDone spy'); const selectionChangeSubscription = stepper.selectionChange.subscribe(selectionChangeSpy); @@ -389,22 +353,20 @@ describe('MatStepper', () => { }); it('should adjust the index when removing a step before the current one', () => { - const stepperComponent: MatStepper = fixture.debugElement.query( - By.css('mat-stepper'), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; - stepperComponent.selectedIndex = 2; + stepper.selectedIndex = 2; fixture.detectChanges(); // Re-assert since the setter has some extra logic. - expect(stepperComponent.selectedIndex).toBe(2); + expect(stepper.selectedIndex).toBe(2); expect(() => { fixture.componentInstance.showStepTwo.set(false); fixture.detectChanges(); }).not.toThrow(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); }); it('should not do anything when pressing the ENTER key with a modifier', () => { @@ -433,35 +395,32 @@ describe('MatStepper', () => { }); it('should add units to unit-less values passed in to animationDuration', () => { - const stepperComponent: MatStepper = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; - - stepperComponent.animationDuration = '1337'; - expect(stepperComponent.animationDuration).toBe('1337ms'); + const stepper = fixture.componentInstance.stepper; + stepper.animationDuration = '1337'; + expect(stepper.animationDuration).toBe('1337ms'); }); }); describe('basic stepper when attempting to set the selected step too early', () => { it('should not throw', () => { const fixture = createComponent(SimpleMatVerticalStepperApp); - const stepperComponent: MatStepper = fixture.debugElement.query( + const stepper: MatStepper = fixture.debugElement.query( By.css('mat-stepper'), )!.componentInstance; - expect(() => stepperComponent.selected).not.toThrow(); + expect(() => stepper.selected).not.toThrow(); }); }); describe('basic stepper when attempting to set the selected step too early', () => { it('should not throw', () => { const fixture = createComponent(SimpleMatVerticalStepperApp); - const stepperComponent: MatStepper = fixture.debugElement.query( + const stepper: MatStepper = fixture.debugElement.query( By.css('mat-stepper'), )!.componentInstance; - expect(() => (stepperComponent.selected = null!)).not.toThrow(); - expect(stepperComponent.selectedIndex).toBe(-1); + expect(() => (stepper.selected = null!)).not.toThrow(); + expect(stepper.selectedIndex).toBe(-1); }); }); @@ -499,14 +458,14 @@ describe('MatStepper', () => { it('should re-render when the completed labels change', () => { const intl = TestBed.inject(MatStepperIntl); - const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!; - const stepperComponent: MatStepper = stepperDebugElement.componentInstance; + const stepperElement = fixture.nativeElement.querySelector('mat-stepper'); + const stepper = fixture.componentInstance.stepper; - stepperComponent.steps.toArray()[0].editable = false; - stepperComponent.next(); + stepper.steps.toArray()[0].editable = false; + stepper.next(); fixture.detectChanges(); - const header = stepperDebugElement.nativeElement.querySelector('mat-step-header'); + const header = stepperElement.querySelector('mat-step-header'); const completedLabel = header.querySelector('.cdk-visually-hidden'); expect(completedLabel).toBeTruthy(); @@ -526,18 +485,19 @@ describe('MatStepper', () => { beforeEach(() => { fixture = createComponent(SimpleMatHorizontalStepperApp); fixture.detectChanges(); + fixture.detectChanges(); }); it('should re-render when the editable label changes', () => { const intl = TestBed.inject(MatStepperIntl); - const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!; - const stepperComponent: MatStepper = stepperDebugElement.componentInstance; + const stepperElement = fixture.nativeElement.querySelector('mat-stepper'); + const stepper = fixture.componentInstance.stepper; - stepperComponent.steps.toArray()[0].editable = true; - stepperComponent.next(); + stepper.steps.toArray()[0].editable = true; + stepper.next(); fixture.detectChanges(); - const header = stepperDebugElement.nativeElement.querySelector('mat-step-header'); + const header = stepperElement.querySelector('mat-step-header'); const editableLabel = header.querySelector('.cdk-visually-hidden'); expect(editableLabel).toBeTruthy(); @@ -560,34 +520,34 @@ describe('MatStepper', () => { }); it('should allow for the `edit` icon to be overridden', () => { - const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!; - const stepperComponent: MatStepper = stepperDebugElement.componentInstance; + const stepperElement = fixture.nativeElement.querySelector('mat-stepper'); + const stepper = fixture.componentInstance.stepper; - stepperComponent.steps.toArray()[0].editable = true; - stepperComponent.next(); + stepper.steps.toArray()[0].editable = true; + stepper.next(); fixture.detectChanges(); - const header = stepperDebugElement.nativeElement.querySelector('mat-step-header'); + const header = stepperElement.querySelector('mat-step-header'); expect(header.textContent).toContain('Custom edit'); }); it('should allow for the `done` icon to be overridden', () => { - const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!; - const stepperComponent: MatStepper = stepperDebugElement.componentInstance; + const stepperElement = fixture.nativeElement.querySelector('mat-stepper'); + const stepper = fixture.componentInstance.stepper; - stepperComponent.steps.toArray()[0].editable = false; - stepperComponent.next(); + stepper.steps.toArray()[0].editable = false; + stepper.next(); fixture.detectChanges(); - const header = stepperDebugElement.nativeElement.querySelector('mat-step-header'); + const header = stepperElement.querySelector('mat-step-header'); expect(header.textContent).toContain('Custom done'); }); it('should allow for the `number` icon to be overridden with context', () => { - const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!; - const headers = stepperDebugElement.nativeElement.querySelectorAll('mat-step-header'); + const stepperElement = fixture.nativeElement.querySelector('mat-stepper'); + const headers = stepperElement.querySelectorAll('mat-step-header'); expect(headers[2].textContent).toContain('III'); }); @@ -603,45 +563,43 @@ describe('MatStepper', () => { }); it('should reverse animation in RTL mode', () => { - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; - expect(stepperComponent._getAnimationDirection(0)).toBe('current'); - expect(stepperComponent._getAnimationDirection(1)).toBe('previous'); - expect(stepperComponent._getAnimationDirection(2)).toBe('previous'); + expect(stepper._getAnimationDirection(0)).toBe('current'); + expect(stepper._getAnimationDirection(1)).toBe('previous'); + expect(stepper._getAnimationDirection(2)).toBe('previous'); - stepperComponent.selectedIndex = 1; + stepper.selectedIndex = 1; fixture.detectChanges(); - expect(stepperComponent._getAnimationDirection(0)).toBe('next'); - expect(stepperComponent._getAnimationDirection(2)).toBe('previous'); - expect(stepperComponent._getAnimationDirection(1)).toBe('current'); + expect(stepper._getAnimationDirection(0)).toBe('next'); + expect(stepper._getAnimationDirection(2)).toBe('previous'); + expect(stepper._getAnimationDirection(1)).toBe('current'); - stepperComponent.selectedIndex = 2; + stepper.selectedIndex = 2; fixture.detectChanges(); - expect(stepperComponent._getAnimationDirection(0)).toBe('next'); - expect(stepperComponent._getAnimationDirection(1)).toBe('next'); - expect(stepperComponent._getAnimationDirection(2)).toBe('current'); + expect(stepper._getAnimationDirection(0)).toBe('next'); + expect(stepper._getAnimationDirection(1)).toBe('next'); + expect(stepper._getAnimationDirection(2)).toBe('current'); }); }); describe('linear stepper', () => { let fixture: ComponentFixture<LinearMatVerticalStepperApp>; let testComponent: LinearMatVerticalStepperApp; - let stepperComponent: MatStepper; + let stepper: MatStepper; beforeEach(() => { fixture = createComponent(LinearMatVerticalStepperApp, [], [], undefined, []); fixture.detectChanges(); testComponent = fixture.componentInstance; - stepperComponent = fixture.debugElement.query(By.css('mat-stepper'))!.componentInstance; + stepper = fixture.debugElement.query(By.css('mat-stepper'))!.componentInstance; }); it('should have true linear attribute', () => { - expect(stepperComponent.linear).toBe(true); + expect(stepper.linear).toBe(true); }); it('should not move to next step if current step is invalid', () => { @@ -649,7 +607,7 @@ describe('MatStepper', () => { expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false); expect(testComponent.oneGroup.valid).toBe(false); expect(testComponent.oneGroup.invalid).toBe(true); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); const stepHeaderEl = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'))[1] .nativeElement; @@ -657,21 +615,21 @@ describe('MatStepper', () => { stepHeaderEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0] .nativeElement; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); testComponent.oneGroup.get('oneCtrl')!.setValue('answer'); stepHeaderEl.click(); fixture.detectChanges(); expect(testComponent.oneGroup.valid).toBe(true); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); }); it('should not move to next step if current step is pending', () => { @@ -683,9 +641,9 @@ describe('MatStepper', () => { testComponent.oneGroup.get('oneCtrl')!.setValue('input'); testComponent.twoGroup.get('twoCtrl')!.setValue('input'); - stepperComponent.selectedIndex = 1; + stepper.selectedIndex = 1; fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); // Step status = PENDING // Assert that linear stepper does not allow step selection change @@ -694,12 +652,12 @@ describe('MatStepper', () => { stepHeaderEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); // Trigger asynchronous validation testComponent.validationTrigger.next(); @@ -711,16 +669,16 @@ describe('MatStepper', () => { stepHeaderEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(2); + expect(stepper.selectedIndex).toBe(2); - stepperComponent.selectedIndex = 1; + stepper.selectedIndex = 1; fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(2); + expect(stepper.selectedIndex).toBe(2); }); it('should be able to focus step header upon click if it is unable to be selected', () => { @@ -736,13 +694,13 @@ describe('MatStepper', () => { testComponent.oneGroup.get('oneCtrl')!.setValue('input'); testComponent.twoGroup.get('twoCtrl')!.setValue('input'); testComponent.validationTrigger.next(); - stepperComponent.selectedIndex = 1; + stepper.selectedIndex = 1; fixture.detectChanges(); - stepperComponent.selectedIndex = 2; + stepper.selectedIndex = 2; fixture.detectChanges(); - expect(stepperComponent.steps.toArray()[2].optional).toBe(true); - expect(stepperComponent.selectedIndex).toBe(2); + expect(stepper.steps.toArray()[2].optional).toBe(true); + expect(stepper.selectedIndex).toBe(2); expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(true); const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[2] @@ -750,34 +708,34 @@ describe('MatStepper', () => { nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex) + expect(stepper.selectedIndex) .withContext('Expected selectedIndex to change when optional step input is empty.') .toBe(3); - stepperComponent.selectedIndex = 2; + stepper.selectedIndex = 2; testComponent.threeGroup.get('threeCtrl')!.setValue('input'); nextButtonNativeEl.click(); fixture.detectChanges(); expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(false); - expect(stepperComponent.selectedIndex) + expect(stepper.selectedIndex) .withContext('Expected selectedIndex to change when optional step input is invalid.') .toBe(3); }); it('should be able to reset the stepper to its initial state', () => { - const steps = stepperComponent.steps.toArray(); + const steps = stepper.steps.toArray(); testComponent.oneGroup.get('oneCtrl')!.setValue('value'); fixture.detectChanges(); - stepperComponent.next(); + stepper.next(); fixture.detectChanges(); - stepperComponent.next(); + stepper.next(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); expect(steps[0].interacted).toBe(true); expect(steps[0].completed).toBe(true); expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(true); @@ -787,10 +745,10 @@ describe('MatStepper', () => { expect(steps[1].completed).toBe(false); expect(testComponent.twoGroup.get('twoCtrl')!.valid).toBe(false); - stepperComponent.reset(); + stepper.reset(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); expect(steps[0].interacted).toBe(false); expect(steps[0].completed).toBe(false); expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false); @@ -802,36 +760,36 @@ describe('MatStepper', () => { }); it('should reset back to the first step when some of the steps are not editable', () => { - const steps = stepperComponent.steps.toArray(); + const steps = stepper.steps.toArray(); steps[0].editable = false; testComponent.oneGroup.get('oneCtrl')!.setValue('value'); fixture.detectChanges(); - stepperComponent.next(); + stepper.next(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); - stepperComponent.reset(); + stepper.reset(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); }); it('should not clobber the `complete` binding when resetting', () => { - const steps: CdkStep[] = stepperComponent.steps.toArray(); + const steps: CdkStep[] = stepper.steps.toArray(); const fillOutStepper = () => { testComponent.oneGroup.get('oneCtrl')!.setValue('input'); testComponent.twoGroup.get('twoCtrl')!.setValue('input'); testComponent.threeGroup.get('threeCtrl')!.setValue('valid'); testComponent.validationTrigger.next(); - stepperComponent.selectedIndex = 1; + stepper.selectedIndex = 1; fixture.detectChanges(); - stepperComponent.selectedIndex = 2; + stepper.selectedIndex = 2; fixture.detectChanges(); - stepperComponent.selectedIndex = 3; + stepper.selectedIndex = 3; fixture.detectChanges(); }; @@ -841,7 +799,7 @@ describe('MatStepper', () => { .withContext('Expected third step to be considered complete after the first run through.') .toBe(true); - stepperComponent.reset(); + stepper.reset(); fixture.detectChanges(); fillOutStepper(); @@ -856,21 +814,21 @@ describe('MatStepper', () => { expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe(''); expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false); expect(testComponent.oneGroup.valid).toBe(false); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0] .nativeElement; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper.selectedIndex).toBe(0); - stepperComponent.steps.first.completed = true; + stepper.steps.first.completed = true; nextButtonNativeEl.click(); fixture.detectChanges(); expect(testComponent.oneGroup.valid).toBe(false); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepper.selectedIndex).toBe(1); }); it('should set aria-disabled if the user is not able to navigate to a step', () => { @@ -894,7 +852,7 @@ describe('MatStepper', () => { beforeEach(() => { preselectedFixture = createComponent(SimplePreselectedMatHorizontalStepperApp); preselectedFixture.detectChanges(); - stepper = preselectedFixture.debugElement.query(By.directive(MatStepper))!.componentInstance; + stepper = preselectedFixture.componentInstance.stepper; }); it('should not throw', () => { @@ -917,7 +875,7 @@ describe('MatStepper', () => { beforeEach(() => { fixture = createComponent(LinearMatVerticalStepperAppForAlreadyFilledForm); fixture.detectChanges(); - stepper = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance; + stepper = fixture.componentInstance.stepper; }); it('should navigate to previous steps', () => { @@ -938,9 +896,7 @@ describe('MatStepper', () => { noStepControlFixture.detectChanges(); }); it('should not move to the next step if the current one is not completed ', () => { - const stepper: MatStepper = noStepControlFixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = noStepControlFixture.componentInstance.stepper; const headers = noStepControlFixture.debugElement.queryAll( By.css('.mat-horizontal-stepper-header'), @@ -966,9 +922,7 @@ describe('MatStepper', () => { expect(controlAndBindingFixture.componentInstance.steps[0].control.valid).toBe(true); expect(controlAndBindingFixture.componentInstance.steps[0].completed).toBe(false); - const stepper: MatStepper = controlAndBindingFixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = controlAndBindingFixture.componentInstance.stepper; const headers = controlAndBindingFixture.debugElement.queryAll( By.css('.mat-horizontal-stepper-header'), @@ -1196,9 +1150,7 @@ describe('MatStepper', () => { const fixture = createComponent(SimpleMatHorizontalStepperApp); fixture.detectChanges(); - const stepper: MatStepper = fixture.debugElement.query( - By.directive(MatStepper), - ).componentInstance; + const stepper = fixture.componentInstance.stepper; expect(stepper.steps.map(step => step.interacted)).toEqual([false, false, false]); @@ -1219,9 +1171,7 @@ describe('MatStepper', () => { const fixture = createComponent(SimpleMatHorizontalStepperApp); fixture.detectChanges(); - const stepper: MatStepper = fixture.debugElement.query( - By.directive(MatStepper), - ).componentInstance; + const stepper = fixture.componentInstance.stepper; const interactedSteps: number[] = []; const subscription = merge(...stepper.steps.map(step => step.interactedStream)).subscribe( step => interactedSteps.push(stepper.steps.toArray().indexOf(step as MatStep)), @@ -1247,9 +1197,7 @@ describe('MatStepper', () => { const fixture = createComponent(SimpleMatHorizontalStepperApp); fixture.detectChanges(); - const stepper: MatStepper = fixture.debugElement.query( - By.directive(MatStepper), - ).componentInstance; + const stepper = fixture.componentInstance.stepper; const interactedSteps: number[] = []; const subscription = merge(...stepper.steps.map(step => step.interactedStream)).subscribe( step => interactedSteps.push(stepper.steps.toArray().indexOf(step as MatStep)), @@ -1383,7 +1331,7 @@ describe('MatStepper', () => { nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.ERROR); + expect(stepper.steps.first.indicatorType()).toBe(STEP_STATE.ERROR); }); it('should respect a custom falsy hasError value', () => { @@ -1395,12 +1343,12 @@ describe('MatStepper', () => { nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.ERROR); + expect(stepper.steps.first.indicatorType()).toBe(STEP_STATE.ERROR); stepper.steps.first.hasError = false; fixture.detectChanges(); - expect(stepper._getIndicatorType(0)).not.toBe(STEP_STATE.ERROR); + expect(stepper.steps.first.indicatorType()).not.toBe(STEP_STATE.ERROR); }); it('should show error state if explicitly enabled, even when disabled globally', () => { @@ -1413,7 +1361,7 @@ describe('MatStepper', () => { nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.ERROR); + expect(stepper.steps.first.indicatorType()).toBe(STEP_STATE.ERROR); }); }); @@ -1445,7 +1393,7 @@ describe('MatStepper', () => { nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.DONE); + expect(stepper.steps.first.indicatorType()).toBe(STEP_STATE.DONE); }); it('should show edit state when step is editable and its the current step', () => { @@ -1453,7 +1401,7 @@ describe('MatStepper', () => { stepper.steps.toArray()[1].editable = true; fixture.detectChanges(); - expect(stepper._getIndicatorType(1)).toBe(STEP_STATE.EDIT); + expect(stepper.steps.get(1)!.indicatorType()).toBe(STEP_STATE.EDIT); }); }); @@ -1463,42 +1411,40 @@ describe('MatStepper', () => { fixture.detectChanges(); const stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header')); - const stepperComponent = fixture.debugElement.query( - By.directive(MatStepper), - )!.componentInstance; + const stepper = fixture.componentInstance.stepper; - expect(stepperComponent.selectedIndex).toBe(0); - expect(stepperComponent.selected instanceof MatStep).toBe(true); + expect(stepper.selectedIndex).toBe(0); + expect(stepper.selected instanceof MatStep).toBe(true); // select the second step let stepHeaderEl = stepHeaders[1].nativeElement; stepHeaderEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); - expect(stepperComponent.selected instanceof MatStep).toBe(true); + expect(stepper.selectedIndex).toBe(1); + expect(stepper.selected instanceof MatStep).toBe(true); // select the third step stepHeaderEl = stepHeaders[2].nativeElement; stepHeaderEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(2); - expect(stepperComponent.selected instanceof MatStep).toBe(true); + expect(stepper.selectedIndex).toBe(2); + expect(stepper.selected instanceof MatStep).toBe(true); }); it('should allow for the `edit` icon to be overridden', () => { const fixture = createComponent(IndirectDescendantIconOverridesStepper); fixture.detectChanges(); - const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!; - const stepperComponent: MatStepper = stepperDebugElement.componentInstance; + const stepperElement = fixture.nativeElement.querySelector('mat-stepper'); + const stepper = fixture.componentInstance.stepper; - stepperComponent.steps.toArray()[0].editable = true; - stepperComponent.next(); + stepper.steps.toArray()[0].editable = true; + stepper.next(); fixture.detectChanges(); - const header = stepperDebugElement.nativeElement.querySelector('mat-step-header'); + const header = stepperElement.querySelector('mat-step-header'); expect(header.textContent).toContain('Custom edit'); }); @@ -1507,14 +1453,14 @@ describe('MatStepper', () => { const fixture = createComponent(IndirectDescendantIconOverridesStepper); fixture.detectChanges(); - const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!; - const stepperComponent: MatStepper = stepperDebugElement.componentInstance; + const stepperElement = fixture.nativeElement.querySelector('mat-stepper'); + const stepper = fixture.componentInstance.stepper; - stepperComponent.steps.toArray()[0].editable = false; - stepperComponent.next(); + stepper.steps.toArray()[0].editable = false; + stepper.next(); fixture.detectChanges(); - const header = stepperDebugElement.nativeElement.querySelector('mat-step-header'); + const header = stepperElement.querySelector('mat-step-header'); expect(header.textContent).toContain('Custom done'); }); @@ -1523,8 +1469,8 @@ describe('MatStepper', () => { const fixture = createComponent(IndirectDescendantIconOverridesStepper); fixture.detectChanges(); - const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!; - const headers = stepperDebugElement.nativeElement.querySelectorAll('mat-step-header'); + const stepperElement = fixture.nativeElement.querySelector('mat-stepper'); + const headers = stepperElement.querySelectorAll('mat-step-header'); expect(headers[2].textContent).toContain('III'); }); @@ -1638,25 +1584,25 @@ describe('MatStepper', () => { /** Asserts that keyboard interaction works correctly. */ function assertCorrectKeyboardInteraction( - fixture: ComponentFixture<any>, + fixture: ComponentFixture<{stepper: MatStepper}>, stepHeaders: DebugElement[], orientation: StepperOrientation, ) { - const stepperComponent = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance; + const stepper = fixture.componentInstance.stepper; const nextKey = orientation === 'vertical' ? DOWN_ARROW : RIGHT_ARROW; const prevKey = orientation === 'vertical' ? UP_ARROW : LEFT_ARROW; - expect(stepperComponent._getFocusIndex()).toBe(0); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper._getFocusIndex()).toBe(0); + expect(stepper.selectedIndex).toBe(0); let stepHeaderEl = stepHeaders[0].nativeElement; dispatchKeyboardEvent(stepHeaderEl, 'keydown', nextKey); fixture.detectChanges(); - expect(stepperComponent._getFocusIndex()) + expect(stepper._getFocusIndex()) .withContext('Expected index of focused step to increase by 1 after pressing the next key.') .toBe(1); - expect(stepperComponent.selectedIndex) + expect(stepper.selectedIndex) .withContext('Expected index of selected step to remain unchanged after pressing the next key.') .toBe(0); @@ -1664,10 +1610,10 @@ function assertCorrectKeyboardInteraction( dispatchKeyboardEvent(stepHeaderEl, 'keydown', ENTER); fixture.detectChanges(); - expect(stepperComponent._getFocusIndex()) + expect(stepper._getFocusIndex()) .withContext('Expected index of focused step to remain unchanged after ENTER event.') .toBe(1); - expect(stepperComponent.selectedIndex) + expect(stepper.selectedIndex) .withContext( 'Expected index of selected step to change to index of focused step ' + 'after ENTER event.', ) @@ -1677,12 +1623,12 @@ function assertCorrectKeyboardInteraction( dispatchKeyboardEvent(stepHeaderEl, 'keydown', prevKey); fixture.detectChanges(); - expect(stepperComponent._getFocusIndex()) + expect(stepper._getFocusIndex()) .withContext( 'Expected index of focused step to decrease by 1 after pressing the ' + 'previous key.', ) .toBe(0); - expect(stepperComponent.selectedIndex) + expect(stepper.selectedIndex) .withContext( 'Expected index of selected step to remain unchanged after pressing the ' + 'previous key.', ) @@ -1690,18 +1636,18 @@ function assertCorrectKeyboardInteraction( // When the focus is on the last step and right arrow key is pressed, the focus should cycle // through to the first step. - stepperComponent._keyManager.updateActiveItem(2); + (stepper as any)._keyManager.updateActiveItem(2); stepHeaderEl = stepHeaders[2].nativeElement; dispatchKeyboardEvent(stepHeaderEl, 'keydown', nextKey); fixture.detectChanges(); - expect(stepperComponent._getFocusIndex()) + expect(stepper._getFocusIndex()) .withContext( 'Expected index of focused step to cycle through to index 0 after pressing ' + 'the next key.', ) .toBe(0); - expect(stepperComponent.selectedIndex) + expect(stepper.selectedIndex) .withContext( 'Expected index of selected step to remain unchanged after pressing ' + 'the next key.', ) @@ -1711,17 +1657,17 @@ function assertCorrectKeyboardInteraction( dispatchKeyboardEvent(stepHeaderEl, 'keydown', SPACE); fixture.detectChanges(); - expect(stepperComponent._getFocusIndex()) + expect(stepper._getFocusIndex()) .withContext('Expected index of focused to remain unchanged after SPACE event.') .toBe(0); - expect(stepperComponent.selectedIndex) + expect(stepper.selectedIndex) .withContext( 'Expected index of selected step to change to index of focused step ' + 'after SPACE event.', ) .toBe(0); const endEvent = dispatchKeyboardEvent(stepHeaderEl, 'keydown', END); - expect(stepperComponent._getFocusIndex()) + expect(stepper._getFocusIndex()) .withContext('Expected last step to be focused when pressing END.') .toBe(stepHeaders.length - 1); expect(endEvent.defaultPrevented) @@ -1729,7 +1675,7 @@ function assertCorrectKeyboardInteraction( .toBe(true); const homeEvent = dispatchKeyboardEvent(stepHeaderEl, 'keydown', HOME); - expect(stepperComponent._getFocusIndex()) + expect(stepper._getFocusIndex()) .withContext('Expected first step to be focused when pressing HOME.') .toBe(0); expect(homeEvent.defaultPrevented) @@ -1739,38 +1685,37 @@ function assertCorrectKeyboardInteraction( /** Asserts that arrow key direction works correctly in RTL mode. */ function assertArrowKeyInteractionInRtl( - fixture: ComponentFixture<any>, + fixture: ComponentFixture<{stepper: MatStepper}>, stepHeaders: DebugElement[], ) { - const stepperComponent = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance; - - expect(stepperComponent._getFocusIndex()).toBe(0); + const stepper = fixture.componentInstance.stepper; + expect(stepper._getFocusIndex()).toBe(0); let stepHeaderEl = stepHeaders[0].nativeElement; dispatchKeyboardEvent(stepHeaderEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); - expect(stepperComponent._getFocusIndex()).toBe(1); + expect(stepper._getFocusIndex()).toBe(1); stepHeaderEl = stepHeaders[1].nativeElement; dispatchKeyboardEvent(stepHeaderEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); - expect(stepperComponent._getFocusIndex()).toBe(0); + expect(stepper._getFocusIndex()).toBe(0); } /** Asserts that keyboard interaction works correctly when the user is pressing a modifier key. */ function assertSelectKeyWithModifierInteraction( - fixture: ComponentFixture<any>, + fixture: ComponentFixture<{stepper: MatStepper}>, stepHeaders: DebugElement[], orientation: StepperOrientation, selectionKey: number, ) { - const stepperComponent = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance; + const stepper = fixture.componentInstance.stepper; const modifiers = ['altKey', 'shiftKey', 'ctrlKey', 'metaKey']; - expect(stepperComponent._getFocusIndex()).toBe(0); - expect(stepperComponent.selectedIndex).toBe(0); + expect(stepper._getFocusIndex()).toBe(0); + expect(stepper.selectedIndex).toBe(0); dispatchKeyboardEvent( stepHeaders[0].nativeElement, @@ -1779,12 +1724,12 @@ function assertSelectKeyWithModifierInteraction( ); fixture.detectChanges(); - expect(stepperComponent._getFocusIndex()) + expect(stepper._getFocusIndex()) .withContext( 'Expected index of focused step to increase by 1 after pressing ' + 'the next key.', ) .toBe(1); - expect(stepperComponent.selectedIndex) + expect(stepper.selectedIndex) .withContext( 'Expected index of selected step to remain unchanged after pressing ' + 'the next key.', ) @@ -1796,7 +1741,7 @@ function assertSelectKeyWithModifierInteraction( dispatchEvent(stepHeaders[1].nativeElement, event); fixture.detectChanges(); - expect(stepperComponent.selectedIndex) + expect(stepper.selectedIndex) .withContext( `Expected selected index to remain unchanged ` + `when pressing the selection key with ${modifier} modifier.`, @@ -1827,7 +1772,6 @@ function createComponent<T>( TestBed.configureTestingModule({ imports: [MatStepperModule, ReactiveFormsModule, ...imports], providers: [ - provideCheckNoChangesConfig({exhaustive: false}), provideFakeDirectionality(dir), {provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}, ...providers, @@ -1917,6 +1861,7 @@ class MatHorizontalStepperWithErrorsApp { standalone: false, }) class SimpleMatHorizontalStepperApp { + @ViewChild(MatStepper) stepper: MatStepper; inputLabel = 'Step 3'; disableRipple = signal(false); stepperTheme = signal<ThemePalette>(undefined); @@ -1957,6 +1902,7 @@ class SimpleMatHorizontalStepperApp { standalone: false, }) class SimpleMatVerticalStepperApp { + @ViewChild(MatStepper) stepper: MatStepper; inputLabel = signal('Step 3'); showStepTwo = signal(true); disableRipple = signal(false); @@ -2028,6 +1974,7 @@ class LinearMatVerticalStepperApp { standalone: false, }) class SimplePreselectedMatHorizontalStepperApp { + @ViewChild(MatStepper) stepper: MatStepper; index = 0; } @@ -2073,6 +2020,7 @@ class SimplePreselectedMatHorizontalStepperApp { standalone: false, }) class LinearMatVerticalStepperAppForAlreadyFilledForm { + @ViewChild(MatStepper) stepper: MatStepper; selectedIndex = signal(2); oneGroup = new FormGroup({ @@ -2097,6 +2045,7 @@ class LinearMatVerticalStepperAppForAlreadyFilledForm { standalone: false, }) class SimpleStepperWithoutStepControl { + @ViewChild(MatStepper) stepper: MatStepper; steps = [ {label: 'One', completed: false}, {label: 'Two', completed: false}, @@ -2118,6 +2067,8 @@ class SimpleStepperWithoutStepControl { standalone: false, }) class SimpleStepperWithStepControlAndCompletedBinding { + @ViewChild(MatStepper) stepper: MatStepper; + steps = [ {label: 'One', completed: false, control: new FormControl('')}, {label: 'Two', completed: false, control: new FormControl('')}, @@ -2142,6 +2093,8 @@ class SimpleStepperWithStepControlAndCompletedBinding { standalone: false, }) class IconOverridesStepper { + @ViewChild(MatStepper) stepper: MatStepper; + getRomanNumeral(value: number) { const numberMap: {[key: number]: string} = { 1: 'I', @@ -2219,7 +2172,9 @@ class StepperWithAriaInputs { `, standalone: false, }) -class StepperWithIndirectDescendantSteps {} +class StepperWithIndirectDescendantSteps { + @ViewChild(MatStepper) stepper: MatStepper; +} @Component({ template: ` @@ -2310,6 +2265,7 @@ class StepperWithLazyContent { standalone: false, }) class HorizontalStepperWithDelayedStep { + @ViewChild(MatStepper) stepper: MatStepper; renderSecondStep = signal(false); } diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts index 17b7781e1907..6d1399dae0df 100644 --- a/src/material/stepper/stepper.ts +++ b/src/material/stepper/stepper.ts @@ -283,10 +283,6 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten this._cleanupTransition?.(); } - _stepIsNavigable(index: number, step: MatStep): boolean { - return step.completed || this.selectedIndex === index || !this.linear; - } - _getAnimationDuration() { if (this._animationsDisabled) { return '0ms';