diff --git a/src/material/stepper/step-header.ts b/src/material/stepper/step-header.ts index eb95f01ad708..db473527aab7 100644 --- a/src/material/stepper/step-header.ts +++ b/src/material/stepper/step-header.ts @@ -36,7 +36,7 @@ import {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/privat 'class': 'mat-step-header', '[class.mat-step-header-empty-label]': '_hasEmptyLabel()', '[class]': '"mat-" + (color || "primary")', - 'role': 'tab', + 'role': '', // ignore cdk role in favor of setting appropriately in html }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index 0f75e58c012b..6f4e3660f668 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -11,7 +11,10 @@ @switch (orientation) { @case ('horizontal') {
-
+
@for (step of steps; track step) { - -
-
-
- +
+ @for (step of steps; track step) { +
+ +
+
+
+ +
-
- } + } +
} } @@ -74,10 +80,14 @@ (keydown)="_onKeydown($event)" [tabIndex]="_getFocusIndex() === step.index() ? 0 : -1" [id]="_getStepLabelId(step.index())" - [attr.aria-posinset]="step.index() + 1" - [attr.aria-setsize]="steps.length" + [attr.role]="orientation === 'horizontal' ? 'tab' : 'button'" + [attr.aria-posinset]="orientation === 'horizontal' ? step.index() + 1 : null" + [attr.aria-setsize]="orientation === 'horizontal' ? steps.length : null" + [attr.aria-selected]="orientation === 'horizontal' ? step.isSelected() : null" + [attr.aria-current]="orientation === 'vertical' && step.isSelected() ? 'step' : null" + [attr.aria-disabled]="orientation === 'vertical' && step.isSelected() ? 'true' : null" + [attr.aria-expanded]="orientation === 'vertical' ? step.isSelected() : null" [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]="step.isNavigable() ? null : true" diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index ad62b508bb8e..55fa9cf193a3 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -114,25 +114,20 @@ describe('MatStepper', () => { expect(stepper.selected instanceof MatStep).toBe(true); }); - it('should set the "tablist" role on stepper', () => { - const stepperEl = fixture.debugElement.query(By.css('mat-stepper'))!.nativeElement; - expect(stepperEl.getAttribute('role')).toBe('tablist'); - }); - it('should display the correct label', () => { - let selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]'); + let selectedLabel = fixture.nativeElement.querySelector('[aria-current="step"]'); expect(selectedLabel.textContent).toMatch('Step 1'); fixture.componentInstance.stepper.selectedIndex = 2; fixture.detectChanges(); - selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]'); + selectedLabel = fixture.nativeElement.querySelector('[aria-current="step"]'); expect(selectedLabel.textContent).toMatch('Step 3'); fixture.componentInstance.inputLabel.set('New Label'); fixture.detectChanges(); - selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]'); + selectedLabel = fixture.nativeElement.querySelector('[aria-current="step"]'); expect(selectedLabel.textContent).toMatch('New Label'); }); @@ -342,15 +337,6 @@ describe('MatStepper', () => { animationDoneSubscription.unsubscribe(); }); - it('should set the correct aria-posinset and aria-setsize', () => { - const headers = Array.from( - fixture.nativeElement.querySelectorAll('.mat-step-header'), - ); - - expect(headers.map(header => header.getAttribute('aria-posinset'))).toEqual(['1', '2', '3']); - expect(headers.every(header => header.getAttribute('aria-setsize') === '3')).toBe(true); - }); - it('should adjust the index when removing a step before the current one', () => { const stepper = fixture.componentInstance.stepper; @@ -937,14 +923,6 @@ describe('MatStepper', () => { }); describe('vertical stepper', () => { - it('should set the aria-orientation to "vertical"', () => { - const fixture = createComponent(SimpleMatVerticalStepperApp); - fixture.detectChanges(); - - const stepperEl = fixture.debugElement.query(By.css('mat-stepper'))!.nativeElement; - expect(stepperEl.getAttribute('aria-orientation')).toBe('vertical'); - }); - it('should support using the left/right arrows to move focus', () => { const fixture = createComponent(SimpleMatVerticalStepperApp); fixture.detectChanges(); @@ -1045,7 +1023,7 @@ describe('MatStepper', () => { const fixture = createComponent(SimpleMatHorizontalStepperApp); fixture.detectChanges(); - const stepperEl = fixture.debugElement.query(By.css('mat-stepper'))!.nativeElement; + const stepperEl = fixture.debugElement.query(By.css('[role="tablist"]'))!.nativeElement; expect(stepperEl.getAttribute('aria-orientation')).toBe('horizontal'); }); @@ -1066,6 +1044,18 @@ describe('MatStepper', () => { assertArrowKeyInteractionInRtl(fixture, stepHeaders); }); + it('should set the correct aria-posinset and aria-setsize', () => { + const fixture = createComponent(SimpleMatHorizontalStepperApp); + fixture.detectChanges(); + + const headers = Array.from( + fixture.nativeElement.querySelectorAll('.mat-step-header'), + ); + + expect(headers.map(header => header.getAttribute('aria-posinset'))).toEqual(['1', '2', '3']); + expect(headers.every(header => header.getAttribute('aria-setsize') === '3')).toBe(true); + }); + it('should maintain the correct navigation order when a step is added later on', () => { const fixture = createComponent(HorizontalStepperWithDelayedStep); fixture.detectChanges(); diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts index 6d1399dae0df..82f3f6db9079 100644 --- a/src/material/stepper/stepper.ts +++ b/src/material/stepper/stepper.ts @@ -130,8 +130,6 @@ export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentI '[class.mat-stepper-header-position-bottom]': 'headerPosition === "bottom"', '[class.mat-stepper-animating]': '_isAnimating()', '[style.--mat-stepper-animation-duration]': '_getAnimationDuration()', - '[attr.aria-orientation]': 'orientation', - 'role': 'tablist', }, providers: [{provide: CdkStepper, useExisting: MatStepper}], encapsulation: ViewEncapsulation.None, diff --git a/src/material/stepper/testing/step-harness.ts b/src/material/stepper/testing/step-harness.ts index f3af257d6667..ca27dc352121 100644 --- a/src/material/stepper/testing/step-harness.ts +++ b/src/material/stepper/testing/step-harness.ts @@ -64,7 +64,10 @@ export class MatStepHarness extends ContentContainerComponentHarness { /** Whether the step is selected. */ async isSelected(): Promise { const host = await this.host(); - return (await host.getAttribute('aria-selected')) === 'true'; + return ( + (await host.getAttribute('aria-selected')) === 'true' || + (await host.getAttribute('aria-current')) === 'step' + ); } /** Whether the step has been filled out. */