@blox/material
Version:
Material Components for Angular
952 lines • 126 kB
JavaScript
import { ContentChildren, Directive, ElementRef, HostBinding, Input, Renderer2, Output, EventEmitter, HostListener, ChangeDetectorRef, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { MDCListFoundation, strings, cssClasses } from '@material/list';
import { asBoolean } from '../../utils/value.utils';
import { AbstractMdcRipple } from '../ripple/abstract.mdc.ripple';
import { MdcEventRegistry } from '../../utils/mdc.event.registry';
import { MdcRadioDirective } from '../radio/mdc.radio.directive';
import { MdcCheckboxDirective } from '../checkbox/mdc.checkbox.directive';
import { Subject, merge, ReplaySubject } from 'rxjs';
import { takeUntil, debounceTime } from 'rxjs/operators';
/**
* Directive for a separator in a list (between list items), or as a separator in a
* list group (between lists).
*
* # Accessibility
* This directive adds a `role=separator` attribute when it is used as a separator
* between list items.
*/
export class MdcListDividerDirective {
constructor(_elm) {
/** @internal */
this._cls = true;
/** @internal */
this._role = 'separator';
/** @internal */
this._disabled = false;
this._inset = false;
this._padded = false;
if (_elm.nativeElement.nodeName.toUpperCase() !== 'LI')
this._role = null;
}
/**
* When this input is defined and does not have value false, the divider is styled with
* an inset.
*/
get inset() {
return this._inset;
}
set inset(val) {
this._inset = asBoolean(val);
}
/**
* When this input is defined and does not have value false, the divider leaves
* gaps on each site to match the padding of <code>mdcListItemMeta</code>.
*/
get padded() {
return this._padded;
}
set padded(val) {
this._padded = asBoolean(val);
}
}
MdcListDividerDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcListDivider]'
},] }
];
MdcListDividerDirective.ctorParameters = () => [
{ type: ElementRef }
];
MdcListDividerDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list-divider',] }],
_role: [{ type: HostBinding, args: ['attr.role',] }],
_disabled: [{ type: HostBinding, args: ['attr.disabled',] }],
inset: [{ type: Input }, { type: HostBinding, args: ['class.mdc-list-divider--inset',] }],
padded: [{ type: Input }, { type: HostBinding, args: ['class.mdc-list-divider--padded',] }]
};
/**
* Directive for the items of a material list.
* This directive should be used for the direct children (list items) of an
* `mdcList`.
*
* # Children
* * Use `mdcListItemText` for the text content of the list. One line and two line
* lists are supported. See `mdcListItemText` for more info.
* * Optional: `mdcListItemGraphic` for a starting detail (typically icon or image).
* * Optional: `mdcListItemMeta` for the end detail (typically icon or image).
*
* # Accessibility
* * All items in a list will get a `tabindex=-1` attribute to make them focusable,
* but not tabbable. The focused, active/current, or first (in that preference) item will
* get `tabindex=0`, so that the list can be tabbed into. Keyboard navigation
* between list items is done with arrow, home, and end keys. Keyboard based selection of
* an item (when items are selectable), can be done with the enter or space key.
* * The `role` attribute with be set to `option` for single selection lists,
* `checkbox` for list items that can be selected with embedded checkbox inputs, `radio`
* for for list items that can be selected with embedded radio inputs, `menuitem` when the
* list is part of an `mdcMenu`. Otherwise there will be no `role` attribute, so the default
* role for a standard list item (`role=listitem`) will apply.
* * Single selection lists set the `aria-selected` or `aria-current` attributes, based on the
* chosen `selectionMode` of the list. Please see [WAI-ARIA aria-current](https://www.w3.org/TR/wai-aria-1.1/#aria-current)
* for recommendations.
* * `aria-checked` will be set for lists with embedded checkbox or radio inputs.
* * Disabled list items will be included in the keyboard navigation. This follows
* [focusability of disabled controls](https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_disabled_controls)
* recommendations in the ARIA practices article. Exception: when the list is part of an `mdcMenu` or `mdcSelect`,
* disabled items are not included in the keyboard navigation.
* * As the user navigates through the list, any button and anchor elements within list items that are not focused
* will receive `tabindex=-1`. When the list item receives focus, those elements will receive `tabindex=0`.
* This allows for the user to tab through list item elements and then tab to the first element after the list.
* * Lists are interactive by default (unless `nonInteractive` is set on the `mdcList`). List items will
* show ripples when interacted with.
* * `aria-disabled` will be set for disabled list items. When the list uses checkbox or radio inputs to control
* the checked state, the disabled state will mirror the state of those inputs.
*/
export class MdcListItemDirective extends AbstractMdcRipple {
constructor(_elm, rndr, registry, doc) {
super(_elm, rndr, registry, doc);
this._elm = _elm;
/** @internal */
this._cls = true;
/** @internal */
this._role = null;
/** @internal */
this._ariaActive = null;
this._initialized = false;
this._interactive = true;
this._disabled = false;
this._active = false;
/** @internal (called valueChanged instead of valueChange so that library consumers cannot by accident use
* this for two-way binding) */
this.valueChanged = new EventEmitter();
/** @internal */
this._activationRequest = new ReplaySubject(1);
/**
* Event emitted for user action on the list item, including keyboard and mouse actions.
* This will not emit when the `mdcList` has `nonInteractive` set.
*/
this.action = new EventEmitter();
/**
* Event emitted when the active state of a list item in a single-selection list
* (`selectionMode` is `single` or `active`) is changed. This event does not emit
* for lists that do not have the mentioned `selectionMode`, and therefore does also
* not emit for lists where the active/selected state is controlled by embedded checkbox
* or radio inputs. (Note that for lists controlled by an `mdcSelect`, the `selectionMode`
* will be either `single` or `active`).
*/
this.selectedChange = new EventEmitter();
this._value = null;
}
ngAfterContentInit() {
this._initialized = true;
if (this._interactive)
this.initRipple();
}
ngOnDestroy() {
this.destroyRipple();
}
/** @internal */
_setInteractive(interactive) {
if (this._interactive !== interactive) {
this._interactive = interactive;
if (this._initialized) {
if (this._interactive)
this.initRipple();
else
this.destroyRipple();
}
}
}
/**
* If set to a value other than false, the item will be disabled. This affects styling
* and selectability, and may affect keyboard navigation.
* This input is ignored for lists where the selection is controlled by embedded checkbox
* or radio inputs. In those cases the disabled state of the input will be used instead.
*/
get disabled() {
if (this._ariaActive === 'checked') {
const input = this._getInput();
return input ? input._elm.nativeElement.disabled : false;
}
return this._disabled;
}
set disabled(val) {
this._disabled = asBoolean(val);
}
/**
* Assign this field with a value that should be reflected in the `value` property of
* a `selectionMode=single|active` or and `mdcMenu` or `mdcSelect` for the active property.
* Ignored for lists that don't offer a selection, and for lists that use checkbox/radio
* inputs for selection.
*/
get value() {
return this._value;
}
set value(newValue) {
if (this._value !== newValue) {
this._value = newValue;
this.valueChanged.emit(newValue);
}
}
/**
* This input can be used to change the active or selected state of the item. This should *not* be used for lists
* inside an `mdcSelect`/`mdcMenu`, or for lists that use checkbox/radio inputs for selection.
* Depending on the `selectionMode` of the list this will update the `selected` or `active` state of the item.
*/
set selected(val) {
let newValue = asBoolean(val);
if (newValue !== this._active)
this._activationRequest.next(val);
}
/** @internal */
get _selected() {
return (this._ariaActive === 'selected' && this._active)
|| (!this._role && this._active);
}
/** @internal */
get _activated() {
return this._ariaActive === 'current' && this._active;
}
/** @internal */
get _ariaDisabled() {
if (this.disabled) // checks checkbox/radio disabled state when appropriate
return 'true';
return null;
}
/** @internal */
get _ariaCurrent() {
if (this._ariaActive === 'current')
return this._active ? 'true' : 'false';
return null;
}
/** @internal */
get _ariaSelected() {
if (this._ariaActive === 'selected')
return this._active ? 'true' : 'false';
return null;
}
/** @internal */
get _ariaChecked() {
if (this._ariaActive === 'checked')
// (this.active: returns checked value of embedded input if appropriate)
return this.active ? 'true' : 'false';
return null;
}
/** @internal */
get active() {
if (this._ariaActive === 'checked') {
const input = this._getInput();
return input ? input._elm.nativeElement.checked : false;
}
return this._active;
}
/** @internal */
set active(value) {
if (value !== this._active) {
this._active = value;
this.selectedChange.emit(value);
}
}
/** @internal */
_getRadio() {
var _a;
return (_a = this._radios) === null || _a === void 0 ? void 0 : _a.first;
}
/** @internal */
_getCheckbox() {
var _a;
return (_a = this._checkBoxes) === null || _a === void 0 ? void 0 : _a.first;
}
/** @internal */
_getInput() {
var _a;
return (_a = (this._getCheckbox() || this._getRadio())) === null || _a === void 0 ? void 0 : _a._input;
}
}
MdcListItemDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcListItem]'
},] }
];
MdcListItemDirective.ctorParameters = () => [
{ type: ElementRef },
{ type: Renderer2 },
{ type: MdcEventRegistry },
{ type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
];
MdcListItemDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list-item',] }],
_role: [{ type: HostBinding, args: ['attr.role',] }],
_radios: [{ type: ContentChildren, args: [MdcRadioDirective, { descendants: true },] }],
_checkBoxes: [{ type: ContentChildren, args: [MdcCheckboxDirective, { descendants: true },] }],
valueChanged: [{ type: Output }],
action: [{ type: Output }],
selectedChange: [{ type: Output }],
disabled: [{ type: HostBinding, args: ['class.mdc-list-item--disabled',] }, { type: Input }],
value: [{ type: Input }],
selected: [{ type: Input }],
_selected: [{ type: HostBinding, args: ['class.mdc-list-item--selected',] }],
_activated: [{ type: HostBinding, args: ['class.mdc-list-item--activated',] }],
_ariaDisabled: [{ type: HostBinding, args: ['attr.aria-disabled',] }],
_ariaCurrent: [{ type: HostBinding, args: ['attr.aria-current',] }],
_ariaSelected: [{ type: HostBinding, args: ['attr.aria-selected',] }],
_ariaChecked: [{ type: HostBinding, args: ['attr.aria-checked',] }]
};
/**
* Directive to mark the text portion(s) of an `mdcListItem`. This directive should be the child of an `mdcListItem`.
* For single line lists, the text can be added directly to this directive.
* For two line lists, add `mdcListItemPrimaryText` and `mdcListItemSecondaryText` children.
*/
export class MdcListItemTextDirective {
constructor() {
/** @internal */
this._cls = true;
}
}
MdcListItemTextDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcListItemText]'
},] }
];
MdcListItemTextDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list-item__text',] }]
};
/**
* Directive to mark the first line of an item with "two line list" styling.
* This directive, if used, should be the child of an `mdcListItemText`.
* Using this directive will put the list "two line" mode.
*/
export class MdcListItemPrimaryTextDirective {
constructor() {
/** @internal */
this._cls = true;
}
}
MdcListItemPrimaryTextDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcListItemPrimaryText]'
},] }
];
MdcListItemPrimaryTextDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list-item__primary-text',] }]
};
/**
* Directive for the secondary text of an item with "two line list" styling.
* This directive, if used, should be the child of an `mdcListItemText`, and
* come after the `mdcListItemPrimaryText`.
*/
export class MdcListItemSecondaryTextDirective {
constructor() {
/** @internal */
this._cls = true;
}
}
MdcListItemSecondaryTextDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcListItemSecondaryText]',
},] }
];
MdcListItemSecondaryTextDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list-item__secondary-text',] }]
};
/**
* Directive for the start detail item of a list item.
* This directive, if used, should be the child of an`mdcListItem`.
*/
export class MdcListItemGraphicDirective {
constructor() {
/** @internal */
this._cls = true;
}
}
MdcListItemGraphicDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcListItemGraphic]',
},] }
];
MdcListItemGraphicDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list-item__graphic',] }]
};
/**
* Directive for the end detail item of a list item.
* This directive, if used, should be the child of an `mdcListItem`.
*/
export class MdcListItemMetaDirective {
constructor() {
/** @internal */
this._cls = true;
}
}
MdcListItemMetaDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcListItemMeta]',
},] }
];
MdcListItemMetaDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list-item__meta',] }]
};
/** @docs-private */
export var MdcListFunction;
(function (MdcListFunction) {
MdcListFunction[MdcListFunction["plain"] = 0] = "plain";
MdcListFunction[MdcListFunction["menu"] = 1] = "menu";
MdcListFunction[MdcListFunction["select"] = 2] = "select";
})(MdcListFunction || (MdcListFunction = {}));
;
// attributes on list-items that we maintain ourselves, so should be ignored
// in the adapter:
const ANGULAR_ITEM_ATTRIBUTES = [
strings.ARIA_CHECKED, strings.ARIA_SELECTED, strings.ARIA_CURRENT, strings.ARIA_DISABLED
];
// classes on list-items that we maintain ourselves, so should be ignored
// in the adapter:
const ANGULAR_ITEM_CLASSES = [
cssClasses.LIST_ITEM_DISABLED_CLASS, cssClasses.LIST_ITEM_ACTIVATED_CLASS, cssClasses.LIST_ITEM_SELECTED_CLASS
];
/**
* Lists are continuous, vertical indexes of text or images. They can be interactive, and may support
* selaction/activation of list of items. Single-line and Two-line lists are supported, as well as
* starting and end details (images or controls) on a list. A list contains `mdcListItem` children,
* and may also contain `mdcListDivider` children.
*
* A list can be used by itself, or contained inside `mdcListGroup`, `mdcMenu`, or `mdcSelect`.
*
* # Accessibility
* * See Accessibility section of `mdcListItem` for navigation, focus, and tab(index) affordances.
* * The `role` attribute will be set to `listbox` for single selection lists (`selectionMode` is `single`
* or `active`), to `radiogroup` when selection is triggered by embedded radio inputs, to
* `checkbox` when selection is triggered by embedded checkbox inputs, to `menu` when used inside
* `mdcMenu`. Otherwise there will be no `role` attribute, so the default role for a standard list
* (`role=list`) will apply.
* * You should set an appropriate `label` for checkbox based selection lists. The
* `label` will be reflected to the `aria-label` attribute.
*/
export class MdcListDirective {
constructor(_elm, rndr, cdRef, doc) {
this._elm = _elm;
this.rndr = rndr;
this.cdRef = cdRef;
this.onDestroy$ = new Subject();
/** @internal */
this._cls = true;
/** @internal */
this.itemsChanged = new EventEmitter();
/** @internal */
this.itemValuesChanged = new EventEmitter();
/** @internal */
this.itemAction = new EventEmitter();
/** @internal */
this._twoLine = false;
/**
* Label announcing the purpose of the list. Should be set for lists that embed checkbox inputs
* for activation/selection. The label is reflected in the `aria-label` attribute value.
*
* @internal
*/
this.label = null;
/**
* Link to the id of an element that announces the purpose of the list. This will be set automatically
* to the id of the `mdcFloatingLabel` when the list is part of an `mdcSelect`.
*
* @internal
*/
this.labelledBy = null;
this._function = MdcListFunction.plain;
/** @internal */
this._hidden = false;
this._dense = false;
this._avatar = false;
this._nonInteractive = false;
this._selectionMode = null;
this._wrapFocus = false;
this.mdcAdapter = {
getAttributeForElementIndex: (index, attr) => {
var _a, _b;
if (attr === strings.ARIA_CURRENT)
return (_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a._ariaCurrent;
return (_b = this.getItem(index)) === null || _b === void 0 ? void 0 : _b._elm.nativeElement.getAttribute(attr);
},
getListItemCount: () => this._items.length,
getFocusedElementIndex: () => this._items.toArray().findIndex(i => i._elm.nativeElement === this.document.activeElement),
setAttributeForElementIndex: (index, attribute, value) => {
var _a;
// ignore attributes we maintain ourselves
if (!ANGULAR_ITEM_ATTRIBUTES.find(a => a === attribute)) {
const elm = (_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a._elm.nativeElement;
if (elm)
this.rndr.setAttribute(elm, attribute, value);
}
},
addClassForElementIndex: (index, className) => {
var _a;
if (!ANGULAR_ITEM_CLASSES.find(c => c === className)) {
const elm = (_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a._elm.nativeElement;
if (elm)
this.rndr.addClass(elm, className);
}
},
removeClassForElementIndex: (index, className) => {
var _a;
if (!ANGULAR_ITEM_CLASSES.find(c => c === className)) {
const elm = (_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a._elm.nativeElement;
if (elm)
this.rndr.addClass(elm, className);
}
},
focusItemAtIndex: (index) => { var _a; return (_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a._elm.nativeElement.focus(); },
setTabIndexForListItemChildren: (index, tabIndexValue) => {
var _a;
// TODO check this plays nice with our own components (mdcButton etc.)
// TODO build this in an abstract class for our own elements?
// TODO limit to our own elements/custom directive?
const elm = (_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a._elm.nativeElement;
const listItemChildren = [].slice.call(elm.querySelectorAll(strings.CHILD_ELEMENTS_TO_TOGGLE_TABINDEX));
listItemChildren.forEach((el) => this.rndr.setAttribute(el, 'tabindex', tabIndexValue));
},
hasRadioAtIndex: () => this._role === 'radiogroup',
hasCheckboxAtIndex: () => this._role === 'group',
isCheckboxCheckedAtIndex: (index) => { var _a, _b, _c; return !!((_c = (_b = (_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a._getCheckbox()) === null || _b === void 0 ? void 0 : _b._input) === null || _c === void 0 ? void 0 : _c.checked); },
isRootFocused: () => this.document.activeElement === this._elm.nativeElement,
listItemAtIndexHasClass: (index, className) => {
var _a, _b;
if (className === cssClasses.LIST_ITEM_DISABLED_CLASS)
return !!((_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a.disabled);
return !!((_b = this.getItem(index)) === null || _b === void 0 ? void 0 : _b._elm.nativeElement.classList.contains(className));
},
setCheckedCheckboxOrRadioAtIndex: (index, isChecked) => {
var _a, _b, _c, _d;
const item = this.getItem(index);
const input = (_b = (_a = ((item === null || item === void 0 ? void 0 : item._getRadio()) || (item === null || item === void 0 ? void 0 : item._getCheckbox()))) === null || _a === void 0 ? void 0 : _a._input) === null || _b === void 0 ? void 0 : _b._elm.nativeElement;
if (input) {
input.checked = isChecked;
// simulate user interaction, as this is triggered from a user interaction:
const event = this.document.createEvent('Event');
event.initEvent('change', true, true);
input.dispatchEvent(event);
// checkbox input listens to clicks, not changed events, so let it know about the change:
(_d = (_c = item === null || item === void 0 ? void 0 : item._getCheckbox()) === null || _c === void 0 ? void 0 : _c._input) === null || _d === void 0 ? void 0 : _d._onChange();
}
},
notifyAction: (index) => {
const item = this.getItem(index);
if (item && !(item === null || item === void 0 ? void 0 : item.disabled)) {
item.action.emit();
this.itemAction.emit({ index, value: item.value });
}
},
isFocusInsideList: () => {
return this._elm.nativeElement.contains(this.document.activeElement);
},
};
this.document = doc; // work around ngc issue https://github.com/angular/angular/issues/20351
}
ngAfterContentInit() {
merge(this._checkboxes.changes, this._radios.changes).pipe(takeUntil(this.onDestroy$)).subscribe(() => {
this.updateItems();
this.updateLayout();
this.updateFoundationSelections();
});
this._items.changes.subscribe(() => {
// when number of items changes, we have to reinitialize the foundation, because
// the focusused item index that the foundation keeps may be invalidated:
this.destroyFoundation();
this.updateItems();
this.initFoundation();
this.itemsChanged.emit();
this.itemValuesChanged.emit();
merge(this._items.map(item => item.valueChanged.asObservable())).pipe(takeUntil(this.onDestroy$), takeUntil(this.itemsChanged), debounceTime(1)).subscribe(() => {
this.itemValuesChanged.emit();
});
this.subscribeItemActivationRequests();
});
this._primaryTexts.changes.subscribe(_ => this._twoLine = this._primaryTexts.length > 0);
this.updateItems();
this._twoLine = this._primaryTexts.length > 0;
this.initFoundation();
this.subscribeItemActivationRequests();
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
this.destroyFoundation();
}
initFoundation() {
this.foundation = new MDCListFoundation(this.mdcAdapter);
this.foundation.init();
this.updateLayout();
const focus = this.getListItemIndex({ target: this.document.activeElement });
if (focus !== -1) // only way to restore focus when a list item already had focus:
this.foundation['focusedItemIndex_'] = focus;
this.updateFoundationSelections();
this.foundation.setWrapFocus(this._wrapFocus);
}
destroyFoundation() {
var _a;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.destroy();
this.foundation = null;
}
subscribeItemActivationRequests() {
this._items.map(item => {
item._activationRequest.asObservable().pipe(takeUntil(this.onDestroy$), takeUntil(this.itemsChanged)).subscribe(active => this.activateOrSelectItem(item, active));
});
}
updateItems() {
let itemRole = {
'menu': 'menuitem',
'listbox': 'option',
'group': 'checkbox',
'radiogroup': 'radio'
}[this._role] || null;
let ariaActive = {
'menu': null,
'listbox': this._selectionMode === 'active' ? 'current' : 'selected',
'group': 'checked',
'radiogroup': 'checked'
}[this._role] || null;
if (this._items) {
const firstTabbable = this._nonInteractive ? null :
this._items.find(item => item._elm.nativeElement.tabIndex === 0) ||
this._items.find(item => item.active) ||
this._items.first;
this._items.forEach(item => {
item._role = itemRole;
item._ariaActive = ariaActive;
item._setInteractive(!this._nonInteractive);
if (this._nonInteractive)
// not focusable if not interactive:
this.rndr.removeAttribute(item._elm.nativeElement, 'tabindex');
this.rndr.setAttribute(item._elm.nativeElement, 'tabindex', item === firstTabbable ? '0' : '-1');
});
// child components were updated (in updateItems above)
// let angular know to prevent ExpressionChangedAfterItHasBeenCheckedError:
this.cdRef.detectChanges();
}
}
updateLayout() {
var _a;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.layout();
}
updateFoundationSelections() {
var _a, _b;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.setSingleSelection(this._role === 'listbox');
(_b = this.foundation) === null || _b === void 0 ? void 0 : _b.setSelectedIndex(this.getSelection());
}
updateItemSelections(active) {
const activeIndexes = typeof active === 'number' ? [active] : active;
// first deactivate, then activate
this._items.toArray().forEach((item, idx) => {
if (activeIndexes.indexOf(idx) === -1)
item.active = false;
});
this._items.toArray().forEach((item, idx) => {
if (activeIndexes.indexOf(idx) !== -1)
item.active = true;
});
}
activateOrSelectItem(item, active) {
var _a;
let activeIndexes = -1;
if (!active) {
if (this._role === 'group' || !this._role)
activeIndexes = this._items.toArray().map((v, i) => v.active && v !== item ? i : null).filter(i => i != null);
else if (this._role === 'listbox' || this._role === 'radiogroup' || this._role === 'menu')
activeIndexes = this._items.toArray().findIndex(i => i.active && i !== item);
}
else {
if (this._role === 'group' || !this._role)
activeIndexes = this._items.toArray().map((v, i) => v.active || v === item ? i : null).filter(i => i != null);
else if (this._role === 'listbox' || this._role === 'radiogroup' || this._role === 'menu')
activeIndexes = this._items.toArray().findIndex(i => i === item);
}
if (this._role === 'group' || this._role === 'listbox' || this._role === 'radiogroup' || this._role === 'menu')
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.setSelectedIndex(activeIndexes);
this.updateItemSelections(activeIndexes);
this.cdRef.detectChanges();
}
getSelection(forFoundation = true) {
if (this._role === 'listbox' || this._role === 'radiogroup' || this._role === 'menu')
return this._items.toArray().findIndex(i => i.active);
if (this._role === 'group')
return this._items.toArray().map((v, i) => v.active ? i : null).filter(i => i != null);
return forFoundation ? -1 : this._items.toArray().map((v, i) => v.active ? i : null).filter(i => i != null);
}
/** @internal */
getSelectedItem() {
if (this._role === 'listbox' || this._role === 'radiogroup' || this._role === 'menu')
return this._items.find(i => i.active);
return null;
}
/** @internal */
get _role() {
if (this._function === MdcListFunction.menu)
return 'menu';
if (this._function === MdcListFunction.select)
return 'listbox';
if (this._selectionMode === 'single' || this._selectionMode === 'active')
return 'listbox';
if (this._checkboxes && this._checkboxes.length > 0)
return 'group';
if (this._radios && this._radios.length > 0)
return 'radiogroup';
return null;
}
/** @internal */
get _ariaHidden() {
return (this._hidden && this._function === MdcListFunction.menu) ? 'true' : null;
}
/** @internal */
get _ariaOrientation() {
return this._function === MdcListFunction.menu ? 'vertical' : null;
}
/** @internal */
get _isMenu() {
return this._function === MdcListFunction.menu;
}
/** @internal */
get _tabindex() {
// the root of a menu should be focusable
return this._function === MdcListFunction.menu ? "-1" : null;
}
/** @internal */
_setFunction(val) {
var _a;
this._function = val;
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.setSingleSelection(this._role === 'listbox');
this.updateItems();
}
/**
* When this input is defined and does not have value false, the list will be styled more
* compact.
*/
get dense() {
return this._dense;
}
set dense(val) {
this._dense = asBoolean(val);
}
/**
* When set to `single` or `active`, the list will act as a single-selection-list.
* This enables the enter and space keys for selecting/deselecting a list item,
* and sets the appropriate accessibility options.
* When not set, the list will not act as a single-selection-list.
*
* When using `single`, the active selection is announced with `aria-selected`
* attributes on the list elements. When using `active`, the active selection
* is announced with `aria-current`. See [WAI-ARIA aria-current](https://www.w3.org/TR/wai-aria-1.1/#aria-current)
* article for recommendations on usage.
*
* The selectionMode is ignored when there are embedded checkbox or radio inputs inside the list, in which case
* those inputs will trigger selection of list items.
*/
get selectionMode() {
return this._selectionMode;
}
set selectionMode(val) {
if (val !== this._selectionMode) {
if (val === 'single' || val === 'active')
this._selectionMode = val;
else
this._selectionMode = null;
this.updateItems();
if (this.foundation) {
this.foundation.setSingleSelection(this._role === 'listbox');
this.foundation.setSelectedIndex(this.getSelection());
this.updateItemSelections(this.getSelection(false));
}
}
}
/**
* When this input is defined and does not have value false, the list will be made
* non-interactive. Users will not be able to interact with list items, and the styling will
* reflect this (e.g. by not adding ripples to the items).
*/
get nonInteractive() {
return this._nonInteractive;
}
set nonInteractive(val) {
let newValue = asBoolean(val);
if (newValue !== this._nonInteractive) {
this._nonInteractive = newValue;
this.updateItems();
}
}
/**
* When this input is defined and does not have value false, focus will wrap from last to
* first and vice versa when using keyboard navigation through list items.
*/
get wrapFocus() {
return this._wrapFocus;
}
set wrapFocus(val) {
var _a;
this._wrapFocus = asBoolean(val);
(_a = this.foundation) === null || _a === void 0 ? void 0 : _a.setWrapFocus(this._wrapFocus);
}
/**
* When this input is defined and does not have value false, elements with directive <code>mdcListItemGraphic</code>
* will be styled for avatars: large, circular elements that lend themselves well to contact images, profile pictures, etc.
*/
get avatarList() {
return this._avatar;
}
set avatarList(val) {
this._avatar = asBoolean(val);
}
/** @internal */
_onFocusIn(event) {
if (this.foundation && !this._nonInteractive) {
this.foundation.setSelectedIndex(this.getSelection());
const index = this.getListItemIndex(event);
this.foundation.handleFocusIn(event, index);
}
}
/** @internal */
_onFocusOut(event) {
if (this.foundation && !this._nonInteractive) {
this.foundation.setSelectedIndex(this.getSelection());
const index = this.getListItemIndex(event);
this.foundation.handleFocusOut(event, index);
}
}
/** @internal */
_onKeydown(event) {
var _a;
if (this.foundation && !this._nonInteractive) {
this.foundation.setSelectedIndex(this.getSelection());
const index = this.getListItemIndex(event);
const onRoot = ((_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a._elm.nativeElement) === event.target;
this.foundation.handleKeydown(event, onRoot, index);
if (this._role === 'listbox')
this.updateItemSelections(this.foundation.getSelectedIndex());
}
}
/** @internal */
_onClick(event) {
var _a, _b, _c, _d;
if (this.foundation && !this._nonInteractive) {
this.foundation.setSelectedIndex(this.getSelection());
const index = this.getListItemIndex(event);
// only toggle radio/checkbox input if it's not already toggled from the event:
const inputElement = ((_b = (_a = this.getItem(index)) === null || _a === void 0 ? void 0 : _a._getCheckbox()) === null || _b === void 0 ? void 0 : _b._input._elm.nativeElement) || ((_d = (_c = this.getItem(index)) === null || _c === void 0 ? void 0 : _c._getRadio()) === null || _d === void 0 ? void 0 : _d._input._elm.nativeElement);
const toggleInput = event.target !== inputElement;
this.foundation.handleClick(index, toggleInput);
if (this._role === 'listbox')
this.updateItemSelections(this.foundation.getSelectedIndex());
}
}
/** @internal */
getItem(index) {
if (index >= 0 && index < this._items.length)
return this._items.toArray()[index];
return null;
}
/** @internal */
getItems() {
var _a;
return ((_a = this._items) === null || _a === void 0 ? void 0 : _a.toArray()) || [];
}
/** @internal */
getItemByElement(element) {
var _a;
return ((_a = this._items) === null || _a === void 0 ? void 0 : _a.find(i => i._elm.nativeElement === element)) || null;
}
getListItemIndex(evt) {
let eventTarget = evt.target;
const itemElements = this._items.map(item => item._elm.nativeElement);
while (eventTarget && eventTarget !== this._elm.nativeElement) {
const index = itemElements.findIndex(e => e === eventTarget);
if (index !== -1)
return index;
eventTarget = eventTarget.parentElement;
}
return -1;
}
}
MdcListDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcList]',
},] }
];
MdcListDirective.ctorParameters = () => [
{ type: ElementRef },
{ type: Renderer2 },
{ type: ChangeDetectorRef },
{ type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
];
MdcListDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list',] }],
_items: [{ type: ContentChildren, args: [MdcListItemDirective,] }],
_primaryTexts: [{ type: ContentChildren, args: [MdcListItemPrimaryTextDirective, { descendants: true },] }],
_checkboxes: [{ type: ContentChildren, args: [MdcCheckboxDirective, { descendants: true },] }],
_radios: [{ type: ContentChildren, args: [MdcRadioDirective, { descendants: true },] }],
itemsChanged: [{ type: Output }],
itemValuesChanged: [{ type: Output }],
itemAction: [{ type: Output }],
_twoLine: [{ type: HostBinding, args: ['class.mdc-list--two-line',] }],
label: [{ type: HostBinding, args: ['attr.aria-label',] }, { type: Input }],
labelledBy: [{ type: HostBinding, args: ['attr.aria-labelledBy',] }, { type: Input }],
_role: [{ type: HostBinding, args: ['attr.role',] }],
_ariaHidden: [{ type: HostBinding, args: ['attr.aria-hidden',] }],
_ariaOrientation: [{ type: HostBinding, args: ['attr.aria-orientation',] }],
_isMenu: [{ type: HostBinding, args: ['class.mdc-menu__items',] }],
_tabindex: [{ type: HostBinding, args: ['attr.tabindex',] }],
dense: [{ type: Input }, { type: HostBinding, args: ['class.mdc-list--dense',] }],
selectionMode: [{ type: Input }],
nonInteractive: [{ type: Input }, { type: HostBinding, args: ['class.mdc-list--non-interactive',] }],
wrapFocus: [{ type: Input }],
avatarList: [{ type: Input }, { type: HostBinding, args: ['class.mdc-list--avatar-list',] }],
_onFocusIn: [{ type: HostListener, args: ['focusin', ['$event'],] }],
_onFocusOut: [{ type: HostListener, args: ['focusout', ['$event'],] }],
_onKeydown: [{ type: HostListener, args: ['keydown', ['$event'],] }],
_onClick: [{ type: HostListener, args: ['click', ['$event'],] }]
};
/**
* Directive for a header inside a list group (<code>mdcListGroup</code>) directive.
*/
export class MdcListGroupSubHeaderDirective {
constructor() {
/** @internal */
this._cls = true;
}
}
MdcListGroupSubHeaderDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcListGroupSubHeader]'
},] }
];
MdcListGroupSubHeaderDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list-group__subheader',] }]
};
/**
* Directive for a material designed list group, grouping several `mdcList` lists.
* List groups should contain elements with `mdcListGroupSubHeader`,
* and `mdcList` directives. Lists may be separated by `mdcListSeparator` directives.
*/
export class MdcListGroupDirective {
constructor() {
/** @internal */
this._cls = true;
}
}
MdcListGroupDirective.decorators = [
{ type: Directive, args: [{
selector: '[mdcListGroup]'
},] }
];
MdcListGroupDirective.propDecorators = {
_cls: [{ type: HostBinding, args: ['class.mdc-list-group',] }]
};
export const LIST_DIRECTIVES = [
MdcListDividerDirective,
MdcListItemDirective,
MdcListItemTextDirective,
MdcListItemPrimaryTextDirective,
MdcListItemSecondaryTextDirective,
MdcListItemGraphicDirective,
MdcListItemMetaDirective,
MdcListDirective,
MdcListGroupSubHeaderDirective,
MdcListGroupDirective
];
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWRjLmxpc3QuZGlyZWN0aXZlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvbGlzdC9tZGMubGlzdC5kaXJlY3RpdmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFvQixlQUFlLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUN0RSxTQUFTLEVBQUUsTUFBTSxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQy9HLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUMzQyxPQUFPLEVBQUUsaUJBQWlCLEVBQWtCLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUN4RixPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDcEQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDbEUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbEUsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDakUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDMUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3JELE9BQU8sRUFBRSxTQUFTLEVBQUUsWUFBWSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFekQ7Ozs7Ozs7R0FPRztBQUlILE1BQU0sT0FBTyx1QkFBdUI7SUFVaEMsWUFBWSxJQUFnQjtRQVQ1QixnQkFBZ0I7UUFDZ0MsU0FBSSxHQUFHLElBQUksQ0FBQztRQUM1RCxnQkFBZ0I7UUFDVSxVQUFLLEdBQWtCLFdBQVcsQ0FBQztRQUM3RCxnQkFBZ0I7UUFDYyxjQUFTLEdB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