diff --git a/packages/app/src/views/drawer/rtl.html b/packages/app/src/views/drawer/rtl.html
index c1b4ea9b..e3e1c9e3 100644
--- a/packages/app/src/views/drawer/rtl.html
+++ b/packages/app/src/views/drawer/rtl.html
@@ -3,12 +3,12 @@
diff --git a/packages/app/src/views/drawer/standard.html b/packages/app/src/views/drawer/standard.html
index f644c180..db02ca04 100644
--- a/packages/app/src/views/drawer/standard.html
+++ b/packages/app/src/views/drawer/standard.html
@@ -3,38 +3,38 @@
-
-
+
- ${item.icon}
+ ${item.icon}
${item.label}
-
-
+
+
Labels
-
-
- bookmark
+
+
+ bookmark
Family
-
- bookmark
+
+ bookmark
Friends
-
- bookmark
+
+ bookmark
Work
-
-
- settings
+
+
+ settings
Settings
-
- announcement
+
+ announcement
Help & feedback
-
+
diff --git a/packages/app/src/views/root/root.html b/packages/app/src/views/root/root.html
index 38785294..c0487127 100644
--- a/packages/app/src/views/root/root.html
+++ b/packages/app/src/views/root/root.html
@@ -23,12 +23,12 @@
-
+
- ${m.title}
-
+ ${m.title}
+
-
+
diff --git a/packages/drawer/src/mdc-drawer.ts b/packages/drawer/src/mdc-drawer.ts
index 16b5ed63..d5755929 100644
--- a/packages/drawer/src/mdc-drawer.ts
+++ b/packages/drawer/src/mdc-drawer.ts
@@ -1,7 +1,6 @@
import { MdcComponent, MdcFocusTrap } from '@aurelia-mdc-web/base';
import { MDCDismissibleDrawerFoundation, cssClasses, strings, MDCModalDrawerFoundation, MDCDrawerAdapter } from '@material/drawer';
import { SpecificEventListener } from '@material/base';
-import { MDCListFoundation } from '@material/list';
import { inject, useView, customElement, bindable } from 'aurelia-framework';
import { PLATFORM } from 'aurelia-pal';
@@ -106,8 +105,7 @@ export class MdcDrawer extends MdcComponent {
- const activeNavItemEl = this.root.querySelector(
- `.${MDCListFoundation.cssClasses.LIST_ITEM_ACTIVATED_CLASS}`);
+ const activeNavItemEl = this.root.querySelector('.mdc-deprecated-list-item--activated');
if (activeNavItemEl) {
activeNavItemEl.focus();
}
diff --git a/packages/list/src/index.ts b/packages/list/src/index.ts
index ba649e31..475ae80a 100644
--- a/packages/list/src/index.ts
+++ b/packages/list/src/index.ts
@@ -15,6 +15,17 @@ export function configure(config: FrameworkConfiguration) {
PLATFORM.moduleName('./mdc-list-item-secondary-text'),
PLATFORM.moduleName('./mdc-list-divider/mdc-list-divider'),
PLATFORM.moduleName('./mdc-list-group'),
+
+ PLATFORM.moduleName('./mdc-deprecated-list/mdc-deprecated-list'),
+ PLATFORM.moduleName('./mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item'),
+ PLATFORM.moduleName('./mdc-deprecated-list/mdc-deprecated-list-item/enhance-mdc-deprecated-list-item'),
+ PLATFORM.moduleName('./mdc-deprecated-list/mdc-deprecated-list-item-primary-text'),
+ PLATFORM.moduleName('./mdc-deprecated-list/mdc-deprecated-list-item-secondary-text'),
+ PLATFORM.moduleName('./mdc-deprecated-list/mdc-deprecated-list-item-graphic'),
+ PLATFORM.moduleName('./mdc-deprecated-list/mdc-deprecated-list-item-meta'),
+ PLATFORM.moduleName('./mdc-deprecated-list/mdc-deprecated-list-divider/mdc-deprecated-list-divider'),
+ PLATFORM.moduleName('./mdc-deprecated-list/mdc-deprecated-list-group'),
+
]);
config.aurelia.use.plugin(PLATFORM.moduleName('@aurelia-mdc-web/ripple'));
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-divider/mdc-deprecated-list-divider.html b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-divider/mdc-deprecated-list-divider.html
new file mode 100644
index 00000000..414f54b2
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-divider/mdc-deprecated-list-divider.html
@@ -0,0 +1,3 @@
+
+
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-divider/mdc-deprecated-list-divider.ts b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-divider/mdc-deprecated-list-divider.ts
new file mode 100644
index 00000000..0631827b
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-divider/mdc-deprecated-list-divider.ts
@@ -0,0 +1,18 @@
+import { customElement, useView, PLATFORM } from 'aurelia-framework';
+import { bindable } from 'aurelia-typed-observable-plugin';
+
+/**
+ * Optional, for list divider element
+ * @selector mdc-deprecated-list-divider
+ */
+@useView(PLATFORM.moduleName('./mdc-deprecated-list-divider.html'))
+@customElement('mdc-deprecated-list-divider')
+export class MdcDeprecatedListDivider {
+ /** To make a divider match the padding of list items */
+ @bindable.booleanAttr
+ padded: boolean;
+
+ /** Increases the leading margin of the divider so that it does not intersect the avatar column */
+ @bindable.booleanAttr
+ inset: boolean;
+ }
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-group.ts b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-group.ts
new file mode 100644
index 00000000..3b179866
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-group.ts
@@ -0,0 +1,25 @@
+import { inlineView, customElement, children, customAttribute, inject } from 'aurelia-framework';
+
+/**
+ * Optional. Wrapper around two or more mdc-list elements to be grouped together.
+ * @selector mdc-deprecated-list-group
+ */
+@inlineView('')
+@customElement('mdc-deprecated-list-group')
+export class MdcDeprecatedListGroup {
+ @children('h1,h2,h3,h4,h5,h6')
+ headers: HTMLElement[];
+ headersChanged() {
+ this.headers.forEach(x => x.classList.add('mdc-deprecated-list-group__subheader'));
+ }
+}
+
+@inject(Element)
+@customAttribute('mdc-deprecated-list-group-subheader')
+export class MdcDeprecatedListGroupSubheader {
+ constructor(private root: HTMLElement) { }
+
+ attached() {
+ this.root.classList.add('mdc-deprecated-list-group__subheader');
+ }
+}
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-graphic.ts b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-graphic.ts
new file mode 100644
index 00000000..c41d832d
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-graphic.ts
@@ -0,0 +1,15 @@
+import { customAttribute, inject } from 'aurelia-framework';
+
+/**
+ * Optional. The first tile in the row (in LTR languages, the first column of the list item). Typically an icon or image.
+ * @selector [mdc-deprecated-list-item-graphic]
+ */
+@inject(Element)
+@customAttribute('mdc-deprecated-list-item-graphic')
+export class MdcDeprecatedListItemGraphic {
+ constructor(private root: HTMLElement) { }
+
+ attached() {
+ this.root.classList.add('mdc-deprecated-list-item__graphic');
+ }
+}
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-meta.ts b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-meta.ts
new file mode 100644
index 00000000..a6983a72
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-meta.ts
@@ -0,0 +1,15 @@
+import { customAttribute, inject } from 'aurelia-framework';
+
+/**
+ * Optional. The last tile in the row (in LTR languages, the last column of the list item). Typically small text, icon. or image.
+ * @selector [mdc-deprecated-list-item-meta]
+ */
+@inject(Element)
+@customAttribute('mdc-deprecated-list-item-meta')
+export class MdcDeprecatedListItemMeta {
+ constructor(private root: HTMLElement) { }
+
+ attached() {
+ this.root.classList.add('mdc-deprecated-list-item__meta');
+ }
+}
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-primary-text.ts b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-primary-text.ts
new file mode 100644
index 00000000..d86c5b78
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-primary-text.ts
@@ -0,0 +1,9 @@
+import { inlineView, customElement } from 'aurelia-framework';
+
+/**
+ * Optional, primary text for the list item
+ * @selector mdc-deprecated-list-item-primary-text
+ */
+@inlineView('')
+@customElement('mdc-deprecated-list-item-primary-text')
+export class MdcDeprecatedListItemPrimaryText { }
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-secondary-text.ts b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-secondary-text.ts
new file mode 100644
index 00000000..ebaedf13
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item-secondary-text.ts
@@ -0,0 +1,9 @@
+import { inlineView, customElement } from 'aurelia-framework';
+
+/**
+ * Optional, secondary text for the list item. Displayed below the primary text.
+ * @selector mdc-deprecated-list-item-secondary-text
+ */
+@inlineView('')
+@customElement('mdc-deprecated-list-item-secondary-text')
+export class MdcDeprecatedListItemPrimaryText { }
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item/enhance-mdc-deprecated-list-item.ts b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item/enhance-mdc-deprecated-list-item.ts
new file mode 100644
index 00000000..f4f2b401
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item/enhance-mdc-deprecated-list-item.ts
@@ -0,0 +1,11 @@
+import { viewEngineHooks } from 'aurelia-framework';
+
+@viewEngineHooks
+export class EnhanceMdcDeprecatedListItem {
+ beforeCompile(template: DocumentFragment) {
+ const actions = template.querySelectorAll('[mdc-deprecated-list-item]');
+ for (const i of Array.from(actions)) {
+ i.setAttribute('as-element', 'mdc-deprecated-list-item');
+ }
+ }
+}
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.html b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.html
new file mode 100644
index 00000000..408791c7
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.html
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.ts b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.ts
new file mode 100644
index 00000000..d2fb97d1
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list-item/mdc-deprecated-list-item.ts
@@ -0,0 +1,97 @@
+import { inject, useView, customElement, processContent, ViewCompiler, ViewResources } from 'aurelia-framework';
+import { PLATFORM } from 'aurelia-pal';
+import { bindable } from 'aurelia-typed-observable-plugin';
+
+let listItemId = 0;
+
+const ENTER = 13;
+const SPACE = 32;
+const LIST_ITEM_ACTION = 'mdclistitem:action';
+
+/**
+ * @selector mdc-deprecated-list-item
+ */
+@inject(Element)
+@useView(PLATFORM.moduleName('./mdc-deprecated-list-item.html'))
+@customElement('mdc-deprecated-list-item')
+@processContent(MdcDeprecatedListItem.processContent)
+export class MdcDeprecatedListItem {
+ constructor(public root: HTMLElement) { }
+
+ static processContent(_viewCompiler: ViewCompiler, _resources: ViewResources, element: Element) {
+ const graphic = element.querySelector('mdc-checkbox:not([mdc-deprecated-list-item-meta]),[mdc-deprecated-list-item-graphic]');
+ if (graphic) {
+ element.removeChild(graphic);
+ }
+ const meta = element.querySelector('[mdc-deprecated-list-item-meta]');
+ if (meta) {
+ element.removeChild(meta);
+ }
+ const itemText = document.createElement('span');
+ itemText.classList.add('mdc-deprecated-list-item__text');
+ const children = [].slice.call(element.childNodes) as ChildNode[];
+ for (let i = 0; i < children.length; ++i) {
+ itemText.appendChild(children[i]);
+ }
+ if (graphic) {
+ element.appendChild(graphic);
+ }
+ element.appendChild(itemText);
+ if (meta) {
+ element.appendChild(meta);
+ }
+ return true;
+ }
+
+ id = ++listItemId;
+
+ /** Disables the list item */
+ @bindable.booleanAttr
+ disabled: boolean;
+
+ /** Styles the row in an activated state */
+ @bindable.booleanAttr
+ activated: boolean;
+
+ /** Random data associated with the list item. Passed in events payload. */
+ @bindable
+ value: unknown;
+
+ /** Disables ripple effect */
+ @bindable.booleanAttr
+ disableRipple: boolean;
+
+ onKeydown(evt: KeyboardEvent) {
+ if ((evt.keyCode === ENTER || evt.keyCode === SPACE) && !this.disabled) {
+ this.root.dispatchEvent(new CustomEvent(LIST_ITEM_ACTION, { detail: { item: this, data: this.value }, bubbles: true }));
+ }
+ return true;
+ }
+
+ onClick() {
+ if (!this.disabled) {
+ this.root.dispatchEvent(new CustomEvent(LIST_ITEM_ACTION, { detail: { item: this, data: this.value }, bubbles: true }));
+ }
+ return true;
+ }
+
+}
+
+/** @hidden */
+export interface IMdcListItemElement extends HTMLElement {
+ au: {
+ controller: {
+ viewModel: MdcDeprecatedListItem;
+ };
+ };
+}
+
+export interface IMdcListActionEventDetail {
+ index: number;
+ data: unknown;
+}
+
+/** @hidden */
+export interface IMdcListActionEvent extends Event {
+ detail: IMdcListActionEventDetail;
+}
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list.html b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list.html
new file mode 100644
index 00000000..f9e34fa3
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list.html
@@ -0,0 +1,15 @@
+
+
+
diff --git a/packages/list/src/mdc-deprecated-list/mdc-deprecated-list.ts b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list.ts
new file mode 100644
index 00000000..1014db17
--- /dev/null
+++ b/packages/list/src/mdc-deprecated-list/mdc-deprecated-list.ts
@@ -0,0 +1,337 @@
+import { MdcComponent } from '@aurelia-mdc-web/base';
+import { MDCListFoundation, MDCListAdapter, strings, MDCListIndex } from '@material/list';
+import { inject, useView, customElement, children } from 'aurelia-framework';
+import { PLATFORM } from 'aurelia-pal';
+import { closest, matches } from '@material/dom/ponyfill';
+import { bindable } from 'aurelia-typed-observable-plugin';
+import { MdcDeprecatedListItem, IMdcListItemElement, IMdcListActionEventDetail } from './mdc-deprecated-list-item/mdc-deprecated-list-item';
+
+strings.ACTION_EVENT = strings.ACTION_EVENT.toLowerCase();
+
+export const mdcListStrings = {
+ ITEMS_CHANGED: 'mdclist:itemschanged'
+};
+
+/**
+ * @selector mdc-list
+ * @emits mdclist:action | Indicates that a list item with the specified index has been activated
+ * @emits mdclist:itemschanged | Indicates that the list of items has changed
+ */
+@inject(Element)
+@useView(PLATFORM.moduleName('./mdc-deprecated-list.html'))
+@customElement('mdc-deprecated-list')
+export class MdcDeprecatedList extends MdcComponent{
+
+ /** Increases the height of the row to give it greater visual separation from adjacent rows */
+ @bindable.booleanAttr
+ twoLine: boolean;
+
+ /** When enabled, the space and enter keys (or click event) will trigger an single list item to become selected and any other previous selected element to become deselected */
+ @bindable.booleanAttr
+ singleSelection: boolean;
+ async singleSelectionChanged() {
+ await this.initialised;
+ this.foundation?.setSingleSelection(this.singleSelection);
+ }
+
+ /** Sets the selection logic to apply/remove the mdc-list-item--activated class */
+ @bindable.booleanAttr
+ activated: boolean;
+ async activatedChanged() {
+ await this.initialised;
+ this.foundation?.setUseActivatedClass(this.activated);
+ }
+
+ /** Sets the list to an orientation causing the keys used for navigation to change. true results in the Up/Down arrow keys being used. If false, the Left/Right arrow keys are used. */
+ @bindable.booleanAttr
+ vertical: boolean = true;
+
+ /** Increases the density of the list, making it appear more compact */
+ @bindable.booleanAttr
+ dense: boolean;
+
+ /** Optional, configures lists that start with text */
+ @bindable.booleanAttr
+ textual: boolean;
+
+ /** Configures the leading tiles of each row to display images instead of icons. This will make the graphics of the list items larger. */
+ @bindable.booleanAttr
+ avatar: boolean;
+
+ /** Optional, configures the leading tile of each row to display icons */
+ @bindable.booleanAttr
+ icon: boolean;
+
+ /** Optional, configures the leading tile of each row to display images */
+ @bindable.booleanAttr
+ image: boolean;
+
+ /** Optional, configures the leading tile of each row to display smaller images (this is analogous to an avatar list but the image will not be rounded) */
+ @bindable.booleanAttr
+ thumbnail: boolean;
+
+ /** Optional, configures the leading tile of each row to display videos */
+ @bindable.booleanAttr
+ video: boolean;
+
+ @children('mdc-deprecated-list-item')
+ items: MdcDeprecatedListItem[];
+ itemsChanged() {
+ this.foundation?.layout();
+ this.emit(mdcListStrings.ITEMS_CHANGED, { items: this.items }, true);
+ }
+
+ @bindable.booleanAttr
+ typeahead: boolean;
+ async typeaheadChanged(hasTypeahead: boolean) {
+ await this.initialised;
+ this.foundation?.setHasTypeahead(hasTypeahead);
+ }
+
+ /** Sets the list to allow the up arrow on the first element to focus the last element of the list and vice versa */
+ @bindable.booleanAttr
+ wrapFocus: boolean;
+ async wrapFocusChanged() {
+ await this.initialised;
+ this.foundation?.setWrapFocus(this.wrapFocus);
+ }
+
+ initialSyncWithDOM() {
+ this.layout();
+ this.initializeListType();
+ }
+
+ get listElements(): Element[] {
+ return Array.from(this.root.querySelectorAll('.mdc-deprecated-list-item'));
+ }
+
+ /**
+ * Extracts the primary text from a list item.
+ * @param item The list item element.
+ * @return The primary text in the element.
+ */
+ getPrimaryText(item: Element): string {
+ const primaryText = item.querySelector('.mdc-deprecated-list-item__primary-text');
+ if (primaryText) {
+ return primaryText.textContent ?? '';
+ }
+
+ const singleLineText = item.querySelector('.mdc-deprecated-list-item__text');
+ return singleLineText?.textContent ?? '';
+ }
+
+ getDefaultFoundation() {
+ // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial.
+ // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable.
+ const adapter: MDCListAdapter = {
+ addClassForElementIndex: (index, className) => {
+ const element = this.listElements[index];
+ if (element) {
+ element.classList.add(className);
+ }
+ },
+ focusItemAtIndex: (index) => {
+ const element = this.listElements[index] as HTMLElement | undefined;
+ if (element) {
+ element.focus();
+ }
+ },
+ getAttributeForElementIndex: (index, attr) => this.listElements[index].getAttribute(attr),
+ getFocusedElementIndex: () => this.listElements.indexOf(document.activeElement!),
+ getListItemCount: () => this.listElements.length,
+ getPrimaryTextAtIndex: (index) => this.getPrimaryText(this.listElements[index]),
+ hasCheckboxAtIndex: (index) => {
+ const listItem = this.listElements[index];
+ return !!listItem.querySelector(strings.CHECKBOX_SELECTOR);
+ },
+ hasRadioAtIndex: (index) => {
+ const listItem = this.listElements[index];
+ return !!listItem.querySelector(strings.RADIO_SELECTOR);
+ },
+ isCheckboxCheckedAtIndex: (index) => {
+ const listItem = this.listElements[index];
+ const toggleEl = listItem.querySelector(strings.CHECKBOX_SELECTOR);
+ return toggleEl!.checked;
+ },
+ isFocusInsideList: () => {
+ return this.root !== document.activeElement && this.root.contains(document.activeElement);
+ },
+ isRootFocused: () => document.activeElement === this.root,
+ listItemAtIndexHasClass: (index, className) => this.listElements[index].classList.contains(className),
+ notifyAction: (index) => {
+ const listItem = this.listElements[index];
+ if (!listItem.hasAttribute('no-list-action')) {
+ const data = (listItem as IMdcListItemElement).au.controller.viewModel.value;
+ this.emit(strings.ACTION_EVENT, { index, data }, /** shouldBubble */ true);
+ }
+ },
+ removeClassForElementIndex: (index, className) => {
+ const element = this.listElements[index];
+ if (element) {
+ element.classList.remove(className);
+ }
+ },
+ setAttributeForElementIndex: (index, attr, value) => {
+ const element = this.listElements[index];
+ if (element) {
+ element.setAttribute(attr, value);
+ }
+ },
+ setCheckedCheckboxOrRadioAtIndex: (index, isChecked) => {
+ const listItem = this.listElements[index];
+ const toggleEl = listItem.querySelector(strings.CHECKBOX_RADIO_SELECTOR);
+ if (toggleEl?.disabled) {
+ return;
+ }
+ toggleEl!.checked = isChecked;
+
+ const event = document.createEvent('Event');
+ event.initEvent('change', true, true);
+ toggleEl!.dispatchEvent(event);
+ },
+ setTabIndexForListItemChildren: (listItemIndex, tabIndexValue) => {
+ const element = this.listElements[listItemIndex];
+ const listItemChildren: Element[] =
+ [].slice.call(element.querySelectorAll(strings.CHILD_ELEMENTS_TO_TOGGLE_TABINDEX));
+ listItemChildren.forEach((el) => el.setAttribute('tabindex', tabIndexValue));
+ },
+ };
+ return new MDCListFoundation(adapter);
+ }
+
+ /**
+ * @hidden
+ * Used to figure out which list item this event is targetting. Or returns -1 if
+ * there is no list item
+ */
+ private getListItemIndex_(evt: Event) {
+ const eventTarget = evt.target as Element;
+ const nearestParent = closest(eventTarget, '.mdc-deprecated-list-item, .mdc-deprecated-list');
+
+ // Get the index of the element if it is a list item.
+ if (nearestParent && matches(nearestParent, '.mdc-deprecated-list-item')) {
+ return this.listElements.indexOf(nearestParent);
+ }
+
+ return -1;
+ }
+
+ /**
+ * @hidden
+ * Used to figure out which element was clicked before sending the event to the foundation.
+ */
+ handleFocusInEvent_(evt: FocusEvent) {
+ const index = this.getListItemIndex_(evt);
+ this.foundation?.handleFocusIn(index);
+ }
+
+ /**
+ * @hidden
+ * Used to figure out which element was clicked before sending the event to the foundation.
+ */
+ handleFocusOutEvent_(evt: FocusEvent) {
+ const index = this.getListItemIndex_(evt);
+ this.foundation?.handleFocusOut(index);
+ }
+
+ /**
+ * @hidden
+ * Used to figure out which element was focused when keydown event occurred before sending the event to the
+ * foundation.
+ */
+ handleKeydownEvent_(evt: KeyboardEvent) {
+ const index = this.getListItemIndex_(evt);
+ const target = evt.target as Element;
+ if (!target.hasAttribute('not-selectable')) {
+ this.foundation?.handleKeydown(evt, target.classList.contains('mdc-deprecated-list-item'), index);
+ }
+ return true;
+ }
+
+ /**
+ * @hidden
+ * Used to figure out which element was clicked before sending the event to the foundation.
+ */
+ handleClickEvent_(evt: MouseEvent) {
+ const index = this.getListItemIndex_(evt);
+ const target = evt.target as Element;
+ // Toggle the checkbox only if it's not the target of the event, or the checkbox will have 2 change events.
+ const toggleCheckbox = !matches(target, strings.CHECKBOX_RADIO_SELECTOR);
+ this.foundation?.handleClick(index, toggleCheckbox);
+ return true;
+ }
+
+ /**
+ * @hidden
+ * @return Whether typeahead is currently matching a user-specified prefix.
+ */
+ get typeaheadInProgress(): boolean {
+ return this.foundation!.isTypeaheadInProgress();
+ }
+
+ /**
+ * @hidden
+ * Given the next desired character from the user, adds it to the typeahead
+ * buffer. Then, attempts to find the next option matching the buffer. Wraps
+ * around if at the end of options.
+ *
+ * @param nextChar The next character to add to the prefix buffer.
+ * @param startingIndex The index from which to start matching. Defaults to
+ * the currently focused index.
+ * @return The index of the matched item.
+ */
+ typeaheadMatchItem(nextChar: string, startingIndex?: number): number {
+ return this.foundation!.typeaheadMatchItem(nextChar, startingIndex, /** skipFocus */ true);
+ }
+
+ layout() {
+ const direction = this.root.getAttribute(strings.ARIA_ORIENTATION);
+ this.vertical = direction !== strings.ARIA_ORIENTATION_HORIZONTAL;
+
+ // List items need to have at least tabindex=-1 to be focusable.
+ [].slice.call(this.root.querySelectorAll('.mdc-deprecated-list-item:not([tabindex])'))
+ .forEach((el: Element) => {
+ el.setAttribute('tabindex', '-1');
+ });
+
+ // Child button/a elements are not tabbable until the list item is focused.
+ [].slice.call(this.root.querySelectorAll(strings.FOCUSABLE_CHILD_ELEMENTS))
+ .forEach((el: Element) => el.setAttribute('tabindex', '-1'));
+
+ this.foundation?.layout();
+ }
+
+ get selectedIndex(): MDCListIndex {
+ return this.foundation!.getSelectedIndex();
+ }
+
+ set selectedIndex(index: MDCListIndex) {
+ this.foundation?.setSelectedIndex(index);
+ }
+
+ /**
+ * @hidden
+ * Initialize selectedIndex value based on pre-selected checkbox list items, single selection or radio.
+ */
+ initializeListType() {
+ const checkboxListItems = this.root.querySelectorAll(strings.ARIA_ROLE_CHECKBOX_SELECTOR);
+ const radioSelectedListItem = this.root.querySelector(strings.ARIA_CHECKED_RADIO_SELECTOR);
+
+ if (checkboxListItems.length) {
+ const preselectedItems = this.root.querySelectorAll(strings.ARIA_CHECKED_CHECKBOX_SELECTOR);
+ this.selectedIndex = [].map.call(preselectedItems, (listItem: Element) => this.listElements.indexOf(listItem)) as number[];
+ } else if (radioSelectedListItem) {
+ this.selectedIndex = this.listElements.indexOf(radioSelectedListItem);
+ }
+ }
+}
+
+/** @hidden */
+export interface IMdcListElement extends HTMLElement {
+ au: {
+ controller: {
+ viewModel: MdcDeprecatedList;
+ };
+ };
+}
+