| (function (global, factory) { |
| typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/common'), require('@angular/core'), require('rxjs'), require('@angular/cdk/keycodes'), require('rxjs/operators'), require('@angular/cdk/coercion'), require('@angular/cdk/platform'), require('@angular/cdk/observers')) : |
| typeof define === 'function' && define.amd ? define('@angular/cdk/a11y', ['exports', '@angular/common', '@angular/core', 'rxjs', '@angular/cdk/keycodes', 'rxjs/operators', '@angular/cdk/coercion', '@angular/cdk/platform', '@angular/cdk/observers'], factory) : |
| (global = global || self, factory((global.ng = global.ng || {}, global.ng.cdk = global.ng.cdk || {}, global.ng.cdk.a11y = {}), global.ng.common, global.ng.core, global.rxjs, global.ng.cdk.keycodes, global.rxjs.operators, global.ng.cdk.coercion, global.ng.cdk.platform, global.ng.cdk.observers)); |
| }(this, (function (exports, i2, i0, rxjs, keycodes, operators, coercion, i1, observers) { 'use strict'; |
| |
| /** |
| * @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 delimited by an empty space, as per the spec. */ |
| var ID_DELIMITER = ' '; |
| /** |
| * Adds the given ID to the specified ARIA attribute on an element. |
| * Used for attributes such as aria-labelledby, aria-owns, etc. |
| */ |
| function addAriaReferencedId(el, attr, id) { |
| var ids = getAriaReferenceIds(el, attr); |
| if (ids.some(function (existingId) { return existingId.trim() == id.trim(); })) { |
| return; |
| } |
| ids.push(id.trim()); |
| el.setAttribute(attr, ids.join(ID_DELIMITER)); |
| } |
| /** |
| * Removes the given ID from the specified ARIA attribute on an element. |
| * Used for attributes such as aria-labelledby, aria-owns, etc. |
| */ |
| function removeAriaReferencedId(el, attr, id) { |
| var ids = getAriaReferenceIds(el, attr); |
| var filteredIds = ids.filter(function (val) { return val != id.trim(); }); |
| if (filteredIds.length) { |
| el.setAttribute(attr, filteredIds.join(ID_DELIMITER)); |
| } |
| 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. |
| */ |
| function getAriaReferenceIds(el, attr) { |
| // Get string array of all individual ids (whitespace delimited) in the attribute value |
| return (el.getAttribute(attr) || '').match(/\S+/g) || []; |
| } |
| |
| /** |
| * @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 |
| */ |
| /** ID used for the body container where all messages are appended. */ |
| var MESSAGES_CONTAINER_ID = 'cdk-describedby-message-container'; |
| /** ID prefix used for each created message element. */ |
| var CDK_DESCRIBEDBY_ID_PREFIX = 'cdk-describedby-message'; |
| /** Attribute given to each host element that is described by a message element. */ |
| var CDK_DESCRIBEDBY_HOST_ATTRIBUTE = 'cdk-describedby-host'; |
| /** Global incremental identifier for each registered message element. */ |
| var nextId = 0; |
| /** Global map of all registered message elements that have been placed into the document. */ |
| var messageRegistry = new Map(); |
| /** Container for all registered messages. */ |
| var 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. |
| */ |
| var AriaDescriber = /** @class */ (function () { |
| function AriaDescriber(_document) { |
| this._document = _document; |
| } |
| AriaDescriber.prototype.describe = function (hostElement, message, role) { |
| if (!this._canBeDescribed(hostElement, message)) { |
| return; |
| } |
| var key = getKey(message, role); |
| if (typeof message !== 'string') { |
| // We need to ensure that the element has an ID. |
| setMessageId(message); |
| messageRegistry.set(key, { messageElement: message, referenceCount: 0 }); |
| } |
| else if (!messageRegistry.has(key)) { |
| this._createMessageElement(message, role); |
| } |
| if (!this._isElementDescribedByMessage(hostElement, key)) { |
| this._addMessageReference(hostElement, key); |
| } |
| }; |
| AriaDescriber.prototype.removeDescription = function (hostElement, message, role) { |
| if (!message || !this._isElementNode(hostElement)) { |
| return; |
| } |
| var key = getKey(message, role); |
| if (this._isElementDescribedByMessage(hostElement, key)) { |
| this._removeMessageReference(hostElement, key); |
| } |
| // 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') { |
| var registeredMessage = messageRegistry.get(key); |
| if (registeredMessage && registeredMessage.referenceCount === 0) { |
| this._deleteMessageElement(key); |
| } |
| } |
| if (messagesContainer && messagesContainer.childNodes.length === 0) { |
| this._deleteMessagesContainer(); |
| } |
| }; |
| /** Unregisters all created message elements and removes the message container. */ |
| AriaDescriber.prototype.ngOnDestroy = function () { |
| var describedElements = this._document.querySelectorAll("[" + CDK_DESCRIBEDBY_HOST_ATTRIBUTE + "]"); |
| for (var 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. |
| */ |
| AriaDescriber.prototype._createMessageElement = function (message, role) { |
| var messageElement = this._document.createElement('div'); |
| setMessageId(messageElement); |
| messageElement.textContent = message; |
| if (role) { |
| messageElement.setAttribute('role', role); |
| } |
| this._createMessagesContainer(); |
| messagesContainer.appendChild(messageElement); |
| messageRegistry.set(getKey(message, role), { messageElement: messageElement, referenceCount: 0 }); |
| }; |
| /** Deletes the message element from the global messages container. */ |
| AriaDescriber.prototype._deleteMessageElement = function (key) { |
| var registeredMessage = messageRegistry.get(key); |
| var messageElement = registeredMessage && registeredMessage.messageElement; |
| if (messagesContainer && messageElement) { |
| messagesContainer.removeChild(messageElement); |
| } |
| messageRegistry.delete(key); |
| }; |
| /** Creates the global container for all aria-describedby messages. */ |
| AriaDescriber.prototype._createMessagesContainer = function () { |
| if (!messagesContainer) { |
| var 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 && preExistingContainer.parentNode) { |
| preExistingContainer.parentNode.removeChild(preExistingContainer); |
| } |
| messagesContainer = this._document.createElement('div'); |
| messagesContainer.id = MESSAGES_CONTAINER_ID; |
| // We add `visibility: hidden` in order to prevent text in this container from |
| // being searchable by the browser's Ctrl + F functionality. |
| // Screen-readers will still read the description for elements with aria-describedby even |
| // when the description element is not visible. |
| messagesContainer.style.visibility = 'hidden'; |
| // Even though we use `visibility: hidden`, we still apply `cdk-visually-hidden` so that |
| // the description element doesn't impact page layout. |
| messagesContainer.classList.add('cdk-visually-hidden'); |
| this._document.body.appendChild(messagesContainer); |
| } |
| }; |
| /** Deletes the global messages container. */ |
| AriaDescriber.prototype._deleteMessagesContainer = function () { |
| if (messagesContainer && messagesContainer.parentNode) { |
| messagesContainer.parentNode.removeChild(messagesContainer); |
| messagesContainer = null; |
| } |
| }; |
| /** Removes all cdk-describedby messages that are hosted through the element. */ |
| AriaDescriber.prototype._removeCdkDescribedByReferenceIds = function (element) { |
| // Remove all aria-describedby reference IDs that are prefixed by CDK_DESCRIBEDBY_ID_PREFIX |
| var originalReferenceIds = getAriaReferenceIds(element, 'aria-describedby') |
| .filter(function (id) { return 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. |
| */ |
| AriaDescriber.prototype._addMessageReference = function (element, key) { |
| var registeredMessage = messageRegistry.get(key); |
| // 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. |
| */ |
| AriaDescriber.prototype._removeMessageReference = function (element, key) { |
| var registeredMessage = messageRegistry.get(key); |
| 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. */ |
| AriaDescriber.prototype._isElementDescribedByMessage = function (element, key) { |
| var referenceIds = getAriaReferenceIds(element, 'aria-describedby'); |
| var registeredMessage = messageRegistry.get(key); |
| var messageId = registeredMessage && registeredMessage.messageElement.id; |
| return !!messageId && referenceIds.indexOf(messageId) != -1; |
| }; |
| /** Determines whether a message can be described on a particular element. */ |
| AriaDescriber.prototype._canBeDescribed = function (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; |
| } |
| var trimmedMessage = message == null ? '' : ("" + message).trim(); |
| var 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. */ |
| AriaDescriber.prototype._isElementNode = function (element) { |
| return element.nodeType === this._document.ELEMENT_NODE; |
| }; |
| return AriaDescriber; |
| }()); |
| AriaDescriber.ɵprov = i0.ɵɵdefineInjectable({ factory: function AriaDescriber_Factory() { return new AriaDescriber(i0.ɵɵinject(i2.DOCUMENT)); }, token: AriaDescriber, providedIn: "root" }); |
| AriaDescriber.decorators = [ |
| { type: i0.Injectable, args: [{ providedIn: 'root' },] } |
| ]; |
| AriaDescriber.ctorParameters = function () { return [ |
| { type: undefined, decorators: [{ type: i0.Inject, args: [i2.DOCUMENT,] }] } |
| ]; }; |
| /** Gets a key that can be used to look messages up in the registry. */ |
| function getKey(message, role) { |
| return typeof message === 'string' ? (role || '') + "/" + message : message; |
| } |
| /** Assigns a unique ID to an element, if it doesn't have one already. */ |
| function setMessageId(element) { |
| if (!element.id) { |
| element.id = CDK_DESCRIBEDBY_ID_PREFIX + "-" + nextId++; |
| } |
| } |
| |
| /*! ***************************************************************************** |
| Copyright (c) Microsoft Corporation. |
| |
| Permission to use, copy, modify, and/or distribute this software for any |
| purpose with or without fee is hereby granted. |
| |
| THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH |
| REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
| AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, |
| INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM |
| LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR |
| OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| PERFORMANCE OF THIS SOFTWARE. |
| ***************************************************************************** */ |
| /* global Reflect, Promise */ |
| var extendStatics = function (d, b) { |
| extendStatics = Object.setPrototypeOf || |
| ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || |
| function (d, b) { for (var p in b) |
| if (Object.prototype.hasOwnProperty.call(b, p)) |
| d[p] = b[p]; }; |
| return extendStatics(d, b); |
| }; |
| function __extends(d, b) { |
| if (typeof b !== "function" && b !== null) |
| throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); |
| extendStatics(d, b); |
| function __() { this.constructor = d; } |
| d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); |
| } |
| var __assign = function () { |
| __assign = Object.assign || function __assign(t) { |
| for (var s, i = 1, n = arguments.length; i < n; i++) { |
| s = arguments[i]; |
| for (var p in s) |
| if (Object.prototype.hasOwnProperty.call(s, p)) |
| t[p] = s[p]; |
| } |
| return t; |
| }; |
| return __assign.apply(this, arguments); |
| }; |
| function __rest(s, e) { |
| var t = {}; |
| for (var p in s) |
| if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) |
| t[p] = s[p]; |
| if (s != null && typeof Object.getOwnPropertySymbols === "function") |
| for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { |
| if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) |
| t[p[i]] = s[p[i]]; |
| } |
| return t; |
| } |
| function __decorate(decorators, target, key, desc) { |
| var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; |
| if (typeof Reflect === "object" && typeof Reflect.decorate === "function") |
| r = Reflect.decorate(decorators, target, key, desc); |
| else |
| for (var i = decorators.length - 1; i >= 0; i--) |
| if (d = decorators[i]) |
| r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; |
| return c > 3 && r && Object.defineProperty(target, key, r), r; |
| } |
| function __param(paramIndex, decorator) { |
| return function (target, key) { decorator(target, key, paramIndex); }; |
| } |
| function __metadata(metadataKey, metadataValue) { |
| if (typeof Reflect === "object" && typeof Reflect.metadata === "function") |
| return Reflect.metadata(metadataKey, metadataValue); |
| } |
| function __awaiter(thisArg, _arguments, P, generator) { |
| function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
| return new (P || (P = Promise))(function (resolve, reject) { |
| function fulfilled(value) { try { |
| step(generator.next(value)); |
| } |
| catch (e) { |
| reject(e); |
| } } |
| function rejected(value) { try { |
| step(generator["throw"](value)); |
| } |
| catch (e) { |
| reject(e); |
| } } |
| function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
| step((generator = generator.apply(thisArg, _arguments || [])).next()); |
| }); |
| } |
| function __generator(thisArg, body) { |
| var _ = { label: 0, sent: function () { if (t[0] & 1) |
| throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; |
| return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { return this; }), g; |
| function verb(n) { return function (v) { return step([n, v]); }; } |
| function step(op) { |
| if (f) |
| throw new TypeError("Generator is already executing."); |
| while (_) |
| try { |
| if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) |
| return t; |
| if (y = 0, t) |
| op = [op[0] & 2, t.value]; |
| switch (op[0]) { |
| case 0: |
| case 1: |
| t = op; |
| break; |
| case 4: |
| _.label++; |
| return { value: op[1], done: false }; |
| case 5: |
| _.label++; |
| y = op[1]; |
| op = [0]; |
| continue; |
| case 7: |
| op = _.ops.pop(); |
| _.trys.pop(); |
| continue; |
| default: |
| if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { |
| _ = 0; |
| continue; |
| } |
| if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { |
| _.label = op[1]; |
| break; |
| } |
| if (op[0] === 6 && _.label < t[1]) { |
| _.label = t[1]; |
| t = op; |
| break; |
| } |
| if (t && _.label < t[2]) { |
| _.label = t[2]; |
| _.ops.push(op); |
| break; |
| } |
| if (t[2]) |
| _.ops.pop(); |
| _.trys.pop(); |
| continue; |
| } |
| op = body.call(thisArg, _); |
| } |
| catch (e) { |
| op = [6, e]; |
| y = 0; |
| } |
| finally { |
| f = t = 0; |
| } |
| if (op[0] & 5) |
| throw op[1]; |
| return { value: op[0] ? op[1] : void 0, done: true }; |
| } |
| } |
| var __createBinding = Object.create ? (function (o, m, k, k2) { |
| if (k2 === undefined) |
| k2 = k; |
| Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } }); |
| }) : (function (o, m, k, k2) { |
| if (k2 === undefined) |
| k2 = k; |
| o[k2] = m[k]; |
| }); |
| function __exportStar(m, o) { |
| for (var p in m) |
| if (p !== "default" && !Object.prototype.hasOwnProperty.call(o, p)) |
| __createBinding(o, m, p); |
| } |
| function __values(o) { |
| var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; |
| if (m) |
| return m.call(o); |
| if (o && typeof o.length === "number") |
| return { |
| next: function () { |
| if (o && i >= o.length) |
| o = void 0; |
| return { value: o && o[i++], done: !o }; |
| } |
| }; |
| throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); |
| } |
| function __read(o, n) { |
| var m = typeof Symbol === "function" && o[Symbol.iterator]; |
| if (!m) |
| return o; |
| var i = m.call(o), r, ar = [], e; |
| try { |
| while ((n === void 0 || n-- > 0) && !(r = i.next()).done) |
| ar.push(r.value); |
| } |
| catch (error) { |
| e = { error: error }; |
| } |
| finally { |
| try { |
| if (r && !r.done && (m = i["return"])) |
| m.call(i); |
| } |
| finally { |
| if (e) |
| throw e.error; |
| } |
| } |
| return ar; |
| } |
| /** @deprecated */ |
| function __spread() { |
| for (var ar = [], i = 0; i < arguments.length; i++) |
| ar = ar.concat(__read(arguments[i])); |
| return ar; |
| } |
| /** @deprecated */ |
| function __spreadArrays() { |
| for (var s = 0, i = 0, il = arguments.length; i < il; i++) |
| s += arguments[i].length; |
| for (var r = Array(s), k = 0, i = 0; i < il; i++) |
| for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) |
| r[k] = a[j]; |
| return r; |
| } |
| function __spreadArray(to, from) { |
| for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) |
| to[j] = from[i]; |
| return to; |
| } |
| function __await(v) { |
| return this instanceof __await ? (this.v = v, this) : new __await(v); |
| } |
| function __asyncGenerator(thisArg, _arguments, generator) { |
| if (!Symbol.asyncIterator) |
| throw new TypeError("Symbol.asyncIterator is not defined."); |
| var g = generator.apply(thisArg, _arguments || []), i, q = []; |
| return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; |
| function verb(n) { if (g[n]) |
| i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } |
| function resume(n, v) { try { |
| step(g[n](v)); |
| } |
| catch (e) { |
| settle(q[0][3], e); |
| } } |
| function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } |
| function fulfill(value) { resume("next", value); } |
| function reject(value) { resume("throw", value); } |
| function settle(f, v) { if (f(v), q.shift(), q.length) |
| resume(q[0][0], q[0][1]); } |
| } |
| function __asyncDelegator(o) { |
| var i, p; |
| return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; |
| function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } |
| } |
| function __asyncValues(o) { |
| if (!Symbol.asyncIterator) |
| throw new TypeError("Symbol.asyncIterator is not defined."); |
| var m = o[Symbol.asyncIterator], i; |
| return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); |
| function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } |
| function settle(resolve, reject, d, v) { Promise.resolve(v).then(function (v) { resolve({ value: v, done: d }); }, reject); } |
| } |
| function __makeTemplateObject(cooked, raw) { |
| if (Object.defineProperty) { |
| Object.defineProperty(cooked, "raw", { value: raw }); |
| } |
| else { |
| cooked.raw = raw; |
| } |
| return cooked; |
| } |
| ; |
| var __setModuleDefault = Object.create ? (function (o, v) { |
| Object.defineProperty(o, "default", { enumerable: true, value: v }); |
| }) : function (o, v) { |
| o["default"] = v; |
| }; |
| function __importStar(mod) { |
| if (mod && mod.__esModule) |
| return mod; |
| var result = {}; |
| if (mod != null) |
| for (var k in mod) |
| if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) |
| __createBinding(result, mod, k); |
| __setModuleDefault(result, mod); |
| return result; |
| } |
| function __importDefault(mod) { |
| return (mod && mod.__esModule) ? mod : { default: mod }; |
| } |
| function __classPrivateFieldGet(receiver, privateMap) { |
| if (!privateMap.has(receiver)) { |
| throw new TypeError("attempted to get private field on non-instance"); |
| } |
| return privateMap.get(receiver); |
| } |
| function __classPrivateFieldSet(receiver, privateMap, value) { |
| if (!privateMap.has(receiver)) { |
| throw new TypeError("attempted to set private field on non-instance"); |
| } |
| privateMap.set(receiver, value); |
| return value; |
| } |
| |
| /** |
| * @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 |
| */ |
| /** |
| * 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. |
| */ |
| var ListKeyManager = /** @class */ (function () { |
| function ListKeyManager(_items) { |
| var _this = this; |
| this._items = _items; |
| this._activeItemIndex = -1; |
| this._activeItem = null; |
| this._wrap = false; |
| this._letterKeyStream = new rxjs.Subject(); |
| this._typeaheadSubscription = rxjs.Subscription.EMPTY; |
| this._vertical = true; |
| this._allowedModifierKeys = []; |
| this._homeAndEnd = false; |
| /** |
| * 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 = function (item) { return 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 rxjs.Subject(); |
| /** Stream that emits whenever the active item of the list manager changes. */ |
| this.change = new rxjs.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 i0.QueryList) { |
| _items.changes.subscribe(function (newItems) { |
| if (_this._activeItem) { |
| var itemArray = newItems.toArray(); |
| var 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. |
| * @param predicate Function that determines whether the given item should be skipped. |
| */ |
| ListKeyManager.prototype.skipPredicate = function (predicate) { |
| this._skipPredicateFn = predicate; |
| return 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. |
| * @param shouldWrap Whether the list should wrap when reaching the end. |
| */ |
| ListKeyManager.prototype.withWrap = function (shouldWrap) { |
| if (shouldWrap === void 0) { shouldWrap = true; } |
| this._wrap = shouldWrap; |
| return this; |
| }; |
| /** |
| * Configures whether the key manager should be able to move the selection vertically. |
| * @param enabled Whether vertical selection should be enabled. |
| */ |
| ListKeyManager.prototype.withVerticalOrientation = function (enabled) { |
| if (enabled === void 0) { enabled = true; } |
| this._vertical = enabled; |
| return this; |
| }; |
| /** |
| * Configures the key manager to move the selection horizontally. |
| * Passing in `null` will disable horizontal movement. |
| * @param direction Direction in which the selection can be moved. |
| */ |
| ListKeyManager.prototype.withHorizontalOrientation = function (direction) { |
| this._horizontal = direction; |
| return 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. |
| */ |
| ListKeyManager.prototype.withAllowedModifierKeys = function (keys) { |
| this._allowedModifierKeys = keys; |
| return this; |
| }; |
| /** |
| * Turns on typeahead mode which allows users to set the active item by typing. |
| * @param debounceInterval Time to wait after the last keystroke before setting the active item. |
| */ |
| ListKeyManager.prototype.withTypeAhead = function (debounceInterval) { |
| var _this = this; |
| if (debounceInterval === void 0) { debounceInterval = 200; } |
| if ((typeof ngDevMode === 'undefined' || ngDevMode) && (this._items.length && |
| this._items.some(function (item) { return typeof item.getLabel !== 'function'; }))) { |
| throw Error('ListKeyManager items in typeahead mode must implement the `getLabel` method.'); |
| } |
| 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. |
| this._typeaheadSubscription = this._letterKeyStream.pipe(operators.tap(function (letter) { return _this._pressedLetters.push(letter); }), operators.debounceTime(debounceInterval), operators.filter(function () { return _this._pressedLetters.length > 0; }), operators.map(function () { return _this._pressedLetters.join(''); })).subscribe(function (inputString) { |
| var items = _this._getItemsArray(); |
| // Start at 1 because we want to start searching at the item immediately |
| // following the current active item. |
| for (var i = 1; i < items.length + 1; i++) { |
| var index = (_this._activeItemIndex + i) % items.length; |
| var item = items[index]; |
| if (!_this._skipPredicateFn(item) && |
| item.getLabel().toUpperCase().trim().indexOf(inputString) === 0) { |
| _this.setActiveItem(index); |
| break; |
| } |
| } |
| _this._pressedLetters = []; |
| }); |
| return this; |
| }; |
| /** |
| * Configures the key manager to activate the first and last items |
| * respectively when the Home or End key is pressed. |
| * @param enabled Whether pressing the Home or End key activates the first/last item. |
| */ |
| ListKeyManager.prototype.withHomeAndEnd = function (enabled) { |
| if (enabled === void 0) { enabled = true; } |
| this._homeAndEnd = enabled; |
| return this; |
| }; |
| ListKeyManager.prototype.setActiveItem = function (item) { |
| var previousActiveItem = this._activeItem; |
| this.updateActiveItem(item); |
| if (this._activeItem !== previousActiveItem) { |
| 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. |
| */ |
| ListKeyManager.prototype.onKeydown = function (event) { |
| var _this = this; |
| var keyCode = event.keyCode; |
| var modifiers = ['altKey', 'ctrlKey', 'metaKey', 'shiftKey']; |
| var isModifierAllowed = modifiers.every(function (modifier) { |
| return !event[modifier] || _this._allowedModifierKeys.indexOf(modifier) > -1; |
| }); |
| switch (keyCode) { |
| case keycodes.TAB: |
| this.tabOut.next(); |
| return; |
| case keycodes.DOWN_ARROW: |
| if (this._vertical && isModifierAllowed) { |
| this.setNextItemActive(); |
| break; |
| } |
| else { |
| return; |
| } |
| case keycodes.UP_ARROW: |
| if (this._vertical && isModifierAllowed) { |
| this.setPreviousItemActive(); |
| break; |
| } |
| else { |
| return; |
| } |
| case keycodes.RIGHT_ARROW: |
| if (this._horizontal && isModifierAllowed) { |
| this._horizontal === 'rtl' ? this.setPreviousItemActive() : this.setNextItemActive(); |
| break; |
| } |
| else { |
| return; |
| } |
| case keycodes.LEFT_ARROW: |
| if (this._horizontal && isModifierAllowed) { |
| this._horizontal === 'rtl' ? this.setNextItemActive() : this.setPreviousItemActive(); |
| break; |
| } |
| else { |
| return; |
| } |
| case keycodes.HOME: |
| if (this._homeAndEnd && isModifierAllowed) { |
| this.setFirstItemActive(); |
| break; |
| } |
| else { |
| return; |
| } |
| case keycodes.END: |
| if (this._homeAndEnd && isModifierAllowed) { |
| this.setLastItemActive(); |
| break; |
| } |
| else { |
| return; |
| } |
| default: |
| if (isModifierAllowed || keycodes.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 >= keycodes.A && keyCode <= keycodes.Z) || (keyCode >= keycodes.ZERO && keyCode <= keycodes.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(); |
| }; |
| Object.defineProperty(ListKeyManager.prototype, "activeItemIndex", { |
| /** Index of the currently active item. */ |
| get: function () { |
| return this._activeItemIndex; |
| }, |
| enumerable: false, |
| configurable: true |
| }); |
| Object.defineProperty(ListKeyManager.prototype, "activeItem", { |
| /** The active item. */ |
| get: function () { |
| return this._activeItem; |
| }, |
| enumerable: false, |
| configurable: true |
| }); |
| /** Gets whether the user is currently typing into the manager using the typeahead feature. */ |
| ListKeyManager.prototype.isTyping = function () { |
| return this._pressedLetters.length > 0; |
| }; |
| /** Sets the active item to the first enabled item in the list. */ |
| ListKeyManager.prototype.setFirstItemActive = function () { |
| this._setActiveItemByIndex(0, 1); |
| }; |
| /** Sets the active item to the last enabled item in the list. */ |
| ListKeyManager.prototype.setLastItemActive = function () { |
| this._setActiveItemByIndex(this._items.length - 1, -1); |
| }; |
| /** Sets the active item to the next enabled item in the list. */ |
| ListKeyManager.prototype.setNextItemActive = function () { |
| this._activeItemIndex < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1); |
| }; |
| /** Sets the active item to a previous enabled item in the list. */ |
| ListKeyManager.prototype.setPreviousItemActive = function () { |
| this._activeItemIndex < 0 && this._wrap ? this.setLastItemActive() |
| : this._setActiveItemByDelta(-1); |
| }; |
| ListKeyManager.prototype.updateActiveItem = function (item) { |
| var itemArray = this._getItemsArray(); |
| var index = typeof item === 'number' ? item : itemArray.indexOf(item); |
| var 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. |
| */ |
| ListKeyManager.prototype._setActiveItemByDelta = function (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. |
| */ |
| ListKeyManager.prototype._setActiveInWrapMode = function (delta) { |
| var items = this._getItemsArray(); |
| for (var i = 1; i <= items.length; i++) { |
| var index = (this._activeItemIndex + (delta * i) + items.length) % items.length; |
| var 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. |
| */ |
| ListKeyManager.prototype._setActiveInDefaultMode = function (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. |
| */ |
| ListKeyManager.prototype._setActiveItemByIndex = function (index, fallbackDelta) { |
| var 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. */ |
| ListKeyManager.prototype._getItemsArray = function () { |
| return this._items instanceof i0.QueryList ? this._items.toArray() : this._items; |
| }; |
| return ListKeyManager; |
| }()); |
| |
| var ActiveDescendantKeyManager = /** @class */ (function (_super) { |
| __extends(ActiveDescendantKeyManager, _super); |
| function ActiveDescendantKeyManager() { |
| return _super !== null && _super.apply(this, arguments) || this; |
| } |
| ActiveDescendantKeyManager.prototype.setActiveItem = function (index) { |
| if (this.activeItem) { |
| this.activeItem.setInactiveStyles(); |
| } |
| _super.prototype.setActiveItem.call(this, index); |
| if (this.activeItem) { |
| this.activeItem.setActiveStyles(); |
| } |
| }; |
| return ActiveDescendantKeyManager; |
| }(ListKeyManager)); |
| |
| var FocusKeyManager = /** @class */ (function (_super) { |
| __extends(FocusKeyManager, _super); |
| function FocusKeyManager() { |
| var _this = _super.apply(this, __spread(arguments)) || this; |
| _this._origin = 'program'; |
| return _this; |
| } |
| /** |
| * Sets the focus origin that will be passed in to the items for any subsequent `focus` calls. |
| * @param origin Focus origin to be used when focusing items. |
| */ |
| FocusKeyManager.prototype.setFocusOrigin = function (origin) { |
| this._origin = origin; |
| return this; |
| }; |
| FocusKeyManager.prototype.setActiveItem = function (item) { |
| _super.prototype.setActiveItem.call(this, item); |
| if (this.activeItem) { |
| this.activeItem.focus(this._origin); |
| } |
| }; |
| return FocusKeyManager; |
| }(ListKeyManager)); |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Configuration for the isFocusable method. |
| */ |
| var IsFocusableConfig = /** @class */ (function () { |
| function IsFocusableConfig() { |
| /** |
| * Whether to count an element as focusable even if it is not currently visible. |
| */ |
| this.ignoreVisibility = false; |
| } |
| return IsFocusableConfig; |
| }()); |
| // 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. |
| */ |
| var InteractivityChecker = /** @class */ (function () { |
| function InteractivityChecker(_platform) { |
| this._platform = _platform; |
| } |
| /** |
| * Gets whether an element is disabled. |
| * |
| * @param element Element to be checked. |
| * @returns Whether the element is disabled. |
| */ |
| InteractivityChecker.prototype.isDisabled = function (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. |
| * |
| * @returns Whether the element is visible. |
| */ |
| InteractivityChecker.prototype.isVisible = function (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. |
| * @returns Whether the element is tabbable. |
| */ |
| InteractivityChecker.prototype.isTabbable = function (element) { |
| // Nothing is tabbable on the server 😎 |
| if (!this._platform.isBrowser) { |
| return false; |
| } |
| var frameElement = getFrameElement(getWindow(element)); |
| if (frameElement) { |
| // Frame elements inherit their tabindex onto all child elements. |
| if (getTabIndexValue(frameElement) === -1) { |
| return false; |
| } |
| // Browsers disable tabbing to an element inside of an invisible frame. |
| if (!this.isVisible(frameElement)) { |
| return false; |
| } |
| } |
| var nodeName = element.nodeName.toLowerCase(); |
| var tabIndexValue = getTabIndexValue(element); |
| if (element.hasAttribute('contenteditable')) { |
| return tabIndexValue !== -1; |
| } |
| if (nodeName === 'iframe' || nodeName === 'object') { |
| // The frame or object's content may be tabbable depending on the content, but it's |
| // not possibly to reliably detect the content of the frames. We always consider such |
| // elements as non-tabbable. |
| return false; |
| } |
| // In iOS, the browser only considers some specific elements as tabbable. |
| if (this._platform.WEBKIT && this._platform.IOS && !isPotentiallyTabbableIOS(element)) { |
| return false; |
| } |
| if (nodeName === 'audio') { |
| // Audio elements without controls enabled are never tabbable, regardless |
| // of the tabindex attribute explicitly being set. |
| if (!element.hasAttribute('controls')) { |
| return false; |
| } |
| // Audio elements with controls are by default tabbable unless the |
| // tabindex attribute is set to `-1` explicitly. |
| return tabIndexValue !== -1; |
| } |
| if (nodeName === 'video') { |
| // For all video elements, if the tabindex attribute is set to `-1`, the video |
| // is not tabbable. Note: We cannot rely on the default `HTMLElement.tabIndex` |
| // property as that one is set to `-1` in Chrome, Edge and Safari v13.1. The |
| // tabindex attribute is the source of truth here. |
| if (tabIndexValue === -1) { |
| return false; |
| } |
| // If the tabindex is explicitly set, and not `-1` (as per check before), the |
| // video element is always tabbable (regardless of whether it has controls or not). |
| if (tabIndexValue !== null) { |
| return true; |
| } |
| // Otherwise (when no explicit tabindex is set), a video is only tabbable if it |
| // has controls enabled. Firefox is special as videos are always tabbable regardless |
| // of whether there are controls or not. |
| return this._platform.FIREFOX || element.hasAttribute('controls'); |
| } |
| return element.tabIndex >= 0; |
| }; |
| /** |
| * Gets whether an element can be focused by the user. |
| * |
| * @param element Element to be checked. |
| * @param config The config object with options to customize this method's behavior |
| * @returns Whether the element is focusable. |
| */ |
| InteractivityChecker.prototype.isFocusable = function (element, config) { |
| // Perform checks in order of left to most expensive. |
| // Again, naive approach that does not capture many edge cases and browser quirks. |
| return isPotentiallyFocusable(element) && !this.isDisabled(element) && |
| ((config === null || config === void 0 ? void 0 : config.ignoreVisibility) || this.isVisible(element)); |
| }; |
| return InteractivityChecker; |
| }()); |
| InteractivityChecker.ɵprov = i0.ɵɵdefineInjectable({ factory: function InteractivityChecker_Factory() { return new InteractivityChecker(i0.ɵɵinject(i1.Platform)); }, token: InteractivityChecker, providedIn: "root" }); |
| InteractivityChecker.decorators = [ |
| { type: i0.Injectable, args: [{ providedIn: 'root' },] } |
| ]; |
| InteractivityChecker.ctorParameters = function () { return [ |
| { type: i1.Platform } |
| ]; }; |
| /** |
| * Returns the frame element from a window object. Since browsers like MS Edge throw errors if |
| * the frameElement property is being accessed from a different host address, this property |
| * should be accessed carefully. |
| */ |
| function getFrameElement(window) { |
| try { |
| return window.frameElement; |
| } |
| catch (_a) { |
| return null; |
| } |
| } |
| /** Checks whether the specified element has any geometry / rectangles. */ |
| function hasGeometry(element) { |
| // Use logic from jQuery to check for an invisible element. |
| // See https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js#L12 |
| return !!(element.offsetWidth || element.offsetHeight || |
| (typeof element.getClientRects === 'function' && element.getClientRects().length)); |
| } |
| /** Gets whether an element's */ |
| function isNativeFormElement(element) { |
| var nodeName = element.nodeName.toLowerCase(); |
| return nodeName === 'input' || |
| nodeName === 'select' || |
| nodeName === 'button' || |
| nodeName === 'textarea'; |
| } |
| /** Gets whether an element is an `<input type="hidden">`. */ |
| function isHiddenInput(element) { |
| return isInputElement(element) && element.type == 'hidden'; |
| } |
| /** Gets whether an element is an anchor that has an href attribute. */ |
| function isAnchorWithHref(element) { |
| return isAnchorElement(element) && element.hasAttribute('href'); |
| } |
| /** Gets whether an element is an input element. */ |
| function isInputElement(element) { |
| return element.nodeName.toLowerCase() == 'input'; |
| } |
| /** Gets whether an element is an anchor element. */ |
| function isAnchorElement(element) { |
| return element.nodeName.toLowerCase() == 'a'; |
| } |
| /** Gets whether an element has a valid tabindex. */ |
| function hasValidTabIndex(element) { |
| if (!element.hasAttribute('tabindex') || element.tabIndex === undefined) { |
| return false; |
| } |
| var tabIndex = element.getAttribute('tabindex'); |
| // IE11 parses tabindex="" as the value "-32768" |
| if (tabIndex == '-32768') { |
| return false; |
| } |
| return !!(tabIndex && !isNaN(parseInt(tabIndex, 10))); |
| } |
| /** |
| * Returns the parsed tabindex from the element attributes instead of returning the |
| * evaluated tabindex from the browsers defaults. |
| */ |
| function getTabIndexValue(element) { |
| if (!hasValidTabIndex(element)) { |
| return null; |
| } |
| // See browser issue in Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054 |
| var tabIndex = parseInt(element.getAttribute('tabindex') || '', 10); |
| return isNaN(tabIndex) ? -1 : tabIndex; |
| } |
| /** Checks whether the specified element is potentially tabbable on iOS */ |
| function isPotentiallyTabbableIOS(element) { |
| var nodeName = element.nodeName.toLowerCase(); |
| var inputType = nodeName === 'input' && element.type; |
| return inputType === 'text' |
| || inputType === 'password' |
| || nodeName === 'select' |
| || nodeName === 'textarea'; |
| } |
| /** |
| * Gets whether an element is potentially focusable without taking current visible/disabled state |
| * into account. |
| */ |
| function isPotentiallyFocusable(element) { |
| // Inputs are potentially focusable *unless* they're type="hidden". |
| if (isHiddenInput(element)) { |
| return false; |
| } |
| return isNativeFormElement(element) || |
| isAnchorWithHref(element) || |
| element.hasAttribute('contenteditable') || |
| hasValidTabIndex(element); |
| } |
| /** Gets the parent window of a DOM node with regards of being inside of an iframe. */ |
| function getWindow(node) { |
| // ownerDocument is null if `node` itself *is* a document. |
| return node.ownerDocument && node.ownerDocument.defaultView || window; |
| } |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Class that allows for trapping focus within a DOM element. |
| * |
| * This class currently uses a relatively simple approach to focus trapping. |
| * It assumes that the tab order is the same as DOM order, which is not necessarily true. |
| * Things like `tabIndex > 0`, flex `order`, and shadow roots can cause the two to be misaligned. |
| * |
| * @deprecated Use `ConfigurableFocusTrap` instead. |
| * @breaking-change 11.0.0 |
| */ |
| var FocusTrap = /** @class */ (function () { |
| function FocusTrap(_element, _checker, _ngZone, _document, deferAnchors) { |
| var _this = this; |
| if (deferAnchors === void 0) { deferAnchors = false; } |
| this._element = _element; |
| this._checker = _checker; |
| this._ngZone = _ngZone; |
| this._document = _document; |
| this._hasAttached = false; |
| // Event listeners for the anchors. Need to be regular functions so that we can unbind them later. |
| this.startAnchorListener = function () { return _this.focusLastTabbableElement(); }; |
| this.endAnchorListener = function () { return _this.focusFirstTabbableElement(); }; |
| this._enabled = true; |
| if (!deferAnchors) { |
| this.attachAnchors(); |
| } |
| } |
| Object.defineProperty(FocusTrap.prototype, "enabled", { |
| /** Whether the focus trap is active. */ |
| get: function () { return this._enabled; }, |
| set: function (value) { |
| this._enabled = value; |
| if (this._startAnchor && this._endAnchor) { |
| this._toggleAnchorTabIndex(value, this._startAnchor); |
| this._toggleAnchorTabIndex(value, this._endAnchor); |
| } |
| }, |
| enumerable: false, |
| configurable: true |
| }); |
| /** Destroys the focus trap by cleaning up the anchors. */ |
| FocusTrap.prototype.destroy = function () { |
| var startAnchor = this._startAnchor; |
| var endAnchor = this._endAnchor; |
| if (startAnchor) { |
| startAnchor.removeEventListener('focus', this.startAnchorListener); |
| if (startAnchor.parentNode) { |
| startAnchor.parentNode.removeChild(startAnchor); |
| } |
| } |
| if (endAnchor) { |
| endAnchor.removeEventListener('focus', this.endAnchorListener); |
| if (endAnchor.parentNode) { |
| endAnchor.parentNode.removeChild(endAnchor); |
| } |
| } |
| this._startAnchor = this._endAnchor = null; |
| this._hasAttached = false; |
| }; |
| /** |
| * Inserts the anchors into the DOM. This is usually done automatically |
| * in the constructor, but can be deferred for cases like directives with `*ngIf`. |
| * @returns Whether the focus trap managed to attach successfully. This may not be the case |
| * if the target element isn't currently in the DOM. |
| */ |
| FocusTrap.prototype.attachAnchors = function () { |
| var _this = this; |
| // If we're not on the browser, there can be no focus to trap. |
| if (this._hasAttached) { |
| return true; |
| } |
| this._ngZone.runOutsideAngular(function () { |
| if (!_this._startAnchor) { |
| _this._startAnchor = _this._createAnchor(); |
| _this._startAnchor.addEventListener('focus', _this.startAnchorListener); |
| } |
| if (!_this._endAnchor) { |
| _this._endAnchor = _this._createAnchor(); |
| _this._endAnchor.addEventListener('focus', _this.endAnchorListener); |
| } |
| }); |
| if (this._element.parentNode) { |
| this._element.parentNode.insertBefore(this._startAnchor, this._element); |
| this._element.parentNode.insertBefore(this._endAnchor, this._element.nextSibling); |
| this._hasAttached = true; |
| } |
| return this._hasAttached; |
| }; |
| /** |
| * Waits for the zone to stabilize, then either focuses the first element that the |
| * user specified, or the first tabbable element. |
| * @returns Returns a promise that resolves with a boolean, depending |
| * on whether focus was moved successfully. |
| */ |
| FocusTrap.prototype.focusInitialElementWhenReady = function () { |
| var _this = this; |
| return new Promise(function (resolve) { |
| _this._executeOnStable(function () { return resolve(_this.focusInitialElement()); }); |
| }); |
| }; |
| /** |
| * Waits for the zone to stabilize, then focuses |
| * the first tabbable element within the focus trap region. |
| * @returns Returns a promise that resolves with a boolean, depending |
| * on whether focus was moved successfully. |
| */ |
| FocusTrap.prototype.focusFirstTabbableElementWhenReady = function () { |
| var _this = this; |
| return new Promise(function (resolve) { |
| _this._executeOnStable(function () { return resolve(_this.focusFirstTabbableElement()); }); |
| }); |
| }; |
| /** |
| * Waits for the zone to stabilize, then focuses |
| * the last tabbable element within the focus trap region. |
| * @returns Returns a promise that resolves with a boolean, depending |
| * on whether focus was moved successfully. |
| */ |
| FocusTrap.prototype.focusLastTabbableElementWhenReady = function () { |
| var _this = this; |
| return new Promise(function (resolve) { |
| _this._executeOnStable(function () { return resolve(_this.focusLastTabbableElement()); }); |
| }); |
| }; |
| /** |
| * Get the specified boundary element of the trapped region. |
| * @param bound The boundary to get (start or end of trapped region). |
| * @returns The boundary element. |
| */ |
| FocusTrap.prototype._getRegionBoundary = function (bound) { |
| // Contains the deprecated version of selector, for temporary backwards comparability. |
| var markers = this._element.querySelectorAll("[cdk-focus-region-" + bound + "], " + |
| ("[cdkFocusRegion" + bound + "], ") + |
| ("[cdk-focus-" + bound + "]")); |
| for (var i = 0; i < markers.length; i++) { |
| // @breaking-change 8.0.0 |
| if (markers[i].hasAttribute("cdk-focus-" + bound)) { |
| console.warn("Found use of deprecated attribute 'cdk-focus-" + bound + "', " + |
| ("use 'cdkFocusRegion" + bound + "' instead. The deprecated ") + |
| "attribute will be removed in 8.0.0.", markers[i]); |
| } |
| else if (markers[i].hasAttribute("cdk-focus-region-" + bound)) { |
| console.warn("Found use of deprecated attribute 'cdk-focus-region-" + bound + "', " + |
| ("use 'cdkFocusRegion" + bound + "' instead. The deprecated attribute ") + |
| "will be removed in 8.0.0.", markers[i]); |
| } |
| } |
| if (bound == 'start') { |
| return markers.length ? markers[0] : this._getFirstTabbableElement(this._element); |
| } |
| return markers.length ? |
| markers[markers.length - 1] : this._getLastTabbableElement(this._element); |
| }; |
| /** |
| * Focuses the element that should be focused when the focus trap is initialized. |
| * @returns Whether focus was moved successfully. |
| */ |
| FocusTrap.prototype.focusInitialElement = function () { |
| // Contains the deprecated version of selector, for temporary backwards comparability. |
| var redirectToElement = this._element.querySelector("[cdk-focus-initial], " + |
| "[cdkFocusInitial]"); |
| if (redirectToElement) { |
| // @breaking-change 8.0.0 |
| if (redirectToElement.hasAttribute("cdk-focus-initial")) { |
| console.warn("Found use of deprecated attribute 'cdk-focus-initial', " + |
| "use 'cdkFocusInitial' instead. The deprecated attribute " + |
| "will be removed in 8.0.0", redirectToElement); |
| } |
| // Warn the consumer if the element they've pointed to |
| // isn't focusable, when not in production mode. |
| if ((typeof ngDevMode === 'undefined' || ngDevMode) && |
| !this._checker.isFocusable(redirectToElement)) { |
| console.warn("Element matching '[cdkFocusInitial]' is not focusable.", redirectToElement); |
| } |
| if (!this._checker.isFocusable(redirectToElement)) { |
| var focusableChild = this._getFirstTabbableElement(redirectToElement); |
| focusableChild === null || focusableChild === void 0 ? void 0 : focusableChild.focus(); |
| return !!focusableChild; |
| } |
| redirectToElement.focus(); |
| return true; |
| } |
| return this.focusFirstTabbableElement(); |
| }; |
| /** |
| * Focuses the first tabbable element within the focus trap region. |
| * @returns Whether focus was moved successfully. |
| */ |
| FocusTrap.prototype.focusFirstTabbableElement = function () { |
| var redirectToElement = this._getRegionBoundary('start'); |
| if (redirectToElement) { |
| redirectToElement.focus(); |
| } |
| return !!redirectToElement; |
| }; |
| /** |
| * Focuses the last tabbable element within the focus trap region. |
| * @returns Whether focus was moved successfully. |
| */ |
| FocusTrap.prototype.focusLastTabbableElement = function () { |
| var redirectToElement = this._getRegionBoundary('end'); |
| if (redirectToElement) { |
| redirectToElement.focus(); |
| } |
| return !!redirectToElement; |
| }; |
| /** |
| * Checks whether the focus trap has successfully been attached. |
| */ |
| FocusTrap.prototype.hasAttached = function () { |
| return this._hasAttached; |
| }; |
| /** Get the first tabbable element from a DOM subtree (inclusive). */ |
| FocusTrap.prototype._getFirstTabbableElement = function (root) { |
| if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) { |
| return root; |
| } |
| // Iterate in DOM order. Note that IE doesn't have `children` for SVG so we fall |
| // back to `childNodes` which includes text nodes, comments etc. |
| var children = root.children || root.childNodes; |
| for (var i = 0; i < children.length; i++) { |
| var tabbableChild = children[i].nodeType === this._document.ELEMENT_NODE ? |
| this._getFirstTabbableElement(children[i]) : |
| null; |
| if (tabbableChild) { |
| return tabbableChild; |
| } |
| } |
| return null; |
| }; |
| /** Get the last tabbable element from a DOM subtree (inclusive). */ |
| FocusTrap.prototype._getLastTabbableElement = function (root) { |
| if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) { |
| return root; |
| } |
| // Iterate in reverse DOM order. |
| var children = root.children || root.childNodes; |
| for (var i = children.length - 1; i >= 0; i--) { |
| var tabbableChild = children[i].nodeType === this._document.ELEMENT_NODE ? |
| this._getLastTabbableElement(children[i]) : |
| null; |
| if (tabbableChild) { |
| return tabbableChild; |
| } |
| } |
| return null; |
| }; |
| /** Creates an anchor element. */ |
| FocusTrap.prototype._createAnchor = function () { |
| var anchor = this._document.createElement('div'); |
| this._toggleAnchorTabIndex(this._enabled, anchor); |
| anchor.classList.add('cdk-visually-hidden'); |
| anchor.classList.add('cdk-focus-trap-anchor'); |
| anchor.setAttribute('aria-hidden', 'true'); |
| return anchor; |
| }; |
| /** |
| * Toggles the `tabindex` of an anchor, based on the enabled state of the focus trap. |
| * @param isEnabled Whether the focus trap is enabled. |
| * @param anchor Anchor on which to toggle the tabindex. |
| */ |
| FocusTrap.prototype._toggleAnchorTabIndex = function (isEnabled, anchor) { |
| // Remove the tabindex completely, rather than setting it to -1, because if the |
| // element has a tabindex, the user might still hit it when navigating with the arrow keys. |
| isEnabled ? anchor.setAttribute('tabindex', '0') : anchor.removeAttribute('tabindex'); |
| }; |
| /** |
| * Toggles the`tabindex` of both anchors to either trap Tab focus or allow it to escape. |
| * @param enabled: Whether the anchors should trap Tab. |
| */ |
| FocusTrap.prototype.toggleAnchors = function (enabled) { |
| if (this._startAnchor && this._endAnchor) { |
| this._toggleAnchorTabIndex(enabled, this._startAnchor); |
| this._toggleAnchorTabIndex(enabled, this._endAnchor); |
| } |
| }; |
| /** Executes a function when the zone is stable. */ |
| FocusTrap.prototype._executeOnStable = function (fn) { |
| if (this._ngZone.isStable) { |
| fn(); |
| } |
| else { |
| this._ngZone.onStable.pipe(operators.take(1)).subscribe(fn); |
| } |
| }; |
| return FocusTrap; |
| }()); |
| /** |
| * Factory that allows easy instantiation of focus traps. |
| * @deprecated Use `ConfigurableFocusTrapFactory` instead. |
| * @breaking-change 11.0.0 |
| */ |
| var FocusTrapFactory = /** @class */ (function () { |
| function FocusTrapFactory(_checker, _ngZone, _document) { |
| this._checker = _checker; |
| this._ngZone = _ngZone; |
| this._document = _document; |
| } |
| /** |
| * Creates a focus-trapped region around the given element. |
| * @param element The element around which focus will be trapped. |
| * @param deferCaptureElements Defers the creation of focus-capturing elements to be done |
| * manually by the user. |
| * @returns The created focus trap instance. |
| */ |
| FocusTrapFactory.prototype.create = function (element, deferCaptureElements) { |
| if (deferCaptureElements === void 0) { deferCaptureElements = false; } |
| return new FocusTrap(element, this._checker, this._ngZone, this._document, deferCaptureElements); |
| }; |
| return FocusTrapFactory; |
| }()); |
| FocusTrapFactory.ɵprov = i0.ɵɵdefineInjectable({ factory: function FocusTrapFactory_Factory() { return new FocusTrapFactory(i0.ɵɵinject(InteractivityChecker), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i2.DOCUMENT)); }, token: FocusTrapFactory, providedIn: "root" }); |
| FocusTrapFactory.decorators = [ |
| { type: i0.Injectable, args: [{ providedIn: 'root' },] } |
| ]; |
| FocusTrapFactory.ctorParameters = function () { return [ |
| { type: InteractivityChecker }, |
| { type: i0.NgZone }, |
| { type: undefined, decorators: [{ type: i0.Inject, args: [i2.DOCUMENT,] }] } |
| ]; }; |
| /** Directive for trapping focus within a region. */ |
| var CdkTrapFocus = /** @class */ (function () { |
| function CdkTrapFocus(_elementRef, _focusTrapFactory, _document) { |
| this._elementRef = _elementRef; |
| this._focusTrapFactory = _focusTrapFactory; |
| /** Previously focused element to restore focus to upon destroy when using autoCapture. */ |
| this._previouslyFocusedElement = null; |
| this._document = _document; |
| this.focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, true); |
| } |
| Object.defineProperty(CdkTrapFocus.prototype, "enabled", { |
| /** Whether the focus trap is active. */ |
| get: function () { return this.focusTrap.enabled; }, |
| set: function (value) { this.focusTrap.enabled = coercion.coerceBooleanProperty(value); }, |
| enumerable: false, |
| configurable: true |
| }); |
| Object.defineProperty(CdkTrapFocus.prototype, "autoCapture", { |
| /** |
| * Whether the directive should automatically move focus into the trapped region upon |
| * initialization and return focus to the previous activeElement upon destruction. |
| */ |
| get: function () { return this._autoCapture; }, |
| set: function (value) { this._autoCapture = coercion.coerceBooleanProperty(value); }, |
| enumerable: false, |
| configurable: true |
| }); |
| CdkTrapFocus.prototype.ngOnDestroy = function () { |
| this.focusTrap.destroy(); |
| // If we stored a previously focused element when using autoCapture, return focus to that |
| // element now that the trapped region is being destroyed. |
| if (this._previouslyFocusedElement) { |
| this._previouslyFocusedElement.focus(); |
| this._previouslyFocusedElement = null; |
| } |
| }; |
| CdkTrapFocus.prototype.ngAfterContentInit = function () { |
| this.focusTrap.attachAnchors(); |
| if (this.autoCapture) { |
| this._captureFocus(); |
| } |
| }; |
| CdkTrapFocus.prototype.ngDoCheck = function () { |
| if (!this.focusTrap.hasAttached()) { |
| this.focusTrap.attachAnchors(); |
| } |
| }; |
| CdkTrapFocus.prototype.ngOnChanges = function (changes) { |
| var autoCaptureChange = changes['autoCapture']; |
| if (autoCaptureChange && !autoCaptureChange.firstChange && this.autoCapture && |
| this.focusTrap.hasAttached()) { |
| this._captureFocus(); |
| } |
| }; |
| CdkTrapFocus.prototype._captureFocus = function () { |
| var _a, _b; |
| // If the `activeElement` is inside a shadow root, `document.activeElement` will |
| // point to the shadow root so we have to descend into it ourselves. |
| var activeElement = (_a = this._document) === null || _a === void 0 ? void 0 : _a.activeElement; |
| this._previouslyFocusedElement = |
| ((_b = activeElement === null || activeElement === void 0 ? void 0 : activeElement.shadowRoot) === null || _b === void 0 ? void 0 : _b.activeElement) || activeElement; |
| this.focusTrap.focusInitialElementWhenReady(); |
| }; |
| return CdkTrapFocus; |
| }()); |
| CdkTrapFocus.decorators = [ |
| { type: i0.Directive, args: [{ |
| selector: '[cdkTrapFocus]', |
| exportAs: 'cdkTrapFocus', |
| },] } |
| ]; |
| CdkTrapFocus.ctorParameters = function () { return [ |
| { type: i0.ElementRef }, |
| { type: FocusTrapFactory }, |
| { type: undefined, decorators: [{ type: i0.Inject, args: [i2.DOCUMENT,] }] } |
| ]; }; |
| CdkTrapFocus.propDecorators = { |
| enabled: [{ type: i0.Input, args: ['cdkTrapFocus',] }], |
| autoCapture: [{ type: i0.Input, args: ['cdkTrapFocusAutoCapture',] }] |
| }; |
| |
| /** |
| * Class that allows for trapping focus within a DOM element. |
| * |
| * This class uses a strategy pattern that determines how it traps focus. |
| * See FocusTrapInertStrategy. |
| */ |
| var ConfigurableFocusTrap = /** @class */ (function (_super) { |
| __extends(ConfigurableFocusTrap, _super); |
| function ConfigurableFocusTrap(_element, _checker, _ngZone, _document, _focusTrapManager, _inertStrategy, config) { |
| var _this = _super.call(this, _element, _checker, _ngZone, _document, config.defer) || this; |
| _this._focusTrapManager = _focusTrapManager; |
| _this._inertStrategy = _inertStrategy; |
| _this._focusTrapManager.register(_this); |
| return _this; |
| } |
| Object.defineProperty(ConfigurableFocusTrap.prototype, "enabled", { |
| /** Whether the FocusTrap is enabled. */ |
| get: function () { return this._enabled; }, |
| set: function (value) { |
| this._enabled = value; |
| if (this._enabled) { |
| this._focusTrapManager.register(this); |
| } |
| else { |
| this._focusTrapManager.deregister(this); |
| } |
| }, |
| enumerable: false, |
| configurable: true |
| }); |
| /** Notifies the FocusTrapManager that this FocusTrap will be destroyed. */ |
| ConfigurableFocusTrap.prototype.destroy = function () { |
| this._focusTrapManager.deregister(this); |
| _super.prototype.destroy.call(this); |
| }; |
| /** @docs-private Implemented as part of ManagedFocusTrap. */ |
| ConfigurableFocusTrap.prototype._enable = function () { |
| this._inertStrategy.preventFocus(this); |
| this.toggleAnchors(true); |
| }; |
| /** @docs-private Implemented as part of ManagedFocusTrap. */ |
| ConfigurableFocusTrap.prototype._disable = function () { |
| this._inertStrategy.allowFocus(this); |
| this.toggleAnchors(false); |
| }; |
| return ConfigurableFocusTrap; |
| }(FocusTrap)); |
| |
| /** |
| * @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 |
| */ |
| /** IE 11 compatible closest implementation that is able to start from non-Element Nodes. */ |
| function closest(element, selector) { |
| if (!(element instanceof Node)) { |
| return null; |
| } |
| var curr = element; |
| while (curr != null && !(curr instanceof Element)) { |
| curr = curr.parentNode; |
| } |
| return curr && (hasNativeClosest ? |
| curr.closest(selector) : polyfillClosest(curr, selector)); |
| } |
| /** Polyfill for browsers without Element.closest. */ |
| function polyfillClosest(element, selector) { |
| var curr = element; |
| while (curr != null && !(curr instanceof Element && matches(curr, selector))) { |
| curr = curr.parentNode; |
| } |
| return (curr || null); |
| } |
| var hasNativeClosest = typeof Element != 'undefined' && !!Element.prototype.closest; |
| /** IE 11 compatible matches implementation. */ |
| function matches(element, selector) { |
| return element.matches ? |
| element.matches(selector) : |
| element['msMatchesSelector'](selector); |
| } |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Lightweight FocusTrapInertStrategy that adds a document focus event |
| * listener to redirect focus back inside the FocusTrap. |
| */ |
| var EventListenerFocusTrapInertStrategy = /** @class */ (function () { |
| function EventListenerFocusTrapInertStrategy() { |
| /** Focus event handler. */ |
| this._listener = null; |
| } |
| /** Adds a document event listener that keeps focus inside the FocusTrap. */ |
| EventListenerFocusTrapInertStrategy.prototype.preventFocus = function (focusTrap) { |
| var _this = this; |
| // Ensure there's only one listener per document |
| if (this._listener) { |
| focusTrap._document.removeEventListener('focus', this._listener, true); |
| } |
| this._listener = function (e) { return _this._trapFocus(focusTrap, e); }; |
| focusTrap._ngZone.runOutsideAngular(function () { |
| focusTrap._document.addEventListener('focus', _this._listener, true); |
| }); |
| }; |
| /** Removes the event listener added in preventFocus. */ |
| EventListenerFocusTrapInertStrategy.prototype.allowFocus = function (focusTrap) { |
| if (!this._listener) { |
| return; |
| } |
| focusTrap._document.removeEventListener('focus', this._listener, true); |
| this._listener = null; |
| }; |
| /** |
| * Refocuses the first element in the FocusTrap if the focus event target was outside |
| * the FocusTrap. |
| * |
| * This is an event listener callback. The event listener is added in runOutsideAngular, |
| * so all this code runs outside Angular as well. |
| */ |
| EventListenerFocusTrapInertStrategy.prototype._trapFocus = function (focusTrap, event) { |
| var target = event.target; |
| var focusTrapRoot = focusTrap._element; |
| // Don't refocus if target was in an overlay, because the overlay might be associated |
| // with an element inside the FocusTrap, ex. mat-select. |
| if (!focusTrapRoot.contains(target) && closest(target, 'div.cdk-overlay-pane') === null) { |
| // Some legacy FocusTrap usages have logic that focuses some element on the page |
| // just before FocusTrap is destroyed. For backwards compatibility, wait |
| // to be sure FocusTrap is still enabled before refocusing. |
| setTimeout(function () { |
| // Check whether focus wasn't put back into the focus trap while the timeout was pending. |
| if (focusTrap.enabled && !focusTrapRoot.contains(focusTrap._document.activeElement)) { |
| focusTrap.focusFirstTabbableElement(); |
| } |
| }); |
| } |
| }; |
| return EventListenerFocusTrapInertStrategy; |
| }()); |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Configuration for creating a ConfigurableFocusTrap. |
| */ |
| var ConfigurableFocusTrapConfig = /** @class */ (function () { |
| function ConfigurableFocusTrapConfig() { |
| /** |
| * Whether to defer the creation of FocusTrap elements to be |
| * done manually by the user. Default is to create them |
| * automatically. |
| */ |
| this.defer = false; |
| } |
| return ConfigurableFocusTrapConfig; |
| }()); |
| |
| /** |
| * @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 |
| */ |
| /** The injection token used to specify the inert strategy. */ |
| var FOCUS_TRAP_INERT_STRATEGY = new i0.InjectionToken('FOCUS_TRAP_INERT_STRATEGY'); |
| |
| /** |
| * @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 |
| */ |
| /** Injectable that ensures only the most recently enabled FocusTrap is active. */ |
| var FocusTrapManager = /** @class */ (function () { |
| function FocusTrapManager() { |
| // A stack of the FocusTraps on the page. Only the FocusTrap at the |
| // top of the stack is active. |
| this._focusTrapStack = []; |
| } |
| /** |
| * Disables the FocusTrap at the top of the stack, and then pushes |
| * the new FocusTrap onto the stack. |
| */ |
| FocusTrapManager.prototype.register = function (focusTrap) { |
| // Dedupe focusTraps that register multiple times. |
| this._focusTrapStack = this._focusTrapStack.filter(function (ft) { return ft !== focusTrap; }); |
| var stack = this._focusTrapStack; |
| if (stack.length) { |
| stack[stack.length - 1]._disable(); |
| } |
| stack.push(focusTrap); |
| focusTrap._enable(); |
| }; |
| /** |
| * Removes the FocusTrap from the stack, and activates the |
| * FocusTrap that is the new top of the stack. |
| */ |
| FocusTrapManager.prototype.deregister = function (focusTrap) { |
| focusTrap._disable(); |
| var stack = this._focusTrapStack; |
| var i = stack.indexOf(focusTrap); |
| if (i !== -1) { |
| stack.splice(i, 1); |
| if (stack.length) { |
| stack[stack.length - 1]._enable(); |
| } |
| } |
| }; |
| return FocusTrapManager; |
| }()); |
| FocusTrapManager.ɵprov = i0.ɵɵdefineInjectable({ factory: function FocusTrapManager_Factory() { return new FocusTrapManager(); }, token: FocusTrapManager, providedIn: "root" }); |
| FocusTrapManager.decorators = [ |
| { type: i0.Injectable, args: [{ providedIn: 'root' },] } |
| ]; |
| |
| /** |
| * @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 |
| */ |
| /** Factory that allows easy instantiation of configurable focus traps. */ |
| var ConfigurableFocusTrapFactory = /** @class */ (function () { |
| function ConfigurableFocusTrapFactory(_checker, _ngZone, _focusTrapManager, _document, _inertStrategy) { |
| this._checker = _checker; |
| this._ngZone = _ngZone; |
| this._focusTrapManager = _focusTrapManager; |
| this._document = _document; |
| // TODO split up the strategies into different modules, similar to DateAdapter. |
| this._inertStrategy = _inertStrategy || new EventListenerFocusTrapInertStrategy(); |
| } |
| ConfigurableFocusTrapFactory.prototype.create = function (element, config) { |
| if (config === void 0) { config = new ConfigurableFocusTrapConfig(); } |
| var configObject; |
| if (typeof config === 'boolean') { |
| configObject = new ConfigurableFocusTrapConfig(); |
| configObject.defer = config; |
| } |
| else { |
| configObject = config; |
| } |
| return new ConfigurableFocusTrap(element, this._checker, this._ngZone, this._document, this._focusTrapManager, this._inertStrategy, configObject); |
| }; |
| return ConfigurableFocusTrapFactory; |
| }()); |
| ConfigurableFocusTrapFactory.ɵprov = i0.ɵɵdefineInjectable({ factory: function ConfigurableFocusTrapFactory_Factory() { return new ConfigurableFocusTrapFactory(i0.ɵɵinject(InteractivityChecker), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(FocusTrapManager), i0.ɵɵinject(i2.DOCUMENT), i0.ɵɵinject(FOCUS_TRAP_INERT_STRATEGY, 8)); }, token: ConfigurableFocusTrapFactory, providedIn: "root" }); |
| ConfigurableFocusTrapFactory.decorators = [ |
| { type: i0.Injectable, args: [{ providedIn: 'root' },] } |
| ]; |
| ConfigurableFocusTrapFactory.ctorParameters = function () { return [ |
| { type: InteractivityChecker }, |
| { type: i0.NgZone }, |
| { type: FocusTrapManager }, |
| { type: undefined, decorators: [{ type: i0.Inject, args: [i2.DOCUMENT,] }] }, |
| { type: undefined, decorators: [{ type: i0.Optional }, { type: i0.Inject, args: [FOCUS_TRAP_INERT_STRATEGY,] }] } |
| ]; }; |
| |
| /** |
| * @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 |
| */ |
| var LIVE_ANNOUNCER_ELEMENT_TOKEN = new i0.InjectionToken('liveAnnouncerElement', { |
| providedIn: 'root', |
| factory: LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY, |
| }); |
| /** @docs-private */ |
| function LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY() { |
| return null; |
| } |
| /** Injection token that can be used to configure the default options for the LiveAnnouncer. */ |
| var LIVE_ANNOUNCER_DEFAULT_OPTIONS = new i0.InjectionToken('LIVE_ANNOUNCER_DEFAULT_OPTIONS'); |
| |
| var LiveAnnouncer = /** @class */ (function () { |
| function LiveAnnouncer(elementToken, _ngZone, _document, _defaultOptions) { |
| this._ngZone = _ngZone; |
| this._defaultOptions = _defaultOptions; |
| // We inject the live element and document as `any` because the constructor signature cannot |
| // reference browser globals (HTMLElement, Document) on non-browser environments, since having |
| // a class decorator causes TypeScript to preserve the constructor signature types. |
| this._document = _document; |
| this._liveElement = elementToken || this._createLiveElement(); |
| } |
| LiveAnnouncer.prototype.announce = function (message) { |
| var _a; |
| var _this = this; |
| var args = []; |
| for (var _i = 1; _i < arguments.length; _i++) { |
| args[_i - 1] = arguments[_i]; |
| } |
| var defaultOptions = this._defaultOptions; |
| var politeness; |
| var duration; |
| if (args.length === 1 && typeof args[0] === 'number') { |
| duration = args[0]; |
| } |
| else { |
| _a = __read(args, 2), politeness = _a[0], duration = _a[1]; |
| } |
| this.clear(); |
| clearTimeout(this._previousTimeout); |
| if (!politeness) { |
| politeness = |
| (defaultOptions && defaultOptions.politeness) ? defaultOptions.politeness : 'polite'; |
| } |
| if (duration == null && defaultOptions) { |
| duration = defaultOptions.duration; |
| } |
| // TODO: ensure changing the politeness works on all environments we support. |
| this._liveElement.setAttribute('aria-live', politeness); |
| // This 100ms timeout is necessary for some browser + screen-reader combinations: |
| // - Both JAWS and NVDA over IE11 will not announce anything without a non-zero timeout. |
| // - With Chrome and IE11 with NVDA or JAWS, a repeated (identical) message won't be read a |
| // second time without clearing and then using a non-zero delay. |
| // (using JAWS 17 at time of this writing). |
| return this._ngZone.runOutsideAngular(function () { |
| return new Promise(function (resolve) { |
| clearTimeout(_this._previousTimeout); |
| _this._previousTimeout = setTimeout(function () { |
| _this._liveElement.textContent = message; |
| resolve(); |
| if (typeof duration === 'number') { |
| _this._previousTimeout = setTimeout(function () { return _this.clear(); }, duration); |
| } |
| }, 100); |
| }); |
| }); |
| }; |
| /** |
| * Clears the current text from the announcer element. Can be used to prevent |
| * screen readers from reading the text out again while the user is going |
| * through the page landmarks. |
| */ |
| LiveAnnouncer.prototype.clear = function () { |
| if (this._liveElement) { |
| this._liveElement.textContent = ''; |
| } |
| }; |
| LiveAnnouncer.prototype.ngOnDestroy = function () { |
| clearTimeout(this._previousTimeout); |
| if (this._liveElement && this._liveElement.parentNode) { |
| this._liveElement.parentNode.removeChild(this._liveElement); |
| this._liveElement = null; |
| } |
| }; |
| LiveAnnouncer.prototype._createLiveElement = function () { |
| var elementClass = 'cdk-live-announcer-element'; |
| var previousElements = this._document.getElementsByClassName(elementClass); |
| var liveEl = this._document.createElement('div'); |
| // Remove any old containers. This can happen when coming in from a server-side-rendered page. |
| for (var i = 0; i < previousElements.length; i++) { |
| previousElements[i].parentNode.removeChild(previousElements[i]); |
| } |
| liveEl.classList.add(elementClass); |
| liveEl.classList.add('cdk-visually-hidden'); |
| liveEl.setAttribute('aria-atomic', 'true'); |
| liveEl.setAttribute('aria-live', 'polite'); |
| this._document.body.appendChild(liveEl); |
| return liveEl; |
| }; |
| return LiveAnnouncer; |
| }()); |
| LiveAnnouncer.ɵprov = i0.ɵɵdefineInjectable({ factory: function LiveAnnouncer_Factory() { return new LiveAnnouncer(i0.ɵɵinject(LIVE_ANNOUNCER_ELEMENT_TOKEN, 8), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i2.DOCUMENT), i0.ɵɵinject(LIVE_ANNOUNCER_DEFAULT_OPTIONS, 8)); }, token: LiveAnnouncer, providedIn: "root" }); |
| LiveAnnouncer.decorators = [ |
| { type: i0.Injectable, args: [{ providedIn: 'root' },] } |
| ]; |
| LiveAnnouncer.ctorParameters = function () { return [ |
| { type: undefined, decorators: [{ type: i0.Optional }, { type: i0.Inject, args: [LIVE_ANNOUNCER_ELEMENT_TOKEN,] }] }, |
| { type: i0.NgZone }, |
| { type: undefined, decorators: [{ type: i0.Inject, args: [i2.DOCUMENT,] }] }, |
| { type: undefined, decorators: [{ type: i0.Optional }, { type: i0.Inject, args: [LIVE_ANNOUNCER_DEFAULT_OPTIONS,] }] } |
| ]; }; |
| /** |
| * A directive that works similarly to aria-live, but uses the LiveAnnouncer to ensure compatibility |
| * with a wider range of browsers and screen readers. |
| */ |
| var CdkAriaLive = /** @class */ (function () { |
| function CdkAriaLive(_elementRef, _liveAnnouncer, _contentObserver, _ngZone) { |
| this._elementRef = _elementRef; |
| this._liveAnnouncer = _liveAnnouncer; |
| this._contentObserver = _contentObserver; |
| this._ngZone = _ngZone; |
| this._politeness = 'polite'; |
| } |
| Object.defineProperty(CdkAriaLive.prototype, "politeness", { |
| /** The aria-live politeness level to use when announcing messages. */ |
| get: function () { return this._politeness; }, |
| set: function (value) { |
| var _this = this; |
| this._politeness = value === 'off' || value === 'assertive' ? value : 'polite'; |
| if (this._politeness === 'off') { |
| if (this._subscription) { |
| this._subscription.unsubscribe(); |
| this._subscription = null; |
| } |
| } |
| else if (!this._subscription) { |
| this._subscription = this._ngZone.runOutsideAngular(function () { |
| return _this._contentObserver |
| .observe(_this._elementRef) |
| .subscribe(function () { |
| // Note that we use textContent here, rather than innerText, in order to avoid a reflow. |
| var elementText = _this._elementRef.nativeElement.textContent; |
| // The `MutationObserver` fires also for attribute |
| // changes which we don't want to announce. |
| if (elementText !== _this._previousAnnouncedText) { |
| _this._liveAnnouncer.announce(elementText, _this._politeness); |
| _this._previousAnnouncedText = elementText; |
| } |
| }); |
| }); |
| } |
| }, |
| enumerable: false, |
| configurable: true |
| }); |
| CdkAriaLive.prototype.ngOnDestroy = function () { |
| if (this._subscription) { |
| this._subscription.unsubscribe(); |
| } |
| }; |
| return CdkAriaLive; |
| }()); |
| CdkAriaLive.decorators = [ |
| { type: i0.Directive, args: [{ |
| selector: '[cdkAriaLive]', |
| exportAs: 'cdkAriaLive', |
| },] } |
| ]; |
| CdkAriaLive.ctorParameters = function () { return [ |
| { type: i0.ElementRef }, |
| { type: LiveAnnouncer }, |
| { type: observers.ContentObserver }, |
| { type: i0.NgZone } |
| ]; }; |
| CdkAriaLive.propDecorators = { |
| politeness: [{ type: i0.Input, args: ['cdkAriaLive',] }] |
| }; |
| |
| /** |
| * @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 |
| */ |
| /** Gets whether an event could be a faked `mousedown` event dispatched by a screen reader. */ |
| function isFakeMousedownFromScreenReader(event) { |
| // We can typically distinguish between these faked mousedown events and real mousedown events |
| // using the "buttons" property. While real mousedowns will indicate the mouse button that was |
| // pressed (e.g. "1" for the left mouse button), faked mousedowns will usually set the property |
| // value to 0. |
| return event.buttons === 0; |
| } |
| /** Gets whether an event could be a faked `touchstart` event dispatched by a screen reader. */ |
| function isFakeTouchstartFromScreenReader(event) { |
| var touch = (event.touches && event.touches[0]) || |
| (event.changedTouches && event.changedTouches[0]); |
| // A fake `touchstart` can be distinguished from a real one by looking at the `identifier` |
| // which is typically >= 0 on a real device versus -1 from a screen reader. Just to be safe, |
| // we can also look at `radiusX` and `radiusY`. This behavior was observed against a Windows 10 |
| // device with a touch screen running NVDA v2020.4 and Firefox 85 or Chrome 88. |
| return !!touch && touch.identifier === -1 && (touch.radiusX == null || touch.radiusX === 1) && |
| (touch.radiusY == null || touch.radiusY === 1); |
| } |
| |
| // This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found |
| // that a value of around 650ms seems appropriate. |
| var TOUCH_BUFFER_MS = 650; |
| /** InjectionToken for FocusMonitorOptions. */ |
| var FOCUS_MONITOR_DEFAULT_OPTIONS = new i0.InjectionToken('cdk-focus-monitor-default-options'); |
| /** |
| * Event listener options that enable capturing and also |
| * mark the listener as passive if the browser supports it. |
| */ |
| var captureEventListenerOptions = i1.normalizePassiveListenerOptions({ |
| passive: true, |
| capture: true |
| }); |
| /** Monitors mouse and keyboard events to determine the cause of focus events. */ |
| var FocusMonitor = /** @class */ (function () { |
| function FocusMonitor(_ngZone, _platform, |
| /** @breaking-change 11.0.0 make document required */ |
| document, options) { |
| var _this = this; |
| this._ngZone = _ngZone; |
| this._platform = _platform; |
| /** The focus origin that the next focus event is a result of. */ |
| this._origin = null; |
| /** Whether the window has just been focused. */ |
| this._windowFocused = false; |
| /** Map of elements being monitored to their info. */ |
| this._elementInfo = new Map(); |
| /** The number of elements currently being monitored. */ |
| this._monitoredElementCount = 0; |
| /** |
| * Keeps track of the root nodes to which we've currently bound a focus/blur handler, |
| * as well as the number of monitored elements that they contain. We have to treat focus/blur |
| * handlers differently from the rest of the events, because the browser won't emit events |
| * to the document when focus moves inside of a shadow root. |
| */ |
| this._rootNodeFocusListenerCount = new Map(); |
| /** |
| * Event listener for `keydown` events on the document. |
| * Needs to be an arrow function in order to preserve the context when it gets bound. |
| */ |
| this._documentKeydownListener = function () { |
| // On keydown record the origin and clear any touch event that may be in progress. |
| _this._lastTouchTarget = null; |
| _this._setOriginForCurrentEventQueue('keyboard'); |
| }; |
| /** |
| * Event listener for `mousedown` events on the document. |
| * Needs to be an arrow function in order to preserve the context when it gets bound. |
| */ |
| this._documentMousedownListener = function (event) { |
| // On mousedown record the origin only if there is not touch |
| // target, since a mousedown can happen as a result of a touch event. |
| if (!_this._lastTouchTarget) { |
| // In some cases screen readers fire fake `mousedown` events instead of `keydown`. |
| // Resolve the focus source to `keyboard` if we detect one of them. |
| var source = isFakeMousedownFromScreenReader(event) ? 'keyboard' : 'mouse'; |
| _this._setOriginForCurrentEventQueue(source); |
| } |
| }; |
| /** |
| * Event listener for `touchstart` events on the document. |
| * Needs to be an arrow function in order to preserve the context when it gets bound. |
| */ |
| this._documentTouchstartListener = function (event) { |
| // Some screen readers will fire a fake `touchstart` event if an element is activated using |
| // the keyboard while on a device with a touchsreen. Consider such events as keyboard focus. |
| if (!isFakeTouchstartFromScreenReader(event)) { |
| // When the touchstart event fires the focus event is not yet in the event queue. This means |
| // we can't rely on the trick used above (setting timeout of 1ms). Instead we wait 650ms to |
| // see if a focus happens. |
| if (_this._touchTimeoutId != null) { |
| clearTimeout(_this._touchTimeoutId); |
| } |
| _this._lastTouchTarget = getTarget(event); |
| _this._touchTimeoutId = setTimeout(function () { return _this._lastTouchTarget = null; }, TOUCH_BUFFER_MS); |
| } |
| else if (!_this._lastTouchTarget) { |
| _this._setOriginForCurrentEventQueue('keyboard'); |
| } |
| }; |
| /** |
| * Event listener for `focus` events on the window. |
| * Needs to be an arrow function in order to preserve the context when it gets bound. |
| */ |
| this._windowFocusListener = function () { |
| // Make a note of when the window regains focus, so we can |
| // restore the origin info for the focused element. |
| _this._windowFocused = true; |
| _this._windowFocusTimeoutId = setTimeout(function () { return _this._windowFocused = false; }); |
| }; |
| /** |
| * Event listener for `focus` and 'blur' events on the document. |
| * Needs to be an arrow function in order to preserve the context when it gets bound. |
| */ |
| this._rootNodeFocusAndBlurListener = function (event) { |
| var target = getTarget(event); |
| var handler = event.type === 'focus' ? _this._onFocus : _this._onBlur; |
| // We need to walk up the ancestor chain in order to support `checkChildren`. |
| for (var element = target; element; element = element.parentElement) { |
| handler.call(_this, event, element); |
| } |
| }; |
| this._document = document; |
| this._detectionMode = (options === null || options === void 0 ? void 0 : options.detectionMode) || 0 /* IMMEDIATE */; |
| } |
| FocusMonitor.prototype.monitor = function (element, checkChildren) { |
| if (checkChildren === void 0) { checkChildren = false; } |
| var nativeElement = coercion.coerceElement(element); |
| // Do nothing if we're not on the browser platform or the passed in node isn't an element. |
| if (!this._platform.isBrowser || nativeElement.nodeType !== 1) { |
| return rxjs.of(null); |
| } |
| // If the element is inside the shadow DOM, we need to bind our focus/blur listeners to |
| // the shadow root, rather than the `document`, because the browser won't emit focus events |
| // to the `document`, if focus is moving within the same shadow root. |
| var rootNode = i1._getShadowRoot(nativeElement) || this._getDocument(); |
| var cachedInfo = this._elementInfo.get(nativeElement); |
| // Check if we're already monitoring this element. |
| if (cachedInfo) { |
| if (checkChildren) { |
| // TODO(COMP-318): this can be problematic, because it'll turn all non-checkChildren |
| // observers into ones that behave as if `checkChildren` was turned on. We need a more |
| // robust solution. |
| cachedInfo.checkChildren = true; |
| } |
| return cachedInfo.subject; |
| } |
| // Create monitored element info. |
| var info = { |
| checkChildren: checkChildren, |
| subject: new rxjs.Subject(), |
| rootNode: rootNode |
| }; |
| this._elementInfo.set(nativeElement, info); |
| this._registerGlobalListeners(info); |
| return info.subject; |
| }; |
| FocusMonitor.prototype.stopMonitoring = function (element) { |
| var nativeElement = coercion.coerceElement(element); |
| var elementInfo = this._elementInfo.get(nativeElement); |
| if (elementInfo) { |
| elementInfo.subject.complete(); |
| this._setClasses(nativeElement); |
| this._elementInfo.delete(nativeElement); |
| this._removeGlobalListeners(elementInfo); |
| } |
| }; |
| FocusMonitor.prototype.focusVia = function (element, origin, options) { |
| var _this = this; |
| var nativeElement = coercion.coerceElement(element); |
| var focusedElement = this._getDocument().activeElement; |
| // If the element is focused already, calling `focus` again won't trigger the event listener |
| // which means that the focus classes won't be updated. If that's the case, update the classes |
| // directly without waiting for an event. |
| if (nativeElement === focusedElement) { |
| this._getClosestElementsInfo(nativeElement) |
| .forEach(function (_a) { |
| var _b = __read(_a, 2), currentElement = _b[0], info = _b[1]; |
| return _this._originChanged(currentElement, origin, info); |
| }); |
| } |
| else { |
| this._setOriginForCurrentEventQueue(origin); |
| // `focus` isn't available on the server |
| if (typeof nativeElement.focus === 'function') { |
| nativeElement.focus(options); |
| } |
| } |
| }; |
| FocusMonitor.prototype.ngOnDestroy = function () { |
| var _this = this; |
| this._elementInfo.forEach(function (_info, element) { return _this.stopMonitoring(element); }); |
| }; |
| /** Access injected document if available or fallback to global document reference */ |
| FocusMonitor.prototype._getDocument = function () { |
| return this._document || document; |
| }; |
| /** Use defaultView of injected document if available or fallback to global window reference */ |
| FocusMonitor.prototype._getWindow = function () { |
| var doc = this._getDocument(); |
| return doc.defaultView || window; |
| }; |
| FocusMonitor.prototype._toggleClass = function (element, className, shouldSet) { |
| if (shouldSet) { |
| element.classList.add(className); |
| } |
| else { |
| element.classList.remove(className); |
| } |
| }; |
| FocusMonitor.prototype._getFocusOrigin = function (event) { |
| // If we couldn't detect a cause for the focus event, it's due to one of three reasons: |
| // 1) The window has just regained focus, in which case we want to restore the focused state of |
| // the element from before the window blurred. |
| // 2) It was caused by a touch event, in which case we mark the origin as 'touch'. |
| // 3) The element was programmatically focused, in which case we should mark the origin as |
| // 'program'. |
| if (this._origin) { |
| return this._origin; |
| } |
| if (this._windowFocused && this._lastFocusOrigin) { |
| return this._lastFocusOrigin; |
| } |
| else if (this._wasCausedByTouch(event)) { |
| return 'touch'; |
| } |
| else { |
| return 'program'; |
| } |
| }; |
| /** |
| * Sets the focus classes on the element based on the given focus origin. |
| * @param element The element to update the classes on. |
| * @param origin The focus origin. |
| */ |
| FocusMonitor.prototype._setClasses = function (element, origin) { |
| this._toggleClass(element, 'cdk-focused', !!origin); |
| this._toggleClass(element, 'cdk-touch-focused', origin === 'touch'); |
| this._toggleClass(element, 'cdk-keyboard-focused', origin === 'keyboard'); |
| this._toggleClass(element, 'cdk-mouse-focused', origin === 'mouse'); |
| this._toggleClass(element, 'cdk-program-focused', origin === 'program'); |
| }; |
| /** |
| * Sets the origin and schedules an async function to clear it at the end of the event queue. |
| * If the detection mode is 'eventual', the origin is never cleared. |
| * @param origin The origin to set. |
| */ |
| FocusMonitor.prototype._setOriginForCurrentEventQueue = function (origin) { |
| var _this = this; |
| this._ngZone.runOutsideAngular(function () { |
| _this._origin = origin; |
| if (_this._detectionMode === 0 /* IMMEDIATE */) { |
| // Sometimes the focus origin won't be valid in Firefox because Firefox seems to focus *one* |
| // tick after the interaction event fired. To ensure the focus origin is always correct, |
| // the focus origin will be determined at the beginning of the next tick. |
| _this._originTimeoutId = setTimeout(function () { return _this._origin = null; }, 1); |
| } |
| }); |
| }; |
| /** |
| * Checks whether the given focus event was caused by a touchstart event. |
| * @param event The focus event to check. |
| * @returns Whether the event was caused by a touch. |
| */ |
| FocusMonitor.prototype._wasCausedByTouch = function (event) { |
| // Note(mmalerba): This implementation is not quite perfect, there is a small edge case. |
| // Consider the following dom structure: |
| // |
| // <div #parent tabindex="0" cdkFocusClasses> |
| // <div #child (click)="#parent.focus()"></div> |
| // </div> |
| // |
| // If the user touches the #child element and the #parent is programmatically focused as a |
| // result, this code will still consider it to have been caused by the touch event and will |
| // apply the cdk-touch-focused class rather than the cdk-program-focused class. This is a |
| // relatively small edge-case that can be worked around by using |
| // focusVia(parentEl, 'program') to focus the parent element. |
| // |
| // If we decide that we absolutely must handle this case correctly, we can do so by listening |
| // for the first focus event after the touchstart, and then the first blur event after that |
| // focus event. When that blur event fires we know that whatever follows is not a result of the |
| // touchstart. |
| var focusTarget = getTarget(event); |
| return this._lastTouchTarget instanceof Node && focusTarget instanceof Node && |
| (focusTarget === this._lastTouchTarget || focusTarget.contains(this._lastTouchTarget)); |
| }; |
| /** |
| * Handles focus events on a registered element. |
| * @param event The focus event. |
| * @param element The monitored element. |
| */ |
| FocusMonitor.prototype._onFocus = function (event, element) { |
| // NOTE(mmalerba): We currently set the classes based on the focus origin of the most recent |
| // focus event affecting the monitored element. If we want to use the origin of the first event |
| // instead we should check for the cdk-focused class here and return if the element already has |
| // it. (This only matters for elements that have includesChildren = true). |
| // If we are not counting child-element-focus as focused, make sure that the event target is the |
| // monitored element itself. |
| var elementInfo = this._elementInfo.get(element); |
| if (!elementInfo || (!elementInfo.checkChildren && element !== getTarget(event))) { |
| return; |
| } |
| this._originChanged(element, this._getFocusOrigin(event), elementInfo); |
| }; |
| /** |
| * Handles blur events on a registered element. |
| * @param event The blur event. |
| * @param element The monitored element. |
| */ |
| FocusMonitor.prototype._onBlur = function (event, element) { |
| // If we are counting child-element-focus as focused, make sure that we aren't just blurring in |
| // order to focus another child of the monitored element. |
| var elementInfo = this._elementInfo.get(element); |
| if (!elementInfo || (elementInfo.checkChildren && event.relatedTarget instanceof Node && |
| element.contains(event.relatedTarget))) { |
| return; |
| } |
| this._setClasses(element); |
| this._emitOrigin(elementInfo.subject, null); |
| }; |
| FocusMonitor.prototype._emitOrigin = function (subject, origin) { |
| this._ngZone.run(function () { return subject.next(origin); }); |
| }; |
| FocusMonitor.prototype._registerGlobalListeners = function (elementInfo) { |
| var _this = this; |
| if (!this._platform.isBrowser) { |
| return; |
| } |
| var rootNode = elementInfo.rootNode; |
| var rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode) || 0; |
| if (!rootNodeFocusListeners) { |
| this._ngZone.runOutsideAngular(function () { |
| rootNode.addEventListener('focus', _this._rootNodeFocusAndBlurListener, captureEventListenerOptions); |
| rootNode.addEventListener('blur', _this._rootNodeFocusAndBlurListener, captureEventListenerOptions); |
| }); |
| } |
| this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners + 1); |
| // Register global listeners when first element is monitored. |
| if (++this._monitoredElementCount === 1) { |
| // Note: we listen to events in the capture phase so we |
| // can detect them even if the user stops propagation. |
| this._ngZone.runOutsideAngular(function () { |
| var document = _this._getDocument(); |
| var window = _this._getWindow(); |
| document.addEventListener('keydown', _this._documentKeydownListener, captureEventListenerOptions); |
| document.addEventListener('mousedown', _this._documentMousedownListener, captureEventListenerOptions); |
| document.addEventListener('touchstart', _this._documentTouchstartListener, captureEventListenerOptions); |
| window.addEventListener('focus', _this._windowFocusListener); |
| }); |
| } |
| }; |
| FocusMonitor.prototype._removeGlobalListeners = function (elementInfo) { |
| var rootNode = elementInfo.rootNode; |
| if (this._rootNodeFocusListenerCount.has(rootNode)) { |
| var rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode); |
| if (rootNodeFocusListeners > 1) { |
| this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners - 1); |
| } |
| else { |
| rootNode.removeEventListener('focus', this._rootNodeFocusAndBlurListener, captureEventListenerOptions); |
| rootNode.removeEventListener('blur', this._rootNodeFocusAndBlurListener, captureEventListenerOptions); |
| this._rootNodeFocusListenerCount.delete(rootNode); |
| } |
| } |
| // Unregister global listeners when last element is unmonitored. |
| if (!--this._monitoredElementCount) { |
| var document = this._getDocument(); |
| var window = this._getWindow(); |
| document.removeEventListener('keydown', this._documentKeydownListener, captureEventListenerOptions); |
| document.removeEventListener('mousedown', this._documentMousedownListener, captureEventListenerOptions); |
| document.removeEventListener('touchstart', this._documentTouchstartListener, captureEventListenerOptions); |
| window.removeEventListener('focus', this._windowFocusListener); |
| // Clear timeouts for all potentially pending timeouts to prevent the leaks. |
| clearTimeout(this._windowFocusTimeoutId); |
| clearTimeout(this._touchTimeoutId); |
| clearTimeout(this._originTimeoutId); |
| } |
| }; |
| /** Updates all the state on an element once its focus origin has changed. */ |
| FocusMonitor.prototype._originChanged = function (element, origin, elementInfo) { |
| this._setClasses(element, origin); |
| this._emitOrigin(elementInfo.subject, origin); |
| this._lastFocusOrigin = origin; |
| }; |
| /** |
| * Collects the `MonitoredElementInfo` of a particular element and |
| * all of its ancestors that have enabled `checkChildren`. |
| * @param element Element from which to start the search. |
| */ |
| FocusMonitor.prototype._getClosestElementsInfo = function (element) { |
| var results = []; |
| this._elementInfo.forEach(function (info, currentElement) { |
| if (currentElement === element || (info.checkChildren && currentElement.contains(element))) { |
| results.push([currentElement, info]); |
| } |
| }); |
| return results; |
| }; |
| return FocusMonitor; |
| }()); |
| FocusMonitor.ɵprov = i0.ɵɵdefineInjectable({ factory: function FocusMonitor_Factory() { return new FocusMonitor(i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i1.Platform), i0.ɵɵinject(i2.DOCUMENT, 8), i0.ɵɵinject(FOCUS_MONITOR_DEFAULT_OPTIONS, 8)); }, token: FocusMonitor, providedIn: "root" }); |
| FocusMonitor.decorators = [ |
| { type: i0.Injectable, args: [{ providedIn: 'root' },] } |
| ]; |
| FocusMonitor.ctorParameters = function () { return [ |
| { type: i0.NgZone }, |
| { type: i1.Platform }, |
| { type: undefined, decorators: [{ type: i0.Optional }, { type: i0.Inject, args: [i2.DOCUMENT,] }] }, |
| { type: undefined, decorators: [{ type: i0.Optional }, { type: i0.Inject, args: [FOCUS_MONITOR_DEFAULT_OPTIONS,] }] } |
| ]; }; |
| /** Gets the target of an event, accounting for Shadow DOM. */ |
| function getTarget(event) { |
| // If an event is bound outside the Shadow DOM, the `event.target` will |
| // point to the shadow root so we have to use `composedPath` instead. |
| return (event.composedPath ? event.composedPath()[0] : event.target); |
| } |
| /** |
| * Directive that determines how a particular element was focused (via keyboard, mouse, touch, or |
| * programmatically) and adds corresponding classes to the element. |
| * |
| * There are two variants of this directive: |
| * 1) cdkMonitorElementFocus: does not consider an element to be focused if one of its children is |
| * focused. |
| * 2) cdkMonitorSubtreeFocus: considers an element focused if it or any of its children are focused. |
| */ |
| var CdkMonitorFocus = /** @class */ (function () { |
| function CdkMonitorFocus(_elementRef, _focusMonitor) { |
| this._elementRef = _elementRef; |
| this._focusMonitor = _focusMonitor; |
| this.cdkFocusChange = new i0.EventEmitter(); |
| } |
| CdkMonitorFocus.prototype.ngAfterViewInit = function () { |
| var _this = this; |
| var element = this._elementRef.nativeElement; |
| this._monitorSubscription = this._focusMonitor.monitor(element, element.nodeType === 1 && element.hasAttribute('cdkMonitorSubtreeFocus')) |
| .subscribe(function (origin) { return _this.cdkFocusChange.emit(origin); }); |
| }; |
| CdkMonitorFocus.prototype.ngOnDestroy = function () { |
| this._focusMonitor.stopMonitoring(this._elementRef); |
| if (this._monitorSubscription) { |
| this._monitorSubscription.unsubscribe(); |
| } |
| }; |
| return CdkMonitorFocus; |
| }()); |
| CdkMonitorFocus.decorators = [ |
| { type: i0.Directive, args: [{ |
| selector: '[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]', |
| },] } |
| ]; |
| CdkMonitorFocus.ctorParameters = function () { return [ |
| { type: i0.ElementRef }, |
| { type: FocusMonitor } |
| ]; }; |
| CdkMonitorFocus.propDecorators = { |
| cdkFocusChange: [{ type: i0.Output }] |
| }; |
| |
| /** |
| * @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 |
| */ |
| /** CSS class applied to the document body when in black-on-white high-contrast mode. */ |
| var BLACK_ON_WHITE_CSS_CLASS = 'cdk-high-contrast-black-on-white'; |
| /** CSS class applied to the document body when in white-on-black high-contrast mode. */ |
| var WHITE_ON_BLACK_CSS_CLASS = 'cdk-high-contrast-white-on-black'; |
| /** CSS class applied to the document body when in high-contrast mode. */ |
| var HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS = 'cdk-high-contrast-active'; |
| /** |
| * Service to determine whether the browser is currently in a high-contrast-mode environment. |
| * |
| * Microsoft Windows supports an accessibility feature called "High Contrast Mode". This mode |
| * changes the appearance of all applications, including web applications, to dramatically increase |
| * contrast. |
| * |
| * IE, Edge, and Firefox currently support this mode. Chrome does not support Windows High Contrast |
| * Mode. This service does not detect high-contrast mode as added by the Chrome "High Contrast" |
| * browser extension. |
| */ |
| var HighContrastModeDetector = /** @class */ (function () { |
| function HighContrastModeDetector(_platform, document) { |
| this._platform = _platform; |
| this._document = document; |
| } |
| /** Gets the current high-contrast-mode for the page. */ |
| HighContrastModeDetector.prototype.getHighContrastMode = function () { |
| if (!this._platform.isBrowser) { |
| return 0 /* NONE */; |
| } |
| // Create a test element with an arbitrary background-color that is neither black nor |
| // white; high-contrast mode will coerce the color to either black or white. Also ensure that |
| // appending the test element to the DOM does not affect layout by absolutely positioning it |
| var testElement = this._document.createElement('div'); |
| testElement.style.backgroundColor = 'rgb(1,2,3)'; |
| testElement.style.position = 'absolute'; |
| this._document.body.appendChild(testElement); |
| // Get the computed style for the background color, collapsing spaces to normalize between |
| // browsers. Once we get this color, we no longer need the test element. Access the `window` |
| // via the document so we can fake it in tests. Note that we have extra null checks, because |
| // this logic will likely run during app bootstrap and throwing can break the entire app. |
| var documentWindow = this._document.defaultView || window; |
| var computedStyle = (documentWindow && documentWindow.getComputedStyle) ? |
| documentWindow.getComputedStyle(testElement) : null; |
| var computedColor = (computedStyle && computedStyle.backgroundColor || '').replace(/ /g, ''); |
| this._document.body.removeChild(testElement); |
| switch (computedColor) { |
| case 'rgb(0,0,0)': return 2 /* WHITE_ON_BLACK */; |
| case 'rgb(255,255,255)': return 1 /* BLACK_ON_WHITE */; |
| } |
| return 0 /* NONE */; |
| }; |
| /** Applies CSS classes indicating high-contrast mode to document body (browser-only). */ |
| HighContrastModeDetector.prototype._applyBodyHighContrastModeCssClasses = function () { |
| if (this._platform.isBrowser && this._document.body) { |
| var bodyClasses = this._document.body.classList; |
| // IE11 doesn't support `classList` operations with multiple arguments |
| bodyClasses.remove(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS); |
| bodyClasses.remove(BLACK_ON_WHITE_CSS_CLASS); |
| bodyClasses.remove(WHITE_ON_BLACK_CSS_CLASS); |
| var mode = this.getHighContrastMode(); |
| if (mode === 1 /* BLACK_ON_WHITE */) { |
| bodyClasses.add(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS); |
| bodyClasses.add(BLACK_ON_WHITE_CSS_CLASS); |
| } |
| else if (mode === 2 /* WHITE_ON_BLACK */) { |
| bodyClasses.add(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS); |
| bodyClasses.add(WHITE_ON_BLACK_CSS_CLASS); |
| } |
| } |
| }; |
| return HighContrastModeDetector; |
| }()); |
| HighContrastModeDetector.ɵprov = i0.ɵɵdefineInjectable({ factory: function HighContrastModeDetector_Factory() { return new HighContrastModeDetector(i0.ɵɵinject(i1.Platform), i0.ɵɵinject(i2.DOCUMENT)); }, token: HighContrastModeDetector, providedIn: "root" }); |
| HighContrastModeDetector.decorators = [ |
| { type: i0.Injectable, args: [{ providedIn: 'root' },] } |
| ]; |
| HighContrastModeDetector.ctorParameters = function () { return [ |
| { type: i1.Platform }, |
| { type: undefined, decorators: [{ type: i0.Inject, args: [i2.DOCUMENT,] }] } |
| ]; }; |
| |
| /** |
| * @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 |
| */ |
| var A11yModule = /** @class */ (function () { |
| function A11yModule(highContrastModeDetector) { |
| highContrastModeDetector._applyBodyHighContrastModeCssClasses(); |
| } |
| return A11yModule; |
| }()); |
| A11yModule.decorators = [ |
| { type: i0.NgModule, args: [{ |
| imports: [i1.PlatformModule, observers.ObserversModule], |
| declarations: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus], |
| exports: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus], |
| },] } |
| ]; |
| A11yModule.ctorParameters = function () { return [ |
| { type: HighContrastModeDetector } |
| ]; }; |
| |
| /** |
| * @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 |
| */ |
| |
| /** |
| * Generated bundle index. Do not edit. |
| */ |
| |
| exports.A11yModule = A11yModule; |
| exports.ActiveDescendantKeyManager = ActiveDescendantKeyManager; |
| exports.AriaDescriber = AriaDescriber; |
| exports.CDK_DESCRIBEDBY_HOST_ATTRIBUTE = CDK_DESCRIBEDBY_HOST_ATTRIBUTE; |
| exports.CDK_DESCRIBEDBY_ID_PREFIX = CDK_DESCRIBEDBY_ID_PREFIX; |
| exports.CdkAriaLive = CdkAriaLive; |
| exports.CdkMonitorFocus = CdkMonitorFocus; |
| exports.CdkTrapFocus = CdkTrapFocus; |
| exports.ConfigurableFocusTrap = ConfigurableFocusTrap; |
| exports.ConfigurableFocusTrapFactory = ConfigurableFocusTrapFactory; |
| exports.EventListenerFocusTrapInertStrategy = EventListenerFocusTrapInertStrategy; |
| exports.FOCUS_MONITOR_DEFAULT_OPTIONS = FOCUS_MONITOR_DEFAULT_OPTIONS; |
| exports.FOCUS_TRAP_INERT_STRATEGY = FOCUS_TRAP_INERT_STRATEGY; |
| exports.FocusKeyManager = FocusKeyManager; |
| exports.FocusMonitor = FocusMonitor; |
| exports.FocusTrap = FocusTrap; |
| exports.FocusTrapFactory = FocusTrapFactory; |
| exports.HighContrastModeDetector = HighContrastModeDetector; |
| exports.InteractivityChecker = InteractivityChecker; |
| exports.IsFocusableConfig = IsFocusableConfig; |
| exports.LIVE_ANNOUNCER_DEFAULT_OPTIONS = LIVE_ANNOUNCER_DEFAULT_OPTIONS; |
| exports.LIVE_ANNOUNCER_ELEMENT_TOKEN = LIVE_ANNOUNCER_ELEMENT_TOKEN; |
| exports.LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY = LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY; |
| exports.ListKeyManager = ListKeyManager; |
| exports.LiveAnnouncer = LiveAnnouncer; |
| exports.MESSAGES_CONTAINER_ID = MESSAGES_CONTAINER_ID; |
| exports.TOUCH_BUFFER_MS = TOUCH_BUFFER_MS; |
| exports.isFakeMousedownFromScreenReader = isFakeMousedownFromScreenReader; |
| exports.isFakeTouchstartFromScreenReader = isFakeTouchstartFromScreenReader; |
| exports.ɵangular_material_src_cdk_a11y_a11y_a = FocusTrapManager; |
| exports.ɵangular_material_src_cdk_a11y_a11y_b = ConfigurableFocusTrapConfig; |
| |
| Object.defineProperty(exports, '__esModule', { value: true }); |
| |
| }))); |
| //# sourceMappingURL=cdk-a11y.umd.js.map |