Skip to content

Commit

Permalink
fix(linear-progress) support aria attributes
Browse files Browse the repository at this point in the history
Adds aria attributes to MDC Linear progress to support screen readers.

BREAKING CHANGE: Adds new adapter methods to MDCLinearProgressAdapter.
  • Loading branch information
rbuckheit committed Nov 11, 2019
1 parent afe0dd1 commit d1ce40e
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 6 deletions.
18 changes: 17 additions & 1 deletion packages/mdc-linear-progress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ npm install @material/linear-progress
## Basic Usage

### HTML Structure

```html
<div role="progressbar" class="mdc-linear-progress">
<div role="progressbar" class="mdc-linear-progress" aria-label="Example Progress Bar" aria-valuemin="0" aria-valuemax="1" aria-valuenow="0">
<div class="mdc-linear-progress__buffering-dots"></div>
<div class="mdc-linear-progress__buffer"></div>
<div class="mdc-linear-progress__bar mdc-linear-progress__primary-bar">
Expand All @@ -52,6 +53,19 @@ npm install @material/linear-progress
</div>
```

### 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";
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions packages/mdc-linear-progress/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions packages/mdc-linear-progress/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ export class MDCLinearProgress extends MDCComponent<MDCLinearProgressFoundation>
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);
Expand Down
1 change: 1 addition & 0 deletions packages/mdc-linear-progress/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
5 changes: 5 additions & 0 deletions packages/mdc-linear-progress/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export class MDCLinearProgressFoundation extends MDCFoundation<MDCLinearProgress
getBuffer: () => null,
getPrimaryBar: () => null,
hasClass: () => false,
removeAttribute: () => undefined,
removeClass: () => undefined,
setAttribute: () => undefined,
setStyle: () => undefined,
};
}
Expand All @@ -68,6 +70,7 @@ export class MDCLinearProgressFoundation extends MDCFoundation<MDCLinearProgress

if (this.isDeterminate_) {
this.adapter_.removeClass(cssClasses.INDETERMINATE_CLASS);
this.adapter_.setAttribute(strings.ARIA_VALUENOW, this.progress_.toString());
this.setScale_(this.adapter_.getPrimaryBar(), this.progress_);
this.setScale_(this.adapter_.getBuffer(), this.buffer_);
} else {
Expand All @@ -83,6 +86,7 @@ export class MDCLinearProgressFoundation extends MDCFoundation<MDCLinearProgress
}

this.adapter_.addClass(cssClasses.INDETERMINATE_CLASS);
this.adapter_.removeAttribute(strings.ARIA_VALUENOW);
this.setScale_(this.adapter_.getPrimaryBar(), 1);
this.setScale_(this.adapter_.getBuffer(), 1);
}
Expand All @@ -92,6 +96,7 @@ export class MDCLinearProgressFoundation extends MDCFoundation<MDCLinearProgress
this.progress_ = value;
if (this.isDeterminate_) {
this.setScale_(this.adapter_.getPrimaryBar(), value);
this.adapter_.setAttribute(strings.ARIA_VALUENOW, value.toString());
}
}

Expand Down
21 changes: 17 additions & 4 deletions test/unit/mdc-linear-progress/foundation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {setupFoundationTest} from '../helpers/setup';
import {verifyDefaultAdapter} from '../helpers/foundation';
import {MDCLinearProgressFoundation} from '../../../packages/mdc-linear-progress/foundation';

const {cssClasses} = MDCLinearProgressFoundation;
const {cssClasses, strings} = MDCLinearProgressFoundation;

suite('MDCLinearProgressFoundation');

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

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 = {};
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand All @@ -109,16 +119,18 @@ 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 = {};
td.when(mockAdapter.getPrimaryBar()).thenReturn(primaryBar);
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', () => {
Expand All @@ -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', () => {
Expand Down
5 changes: 4 additions & 1 deletion test/unit/mdc-linear-progress/mdc-linear-progress.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {MDCLinearProgress, MDCLinearProgressFoundation} from '../../../packages/

function getFixture() {
return bel`
<div role="progressbar" class="mdc-linear-progress">
<div role="progressbar" class="mdc-linear-progress" aria-label="Unit Test Progress Bar" aria-valuemin="0"
aria-valuemax="1" aria-valuenow="0">
<div class="mdc-linear-progress__buffering-dots"></div>
<div class="mdc-linear-progress__buffer"></div>
<div class="mdc-linear-progress__bar mdc-linear-progress__primary-bar">
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand Down

0 comments on commit d1ce40e

Please sign in to comment.