source: trip-planner-front/node_modules/@angular/cdk/fesm2015/a11y.js@ fa375fe

Last change on this file since fa375fe was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 102.7 KB
Line 
1import * as i2 from '@angular/common';
2import { DOCUMENT } from '@angular/common';
3import * as i0 from '@angular/core';
4import { Injectable, Inject, QueryList, NgZone, Directive, ElementRef, Input, InjectionToken, Optional, EventEmitter, Output, NgModule } from '@angular/core';
5import { Subject, Subscription, BehaviorSubject, of } from 'rxjs';
6import { hasModifierKey, A, Z, ZERO, NINE, END, HOME, LEFT_ARROW, RIGHT_ARROW, UP_ARROW, DOWN_ARROW, TAB, ALT, CONTROL, MAC_META, META, SHIFT } from '@angular/cdk/keycodes';
7import { tap, debounceTime, filter, map, take, skip, distinctUntilChanged, takeUntil } from 'rxjs/operators';
8import { coerceBooleanProperty, coerceElement } from '@angular/cdk/coercion';
9import * as i1 from '@angular/cdk/platform';
10import { Platform, _getFocusedElementPierceShadowDom, normalizePassiveListenerOptions, _getEventTarget, _getShadowRoot, PlatformModule } from '@angular/cdk/platform';
11import { ContentObserver, ObserversModule } from '@angular/cdk/observers';
12
13/**
14 * @license
15 * Copyright Google LLC All Rights Reserved.
16 *
17 * Use of this source code is governed by an MIT-style license that can be
18 * found in the LICENSE file at https://angular.io/license
19 */
20/** IDs are delimited by an empty space, as per the spec. */
21const ID_DELIMITER = ' ';
22/**
23 * Adds the given ID to the specified ARIA attribute on an element.
24 * Used for attributes such as aria-labelledby, aria-owns, etc.
25 */
26function addAriaReferencedId(el, attr, id) {
27 const ids = getAriaReferenceIds(el, attr);
28 if (ids.some(existingId => existingId.trim() == id.trim())) {
29 return;
30 }
31 ids.push(id.trim());
32 el.setAttribute(attr, ids.join(ID_DELIMITER));
33}
34/**
35 * Removes the given ID from the specified ARIA attribute on an element.
36 * Used for attributes such as aria-labelledby, aria-owns, etc.
37 */
38function removeAriaReferencedId(el, attr, id) {
39 const ids = getAriaReferenceIds(el, attr);
40 const filteredIds = ids.filter(val => val != id.trim());
41 if (filteredIds.length) {
42 el.setAttribute(attr, filteredIds.join(ID_DELIMITER));
43 }
44 else {
45 el.removeAttribute(attr);
46 }
47}
48/**
49 * Gets the list of IDs referenced by the given ARIA attribute on an element.
50 * Used for attributes such as aria-labelledby, aria-owns, etc.
51 */
52function getAriaReferenceIds(el, attr) {
53 // Get string array of all individual ids (whitespace delimited) in the attribute value
54 return (el.getAttribute(attr) || '').match(/\S+/g) || [];
55}
56
57/**
58 * @license
59 * Copyright Google LLC All Rights Reserved.
60 *
61 * Use of this source code is governed by an MIT-style license that can be
62 * found in the LICENSE file at https://angular.io/license
63 */
64/** ID used for the body container where all messages are appended. */
65const MESSAGES_CONTAINER_ID = 'cdk-describedby-message-container';
66/** ID prefix used for each created message element. */
67const CDK_DESCRIBEDBY_ID_PREFIX = 'cdk-describedby-message';
68/** Attribute given to each host element that is described by a message element. */
69const CDK_DESCRIBEDBY_HOST_ATTRIBUTE = 'cdk-describedby-host';
70/** Global incremental identifier for each registered message element. */
71let nextId = 0;
72/** Global map of all registered message elements that have been placed into the document. */
73const messageRegistry = new Map();
74/** Container for all registered messages. */
75let messagesContainer = null;
76/**
77 * Utility that creates visually hidden elements with a message content. Useful for elements that
78 * want to use aria-describedby to further describe themselves without adding additional visual
79 * content.
80 */
81class AriaDescriber {
82 constructor(_document) {
83 this._document = _document;
84 }
85 describe(hostElement, message, role) {
86 if (!this._canBeDescribed(hostElement, message)) {
87 return;
88 }
89 const key = getKey(message, role);
90 if (typeof message !== 'string') {
91 // We need to ensure that the element has an ID.
92 setMessageId(message);
93 messageRegistry.set(key, { messageElement: message, referenceCount: 0 });
94 }
95 else if (!messageRegistry.has(key)) {
96 this._createMessageElement(message, role);
97 }
98 if (!this._isElementDescribedByMessage(hostElement, key)) {
99 this._addMessageReference(hostElement, key);
100 }
101 }
102 removeDescription(hostElement, message, role) {
103 if (!message || !this._isElementNode(hostElement)) {
104 return;
105 }
106 const key = getKey(message, role);
107 if (this._isElementDescribedByMessage(hostElement, key)) {
108 this._removeMessageReference(hostElement, key);
109 }
110 // If the message is a string, it means that it's one that we created for the
111 // consumer so we can remove it safely, otherwise we should leave it in place.
112 if (typeof message === 'string') {
113 const registeredMessage = messageRegistry.get(key);
114 if (registeredMessage && registeredMessage.referenceCount === 0) {
115 this._deleteMessageElement(key);
116 }
117 }
118 if (messagesContainer && messagesContainer.childNodes.length === 0) {
119 this._deleteMessagesContainer();
120 }
121 }
122 /** Unregisters all created message elements and removes the message container. */
123 ngOnDestroy() {
124 const describedElements = this._document.querySelectorAll(`[${CDK_DESCRIBEDBY_HOST_ATTRIBUTE}]`);
125 for (let i = 0; i < describedElements.length; i++) {
126 this._removeCdkDescribedByReferenceIds(describedElements[i]);
127 describedElements[i].removeAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE);
128 }
129 if (messagesContainer) {
130 this._deleteMessagesContainer();
131 }
132 messageRegistry.clear();
133 }
134 /**
135 * Creates a new element in the visually hidden message container element with the message
136 * as its content and adds it to the message registry.
137 */
138 _createMessageElement(message, role) {
139 const messageElement = this._document.createElement('div');
140 setMessageId(messageElement);
141 messageElement.textContent = message;
142 if (role) {
143 messageElement.setAttribute('role', role);
144 }
145 this._createMessagesContainer();
146 messagesContainer.appendChild(messageElement);
147 messageRegistry.set(getKey(message, role), { messageElement, referenceCount: 0 });
148 }
149 /** Deletes the message element from the global messages container. */
150 _deleteMessageElement(key) {
151 const registeredMessage = messageRegistry.get(key);
152 const messageElement = registeredMessage && registeredMessage.messageElement;
153 if (messagesContainer && messageElement) {
154 messagesContainer.removeChild(messageElement);
155 }
156 messageRegistry.delete(key);
157 }
158 /** Creates the global container for all aria-describedby messages. */
159 _createMessagesContainer() {
160 if (!messagesContainer) {
161 const preExistingContainer = this._document.getElementById(MESSAGES_CONTAINER_ID);
162 // When going from the server to the client, we may end up in a situation where there's
163 // already a container on the page, but we don't have a reference to it. Clear the
164 // old container so we don't get duplicates. Doing this, instead of emptying the previous
165 // container, should be slightly faster.
166 if (preExistingContainer && preExistingContainer.parentNode) {
167 preExistingContainer.parentNode.removeChild(preExistingContainer);
168 }
169 messagesContainer = this._document.createElement('div');
170 messagesContainer.id = MESSAGES_CONTAINER_ID;
171 // We add `visibility: hidden` in order to prevent text in this container from
172 // being searchable by the browser's Ctrl + F functionality.
173 // Screen-readers will still read the description for elements with aria-describedby even
174 // when the description element is not visible.
175 messagesContainer.style.visibility = 'hidden';
176 // Even though we use `visibility: hidden`, we still apply `cdk-visually-hidden` so that
177 // the description element doesn't impact page layout.
178 messagesContainer.classList.add('cdk-visually-hidden');
179 this._document.body.appendChild(messagesContainer);
180 }
181 }
182 /** Deletes the global messages container. */
183 _deleteMessagesContainer() {
184 if (messagesContainer && messagesContainer.parentNode) {
185 messagesContainer.parentNode.removeChild(messagesContainer);
186 messagesContainer = null;
187 }
188 }
189 /** Removes all cdk-describedby messages that are hosted through the element. */
190 _removeCdkDescribedByReferenceIds(element) {
191 // Remove all aria-describedby reference IDs that are prefixed by CDK_DESCRIBEDBY_ID_PREFIX
192 const originalReferenceIds = getAriaReferenceIds(element, 'aria-describedby')
193 .filter(id => id.indexOf(CDK_DESCRIBEDBY_ID_PREFIX) != 0);
194 element.setAttribute('aria-describedby', originalReferenceIds.join(' '));
195 }
196 /**
197 * Adds a message reference to the element using aria-describedby and increments the registered
198 * message's reference count.
199 */
200 _addMessageReference(element, key) {
201 const registeredMessage = messageRegistry.get(key);
202 // Add the aria-describedby reference and set the
203 // describedby_host attribute to mark the element.
204 addAriaReferencedId(element, 'aria-describedby', registeredMessage.messageElement.id);
205 element.setAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE, '');
206 registeredMessage.referenceCount++;
207 }
208 /**
209 * Removes a message reference from the element using aria-describedby
210 * and decrements the registered message's reference count.
211 */
212 _removeMessageReference(element, key) {
213 const registeredMessage = messageRegistry.get(key);
214 registeredMessage.referenceCount--;
215 removeAriaReferencedId(element, 'aria-describedby', registeredMessage.messageElement.id);
216 element.removeAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE);
217 }
218 /** Returns true if the element has been described by the provided message ID. */
219 _isElementDescribedByMessage(element, key) {
220 const referenceIds = getAriaReferenceIds(element, 'aria-describedby');
221 const registeredMessage = messageRegistry.get(key);
222 const messageId = registeredMessage && registeredMessage.messageElement.id;
223 return !!messageId && referenceIds.indexOf(messageId) != -1;
224 }
225 /** Determines whether a message can be described on a particular element. */
226 _canBeDescribed(element, message) {
227 if (!this._isElementNode(element)) {
228 return false;
229 }
230 if (message && typeof message === 'object') {
231 // We'd have to make some assumptions about the description element's text, if the consumer
232 // passed in an element. Assume that if an element is passed in, the consumer has verified
233 // that it can be used as a description.
234 return true;
235 }
236 const trimmedMessage = message == null ? '' : `${message}`.trim();
237 const ariaLabel = element.getAttribute('aria-label');
238 // We shouldn't set descriptions if they're exactly the same as the `aria-label` of the
239 // element, because screen readers will end up reading out the same text twice in a row.
240 return trimmedMessage ? (!ariaLabel || ariaLabel.trim() !== trimmedMessage) : false;
241 }
242 /** Checks whether a node is an Element node. */
243 _isElementNode(element) {
244 return element.nodeType === this._document.ELEMENT_NODE;
245 }
246}
247AriaDescriber.ɵprov = i0.ɵɵdefineInjectable({ factory: function AriaDescriber_Factory() { return new AriaDescriber(i0.ɵɵinject(i2.DOCUMENT)); }, token: AriaDescriber, providedIn: "root" });
248AriaDescriber.decorators = [
249 { type: Injectable, args: [{ providedIn: 'root' },] }
250];
251AriaDescriber.ctorParameters = () => [
252 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
253];
254/** Gets a key that can be used to look messages up in the registry. */
255function getKey(message, role) {
256 return typeof message === 'string' ? `${role || ''}/${message}` : message;
257}
258/** Assigns a unique ID to an element, if it doesn't have one already. */
259function setMessageId(element) {
260 if (!element.id) {
261 element.id = `${CDK_DESCRIBEDBY_ID_PREFIX}-${nextId++}`;
262 }
263}
264
265/**
266 * @license
267 * Copyright Google LLC All Rights Reserved.
268 *
269 * Use of this source code is governed by an MIT-style license that can be
270 * found in the LICENSE file at https://angular.io/license
271 */
272/**
273 * This class manages keyboard events for selectable lists. If you pass it a query list
274 * of items, it will set the active item correctly when arrow events occur.
275 */
276class ListKeyManager {
277 constructor(_items) {
278 this._items = _items;
279 this._activeItemIndex = -1;
280 this._activeItem = null;
281 this._wrap = false;
282 this._letterKeyStream = new Subject();
283 this._typeaheadSubscription = Subscription.EMPTY;
284 this._vertical = true;
285 this._allowedModifierKeys = [];
286 this._homeAndEnd = false;
287 /**
288 * Predicate function that can be used to check whether an item should be skipped
289 * by the key manager. By default, disabled items are skipped.
290 */
291 this._skipPredicateFn = (item) => item.disabled;
292 // Buffer for the letters that the user has pressed when the typeahead option is turned on.
293 this._pressedLetters = [];
294 /**
295 * Stream that emits any time the TAB key is pressed, so components can react
296 * when focus is shifted off of the list.
297 */
298 this.tabOut = new Subject();
299 /** Stream that emits whenever the active item of the list manager changes. */
300 this.change = new Subject();
301 // We allow for the items to be an array because, in some cases, the consumer may
302 // not have access to a QueryList of the items they want to manage (e.g. when the
303 // items aren't being collected via `ViewChildren` or `ContentChildren`).
304 if (_items instanceof QueryList) {
305 _items.changes.subscribe((newItems) => {
306 if (this._activeItem) {
307 const itemArray = newItems.toArray();
308 const newIndex = itemArray.indexOf(this._activeItem);
309 if (newIndex > -1 && newIndex !== this._activeItemIndex) {
310 this._activeItemIndex = newIndex;
311 }
312 }
313 });
314 }
315 }
316 /**
317 * Sets the predicate function that determines which items should be skipped by the
318 * list key manager.
319 * @param predicate Function that determines whether the given item should be skipped.
320 */
321 skipPredicate(predicate) {
322 this._skipPredicateFn = predicate;
323 return this;
324 }
325 /**
326 * Configures wrapping mode, which determines whether the active item will wrap to
327 * the other end of list when there are no more items in the given direction.
328 * @param shouldWrap Whether the list should wrap when reaching the end.
329 */
330 withWrap(shouldWrap = true) {
331 this._wrap = shouldWrap;
332 return this;
333 }
334 /**
335 * Configures whether the key manager should be able to move the selection vertically.
336 * @param enabled Whether vertical selection should be enabled.
337 */
338 withVerticalOrientation(enabled = true) {
339 this._vertical = enabled;
340 return this;
341 }
342 /**
343 * Configures the key manager to move the selection horizontally.
344 * Passing in `null` will disable horizontal movement.
345 * @param direction Direction in which the selection can be moved.
346 */
347 withHorizontalOrientation(direction) {
348 this._horizontal = direction;
349 return this;
350 }
351 /**
352 * Modifier keys which are allowed to be held down and whose default actions will be prevented
353 * as the user is pressing the arrow keys. Defaults to not allowing any modifier keys.
354 */
355 withAllowedModifierKeys(keys) {
356 this._allowedModifierKeys = keys;
357 return this;
358 }
359 /**
360 * Turns on typeahead mode which allows users to set the active item by typing.
361 * @param debounceInterval Time to wait after the last keystroke before setting the active item.
362 */
363 withTypeAhead(debounceInterval = 200) {
364 if ((typeof ngDevMode === 'undefined' || ngDevMode) && (this._items.length &&
365 this._items.some(item => typeof item.getLabel !== 'function'))) {
366 throw Error('ListKeyManager items in typeahead mode must implement the `getLabel` method.');
367 }
368 this._typeaheadSubscription.unsubscribe();
369 // Debounce the presses of non-navigational keys, collect the ones that correspond to letters
370 // and convert those letters back into a string. Afterwards find the first item that starts
371 // with that string and select it.
372 this._typeaheadSubscription = this._letterKeyStream.pipe(tap(letter => this._pressedLetters.push(letter)), debounceTime(debounceInterval), filter(() => this._pressedLetters.length > 0), map(() => this._pressedLetters.join(''))).subscribe(inputString => {
373 const items = this._getItemsArray();
374 // Start at 1 because we want to start searching at the item immediately
375 // following the current active item.
376 for (let i = 1; i < items.length + 1; i++) {
377 const index = (this._activeItemIndex + i) % items.length;
378 const item = items[index];
379 if (!this._skipPredicateFn(item) &&
380 item.getLabel().toUpperCase().trim().indexOf(inputString) === 0) {
381 this.setActiveItem(index);
382 break;
383 }
384 }
385 this._pressedLetters = [];
386 });
387 return this;
388 }
389 /**
390 * Configures the key manager to activate the first and last items
391 * respectively when the Home or End key is pressed.
392 * @param enabled Whether pressing the Home or End key activates the first/last item.
393 */
394 withHomeAndEnd(enabled = true) {
395 this._homeAndEnd = enabled;
396 return this;
397 }
398 setActiveItem(item) {
399 const previousActiveItem = this._activeItem;
400 this.updateActiveItem(item);
401 if (this._activeItem !== previousActiveItem) {
402 this.change.next(this._activeItemIndex);
403 }
404 }
405 /**
406 * Sets the active item depending on the key event passed in.
407 * @param event Keyboard event to be used for determining which element should be active.
408 */
409 onKeydown(event) {
410 const keyCode = event.keyCode;
411 const modifiers = ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'];
412 const isModifierAllowed = modifiers.every(modifier => {
413 return !event[modifier] || this._allowedModifierKeys.indexOf(modifier) > -1;
414 });
415 switch (keyCode) {
416 case TAB:
417 this.tabOut.next();
418 return;
419 case DOWN_ARROW:
420 if (this._vertical && isModifierAllowed) {
421 this.setNextItemActive();
422 break;
423 }
424 else {
425 return;
426 }
427 case UP_ARROW:
428 if (this._vertical && isModifierAllowed) {
429 this.setPreviousItemActive();
430 break;
431 }
432 else {
433 return;
434 }
435 case RIGHT_ARROW:
436 if (this._horizontal && isModifierAllowed) {
437 this._horizontal === 'rtl' ? this.setPreviousItemActive() : this.setNextItemActive();
438 break;
439 }
440 else {
441 return;
442 }
443 case LEFT_ARROW:
444 if (this._horizontal && isModifierAllowed) {
445 this._horizontal === 'rtl' ? this.setNextItemActive() : this.setPreviousItemActive();
446 break;
447 }
448 else {
449 return;
450 }
451 case HOME:
452 if (this._homeAndEnd && isModifierAllowed) {
453 this.setFirstItemActive();
454 break;
455 }
456 else {
457 return;
458 }
459 case END:
460 if (this._homeAndEnd && isModifierAllowed) {
461 this.setLastItemActive();
462 break;
463 }
464 else {
465 return;
466 }
467 default:
468 if (isModifierAllowed || hasModifierKey(event, 'shiftKey')) {
469 // Attempt to use the `event.key` which also maps it to the user's keyboard language,
470 // otherwise fall back to resolving alphanumeric characters via the keyCode.
471 if (event.key && event.key.length === 1) {
472 this._letterKeyStream.next(event.key.toLocaleUpperCase());
473 }
474 else if ((keyCode >= A && keyCode <= Z) || (keyCode >= ZERO && keyCode <= NINE)) {
475 this._letterKeyStream.next(String.fromCharCode(keyCode));
476 }
477 }
478 // Note that we return here, in order to avoid preventing
479 // the default action of non-navigational keys.
480 return;
481 }
482 this._pressedLetters = [];
483 event.preventDefault();
484 }
485 /** Index of the currently active item. */
486 get activeItemIndex() {
487 return this._activeItemIndex;
488 }
489 /** The active item. */
490 get activeItem() {
491 return this._activeItem;
492 }
493 /** Gets whether the user is currently typing into the manager using the typeahead feature. */
494 isTyping() {
495 return this._pressedLetters.length > 0;
496 }
497 /** Sets the active item to the first enabled item in the list. */
498 setFirstItemActive() {
499 this._setActiveItemByIndex(0, 1);
500 }
501 /** Sets the active item to the last enabled item in the list. */
502 setLastItemActive() {
503 this._setActiveItemByIndex(this._items.length - 1, -1);
504 }
505 /** Sets the active item to the next enabled item in the list. */
506 setNextItemActive() {
507 this._activeItemIndex < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1);
508 }
509 /** Sets the active item to a previous enabled item in the list. */
510 setPreviousItemActive() {
511 this._activeItemIndex < 0 && this._wrap ? this.setLastItemActive()
512 : this._setActiveItemByDelta(-1);
513 }
514 updateActiveItem(item) {
515 const itemArray = this._getItemsArray();
516 const index = typeof item === 'number' ? item : itemArray.indexOf(item);
517 const activeItem = itemArray[index];
518 // Explicitly check for `null` and `undefined` because other falsy values are valid.
519 this._activeItem = activeItem == null ? null : activeItem;
520 this._activeItemIndex = index;
521 }
522 /**
523 * This method sets the active item, given a list of items and the delta between the
524 * currently active item and the new active item. It will calculate differently
525 * depending on whether wrap mode is turned on.
526 */
527 _setActiveItemByDelta(delta) {
528 this._wrap ? this._setActiveInWrapMode(delta) : this._setActiveInDefaultMode(delta);
529 }
530 /**
531 * Sets the active item properly given "wrap" mode. In other words, it will continue to move
532 * down the list until it finds an item that is not disabled, and it will wrap if it
533 * encounters either end of the list.
534 */
535 _setActiveInWrapMode(delta) {
536 const items = this._getItemsArray();
537 for (let i = 1; i <= items.length; i++) {
538 const index = (this._activeItemIndex + (delta * i) + items.length) % items.length;
539 const item = items[index];
540 if (!this._skipPredicateFn(item)) {
541 this.setActiveItem(index);
542 return;
543 }
544 }
545 }
546 /**
547 * Sets the active item properly given the default mode. In other words, it will
548 * continue to move down the list until it finds an item that is not disabled. If
549 * it encounters either end of the list, it will stop and not wrap.
550 */
551 _setActiveInDefaultMode(delta) {
552 this._setActiveItemByIndex(this._activeItemIndex + delta, delta);
553 }
554 /**
555 * Sets the active item to the first enabled item starting at the index specified. If the
556 * item is disabled, it will move in the fallbackDelta direction until it either
557 * finds an enabled item or encounters the end of the list.
558 */
559 _setActiveItemByIndex(index, fallbackDelta) {
560 const items = this._getItemsArray();
561 if (!items[index]) {
562 return;
563 }
564 while (this._skipPredicateFn(items[index])) {
565 index += fallbackDelta;
566 if (!items[index]) {
567 return;
568 }
569 }
570 this.setActiveItem(index);
571 }
572 /** Returns the items as an array. */
573 _getItemsArray() {
574 return this._items instanceof QueryList ? this._items.toArray() : this._items;
575 }
576}
577
578/**
579 * @license
580 * Copyright Google LLC All Rights Reserved.
581 *
582 * Use of this source code is governed by an MIT-style license that can be
583 * found in the LICENSE file at https://angular.io/license
584 */
585class ActiveDescendantKeyManager extends ListKeyManager {
586 setActiveItem(index) {
587 if (this.activeItem) {
588 this.activeItem.setInactiveStyles();
589 }
590 super.setActiveItem(index);
591 if (this.activeItem) {
592 this.activeItem.setActiveStyles();
593 }
594 }
595}
596
597/**
598 * @license
599 * Copyright Google LLC All Rights Reserved.
600 *
601 * Use of this source code is governed by an MIT-style license that can be
602 * found in the LICENSE file at https://angular.io/license
603 */
604class FocusKeyManager extends ListKeyManager {
605 constructor() {
606 super(...arguments);
607 this._origin = 'program';
608 }
609 /**
610 * Sets the focus origin that will be passed in to the items for any subsequent `focus` calls.
611 * @param origin Focus origin to be used when focusing items.
612 */
613 setFocusOrigin(origin) {
614 this._origin = origin;
615 return this;
616 }
617 setActiveItem(item) {
618 super.setActiveItem(item);
619 if (this.activeItem) {
620 this.activeItem.focus(this._origin);
621 }
622 }
623}
624
625/**
626 * @license
627 * Copyright Google LLC All Rights Reserved.
628 *
629 * Use of this source code is governed by an MIT-style license that can be
630 * found in the LICENSE file at https://angular.io/license
631 */
632/**
633 * Configuration for the isFocusable method.
634 */
635class IsFocusableConfig {
636 constructor() {
637 /**
638 * Whether to count an element as focusable even if it is not currently visible.
639 */
640 this.ignoreVisibility = false;
641 }
642}
643// The InteractivityChecker leans heavily on the ally.js accessibility utilities.
644// Methods like `isTabbable` are only covering specific edge-cases for the browsers which are
645// supported.
646/**
647 * Utility for checking the interactivity of an element, such as whether is is focusable or
648 * tabbable.
649 */
650class InteractivityChecker {
651 constructor(_platform) {
652 this._platform = _platform;
653 }
654 /**
655 * Gets whether an element is disabled.
656 *
657 * @param element Element to be checked.
658 * @returns Whether the element is disabled.
659 */
660 isDisabled(element) {
661 // This does not capture some cases, such as a non-form control with a disabled attribute or
662 // a form control inside of a disabled form, but should capture the most common cases.
663 return element.hasAttribute('disabled');
664 }
665 /**
666 * Gets whether an element is visible for the purposes of interactivity.
667 *
668 * This will capture states like `display: none` and `visibility: hidden`, but not things like
669 * being clipped by an `overflow: hidden` parent or being outside the viewport.
670 *
671 * @returns Whether the element is visible.
672 */
673 isVisible(element) {
674 return hasGeometry(element) && getComputedStyle(element).visibility === 'visible';
675 }
676 /**
677 * Gets whether an element can be reached via Tab key.
678 * Assumes that the element has already been checked with isFocusable.
679 *
680 * @param element Element to be checked.
681 * @returns Whether the element is tabbable.
682 */
683 isTabbable(element) {
684 // Nothing is tabbable on the server 😎
685 if (!this._platform.isBrowser) {
686 return false;
687 }
688 const frameElement = getFrameElement(getWindow(element));
689 if (frameElement) {
690 // Frame elements inherit their tabindex onto all child elements.
691 if (getTabIndexValue(frameElement) === -1) {
692 return false;
693 }
694 // Browsers disable tabbing to an element inside of an invisible frame.
695 if (!this.isVisible(frameElement)) {
696 return false;
697 }
698 }
699 let nodeName = element.nodeName.toLowerCase();
700 let tabIndexValue = getTabIndexValue(element);
701 if (element.hasAttribute('contenteditable')) {
702 return tabIndexValue !== -1;
703 }
704 if (nodeName === 'iframe' || nodeName === 'object') {
705 // The frame or object's content may be tabbable depending on the content, but it's
706 // not possibly to reliably detect the content of the frames. We always consider such
707 // elements as non-tabbable.
708 return false;
709 }
710 // In iOS, the browser only considers some specific elements as tabbable.
711 if (this._platform.WEBKIT && this._platform.IOS && !isPotentiallyTabbableIOS(element)) {
712 return false;
713 }
714 if (nodeName === 'audio') {
715 // Audio elements without controls enabled are never tabbable, regardless
716 // of the tabindex attribute explicitly being set.
717 if (!element.hasAttribute('controls')) {
718 return false;
719 }
720 // Audio elements with controls are by default tabbable unless the
721 // tabindex attribute is set to `-1` explicitly.
722 return tabIndexValue !== -1;
723 }
724 if (nodeName === 'video') {
725 // For all video elements, if the tabindex attribute is set to `-1`, the video
726 // is not tabbable. Note: We cannot rely on the default `HTMLElement.tabIndex`
727 // property as that one is set to `-1` in Chrome, Edge and Safari v13.1. The
728 // tabindex attribute is the source of truth here.
729 if (tabIndexValue === -1) {
730 return false;
731 }
732 // If the tabindex is explicitly set, and not `-1` (as per check before), the
733 // video element is always tabbable (regardless of whether it has controls or not).
734 if (tabIndexValue !== null) {
735 return true;
736 }
737 // Otherwise (when no explicit tabindex is set), a video is only tabbable if it
738 // has controls enabled. Firefox is special as videos are always tabbable regardless
739 // of whether there are controls or not.
740 return this._platform.FIREFOX || element.hasAttribute('controls');
741 }
742 return element.tabIndex >= 0;
743 }
744 /**
745 * Gets whether an element can be focused by the user.
746 *
747 * @param element Element to be checked.
748 * @param config The config object with options to customize this method's behavior
749 * @returns Whether the element is focusable.
750 */
751 isFocusable(element, config) {
752 // Perform checks in order of left to most expensive.
753 // Again, naive approach that does not capture many edge cases and browser quirks.
754 return isPotentiallyFocusable(element) && !this.isDisabled(element) &&
755 ((config === null || config === void 0 ? void 0 : config.ignoreVisibility) || this.isVisible(element));
756 }
757}
758InteractivityChecker.ɵprov = i0.ɵɵdefineInjectable({ factory: function InteractivityChecker_Factory() { return new InteractivityChecker(i0.ɵɵinject(i1.Platform)); }, token: InteractivityChecker, providedIn: "root" });
759InteractivityChecker.decorators = [
760 { type: Injectable, args: [{ providedIn: 'root' },] }
761];
762InteractivityChecker.ctorParameters = () => [
763 { type: Platform }
764];
765/**
766 * Returns the frame element from a window object. Since browsers like MS Edge throw errors if
767 * the frameElement property is being accessed from a different host address, this property
768 * should be accessed carefully.
769 */
770function getFrameElement(window) {
771 try {
772 return window.frameElement;
773 }
774 catch (_a) {
775 return null;
776 }
777}
778/** Checks whether the specified element has any geometry / rectangles. */
779function hasGeometry(element) {
780 // Use logic from jQuery to check for an invisible element.
781 // See https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js#L12
782 return !!(element.offsetWidth || element.offsetHeight ||
783 (typeof element.getClientRects === 'function' && element.getClientRects().length));
784}
785/** Gets whether an element's */
786function isNativeFormElement(element) {
787 let nodeName = element.nodeName.toLowerCase();
788 return nodeName === 'input' ||
789 nodeName === 'select' ||
790 nodeName === 'button' ||
791 nodeName === 'textarea';
792}
793/** Gets whether an element is an `<input type="hidden">`. */
794function isHiddenInput(element) {
795 return isInputElement(element) && element.type == 'hidden';
796}
797/** Gets whether an element is an anchor that has an href attribute. */
798function isAnchorWithHref(element) {
799 return isAnchorElement(element) && element.hasAttribute('href');
800}
801/** Gets whether an element is an input element. */
802function isInputElement(element) {
803 return element.nodeName.toLowerCase() == 'input';
804}
805/** Gets whether an element is an anchor element. */
806function isAnchorElement(element) {
807 return element.nodeName.toLowerCase() == 'a';
808}
809/** Gets whether an element has a valid tabindex. */
810function hasValidTabIndex(element) {
811 if (!element.hasAttribute('tabindex') || element.tabIndex === undefined) {
812 return false;
813 }
814 let tabIndex = element.getAttribute('tabindex');
815 // IE11 parses tabindex="" as the value "-32768"
816 if (tabIndex == '-32768') {
817 return false;
818 }
819 return !!(tabIndex && !isNaN(parseInt(tabIndex, 10)));
820}
821/**
822 * Returns the parsed tabindex from the element attributes instead of returning the
823 * evaluated tabindex from the browsers defaults.
824 */
825function getTabIndexValue(element) {
826 if (!hasValidTabIndex(element)) {
827 return null;
828 }
829 // See browser issue in Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054
830 const tabIndex = parseInt(element.getAttribute('tabindex') || '', 10);
831 return isNaN(tabIndex) ? -1 : tabIndex;
832}
833/** Checks whether the specified element is potentially tabbable on iOS */
834function isPotentiallyTabbableIOS(element) {
835 let nodeName = element.nodeName.toLowerCase();
836 let inputType = nodeName === 'input' && element.type;
837 return inputType === 'text'
838 || inputType === 'password'
839 || nodeName === 'select'
840 || nodeName === 'textarea';
841}
842/**
843 * Gets whether an element is potentially focusable without taking current visible/disabled state
844 * into account.
845 */
846function isPotentiallyFocusable(element) {
847 // Inputs are potentially focusable *unless* they're type="hidden".
848 if (isHiddenInput(element)) {
849 return false;
850 }
851 return isNativeFormElement(element) ||
852 isAnchorWithHref(element) ||
853 element.hasAttribute('contenteditable') ||
854 hasValidTabIndex(element);
855}
856/** Gets the parent window of a DOM node with regards of being inside of an iframe. */
857function getWindow(node) {
858 // ownerDocument is null if `node` itself *is* a document.
859 return node.ownerDocument && node.ownerDocument.defaultView || window;
860}
861
862/**
863 * @license
864 * Copyright Google LLC All Rights Reserved.
865 *
866 * Use of this source code is governed by an MIT-style license that can be
867 * found in the LICENSE file at https://angular.io/license
868 */
869/**
870 * Class that allows for trapping focus within a DOM element.
871 *
872 * This class currently uses a relatively simple approach to focus trapping.
873 * It assumes that the tab order is the same as DOM order, which is not necessarily true.
874 * Things like `tabIndex > 0`, flex `order`, and shadow roots can cause the two to be misaligned.
875 *
876 * @deprecated Use `ConfigurableFocusTrap` instead.
877 * @breaking-change 11.0.0
878 */
879class FocusTrap {
880 constructor(_element, _checker, _ngZone, _document, deferAnchors = false) {
881 this._element = _element;
882 this._checker = _checker;
883 this._ngZone = _ngZone;
884 this._document = _document;
885 this._hasAttached = false;
886 // Event listeners for the anchors. Need to be regular functions so that we can unbind them later.
887 this.startAnchorListener = () => this.focusLastTabbableElement();
888 this.endAnchorListener = () => this.focusFirstTabbableElement();
889 this._enabled = true;
890 if (!deferAnchors) {
891 this.attachAnchors();
892 }
893 }
894 /** Whether the focus trap is active. */
895 get enabled() { return this._enabled; }
896 set enabled(value) {
897 this._enabled = value;
898 if (this._startAnchor && this._endAnchor) {
899 this._toggleAnchorTabIndex(value, this._startAnchor);
900 this._toggleAnchorTabIndex(value, this._endAnchor);
901 }
902 }
903 /** Destroys the focus trap by cleaning up the anchors. */
904 destroy() {
905 const startAnchor = this._startAnchor;
906 const endAnchor = this._endAnchor;
907 if (startAnchor) {
908 startAnchor.removeEventListener('focus', this.startAnchorListener);
909 if (startAnchor.parentNode) {
910 startAnchor.parentNode.removeChild(startAnchor);
911 }
912 }
913 if (endAnchor) {
914 endAnchor.removeEventListener('focus', this.endAnchorListener);
915 if (endAnchor.parentNode) {
916 endAnchor.parentNode.removeChild(endAnchor);
917 }
918 }
919 this._startAnchor = this._endAnchor = null;
920 this._hasAttached = false;
921 }
922 /**
923 * Inserts the anchors into the DOM. This is usually done automatically
924 * in the constructor, but can be deferred for cases like directives with `*ngIf`.
925 * @returns Whether the focus trap managed to attach successfully. This may not be the case
926 * if the target element isn't currently in the DOM.
927 */
928 attachAnchors() {
929 // If we're not on the browser, there can be no focus to trap.
930 if (this._hasAttached) {
931 return true;
932 }
933 this._ngZone.runOutsideAngular(() => {
934 if (!this._startAnchor) {
935 this._startAnchor = this._createAnchor();
936 this._startAnchor.addEventListener('focus', this.startAnchorListener);
937 }
938 if (!this._endAnchor) {
939 this._endAnchor = this._createAnchor();
940 this._endAnchor.addEventListener('focus', this.endAnchorListener);
941 }
942 });
943 if (this._element.parentNode) {
944 this._element.parentNode.insertBefore(this._startAnchor, this._element);
945 this._element.parentNode.insertBefore(this._endAnchor, this._element.nextSibling);
946 this._hasAttached = true;
947 }
948 return this._hasAttached;
949 }
950 /**
951 * Waits for the zone to stabilize, then either focuses the first element that the
952 * user specified, or the first tabbable element.
953 * @returns Returns a promise that resolves with a boolean, depending
954 * on whether focus was moved successfully.
955 */
956 focusInitialElementWhenReady(options) {
957 return new Promise(resolve => {
958 this._executeOnStable(() => resolve(this.focusInitialElement(options)));
959 });
960 }
961 /**
962 * Waits for the zone to stabilize, then focuses
963 * the first tabbable element within the focus trap region.
964 * @returns Returns a promise that resolves with a boolean, depending
965 * on whether focus was moved successfully.
966 */
967 focusFirstTabbableElementWhenReady(options) {
968 return new Promise(resolve => {
969 this._executeOnStable(() => resolve(this.focusFirstTabbableElement(options)));
970 });
971 }
972 /**
973 * Waits for the zone to stabilize, then focuses
974 * the last tabbable element within the focus trap region.
975 * @returns Returns a promise that resolves with a boolean, depending
976 * on whether focus was moved successfully.
977 */
978 focusLastTabbableElementWhenReady(options) {
979 return new Promise(resolve => {
980 this._executeOnStable(() => resolve(this.focusLastTabbableElement(options)));
981 });
982 }
983 /**
984 * Get the specified boundary element of the trapped region.
985 * @param bound The boundary to get (start or end of trapped region).
986 * @returns The boundary element.
987 */
988 _getRegionBoundary(bound) {
989 // Contains the deprecated version of selector, for temporary backwards comparability.
990 let markers = this._element.querySelectorAll(`[cdk-focus-region-${bound}], ` +
991 `[cdkFocusRegion${bound}], ` +
992 `[cdk-focus-${bound}]`);
993 for (let i = 0; i < markers.length; i++) {
994 // @breaking-change 8.0.0
995 if (markers[i].hasAttribute(`cdk-focus-${bound}`)) {
996 console.warn(`Found use of deprecated attribute 'cdk-focus-${bound}', ` +
997 `use 'cdkFocusRegion${bound}' instead. The deprecated ` +
998 `attribute will be removed in 8.0.0.`, markers[i]);
999 }
1000 else if (markers[i].hasAttribute(`cdk-focus-region-${bound}`)) {
1001 console.warn(`Found use of deprecated attribute 'cdk-focus-region-${bound}', ` +
1002 `use 'cdkFocusRegion${bound}' instead. The deprecated attribute ` +
1003 `will be removed in 8.0.0.`, markers[i]);
1004 }
1005 }
1006 if (bound == 'start') {
1007 return markers.length ? markers[0] : this._getFirstTabbableElement(this._element);
1008 }
1009 return markers.length ?
1010 markers[markers.length - 1] : this._getLastTabbableElement(this._element);
1011 }
1012 /**
1013 * Focuses the element that should be focused when the focus trap is initialized.
1014 * @returns Whether focus was moved successfully.
1015 */
1016 focusInitialElement(options) {
1017 // Contains the deprecated version of selector, for temporary backwards comparability.
1018 const redirectToElement = this._element.querySelector(`[cdk-focus-initial], ` +
1019 `[cdkFocusInitial]`);
1020 if (redirectToElement) {
1021 // @breaking-change 8.0.0
1022 if (redirectToElement.hasAttribute(`cdk-focus-initial`)) {
1023 console.warn(`Found use of deprecated attribute 'cdk-focus-initial', ` +
1024 `use 'cdkFocusInitial' instead. The deprecated attribute ` +
1025 `will be removed in 8.0.0`, redirectToElement);
1026 }
1027 // Warn the consumer if the element they've pointed to
1028 // isn't focusable, when not in production mode.
1029 if ((typeof ngDevMode === 'undefined' || ngDevMode) &&
1030 !this._checker.isFocusable(redirectToElement)) {
1031 console.warn(`Element matching '[cdkFocusInitial]' is not focusable.`, redirectToElement);
1032 }
1033 if (!this._checker.isFocusable(redirectToElement)) {
1034 const focusableChild = this._getFirstTabbableElement(redirectToElement);
1035 focusableChild === null || focusableChild === void 0 ? void 0 : focusableChild.focus(options);
1036 return !!focusableChild;
1037 }
1038 redirectToElement.focus(options);
1039 return true;
1040 }
1041 return this.focusFirstTabbableElement(options);
1042 }
1043 /**
1044 * Focuses the first tabbable element within the focus trap region.
1045 * @returns Whether focus was moved successfully.
1046 */
1047 focusFirstTabbableElement(options) {
1048 const redirectToElement = this._getRegionBoundary('start');
1049 if (redirectToElement) {
1050 redirectToElement.focus(options);
1051 }
1052 return !!redirectToElement;
1053 }
1054 /**
1055 * Focuses the last tabbable element within the focus trap region.
1056 * @returns Whether focus was moved successfully.
1057 */
1058 focusLastTabbableElement(options) {
1059 const redirectToElement = this._getRegionBoundary('end');
1060 if (redirectToElement) {
1061 redirectToElement.focus(options);
1062 }
1063 return !!redirectToElement;
1064 }
1065 /**
1066 * Checks whether the focus trap has successfully been attached.
1067 */
1068 hasAttached() {
1069 return this._hasAttached;
1070 }
1071 /** Get the first tabbable element from a DOM subtree (inclusive). */
1072 _getFirstTabbableElement(root) {
1073 if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
1074 return root;
1075 }
1076 // Iterate in DOM order. Note that IE doesn't have `children` for SVG so we fall
1077 // back to `childNodes` which includes text nodes, comments etc.
1078 let children = root.children || root.childNodes;
1079 for (let i = 0; i < children.length; i++) {
1080 let tabbableChild = children[i].nodeType === this._document.ELEMENT_NODE ?
1081 this._getFirstTabbableElement(children[i]) :
1082 null;
1083 if (tabbableChild) {
1084 return tabbableChild;
1085 }
1086 }
1087 return null;
1088 }
1089 /** Get the last tabbable element from a DOM subtree (inclusive). */
1090 _getLastTabbableElement(root) {
1091 if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
1092 return root;
1093 }
1094 // Iterate in reverse DOM order.
1095 let children = root.children || root.childNodes;
1096 for (let i = children.length - 1; i >= 0; i--) {
1097 let tabbableChild = children[i].nodeType === this._document.ELEMENT_NODE ?
1098 this._getLastTabbableElement(children[i]) :
1099 null;
1100 if (tabbableChild) {
1101 return tabbableChild;
1102 }
1103 }
1104 return null;
1105 }
1106 /** Creates an anchor element. */
1107 _createAnchor() {
1108 const anchor = this._document.createElement('div');
1109 this._toggleAnchorTabIndex(this._enabled, anchor);
1110 anchor.classList.add('cdk-visually-hidden');
1111 anchor.classList.add('cdk-focus-trap-anchor');
1112 anchor.setAttribute('aria-hidden', 'true');
1113 return anchor;
1114 }
1115 /**
1116 * Toggles the `tabindex` of an anchor, based on the enabled state of the focus trap.
1117 * @param isEnabled Whether the focus trap is enabled.
1118 * @param anchor Anchor on which to toggle the tabindex.
1119 */
1120 _toggleAnchorTabIndex(isEnabled, anchor) {
1121 // Remove the tabindex completely, rather than setting it to -1, because if the
1122 // element has a tabindex, the user might still hit it when navigating with the arrow keys.
1123 isEnabled ? anchor.setAttribute('tabindex', '0') : anchor.removeAttribute('tabindex');
1124 }
1125 /**
1126 * Toggles the`tabindex` of both anchors to either trap Tab focus or allow it to escape.
1127 * @param enabled: Whether the anchors should trap Tab.
1128 */
1129 toggleAnchors(enabled) {
1130 if (this._startAnchor && this._endAnchor) {
1131 this._toggleAnchorTabIndex(enabled, this._startAnchor);
1132 this._toggleAnchorTabIndex(enabled, this._endAnchor);
1133 }
1134 }
1135 /** Executes a function when the zone is stable. */
1136 _executeOnStable(fn) {
1137 if (this._ngZone.isStable) {
1138 fn();
1139 }
1140 else {
1141 this._ngZone.onStable.pipe(take(1)).subscribe(fn);
1142 }
1143 }
1144}
1145/**
1146 * Factory that allows easy instantiation of focus traps.
1147 * @deprecated Use `ConfigurableFocusTrapFactory` instead.
1148 * @breaking-change 11.0.0
1149 */
1150class FocusTrapFactory {
1151 constructor(_checker, _ngZone, _document) {
1152 this._checker = _checker;
1153 this._ngZone = _ngZone;
1154 this._document = _document;
1155 }
1156 /**
1157 * Creates a focus-trapped region around the given element.
1158 * @param element The element around which focus will be trapped.
1159 * @param deferCaptureElements Defers the creation of focus-capturing elements to be done
1160 * manually by the user.
1161 * @returns The created focus trap instance.
1162 */
1163 create(element, deferCaptureElements = false) {
1164 return new FocusTrap(element, this._checker, this._ngZone, this._document, deferCaptureElements);
1165 }
1166}
1167FocusTrapFactory.ɵ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" });
1168FocusTrapFactory.decorators = [
1169 { type: Injectable, args: [{ providedIn: 'root' },] }
1170];
1171FocusTrapFactory.ctorParameters = () => [
1172 { type: InteractivityChecker },
1173 { type: NgZone },
1174 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
1175];
1176/** Directive for trapping focus within a region. */
1177class CdkTrapFocus {
1178 constructor(_elementRef, _focusTrapFactory,
1179 /**
1180 * @deprecated No longer being used. To be removed.
1181 * @breaking-change 13.0.0
1182 */
1183 _document) {
1184 this._elementRef = _elementRef;
1185 this._focusTrapFactory = _focusTrapFactory;
1186 /** Previously focused element to restore focus to upon destroy when using autoCapture. */
1187 this._previouslyFocusedElement = null;
1188 this.focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, true);
1189 }
1190 /** Whether the focus trap is active. */
1191 get enabled() { return this.focusTrap.enabled; }
1192 set enabled(value) { this.focusTrap.enabled = coerceBooleanProperty(value); }
1193 /**
1194 * Whether the directive should automatically move focus into the trapped region upon
1195 * initialization and return focus to the previous activeElement upon destruction.
1196 */
1197 get autoCapture() { return this._autoCapture; }
1198 set autoCapture(value) { this._autoCapture = coerceBooleanProperty(value); }
1199 ngOnDestroy() {
1200 this.focusTrap.destroy();
1201 // If we stored a previously focused element when using autoCapture, return focus to that
1202 // element now that the trapped region is being destroyed.
1203 if (this._previouslyFocusedElement) {
1204 this._previouslyFocusedElement.focus();
1205 this._previouslyFocusedElement = null;
1206 }
1207 }
1208 ngAfterContentInit() {
1209 this.focusTrap.attachAnchors();
1210 if (this.autoCapture) {
1211 this._captureFocus();
1212 }
1213 }
1214 ngDoCheck() {
1215 if (!this.focusTrap.hasAttached()) {
1216 this.focusTrap.attachAnchors();
1217 }
1218 }
1219 ngOnChanges(changes) {
1220 const autoCaptureChange = changes['autoCapture'];
1221 if (autoCaptureChange && !autoCaptureChange.firstChange && this.autoCapture &&
1222 this.focusTrap.hasAttached()) {
1223 this._captureFocus();
1224 }
1225 }
1226 _captureFocus() {
1227 this._previouslyFocusedElement = _getFocusedElementPierceShadowDom();
1228 this.focusTrap.focusInitialElementWhenReady();
1229 }
1230}
1231CdkTrapFocus.decorators = [
1232 { type: Directive, args: [{
1233 selector: '[cdkTrapFocus]',
1234 exportAs: 'cdkTrapFocus',
1235 },] }
1236];
1237CdkTrapFocus.ctorParameters = () => [
1238 { type: ElementRef },
1239 { type: FocusTrapFactory },
1240 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
1241];
1242CdkTrapFocus.propDecorators = {
1243 enabled: [{ type: Input, args: ['cdkTrapFocus',] }],
1244 autoCapture: [{ type: Input, args: ['cdkTrapFocusAutoCapture',] }]
1245};
1246
1247/**
1248 * @license
1249 * Copyright Google LLC All Rights Reserved.
1250 *
1251 * Use of this source code is governed by an MIT-style license that can be
1252 * found in the LICENSE file at https://angular.io/license
1253 */
1254/**
1255 * Class that allows for trapping focus within a DOM element.
1256 *
1257 * This class uses a strategy pattern that determines how it traps focus.
1258 * See FocusTrapInertStrategy.
1259 */
1260class ConfigurableFocusTrap extends FocusTrap {
1261 constructor(_element, _checker, _ngZone, _document, _focusTrapManager, _inertStrategy, config) {
1262 super(_element, _checker, _ngZone, _document, config.defer);
1263 this._focusTrapManager = _focusTrapManager;
1264 this._inertStrategy = _inertStrategy;
1265 this._focusTrapManager.register(this);
1266 }
1267 /** Whether the FocusTrap is enabled. */
1268 get enabled() { return this._enabled; }
1269 set enabled(value) {
1270 this._enabled = value;
1271 if (this._enabled) {
1272 this._focusTrapManager.register(this);
1273 }
1274 else {
1275 this._focusTrapManager.deregister(this);
1276 }
1277 }
1278 /** Notifies the FocusTrapManager that this FocusTrap will be destroyed. */
1279 destroy() {
1280 this._focusTrapManager.deregister(this);
1281 super.destroy();
1282 }
1283 /** @docs-private Implemented as part of ManagedFocusTrap. */
1284 _enable() {
1285 this._inertStrategy.preventFocus(this);
1286 this.toggleAnchors(true);
1287 }
1288 /** @docs-private Implemented as part of ManagedFocusTrap. */
1289 _disable() {
1290 this._inertStrategy.allowFocus(this);
1291 this.toggleAnchors(false);
1292 }
1293}
1294
1295/**
1296 * @license
1297 * Copyright Google LLC All Rights Reserved.
1298 *
1299 * Use of this source code is governed by an MIT-style license that can be
1300 * found in the LICENSE file at https://angular.io/license
1301 */
1302
1303/**
1304 * @license
1305 * Copyright Google LLC All Rights Reserved.
1306 *
1307 * Use of this source code is governed by an MIT-style license that can be
1308 * found in the LICENSE file at https://angular.io/license
1309 */
1310/** The injection token used to specify the inert strategy. */
1311const FOCUS_TRAP_INERT_STRATEGY = new InjectionToken('FOCUS_TRAP_INERT_STRATEGY');
1312
1313/**
1314 * @license
1315 * Copyright Google LLC All Rights Reserved.
1316 *
1317 * Use of this source code is governed by an MIT-style license that can be
1318 * found in the LICENSE file at https://angular.io/license
1319 */
1320/** IE 11 compatible closest implementation that is able to start from non-Element Nodes. */
1321function closest(element, selector) {
1322 if (!(element instanceof Node)) {
1323 return null;
1324 }
1325 let curr = element;
1326 while (curr != null && !(curr instanceof Element)) {
1327 curr = curr.parentNode;
1328 }
1329 return curr && (hasNativeClosest ?
1330 curr.closest(selector) : polyfillClosest(curr, selector));
1331}
1332/** Polyfill for browsers without Element.closest. */
1333function polyfillClosest(element, selector) {
1334 let curr = element;
1335 while (curr != null && !(curr instanceof Element && matches(curr, selector))) {
1336 curr = curr.parentNode;
1337 }
1338 return (curr || null);
1339}
1340const hasNativeClosest = typeof Element != 'undefined' && !!Element.prototype.closest;
1341/** IE 11 compatible matches implementation. */
1342function matches(element, selector) {
1343 return element.matches ?
1344 element.matches(selector) :
1345 element['msMatchesSelector'](selector);
1346}
1347
1348/**
1349 * @license
1350 * Copyright Google LLC All Rights Reserved.
1351 *
1352 * Use of this source code is governed by an MIT-style license that can be
1353 * found in the LICENSE file at https://angular.io/license
1354 */
1355/**
1356 * Lightweight FocusTrapInertStrategy that adds a document focus event
1357 * listener to redirect focus back inside the FocusTrap.
1358 */
1359class EventListenerFocusTrapInertStrategy {
1360 constructor() {
1361 /** Focus event handler. */
1362 this._listener = null;
1363 }
1364 /** Adds a document event listener that keeps focus inside the FocusTrap. */
1365 preventFocus(focusTrap) {
1366 // Ensure there's only one listener per document
1367 if (this._listener) {
1368 focusTrap._document.removeEventListener('focus', this._listener, true);
1369 }
1370 this._listener = (e) => this._trapFocus(focusTrap, e);
1371 focusTrap._ngZone.runOutsideAngular(() => {
1372 focusTrap._document.addEventListener('focus', this._listener, true);
1373 });
1374 }
1375 /** Removes the event listener added in preventFocus. */
1376 allowFocus(focusTrap) {
1377 if (!this._listener) {
1378 return;
1379 }
1380 focusTrap._document.removeEventListener('focus', this._listener, true);
1381 this._listener = null;
1382 }
1383 /**
1384 * Refocuses the first element in the FocusTrap if the focus event target was outside
1385 * the FocusTrap.
1386 *
1387 * This is an event listener callback. The event listener is added in runOutsideAngular,
1388 * so all this code runs outside Angular as well.
1389 */
1390 _trapFocus(focusTrap, event) {
1391 const target = event.target;
1392 const focusTrapRoot = focusTrap._element;
1393 // Don't refocus if target was in an overlay, because the overlay might be associated
1394 // with an element inside the FocusTrap, ex. mat-select.
1395 if (!focusTrapRoot.contains(target) && closest(target, 'div.cdk-overlay-pane') === null) {
1396 // Some legacy FocusTrap usages have logic that focuses some element on the page
1397 // just before FocusTrap is destroyed. For backwards compatibility, wait
1398 // to be sure FocusTrap is still enabled before refocusing.
1399 setTimeout(() => {
1400 // Check whether focus wasn't put back into the focus trap while the timeout was pending.
1401 if (focusTrap.enabled && !focusTrapRoot.contains(focusTrap._document.activeElement)) {
1402 focusTrap.focusFirstTabbableElement();
1403 }
1404 });
1405 }
1406 }
1407}
1408
1409/**
1410 * @license
1411 * Copyright Google LLC All Rights Reserved.
1412 *
1413 * Use of this source code is governed by an MIT-style license that can be
1414 * found in the LICENSE file at https://angular.io/license
1415 */
1416/** Injectable that ensures only the most recently enabled FocusTrap is active. */
1417class FocusTrapManager {
1418 constructor() {
1419 // A stack of the FocusTraps on the page. Only the FocusTrap at the
1420 // top of the stack is active.
1421 this._focusTrapStack = [];
1422 }
1423 /**
1424 * Disables the FocusTrap at the top of the stack, and then pushes
1425 * the new FocusTrap onto the stack.
1426 */
1427 register(focusTrap) {
1428 // Dedupe focusTraps that register multiple times.
1429 this._focusTrapStack = this._focusTrapStack.filter((ft) => ft !== focusTrap);
1430 let stack = this._focusTrapStack;
1431 if (stack.length) {
1432 stack[stack.length - 1]._disable();
1433 }
1434 stack.push(focusTrap);
1435 focusTrap._enable();
1436 }
1437 /**
1438 * Removes the FocusTrap from the stack, and activates the
1439 * FocusTrap that is the new top of the stack.
1440 */
1441 deregister(focusTrap) {
1442 focusTrap._disable();
1443 const stack = this._focusTrapStack;
1444 const i = stack.indexOf(focusTrap);
1445 if (i !== -1) {
1446 stack.splice(i, 1);
1447 if (stack.length) {
1448 stack[stack.length - 1]._enable();
1449 }
1450 }
1451 }
1452}
1453FocusTrapManager.ɵprov = i0.ɵɵdefineInjectable({ factory: function FocusTrapManager_Factory() { return new FocusTrapManager(); }, token: FocusTrapManager, providedIn: "root" });
1454FocusTrapManager.decorators = [
1455 { type: Injectable, args: [{ providedIn: 'root' },] }
1456];
1457
1458/**
1459 * @license
1460 * Copyright Google LLC All Rights Reserved.
1461 *
1462 * Use of this source code is governed by an MIT-style license that can be
1463 * found in the LICENSE file at https://angular.io/license
1464 */
1465/** Factory that allows easy instantiation of configurable focus traps. */
1466class ConfigurableFocusTrapFactory {
1467 constructor(_checker, _ngZone, _focusTrapManager, _document, _inertStrategy) {
1468 this._checker = _checker;
1469 this._ngZone = _ngZone;
1470 this._focusTrapManager = _focusTrapManager;
1471 this._document = _document;
1472 // TODO split up the strategies into different modules, similar to DateAdapter.
1473 this._inertStrategy = _inertStrategy || new EventListenerFocusTrapInertStrategy();
1474 }
1475 create(element, config = { defer: false }) {
1476 let configObject;
1477 if (typeof config === 'boolean') {
1478 configObject = { defer: config };
1479 }
1480 else {
1481 configObject = config;
1482 }
1483 return new ConfigurableFocusTrap(element, this._checker, this._ngZone, this._document, this._focusTrapManager, this._inertStrategy, configObject);
1484 }
1485}
1486ConfigurableFocusTrapFactory.ɵ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" });
1487ConfigurableFocusTrapFactory.decorators = [
1488 { type: Injectable, args: [{ providedIn: 'root' },] }
1489];
1490ConfigurableFocusTrapFactory.ctorParameters = () => [
1491 { type: InteractivityChecker },
1492 { type: NgZone },
1493 { type: FocusTrapManager },
1494 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
1495 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [FOCUS_TRAP_INERT_STRATEGY,] }] }
1496];
1497
1498/**
1499 * @license
1500 * Copyright Google LLC All Rights Reserved.
1501 *
1502 * Use of this source code is governed by an MIT-style license that can be
1503 * found in the LICENSE file at https://angular.io/license
1504 */
1505/** Gets whether an event could be a faked `mousedown` event dispatched by a screen reader. */
1506function isFakeMousedownFromScreenReader(event) {
1507 // Some screen readers will dispatch a fake `mousedown` event when pressing enter or space on
1508 // a clickable element. We can distinguish these events when both `offsetX` and `offsetY` are
1509 // zero. Note that there's an edge case where the user could click the 0x0 spot of the screen
1510 // themselves, but that is unlikely to contain interaction elements. Historially we used to check
1511 // `event.buttons === 0`, however that no longer works on recent versions of NVDA.
1512 return event.offsetX === 0 && event.offsetY === 0;
1513}
1514/** Gets whether an event could be a faked `touchstart` event dispatched by a screen reader. */
1515function isFakeTouchstartFromScreenReader(event) {
1516 const touch = (event.touches && event.touches[0]) ||
1517 (event.changedTouches && event.changedTouches[0]);
1518 // A fake `touchstart` can be distinguished from a real one by looking at the `identifier`
1519 // which is typically >= 0 on a real device versus -1 from a screen reader. Just to be safe,
1520 // we can also look at `radiusX` and `radiusY`. This behavior was observed against a Windows 10
1521 // device with a touch screen running NVDA v2020.4 and Firefox 85 or Chrome 88.
1522 return !!touch && touch.identifier === -1 && (touch.radiusX == null || touch.radiusX === 1) &&
1523 (touch.radiusY == null || touch.radiusY === 1);
1524}
1525
1526/**
1527 * @license
1528 * Copyright Google LLC All Rights Reserved.
1529 *
1530 * Use of this source code is governed by an MIT-style license that can be
1531 * found in the LICENSE file at https://angular.io/license
1532 */
1533/**
1534 * Injectable options for the InputModalityDetector. These are shallowly merged with the default
1535 * options.
1536 */
1537const INPUT_MODALITY_DETECTOR_OPTIONS = new InjectionToken('cdk-input-modality-detector-options');
1538/**
1539 * Default options for the InputModalityDetector.
1540 *
1541 * Modifier keys are ignored by default (i.e. when pressed won't cause the service to detect
1542 * keyboard input modality) for two reasons:
1543 *
1544 * 1. Modifier keys are commonly used with mouse to perform actions such as 'right click' or 'open
1545 * in new tab', and are thus less representative of actual keyboard interaction.
1546 * 2. VoiceOver triggers some keyboard events when linearly navigating with Control + Option (but
1547 * confusingly not with Caps Lock). Thus, to have parity with other screen readers, we ignore
1548 * these keys so as to not update the input modality.
1549 *
1550 * Note that we do not by default ignore the right Meta key on Safari because it has the same key
1551 * code as the ContextMenu key on other browsers. When we switch to using event.key, we can
1552 * distinguish between the two.
1553 */
1554const INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS = {
1555 ignoreKeys: [ALT, CONTROL, MAC_META, META, SHIFT],
1556};
1557/**
1558 * The amount of time needed to pass after a touchstart event in order for a subsequent mousedown
1559 * event to be attributed as mouse and not touch.
1560 *
1561 * This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found
1562 * that a value of around 650ms seems appropriate.
1563 */
1564const TOUCH_BUFFER_MS = 650;
1565/**
1566 * Event listener options that enable capturing and also mark the listener as passive if the browser
1567 * supports it.
1568 */
1569const modalityEventListenerOptions = normalizePassiveListenerOptions({
1570 passive: true,
1571 capture: true,
1572});
1573/**
1574 * Service that detects the user's input modality.
1575 *
1576 * This service does not update the input modality when a user navigates with a screen reader
1577 * (e.g. linear navigation with VoiceOver, object navigation / browse mode with NVDA, virtual PC
1578 * cursor mode with JAWS). This is in part due to technical limitations (i.e. keyboard events do not
1579 * fire as expected in these modes) but is also arguably the correct behavior. Navigating with a
1580 * screen reader is akin to visually scanning a page, and should not be interpreted as actual user
1581 * input interaction.
1582 *
1583 * When a user is not navigating but *interacting* with a screen reader, this service attempts to
1584 * update the input modality to keyboard, but in general this service's behavior is largely
1585 * undefined.
1586 */
1587class InputModalityDetector {
1588 constructor(_platform, ngZone, document, options) {
1589 this._platform = _platform;
1590 /**
1591 * The most recently detected input modality event target. Is null if no input modality has been
1592 * detected or if the associated event target is null for some unknown reason.
1593 */
1594 this._mostRecentTarget = null;
1595 /** The underlying BehaviorSubject that emits whenever an input modality is detected. */
1596 this._modality = new BehaviorSubject(null);
1597 /**
1598 * The timestamp of the last touch input modality. Used to determine whether mousedown events
1599 * should be attributed to mouse or touch.
1600 */
1601 this._lastTouchMs = 0;
1602 /**
1603 * Handles keydown events. Must be an arrow function in order to preserve the context when it gets
1604 * bound.
1605 */
1606 this._onKeydown = (event) => {
1607 var _a, _b;
1608 // If this is one of the keys we should ignore, then ignore it and don't update the input
1609 // modality to keyboard.
1610 if ((_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.ignoreKeys) === null || _b === void 0 ? void 0 : _b.some(keyCode => keyCode === event.keyCode)) {
1611 return;
1612 }
1613 this._modality.next('keyboard');
1614 this._mostRecentTarget = _getEventTarget(event);
1615 };
1616 /**
1617 * Handles mousedown events. Must be an arrow function in order to preserve the context when it
1618 * gets bound.
1619 */
1620 this._onMousedown = (event) => {
1621 // Touches trigger both touch and mouse events, so we need to distinguish between mouse events
1622 // that were triggered via mouse vs touch. To do so, check if the mouse event occurs closely
1623 // after the previous touch event.
1624 if (Date.now() - this._lastTouchMs < TOUCH_BUFFER_MS) {
1625 return;
1626 }
1627 // Fake mousedown events are fired by some screen readers when controls are activated by the
1628 // screen reader. Attribute them to keyboard input modality.
1629 this._modality.next(isFakeMousedownFromScreenReader(event) ? 'keyboard' : 'mouse');
1630 this._mostRecentTarget = _getEventTarget(event);
1631 };
1632 /**
1633 * Handles touchstart events. Must be an arrow function in order to preserve the context when it
1634 * gets bound.
1635 */
1636 this._onTouchstart = (event) => {
1637 // Same scenario as mentioned in _onMousedown, but on touch screen devices, fake touchstart
1638 // events are fired. Again, attribute to keyboard input modality.
1639 if (isFakeTouchstartFromScreenReader(event)) {
1640 this._modality.next('keyboard');
1641 return;
1642 }
1643 // Store the timestamp of this touch event, as it's used to distinguish between mouse events
1644 // triggered via mouse vs touch.
1645 this._lastTouchMs = Date.now();
1646 this._modality.next('touch');
1647 this._mostRecentTarget = _getEventTarget(event);
1648 };
1649 this._options = Object.assign(Object.assign({}, INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS), options);
1650 // Skip the first emission as it's null.
1651 this.modalityDetected = this._modality.pipe(skip(1));
1652 this.modalityChanged = this.modalityDetected.pipe(distinctUntilChanged());
1653 // If we're not in a browser, this service should do nothing, as there's no relevant input
1654 // modality to detect.
1655 if (_platform.isBrowser) {
1656 ngZone.runOutsideAngular(() => {
1657 document.addEventListener('keydown', this._onKeydown, modalityEventListenerOptions);
1658 document.addEventListener('mousedown', this._onMousedown, modalityEventListenerOptions);
1659 document.addEventListener('touchstart', this._onTouchstart, modalityEventListenerOptions);
1660 });
1661 }
1662 }
1663 /** The most recently detected input modality. */
1664 get mostRecentModality() {
1665 return this._modality.value;
1666 }
1667 ngOnDestroy() {
1668 this._modality.complete();
1669 if (this._platform.isBrowser) {
1670 document.removeEventListener('keydown', this._onKeydown, modalityEventListenerOptions);
1671 document.removeEventListener('mousedown', this._onMousedown, modalityEventListenerOptions);
1672 document.removeEventListener('touchstart', this._onTouchstart, modalityEventListenerOptions);
1673 }
1674 }
1675}
1676InputModalityDetector.ɵprov = i0.ɵɵdefineInjectable({ factory: function InputModalityDetector_Factory() { return new InputModalityDetector(i0.ɵɵinject(i1.Platform), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i2.DOCUMENT), i0.ɵɵinject(INPUT_MODALITY_DETECTOR_OPTIONS, 8)); }, token: InputModalityDetector, providedIn: "root" });
1677InputModalityDetector.decorators = [
1678 { type: Injectable, args: [{ providedIn: 'root' },] }
1679];
1680InputModalityDetector.ctorParameters = () => [
1681 { type: Platform },
1682 { type: NgZone },
1683 { type: Document, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
1684 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [INPUT_MODALITY_DETECTOR_OPTIONS,] }] }
1685];
1686
1687/**
1688 * @license
1689 * Copyright Google LLC All Rights Reserved.
1690 *
1691 * Use of this source code is governed by an MIT-style license that can be
1692 * found in the LICENSE file at https://angular.io/license
1693 */
1694const LIVE_ANNOUNCER_ELEMENT_TOKEN = new InjectionToken('liveAnnouncerElement', {
1695 providedIn: 'root',
1696 factory: LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY,
1697});
1698/** @docs-private */
1699function LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY() {
1700 return null;
1701}
1702/** Injection token that can be used to configure the default options for the LiveAnnouncer. */
1703const LIVE_ANNOUNCER_DEFAULT_OPTIONS = new InjectionToken('LIVE_ANNOUNCER_DEFAULT_OPTIONS');
1704
1705/**
1706 * @license
1707 * Copyright Google LLC All Rights Reserved.
1708 *
1709 * Use of this source code is governed by an MIT-style license that can be
1710 * found in the LICENSE file at https://angular.io/license
1711 */
1712class LiveAnnouncer {
1713 constructor(elementToken, _ngZone, _document, _defaultOptions) {
1714 this._ngZone = _ngZone;
1715 this._defaultOptions = _defaultOptions;
1716 // We inject the live element and document as `any` because the constructor signature cannot
1717 // reference browser globals (HTMLElement, Document) on non-browser environments, since having
1718 // a class decorator causes TypeScript to preserve the constructor signature types.
1719 this._document = _document;
1720 this._liveElement = elementToken || this._createLiveElement();
1721 }
1722 announce(message, ...args) {
1723 const defaultOptions = this._defaultOptions;
1724 let politeness;
1725 let duration;
1726 if (args.length === 1 && typeof args[0] === 'number') {
1727 duration = args[0];
1728 }
1729 else {
1730 [politeness, duration] = args;
1731 }
1732 this.clear();
1733 clearTimeout(this._previousTimeout);
1734 if (!politeness) {
1735 politeness =
1736 (defaultOptions && defaultOptions.politeness) ? defaultOptions.politeness : 'polite';
1737 }
1738 if (duration == null && defaultOptions) {
1739 duration = defaultOptions.duration;
1740 }
1741 // TODO: ensure changing the politeness works on all environments we support.
1742 this._liveElement.setAttribute('aria-live', politeness);
1743 // This 100ms timeout is necessary for some browser + screen-reader combinations:
1744 // - Both JAWS and NVDA over IE11 will not announce anything without a non-zero timeout.
1745 // - With Chrome and IE11 with NVDA or JAWS, a repeated (identical) message won't be read a
1746 // second time without clearing and then using a non-zero delay.
1747 // (using JAWS 17 at time of this writing).
1748 return this._ngZone.runOutsideAngular(() => {
1749 return new Promise(resolve => {
1750 clearTimeout(this._previousTimeout);
1751 this._previousTimeout = setTimeout(() => {
1752 this._liveElement.textContent = message;
1753 resolve();
1754 if (typeof duration === 'number') {
1755 this._previousTimeout = setTimeout(() => this.clear(), duration);
1756 }
1757 }, 100);
1758 });
1759 });
1760 }
1761 /**
1762 * Clears the current text from the announcer element. Can be used to prevent
1763 * screen readers from reading the text out again while the user is going
1764 * through the page landmarks.
1765 */
1766 clear() {
1767 if (this._liveElement) {
1768 this._liveElement.textContent = '';
1769 }
1770 }
1771 ngOnDestroy() {
1772 clearTimeout(this._previousTimeout);
1773 if (this._liveElement && this._liveElement.parentNode) {
1774 this._liveElement.parentNode.removeChild(this._liveElement);
1775 this._liveElement = null;
1776 }
1777 }
1778 _createLiveElement() {
1779 const elementClass = 'cdk-live-announcer-element';
1780 const previousElements = this._document.getElementsByClassName(elementClass);
1781 const liveEl = this._document.createElement('div');
1782 // Remove any old containers. This can happen when coming in from a server-side-rendered page.
1783 for (let i = 0; i < previousElements.length; i++) {
1784 previousElements[i].parentNode.removeChild(previousElements[i]);
1785 }
1786 liveEl.classList.add(elementClass);
1787 liveEl.classList.add('cdk-visually-hidden');
1788 liveEl.setAttribute('aria-atomic', 'true');
1789 liveEl.setAttribute('aria-live', 'polite');
1790 this._document.body.appendChild(liveEl);
1791 return liveEl;
1792 }
1793}
1794LiveAnnouncer.ɵ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" });
1795LiveAnnouncer.decorators = [
1796 { type: Injectable, args: [{ providedIn: 'root' },] }
1797];
1798LiveAnnouncer.ctorParameters = () => [
1799 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [LIVE_ANNOUNCER_ELEMENT_TOKEN,] }] },
1800 { type: NgZone },
1801 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
1802 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [LIVE_ANNOUNCER_DEFAULT_OPTIONS,] }] }
1803];
1804/**
1805 * A directive that works similarly to aria-live, but uses the LiveAnnouncer to ensure compatibility
1806 * with a wider range of browsers and screen readers.
1807 */
1808class CdkAriaLive {
1809 constructor(_elementRef, _liveAnnouncer, _contentObserver, _ngZone) {
1810 this._elementRef = _elementRef;
1811 this._liveAnnouncer = _liveAnnouncer;
1812 this._contentObserver = _contentObserver;
1813 this._ngZone = _ngZone;
1814 this._politeness = 'polite';
1815 }
1816 /** The aria-live politeness level to use when announcing messages. */
1817 get politeness() { return this._politeness; }
1818 set politeness(value) {
1819 this._politeness = value === 'off' || value === 'assertive' ? value : 'polite';
1820 if (this._politeness === 'off') {
1821 if (this._subscription) {
1822 this._subscription.unsubscribe();
1823 this._subscription = null;
1824 }
1825 }
1826 else if (!this._subscription) {
1827 this._subscription = this._ngZone.runOutsideAngular(() => {
1828 return this._contentObserver
1829 .observe(this._elementRef)
1830 .subscribe(() => {
1831 // Note that we use textContent here, rather than innerText, in order to avoid a reflow.
1832 const elementText = this._elementRef.nativeElement.textContent;
1833 // The `MutationObserver` fires also for attribute
1834 // changes which we don't want to announce.
1835 if (elementText !== this._previousAnnouncedText) {
1836 this._liveAnnouncer.announce(elementText, this._politeness);
1837 this._previousAnnouncedText = elementText;
1838 }
1839 });
1840 });
1841 }
1842 }
1843 ngOnDestroy() {
1844 if (this._subscription) {
1845 this._subscription.unsubscribe();
1846 }
1847 }
1848}
1849CdkAriaLive.decorators = [
1850 { type: Directive, args: [{
1851 selector: '[cdkAriaLive]',
1852 exportAs: 'cdkAriaLive',
1853 },] }
1854];
1855CdkAriaLive.ctorParameters = () => [
1856 { type: ElementRef },
1857 { type: LiveAnnouncer },
1858 { type: ContentObserver },
1859 { type: NgZone }
1860];
1861CdkAriaLive.propDecorators = {
1862 politeness: [{ type: Input, args: ['cdkAriaLive',] }]
1863};
1864
1865/**
1866 * @license
1867 * Copyright Google LLC All Rights Reserved.
1868 *
1869 * Use of this source code is governed by an MIT-style license that can be
1870 * found in the LICENSE file at https://angular.io/license
1871 */
1872/** InjectionToken for FocusMonitorOptions. */
1873const FOCUS_MONITOR_DEFAULT_OPTIONS = new InjectionToken('cdk-focus-monitor-default-options');
1874/**
1875 * Event listener options that enable capturing and also
1876 * mark the listener as passive if the browser supports it.
1877 */
1878const captureEventListenerOptions = normalizePassiveListenerOptions({
1879 passive: true,
1880 capture: true
1881});
1882/** Monitors mouse and keyboard events to determine the cause of focus events. */
1883class FocusMonitor {
1884 constructor(_ngZone, _platform, _inputModalityDetector,
1885 /** @breaking-change 11.0.0 make document required */
1886 document, options) {
1887 this._ngZone = _ngZone;
1888 this._platform = _platform;
1889 this._inputModalityDetector = _inputModalityDetector;
1890 /** The focus origin that the next focus event is a result of. */
1891 this._origin = null;
1892 /** Whether the window has just been focused. */
1893 this._windowFocused = false;
1894 /**
1895 * Whether the origin was determined via a touch interaction. Necessary as properly attributing
1896 * focus events to touch interactions requires special logic.
1897 */
1898 this._originFromTouchInteraction = false;
1899 /** Map of elements being monitored to their info. */
1900 this._elementInfo = new Map();
1901 /** The number of elements currently being monitored. */
1902 this._monitoredElementCount = 0;
1903 /**
1904 * Keeps track of the root nodes to which we've currently bound a focus/blur handler,
1905 * as well as the number of monitored elements that they contain. We have to treat focus/blur
1906 * handlers differently from the rest of the events, because the browser won't emit events
1907 * to the document when focus moves inside of a shadow root.
1908 */
1909 this._rootNodeFocusListenerCount = new Map();
1910 /**
1911 * Event listener for `focus` events on the window.
1912 * Needs to be an arrow function in order to preserve the context when it gets bound.
1913 */
1914 this._windowFocusListener = () => {
1915 // Make a note of when the window regains focus, so we can
1916 // restore the origin info for the focused element.
1917 this._windowFocused = true;
1918 this._windowFocusTimeoutId = setTimeout(() => this._windowFocused = false);
1919 };
1920 /** Subject for stopping our InputModalityDetector subscription. */
1921 this._stopInputModalityDetector = new Subject();
1922 /**
1923 * Event listener for `focus` and 'blur' events on the document.
1924 * Needs to be an arrow function in order to preserve the context when it gets bound.
1925 */
1926 this._rootNodeFocusAndBlurListener = (event) => {
1927 const target = _getEventTarget(event);
1928 const handler = event.type === 'focus' ? this._onFocus : this._onBlur;
1929 // We need to walk up the ancestor chain in order to support `checkChildren`.
1930 for (let element = target; element; element = element.parentElement) {
1931 handler.call(this, event, element);
1932 }
1933 };
1934 this._document = document;
1935 this._detectionMode = (options === null || options === void 0 ? void 0 : options.detectionMode) || 0 /* IMMEDIATE */;
1936 }
1937 monitor(element, checkChildren = false) {
1938 const nativeElement = coerceElement(element);
1939 // Do nothing if we're not on the browser platform or the passed in node isn't an element.
1940 if (!this._platform.isBrowser || nativeElement.nodeType !== 1) {
1941 return of(null);
1942 }
1943 // If the element is inside the shadow DOM, we need to bind our focus/blur listeners to
1944 // the shadow root, rather than the `document`, because the browser won't emit focus events
1945 // to the `document`, if focus is moving within the same shadow root.
1946 const rootNode = _getShadowRoot(nativeElement) || this._getDocument();
1947 const cachedInfo = this._elementInfo.get(nativeElement);
1948 // Check if we're already monitoring this element.
1949 if (cachedInfo) {
1950 if (checkChildren) {
1951 // TODO(COMP-318): this can be problematic, because it'll turn all non-checkChildren
1952 // observers into ones that behave as if `checkChildren` was turned on. We need a more
1953 // robust solution.
1954 cachedInfo.checkChildren = true;
1955 }
1956 return cachedInfo.subject;
1957 }
1958 // Create monitored element info.
1959 const info = {
1960 checkChildren: checkChildren,
1961 subject: new Subject(),
1962 rootNode
1963 };
1964 this._elementInfo.set(nativeElement, info);
1965 this._registerGlobalListeners(info);
1966 return info.subject;
1967 }
1968 stopMonitoring(element) {
1969 const nativeElement = coerceElement(element);
1970 const elementInfo = this._elementInfo.get(nativeElement);
1971 if (elementInfo) {
1972 elementInfo.subject.complete();
1973 this._setClasses(nativeElement);
1974 this._elementInfo.delete(nativeElement);
1975 this._removeGlobalListeners(elementInfo);
1976 }
1977 }
1978 focusVia(element, origin, options) {
1979 const nativeElement = coerceElement(element);
1980 const focusedElement = this._getDocument().activeElement;
1981 // If the element is focused already, calling `focus` again won't trigger the event listener
1982 // which means that the focus classes won't be updated. If that's the case, update the classes
1983 // directly without waiting for an event.
1984 if (nativeElement === focusedElement) {
1985 this._getClosestElementsInfo(nativeElement)
1986 .forEach(([currentElement, info]) => this._originChanged(currentElement, origin, info));
1987 }
1988 else {
1989 this._setOrigin(origin);
1990 // `focus` isn't available on the server
1991 if (typeof nativeElement.focus === 'function') {
1992 nativeElement.focus(options);
1993 }
1994 }
1995 }
1996 ngOnDestroy() {
1997 this._elementInfo.forEach((_info, element) => this.stopMonitoring(element));
1998 }
1999 /** Access injected document if available or fallback to global document reference */
2000 _getDocument() {
2001 return this._document || document;
2002 }
2003 /** Use defaultView of injected document if available or fallback to global window reference */
2004 _getWindow() {
2005 const doc = this._getDocument();
2006 return doc.defaultView || window;
2007 }
2008 _toggleClass(element, className, shouldSet) {
2009 if (shouldSet) {
2010 element.classList.add(className);
2011 }
2012 else {
2013 element.classList.remove(className);
2014 }
2015 }
2016 _getFocusOrigin(focusEventTarget) {
2017 if (this._origin) {
2018 // If the origin was realized via a touch interaction, we need to perform additional checks
2019 // to determine whether the focus origin should be attributed to touch or program.
2020 if (this._originFromTouchInteraction) {
2021 return this._shouldBeAttributedToTouch(focusEventTarget) ? 'touch' : 'program';
2022 }
2023 else {
2024 return this._origin;
2025 }
2026 }
2027 // If the window has just regained focus, we can restore the most recent origin from before the
2028 // window blurred. Otherwise, we've reached the point where we can't identify the source of the
2029 // focus. This typically means one of two things happened:
2030 //
2031 // 1) The element was programmatically focused, or
2032 // 2) The element was focused via screen reader navigation (which generally doesn't fire
2033 // events).
2034 //
2035 // Because we can't distinguish between these two cases, we default to setting `program`.
2036 return (this._windowFocused && this._lastFocusOrigin) ? this._lastFocusOrigin : 'program';
2037 }
2038 /**
2039 * Returns whether the focus event should be attributed to touch. Recall that in IMMEDIATE mode, a
2040 * touch origin isn't immediately reset at the next tick (see _setOrigin). This means that when we
2041 * handle a focus event following a touch interaction, we need to determine whether (1) the focus
2042 * event was directly caused by the touch interaction or (2) the focus event was caused by a
2043 * subsequent programmatic focus call triggered by the touch interaction.
2044 * @param focusEventTarget The target of the focus event under examination.
2045 */
2046 _shouldBeAttributedToTouch(focusEventTarget) {
2047 // Please note that this check is not perfect. Consider the following edge case:
2048 //
2049 // <div #parent tabindex="0">
2050 // <div #child tabindex="0" (click)="#parent.focus()"></div>
2051 // </div>
2052 //
2053 // Suppose there is a FocusMonitor in IMMEDIATE mode attached to #parent. When the user touches
2054 // #child, #parent is programmatically focused. This code will attribute the focus to touch
2055 // instead of program. This is a relatively minor edge-case that can be worked around by using
2056 // focusVia(parent, 'program') to focus #parent.
2057 return (this._detectionMode === 1 /* EVENTUAL */) ||
2058 !!(focusEventTarget === null || focusEventTarget === void 0 ? void 0 : focusEventTarget.contains(this._inputModalityDetector._mostRecentTarget));
2059 }
2060 /**
2061 * Sets the focus classes on the element based on the given focus origin.
2062 * @param element The element to update the classes on.
2063 * @param origin The focus origin.
2064 */
2065 _setClasses(element, origin) {
2066 this._toggleClass(element, 'cdk-focused', !!origin);
2067 this._toggleClass(element, 'cdk-touch-focused', origin === 'touch');
2068 this._toggleClass(element, 'cdk-keyboard-focused', origin === 'keyboard');
2069 this._toggleClass(element, 'cdk-mouse-focused', origin === 'mouse');
2070 this._toggleClass(element, 'cdk-program-focused', origin === 'program');
2071 }
2072 /**
2073 * Updates the focus origin. If we're using immediate detection mode, we schedule an async
2074 * function to clear the origin at the end of a timeout. The duration of the timeout depends on
2075 * the origin being set.
2076 * @param origin The origin to set.
2077 * @param isFromInteraction Whether we are setting the origin from an interaction event.
2078 */
2079 _setOrigin(origin, isFromInteraction = false) {
2080 this._ngZone.runOutsideAngular(() => {
2081 this._origin = origin;
2082 this._originFromTouchInteraction = (origin === 'touch') && isFromInteraction;
2083 // If we're in IMMEDIATE mode, reset the origin at the next tick (or in `TOUCH_BUFFER_MS` ms
2084 // for a touch event). We reset the origin at the next tick because Firefox focuses one tick
2085 // after the interaction event. We wait `TOUCH_BUFFER_MS` ms before resetting the origin for
2086 // a touch event because when a touch event is fired, the associated focus event isn't yet in
2087 // the event queue. Before doing so, clear any pending timeouts.
2088 if (this._detectionMode === 0 /* IMMEDIATE */) {
2089 clearTimeout(this._originTimeoutId);
2090 const ms = this._originFromTouchInteraction ? TOUCH_BUFFER_MS : 1;
2091 this._originTimeoutId = setTimeout(() => this._origin = null, ms);
2092 }
2093 });
2094 }
2095 /**
2096 * Handles focus events on a registered element.
2097 * @param event The focus event.
2098 * @param element The monitored element.
2099 */
2100 _onFocus(event, element) {
2101 // NOTE(mmalerba): We currently set the classes based on the focus origin of the most recent
2102 // focus event affecting the monitored element. If we want to use the origin of the first event
2103 // instead we should check for the cdk-focused class here and return if the element already has
2104 // it. (This only matters for elements that have includesChildren = true).
2105 // If we are not counting child-element-focus as focused, make sure that the event target is the
2106 // monitored element itself.
2107 const elementInfo = this._elementInfo.get(element);
2108 const focusEventTarget = _getEventTarget(event);
2109 if (!elementInfo || (!elementInfo.checkChildren && element !== focusEventTarget)) {
2110 return;
2111 }
2112 this._originChanged(element, this._getFocusOrigin(focusEventTarget), elementInfo);
2113 }
2114 /**
2115 * Handles blur events on a registered element.
2116 * @param event The blur event.
2117 * @param element The monitored element.
2118 */
2119 _onBlur(event, element) {
2120 // If we are counting child-element-focus as focused, make sure that we aren't just blurring in
2121 // order to focus another child of the monitored element.
2122 const elementInfo = this._elementInfo.get(element);
2123 if (!elementInfo || (elementInfo.checkChildren && event.relatedTarget instanceof Node &&
2124 element.contains(event.relatedTarget))) {
2125 return;
2126 }
2127 this._setClasses(element);
2128 this._emitOrigin(elementInfo.subject, null);
2129 }
2130 _emitOrigin(subject, origin) {
2131 this._ngZone.run(() => subject.next(origin));
2132 }
2133 _registerGlobalListeners(elementInfo) {
2134 if (!this._platform.isBrowser) {
2135 return;
2136 }
2137 const rootNode = elementInfo.rootNode;
2138 const rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode) || 0;
2139 if (!rootNodeFocusListeners) {
2140 this._ngZone.runOutsideAngular(() => {
2141 rootNode.addEventListener('focus', this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
2142 rootNode.addEventListener('blur', this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
2143 });
2144 }
2145 this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners + 1);
2146 // Register global listeners when first element is monitored.
2147 if (++this._monitoredElementCount === 1) {
2148 // Note: we listen to events in the capture phase so we
2149 // can detect them even if the user stops propagation.
2150 this._ngZone.runOutsideAngular(() => {
2151 const window = this._getWindow();
2152 window.addEventListener('focus', this._windowFocusListener);
2153 });
2154 // The InputModalityDetector is also just a collection of global listeners.
2155 this._inputModalityDetector.modalityDetected
2156 .pipe(takeUntil(this._stopInputModalityDetector))
2157 .subscribe(modality => { this._setOrigin(modality, true /* isFromInteraction */); });
2158 }
2159 }
2160 _removeGlobalListeners(elementInfo) {
2161 const rootNode = elementInfo.rootNode;
2162 if (this._rootNodeFocusListenerCount.has(rootNode)) {
2163 const rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode);
2164 if (rootNodeFocusListeners > 1) {
2165 this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners - 1);
2166 }
2167 else {
2168 rootNode.removeEventListener('focus', this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
2169 rootNode.removeEventListener('blur', this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
2170 this._rootNodeFocusListenerCount.delete(rootNode);
2171 }
2172 }
2173 // Unregister global listeners when last element is unmonitored.
2174 if (!--this._monitoredElementCount) {
2175 const window = this._getWindow();
2176 window.removeEventListener('focus', this._windowFocusListener);
2177 // Equivalently, stop our InputModalityDetector subscription.
2178 this._stopInputModalityDetector.next();
2179 // Clear timeouts for all potentially pending timeouts to prevent the leaks.
2180 clearTimeout(this._windowFocusTimeoutId);
2181 clearTimeout(this._originTimeoutId);
2182 }
2183 }
2184 /** Updates all the state on an element once its focus origin has changed. */
2185 _originChanged(element, origin, elementInfo) {
2186 this._setClasses(element, origin);
2187 this._emitOrigin(elementInfo.subject, origin);
2188 this._lastFocusOrigin = origin;
2189 }
2190 /**
2191 * Collects the `MonitoredElementInfo` of a particular element and
2192 * all of its ancestors that have enabled `checkChildren`.
2193 * @param element Element from which to start the search.
2194 */
2195 _getClosestElementsInfo(element) {
2196 const results = [];
2197 this._elementInfo.forEach((info, currentElement) => {
2198 if (currentElement === element || (info.checkChildren && currentElement.contains(element))) {
2199 results.push([currentElement, info]);
2200 }
2201 });
2202 return results;
2203 }
2204}
2205FocusMonitor.ɵprov = i0.ɵɵdefineInjectable({ factory: function FocusMonitor_Factory() { return new FocusMonitor(i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i1.Platform), i0.ɵɵinject(InputModalityDetector), i0.ɵɵinject(i2.DOCUMENT, 8), i0.ɵɵinject(FOCUS_MONITOR_DEFAULT_OPTIONS, 8)); }, token: FocusMonitor, providedIn: "root" });
2206FocusMonitor.decorators = [
2207 { type: Injectable, args: [{ providedIn: 'root' },] }
2208];
2209FocusMonitor.ctorParameters = () => [
2210 { type: NgZone },
2211 { type: Platform },
2212 { type: InputModalityDetector },
2213 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] },
2214 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [FOCUS_MONITOR_DEFAULT_OPTIONS,] }] }
2215];
2216/**
2217 * Directive that determines how a particular element was focused (via keyboard, mouse, touch, or
2218 * programmatically) and adds corresponding classes to the element.
2219 *
2220 * There are two variants of this directive:
2221 * 1) cdkMonitorElementFocus: does not consider an element to be focused if one of its children is
2222 * focused.
2223 * 2) cdkMonitorSubtreeFocus: considers an element focused if it or any of its children are focused.
2224 */
2225class CdkMonitorFocus {
2226 constructor(_elementRef, _focusMonitor) {
2227 this._elementRef = _elementRef;
2228 this._focusMonitor = _focusMonitor;
2229 this.cdkFocusChange = new EventEmitter();
2230 }
2231 ngAfterViewInit() {
2232 const element = this._elementRef.nativeElement;
2233 this._monitorSubscription = this._focusMonitor.monitor(element, element.nodeType === 1 && element.hasAttribute('cdkMonitorSubtreeFocus'))
2234 .subscribe(origin => this.cdkFocusChange.emit(origin));
2235 }
2236 ngOnDestroy() {
2237 this._focusMonitor.stopMonitoring(this._elementRef);
2238 if (this._monitorSubscription) {
2239 this._monitorSubscription.unsubscribe();
2240 }
2241 }
2242}
2243CdkMonitorFocus.decorators = [
2244 { type: Directive, args: [{
2245 selector: '[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]',
2246 },] }
2247];
2248CdkMonitorFocus.ctorParameters = () => [
2249 { type: ElementRef },
2250 { type: FocusMonitor }
2251];
2252CdkMonitorFocus.propDecorators = {
2253 cdkFocusChange: [{ type: Output }]
2254};
2255
2256/**
2257 * @license
2258 * Copyright Google LLC All Rights Reserved.
2259 *
2260 * Use of this source code is governed by an MIT-style license that can be
2261 * found in the LICENSE file at https://angular.io/license
2262 */
2263/** CSS class applied to the document body when in black-on-white high-contrast mode. */
2264const BLACK_ON_WHITE_CSS_CLASS = 'cdk-high-contrast-black-on-white';
2265/** CSS class applied to the document body when in white-on-black high-contrast mode. */
2266const WHITE_ON_BLACK_CSS_CLASS = 'cdk-high-contrast-white-on-black';
2267/** CSS class applied to the document body when in high-contrast mode. */
2268const HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS = 'cdk-high-contrast-active';
2269/**
2270 * Service to determine whether the browser is currently in a high-contrast-mode environment.
2271 *
2272 * Microsoft Windows supports an accessibility feature called "High Contrast Mode". This mode
2273 * changes the appearance of all applications, including web applications, to dramatically increase
2274 * contrast.
2275 *
2276 * IE, Edge, and Firefox currently support this mode. Chrome does not support Windows High Contrast
2277 * Mode. This service does not detect high-contrast mode as added by the Chrome "High Contrast"
2278 * browser extension.
2279 */
2280class HighContrastModeDetector {
2281 constructor(_platform, document) {
2282 this._platform = _platform;
2283 this._document = document;
2284 }
2285 /** Gets the current high-contrast-mode for the page. */
2286 getHighContrastMode() {
2287 if (!this._platform.isBrowser) {
2288 return 0 /* NONE */;
2289 }
2290 // Create a test element with an arbitrary background-color that is neither black nor
2291 // white; high-contrast mode will coerce the color to either black or white. Also ensure that
2292 // appending the test element to the DOM does not affect layout by absolutely positioning it
2293 const testElement = this._document.createElement('div');
2294 testElement.style.backgroundColor = 'rgb(1,2,3)';
2295 testElement.style.position = 'absolute';
2296 this._document.body.appendChild(testElement);
2297 // Get the computed style for the background color, collapsing spaces to normalize between
2298 // browsers. Once we get this color, we no longer need the test element. Access the `window`
2299 // via the document so we can fake it in tests. Note that we have extra null checks, because
2300 // this logic will likely run during app bootstrap and throwing can break the entire app.
2301 const documentWindow = this._document.defaultView || window;
2302 const computedStyle = (documentWindow && documentWindow.getComputedStyle) ?
2303 documentWindow.getComputedStyle(testElement) : null;
2304 const computedColor = (computedStyle && computedStyle.backgroundColor || '').replace(/ /g, '');
2305 this._document.body.removeChild(testElement);
2306 switch (computedColor) {
2307 case 'rgb(0,0,0)': return 2 /* WHITE_ON_BLACK */;
2308 case 'rgb(255,255,255)': return 1 /* BLACK_ON_WHITE */;
2309 }
2310 return 0 /* NONE */;
2311 }
2312 /** Applies CSS classes indicating high-contrast mode to document body (browser-only). */
2313 _applyBodyHighContrastModeCssClasses() {
2314 if (!this._hasCheckedHighContrastMode && this._platform.isBrowser && this._document.body) {
2315 const bodyClasses = this._document.body.classList;
2316 // IE11 doesn't support `classList` operations with multiple arguments
2317 bodyClasses.remove(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS);
2318 bodyClasses.remove(BLACK_ON_WHITE_CSS_CLASS);
2319 bodyClasses.remove(WHITE_ON_BLACK_CSS_CLASS);
2320 this._hasCheckedHighContrastMode = true;
2321 const mode = this.getHighContrastMode();
2322 if (mode === 1 /* BLACK_ON_WHITE */) {
2323 bodyClasses.add(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS);
2324 bodyClasses.add(BLACK_ON_WHITE_CSS_CLASS);
2325 }
2326 else if (mode === 2 /* WHITE_ON_BLACK */) {
2327 bodyClasses.add(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS);
2328 bodyClasses.add(WHITE_ON_BLACK_CSS_CLASS);
2329 }
2330 }
2331 }
2332}
2333HighContrastModeDetector.ɵprov = i0.ɵɵdefineInjectable({ factory: function HighContrastModeDetector_Factory() { return new HighContrastModeDetector(i0.ɵɵinject(i1.Platform), i0.ɵɵinject(i2.DOCUMENT)); }, token: HighContrastModeDetector, providedIn: "root" });
2334HighContrastModeDetector.decorators = [
2335 { type: Injectable, args: [{ providedIn: 'root' },] }
2336];
2337HighContrastModeDetector.ctorParameters = () => [
2338 { type: Platform },
2339 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
2340];
2341
2342/**
2343 * @license
2344 * Copyright Google LLC All Rights Reserved.
2345 *
2346 * Use of this source code is governed by an MIT-style license that can be
2347 * found in the LICENSE file at https://angular.io/license
2348 */
2349class A11yModule {
2350 constructor(highContrastModeDetector) {
2351 highContrastModeDetector._applyBodyHighContrastModeCssClasses();
2352 }
2353}
2354A11yModule.decorators = [
2355 { type: NgModule, args: [{
2356 imports: [PlatformModule, ObserversModule],
2357 declarations: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus],
2358 exports: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus],
2359 },] }
2360];
2361A11yModule.ctorParameters = () => [
2362 { type: HighContrastModeDetector }
2363];
2364
2365/**
2366 * @license
2367 * Copyright Google LLC All Rights Reserved.
2368 *
2369 * Use of this source code is governed by an MIT-style license that can be
2370 * found in the LICENSE file at https://angular.io/license
2371 */
2372
2373/**
2374 * Generated bundle index. Do not edit.
2375 */
2376
2377export { A11yModule, ActiveDescendantKeyManager, AriaDescriber, CDK_DESCRIBEDBY_HOST_ATTRIBUTE, CDK_DESCRIBEDBY_ID_PREFIX, CdkAriaLive, CdkMonitorFocus, CdkTrapFocus, ConfigurableFocusTrap, ConfigurableFocusTrapFactory, EventListenerFocusTrapInertStrategy, FOCUS_MONITOR_DEFAULT_OPTIONS, FOCUS_TRAP_INERT_STRATEGY, FocusKeyManager, FocusMonitor, FocusTrap, FocusTrapFactory, HighContrastModeDetector, INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS, INPUT_MODALITY_DETECTOR_OPTIONS, InputModalityDetector, InteractivityChecker, IsFocusableConfig, LIVE_ANNOUNCER_DEFAULT_OPTIONS, LIVE_ANNOUNCER_ELEMENT_TOKEN, LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY, ListKeyManager, LiveAnnouncer, MESSAGES_CONTAINER_ID, isFakeMousedownFromScreenReader, isFakeTouchstartFromScreenReader, FocusTrapManager as ɵangular_material_src_cdk_a11y_a11y_a };
2378//# sourceMappingURL=a11y.js.map
Note: See TracBrowser for help on using the repository browser.