fix(list): text items are no longer tabbable, links cannot be disabled

PiperOrigin-RevId: 568318309
This commit is contained in:
Elizabeth Mitchell 2023-09-25 14:02:45 -07:00 committed by Copybara-Service
parent 6d0c7e8538
commit 54c4ddba4c
8 changed files with 208 additions and 162 deletions

View File

@ -37,7 +37,7 @@ const standard: MaterialStoryInit<StoryKnobs> = {
styles,
render(knobs) {
return html`
<md-list>
<md-list aria-label="Static example">
<md-list-item ?disabled=${knobs.disabled}>
Single line item
${getKnobContent(knobs)}
@ -68,7 +68,7 @@ const interactive: MaterialStoryInit<StoryKnobs> = {
render(knobs) {
const knobsNoTrailing = {...knobs, trailingIcon: false};
return html`
<md-list>
<md-list aria-label="Interactive example">
<md-list-item ?disabled=${knobs.disabled}
type="link"
href="https://google.com"

View File

@ -58,6 +58,11 @@ export interface ListControllerConfig<Item extends ListItem> {
* Whether or not the key should be handled by the list for navigation.
*/
isNavigableKey: (key: string) => boolean;
/**
* Whether or not the item can be activated. Defaults to items that are not
* disabled.
*/
isActivatable?: (item: Item) => boolean;
}
/**
@ -70,6 +75,7 @@ export class ListController<Item extends ListItem> {
private readonly deactivateItem: (item: Item) => void;
private readonly activateItem: (item: Item) => void;
private readonly isNavigableKey: (key: string) => boolean;
private readonly isActivatable?: (item: Item) => boolean;
constructor(config: ListControllerConfig<Item>) {
const {
@ -79,6 +85,7 @@ export class ListController<Item extends ListItem> {
deactivateItem,
activateItem,
isNavigableKey,
isActivatable,
} = config;
this.isItem = isItem;
this.getPossibleItems = getPossibleItems;
@ -86,6 +93,7 @@ export class ListController<Item extends ListItem> {
this.deactivateItem = deactivateItem;
this.activateItem = activateItem;
this.isNavigableKey = isNavigableKey;
this.isActivatable = isActivatable;
}
/**
@ -131,7 +139,7 @@ export class ListController<Item extends ListItem> {
return;
}
const activeItemRecord = getActiveItem(items);
const activeItemRecord = getActiveItem(items, this.isActivatable);
if (activeItemRecord) {
activeItemRecord.item.tabIndex = -1;
@ -149,23 +157,23 @@ export class ListController<Item extends ListItem> {
// Activate the next item
case NavigableKeys.ArrowDown:
case inlineNext:
activateNextItem(items, activeItemRecord);
activateNextItem(items, activeItemRecord, this.isActivatable);
break;
// Activate the previous item
case NavigableKeys.ArrowUp:
case inlinePrevious:
activatePreviousItem(items, activeItemRecord);
activatePreviousItem(items, activeItemRecord, this.isActivatable);
break;
// Activate the first item
case NavigableKeys.Home:
activateFirstItem(items);
activateFirstItem(items, this.isActivatable);
break;
// Activate the last item
case NavigableKeys.End:
activateLastItem(items);
activateLastItem(items, this.isActivatable);
break;
default:
@ -179,13 +187,13 @@ export class ListController<Item extends ListItem> {
*
* @return The activated list item or `null` if there are no items.
*/
activateNextItem(): ListItem|null {
activateNextItem(): Item|null {
const items = this.items;
const activeItemRecord = getActiveItem(items);
const activeItemRecord = getActiveItem(items, this.isActivatable);
if (activeItemRecord) {
activeItemRecord.item.tabIndex = -1;
}
return activateNextItem(items, activeItemRecord);
return activateNextItem(items, activeItemRecord, this.isActivatable);
}
/**
@ -194,13 +202,13 @@ export class ListController<Item extends ListItem> {
*
* @return The activated list item or `null` if there are no items.
*/
activatePreviousItem(): ListItem|null {
activatePreviousItem(): Item|null {
const items = this.items;
const activeItemRecord = getActiveItem(items);
const activeItemRecord = getActiveItem(items, this.isActivatable);
if (activeItemRecord) {
activeItemRecord.item.tabIndex = -1;
}
return activatePreviousItem(items, activeItemRecord);
return activatePreviousItem(items, activeItemRecord, this.isActivatable);
}
/**
@ -250,7 +258,8 @@ export class ListController<Item extends ListItem> {
return;
}
const firstActivatableItem = getFirstActivatableItem(items);
const firstActivatableItem =
getFirstActivatableItem(items, this.isActivatable);
if (!firstActivatableItem) {
return;
@ -258,4 +267,4 @@ export class ListController<Item extends ListItem> {
firstActivatableItem.tabIndex = 0;
};
}
}

View File

@ -4,8 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
// TODO: move this file to List
export interface ListItem extends HTMLElement {
disabled: boolean;
}
@ -19,26 +17,20 @@ export interface ItemRecord<Item extends ListItem> {
index: number;
}
/**
* A record that describes a list item in a list with metadata such a reference
* to the item and its index in the list.
*/
export interface ItemRecord<Item extends ListItem> {
item: Item;
index: number;
}
/**
* Activates the first non-disabled item of a given array of items.
*
* @param items {Array<ListItem>} The items from which to activate the
* first item.
* first item.
* @param isActivatable Function to determine if an item can be activated.
* Defaults to non-disabled items.
*/
export function activateFirstItem<Item extends ListItem>(items: Item[]) {
export function activateFirstItem<Item extends ListItem>(
items: Item[], isActivatable = isItemNotDisabled<Item>) {
// NOTE: These selector functions are static and not on the instance such
// that multiple operations can be chained and we do not have to re-query
// the DOM
const firstItem = getFirstActivatableItem(items);
const firstItem = getFirstActivatableItem(items, isActivatable);
if (firstItem) {
firstItem.tabIndex = 0;
firstItem.focus();
@ -50,11 +42,14 @@ export function activateFirstItem<Item extends ListItem>(items: Item[]) {
* Activates the last non-disabled item of a given array of items.
*
* @param items {Array<ListItem>} The items from which to activate the
* last item.
* last item.
* @param isActivatable Function to determine if an item can be activated.
* Defaults to non-disabled items.
* @nocollapse
*/
export function activateLastItem<Item extends ListItem>(items: Item[]) {
const lastItem = getLastActivatableItem(items);
export function activateLastItem<Item extends ListItem>(
items: Item[], isActivatable = isItemNotDisabled<Item>) {
const lastItem = getLastActivatableItem(items, isActivatable);
if (lastItem) {
lastItem.tabIndex = 0;
lastItem.focus();
@ -66,13 +61,16 @@ export function activateLastItem<Item extends ListItem>(items: Item[]) {
* Deactivates the currently active item of a given array of items.
*
* @param items {Array<ListItem>} The items from which to deactivate the
* active item.
* active item.
* @param isActivatable Function to determine if an item can be activated.
* Defaults to non-disabled items.
* @return A record of the deleselcted activated item including the item and
* the index of the item or `null` if none are deactivated.
* the index of the item or `null` if none are deactivated.
* @nocollapse
*/
export function deactivateActiveItem<Item extends ListItem>(items: Item[]) {
const activeItem = getActiveItem(items);
export function deactivateActiveItem<Item extends ListItem>(
items: Item[], isActivatable = isItemNotDisabled<Item>) {
const activeItem = getActiveItem(items, isActivatable);
if (activeItem) {
activeItem.item.tabIndex = -1;
}
@ -83,14 +81,17 @@ export function deactivateActiveItem<Item extends ListItem>(items: Item[]) {
* Retrieves the first activated item of a given array of items.
*
* @param items {Array<ListItem>} The items to search.
* @param isActivatable Function to determine if an item can be activated.
* Defaults to non-disabled items.
* @return A record of the first activated item including the item and the
* index of the item or `null` if none are activated.
* index of the item or `null` if none are activated.
* @nocollapse
*/
export function getActiveItem<Item extends ListItem>(items: Item[]) {
export function getActiveItem<Item extends ListItem>(
items: Item[], isActivatable = isItemNotDisabled<Item>) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.tabIndex === 0 && !item.disabled) {
if (item.tabIndex === 0 && isActivatable(item)) {
return {
item,
index: i,
@ -105,12 +106,15 @@ export function getActiveItem<Item extends ListItem>(items: Item[]) {
* the first item that is not disabled.
*
* @param items {Array<ListItem>} The items to search.
* @param isActivatable Function to determine if an item can be activated.
* Defaults to non-disabled items.
* @return The first activatable item or `null` if none are activatable.
* @nocollapse
*/
export function getFirstActivatableItem<Item extends ListItem>(items: Item[]) {
export function getFirstActivatableItem<Item extends ListItem>(
items: Item[], isActivatable = isItemNotDisabled<Item>) {
for (const item of items) {
if (!item.disabled) {
if (isActivatable(item)) {
return item;
}
}
@ -122,13 +126,16 @@ export function getFirstActivatableItem<Item extends ListItem>(items: Item[]) {
* Retrieves the last non-disabled item of a given array of items.
*
* @param items {Array<ListItem>} The items to search.
* @param isActivatable Function to determine if an item can be activated.
* Defaults to non-disabled items.
* @return The last activatable item or `null` if none are activatable.
* @nocollapse
*/
export function getLastActivatableItem<Item extends ListItem>(items: Item[]) {
export function getLastActivatableItem<Item extends ListItem>(
items: Item[], isActivatable = isItemNotDisabled<Item>) {
for (let i = items.length - 1; i >= 0; i--) {
const item = items[i];
if (!item.disabled) {
if (isActivatable(item)) {
return item;
}
}
@ -141,14 +148,16 @@ export function getLastActivatableItem<Item extends ListItem>(items: Item[]) {
*
* @param items {Array<ListItem>} The items to search.
* @param index {{index: number}} The index to search from.
* @param isActivatable Function to determine if an item can be activated.
* Defaults to non-disabled items.
* @return The next activatable item or `null` if none are activatable.
*/
export function getNextItem<Item extends ListItem>(
items: Item[], index: number) {
items: Item[], index: number, isActivatable = isItemNotDisabled<Item>) {
for (let i = 1; i < items.length; i++) {
const nextIndex = (i + index) % items.length;
const item = items[nextIndex];
if (!item.disabled) {
if (isActivatable(item)) {
return item;
}
}
@ -161,15 +170,17 @@ export function getNextItem<Item extends ListItem>(
*
* @param items {Array<ListItem>} The items to search.
* @param index {{index: number}} The index to search from.
* @param isActivatable Function to determine if an item can be activated.
* Defaults to non-disabled items.
* @return The previous activatable item or `null` if none are activatable.
*/
export function getPrevItem<Item extends ListItem>(
items: Item[], index: number) {
items: Item[], index: number, isActivatable = isItemNotDisabled<Item>) {
for (let i = 1; i < items.length; i++) {
const prevIndex = (index - i + items.length) % items.length;
const item = items[prevIndex];
if (!item.disabled) {
if (isActivatable(item)) {
return item;
}
}
@ -182,9 +193,10 @@ export function getPrevItem<Item extends ListItem>(
* activates the first item.
*/
export function activateNextItem<Item extends ListItem>(
items: Item[], activeItemRecord: null|ItemRecord<Item>): Item|null {
items: Item[], activeItemRecord: null|ItemRecord<Item>,
isActivatable = isItemNotDisabled<Item>): Item|null {
if (activeItemRecord) {
const next = getNextItem(items, activeItemRecord.index);
const next = getNextItem(items, activeItemRecord.index, isActivatable);
if (next) {
next.tabIndex = 0;
@ -193,7 +205,7 @@ export function activateNextItem<Item extends ListItem>(
return next;
} else {
return activateFirstItem(items);
return activateFirstItem(items, isActivatable);
}
}
@ -202,16 +214,17 @@ export function activateNextItem<Item extends ListItem>(
* activated, activates the last item.
*/
export function activatePreviousItem<Item extends ListItem>(
items: Item[], activeItemRecord: null|ItemRecord<Item>): Item|null {
items: Item[], activeItemRecord: null|ItemRecord<Item>,
isActivatable = isItemNotDisabled<Item>): Item|null {
if (activeItemRecord) {
const prev = getPrevItem(items, activeItemRecord.index);
const prev = getPrevItem(items, activeItemRecord.index, isActivatable);
if (prev) {
prev.tabIndex = 0;
prev.focus();
}
return prev;
} else {
return activateLastItem(items);
return activateLastItem(items, isActivatable);
}
}
@ -246,4 +259,15 @@ export function createRequestActivationEvent() {
* The type of the event that requests the list activates and focuses the item.
*/
export type RequestActivationEvent =
ReturnType<typeof createRequestActivationEvent>;
ReturnType<typeof createRequestActivationEvent>;
/**
* The default `isActivatable` function, which checks if an item is not
* disabled.
*
* @param item The item to check.
* @return true if `item.disabled` is `false.
*/
function isItemNotDisabled<Item extends ListItem>(item: Item) {
return !item.disabled;
}

View File

@ -10,10 +10,14 @@ import {queryAssignedElements} from 'lit/decorators.js';
import {polyfillElementInternalsAria, setupHostAria} from '../../internal/aria/aria.js';
import {ListController, NavigableKeys} from './list-controller.js';
import {ListItem} from './list-navigation-helpers.js';
import {ListItem as SharedListItem} from './list-navigation-helpers.js';
const NAVIGABLE_KEY_SET = new Set<string>(Object.values(NavigableKeys));
interface ListItem extends SharedListItem {
type: 'text'|'button'|'link';
}
// tslint:disable-next-line:enforce-comments-on-exported-symbols
export class List extends LitElement {
static {
@ -50,6 +54,7 @@ export class List extends LitElement {
item.tabIndex = 0;
},
isNavigableKey: (key) => NAVIGABLE_KEY_SET.has(key),
isActivatable: (item) => !item.disabled && item.type !== 'text',
});
private readonly internals = polyfillElementInternalsAria(

View File

@ -48,11 +48,6 @@
);
}
:host([disabled]) {
opacity: map.get($tokens, 'disabled-opacity');
pointer-events: none;
}
md-focus-ring {
z-index: 1;
@ -82,10 +77,15 @@
outline: none;
// hide android tap color since we have ripple
-webkit-tap-highlight-color: transparent;
}
&:not(.disabled).interactive {
cursor: pointer;
}
.list-item.interactive {
cursor: pointer;
}
.list-item.disabled {
opacity: map.get($tokens, 'disabled-opacity');
pointer-events: none;
}
[slot='container'] {

View File

@ -8,8 +8,11 @@
// go/keep-sorted end
@media (forced-colors: active) {
:host([disabled]),
:host([disabled]) slot {
.disabled slot {
color: GrayText;
}
.list-item.disabled {
color: GrayText;
opacity: 1;
}

View File

@ -66,6 +66,10 @@ export class ListItemEl extends LitElement implements ListItem {
@query('.list-item') protected readonly listItemRoot!: HTMLElement|null;
private get isDisabled() {
return this.disabled && this.type !== 'link';
}
protected override willUpdate(changed: PropertyValues<ListItemEl>) {
if (this.href) {
this.type = 'link';
@ -109,13 +113,15 @@ export class ListItemEl extends LitElement implements ListItem {
break;
}
const isInteractive = this.type !== 'text';
// TODO(b/265339866): announce "button"/"link" inside of a list item. Until
// then all are "listitem" roles for correct announcement.
const target = isAnchor && !!this.target ? this.target : nothing;
return staticHtml`
<${tag}
id="item"
tabindex="${this.disabled && !isAnchor ? -1 : 0}"
tabindex="${this.isDisabled || !isInteractive ? -1 : 0}"
?disabled=${this.isDisabled}
role="listitem"
aria-selected=${(this as ARIAMixinStrict).ariaSelected || nothing}
aria-checked=${(this as ARIAMixinStrict).ariaChecked || nothing}
@ -141,7 +147,7 @@ export class ListItemEl extends LitElement implements ListItem {
<md-ripple
part="ripple"
for="item"
?disabled=${this.disabled}></md-ripple>`;
?disabled=${this.isDisabled}></md-ripple>`;
}
/**
@ -166,8 +172,7 @@ export class ListItemEl extends LitElement implements ListItem {
* Classes applied to the list item root.
*/
protected getRenderClasses(): ClassInfo {
// TODO(b/265339866): links may not be disabled
return {'disabled': this.disabled};
return {'disabled': this.isDisabled};
}
/**

View File

@ -28,9 +28,9 @@ describe('<md-list>', () => {
it('non-nagivable key does nothing', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
@ -55,9 +55,9 @@ describe('<md-list>', () => {
it('ArrowDown activates the next item', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
@ -82,9 +82,9 @@ describe('<md-list>', () => {
it('ArrowRight in LTR activates the next item', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
@ -109,9 +109,9 @@ describe('<md-list>', () => {
it('ArrowLeft in RTL activates the next item', async () => {
const root = env.render(html`
<md-list dir="rtl">
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
@ -136,9 +136,9 @@ describe('<md-list>', () => {
it('ArrowDown will loop around', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -162,8 +162,8 @@ describe('<md-list>', () => {
it('ArrowDown will do nothing if nothing is selectable', async () => {
const root = env.render(html`
<md-list>
<md-list-item disabled></md-list-item>
<md-list-item disabled></md-list-item>
<md-list-item type="button" disabled></md-list-item>
<md-list-item type="button" disabled></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -184,9 +184,9 @@ describe('<md-list>', () => {
it('ArrowDown does not activate disabled items', async () => {
const root = env.render(html`
<md-list>
<md-list-item></md-list-item>
<md-list-item disabled></md-list-item>
<md-list-item></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button" disabled></md-list-item>
<md-list-item type="button"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -211,7 +211,7 @@ describe('<md-list>', () => {
it('ArrowDown will select itself if its the only activatable', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -231,9 +231,9 @@ describe('<md-list>', () => {
it('ArrowUp activates the previous item', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -258,9 +258,9 @@ describe('<md-list>', () => {
it('ArrowLeft in LTR activates the previous item', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -284,9 +284,9 @@ describe('<md-list>', () => {
it('ArrowRight in RTL activates the previous item', async () => {
const root = env.render(html`
<md-list dir="rtl">
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -310,9 +310,9 @@ describe('<md-list>', () => {
it('activatePreviousItem will loop around', async () => {
const root = env.render(html`
<md-list>
<md-list-item></md-list-item>
<md-list-item></md-list-item>
<md-list-item></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -337,8 +337,8 @@ describe('<md-list>', () => {
it('ArrowUp will return null if nothing is selectable', async () => {
const root = env.render(html`
<md-list>
<md-list-item disabled></md-list-item>
<md-list-item disabled></md-list-item>
<md-list-item type="button" disabled></md-list-item>
<md-list-item type="button" disabled></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -359,9 +359,9 @@ describe('<md-list>', () => {
it('ArrowUp does not activate disabled items', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item disabled></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" disabled></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -386,7 +386,7 @@ describe('<md-list>', () => {
it('ArrowUp will select itself if its the only activatable', async () => {
const root = env.render(html`
<md-list>
<md-list-item></md-list-item>
<md-list-item type="button"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -407,9 +407,9 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -435,9 +435,9 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item disabled></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item type="button" disabled></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -463,9 +463,9 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -491,9 +491,9 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -519,9 +519,9 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item></md-list-item>
<md-list-item></md-list-item>
<md-list-item disabled></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button" disabled></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -546,9 +546,9 @@ describe('<md-list>', () => {
it('End will select the last item if it is already selected', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const listHarness = new ListHarness(listEl);
@ -573,9 +573,9 @@ describe('<md-list>', () => {
it('Clicking on an item will activate it', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
</md-list>`);
const [first, second, third] =
Array.from(root.querySelectorAll('md-list-item'));
@ -601,9 +601,9 @@ describe('<md-list>', () => {
it('List will activate only first item by default', async () => {
const root = env.render(html`
<md-list>
<md-list-item></md-list-item>
<md-list-item></md-list-item>
<md-list-item></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button"></md-list-item>
</md-list>`);
const [first, second, third] =
Array.from(root.querySelectorAll('md-list-item'));
@ -620,9 +620,9 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item disabled></md-list-item>
<md-list-item></md-list-item>
<md-list-item></md-list-item>
<md-list-item type="button" disabled></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button"></md-list-item>
</md-list>`);
const [first, second, third] =
Array.from(root.querySelectorAll('md-list-item'));
@ -639,9 +639,9 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const [first, second, third] =
Array.from(root.querySelectorAll('md-list-item'));
@ -658,9 +658,9 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="2"></md-list-item>
<md-list-item tabindex="1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="2"></md-list-item>
<md-list-item type="button" tabindex="1"></md-list-item>
</md-list>`);
const [first, second, third] =
Array.from(root.querySelectorAll('md-list-item'));
@ -676,9 +676,9 @@ describe('<md-list>', () => {
it('activateNextItem activates the next item', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const [first, second, third] =
@ -703,9 +703,9 @@ describe('<md-list>', () => {
it('activateNextItem will loop around', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const [first, second, third] =
@ -745,9 +745,9 @@ describe('<md-list>', () => {
it('activateNextItem does not activate disabled items', async () => {
const root = env.render(html`
<md-list>
<md-list-item></md-list-item>
<md-list-item disabled></md-list-item>
<md-list-item></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button" disabled></md-list-item>
<md-list-item type="button"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const [first, second, third] =
@ -773,7 +773,7 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const first = root.querySelector('md-list-item')!;
@ -793,9 +793,9 @@ describe('<md-list>', () => {
it('activatePreviousItem activates the previous item', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const [first, second, third] =
@ -820,9 +820,9 @@ describe('<md-list>', () => {
it('activatePreviousItem will loop around', async () => {
const root = env.render(html`
<md-list>
<md-list-item></md-list-item>
<md-list-item></md-list-item>
<md-list-item></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button"></md-list-item>
<md-list-item type="button"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const [first, second, third] =
@ -863,9 +863,9 @@ describe('<md-list>', () => {
it('activatePreviousItem does not activate disabled items', async () => {
const root = env.render(html`
<md-list>
<md-list-item tabindex="-1"></md-list-item>
<md-list-item disabled></md-list-item>
<md-list-item tabindex="0"></md-list-item>
<md-list-item type="button" tabindex="-1"></md-list-item>
<md-list-item type="button" disabled></md-list-item>
<md-list-item type="button" tabindex="0"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const [first, second, third] =
@ -891,7 +891,7 @@ describe('<md-list>', () => {
async () => {
const root = env.render(html`
<md-list>
<md-list-item></md-list-item>
<md-list-item type="button"></md-list-item>
</md-list>`);
const listEl = root.querySelector('md-list')!;
const first = root.querySelector('md-list-item')!;
@ -960,7 +960,7 @@ describe('<md-list-item>', () => {
});
it('disabled overrides tabIndex', async () => {
const root = env.render(html`<md-list-item></md-list-item>`);
const root = env.render(html`<md-list-item type="button"></md-list-item>`);
const listItem = root.querySelector('md-list-item')!;