diff --git a/packages/mdc-linear-progress/README.md b/packages/mdc-linear-progress/README.md index 232aa987717..b6ca6d3cbf9 100644 --- a/packages/mdc-linear-progress/README.md +++ b/packages/mdc-linear-progress/README.md @@ -39,8 +39,9 @@ npm install @material/linear-progress ## Basic Usage ### HTML Structure + ```html -
+
@@ -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', () => {