@@ -52,6 +53,19 @@ npm install @material/linear-progress
```
+### Accessibility
+
+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:
+
+| Attribute | Description |
+| --------- | ----------- |
+| `aria-label` | Label indicating how the progress bar should be announced to the user. |
+| `aria-valuemin` | The minimum numeric value of the progress bar, which should always be `0`. |
+| `aria-valuemax` | The maximum numeric value of the progress bar, which should always be `1`. |
+| `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. |
+
+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.
+
### Styles
```scss
@import "@material/linear-progress/mdc-linear-progress";
@@ -93,11 +107,13 @@ The adapter for linear progress must provide the following functions, with corre
| Method Signature | Description |
| --- | --- |
| `addClass(className: string) => void` | Adds a class to the root element. |
+| `removeAttribute(attributeName: string) => void` | Removes the specified attribute from the root element. |
| `removeClass(className: string) => void` | Removes a class from the root element. |
| `hasClass(className: string) => boolean` | Returns boolean indicating whether the root element has a given class. |
| `forceLayout() => void` | Force-trigger a layout on the root element. This is needed to restart animations correctly. |
| `getPrimaryBar() => Element` | Returns the primary bar element. |
| `getBuffer() => Element` | Returns the buffer element. |
+| `setAttribute(attributeName: string, value: string) => void` | Sets the specified attribute on the root element. |
| `setStyle(el: Element, styleProperty: string, value: string) => void` | Sets the inline style on the given element. |
### MDCLinearProgressFoundation API
diff --git a/packages/mdc-linear-progress/adapter.ts b/packages/mdc-linear-progress/adapter.ts
index b21e3f36c96..46d23366897 100644
--- a/packages/mdc-linear-progress/adapter.ts
+++ b/packages/mdc-linear-progress/adapter.ts
@@ -35,5 +35,7 @@ export interface MDCLinearProgressAdapter {
getPrimaryBar(): HTMLElement | null;
hasClass(className: string): boolean;
removeClass(className: string): void;
+ removeAttribute(name: string): void;
+ setAttribute(name: string, value: string): void;
setStyle(el: HTMLElement, styleProperty: string, value: string): void;
}
diff --git a/packages/mdc-linear-progress/component.ts b/packages/mdc-linear-progress/component.ts
index 83cff002fb0..2122bc9dde4 100644
--- a/packages/mdc-linear-progress/component.ts
+++ b/packages/mdc-linear-progress/component.ts
@@ -63,7 +63,13 @@ export class MDCLinearProgress extends MDCComponent
getBuffer: () => this.root_.querySelector(MDCLinearProgressFoundation.strings.BUFFER_SELECTOR),
getPrimaryBar: () => this.root_.querySelector(MDCLinearProgressFoundation.strings.PRIMARY_BAR_SELECTOR),
hasClass: (className: string) => this.root_.classList.contains(className),
+ removeAttribute: (attributeName: string) => {
+ this.root_.removeAttribute(attributeName);
+ },
removeClass: (className: string) => this.root_.classList.remove(className),
+ setAttribute: (attributeName: string, value: string) => {
+ this.root_.setAttribute(attributeName, value);
+ },
setStyle: (el: HTMLElement, styleProperty: string, value: string) => el.style.setProperty(styleProperty, value),
};
return new MDCLinearProgressFoundation(adapter);
diff --git a/packages/mdc-linear-progress/constants.ts b/packages/mdc-linear-progress/constants.ts
index 8235127065c..339c105048e 100644
--- a/packages/mdc-linear-progress/constants.ts
+++ b/packages/mdc-linear-progress/constants.ts
@@ -28,6 +28,7 @@ export const cssClasses = {
};
export const strings = {
+ ARIA_VALUENOW: 'aria-valuenow',
BUFFER_SELECTOR: '.mdc-linear-progress__buffer',
PRIMARY_BAR_SELECTOR: '.mdc-linear-progress__primary-bar',
};
diff --git a/packages/mdc-linear-progress/foundation.ts b/packages/mdc-linear-progress/foundation.ts
index 35fbe32020c..71de2003f46 100644
--- a/packages/mdc-linear-progress/foundation.ts
+++ b/packages/mdc-linear-progress/foundation.ts
@@ -42,7 +42,9 @@ export class MDCLinearProgressFoundation extends MDCFoundation null,
getPrimaryBar: () => null,
hasClass: () => false,
+ removeAttribute: () => undefined,
removeClass: () => undefined,
+ setAttribute: () => undefined,
setStyle: () => undefined,
};
}
@@ -68,6 +70,7 @@ export class MDCLinearProgressFoundation extends MDCFoundation {
test('defaultAdapter returns a complete adapter implementation', () => {
verifyDefaultAdapter(MDCLinearProgressFoundation, [
- 'addClass', 'getPrimaryBar', 'forceLayout', 'getBuffer', 'hasClass', 'removeClass', 'setStyle',
+ 'addClass',
+ 'getPrimaryBar',
+ 'forceLayout',
+ 'getBuffer',
+ 'hasClass',
+ 'removeAttribute',
+ 'removeClass',
+ 'setAttribute',
+ 'setStyle',
]);
});
const setupTest = () => setupFoundationTest(MDCLinearProgressFoundation);
-test('#setDeterminate adds class and resets transforms', () => {
+test('#setDeterminate false adds class, resets transforms, and removes aria-valuenow', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(false);
const primaryBar = {};
@@ -60,6 +68,7 @@ test('#setDeterminate adds class and resets transforms', () => {
td.verify(mockAdapter.addClass(cssClasses.INDETERMINATE_CLASS));
td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(1)'));
td.verify(mockAdapter.setStyle(buffer, 'transform', 'scaleX(1)'));
+ td.verify(mockAdapter.removeAttribute(strings.ARIA_VALUENOW));
});
test('#setDeterminate removes class', () => {
@@ -87,6 +96,7 @@ test('#setDeterminate restores previous progress value after toggled from false
foundation.setDeterminate(false);
foundation.setDeterminate(true);
td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(0.123)'), {times: 2});
+ td.verify(mockAdapter.setAttribute(strings.ARIA_VALUENOW, '0.123'), {times: 2});
});
test('#setDeterminate restores previous buffer value after toggled from false to true', () => {
@@ -109,9 +119,10 @@ test('#setDeterminate updates progress value set while determinate is false afte
foundation.setProgress(0.123);
foundation.setDeterminate(true);
td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(0.123)'));
+ td.verify(mockAdapter.setAttribute(strings.ARIA_VALUENOW, '0.123'));
});
-test('#setProgress sets transform', () => {
+test('#setProgress sets transform and aria-valuenow', () => {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(false);
const primaryBar = {};
@@ -119,6 +130,7 @@ test('#setProgress sets transform', () => {
foundation.init();
foundation.setProgress(0.5);
td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(0.5)'));
+ td.verify(mockAdapter.setAttribute(strings.ARIA_VALUENOW, '0.5'));
});
test('#setProgress on indeterminate does nothing', () => {
@@ -129,6 +141,7 @@ test('#setProgress on indeterminate does nothing', () => {
foundation.init();
foundation.setProgress(0.5);
td.verify(mockAdapter.setStyle(), {times: 0, ignoreExtraArgs: true});
+ td.verify(mockAdapter.setAttribute(), {times: 0, ignoreExtraArgs: true});
});
test('#setBuffer sets transform', () => {
diff --git a/test/unit/mdc-linear-progress/mdc-linear-progress.test.js b/test/unit/mdc-linear-progress/mdc-linear-progress.test.js
index 58025e5cbdd..f6601a1744f 100644
--- a/test/unit/mdc-linear-progress/mdc-linear-progress.test.js
+++ b/test/unit/mdc-linear-progress/mdc-linear-progress.test.js
@@ -28,7 +28,8 @@ import {MDCLinearProgress, MDCLinearProgressFoundation} from '../../../packages/
function getFixture() {
return bel`
-
+
@@ -58,6 +59,7 @@ test('set indeterminate', () => {
component.determinate = false;
assert.isOk(root.classList.contains('mdc-linear-progress--indeterminate'));
+ assert.equal(undefined, root.getAttribute(MDCLinearProgressFoundation.strings.ARIA_VALUENOW));
});
test('set progress', () => {
@@ -66,6 +68,7 @@ test('set progress', () => {
component.progress = 0.5;
const primaryBar = root.querySelector(MDCLinearProgressFoundation.strings.PRIMARY_BAR_SELECTOR);
assert.equal('scaleX(0.5)', primaryBar.style.transform);
+ assert.equal('0.5', root.getAttribute(MDCLinearProgressFoundation.strings.ARIA_VALUENOW));
});
test('set buffer', () => {