@aurelia-mdc-web/list
Version:
Wrapper for Material Components Web List
289 lines • 12.5 kB
JavaScript
import { __awaiter, __decorate, __metadata } from "tslib";
import { MdcComponent } from '@aurelia-mdc-web/base';
import { MDCListFoundation, strings, cssClasses } 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';
strings.ACTION_EVENT = strings.ACTION_EVENT.toLowerCase();
strings.SELECTION_CHANGE_EVENT = strings.SELECTION_CHANGE_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
*/
let MdcList = class MdcList extends MdcComponent {
constructor() {
super(...arguments);
this.cssClasses = cssClasses;
}
singleSelectionChanged() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
yield this.initialised;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.setSingleSelection(this.singleSelection);
});
}
activatedChanged() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
yield this.initialised;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.setUseActivatedClass(this.activated);
});
}
itemsChanged() {
var _a;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.layout();
this.emit(mdcListStrings.ITEMS_CHANGED, { items: this.items }, true);
}
typeaheadChanged(hasTypeahead) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
yield this.initialised;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.setHasTypeahead(hasTypeahead);
});
}
wrapFocusChanged() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
yield this.initialised;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.setWrapFocus(this.wrapFocus);
});
}
initialSyncWithDOM() {
this.layout();
this.initializeListType();
}
get listElements() {
return Array.from(this.root.querySelectorAll(`.${cssClasses.LIST_ITEM_CLASS}`));
}
/**
* Extracts the primary text from a list item.
* @param item The list item element.
* @return The primary text in the element.
*/
getPrimaryText(item) {
var _a, _b;
const primaryText = item.querySelector(`.${cssClasses.LIST_ITEM_PRIMARY_TEXT_CLASS}`);
if (primaryText) {
return (_a = primaryText.textContent) !== null && _a !== void 0 ? _a : '';
}
const singleLineText = item.querySelector(`.${cssClasses.LIST_ITEM_TEXT_CLASS}`);
return (_b = singleLineText === null || singleLineText === void 0 ? void 0 : singleLineText.textContent) !== null && _b !== void 0 ? _b : '';
}
getDefaultFoundation() {
// DO NOT INLINE this variable. For backward compatibility, foundations take a Partial<MDCFooAdapter>.
// To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable.
const adapter = {
addClassForElementIndex: (index, className) => {
const element = this.listElements[index];
if (element) {
element.classList.add(className);
}
},
focusItemAtIndex: (index) => {
const element = this.listElements[index];
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.au.controller.viewModel.value;
this.emit(strings.ACTION_EVENT, { index, data }, /** shouldBubble */ true);
}
},
notifySelectionChange: (changedIndices) => {
this.emit(strings.SELECTION_CHANGE_EVENT, { changedIndices }, /** 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 === null || toggleEl === void 0 ? void 0 : 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 = [].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
*/
getListItemIndex_(evt) {
const eventTarget = evt.target;
const nearestParent = closest(eventTarget, `.${cssClasses.LIST_ITEM_CLASS}, .${cssClasses.ROOT}`);
// Get the index of the element if it is a list item.
if (nearestParent && matches(nearestParent, `.${cssClasses.LIST_ITEM_CLASS}`)) {
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) {
var _a;
const index = this.getListItemIndex_(evt);
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.handleFocusIn(index);
}
/**
* @hidden
* Used to figure out which element was clicked before sending the event to the foundation.
*/
handleFocusOutEvent_(evt) {
var _a;
const index = this.getListItemIndex_(evt);
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.handleFocusOut(index);
}
/**
* @hidden
* Used to figure out which element was focused when keydown event occurred before sending the event to the
* foundation.
*/
handleKeydownEvent_(evt) {
var _a;
const index = this.getListItemIndex_(evt);
const target = evt.target;
if (!target.hasAttribute('not-selectable')) {
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.handleKeydown(evt, target.classList.contains(cssClasses.LIST_ITEM_CLASS), index);
}
return true;
}
/**
* @hidden
* Used to figure out which element was clicked before sending the event to the foundation.
*/
handleClickEvent_(evt) {
var _a;
const index = this.getListItemIndex_(evt);
const target = evt.target;
// Toggle the checkbox only if it's not the target of the event, or the checkbox will have 2 change events.
const isCheckboxAlreadyUpdatedInAdapter = matches(target, strings.CHECKBOX_RADIO_SELECTOR);
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.handleClick(index, isCheckboxAlreadyUpdatedInAdapter, evt);
return true;
}
/**
* @hidden
* @return Whether typeahead is currently matching a user-specified prefix.
*/
get typeaheadInProgress() {
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, startingIndex) {
return this.foundation.typeaheadMatchItem(nextChar, startingIndex, /** skipFocus */ true);
}
layout() {
var _a;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.layout();
}
get selectedIndex() {
return this.foundation.getSelectedIndex();
}
set selectedIndex(index) {
var _a;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.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) => this.listElements.indexOf(listItem));
}
else if (radioSelectedListItem) {
this.selectedIndex = this.listElements.indexOf(radioSelectedListItem);
}
}
};
__decorate([
bindable.booleanAttr,
__metadata("design:type", Boolean)
], MdcList.prototype, "singleSelection", void 0);
__decorate([
bindable.booleanAttr,
__metadata("design:type", Boolean)
], MdcList.prototype, "activated", void 0);
__decorate([
children('mdc-list-item'),
__metadata("design:type", Array)
], MdcList.prototype, "items", void 0);
__decorate([
bindable.booleanAttr,
__metadata("design:type", Boolean)
], MdcList.prototype, "typeahead", void 0);
__decorate([
bindable.booleanAttr,
__metadata("design:type", Boolean)
], MdcList.prototype, "wrapFocus", void 0);
MdcList = __decorate([
inject(Element),
useView(PLATFORM.moduleName('./mdc-list.html')),
customElement(cssClasses.ROOT)
], MdcList);
export { MdcList };
//# sourceMappingURL=mdc-list.js.map