Skip to content

Commit

Permalink
refactor(list)!: move list aria to host
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Aria and roles on List have been moved to the host element. list-tabindex attribute should be migrated to tabindex attribute. type attribute should be migrated to role attribute.

PiperOrigin-RevId: 565767899
  • Loading branch information
Elliott Marquez authored and copybara-github committed Sep 15, 2023
1 parent cfd053c commit 9447ec7
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 41 deletions.
2 changes: 1 addition & 1 deletion catalog/site/_includes/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
<!-- unrelated change to components -->
{% block content %}{{ content | safe }}{% endblock %}
</main>
<md-list list-tabindex="-1">
<md-list>
{% for component in collections.component|filtersort('data.name') %}
<li>
<md-list-item
Expand Down
16 changes: 10 additions & 6 deletions docs/components/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,15 @@ Videos can also be slotted into list-items' `start-video"` or

## Accessibility

List and List Items can have their internal `role` attribute set via the `type`
attribute which by default is set to
[`'list'`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/list_role)<!-- {.external} -->
and[ `'listitem'`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listitem_role)<!-- {.external} -->
respectively.
List can have its `role` and `tabindex` set via the `role` and `tabindex`
attributes, and list items can have their internal `role` and `tabindex` set via
the `type` and `item-tabindex` attributes respectively.

By default these values are set to
[`role="list"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/list_role)<!-- {.external} -->
and `tabindex="-1"` for list, and
[`role="listitem"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listitem_role)<!-- {.external} -->
and `tabindex="0"` for list items.

The following example sets
[`role="listbox"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role)<!-- {.external} -->
Expand All @@ -246,7 +250,7 @@ on the internal list and
on the internal list item nodes.

```html
<md-list type="listbox">
<md-list role="listbox" tabindex="0">
<md-list-item type="option" headline="icon">
<md-icon slot="start-icon">account_circle</md-icon>
</md-list-item>
Expand Down
3 changes: 1 addition & 2 deletions list/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import './index.js';
import './material-collection.js';

import {KnobTypesToKnobs, MaterialCollection, materialInitsToStoryInits, setUpDemo, title} from './material-collection.js';
import {boolInput, Knob, numberInput, textInput} from './index.js';
import {boolInput, Knob, textInput} from './index.js';

import {stories, StoryKnobs} from './stories.js';

Expand Down Expand Up @@ -36,7 +36,6 @@ export const VIDEO_URL =

const collection =
new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>('List', [
new Knob('listTabIndex', {ui: numberInput(), defaultValue: -1}),
new Knob('md-list-item', {ui: title()}),
new Knob('disabled', {ui: boolInput(), defaultValue: false}),
new Knob('noninteractive', {ui: boolInput(), defaultValue: false}),
Expand Down
7 changes: 1 addition & 6 deletions list/demo/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import {css, html} from 'lit';

/** Knob types for list stories. */
export interface StoryKnobs {
listTabIndex: number;

'md-list-item': void;
disabled: boolean;
noninteractive: boolean;
Expand Down Expand Up @@ -59,7 +57,6 @@ const standard: MaterialStoryInit<StoryKnobs> = {
}`,
render(knobs) {
const {
listTabIndex,
disabled,
noninteractive,
multiLineSupportingText,
Expand All @@ -72,9 +69,7 @@ const standard: MaterialStoryInit<StoryKnobs> = {
} = knobs;
return html`
<div class="list-demo">
<md-list
.listTabIndex=${listTabIndex}
class="list">
<md-list class="list" role="listbox">
<md-list-item
.headline=${headline}
.supportingText=${supportingText}
Expand Down
40 changes: 16 additions & 24 deletions list/internal/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {html, LitElement, nothing} from 'lit';
import {property, query, queryAssignedElements} from 'lit/decorators.js';
import {html, isServer, LitElement} from 'lit';
import {query, queryAssignedElements} from 'lit/decorators.js';

import {ARIAMixinStrict} from '../../internal/aria/aria.js';
import {requestUpdateOnAriaChange} from '../../internal/aria/delegate.js';
import {polyfillElementInternalsAria, setupHostAria} from '../../internal/aria/aria.js';

import {ListItem} from './listitem/list-item.js';

Expand Down Expand Up @@ -44,20 +43,9 @@ function isNavigableKey(key: string): key is NavigatableValues {
// tslint:disable-next-line:enforce-comments-on-exported-symbols
export class List extends LitElement {
static {
requestUpdateOnAriaChange(List);
setupHostAria(List, {focusable: false});
}

/** @nocollapse */
static override shadowRootOptions:
ShadowRootInit = {mode: 'open', delegatesFocus: true};

@property() type: 'menu'|'menubar'|'listbox'|'list'|'' = 'list';

/**
* The tabindex of the underlying list.
*/
@property({type: Number, attribute: 'list-tabindex'}) listTabIndex = -1;

@query('.list') private listRoot!: HTMLElement|null;

/**
Expand All @@ -71,6 +59,17 @@ export class List extends LitElement {
@queryAssignedElements({flatten: true, selector: '[md-list-item]'})
items!: ListItem[];

private readonly internals = polyfillElementInternalsAria(
this, (this as HTMLElement /* needed for closure */).attachInternals());

constructor() {
super();
if (!isServer) {
this.internals.role = 'list';
this.addEventListener('keydown', this.handleKeydown.bind(this));
}
}

protected override render() {
return this.renderList();
}
Expand All @@ -79,15 +78,8 @@ export class List extends LitElement {
* Renders the main list element.
*/
private renderList() {
// Needed for closure conformance
const {ariaLabel} = this as ARIAMixinStrict;
return html`
<ul class="list"
aria-label=${ariaLabel || nothing}
tabindex=${this.listTabIndex}
role=${this.type || nothing}
@keydown=${this.handleKeydown}
>
<ul class="list" role="presentation">
${this.renderContent()}
</ul>
`;
Expand Down
28 changes: 28 additions & 0 deletions list/list_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,34 @@ describe('<md-list>', () => {
expect(document.activeElement).toEqual(first);
});
});

describe('aria', () => {
it('Sets default role to list', async () => {
const root = env.render(html`<md-list></md-list>`);
const listEl = root.querySelector('md-list')!;
await env.waitForStability();
const internals =
(listEl as unknown as {internals: {role: string | null}}).internals;

expect(internals.role).toEqual('list');
});

it('Does not override user given role attribute', async () => {
const root = env.render(html`<md-list role="listbox"></md-list>`);
const listEl = root.querySelector('md-list')!;
await env.waitForStability();

expect(listEl.getAttribute('role')).toBe('listbox');
});

it('Does not override user given role property', async () => {
const root = env.render(html`<md-list .role=${'listbox'}></md-list>`);
const listEl = root.querySelector('md-list')!;
await env.waitForStability();

expect(listEl.role).toBe('listbox');
});
});
});

describe('<md-list-item>', () => {
Expand Down
4 changes: 2 additions & 2 deletions menu/internal/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ export abstract class Menu extends LitElement {
part="list"
id="list"
aria-label=${ariaLabel || nothing}
type=${this.type}
listTabIndex=${this.listTabIndex}
role=${this.type}
tabindex=${this.listTabIndex}
@keydown=${this.handleListKeydown}>
${this.renderMenuItems()}
</md-list>`;
Expand Down

0 comments on commit 9447ec7

Please sign in to comment.