Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit d1ce40e

Browse files
committed
fix(linear-progress) support aria attributes
Adds aria attributes to MDC Linear progress to support screen readers. BREAKING CHANGE: Adds new adapter methods to MDCLinearProgressAdapter.
1 parent afe0dd1 commit d1ce40e

File tree

7 files changed

+48
-6
lines changed

7 files changed

+48
-6
lines changed

packages/mdc-linear-progress/README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ npm install @material/linear-progress
3939
## Basic Usage
4040

4141
### HTML Structure
42+
4243
```html
43-
<div role="progressbar" class="mdc-linear-progress">
44+
<div role="progressbar" class="mdc-linear-progress" aria-label="Example Progress Bar" aria-valuemin="0" aria-valuemax="1" aria-valuenow="0">
4445
<div class="mdc-linear-progress__buffering-dots"></div>
4546
<div class="mdc-linear-progress__buffer"></div>
4647
<div class="mdc-linear-progress__bar mdc-linear-progress__primary-bar">
@@ -52,6 +53,19 @@ npm install @material/linear-progress
5253
</div>
5354
```
5455

56+
### Accessibility
57+
58+
Progress bars conform to the [WAI-ARIA Progressbar Specification](https://www.w3.org/TR/wai-aria/#progressbar). The supported ARIA attributes for this progress bar are:
59+
60+
| Attribute | Description |
61+
| --------- | ----------- |
62+
| `aria-label` | Label indicating how the progress bar should be announced to the user. |
63+
| `aria-valuemin` | The minimum numeric value of the progress bar, which should always be `0`. |
64+
| `aria-valuemax` | The maximum numeric value of the progress bar, which should always be `1`. |
65+
| `aria-valuenow` | A numeric value between `aria-valuemin` and `aria-valuemax` indicating the progress value of the primary progress bar. This attribute is removed in indeterminate progress bars. |
66+
67+
Note that `aria-label`, `aria-valuemin`, and `aria-valuemax` are static and must be configured in the HTML. `aria-valuenow` is updated dynamically by the foundation when the progress value is updated in determinate progress bars.
68+
5569
### Styles
5670
```scss
5771
@import "@material/linear-progress/mdc-linear-progress";
@@ -93,11 +107,13 @@ The adapter for linear progress must provide the following functions, with corre
93107
| Method Signature | Description |
94108
| --- | --- |
95109
| `addClass(className: string) => void` | Adds a class to the root element. |
110+
| `removeAttribute(attributeName: string) => void` | Removes the specified attribute from the root element. |
96111
| `removeClass(className: string) => void` | Removes a class from the root element. |
97112
| `hasClass(className: string) => boolean` | Returns boolean indicating whether the root element has a given class. |
98113
| `forceLayout() => void` | Force-trigger a layout on the root element. This is needed to restart animations correctly. |
99114
| `getPrimaryBar() => Element` | Returns the primary bar element. |
100115
| `getBuffer() => Element` | Returns the buffer element. |
116+
| `setAttribute(attributeName: string, value: string) => void` | Sets the specified attribute on the root element. |
101117
| `setStyle(el: Element, styleProperty: string, value: string) => void` | Sets the inline style on the given element. |
102118

103119
### MDCLinearProgressFoundation API

packages/mdc-linear-progress/adapter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,7 @@ export interface MDCLinearProgressAdapter {
3535
getPrimaryBar(): HTMLElement | null;
3636
hasClass(className: string): boolean;
3737
removeClass(className: string): void;
38+
removeAttribute(name: string): void;
39+
setAttribute(name: string, value: string): void;
3840
setStyle(el: HTMLElement, styleProperty: string, value: string): void;
3941
}

packages/mdc-linear-progress/component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ export class MDCLinearProgress extends MDCComponent<MDCLinearProgressFoundation>
6363
getBuffer: () => this.root_.querySelector(MDCLinearProgressFoundation.strings.BUFFER_SELECTOR),
6464
getPrimaryBar: () => this.root_.querySelector(MDCLinearProgressFoundation.strings.PRIMARY_BAR_SELECTOR),
6565
hasClass: (className: string) => this.root_.classList.contains(className),
66+
removeAttribute: (attributeName: string) => this.root_.removeAttribute(attributeName),
6667
removeClass: (className: string) => this.root_.classList.remove(className),
68+
setAttribute: (attributeName: string, value: string) => this.root_.setAttribute(attributeName, value),
6769
setStyle: (el: HTMLElement, styleProperty: string, value: string) => el.style.setProperty(styleProperty, value),
6870
};
6971
return new MDCLinearProgressFoundation(adapter);

packages/mdc-linear-progress/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const cssClasses = {
2828
};
2929

3030
export const strings = {
31+
ARIA_VALUENOW: 'aria-valuenow',
3132
BUFFER_SELECTOR: '.mdc-linear-progress__buffer',
3233
PRIMARY_BAR_SELECTOR: '.mdc-linear-progress__primary-bar',
3334
};

packages/mdc-linear-progress/foundation.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ export class MDCLinearProgressFoundation extends MDCFoundation<MDCLinearProgress
4242
getBuffer: () => null,
4343
getPrimaryBar: () => null,
4444
hasClass: () => false,
45+
removeAttribute: () => undefined,
4546
removeClass: () => undefined,
47+
setAttribute: () => undefined,
4648
setStyle: () => undefined,
4749
};
4850
}
@@ -68,6 +70,7 @@ export class MDCLinearProgressFoundation extends MDCFoundation<MDCLinearProgress
6870

6971
if (this.isDeterminate_) {
7072
this.adapter_.removeClass(cssClasses.INDETERMINATE_CLASS);
73+
this.adapter_.setAttribute(strings.ARIA_VALUENOW, this.progress_.toString());
7174
this.setScale_(this.adapter_.getPrimaryBar(), this.progress_);
7275
this.setScale_(this.adapter_.getBuffer(), this.buffer_);
7376
} else {
@@ -83,6 +86,7 @@ export class MDCLinearProgressFoundation extends MDCFoundation<MDCLinearProgress
8386
}
8487

8588
this.adapter_.addClass(cssClasses.INDETERMINATE_CLASS);
89+
this.adapter_.removeAttribute(strings.ARIA_VALUENOW);
8690
this.setScale_(this.adapter_.getPrimaryBar(), 1);
8791
this.setScale_(this.adapter_.getBuffer(), 1);
8892
}
@@ -92,6 +96,7 @@ export class MDCLinearProgressFoundation extends MDCFoundation<MDCLinearProgress
9296
this.progress_ = value;
9397
if (this.isDeterminate_) {
9498
this.setScale_(this.adapter_.getPrimaryBar(), value);
99+
this.adapter_.setAttribute(strings.ARIA_VALUENOW, value.toString());
95100
}
96101
}
97102

test/unit/mdc-linear-progress/foundation.test.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {setupFoundationTest} from '../helpers/setup';
2828
import {verifyDefaultAdapter} from '../helpers/foundation';
2929
import {MDCLinearProgressFoundation} from '../../../packages/mdc-linear-progress/foundation';
3030

31-
const {cssClasses} = MDCLinearProgressFoundation;
31+
const {cssClasses, strings} = MDCLinearProgressFoundation;
3232

3333
suite('MDCLinearProgressFoundation');
3434

@@ -42,13 +42,21 @@ test('exports cssClasses', () => {
4242

4343
test('defaultAdapter returns a complete adapter implementation', () => {
4444
verifyDefaultAdapter(MDCLinearProgressFoundation, [
45-
'addClass', 'getPrimaryBar', 'forceLayout', 'getBuffer', 'hasClass', 'removeClass', 'setStyle',
45+
'addClass',
46+
'getPrimaryBar',
47+
'forceLayout',
48+
'getBuffer',
49+
'hasClass',
50+
'removeAttribute',
51+
'removeClass',
52+
'setAttribute',
53+
'setStyle',
4654
]);
4755
});
4856

4957
const setupTest = () => setupFoundationTest(MDCLinearProgressFoundation);
5058

51-
test('#setDeterminate adds class and resets transforms', () => {
59+
test('#setDeterminate false adds class, resets transforms, and removes aria-valuenow', () => {
5260
const {foundation, mockAdapter} = setupTest();
5361
td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(false);
5462
const primaryBar = {};
@@ -60,6 +68,7 @@ test('#setDeterminate adds class and resets transforms', () => {
6068
td.verify(mockAdapter.addClass(cssClasses.INDETERMINATE_CLASS));
6169
td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(1)'));
6270
td.verify(mockAdapter.setStyle(buffer, 'transform', 'scaleX(1)'));
71+
td.verify(mockAdapter.removeAttribute(strings.ARIA_VALUENOW));
6372
});
6473

6574
test('#setDeterminate removes class', () => {
@@ -87,6 +96,7 @@ test('#setDeterminate restores previous progress value after toggled from false
8796
foundation.setDeterminate(false);
8897
foundation.setDeterminate(true);
8998
td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(0.123)'), {times: 2});
99+
td.verify(mockAdapter.setAttribute(strings.ARIA_VALUENOW, '0.123'), {times: 2});
90100
});
91101

92102
test('#setDeterminate restores previous buffer value after toggled from false to true', () => {
@@ -109,16 +119,18 @@ test('#setDeterminate updates progress value set while determinate is false afte
109119
foundation.setProgress(0.123);
110120
foundation.setDeterminate(true);
111121
td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(0.123)'));
122+
td.verify(mockAdapter.setAttribute(strings.ARIA_VALUENOW, '0.123'));
112123
});
113124

114-
test('#setProgress sets transform', () => {
125+
test('#setProgress sets transform and aria-valuenow', () => {
115126
const {foundation, mockAdapter} = setupTest();
116127
td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(false);
117128
const primaryBar = {};
118129
td.when(mockAdapter.getPrimaryBar()).thenReturn(primaryBar);
119130
foundation.init();
120131
foundation.setProgress(0.5);
121132
td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(0.5)'));
133+
td.verify(mockAdapter.setAttribute(strings.ARIA_VALUENOW, '0.5'));
122134
});
123135

124136
test('#setProgress on indeterminate does nothing', () => {
@@ -129,6 +141,7 @@ test('#setProgress on indeterminate does nothing', () => {
129141
foundation.init();
130142
foundation.setProgress(0.5);
131143
td.verify(mockAdapter.setStyle(), {times: 0, ignoreExtraArgs: true});
144+
td.verify(mockAdapter.setAttribute(), {times: 0, ignoreExtraArgs: true});
132145
});
133146

134147
test('#setBuffer sets transform', () => {

test/unit/mdc-linear-progress/mdc-linear-progress.test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import {MDCLinearProgress, MDCLinearProgressFoundation} from '../../../packages/
2828

2929
function getFixture() {
3030
return bel`
31-
<div role="progressbar" class="mdc-linear-progress">
31+
<div role="progressbar" class="mdc-linear-progress" aria-label="Unit Test Progress Bar" aria-valuemin="0"
32+
aria-valuemax="1" aria-valuenow="0">
3233
<div class="mdc-linear-progress__buffering-dots"></div>
3334
<div class="mdc-linear-progress__buffer"></div>
3435
<div class="mdc-linear-progress__bar mdc-linear-progress__primary-bar">
@@ -58,6 +59,7 @@ test('set indeterminate', () => {
5859

5960
component.determinate = false;
6061
assert.isOk(root.classList.contains('mdc-linear-progress--indeterminate'));
62+
assert.equal(undefined, root.getAttribute(MDCLinearProgressFoundation.strings.ARIA_VALUENOW));
6163
});
6264

6365
test('set progress', () => {
@@ -66,6 +68,7 @@ test('set progress', () => {
6668
component.progress = 0.5;
6769
const primaryBar = root.querySelector(MDCLinearProgressFoundation.strings.PRIMARY_BAR_SELECTOR);
6870
assert.equal('scaleX(0.5)', primaryBar.style.transform);
71+
assert.equal('0.5', root.getAttribute(MDCLinearProgressFoundation.strings.ARIA_VALUENOW));
6972
});
7073

7174
test('set buffer', () => {

0 commit comments

Comments
 (0)