Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/material/stepper/step-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
56 changes: 33 additions & 23 deletions src/material/stepper/stepper.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
@switch (orientation) {
@case ('horizontal') {
<div class="mat-horizontal-stepper-wrapper">
<div class="mat-horizontal-stepper-header-container">
<div
aria-orientation="horizontal"
class="mat-horizontal-stepper-header-container"
role="tablist">
@for (step of steps; track step) {
<ng-container
[ngTemplateOutlet]="stepTemplate"
Expand Down Expand Up @@ -40,28 +43,31 @@
}

@case ('vertical') {
@for (step of steps; track step) {
<div class="mat-step">
<ng-container
[ngTemplateOutlet]="stepTemplate"
[ngTemplateOutletContext]="{step}"/>
<div
#animatedContainer
class="mat-vertical-content-container"
[class.mat-stepper-vertical-line]="!$last"
[class.mat-vertical-content-container-active]="selectedIndex === $index"
[attr.inert]="selectedIndex === $index ? null : ''">
<div class="mat-vertical-stepper-content"
role="tabpanel"
[id]="_getStepContentId($index)"
[attr.aria-labelledby]="_getStepLabelId($index)">
<div class="mat-vertical-content">
<ng-container [ngTemplateOutlet]="step.content"/>
<div class="mat-vertical-stepper-wrapper">
@for (step of steps; track step) {
<div class="mat-step">
<ng-container
[ngTemplateOutlet]="stepTemplate"
[ngTemplateOutletContext]="{step}"/>
<div
#animatedContainer
class="mat-vertical-content-container"
[class.mat-stepper-vertical-line]="!$last"
[class.mat-vertical-content-container-active]="selectedIndex === $index"
[attr.inert]="selectedIndex === $index ? null : ''">
<div
class="mat-vertical-stepper-content"
role="region"
[id]="_getStepContentId($index)"
[attr.aria-labelledby]="_getStepLabelId($index)">
<div class="mat-vertical-content">
<ng-container [ngTemplateOutlet]="step.content"/>
</div>
</div>
</div>
</div>
</div>
}
}
</div>
}
}

Expand All @@ -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"
Expand Down
42 changes: 16 additions & 26 deletions src/material/stepper/stepper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

Expand Down Expand Up @@ -342,15 +337,6 @@ describe('MatStepper', () => {
animationDoneSubscription.unsubscribe();
});

it('should set the correct aria-posinset and aria-setsize', () => {
const headers = Array.from<HTMLElement>(
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;

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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');
});

Expand All @@ -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<HTMLElement>(
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();
Expand Down
2 changes: 0 additions & 2 deletions src/material/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion src/material/stepper/testing/step-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export class MatStepHarness extends ContentContainerComponentHarness<string> {
/** Whether the step is selected. */
async isSelected(): Promise<boolean> {
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. */
Expand Down
Loading