UNPKG

@blox/material

Version:

Material Components for Angular

952 lines 126 kB
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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