import { DOCUMENT } from '@angular/common'; import { Injectable, Inject, ɵɵdefineInjectable, ɵɵinject, QueryList, isDevMode, NgZone, Directive, ElementRef, Input, InjectionToken, Optional, EventEmitter, Output, NgModule } from '@angular/core'; import { Subject, Subscription, of } from 'rxjs'; import { hasModifierKey, A, Z, ZERO, NINE, LEFT_ARROW, RIGHT_ARROW, UP_ARROW, DOWN_ARROW, TAB } from '@angular/cdk/keycodes'; import { tap, debounceTime, filter, map, take } from 'rxjs/operators'; import { coerceBooleanProperty, coerceElement } from '@angular/cdk/coercion'; import { Platform, normalizePassiveListenerOptions, PlatformModule } from '@angular/cdk/platform'; import { ContentObserver, ObserversModule } from '@angular/cdk/observers'; /** * @fileoverview added by tsickle * Generated from: src/cdk/a11y/aria-describer/aria-reference.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * IDs are deliminated by an empty space, as per the spec. * @type {?} */ const ID_DELIMINATOR = ' '; /** * Adds the given ID to the specified ARIA attribute on an element. * Used for attributes such as aria-labelledby, aria-owns, etc. * @param {?} el * @param {?} attr * @param {?} id * @return {?} */ function addAriaReferencedId(el, attr, id) { /** @type {?} */ const ids = getAriaReferenceIds(el, attr); if (ids.some((/** * @param {?} existingId * @return {?} */ existingId => existingId.trim() == id.trim()))) { return; } ids.push(id.trim()); el.setAttribute(attr, ids.join(ID_DELIMINATOR)); } /** * Removes the given ID from the specified ARIA attribute on an element. * Used for attributes such as aria-labelledby, aria-owns, etc. * @param {?} el * @param {?} attr * @param {?} id * @return {?} */ function removeAriaReferencedId(el, attr, id) { /** @type {?} */ const ids = getAriaReferenceIds(el, attr); /** @type {?} */ const filteredIds = ids.filter((/** * @param {?} val * @return {?} */ val => val != id.trim())); if (filteredIds.length) { el.setAttribute(attr, filteredIds.join(ID_DELIMINATOR)); } else { el.removeAttribute(attr); } } /** * Gets the list of IDs referenced by the given ARIA attribute on an element. * Used for attributes such as aria-labelledby, aria-owns, etc. * @param {?} el * @param {?} attr * @return {?} */ function getAriaReferenceIds(el, attr) { // Get string array of all individual ids (whitespace deliminated) in the attribute value return (el.getAttribute(attr) || '').match(/\S+/g) || []; } /** * @fileoverview added by tsickle * Generated from: src/cdk/a11y/aria-describer/aria-describer.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Interface used to register message elements and keep a count of how many registrations have * the same message and the reference to the message element used for the `aria-describedby`. * @record */ function RegisteredMessage() { } if (false) { /** * The element containing the message. * @type {?} */ RegisteredMessage.prototype.messageElement; /** * The number of elements that reference this message element via `aria-describedby`. * @type {?} */ RegisteredMessage.prototype.referenceCount; } /** * ID used for the body container where all messages are appended. * @type {?} */ const MESSAGES_CONTAINER_ID = 'cdk-describedby-message-container'; /** * ID prefix used for each created message element. * @type {?} */ const CDK_DESCRIBEDBY_ID_PREFIX = 'cdk-describedby-message'; /** * Attribute given to each host element that is described by a message element. * @type {?} */ const CDK_DESCRIBEDBY_HOST_ATTRIBUTE = 'cdk-describedby-host'; /** * Global incremental identifier for each registered message element. * @type {?} */ let nextId = 0; /** * Global map of all registered message elements that have been placed into the document. * @type {?} */ const messageRegistry = new Map(); /** * Container for all registered messages. * @type {?} */ let messagesContainer = null; /** * Utility that creates visually hidden elements with a message content. Useful for elements that * want to use aria-describedby to further describe themselves without adding additional visual * content. */ class AriaDescriber { /** * @param {?} _document */ constructor(_document) { this._document = _document; } /** * Adds to the host element an aria-describedby reference to a hidden element that contains * the message. If the same message has already been registered, then it will reuse the created * message element. * @param {?} hostElement * @param {?} message * @return {?} */ describe(hostElement, message) { if (!this._canBeDescribed(hostElement, message)) { return; } if (typeof message !== 'string') { // We need to ensure that the element has an ID. this._setMessageId(message); messageRegistry.set(message, { messageElement: message, referenceCount: 0 }); } else if (!messageRegistry.has(message)) { this._createMessageElement(message); } if (!this._isElementDescribedByMessage(hostElement, message)) { this._addMessageReference(hostElement, message); } } /** * Removes the host element's aria-describedby reference to the message element. * @param {?} hostElement * @param {?} message * @return {?} */ removeDescription(hostElement, message) { if (!this._isElementNode(hostElement)) { return; } if (this._isElementDescribedByMessage(hostElement, message)) { this._removeMessageReference(hostElement, message); } // If the message is a string, it means that it's one that we created for the // consumer so we can remove it safely, otherwise we should leave it in place. if (typeof message === 'string') { /** @type {?} */ const registeredMessage = messageRegistry.get(message); if (registeredMessage && registeredMessage.referenceCount === 0) { this._deleteMessageElement(message); } } if (messagesContainer && messagesContainer.childNodes.length === 0) { this._deleteMessagesContainer(); } } /** * Unregisters all created message elements and removes the message container. * @return {?} */ ngOnDestroy() { /** @type {?} */ const describedElements = this._document.querySelectorAll(`[${CDK_DESCRIBEDBY_HOST_ATTRIBUTE}]`); for (let i = 0; i < describedElements.length; i++) { this._removeCdkDescribedByReferenceIds(describedElements[i]); describedElements[i].removeAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE); } if (messagesContainer) { this._deleteMessagesContainer(); } messageRegistry.clear(); } /** * Creates a new element in the visually hidden message container element with the message * as its content and adds it to the message registry. * @private * @param {?} message * @return {?} */ _createMessageElement(message) { /** @type {?} */ const messageElement = this._document.createElement('div'); this._setMessageId(messageElement); messageElement.textContent = message; this._createMessagesContainer(); (/** @type {?} */ (messagesContainer)).appendChild(messageElement); messageRegistry.set(message, { messageElement, referenceCount: 0 }); } /** * Assigns a unique ID to an element, if it doesn't have one already. * @private * @param {?} element * @return {?} */ _setMessageId(element) { if (!element.id) { element.id = `${CDK_DESCRIBEDBY_ID_PREFIX}-${nextId++}`; } } /** * Deletes the message element from the global messages container. * @private * @param {?} message * @return {?} */ _deleteMessageElement(message) { /** @type {?} */ const registeredMessage = messageRegistry.get(message); /** @type {?} */ const messageElement = registeredMessage && registeredMessage.messageElement; if (messagesContainer && messageElement) { messagesContainer.removeChild(messageElement); } messageRegistry.delete(message); } /** * Creates the global container for all aria-describedby messages. * @private * @return {?} */ _createMessagesContainer() { if (!messagesContainer) { /** @type {?} */ const preExistingContainer = this._document.getElementById(MESSAGES_CONTAINER_ID); // When going from the server to the client, we may end up in a situation where there's // already a container on the page, but we don't have a reference to it. Clear the // old container so we don't get duplicates. Doing this, instead of emptying the previous // container, should be slightly faster. if (preExistingContainer) { (/** @type {?} */ (preExistingContainer.parentNode)).removeChild(preExistingContainer); } messagesContainer = this._document.createElement('div'); messagesContainer.id = MESSAGES_CONTAINER_ID; messagesContainer.setAttribute('aria-hidden', 'true'); messagesContainer.style.display = 'none'; this._document.body.appendChild(messagesContainer); } } /** * Deletes the global messages container. * @private * @return {?} */ _deleteMessagesContainer() { if (messagesContainer && messagesContainer.parentNode) { messagesContainer.parentNode.removeChild(messagesContainer); messagesContainer = null; } } /** * Removes all cdk-describedby messages that are hosted through the element. * @private * @param {?} element * @return {?} */ _removeCdkDescribedByReferenceIds(element) { // Remove all aria-describedby reference IDs that are prefixed by CDK_DESCRIBEDBY_ID_PREFIX /** @type {?} */ const originalReferenceIds = getAriaReferenceIds(element, 'aria-describedby') .filter((/** * @param {?} id * @return {?} */ id => id.indexOf(CDK_DESCRIBEDBY_ID_PREFIX) != 0)); element.setAttribute('aria-describedby', originalReferenceIds.join(' ')); } /** * Adds a message reference to the element using aria-describedby and increments the registered * message's reference count. * @private * @param {?} element * @param {?} message * @return {?} */ _addMessageReference(element, message) { /** @type {?} */ const registeredMessage = (/** @type {?} */ (messageRegistry.get(message))); // Add the aria-describedby reference and set the // describedby_host attribute to mark the element. addAriaReferencedId(element, 'aria-describedby', registeredMessage.messageElement.id); element.setAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE, ''); registeredMessage.referenceCount++; } /** * Removes a message reference from the element using aria-describedby * and decrements the registered message's reference count. * @private * @param {?} element * @param {?} message * @return {?} */ _removeMessageReference(element, message) { /** @type {?} */ const registeredMessage = (/** @type {?} */ (messageRegistry.get(message))); registeredMessage.referenceCount--; removeAriaReferencedId(element, 'aria-describedby', registeredMessage.messageElement.id); element.removeAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE); } /** * Returns true if the element has been described by the provided message ID. * @private * @param {?} element * @param {?} message * @return {?} */ _isElementDescribedByMessage(element, message) { /** @type {?} */ const referenceIds = getAriaReferenceIds(element, 'aria-describedby'); /** @type {?} */ const registeredMessage = messageRegistry.get(message); /** @type {?} */ const messageId = registeredMessage && registeredMessage.messageElement.id; return !!messageId && referenceIds.indexOf(messageId) != -1; } /** * Determines whether a message can be described on a particular element. * @private * @param {?} element * @param {?} message * @return {?} */ _canBeDescribed(element, message) { if (!this._isElementNode(element)) { return false; } if (message && typeof message === 'object') { // We'd have to make some assumptions about the description element's text, if the consumer // passed in an element. Assume that if an element is passed in, the consumer has verified // that it can be used as a description. return true; } /** @type {?} */ const trimmedMessage = message == null ? '' : `${message}`.trim(); /** @type {?} */ const ariaLabel = element.getAttribute('aria-label'); // We shouldn't set descriptions if they're exactly the same as the `aria-label` of the // element, because screen readers will end up reading out the same text twice in a row. return trimmedMessage ? (!ariaLabel || ariaLabel.trim() !== trimmedMessage) : false; } /** * Checks whether a node is an Element node. * @private * @param {?} element * @return {?} */ _isElementNode(element) { return element.nodeType === this._document.ELEMENT_NODE; } } AriaDescriber.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; /** @nocollapse */ AriaDescriber.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; /** @nocollapse */ AriaDescriber.ɵprov = ɵɵdefineInjectable({ factory: function AriaDescriber_Factory() { return new AriaDescriber(ɵɵinject(DOCUMENT)); }, token: AriaDescriber, providedIn: "root" }); if (false) { /** * @type {?} * @private */ AriaDescriber.prototype._document; } /** * @fileoverview added by tsickle * Generated from: src/cdk/a11y/key-manager/list-key-manager.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * This interface is for items that can be passed to a ListKeyManager. * @record */ function ListKeyManagerOption() { } if (false) { /** * Whether the option is disabled. * @type {?|undefined} */ ListKeyManagerOption.prototype.disabled; /** * Gets the label for this option. * @return {?} */ ListKeyManagerOption.prototype.getLabel = function () { }; } /** * This class manages keyboard events for selectable lists. If you pass it a query list * of items, it will set the active item correctly when arrow events occur. * @template T */ class ListKeyManager { /** * @param {?} _items */ constructor(_items) { this._items = _items; this._activeItemIndex = -1; this._activeItem = null; this._wrap = false; this._letterKeyStream = new Subject(); this._typeaheadSubscription = Subscription.EMPTY; this._vertical = true; this._allowedModifierKeys = []; /** * Predicate function that can be used to check whether an item should be skipped * by the key manager. By default, disabled items are skipped. */ this._skipPredicateFn = (/** * @param {?} item * @return {?} */ (item) => item.disabled); // Buffer for the letters that the user has pressed when the typeahead option is turned on. this._pressedLetters = []; /** * Stream that emits any time the TAB key is pressed, so components can react * when focus is shifted off of the list. */ this.tabOut = new Subject(); /** * Stream that emits whenever the active item of the list manager changes. */ this.change = new Subject(); // We allow for the items to be an array because, in some cases, the consumer may // not have access to a QueryList of the items they want to manage (e.g. when the // items aren't being collected via `ViewChildren` or `ContentChildren`). if (_items instanceof QueryList) { _items.changes.subscribe((/** * @param {?} newItems * @return {?} */ (newItems) => { if (this._activeItem) { /** @type {?} */ const itemArray = newItems.toArray(); /** @type {?} */ const newIndex = itemArray.indexOf(this._activeItem); if (newIndex > -1 && newIndex !== this._activeItemIndex) { this._activeItemIndex = newIndex; } } })); } } /** * Sets the predicate function that determines which items should be skipped by the * list key manager. * @template THIS * @this {THIS} * @param {?} predicate Function that determines whether the given item should be skipped. * @return {THIS} */ skipPredicate(predicate) { (/** @type {?} */ (this))._skipPredicateFn = predicate; return (/** @type {?} */ (this)); } /** * Configures wrapping mode, which determines whether the active item will wrap to * the other end of list when there are no more items in the given direction. * @template THIS * @this {THIS} * @param {?=} shouldWrap Whether the list should wrap when reaching the end. * @return {THIS} */ withWrap(shouldWrap = true) { (/** @type {?} */ (this))._wrap = shouldWrap; return (/** @type {?} */ (this)); } /** * Configures whether the key manager should be able to move the selection vertically. * @template THIS * @this {THIS} * @param {?=} enabled Whether vertical selection should be enabled. * @return {THIS} */ withVerticalOrientation(enabled = true) { (/** @type {?} */ (this))._vertical = enabled; return (/** @type {?} */ (this)); } /** * Configures the key manager to move the selection horizontally. * Passing in `null` will disable horizontal movement. * @template THIS * @this {THIS} * @param {?} direction Direction in which the selection can be moved. * @return {THIS} */ withHorizontalOrientation(direction) { (/** @type {?} */ (this))._horizontal = direction; return (/** @type {?} */ (this)); } /** * Modifier keys which are allowed to be held down and whose default actions will be prevented * as the user is pressing the arrow keys. Defaults to not allowing any modifier keys. * @template THIS * @this {THIS} * @param {?} keys * @return {THIS} */ withAllowedModifierKeys(keys) { (/** @type {?} */ (this))._allowedModifierKeys = keys; return (/** @type {?} */ (this)); } /** * Turns on typeahead mode which allows users to set the active item by typing. * @template THIS * @this {THIS} * @param {?=} debounceInterval Time to wait after the last keystroke before setting the active item. * @return {THIS} */ withTypeAhead(debounceInterval = 200) { if ((/** @type {?} */ (this))._items.length && (/** @type {?} */ (this))._items.some((/** * @param {?} item * @return {?} */ item => typeof item.getLabel !== 'function'))) { throw Error('ListKeyManager items in typeahead mode must implement the `getLabel` method.'); } (/** @type {?} */ (this))._typeaheadSubscription.unsubscribe(); // Debounce the presses of non-navigational keys, collect the ones that correspond to letters // and convert those letters back into a string. Afterwards find the first item that starts // with that string and select it. (/** @type {?} */ (this))._typeaheadSubscription = (/** @type {?} */ (this))._letterKeyStream.pipe(tap((/** * @param {?} letter * @return {?} */ letter => (/** @type {?} */ (this))._pressedLetters.push(letter))), debounceTime(debounceInterval), filter((/** * @return {?} */ () => (/** @type {?} */ (this))._pressedLetters.length > 0)), map((/** * @return {?} */ () => (/** @type {?} */ (this))._pressedLetters.join('')))).subscribe((/** * @param {?} inputString * @return {?} */ inputString => { /** @type {?} */ const items = (/** @type {?} */ (this))._getItemsArray(); // Start at 1 because we want to start searching at the item immediately // following the current active item. for (let i = 1; i < items.length + 1; i++) { /** @type {?} */ const index = ((/** @type {?} */ (this))._activeItemIndex + i) % items.length; /** @type {?} */ const item = items[index]; if (!(/** @type {?} */ (this))._skipPredicateFn(item) && (/** @type {?} */ (item.getLabel))().toUpperCase().trim().indexOf(inputString) === 0) { (/** @type {?} */ (this)).setActiveItem(index); break; } } (/** @type {?} */ (this))._pressedLetters = []; })); return (/** @type {?} */ (this)); } /** * @param {?} item * @return {?} */ setActiveItem(item) { /** @type {?} */ const previousIndex = this._activeItemIndex; this.updateActiveItem(item); if (this._activeItemIndex !== previousIndex) { this.change.next(this._activeItemIndex); } } /** * Sets the active item depending on the key event passed in. * @param {?} event Keyboard event to be used for determining which element should be active. * @return {?} */ onKeydown(event) { /** @type {?} */ const keyCode = event.keyCode; /** @type {?} */ const modifiers = ['altKey', 'ctrlKey', 'metaKey', 'shiftKey']; /** @type {?} */ const isModifierAllowed = modifiers.every((/** * @param {?} modifier * @return {?} */ modifier => { return !event[modifier] || this._allowedModifierKeys.indexOf(modifier) > -1; })); switch (keyCode) { case TAB: this.tabOut.next(); return; case DOWN_ARROW: if (this._vertical && isModifierAllowed) { this.setNextItemActive(); break; } else { return; } case UP_ARROW: if (this._vertical && isModifierAllowed) { this.setPreviousItemActive(); break; } else { return; } case RIGHT_ARROW: if (this._horizontal && isModifierAllowed) { this._horizontal === 'rtl' ? this.setPreviousItemActive() : this.setNextItemActive(); break; } else { return; } case LEFT_ARROW: if (this._horizontal && isModifierAllowed) { this._horizontal === 'rtl' ? this.setNextItemActive() : this.setPreviousItemActive(); break; } else { return; } default: if (isModifierAllowed || hasModifierKey(event, 'shiftKey')) { // Attempt to use the `event.key` which also maps it to the user's keyboard language, // otherwise fall back to resolving alphanumeric characters via the keyCode. if (event.key && event.key.length === 1) { this._letterKeyStream.next(event.key.toLocaleUpperCase()); } else if ((keyCode >= A && keyCode <= Z) || (keyCode >= ZERO && keyCode <= NINE)) { this._letterKeyStream.next(String.fromCharCode(keyCode)); } } // Note that we return here, in order to avoid preventing // the default action of non-navigational keys. return; } this._pressedLetters = []; event.preventDefault(); } /** * Index of the currently active item. * @return {?} */ get activeItemIndex() { return this._activeItemIndex; } /** * The active item. * @return {?} */ get activeItem() { return this._activeItem; } /** * Gets whether the user is currently typing into the manager using the typeahead feature. * @return {?} */ isTyping() { return this._pressedLetters.length > 0; } /** * Sets the active item to the first enabled item in the list. * @return {?} */ setFirstItemActive() { this._setActiveItemByIndex(0, 1); } /** * Sets the active item to the last enabled item in the list. * @return {?} */ setLastItemActive() { this._setActiveItemByIndex(this._items.length - 1, -1); } /** * Sets the active item to the next enabled item in the list. * @return {?} */ setNextItemActive() { this._activeItemIndex < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1); } /** * Sets the active item to a previous enabled item in the list. * @return {?} */ setPreviousItemActive() { this._activeItemIndex < 0 && this._wrap ? this.setLastItemActive() : this._setActiveItemByDelta(-1); } /** * @param {?} item * @return {?} */ updateActiveItem(item) { /** @type {?} */ const itemArray = this._getItemsArray(); /** @type {?} */ const index = typeof item === 'number' ? item : itemArray.indexOf(item); /** @type {?} */ const activeItem = itemArray[index]; // Explicitly check for `null` and `undefined` because other falsy values are valid. this._activeItem = activeItem == null ? null : activeItem; this._activeItemIndex = index; } /** * This method sets the active item, given a list of items and the delta between the * currently active item and the new active item. It will calculate differently * depending on whether wrap mode is turned on. * @private * @param {?} delta * @return {?} */ _setActiveItemByDelta(delta) { this._wrap ? this._setActiveInWrapMode(delta) : this._setActiveInDefaultMode(delta); } /** * Sets the active item properly given "wrap" mode. In other words, it will continue to move * down the list until it finds an item that is not disabled, and it will wrap if it * encounters either end of the list. * @private * @param {?} delta * @return {?} */ _setActiveInWrapMode(delta) { /** @type {?} */ const items = this._getItemsArray(); for (let i = 1; i <= items.length; i++) { /** @type {?} */ const index = (this._activeItemIndex + (delta * i) + items.length) % items.length; /** @type {?} */ const item = items[index]; if (!this._skipPredicateFn(item)) { this.setActiveItem(index); return; } } } /** * Sets the active item properly given the default mode. In other words, it will * continue to move down the list until it finds an item that is not disabled. If * it encounters either end of the list, it will stop and not wrap. * @private * @param {?} delta * @return {?} */ _setActiveInDefaultMode(delta) { this._setActiveItemByIndex(this._activeItemIndex + delta, delta); } /** * Sets the active item to the first enabled item starting at the index specified. If the * item is disabled, it will move in the fallbackDelta direction until it either * finds an enabled item or encounters the end of the list. * @private * @param {?} index * @param {?} fallbackDelta * @return {?} */ _setActiveItemByIndex(index, fallbackDelta) { /** @type {?} */ const items = this._getItemsArray(); if (!items[index]) { return; } while (this._skipPredicateFn(items[index])) { index += fallbackDelta; if (!items[index]) { return; } } this.setActiveItem(index); } /** * Returns the items as an array. * @private * @return {?} */ _getItemsArray() { return this._items instanceof QueryList ? this._items.toArray() : this._items; } } if (false) { /** * @type {?} * @private */ ListKeyManager.prototype._activeItemIndex; /** * @type {?} * @private */ ListKeyManager.prototype._activeItem; /** * @type {?} * @private */ ListKeyManager.prototype._wrap; /** * @type {?} * @private */ ListKeyManager.prototype._letterKeyStream; /** * @type {?} * @private */ ListKeyManager.prototype._typeaheadSubscription; /** * @type {?} * @private */ ListKeyManager.prototype._vertical; /** * @type {?} * @private */ ListKeyManager.prototype._horizontal; /** * @type {?} * @private */ ListKeyManager.prototype._allowedModifierKeys; /** * Predicate function that can be used to check whether an item should be skipped * by the key manager. By default, disabled items are skipped. * @type {?} * @private */ ListKeyManager.prototype._skipPredicateFn; /** * @type {?} * @private */ ListKeyManager.prototype._pressedLetters; /** * Stream that emits any time the TAB key is pressed, so components can react * when focus is shifted off of the list. * @type {?} */ ListKeyManager.prototype.tabOut; /** * Stream that emits whenever the active item of the list manager changes. * @type {?} */ ListKeyManager.prototype.change; /** * @type {?} * @private */ ListKeyManager.prototype._items; } /** * @fileoverview added by tsickle * Generated from: src/cdk/a11y/key-manager/activedescendant-key-manager.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * This is the interface for highlightable items (used by the ActiveDescendantKeyManager). * Each item must know how to style itself as active or inactive and whether or not it is * currently disabled. * @record */ function Highlightable() { } if (false) { /** * Applies the styles for an active item to this item. * @return {?} */ Highlightable.prototype.setActiveStyles = function () { }; /** * Applies the styles for an inactive item to this item. * @return {?} */ Highlightable.prototype.setInactiveStyles = function () { }; } /** * @template T */ class ActiveDescendantKeyManager extends ListKeyManager { /** * @param {?} index * @return {?} */ setActiveItem(index) { if (this.activeItem) { this.activeItem.setInactiveStyles(); } super.setActiveItem(index); if (this.activeItem) { this.activeItem.setActiveStyles(); } } } /** * @fileoverview added by tsickle * Generated from: src/cdk/a11y/key-manager/focus-key-manager.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * This is the interface for focusable items (used by the FocusKeyManager). * Each item must know how to focus itself, whether or not it is currently disabled * and be able to supply its label. * @record */ function FocusableOption() { } if (false) { /** * Focuses the `FocusableOption`. * @param {?=} origin * @return {?} */ FocusableOption.prototype.focus = function (origin) { }; } /** * @template T */ class FocusKeyManager extends ListKeyManager { constructor() { super(...arguments); this._origin = 'program'; } /** * Sets the focus origin that will be passed in to the items for any subsequent `focus` calls. * @template THIS * @this {THIS} * @param {?} origin Focus origin to be used when focusing items. * @return {THIS} */ setFocusOrigin(origin) { (/** @type {?} */ (this))._origin = origin; return (/** @type {?} */ (this)); } /** * @param {?} item * @return {?} */ setActiveItem(item) { super.setActiveItem(item); if (this.activeItem) { this.activeItem.focus(this._origin); } } } if (false) { /** * @type {?} * @private */ FocusKeyManager.prototype._origin; } /** * @fileoverview added by tsickle * Generated from: src/cdk/a11y/interactivity-checker/interactivity-checker.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // The InteractivityChecker leans heavily on the ally.js accessibility utilities. // Methods like `isTabbable` are only covering specific edge-cases for the browsers which are // supported. /** * Utility for checking the interactivity of an element, such as whether is is focusable or * tabbable. */ class InteractivityChecker { /** * @param {?} _platform */ constructor(_platform) { this._platform = _platform; } /** * Gets whether an element is disabled. * * @param {?} element Element to be checked. * @return {?} Whether the element is disabled. */ isDisabled(element) { // This does not capture some cases, such as a non-form control with a disabled attribute or // a form control inside of a disabled form, but should capture the most common cases. return element.hasAttribute('disabled'); } /** * Gets whether an element is visible for the purposes of interactivity. * * This will capture states like `display: none` and `visibility: hidden`, but not things like * being clipped by an `overflow: hidden` parent or being outside the viewport. * * @param {?} element * @return {?} Whether the element is visible. */ isVisible(element) { return hasGeometry(element) && getComputedStyle(element).visibility === 'visible'; } /** * Gets whether an element can be reached via Tab key. * Assumes that the element has already been checked with isFocusable. * * @param {?} element Element to be checked. * @return {?} Whether the element is tabbable. */ isTabbable(element) { // Nothing is tabbable on the server 😎 if (!this._platform.isBrowser) { return false; } /** @type {?} */ const frameElement = getFrameElement(getWindow(element)); if (frameElement) { /** @type {?} */ const frameType = frameElement && frameElement.nodeName.toLowerCase(); // Frame elements inherit their tabindex onto all child elements. if (getTabIndexValue(frameElement) === -1) { return false; } // Webkit and Blink consider anything inside of an element as non-tabbable. if ((this._platform.BLINK || this._platform.WEBKIT) && frameType === 'object') { return false; } // Webkit and Blink disable tabbing to an element inside of an invisible frame. if ((this._platform.BLINK || this._platform.WEBKIT) && !this.isVisible(frameElement)) { return false; } } /** @type {?} */ let nodeName = element.nodeName.toLowerCase(); /** @type {?} */ let tabIndexValue = getTabIndexValue(element); if (element.hasAttribute('contenteditable')) { return tabIndexValue !== -1; } if (nodeName === 'iframe') { // The frames may be tabbable depending on content, but it's not possibly to reliably // investigate the content of the frames. return false; } if (nodeName === 'audio') { if (!element.hasAttribute('controls')) { // By default an