Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(list): Add arrow key a11y support. #2871

Merged
merged 15 commits into from
Jun 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 91 additions & 63 deletions demos/list.html

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
"mdc-icon-button",
"mdc-icon-toggle",
"mdc-line-ripple",
"mdc-list",
"mdc-menu",
"mdc-notched-outline",
"mdc-radio",
Expand Down
3 changes: 3 additions & 0 deletions packages/material-components-web/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as iconButton from '@material/icon-button/index';
import * as iconToggle from '@material/icon-toggle/index';
import * as linearProgress from '@material/linear-progress/index';
import * as lineRipple from '@material/line-ripple/index';
import * as list from '@material/list/index';
import * as menu from '@material/menu/index';
import * as notchedOutline from '@material/notched-outline/index';
import * as radio from '@material/radio/index';
Expand Down Expand Up @@ -55,6 +56,7 @@ autoInit.register('MDCIconButtonToggle', iconButton.MDCIconButtonToggle);
autoInit.register('MDCIconToggle', iconToggle.MDCIconToggle);
autoInit.register('MDCLineRipple', lineRipple.MDCLineRipple);
autoInit.register('MDCLinearProgress', linearProgress.MDCLinearProgress);
autoInit.register('MDCList', list.MDCList);
autoInit.register('MDCNotchedOutline', notchedOutline.MDCNotchedOutline);
autoInit.register('MDCRadio', radio.MDCRadio);
autoInit.register('MDCSnackbar', snackbar.MDCSnackbar);
Expand Down Expand Up @@ -82,6 +84,7 @@ export {
iconToggle,
lineRipple,
linearProgress,
list,
menu,
notchedOutline,
radio,
Expand Down
143 changes: 96 additions & 47 deletions packages/mdc-list/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ path: /catalog/lists/
</a>
</div>-->

MDC List provides styles which implement [Material Design Lists](https://material.io/go/design-lists) -
"A single continuous column of tessellated subdivisions of equal width." Both single-line and two-line lists are
supported (with three-line lists [planned](https://github.com/material-components/material-components-web/issues/31)).
MDC Lists are designed to be accessible and RTL aware.
Lists are continuous, vertical indexes of text or images.

## Design & API Documentation

<ul class="icon-list">
<li class="icon-list-item icon-list-item--spec">
<a href="https://material.io/go/design-lists">Material Design guidelines: Lists</a>
<a href="https://material.io/design/components/lists.html">Material Design guidelines: Lists</a>
</li>
<li class="icon-list-item icon-list-item--link">
<a href="https://material-components.github.io/material-components-web-catalog/#/component/list">Demo</a>
Expand All @@ -37,29 +34,28 @@ MDC Lists are designed to be accessible and RTL aware.
npm install @material/list
```

## Usage
## Basic Usage

### HTML Structure

#### Single-Line List
A basic list consists simply of the list itself, and list items taking up one line.

List items (rows) can contain primary and secondary actions. Lists items can contain 1 supporting graphic tile and/or 1 metadata tile that are positioned at the start and end of the list item, respectively.


```html
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">Single-line item</li>
<li class="mdc-list-item">Single-line item</li>
<li class="mdc-list-item">Single-line item</li>
</ul>
```

#### Two-Line List
While in theory you can add any number of "lines" to a list item, you can use the `mdc-list--two-line` combined with some extra markup around the text to style a list in the two-line list style as defined by [the spec](https://material.io/go/design-lists#lists-specs) (see "Two-line lists").
## Variants

### Two-Line List

You can use the `mdc-list--two-line` combined with some extra markup around the text to style a list
in the double line list style as defined by
[the spec](https://material.io/design/components/lists.html#specs) (see "Double line").

```html
<ul class="mdc-list mdc-list--two-line">
<ul class="mdc-list mdc-list--two-line" aria-orientation="vertical">
<li class="mdc-list-item">
<span class="mdc-list-item__text">
First-line text
Expand Down Expand Up @@ -87,32 +83,34 @@ While in theory you can add any number of "lines" to a list item, you can use th
</ul>
```

#### List Groups
### List Groups

Multiple related lists can be grouped together using the `mdc-list-group` class on a containing element.

```html
<div class="mdc-list-group">
<h3 class="mdc-list-group__subheader">List 1</h3>
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">line item</li>
<li class="mdc-list-item">line item</li>
<li class="mdc-list-item">line item</li>
</ul>

<h3 class="mdc-list-group__subheader">List 2</h3>
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">line item</li>
<li class="mdc-list-item">line item</li>
<li class="mdc-list-item">line item</li>
</ul>
</div>
```

#### List Dividers
### List Dividers

MDC List contains an `mdc-list-divider` class which can be used as full-width or inset subdivisions either within lists themselves, or standalone between related groups of content.

```html
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">Item 1 - Division 1</li>
<li class="mdc-list-item">Item 2 - Division 1</li>
<li role="separator" class="mdc-list-divider"></li>
Expand All @@ -126,56 +124,107 @@ MDC List contains an `mdc-list-divider` class which can be used as full-width or
OR

```html
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">Item 1 - List 1</li>
<li class="mdc-list-item">Item 2 - List 1</li>
</ul>
<hr class="mdc-list-divider">
<ul class="mdc-list">
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">Item 1 - List 2</li>
<li class="mdc-list-item">Item 2 - List 2</li>
</ul>
```

## Style Customization

### CSS Classes

CSS Class | Description
--- | ---
`mdc-list` | Mandatory, for the list element
`mdc-list--non-interactive` | Optional, disables interactivity affordances
`mdc-list--dense` | Optional, styles the density of the list, making it appear more compact
`mdc-list--avatar-list` | Optional, configures the leading tiles of each row to display images instead of icons. This will make the graphics of the list items larger
`mdc-list--two-line` | Optional, modifier to style list with two lines (primary and secondary lines)
`mdc-list-item` | Mandatory, for the list item element
`mdc-list-item__text` | Optional, primary text for the row (displayed as middle column of the list item)
`mdc-list-item__secondary-text` | Optional, secondary text for the list item. Displayed below the primary text. Should be the child of `mdc-list-item__text`
`mdc-list-item--selected` | Optional, styles the row in an selected* state
`mdc-list-item--activated` | Optional, styles the row in an activated* state
`mdc-list` | Mandatory, for the list element.
`mdc-list--non-interactive` | Optional, disables interactivity affordances.
`mdc-list--dense` | Optional, styles the density of the list, making it appear more compact.
`mdc-list--avatar-list` | Optional, configures the leading tiles of each row to display images instead of icons. This will make the graphics of the list items larger.
`mdc-list--two-line` | Optional, modifier to style list with two lines (primary and secondary lines).
`mdc-list-item` | Mandatory, for the list item element.
`mdc-list-item__text` | Optional, primary text for the row (displayed as middle column of the list item).
`mdc-list-item__secondary-text` | Optional, secondary text for the list item. Displayed below the primary text. Should be the child of `mdc-list-item__text`.
`mdc-list-item--selected` | Optional, styles the row in an selected* state.
`mdc-list-item--activated` | Optional, styles the row in an activated* state.
`mdc-list-item__graphic` | Optional, the first tile in the row (in LTR languages, the first column of the list item). Typically an icon or image.
`mdc-list-item__meta` | Optional, the last tile in the row (in LTR languages, the last column of the list item). Typically small text, icon. or image.
`mdc-list-group` | Optional, wrapper around two or more mdc-list elements to be grouped together
`mdc-list-group__subheader` | Optional, heading text displayed above each list in a group
`mdc-list-divider` | Optional, for list divider element
`mdc-list-divider--padded` | Optional, leaves gaps on each side of divider to match padding of `list-item__meta`
`mdc-list-divider--inset` | Optional, increases the leading margin of the divider so that it does not intersect the avatar column
`mdc-list-group` | Optional, wrapper around two or more mdc-list elements to be grouped together.
`mdc-list-group__subheader` | Optional, heading text displayed above each list in a group.
`mdc-list-divider` | Optional, for list divider element.
`mdc-list-divider--padded` | Optional, leaves gaps on each side of divider to match padding of `list-item__meta`.
`mdc-list-divider--inset` | Optional, increases the leading margin of the divider so that it does not intersect the avatar column.

> NOTE: `mdc-list-divider` class can be used between list items (example 1) *OR* between two lists (example 2)
> NOTE: `mdc-list-divider` class can be used between list items (example 1) *OR* between two lists (example 2).

> NOTE: the difference between selected and activated states:

* *Selected* state should be implemented on the `.list-item` when it is likely to change soon. Eg., selecting one or more photos to share in Google Photos.
* Multiple items can be selected at the same time when using the *selected* state
* Multiple items can be selected at the same time when using the *selected* state.
* *Activated* state is similar to selected state, however should only be implemented once within a specific list.
* *Activated* state is more permanent than selected state, and will **NOT** change soon relative to the lifetime of the page.

### Sass Mixins

Mixin | Description
--- | ---
`mdc-list-item-primary-text-ink-color($color)` | Sets the ink color of the primary text of the list item
`mdc-list-item-secondary-text-ink-color($color)` | Sets the ink color of the secondary text of the list item
`mdc-list-item-graphic-fill-color($color)` | Sets background ink color of the graphic element within list item
`mdc-list-item-graphic-ink-color($color)` | Sets ink color of the graphic element within list item
`mdc-list-item-meta-ink-color($color)` | Sets ink color of the meta element within list item
`mdc-list-divider-color($color)` | Sets divider ink color
`mdc-list-group-subheader-ink-color($color)` | Sets ink color of subheader text within list group
`mdc-list-item-primary-text-ink-color($color)` | Sets the ink color of the primary text of the list item.
`mdc-list-item-secondary-text-ink-color($color)` | Sets the ink color of the secondary text of the list item.
`mdc-list-item-graphic-fill-color($color)` | Sets background ink color of the graphic element within list item.
`mdc-list-item-graphic-ink-color($color)` | Sets ink color of the graphic element within list item.
`mdc-list-item-meta-ink-color($color)` | Sets ink color of the meta element within list item.
`mdc-list-divider-color($color)` | Sets divider ink color.
`mdc-list-group-subheader-ink-color($color)` | Sets ink color of subheader text within list group.

### Accessibility

The MDCList JavaScript component implements the WAI-ARIA best practices for
[Listbox](https://www.w3.org/TR/wai-aria-practices-1.1/#Listbox). This includes overriding the default tab behavior
within the list component. You should not add `tabindex` to any of the `li` elements in a list.

As the user navigates through the list, any `button` or `a` elements within the list will receive `tabindex="-1"`
when the list item is not focused. When the list item receives focus, the child `button` and `a` elements will
receive `tabIndex="0"`. This allows for the user to tab through list items elements and then tab to the
first element after the list. The `Arrow`, `Home`, and `End` keys should be used for navigating internal list elements.
The MDCList will perform the following actions for each key press

Key | Action
--- | ---
`ArrowUp` | When the list is in a vertical orientation, it will cause the previous list item to receive focus.
`ArrowDown` | When the list is in a vertical orientation, it will cause the next list item to receive focus.
`ArrowLeft` | When the list is in a horizontal orientation (default), it will cause the previous list item to receive focus.
`ArrowRight` | When the list is in a horizontal orientation (default), it will cause the next list item to receive focus.
`Home` | Will cause the first list item in the list to receive focus.
`End` | Will cause the last list item in the list to receive focus.

## Usage within Web Frameworks

If you are using a JavaScript framework, such as React or Angular, you can create a List for your framework. Depending on your needs, you can use the _Simple Approach: Wrapping MDC Web Vanilla Components_, or the _Advanced Approach: Using Foundations and Adapters_. Please follow the instructions [here](../../docs/integrating-into-frameworks.md).

### `MDCListAdapter`

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.
`focusItemAtIndex(ndx: Number) => void` | Focuses the list item at the `ndx` value specified.
`setTabIndexForListItemChildren(ndx: Number, value: Number) => void` | Sets the `tabindex` attribute to `value` for each child `button` and `a` element in the list item at the `ndx` specified.

### `MDCListFoundation`

Method Signature | Description
--- | ---
`setWrapFocus(value: Boolean) => void` | Sets the list to allow the up arrow on the first element to focus the last element of the list and vice versa.
`setVerticalOrientation(value: Boolean) => void` | Sets the list to an orientation causing the keys used for navigation to change. `true` results in the Up/Down arrow keys being used. `false` results in the Left/Right arrow keys being used.
`handleFocusIn(evt: Event) => void` | Handles the changing of `tabindex` to `0` for all `button` and `a` elements when a list item receives focus.
`handleFocusOut(evt: Event) => void` | Handles the changing of `tabindex` to `-1` for all `button` and `a` elements when a list item loses focus.
`handleKeydown(evt: Event) => void` | Handles determining if a focus action should occur when a key event is triggered.
`focusNextElement(index: Number) => void` | Handles focusing the next element using the current `index`.
`focusPrevElement(index: Number) => void` | Handles focusing the previous element using the current `index`.
`focusFirstElement() => void` | Handles focusing the first element in a list.
`focusLastElement() => void` | Handles focusing the last element in a list.
59 changes: 59 additions & 0 deletions packages/mdc-list/adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @license
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* eslint no-unused-vars: [2, {"args": "none"}] */

/**
* Adapter for MDC List. Provides an interface for managing focus.
*
* Additionally, provides type information for the adapter to the Closure
* compiler.
*
* Implement this adapter for your framework of choice to delegate updates to
* the component in your framework of choice. See architecture documentation
* for more details.
* https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md
*
* @record
*/
class MDCListAdapter {
/** @return {Number} */
getListItemCount() {}

/**
* @return {Number} */
getFocusedElementIndex() {}

/** @param {Element} node */
getListItemIndex(node) {}

/**
* Focuses list item at the index specified.
* @param {Number} ndx
*/
focusItemAtIndex(ndx) {}

/**
* Sets the tabindex to the value specified for all button/a element children of
* the list item at the index specified.
* @param {Number} listItemIndex
* @param {Number} tabIndexValue
*/
setTabIndexForListItemChildren(listItemIndex, tabIndexValue) {}
}

export {MDCListAdapter};
31 changes: 31 additions & 0 deletions packages/mdc-list/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/** @enum {string} */
const cssClasses = {
LIST_ITEM_CLASS: 'mdc-list-item',
};

/** @enum {string} */
const strings = {
ARIA_ORIENTATION: 'aria-orientation',
ARIA_ORIENTATION_VERTICAL: 'vertical',
FOCUSABLE_CHILD_ELEMENTS: 'button:not(:disabled), a',
ITEMS_SELECTOR: '.mdc-list-item',
};

export {strings, cssClasses};
Loading