Skip to content

Commit

Permalink
feat(list): Automatically use appropriate aria attribute for single s…
Browse files Browse the repository at this point in the history
…election list. (#4479)

(cherry picked from commit 077c809)
  • Loading branch information
abhiomkar authored and Kenneth G. Franqueiro committed Mar 26, 2019
1 parent b4b954b commit 3804743
Show file tree
Hide file tree
Showing 17 changed files with 186 additions and 38 deletions.
35 changes: 35 additions & 0 deletions demos/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,41 @@ <h3>Example - Interactive List</h3>
</a>
</nav>
</section>
<section>
<h3>Example - w/ Navigation list</h3>
<nav class="mdc-list demo-list" data-demo-interactive-list>
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page" tabindex="0">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox<i class="test-font--redact-prev-letter"></i></span>
</a>
<a class="mdc-list-item" href="#">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">star</i>
<span class="mdc-list-item__text">Star<i class="test-font--redact-prev-letter"></i></span>
</a>
<a class="mdc-list-item" href="#">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">send</i>
<span class="mdc-list-item__text">Sent Mail<i class="test-font--redact-prev-letter"></i></span>
</a>
<a class="mdc-list-item" href="#">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">drafts</i>
<span class="mdc-list-item__text">Drafts<i class="test-font--redact-prev-letter"></i></span>
</a>

<hr class="mdc-list-divider">
<h6 class="mdc-list-group__subheader">Labels</h6>
<a class="mdc-list-item" href="#">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">bookmark</i>
<span class="mdc-list-item__text">Family<i class="test-font--redact-prev-letter"></i></span>
</a>
<a class="mdc-list-item" href="#">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">bookmark</i>
<span class="mdc-list-item__text">Friends<i class="test-font--redact-prev-letter"></i></span>
</a>
<a class="mdc-list-item" href="#">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">bookmark</i>
<span class="mdc-list-item__text">Work<i class="test-font--redact-prev-letter"></i></span>
</a>
</section>
</section>
</div>
</main>
Expand Down
14 changes: 7 additions & 7 deletions packages/mdc-drawer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ npm install @material/drawer
<aside class="mdc-drawer">
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox</span>
</a>
Expand Down Expand Up @@ -95,7 +95,7 @@ const drawer = MDCDrawer.attachTo(document.querySelector('.mdc-drawer'));
<aside class="mdc-drawer">
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox</span>
</a>
Expand Down Expand Up @@ -143,7 +143,7 @@ Drawers can contain a header element which will not scroll with the rest of the
</div>
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox</span>
</a>
Expand All @@ -169,7 +169,7 @@ Dismissible drawers are by default hidden off screen, and can slide into view. D
<aside class="mdc-drawer mdc-drawer--dismissible">
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox</span>
</a>
Expand Down Expand Up @@ -206,7 +206,7 @@ In the following example, the `mdc-drawer__content` and `main-content` elements
<aside class="mdc-drawer mdc-drawer--dismissible">
<div class="mdc-drawer__content">
<div class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox</span>
</a>
Expand Down Expand Up @@ -258,7 +258,7 @@ In cases where the drawer appears below the top app bar you will want to follow
<aside class="mdc-drawer mdc-drawer--dismissible mdc-top-app-bar--fixed-adjust">
<div class="mdc-drawer__content">
<div class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox</span>
</a>
Expand Down Expand Up @@ -333,7 +333,7 @@ Modal drawers are elevated above most of the app's UI and don't affect the scree
<aside class="mdc-drawer mdc-drawer--modal">
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox</span>
</a>
Expand Down
12 changes: 8 additions & 4 deletions packages/mdc-list/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ list.singleSelection = true;
#### Pre-selected list item

When rendering the list with a pre-selected list item, the list item that needs to be selected should contain
the `mdc-list-item--selected` or `mdc-list-item--activated` class and `aria-selected="true"` attribute before
creating the list.
the `mdc-list-item--selected` or `mdc-list-item--activated` class before creating the list. Please see
[Accessibility](#Accessibility) section for appropriate aria attributes.

```html
<ul id="my-list" class="mdc-list" role="listbox">
Expand Down Expand Up @@ -424,6 +424,9 @@ Use `role="listbox"` only for single selection list, without this role the `ul`
Do not use `aria-orientation` attribute for standard list (i.e., `role="list"`), use component's `vertical` property to set the orientation
to vertical.

Single selection list supports `aria-selected` and `aria-current` attributes. List automatically detects the presence of these attributes
and sets it to next selected list item based on which ARIA attribute you use (i.e., `aria-selected` or `aria-current`). Please see WAI-ARIA [aria-current](https://www.w3.org/TR/wai-aria-1.1/#aria-current) article for recommended usage and available attribute values.

As the user navigates through the list, any `button` and `a` elements within the list will receive `tabindex="-1"` when
the list item is not focused. When the list item receives focus, the aforementioned elements will receive
`tabIndex="0"`. This allows for the user to tab through list item elements and then tab to the first element after the
Expand Down Expand Up @@ -502,10 +505,10 @@ these should also receive `tabIndex="-1"`.
#### Setup in `singleSelection()`

When implementing a component that will use the single selection variant, the HTML should be modified to include
the `aria-selected` attribute, the `mdc-list-item--selected` or `mdc-list-item--activated` class should be added,
the `mdc-list-item--selected` or `mdc-list-item--activated` class name,
and the `tabindex` of the selected element should be `0`. The first list item should have the `tabindex` updated
to `-1`. The foundation method `setSelectedIndex()` should be called with the initially selected element immediately
after the foundation is instantiated.
after the foundation is instantiated. Please see [Accessibility](#Accessibility) section for appropriate aria attributes.

```html
<ul id="my-list" class="mdc-list">
Expand All @@ -529,6 +532,7 @@ Method Signature | Description
`getListItemCount() => Number` | Returns the total number of list items (elements with `mdc-list-item` class) that are direct children of the `root_` element.
`getFocusedElementIndex() => Number` | Returns the `index` value of the currently focused element.
`getListItemIndex(ele: Element) => Number` | Returns the `index` value of the provided `ele` element.
`getAttributeForElementIndex(index: number, attribute: string) => string | null` | Returns the attribute value of list item at given `index`.
`setAttributeForElementIndex(index: Number, attr: String, value: String) => void` | Sets the `attr` attribute to `value` for the list item at `index`.
`addClassForElementIndex(index: Number, className: String) => void` | Adds the `className` class to the list item at `index`.
`removeClassForElementIndex(index: Number, className: String) => void` | Removes the `className` class to the list item at `index`.
Expand Down
5 changes: 5 additions & 0 deletions packages/mdc-list/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
* https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md
*/
export interface MDCListAdapter {
/**
* Returns the attribute value of list item at given `index`.
*/
getAttributeForElementIndex(index: number, attr: string): string | null;

getListItemCount(): number;

getFocusedElementIndex(): number;
Expand Down
1 change: 1 addition & 0 deletions packages/mdc-list/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export class MDCList extends MDCComponent<MDCListFoundation> {
element.focus();
}
},
getAttributeForElementIndex: (index, attr) => this.listElements[index].getAttribute(attr),
getFocusedElementIndex: () => this.listElements.indexOf(document.activeElement!),
getListItemCount: () => this.listElements.length,
hasCheckboxAtIndex: (index) => {
Expand Down
7 changes: 6 additions & 1 deletion packages/mdc-list/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const strings = {
ARIA_CHECKED: 'aria-checked',
ARIA_CHECKED_CHECKBOX_SELECTOR: '[role="checkbox"][aria-checked="true"]',
ARIA_CHECKED_RADIO_SELECTOR: '[role="radio"][aria-checked="true"]',
ARIA_CURRENT: 'aria-current',
ARIA_ORIENTATION: 'aria-orientation',
ARIA_ORIENTATION_HORIZONTAL: 'horizontal',
ARIA_ROLE_CHECKBOX_SELECTOR: '[role="checkbox"]',
Expand All @@ -53,4 +54,8 @@ const strings = {
RADIO_SELECTOR: 'input[type="radio"]:not(:disabled)',
};

export {strings, cssClasses};
const numbers = {
UNSET_INDEX: -1,
};

export {strings, cssClasses, numbers};
57 changes: 43 additions & 14 deletions packages/mdc-list/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import {MDCFoundation} from '@material/base/foundation';
import {MDCListAdapter} from './adapter';
import {cssClasses, strings} from './constants';
import {cssClasses, numbers, strings} from './constants';
import {MDCListIndex} from './types';

const ELEMENTS_KEY_ALLOWED_IN = ['input', 'button', 'textarea', 'select'];
Expand All @@ -41,10 +41,15 @@ export class MDCListFoundation extends MDCFoundation<MDCListAdapter> {
return cssClasses;
}

static get numbers() {
return numbers;
}

static get defaultAdapter(): MDCListAdapter {
return {
addClassForElementIndex: () => undefined,
focusItemAtIndex: () => undefined,
getAttributeForElementIndex: () => null,
getFocusedElementIndex: () => 0,
getListItemCount: () => 0,
hasCheckboxAtIndex: () => false,
Expand All @@ -62,9 +67,10 @@ export class MDCListFoundation extends MDCFoundation<MDCListAdapter> {
private wrapFocus_ = false;
private isVertical_ = true;
private isSingleSelectionList_ = false;
private selectedIndex_: MDCListIndex = -1;
private focusedItemIndex_ = -1;
private selectedIndex_: MDCListIndex = numbers.UNSET_INDEX;
private focusedItemIndex_ = numbers.UNSET_INDEX;
private useActivatedClass_ = false;
private ariaCurrentAttrValue_: string | null = null;
private isCheckboxList_ = false;
private isRadioList_ = false;

Expand Down Expand Up @@ -172,8 +178,8 @@ export class MDCListFoundation extends MDCFoundation<MDCListAdapter> {
const isSpace = evt.key === 'Space' || evt.keyCode === 32;

let currentIndex = this.adapter_.getFocusedElementIndex();
let nextIndex = -1;
if (currentIndex === -1) {
let nextIndex = numbers.UNSET_INDEX;
if (currentIndex === numbers.UNSET_INDEX) {
currentIndex = listItemIndex;
if (currentIndex < 0) {
// If this event doesn't have a mdc-list-item ancestor from the
Expand Down Expand Up @@ -223,7 +229,7 @@ export class MDCListFoundation extends MDCFoundation<MDCListAdapter> {
* Click handler for the list.
*/
handleClick(index: number, toggleCheckbox: boolean) {
if (index === -1) {
if (index === numbers.UNSET_INDEX) {
return;
}

Expand Down Expand Up @@ -298,29 +304,52 @@ export class MDCListFoundation extends MDCFoundation<MDCListAdapter> {
}

private setSingleSelectionAtIndex_(index: number) {
if (this.selectedIndex_ === index) {
return;
}

let selectedClassName = cssClasses.LIST_ITEM_SELECTED_CLASS;
if (this.useActivatedClass_) {
selectedClassName = cssClasses.LIST_ITEM_ACTIVATED_CLASS;
}

if (this.selectedIndex_ >= 0 && this.selectedIndex_ !== index) {
if (this.selectedIndex_ !== numbers.UNSET_INDEX) {
this.adapter_.removeClassForElementIndex(this.selectedIndex_ as number, selectedClassName);
this.adapter_.setAttributeForElementIndex(this.selectedIndex_ as number, strings.ARIA_SELECTED, 'false');
}

this.adapter_.addClassForElementIndex(index, selectedClassName);
this.adapter_.setAttributeForElementIndex(index, strings.ARIA_SELECTED, 'true');
this.setAriaForSingleSelectionAtIndex_(index);

this.selectedIndex_ = index;
}

/**
* Sets aria attribute for single selection at given index.
*/
private setAriaForSingleSelectionAtIndex_(index: number) {
// Detect the presence of aria-current and get the value only during list initialization when it is in unset state.
if (this.selectedIndex_ === numbers.UNSET_INDEX) {
this.ariaCurrentAttrValue_ =
this.adapter_.getAttributeForElementIndex(index, strings.ARIA_CURRENT);
}

const isAriaCurrent = this.ariaCurrentAttrValue_ !== null;
const ariaAttribute = isAriaCurrent ? strings.ARIA_CURRENT : strings.ARIA_SELECTED;

if (this.selectedIndex_ !== numbers.UNSET_INDEX) {
this.adapter_.setAttributeForElementIndex(this.selectedIndex_ as number, ariaAttribute, 'false');
}

const ariaAttributeValue = isAriaCurrent ? this.ariaCurrentAttrValue_ : 'true';
this.adapter_.setAttributeForElementIndex(index, ariaAttribute, ariaAttributeValue as string);
}

/**
* Toggles radio at give index. Radio doesn't change the checked state if it is already checked.
*/
private setRadioAtIndex_(index: number) {
this.adapter_.setCheckedCheckboxOrRadioAtIndex(index, true);

if (this.selectedIndex_ >= 0) {
if (this.selectedIndex_ !== numbers.UNSET_INDEX) {
this.adapter_.setAttributeForElementIndex(this.selectedIndex_ as number, strings.ARIA_CHECKED, 'false');
}

Expand All @@ -344,7 +373,7 @@ export class MDCListFoundation extends MDCFoundation<MDCListAdapter> {
}

private setTabindexAtIndex_(index: number) {
if (this.focusedItemIndex_ === -1 && index !== 0) {
if (this.focusedItemIndex_ === numbers.UNSET_INDEX && index !== 0) {
// If no list item was selected set first list item's tabindex to -1.
// Generally, tabindex is set to 0 on first list item of list that has no preselected items.
this.adapter_.setAttributeForElementIndex(0, 'tabindex', '-1');
Expand All @@ -366,7 +395,7 @@ export class MDCListFoundation extends MDCFoundation<MDCListAdapter> {
let targetIndex = 0;

if (this.isSelectableList_()) {
if (typeof this.selectedIndex_ === 'number' && this.selectedIndex_ !== -1) {
if (typeof this.selectedIndex_ === 'number' && this.selectedIndex_ !== numbers.UNSET_INDEX) {
targetIndex = this.selectedIndex_;
} else if (isNumberArray(this.selectedIndex_) && this.selectedIndex_.length > 0) {
targetIndex = this.selectedIndex_.reduce((currentIndex, minIndex) => Math.min(currentIndex, minIndex));
Expand Down Expand Up @@ -421,7 +450,7 @@ export class MDCListFoundation extends MDCFoundation<MDCListAdapter> {
this.adapter_.setAttributeForElementIndex(index, strings.ARIA_CHECKED, isChecked ? 'true' : 'false');

// If none of the checkbox items are selected and selectedIndex is not initialized then provide a default value.
let selectedIndexes = this.selectedIndex_ === -1 ? [] : (this.selectedIndex_ as number[]).slice();
let selectedIndexes = this.selectedIndex_ === numbers.UNSET_INDEX ? [] : (this.selectedIndex_ as number[]).slice();

if (isChecked) {
selectedIndexes.push(index);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
</header>
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true" tabindex="0">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page" tabindex="0">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox<i class="test-font--redact-prev-letter"></i></span>
</a>
Expand Down
2 changes: 1 addition & 1 deletion test/screenshot/spec/mdc-drawer/classes/dismissible.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
</header>
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true" tabindex="0">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page" tabindex="0">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox<i class="test-font--redact-prev-letter"></i></span>
</a>
Expand Down
2 changes: 1 addition & 1 deletion test/screenshot/spec/mdc-drawer/classes/modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
</header>
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true" tabindex="0">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page" tabindex="0">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox<i class="test-font--redact-prev-letter"></i></span>
</a>
Expand Down
2 changes: 1 addition & 1 deletion test/screenshot/spec/mdc-drawer/classes/permanent.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
</header>
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true" tabindex="0">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page" tabindex="0">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox<i class="test-font--redact-prev-letter"></i></span>
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
</header>
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true" tabindex="0">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page" tabindex="0">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox<i class="test-font--redact-prev-letter"></i></span>
</a>
Expand Down
2 changes: 1 addition & 1 deletion test/screenshot/spec/mdc-drawer/mixins/fill-color.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
</header>
<div class="mdc-drawer__content">
<nav class="mdc-list">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-selected="true" tabindex="0">
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page" tabindex="0">
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">inbox</i>
<span class="mdc-list-item__text">Inbox<i class="test-font--redact-prev-letter"></i></span>
</a>
Expand Down
Loading

0 comments on commit 3804743

Please sign in to comment.