Skip to content
This repository was archived by the owner on Dec 18, 2024. It is now read-only.

Commit a3b3acf

Browse files
committed
chore(accessibility): Add aria attributes to aid screen reader flow
1 parent 1acc345 commit a3b3acf

File tree

13 files changed

+137
-28
lines changed

13 files changed

+137
-28
lines changed

src/app/material-docs-app.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Component, ViewEncapsulation} from '@angular/core';
22
import {Router, NavigationStart} from '@angular/router';
3+
import {Title} from '@angular/platform-browser';
34

45

56
@Component({
@@ -10,8 +11,9 @@ import {Router, NavigationStart} from '@angular/router';
1011
})
1112
export class MaterialDocsApp {
1213
showShadow = false;
14+
baseTitle = 'Angular Material';
1315

14-
constructor(router: Router) {
16+
constructor(router: Router, private _titleService: Title) {
1517
let previousRoute = router.routerState.snapshot.url;
1618

1719
router.events.subscribe((data: NavigationStart) => {
@@ -24,8 +26,33 @@ export class MaterialDocsApp {
2426
}
2527

2628
previousRoute = data.url;
29+
30+
// set page title
31+
this._setTitle(window.location.pathname);
2732
});
2833
}
34+
35+
private _setTitle(pathname) {
36+
const title = this._getTitle(pathname);
37+
title ?
38+
this._titleService.setTitle(`${this.baseTitle} - ${this._capitalizeTitle(title)}`) :
39+
this._titleService.setTitle(this.baseTitle);
40+
}
41+
42+
private _getTitle(pathname) {
43+
return pathname.split('/').filter(Boolean).pop();
44+
}
45+
46+
private _trimFilename(filename) {
47+
const isFilenameRegex = new RegExp(/.+\./g);
48+
return ~filename.search(isFilenameRegex) ?
49+
filename.match(isFilenameRegex)[0].replace('.', '') :
50+
filename;
51+
}
52+
53+
private _capitalizeTitle(title) {
54+
return title[0].toUpperCase() + this._trimFilename(title.slice(1));
55+
}
2956
}
3057

3158
function isNavigationWithinComponentView(oldUrl: string, newUrl: string) {
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<div class="docs-component-category-list">
2-
<md-card
3-
*ngFor="let category of docItems.getItemsInCategories()"
4-
class="docs-component-category-list-card"
5-
[routerLink]="['/categories/', category.id]">
6-
<md-card-title>{{category.name}}</md-card-title>
7-
<p class="docs-component-category-list-card-summary">{{category.summary}}</p>
8-
<docs-svg-viewer class="docs-component-category-list-card-image"
9-
[src]="'../../../assets/img/component-categories/' + category.id + '.svg'">
10-
</docs-svg-viewer>
11-
</md-card>
2+
<a
3+
*ngFor="let category of docItems.getItemsInCategories()"
4+
[routerLink]="['/components/category/', category.id]">
5+
<md-card class="docs-component-category-list-card">
6+
<md-card-title>{{category.name}}</md-card-title>
7+
<p class="docs-component-category-list-card-summary">{{category.summary}}</p>
8+
<docs-svg-viewer class="docs-component-category-list-card-image"
9+
[src]="'../../../assets/img/component-categories/' + category.id + '.svg'">
10+
</docs-svg-viewer>
11+
</md-card>
12+
</a>
1213
</div>

src/app/pages/component-category-list/component-category-list.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
2+
import {MaterialModule} from '@angular/material';
3+
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
4+
import {RouterTestingModule} from '@angular/router/testing';
5+
26
import {ComponentCategoryList, ComponentCategoryListModule} from './component-category-list';
37
import {DocsAppTestingModule} from '../../testing/testing-module';
8+
import {DocumentationItems} from '../../shared/documentation-items/documentation-items';
9+
import {ComponentPageTitle} from '../page-title/page-title';
410

511

612
describe('ComponentCategoryList', () => {
@@ -9,6 +15,7 @@ describe('ComponentCategoryList', () => {
915
beforeEach(async(() => {
1016
TestBed.configureTestingModule({
1117
imports: [ComponentCategoryListModule, DocsAppTestingModule],
18+
providers: [ComponentPageTitle],
1219
}).compileComponents();
1320
}));
1421

src/app/pages/component-page-header/component-page-header.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
<md-icon>menu</md-icon>
44
</button>
55

6-
<h1>{{getTitle()}} </h1>
6+
<h1 [attr.aria-alert]="getTitle()">{{getTitle()}} </h1>
77
</div>

src/app/pages/component-sidenav/component-sidenav.html

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<md-sidenav-container class="docs-component-viewer-sidenav-container">
2-
<md-sidenav #sidenav class="docs-component-viewer-sidenav"
2+
<md-sidenav aria-label="Component navigation" role="navigation" #sidenav class="docs-component-viewer-sidenav"
33
[opened]="!isScreenSmall()"
44
[mode]="isScreenSmall() ? 'over' : 'side'">
5-
<nav *ngFor="let category of docItems.getItemsInCategories()">
6-
<h3>{{category.name}}</h3>
5+
<nav [attr.aria-labelledby]="category.id + '-id'" *ngFor="let category of docItems.getItemsInCategories()">
6+
<h3 tabindex="0" id="{{category.id}}-id">{{category.name}}</h3>
77
<ul>
88
<li *ngFor="let component of category.items">
99
<a [routerLink]="['/components/', component.id]"
@@ -15,7 +15,7 @@ <h3>{{category.name}}</h3>
1515
</nav>
1616
</md-sidenav>
1717

18-
<div class="docs-component-sidenav-content">
18+
<div role="main" class="docs-component-sidenav-content">
1919
<component-page-header (toggleSidenav)="sidenav.toggle()"></component-page-header>
2020
<router-outlet></router-outlet>
2121
<app-footer></app-footer>
+11-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
<div class="docs-primary-header">
2-
<h1>Guides</h1>
3-
</div>
4-
5-
<md-nav-list class="docs-guide-list">
6-
<a md-list-item *ngFor="let guide of guideItems.getAllItems()"
1+
<div class="docs-guides-container" role="main">
2+
<div class="docs-primary-header">
3+
<h1 [attr.aria-alert]="'Guides'">Guides</h1>
4+
</div>
5+
<md-nav-list class="docs-guide-list">
6+
<a md-list-item
77
class="docs-guide-item"
8+
*ngFor="let guide of guideItems.getAllItems()"
89
[routerLink]="['/guide/', guide.id]">
9-
{{guide.name}}
10-
</a>
11-
</md-nav-list>
10+
{{guide.name}}
11+
</a>
12+
</md-nav-list>
13+
</div>
1214

1315
<app-footer></app-footer>

src/app/pages/guide-list/guide-list.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
:host {
1+
:host, .docs-guides-container {
22
display: flex;
33
flex-direction: column;
44
flex-grow: 1;

src/app/pages/guide-viewer/guide-viewer.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="docs-primary-header">
2-
<h1>{{guide.name}}</h1>
2+
<h1 [attr.aria-alert]="guide.name">{{guide.name}}</h1>
33
</div>
44

55
<doc-viewer class="docs-guide-content" [documentUrl]="guide.document"></doc-viewer>

src/app/pages/homepage/homepage.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ <h2> Material Design components for Angular</h2>
1010
</div>
1111
</header>
1212

13-
<div class="docs-homepage-promo">
13+
<div role="main" class="docs-homepage-promo">
1414
<div class="docs-homepage-row">
1515
<div class="docs-homepage-promo-img">
1616
<docs-svg-viewer

src/app/shared/example-viewer/example-viewer.html

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<div class="docs-example-viewer-title-spacer">{{exampleData?.title}}</div>
44

55
<button md-icon-button type="button" (click)="toggleSourceView()"
6+
aria-label="Toggle View Source"
67
[mdTooltip]="'View source'">
78
<md-icon>
89
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fit="" preserveAspectRatio="xMidYMid meet" focusable="false">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {async, TestBed, ComponentFixture} from '@angular/core/testing';
2+
import {ViewChild, Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
3+
import {MaterialModule} from '@angular/material';
4+
5+
import {FocusElementDirective} from './focus-element';
6+
7+
8+
@Component({
9+
selector: 'test-component',
10+
template: '<h1 focusElement>Whoa!</h1>'
11+
})
12+
class TestComponent {
13+
}
14+
15+
16+
describe('FocusElementDirective', () => {
17+
let fixture: ComponentFixture<TestComponent>;
18+
19+
beforeEach(async(() => {
20+
TestBed.configureTestingModule({
21+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
22+
declarations: [FocusElementDirective, TestComponent]
23+
});
24+
25+
fixture = TestBed.createComponent(TestComponent);
26+
}));
27+
28+
it('should set the tabindex of the host component to 0', () => {
29+
fixture.detectChanges();
30+
const tabindex = fixture
31+
.nativeElement
32+
.querySelector('h1')
33+
.getAttribute('tabindex');
34+
expect(tabindex).toEqual('0');
35+
});
36+
37+
it('should call focus to the host components nativeElement', () => {
38+
const el = fixture.nativeElement.querySelector('h1');
39+
spyOn(el, 'focus');
40+
fixture.detectChanges();
41+
expect(el).not.toBeNull();
42+
expect(el.focus).toHaveBeenCalled();
43+
});
44+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {Renderer, Directive, ElementRef} from '@angular/core';
2+
3+
4+
@Directive({
5+
selector: '[focusElement]'
6+
})
7+
8+
export class FocusElementDirective {
9+
10+
constructor(
11+
private _element: ElementRef,
12+
private _renderer: Renderer
13+
) { }
14+
15+
ngAfterViewInit() {
16+
// Add tabindex 0 so element is focusable by keyboard
17+
this
18+
._renderer
19+
.setElementAttribute(this._element.nativeElement, 'tabindex', '0');
20+
21+
// Focus host element after view loads
22+
// so screen readers will be alerted
23+
// the page has changed.
24+
this._element.nativeElement.focus();
25+
}
26+
}

src/app/shared/plunker/plunker-button.html

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<!-- TODO: change the template to be plunker icon -->
22
<div [mdTooltip]="isDisabled ? 'Building Plunker example...' : 'Edit in Plunker'">
33
<button md-icon-button type="button"
4+
aria-label="Edit in Plunker"
45
(click)="openPlunker()"
56
[disabled]="isDisabled">
67
<md-icon>

0 commit comments

Comments
 (0)