You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2014 lines
66 KiB
2014 lines
66 KiB
import {
|
|
ALT,
|
|
CONTROL,
|
|
MAC_META,
|
|
META,
|
|
SHIFT
|
|
} from "./chunk-UMSNQQWJ.js";
|
|
import {
|
|
BreakpointObserver
|
|
} from "./chunk-5OJ3CSUZ.js";
|
|
import {
|
|
Platform,
|
|
_getEventTarget,
|
|
_getFocusedElementPierceShadowDom,
|
|
_getShadowRoot,
|
|
normalizePassiveListenerOptions
|
|
} from "./chunk-UPQZZZIG.js";
|
|
import {
|
|
coerceElement,
|
|
coerceNumberProperty
|
|
} from "./chunk-7XTJNSRU.js";
|
|
import {
|
|
DOCUMENT
|
|
} from "./chunk-OSQBUYO6.js";
|
|
import {
|
|
APP_ID,
|
|
BehaviorSubject,
|
|
Directive,
|
|
ElementRef,
|
|
EventEmitter,
|
|
Inject,
|
|
Injectable,
|
|
InjectionToken,
|
|
Input,
|
|
NgModule,
|
|
NgZone,
|
|
Observable,
|
|
Optional,
|
|
Output,
|
|
Subject,
|
|
booleanAttribute,
|
|
debounceTime,
|
|
distinctUntilChanged,
|
|
inject,
|
|
of,
|
|
setClassMetadata,
|
|
skip,
|
|
take,
|
|
takeUntil,
|
|
ɵɵInputTransformsFeature,
|
|
ɵɵNgOnChangesFeature,
|
|
ɵɵdefineDirective,
|
|
ɵɵdefineInjectable,
|
|
ɵɵdefineInjector,
|
|
ɵɵdefineNgModule,
|
|
ɵɵdirectiveInject,
|
|
ɵɵinject
|
|
} from "./chunk-FGESKT7O.js";
|
|
import {
|
|
__spreadValues
|
|
} from "./chunk-HSNDBVJ3.js";
|
|
|
|
// node_modules/@angular/cdk/fesm2022/observers.mjs
|
|
var _MutationObserverFactory = class _MutationObserverFactory {
|
|
create(callback) {
|
|
return typeof MutationObserver === "undefined" ? null : new MutationObserver(callback);
|
|
}
|
|
};
|
|
_MutationObserverFactory.ɵfac = function MutationObserverFactory_Factory(t) {
|
|
return new (t || _MutationObserverFactory)();
|
|
};
|
|
_MutationObserverFactory.ɵprov = ɵɵdefineInjectable({
|
|
token: _MutationObserverFactory,
|
|
factory: _MutationObserverFactory.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var MutationObserverFactory = _MutationObserverFactory;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(MutationObserverFactory, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
var _ContentObserver = class _ContentObserver {
|
|
constructor(_mutationObserverFactory) {
|
|
this._mutationObserverFactory = _mutationObserverFactory;
|
|
this._observedElements = /* @__PURE__ */ new Map();
|
|
}
|
|
ngOnDestroy() {
|
|
this._observedElements.forEach((_, element) => this._cleanupObserver(element));
|
|
}
|
|
observe(elementOrRef) {
|
|
const element = coerceElement(elementOrRef);
|
|
return new Observable((observer) => {
|
|
const stream = this._observeElement(element);
|
|
const subscription = stream.subscribe(observer);
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
this._unobserveElement(element);
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* Observes the given element by using the existing MutationObserver if available, or creating a
|
|
* new one if not.
|
|
*/
|
|
_observeElement(element) {
|
|
if (!this._observedElements.has(element)) {
|
|
const stream = new Subject();
|
|
const observer = this._mutationObserverFactory.create((mutations) => stream.next(mutations));
|
|
if (observer) {
|
|
observer.observe(element, {
|
|
characterData: true,
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
}
|
|
this._observedElements.set(element, {
|
|
observer,
|
|
stream,
|
|
count: 1
|
|
});
|
|
} else {
|
|
this._observedElements.get(element).count++;
|
|
}
|
|
return this._observedElements.get(element).stream;
|
|
}
|
|
/**
|
|
* Un-observes the given element and cleans up the underlying MutationObserver if nobody else is
|
|
* observing this element.
|
|
*/
|
|
_unobserveElement(element) {
|
|
if (this._observedElements.has(element)) {
|
|
this._observedElements.get(element).count--;
|
|
if (!this._observedElements.get(element).count) {
|
|
this._cleanupObserver(element);
|
|
}
|
|
}
|
|
}
|
|
/** Clean up the underlying MutationObserver for the specified element. */
|
|
_cleanupObserver(element) {
|
|
if (this._observedElements.has(element)) {
|
|
const {
|
|
observer,
|
|
stream
|
|
} = this._observedElements.get(element);
|
|
if (observer) {
|
|
observer.disconnect();
|
|
}
|
|
stream.complete();
|
|
this._observedElements.delete(element);
|
|
}
|
|
}
|
|
};
|
|
_ContentObserver.ɵfac = function ContentObserver_Factory(t) {
|
|
return new (t || _ContentObserver)(ɵɵinject(MutationObserverFactory));
|
|
};
|
|
_ContentObserver.ɵprov = ɵɵdefineInjectable({
|
|
token: _ContentObserver,
|
|
factory: _ContentObserver.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var ContentObserver = _ContentObserver;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ContentObserver, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: MutationObserverFactory
|
|
}], null);
|
|
})();
|
|
var _CdkObserveContent = class _CdkObserveContent {
|
|
/**
|
|
* Whether observing content is disabled. This option can be used
|
|
* to disconnect the underlying MutationObserver until it is needed.
|
|
*/
|
|
get disabled() {
|
|
return this._disabled;
|
|
}
|
|
set disabled(value) {
|
|
this._disabled = value;
|
|
this._disabled ? this._unsubscribe() : this._subscribe();
|
|
}
|
|
/** Debounce interval for emitting the changes. */
|
|
get debounce() {
|
|
return this._debounce;
|
|
}
|
|
set debounce(value) {
|
|
this._debounce = coerceNumberProperty(value);
|
|
this._subscribe();
|
|
}
|
|
constructor(_contentObserver, _elementRef, _ngZone) {
|
|
this._contentObserver = _contentObserver;
|
|
this._elementRef = _elementRef;
|
|
this._ngZone = _ngZone;
|
|
this.event = new EventEmitter();
|
|
this._disabled = false;
|
|
this._currentSubscription = null;
|
|
}
|
|
ngAfterContentInit() {
|
|
if (!this._currentSubscription && !this.disabled) {
|
|
this._subscribe();
|
|
}
|
|
}
|
|
ngOnDestroy() {
|
|
this._unsubscribe();
|
|
}
|
|
_subscribe() {
|
|
this._unsubscribe();
|
|
const stream = this._contentObserver.observe(this._elementRef);
|
|
this._ngZone.runOutsideAngular(() => {
|
|
this._currentSubscription = (this.debounce ? stream.pipe(debounceTime(this.debounce)) : stream).subscribe(this.event);
|
|
});
|
|
}
|
|
_unsubscribe() {
|
|
this._currentSubscription?.unsubscribe();
|
|
}
|
|
};
|
|
_CdkObserveContent.ɵfac = function CdkObserveContent_Factory(t) {
|
|
return new (t || _CdkObserveContent)(ɵɵdirectiveInject(ContentObserver), ɵɵdirectiveInject(ElementRef), ɵɵdirectiveInject(NgZone));
|
|
};
|
|
_CdkObserveContent.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkObserveContent,
|
|
selectors: [["", "cdkObserveContent", ""]],
|
|
inputs: {
|
|
disabled: ["cdkObserveContentDisabled", "disabled", booleanAttribute],
|
|
debounce: "debounce"
|
|
},
|
|
outputs: {
|
|
event: "cdkObserveContent"
|
|
},
|
|
exportAs: ["cdkObserveContent"],
|
|
features: [ɵɵInputTransformsFeature]
|
|
});
|
|
var CdkObserveContent = _CdkObserveContent;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkObserveContent, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkObserveContent]",
|
|
exportAs: "cdkObserveContent"
|
|
}]
|
|
}], () => [{
|
|
type: ContentObserver
|
|
}, {
|
|
type: ElementRef
|
|
}, {
|
|
type: NgZone
|
|
}], {
|
|
event: [{
|
|
type: Output,
|
|
args: ["cdkObserveContent"]
|
|
}],
|
|
disabled: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkObserveContentDisabled",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
debounce: [{
|
|
type: Input
|
|
}]
|
|
});
|
|
})();
|
|
var _ObserversModule = class _ObserversModule {
|
|
};
|
|
_ObserversModule.ɵfac = function ObserversModule_Factory(t) {
|
|
return new (t || _ObserversModule)();
|
|
};
|
|
_ObserversModule.ɵmod = ɵɵdefineNgModule({
|
|
type: _ObserversModule,
|
|
declarations: [CdkObserveContent],
|
|
exports: [CdkObserveContent]
|
|
});
|
|
_ObserversModule.ɵinj = ɵɵdefineInjector({
|
|
providers: [MutationObserverFactory]
|
|
});
|
|
var ObserversModule = _ObserversModule;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ObserversModule, [{
|
|
type: NgModule,
|
|
args: [{
|
|
exports: [CdkObserveContent],
|
|
declarations: [CdkObserveContent],
|
|
providers: [MutationObserverFactory]
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/a11y.mjs
|
|
var ID_DELIMITER = " ";
|
|
function addAriaReferencedId(el, attr, id) {
|
|
const ids = getAriaReferenceIds(el, attr);
|
|
if (ids.some((existingId) => existingId.trim() == id.trim())) {
|
|
return;
|
|
}
|
|
ids.push(id.trim());
|
|
el.setAttribute(attr, ids.join(ID_DELIMITER));
|
|
}
|
|
function removeAriaReferencedId(el, attr, id) {
|
|
const ids = getAriaReferenceIds(el, attr);
|
|
const filteredIds = ids.filter((val) => val != id.trim());
|
|
if (filteredIds.length) {
|
|
el.setAttribute(attr, filteredIds.join(ID_DELIMITER));
|
|
} else {
|
|
el.removeAttribute(attr);
|
|
}
|
|
}
|
|
function getAriaReferenceIds(el, attr) {
|
|
return (el.getAttribute(attr) || "").match(/\S+/g) || [];
|
|
}
|
|
var CDK_DESCRIBEDBY_ID_PREFIX = "cdk-describedby-message";
|
|
var CDK_DESCRIBEDBY_HOST_ATTRIBUTE = "cdk-describedby-host";
|
|
var nextId = 0;
|
|
var _AriaDescriber = class _AriaDescriber {
|
|
constructor(_document, _platform) {
|
|
this._platform = _platform;
|
|
this._messageRegistry = /* @__PURE__ */ new Map();
|
|
this._messagesContainer = null;
|
|
this._id = `${nextId++}`;
|
|
this._document = _document;
|
|
this._id = inject(APP_ID) + "-" + nextId++;
|
|
}
|
|
describe(hostElement, message, role) {
|
|
if (!this._canBeDescribed(hostElement, message)) {
|
|
return;
|
|
}
|
|
const key = getKey(message, role);
|
|
if (typeof message !== "string") {
|
|
setMessageId(message, this._id);
|
|
this._messageRegistry.set(key, {
|
|
messageElement: message,
|
|
referenceCount: 0
|
|
});
|
|
} else if (!this._messageRegistry.has(key)) {
|
|
this._createMessageElement(message, role);
|
|
}
|
|
if (!this._isElementDescribedByMessage(hostElement, key)) {
|
|
this._addMessageReference(hostElement, key);
|
|
}
|
|
}
|
|
removeDescription(hostElement, message, role) {
|
|
if (!message || !this._isElementNode(hostElement)) {
|
|
return;
|
|
}
|
|
const key = getKey(message, role);
|
|
if (this._isElementDescribedByMessage(hostElement, key)) {
|
|
this._removeMessageReference(hostElement, key);
|
|
}
|
|
if (typeof message === "string") {
|
|
const registeredMessage = this._messageRegistry.get(key);
|
|
if (registeredMessage && registeredMessage.referenceCount === 0) {
|
|
this._deleteMessageElement(key);
|
|
}
|
|
}
|
|
if (this._messagesContainer?.childNodes.length === 0) {
|
|
this._messagesContainer.remove();
|
|
this._messagesContainer = null;
|
|
}
|
|
}
|
|
/** Unregisters all created message elements and removes the message container. */
|
|
ngOnDestroy() {
|
|
const describedElements = this._document.querySelectorAll(`[${CDK_DESCRIBEDBY_HOST_ATTRIBUTE}="${this._id}"]`);
|
|
for (let i = 0; i < describedElements.length; i++) {
|
|
this._removeCdkDescribedByReferenceIds(describedElements[i]);
|
|
describedElements[i].removeAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE);
|
|
}
|
|
this._messagesContainer?.remove();
|
|
this._messagesContainer = null;
|
|
this._messageRegistry.clear();
|
|
}
|
|
/**
|
|
* Creates a new element in the visually hidden message container element with the message
|
|
* as its content and adds it to the message registry.
|
|
*/
|
|
_createMessageElement(message, role) {
|
|
const messageElement = this._document.createElement("div");
|
|
setMessageId(messageElement, this._id);
|
|
messageElement.textContent = message;
|
|
if (role) {
|
|
messageElement.setAttribute("role", role);
|
|
}
|
|
this._createMessagesContainer();
|
|
this._messagesContainer.appendChild(messageElement);
|
|
this._messageRegistry.set(getKey(message, role), {
|
|
messageElement,
|
|
referenceCount: 0
|
|
});
|
|
}
|
|
/** Deletes the message element from the global messages container. */
|
|
_deleteMessageElement(key) {
|
|
this._messageRegistry.get(key)?.messageElement?.remove();
|
|
this._messageRegistry.delete(key);
|
|
}
|
|
/** Creates the global container for all aria-describedby messages. */
|
|
_createMessagesContainer() {
|
|
if (this._messagesContainer) {
|
|
return;
|
|
}
|
|
const containerClassName = "cdk-describedby-message-container";
|
|
const serverContainers = this._document.querySelectorAll(`.${containerClassName}[platform="server"]`);
|
|
for (let i = 0; i < serverContainers.length; i++) {
|
|
serverContainers[i].remove();
|
|
}
|
|
const messagesContainer = this._document.createElement("div");
|
|
messagesContainer.style.visibility = "hidden";
|
|
messagesContainer.classList.add(containerClassName);
|
|
messagesContainer.classList.add("cdk-visually-hidden");
|
|
if (this._platform && !this._platform.isBrowser) {
|
|
messagesContainer.setAttribute("platform", "server");
|
|
}
|
|
this._document.body.appendChild(messagesContainer);
|
|
this._messagesContainer = messagesContainer;
|
|
}
|
|
/** Removes all cdk-describedby messages that are hosted through the element. */
|
|
_removeCdkDescribedByReferenceIds(element) {
|
|
const originalReferenceIds = getAriaReferenceIds(element, "aria-describedby").filter((id) => id.indexOf(CDK_DESCRIBEDBY_ID_PREFIX) != 0);
|
|
element.setAttribute("aria-describedby", originalReferenceIds.join(" "));
|
|
}
|
|
/**
|
|
* Adds a message reference to the element using aria-describedby and increments the registered
|
|
* message's reference count.
|
|
*/
|
|
_addMessageReference(element, key) {
|
|
const registeredMessage = this._messageRegistry.get(key);
|
|
addAriaReferencedId(element, "aria-describedby", registeredMessage.messageElement.id);
|
|
element.setAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE, this._id);
|
|
registeredMessage.referenceCount++;
|
|
}
|
|
/**
|
|
* Removes a message reference from the element using aria-describedby
|
|
* and decrements the registered message's reference count.
|
|
*/
|
|
_removeMessageReference(element, key) {
|
|
const registeredMessage = this._messageRegistry.get(key);
|
|
registeredMessage.referenceCount--;
|
|
removeAriaReferencedId(element, "aria-describedby", registeredMessage.messageElement.id);
|
|
element.removeAttribute(CDK_DESCRIBEDBY_HOST_ATTRIBUTE);
|
|
}
|
|
/** Returns true if the element has been described by the provided message ID. */
|
|
_isElementDescribedByMessage(element, key) {
|
|
const referenceIds = getAriaReferenceIds(element, "aria-describedby");
|
|
const registeredMessage = this._messageRegistry.get(key);
|
|
const messageId = registeredMessage && registeredMessage.messageElement.id;
|
|
return !!messageId && referenceIds.indexOf(messageId) != -1;
|
|
}
|
|
/** Determines whether a message can be described on a particular element. */
|
|
_canBeDescribed(element, message) {
|
|
if (!this._isElementNode(element)) {
|
|
return false;
|
|
}
|
|
if (message && typeof message === "object") {
|
|
return true;
|
|
}
|
|
const trimmedMessage = message == null ? "" : `${message}`.trim();
|
|
const ariaLabel = element.getAttribute("aria-label");
|
|
return trimmedMessage ? !ariaLabel || ariaLabel.trim() !== trimmedMessage : false;
|
|
}
|
|
/** Checks whether a node is an Element node. */
|
|
_isElementNode(element) {
|
|
return element.nodeType === this._document.ELEMENT_NODE;
|
|
}
|
|
};
|
|
_AriaDescriber.ɵfac = function AriaDescriber_Factory(t) {
|
|
return new (t || _AriaDescriber)(ɵɵinject(DOCUMENT), ɵɵinject(Platform));
|
|
};
|
|
_AriaDescriber.ɵprov = ɵɵdefineInjectable({
|
|
token: _AriaDescriber,
|
|
factory: _AriaDescriber.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var AriaDescriber = _AriaDescriber;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(AriaDescriber, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: Platform
|
|
}], null);
|
|
})();
|
|
function getKey(message, role) {
|
|
return typeof message === "string" ? `${role || ""}/${message}` : message;
|
|
}
|
|
function setMessageId(element, serviceId) {
|
|
if (!element.id) {
|
|
element.id = `${CDK_DESCRIBEDBY_ID_PREFIX}-${serviceId}-${nextId++}`;
|
|
}
|
|
}
|
|
var _InteractivityChecker = class _InteractivityChecker {
|
|
constructor(_platform) {
|
|
this._platform = _platform;
|
|
}
|
|
/**
|
|
* Gets whether an element is disabled.
|
|
*
|
|
* @param element Element to be checked.
|
|
* @returns Whether the element is disabled.
|
|
*/
|
|
isDisabled(element) {
|
|
return element.hasAttribute("disabled");
|
|
}
|
|
/**
|
|
* Gets whether an element is visible for the purposes of interactivity.
|
|
*
|
|
* This will capture states like `display: none` and `visibility: hidden`, but not things like
|
|
* being clipped by an `overflow: hidden` parent or being outside the viewport.
|
|
*
|
|
* @returns Whether the element is visible.
|
|
*/
|
|
isVisible(element) {
|
|
return hasGeometry(element) && getComputedStyle(element).visibility === "visible";
|
|
}
|
|
/**
|
|
* Gets whether an element can be reached via Tab key.
|
|
* Assumes that the element has already been checked with isFocusable.
|
|
*
|
|
* @param element Element to be checked.
|
|
* @returns Whether the element is tabbable.
|
|
*/
|
|
isTabbable(element) {
|
|
if (!this._platform.isBrowser) {
|
|
return false;
|
|
}
|
|
const frameElement = getFrameElement(getWindow(element));
|
|
if (frameElement) {
|
|
if (getTabIndexValue(frameElement) === -1) {
|
|
return false;
|
|
}
|
|
if (!this.isVisible(frameElement)) {
|
|
return false;
|
|
}
|
|
}
|
|
let nodeName = element.nodeName.toLowerCase();
|
|
let tabIndexValue = getTabIndexValue(element);
|
|
if (element.hasAttribute("contenteditable")) {
|
|
return tabIndexValue !== -1;
|
|
}
|
|
if (nodeName === "iframe" || nodeName === "object") {
|
|
return false;
|
|
}
|
|
if (this._platform.WEBKIT && this._platform.IOS && !isPotentiallyTabbableIOS(element)) {
|
|
return false;
|
|
}
|
|
if (nodeName === "audio") {
|
|
if (!element.hasAttribute("controls")) {
|
|
return false;
|
|
}
|
|
return tabIndexValue !== -1;
|
|
}
|
|
if (nodeName === "video") {
|
|
if (tabIndexValue === -1) {
|
|
return false;
|
|
}
|
|
if (tabIndexValue !== null) {
|
|
return true;
|
|
}
|
|
return this._platform.FIREFOX || element.hasAttribute("controls");
|
|
}
|
|
return element.tabIndex >= 0;
|
|
}
|
|
/**
|
|
* Gets whether an element can be focused by the user.
|
|
*
|
|
* @param element Element to be checked.
|
|
* @param config The config object with options to customize this method's behavior
|
|
* @returns Whether the element is focusable.
|
|
*/
|
|
isFocusable(element, config) {
|
|
return isPotentiallyFocusable(element) && !this.isDisabled(element) && (config?.ignoreVisibility || this.isVisible(element));
|
|
}
|
|
};
|
|
_InteractivityChecker.ɵfac = function InteractivityChecker_Factory(t) {
|
|
return new (t || _InteractivityChecker)(ɵɵinject(Platform));
|
|
};
|
|
_InteractivityChecker.ɵprov = ɵɵdefineInjectable({
|
|
token: _InteractivityChecker,
|
|
factory: _InteractivityChecker.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var InteractivityChecker = _InteractivityChecker;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(InteractivityChecker, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: Platform
|
|
}], null);
|
|
})();
|
|
function getFrameElement(window2) {
|
|
try {
|
|
return window2.frameElement;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
function hasGeometry(element) {
|
|
return !!(element.offsetWidth || element.offsetHeight || typeof element.getClientRects === "function" && element.getClientRects().length);
|
|
}
|
|
function isNativeFormElement(element) {
|
|
let nodeName = element.nodeName.toLowerCase();
|
|
return nodeName === "input" || nodeName === "select" || nodeName === "button" || nodeName === "textarea";
|
|
}
|
|
function isHiddenInput(element) {
|
|
return isInputElement(element) && element.type == "hidden";
|
|
}
|
|
function isAnchorWithHref(element) {
|
|
return isAnchorElement(element) && element.hasAttribute("href");
|
|
}
|
|
function isInputElement(element) {
|
|
return element.nodeName.toLowerCase() == "input";
|
|
}
|
|
function isAnchorElement(element) {
|
|
return element.nodeName.toLowerCase() == "a";
|
|
}
|
|
function hasValidTabIndex(element) {
|
|
if (!element.hasAttribute("tabindex") || element.tabIndex === void 0) {
|
|
return false;
|
|
}
|
|
let tabIndex = element.getAttribute("tabindex");
|
|
return !!(tabIndex && !isNaN(parseInt(tabIndex, 10)));
|
|
}
|
|
function getTabIndexValue(element) {
|
|
if (!hasValidTabIndex(element)) {
|
|
return null;
|
|
}
|
|
const tabIndex = parseInt(element.getAttribute("tabindex") || "", 10);
|
|
return isNaN(tabIndex) ? -1 : tabIndex;
|
|
}
|
|
function isPotentiallyTabbableIOS(element) {
|
|
let nodeName = element.nodeName.toLowerCase();
|
|
let inputType = nodeName === "input" && element.type;
|
|
return inputType === "text" || inputType === "password" || nodeName === "select" || nodeName === "textarea";
|
|
}
|
|
function isPotentiallyFocusable(element) {
|
|
if (isHiddenInput(element)) {
|
|
return false;
|
|
}
|
|
return isNativeFormElement(element) || isAnchorWithHref(element) || element.hasAttribute("contenteditable") || hasValidTabIndex(element);
|
|
}
|
|
function getWindow(node) {
|
|
return node.ownerDocument && node.ownerDocument.defaultView || window;
|
|
}
|
|
var FocusTrap = class {
|
|
/** Whether the focus trap is active. */
|
|
get enabled() {
|
|
return this._enabled;
|
|
}
|
|
set enabled(value) {
|
|
this._enabled = value;
|
|
if (this._startAnchor && this._endAnchor) {
|
|
this._toggleAnchorTabIndex(value, this._startAnchor);
|
|
this._toggleAnchorTabIndex(value, this._endAnchor);
|
|
}
|
|
}
|
|
constructor(_element, _checker, _ngZone, _document, deferAnchors = false) {
|
|
this._element = _element;
|
|
this._checker = _checker;
|
|
this._ngZone = _ngZone;
|
|
this._document = _document;
|
|
this._hasAttached = false;
|
|
this.startAnchorListener = () => this.focusLastTabbableElement();
|
|
this.endAnchorListener = () => this.focusFirstTabbableElement();
|
|
this._enabled = true;
|
|
if (!deferAnchors) {
|
|
this.attachAnchors();
|
|
}
|
|
}
|
|
/** Destroys the focus trap by cleaning up the anchors. */
|
|
destroy() {
|
|
const startAnchor = this._startAnchor;
|
|
const endAnchor = this._endAnchor;
|
|
if (startAnchor) {
|
|
startAnchor.removeEventListener("focus", this.startAnchorListener);
|
|
startAnchor.remove();
|
|
}
|
|
if (endAnchor) {
|
|
endAnchor.removeEventListener("focus", this.endAnchorListener);
|
|
endAnchor.remove();
|
|
}
|
|
this._startAnchor = this._endAnchor = null;
|
|
this._hasAttached = false;
|
|
}
|
|
/**
|
|
* Inserts the anchors into the DOM. This is usually done automatically
|
|
* in the constructor, but can be deferred for cases like directives with `*ngIf`.
|
|
* @returns Whether the focus trap managed to attach successfully. This may not be the case
|
|
* if the target element isn't currently in the DOM.
|
|
*/
|
|
attachAnchors() {
|
|
if (this._hasAttached) {
|
|
return true;
|
|
}
|
|
this._ngZone.runOutsideAngular(() => {
|
|
if (!this._startAnchor) {
|
|
this._startAnchor = this._createAnchor();
|
|
this._startAnchor.addEventListener("focus", this.startAnchorListener);
|
|
}
|
|
if (!this._endAnchor) {
|
|
this._endAnchor = this._createAnchor();
|
|
this._endAnchor.addEventListener("focus", this.endAnchorListener);
|
|
}
|
|
});
|
|
if (this._element.parentNode) {
|
|
this._element.parentNode.insertBefore(this._startAnchor, this._element);
|
|
this._element.parentNode.insertBefore(this._endAnchor, this._element.nextSibling);
|
|
this._hasAttached = true;
|
|
}
|
|
return this._hasAttached;
|
|
}
|
|
/**
|
|
* Waits for the zone to stabilize, then focuses the first tabbable element.
|
|
* @returns Returns a promise that resolves with a boolean, depending
|
|
* on whether focus was moved successfully.
|
|
*/
|
|
focusInitialElementWhenReady(options) {
|
|
return new Promise((resolve) => {
|
|
this._executeOnStable(() => resolve(this.focusInitialElement(options)));
|
|
});
|
|
}
|
|
/**
|
|
* Waits for the zone to stabilize, then focuses
|
|
* the first tabbable element within the focus trap region.
|
|
* @returns Returns a promise that resolves with a boolean, depending
|
|
* on whether focus was moved successfully.
|
|
*/
|
|
focusFirstTabbableElementWhenReady(options) {
|
|
return new Promise((resolve) => {
|
|
this._executeOnStable(() => resolve(this.focusFirstTabbableElement(options)));
|
|
});
|
|
}
|
|
/**
|
|
* Waits for the zone to stabilize, then focuses
|
|
* the last tabbable element within the focus trap region.
|
|
* @returns Returns a promise that resolves with a boolean, depending
|
|
* on whether focus was moved successfully.
|
|
*/
|
|
focusLastTabbableElementWhenReady(options) {
|
|
return new Promise((resolve) => {
|
|
this._executeOnStable(() => resolve(this.focusLastTabbableElement(options)));
|
|
});
|
|
}
|
|
/**
|
|
* Get the specified boundary element of the trapped region.
|
|
* @param bound The boundary to get (start or end of trapped region).
|
|
* @returns The boundary element.
|
|
*/
|
|
_getRegionBoundary(bound) {
|
|
const markers = this._element.querySelectorAll(`[cdk-focus-region-${bound}], [cdkFocusRegion${bound}], [cdk-focus-${bound}]`);
|
|
if (typeof ngDevMode === "undefined" || ngDevMode) {
|
|
for (let i = 0; i < markers.length; i++) {
|
|
if (markers[i].hasAttribute(`cdk-focus-${bound}`)) {
|
|
console.warn(`Found use of deprecated attribute 'cdk-focus-${bound}', use 'cdkFocusRegion${bound}' instead. The deprecated attribute will be removed in 8.0.0.`, markers[i]);
|
|
} else if (markers[i].hasAttribute(`cdk-focus-region-${bound}`)) {
|
|
console.warn(`Found use of deprecated attribute 'cdk-focus-region-${bound}', use 'cdkFocusRegion${bound}' instead. The deprecated attribute will be removed in 8.0.0.`, markers[i]);
|
|
}
|
|
}
|
|
}
|
|
if (bound == "start") {
|
|
return markers.length ? markers[0] : this._getFirstTabbableElement(this._element);
|
|
}
|
|
return markers.length ? markers[markers.length - 1] : this._getLastTabbableElement(this._element);
|
|
}
|
|
/**
|
|
* Focuses the element that should be focused when the focus trap is initialized.
|
|
* @returns Whether focus was moved successfully.
|
|
*/
|
|
focusInitialElement(options) {
|
|
const redirectToElement = this._element.querySelector(`[cdk-focus-initial], [cdkFocusInitial]`);
|
|
if (redirectToElement) {
|
|
if ((typeof ngDevMode === "undefined" || ngDevMode) && redirectToElement.hasAttribute(`cdk-focus-initial`)) {
|
|
console.warn(`Found use of deprecated attribute 'cdk-focus-initial', use 'cdkFocusInitial' instead. The deprecated attribute will be removed in 8.0.0`, redirectToElement);
|
|
}
|
|
if ((typeof ngDevMode === "undefined" || ngDevMode) && !this._checker.isFocusable(redirectToElement)) {
|
|
console.warn(`Element matching '[cdkFocusInitial]' is not focusable.`, redirectToElement);
|
|
}
|
|
if (!this._checker.isFocusable(redirectToElement)) {
|
|
const focusableChild = this._getFirstTabbableElement(redirectToElement);
|
|
focusableChild?.focus(options);
|
|
return !!focusableChild;
|
|
}
|
|
redirectToElement.focus(options);
|
|
return true;
|
|
}
|
|
return this.focusFirstTabbableElement(options);
|
|
}
|
|
/**
|
|
* Focuses the first tabbable element within the focus trap region.
|
|
* @returns Whether focus was moved successfully.
|
|
*/
|
|
focusFirstTabbableElement(options) {
|
|
const redirectToElement = this._getRegionBoundary("start");
|
|
if (redirectToElement) {
|
|
redirectToElement.focus(options);
|
|
}
|
|
return !!redirectToElement;
|
|
}
|
|
/**
|
|
* Focuses the last tabbable element within the focus trap region.
|
|
* @returns Whether focus was moved successfully.
|
|
*/
|
|
focusLastTabbableElement(options) {
|
|
const redirectToElement = this._getRegionBoundary("end");
|
|
if (redirectToElement) {
|
|
redirectToElement.focus(options);
|
|
}
|
|
return !!redirectToElement;
|
|
}
|
|
/**
|
|
* Checks whether the focus trap has successfully been attached.
|
|
*/
|
|
hasAttached() {
|
|
return this._hasAttached;
|
|
}
|
|
/** Get the first tabbable element from a DOM subtree (inclusive). */
|
|
_getFirstTabbableElement(root) {
|
|
if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
|
|
return root;
|
|
}
|
|
const children = root.children;
|
|
for (let i = 0; i < children.length; i++) {
|
|
const tabbableChild = children[i].nodeType === this._document.ELEMENT_NODE ? this._getFirstTabbableElement(children[i]) : null;
|
|
if (tabbableChild) {
|
|
return tabbableChild;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
/** Get the last tabbable element from a DOM subtree (inclusive). */
|
|
_getLastTabbableElement(root) {
|
|
if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
|
|
return root;
|
|
}
|
|
const children = root.children;
|
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
const tabbableChild = children[i].nodeType === this._document.ELEMENT_NODE ? this._getLastTabbableElement(children[i]) : null;
|
|
if (tabbableChild) {
|
|
return tabbableChild;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
/** Creates an anchor element. */
|
|
_createAnchor() {
|
|
const anchor = this._document.createElement("div");
|
|
this._toggleAnchorTabIndex(this._enabled, anchor);
|
|
anchor.classList.add("cdk-visually-hidden");
|
|
anchor.classList.add("cdk-focus-trap-anchor");
|
|
anchor.setAttribute("aria-hidden", "true");
|
|
return anchor;
|
|
}
|
|
/**
|
|
* Toggles the `tabindex` of an anchor, based on the enabled state of the focus trap.
|
|
* @param isEnabled Whether the focus trap is enabled.
|
|
* @param anchor Anchor on which to toggle the tabindex.
|
|
*/
|
|
_toggleAnchorTabIndex(isEnabled, anchor) {
|
|
isEnabled ? anchor.setAttribute("tabindex", "0") : anchor.removeAttribute("tabindex");
|
|
}
|
|
/**
|
|
* Toggles the`tabindex` of both anchors to either trap Tab focus or allow it to escape.
|
|
* @param enabled: Whether the anchors should trap Tab.
|
|
*/
|
|
toggleAnchors(enabled) {
|
|
if (this._startAnchor && this._endAnchor) {
|
|
this._toggleAnchorTabIndex(enabled, this._startAnchor);
|
|
this._toggleAnchorTabIndex(enabled, this._endAnchor);
|
|
}
|
|
}
|
|
/** Executes a function when the zone is stable. */
|
|
_executeOnStable(fn) {
|
|
if (this._ngZone.isStable) {
|
|
fn();
|
|
} else {
|
|
this._ngZone.onStable.pipe(take(1)).subscribe(fn);
|
|
}
|
|
}
|
|
};
|
|
var _FocusTrapFactory = class _FocusTrapFactory {
|
|
constructor(_checker, _ngZone, _document) {
|
|
this._checker = _checker;
|
|
this._ngZone = _ngZone;
|
|
this._document = _document;
|
|
}
|
|
/**
|
|
* Creates a focus-trapped region around the given element.
|
|
* @param element The element around which focus will be trapped.
|
|
* @param deferCaptureElements Defers the creation of focus-capturing elements to be done
|
|
* manually by the user.
|
|
* @returns The created focus trap instance.
|
|
*/
|
|
create(element, deferCaptureElements = false) {
|
|
return new FocusTrap(element, this._checker, this._ngZone, this._document, deferCaptureElements);
|
|
}
|
|
};
|
|
_FocusTrapFactory.ɵfac = function FocusTrapFactory_Factory(t) {
|
|
return new (t || _FocusTrapFactory)(ɵɵinject(InteractivityChecker), ɵɵinject(NgZone), ɵɵinject(DOCUMENT));
|
|
};
|
|
_FocusTrapFactory.ɵprov = ɵɵdefineInjectable({
|
|
token: _FocusTrapFactory,
|
|
factory: _FocusTrapFactory.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var FocusTrapFactory = _FocusTrapFactory;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(FocusTrapFactory, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: InteractivityChecker
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _CdkTrapFocus = class _CdkTrapFocus {
|
|
/** Whether the focus trap is active. */
|
|
get enabled() {
|
|
return this.focusTrap.enabled;
|
|
}
|
|
set enabled(value) {
|
|
this.focusTrap.enabled = value;
|
|
}
|
|
constructor(_elementRef, _focusTrapFactory, _document) {
|
|
this._elementRef = _elementRef;
|
|
this._focusTrapFactory = _focusTrapFactory;
|
|
this._previouslyFocusedElement = null;
|
|
this.focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, true);
|
|
}
|
|
ngOnDestroy() {
|
|
this.focusTrap.destroy();
|
|
if (this._previouslyFocusedElement) {
|
|
this._previouslyFocusedElement.focus();
|
|
this._previouslyFocusedElement = null;
|
|
}
|
|
}
|
|
ngAfterContentInit() {
|
|
this.focusTrap.attachAnchors();
|
|
if (this.autoCapture) {
|
|
this._captureFocus();
|
|
}
|
|
}
|
|
ngDoCheck() {
|
|
if (!this.focusTrap.hasAttached()) {
|
|
this.focusTrap.attachAnchors();
|
|
}
|
|
}
|
|
ngOnChanges(changes) {
|
|
const autoCaptureChange = changes["autoCapture"];
|
|
if (autoCaptureChange && !autoCaptureChange.firstChange && this.autoCapture && this.focusTrap.hasAttached()) {
|
|
this._captureFocus();
|
|
}
|
|
}
|
|
_captureFocus() {
|
|
this._previouslyFocusedElement = _getFocusedElementPierceShadowDom();
|
|
this.focusTrap.focusInitialElementWhenReady();
|
|
}
|
|
};
|
|
_CdkTrapFocus.ɵfac = function CdkTrapFocus_Factory(t) {
|
|
return new (t || _CdkTrapFocus)(ɵɵdirectiveInject(ElementRef), ɵɵdirectiveInject(FocusTrapFactory), ɵɵdirectiveInject(DOCUMENT));
|
|
};
|
|
_CdkTrapFocus.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkTrapFocus,
|
|
selectors: [["", "cdkTrapFocus", ""]],
|
|
inputs: {
|
|
enabled: ["cdkTrapFocus", "enabled", booleanAttribute],
|
|
autoCapture: ["cdkTrapFocusAutoCapture", "autoCapture", booleanAttribute]
|
|
},
|
|
exportAs: ["cdkTrapFocus"],
|
|
features: [ɵɵInputTransformsFeature, ɵɵNgOnChangesFeature]
|
|
});
|
|
var CdkTrapFocus = _CdkTrapFocus;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkTrapFocus, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkTrapFocus]",
|
|
exportAs: "cdkTrapFocus"
|
|
}]
|
|
}], () => [{
|
|
type: ElementRef
|
|
}, {
|
|
type: FocusTrapFactory
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}], {
|
|
enabled: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkTrapFocus",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
autoCapture: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkTrapFocusAutoCapture",
|
|
transform: booleanAttribute
|
|
}]
|
|
}]
|
|
});
|
|
})();
|
|
var ConfigurableFocusTrap = class extends FocusTrap {
|
|
/** Whether the FocusTrap is enabled. */
|
|
get enabled() {
|
|
return this._enabled;
|
|
}
|
|
set enabled(value) {
|
|
this._enabled = value;
|
|
if (this._enabled) {
|
|
this._focusTrapManager.register(this);
|
|
} else {
|
|
this._focusTrapManager.deregister(this);
|
|
}
|
|
}
|
|
constructor(_element, _checker, _ngZone, _document, _focusTrapManager, _inertStrategy, config) {
|
|
super(_element, _checker, _ngZone, _document, config.defer);
|
|
this._focusTrapManager = _focusTrapManager;
|
|
this._inertStrategy = _inertStrategy;
|
|
this._focusTrapManager.register(this);
|
|
}
|
|
/** Notifies the FocusTrapManager that this FocusTrap will be destroyed. */
|
|
destroy() {
|
|
this._focusTrapManager.deregister(this);
|
|
super.destroy();
|
|
}
|
|
/** @docs-private Implemented as part of ManagedFocusTrap. */
|
|
_enable() {
|
|
this._inertStrategy.preventFocus(this);
|
|
this.toggleAnchors(true);
|
|
}
|
|
/** @docs-private Implemented as part of ManagedFocusTrap. */
|
|
_disable() {
|
|
this._inertStrategy.allowFocus(this);
|
|
this.toggleAnchors(false);
|
|
}
|
|
};
|
|
var FOCUS_TRAP_INERT_STRATEGY = new InjectionToken("FOCUS_TRAP_INERT_STRATEGY");
|
|
var EventListenerFocusTrapInertStrategy = class {
|
|
constructor() {
|
|
this._listener = null;
|
|
}
|
|
/** Adds a document event listener that keeps focus inside the FocusTrap. */
|
|
preventFocus(focusTrap) {
|
|
if (this._listener) {
|
|
focusTrap._document.removeEventListener("focus", this._listener, true);
|
|
}
|
|
this._listener = (e) => this._trapFocus(focusTrap, e);
|
|
focusTrap._ngZone.runOutsideAngular(() => {
|
|
focusTrap._document.addEventListener("focus", this._listener, true);
|
|
});
|
|
}
|
|
/** Removes the event listener added in preventFocus. */
|
|
allowFocus(focusTrap) {
|
|
if (!this._listener) {
|
|
return;
|
|
}
|
|
focusTrap._document.removeEventListener("focus", this._listener, true);
|
|
this._listener = null;
|
|
}
|
|
/**
|
|
* Refocuses the first element in the FocusTrap if the focus event target was outside
|
|
* the FocusTrap.
|
|
*
|
|
* This is an event listener callback. The event listener is added in runOutsideAngular,
|
|
* so all this code runs outside Angular as well.
|
|
*/
|
|
_trapFocus(focusTrap, event) {
|
|
const target = event.target;
|
|
const focusTrapRoot = focusTrap._element;
|
|
if (target && !focusTrapRoot.contains(target) && !target.closest?.("div.cdk-overlay-pane")) {
|
|
setTimeout(() => {
|
|
if (focusTrap.enabled && !focusTrapRoot.contains(focusTrap._document.activeElement)) {
|
|
focusTrap.focusFirstTabbableElement();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
var _FocusTrapManager = class _FocusTrapManager {
|
|
constructor() {
|
|
this._focusTrapStack = [];
|
|
}
|
|
/**
|
|
* Disables the FocusTrap at the top of the stack, and then pushes
|
|
* the new FocusTrap onto the stack.
|
|
*/
|
|
register(focusTrap) {
|
|
this._focusTrapStack = this._focusTrapStack.filter((ft) => ft !== focusTrap);
|
|
let stack = this._focusTrapStack;
|
|
if (stack.length) {
|
|
stack[stack.length - 1]._disable();
|
|
}
|
|
stack.push(focusTrap);
|
|
focusTrap._enable();
|
|
}
|
|
/**
|
|
* Removes the FocusTrap from the stack, and activates the
|
|
* FocusTrap that is the new top of the stack.
|
|
*/
|
|
deregister(focusTrap) {
|
|
focusTrap._disable();
|
|
const stack = this._focusTrapStack;
|
|
const i = stack.indexOf(focusTrap);
|
|
if (i !== -1) {
|
|
stack.splice(i, 1);
|
|
if (stack.length) {
|
|
stack[stack.length - 1]._enable();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
_FocusTrapManager.ɵfac = function FocusTrapManager_Factory(t) {
|
|
return new (t || _FocusTrapManager)();
|
|
};
|
|
_FocusTrapManager.ɵprov = ɵɵdefineInjectable({
|
|
token: _FocusTrapManager,
|
|
factory: _FocusTrapManager.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var FocusTrapManager = _FocusTrapManager;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(FocusTrapManager, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
var _ConfigurableFocusTrapFactory = class _ConfigurableFocusTrapFactory {
|
|
constructor(_checker, _ngZone, _focusTrapManager, _document, _inertStrategy) {
|
|
this._checker = _checker;
|
|
this._ngZone = _ngZone;
|
|
this._focusTrapManager = _focusTrapManager;
|
|
this._document = _document;
|
|
this._inertStrategy = _inertStrategy || new EventListenerFocusTrapInertStrategy();
|
|
}
|
|
create(element, config = {
|
|
defer: false
|
|
}) {
|
|
let configObject;
|
|
if (typeof config === "boolean") {
|
|
configObject = {
|
|
defer: config
|
|
};
|
|
} else {
|
|
configObject = config;
|
|
}
|
|
return new ConfigurableFocusTrap(element, this._checker, this._ngZone, this._document, this._focusTrapManager, this._inertStrategy, configObject);
|
|
}
|
|
};
|
|
_ConfigurableFocusTrapFactory.ɵfac = function ConfigurableFocusTrapFactory_Factory(t) {
|
|
return new (t || _ConfigurableFocusTrapFactory)(ɵɵinject(InteractivityChecker), ɵɵinject(NgZone), ɵɵinject(FocusTrapManager), ɵɵinject(DOCUMENT), ɵɵinject(FOCUS_TRAP_INERT_STRATEGY, 8));
|
|
};
|
|
_ConfigurableFocusTrapFactory.ɵprov = ɵɵdefineInjectable({
|
|
token: _ConfigurableFocusTrapFactory,
|
|
factory: _ConfigurableFocusTrapFactory.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var ConfigurableFocusTrapFactory = _ConfigurableFocusTrapFactory;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ConfigurableFocusTrapFactory, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: InteractivityChecker
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: FocusTrapManager
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [FOCUS_TRAP_INERT_STRATEGY]
|
|
}]
|
|
}], null);
|
|
})();
|
|
function isFakeMousedownFromScreenReader(event) {
|
|
return event.buttons === 0 || event.detail === 0;
|
|
}
|
|
function isFakeTouchstartFromScreenReader(event) {
|
|
const touch = event.touches && event.touches[0] || event.changedTouches && event.changedTouches[0];
|
|
return !!touch && touch.identifier === -1 && (touch.radiusX == null || touch.radiusX === 1) && (touch.radiusY == null || touch.radiusY === 1);
|
|
}
|
|
var INPUT_MODALITY_DETECTOR_OPTIONS = new InjectionToken("cdk-input-modality-detector-options");
|
|
var INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS = {
|
|
ignoreKeys: [ALT, CONTROL, MAC_META, META, SHIFT]
|
|
};
|
|
var TOUCH_BUFFER_MS = 650;
|
|
var modalityEventListenerOptions = normalizePassiveListenerOptions({
|
|
passive: true,
|
|
capture: true
|
|
});
|
|
var _InputModalityDetector = class _InputModalityDetector {
|
|
/** The most recently detected input modality. */
|
|
get mostRecentModality() {
|
|
return this._modality.value;
|
|
}
|
|
constructor(_platform, ngZone, document2, options) {
|
|
this._platform = _platform;
|
|
this._mostRecentTarget = null;
|
|
this._modality = new BehaviorSubject(null);
|
|
this._lastTouchMs = 0;
|
|
this._onKeydown = (event) => {
|
|
if (this._options?.ignoreKeys?.some((keyCode) => keyCode === event.keyCode)) {
|
|
return;
|
|
}
|
|
this._modality.next("keyboard");
|
|
this._mostRecentTarget = _getEventTarget(event);
|
|
};
|
|
this._onMousedown = (event) => {
|
|
if (Date.now() - this._lastTouchMs < TOUCH_BUFFER_MS) {
|
|
return;
|
|
}
|
|
this._modality.next(isFakeMousedownFromScreenReader(event) ? "keyboard" : "mouse");
|
|
this._mostRecentTarget = _getEventTarget(event);
|
|
};
|
|
this._onTouchstart = (event) => {
|
|
if (isFakeTouchstartFromScreenReader(event)) {
|
|
this._modality.next("keyboard");
|
|
return;
|
|
}
|
|
this._lastTouchMs = Date.now();
|
|
this._modality.next("touch");
|
|
this._mostRecentTarget = _getEventTarget(event);
|
|
};
|
|
this._options = __spreadValues(__spreadValues({}, INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS), options);
|
|
this.modalityDetected = this._modality.pipe(skip(1));
|
|
this.modalityChanged = this.modalityDetected.pipe(distinctUntilChanged());
|
|
if (_platform.isBrowser) {
|
|
ngZone.runOutsideAngular(() => {
|
|
document2.addEventListener("keydown", this._onKeydown, modalityEventListenerOptions);
|
|
document2.addEventListener("mousedown", this._onMousedown, modalityEventListenerOptions);
|
|
document2.addEventListener("touchstart", this._onTouchstart, modalityEventListenerOptions);
|
|
});
|
|
}
|
|
}
|
|
ngOnDestroy() {
|
|
this._modality.complete();
|
|
if (this._platform.isBrowser) {
|
|
document.removeEventListener("keydown", this._onKeydown, modalityEventListenerOptions);
|
|
document.removeEventListener("mousedown", this._onMousedown, modalityEventListenerOptions);
|
|
document.removeEventListener("touchstart", this._onTouchstart, modalityEventListenerOptions);
|
|
}
|
|
}
|
|
};
|
|
_InputModalityDetector.ɵfac = function InputModalityDetector_Factory(t) {
|
|
return new (t || _InputModalityDetector)(ɵɵinject(Platform), ɵɵinject(NgZone), ɵɵinject(DOCUMENT), ɵɵinject(INPUT_MODALITY_DETECTOR_OPTIONS, 8));
|
|
};
|
|
_InputModalityDetector.ɵprov = ɵɵdefineInjectable({
|
|
token: _InputModalityDetector,
|
|
factory: _InputModalityDetector.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var InputModalityDetector = _InputModalityDetector;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(InputModalityDetector, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: Platform
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: Document,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [INPUT_MODALITY_DETECTOR_OPTIONS]
|
|
}]
|
|
}], null);
|
|
})();
|
|
var LIVE_ANNOUNCER_ELEMENT_TOKEN = new InjectionToken("liveAnnouncerElement", {
|
|
providedIn: "root",
|
|
factory: LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY
|
|
});
|
|
function LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY() {
|
|
return null;
|
|
}
|
|
var LIVE_ANNOUNCER_DEFAULT_OPTIONS = new InjectionToken("LIVE_ANNOUNCER_DEFAULT_OPTIONS");
|
|
var uniqueIds = 0;
|
|
var _LiveAnnouncer = class _LiveAnnouncer {
|
|
constructor(elementToken, _ngZone, _document, _defaultOptions) {
|
|
this._ngZone = _ngZone;
|
|
this._defaultOptions = _defaultOptions;
|
|
this._document = _document;
|
|
this._liveElement = elementToken || this._createLiveElement();
|
|
}
|
|
announce(message, ...args) {
|
|
const defaultOptions = this._defaultOptions;
|
|
let politeness;
|
|
let duration;
|
|
if (args.length === 1 && typeof args[0] === "number") {
|
|
duration = args[0];
|
|
} else {
|
|
[politeness, duration] = args;
|
|
}
|
|
this.clear();
|
|
clearTimeout(this._previousTimeout);
|
|
if (!politeness) {
|
|
politeness = defaultOptions && defaultOptions.politeness ? defaultOptions.politeness : "polite";
|
|
}
|
|
if (duration == null && defaultOptions) {
|
|
duration = defaultOptions.duration;
|
|
}
|
|
this._liveElement.setAttribute("aria-live", politeness);
|
|
if (this._liveElement.id) {
|
|
this._exposeAnnouncerToModals(this._liveElement.id);
|
|
}
|
|
return this._ngZone.runOutsideAngular(() => {
|
|
if (!this._currentPromise) {
|
|
this._currentPromise = new Promise((resolve) => this._currentResolve = resolve);
|
|
}
|
|
clearTimeout(this._previousTimeout);
|
|
this._previousTimeout = setTimeout(() => {
|
|
this._liveElement.textContent = message;
|
|
if (typeof duration === "number") {
|
|
this._previousTimeout = setTimeout(() => this.clear(), duration);
|
|
}
|
|
this._currentResolve();
|
|
this._currentPromise = this._currentResolve = void 0;
|
|
}, 100);
|
|
return this._currentPromise;
|
|
});
|
|
}
|
|
/**
|
|
* Clears the current text from the announcer element. Can be used to prevent
|
|
* screen readers from reading the text out again while the user is going
|
|
* through the page landmarks.
|
|
*/
|
|
clear() {
|
|
if (this._liveElement) {
|
|
this._liveElement.textContent = "";
|
|
}
|
|
}
|
|
ngOnDestroy() {
|
|
clearTimeout(this._previousTimeout);
|
|
this._liveElement?.remove();
|
|
this._liveElement = null;
|
|
this._currentResolve?.();
|
|
this._currentPromise = this._currentResolve = void 0;
|
|
}
|
|
_createLiveElement() {
|
|
const elementClass = "cdk-live-announcer-element";
|
|
const previousElements = this._document.getElementsByClassName(elementClass);
|
|
const liveEl = this._document.createElement("div");
|
|
for (let i = 0; i < previousElements.length; i++) {
|
|
previousElements[i].remove();
|
|
}
|
|
liveEl.classList.add(elementClass);
|
|
liveEl.classList.add("cdk-visually-hidden");
|
|
liveEl.setAttribute("aria-atomic", "true");
|
|
liveEl.setAttribute("aria-live", "polite");
|
|
liveEl.id = `cdk-live-announcer-${uniqueIds++}`;
|
|
this._document.body.appendChild(liveEl);
|
|
return liveEl;
|
|
}
|
|
/**
|
|
* Some browsers won't expose the accessibility node of the live announcer element if there is an
|
|
* `aria-modal` and the live announcer is outside of it. This method works around the issue by
|
|
* pointing the `aria-owns` of all modals to the live announcer element.
|
|
*/
|
|
_exposeAnnouncerToModals(id) {
|
|
const modals = this._document.querySelectorAll('body > .cdk-overlay-container [aria-modal="true"]');
|
|
for (let i = 0; i < modals.length; i++) {
|
|
const modal = modals[i];
|
|
const ariaOwns = modal.getAttribute("aria-owns");
|
|
if (!ariaOwns) {
|
|
modal.setAttribute("aria-owns", id);
|
|
} else if (ariaOwns.indexOf(id) === -1) {
|
|
modal.setAttribute("aria-owns", ariaOwns + " " + id);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
_LiveAnnouncer.ɵfac = function LiveAnnouncer_Factory(t) {
|
|
return new (t || _LiveAnnouncer)(ɵɵinject(LIVE_ANNOUNCER_ELEMENT_TOKEN, 8), ɵɵinject(NgZone), ɵɵinject(DOCUMENT), ɵɵinject(LIVE_ANNOUNCER_DEFAULT_OPTIONS, 8));
|
|
};
|
|
_LiveAnnouncer.ɵprov = ɵɵdefineInjectable({
|
|
token: _LiveAnnouncer,
|
|
factory: _LiveAnnouncer.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var LiveAnnouncer = _LiveAnnouncer;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(LiveAnnouncer, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [LIVE_ANNOUNCER_ELEMENT_TOKEN]
|
|
}]
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [LIVE_ANNOUNCER_DEFAULT_OPTIONS]
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _CdkAriaLive = class _CdkAriaLive {
|
|
/** The aria-live politeness level to use when announcing messages. */
|
|
get politeness() {
|
|
return this._politeness;
|
|
}
|
|
set politeness(value) {
|
|
this._politeness = value === "off" || value === "assertive" ? value : "polite";
|
|
if (this._politeness === "off") {
|
|
if (this._subscription) {
|
|
this._subscription.unsubscribe();
|
|
this._subscription = null;
|
|
}
|
|
} else if (!this._subscription) {
|
|
this._subscription = this._ngZone.runOutsideAngular(() => {
|
|
return this._contentObserver.observe(this._elementRef).subscribe(() => {
|
|
const elementText = this._elementRef.nativeElement.textContent;
|
|
if (elementText !== this._previousAnnouncedText) {
|
|
this._liveAnnouncer.announce(elementText, this._politeness, this.duration);
|
|
this._previousAnnouncedText = elementText;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
constructor(_elementRef, _liveAnnouncer, _contentObserver, _ngZone) {
|
|
this._elementRef = _elementRef;
|
|
this._liveAnnouncer = _liveAnnouncer;
|
|
this._contentObserver = _contentObserver;
|
|
this._ngZone = _ngZone;
|
|
this._politeness = "polite";
|
|
}
|
|
ngOnDestroy() {
|
|
if (this._subscription) {
|
|
this._subscription.unsubscribe();
|
|
}
|
|
}
|
|
};
|
|
_CdkAriaLive.ɵfac = function CdkAriaLive_Factory(t) {
|
|
return new (t || _CdkAriaLive)(ɵɵdirectiveInject(ElementRef), ɵɵdirectiveInject(LiveAnnouncer), ɵɵdirectiveInject(ContentObserver), ɵɵdirectiveInject(NgZone));
|
|
};
|
|
_CdkAriaLive.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkAriaLive,
|
|
selectors: [["", "cdkAriaLive", ""]],
|
|
inputs: {
|
|
politeness: ["cdkAriaLive", "politeness"],
|
|
duration: ["cdkAriaLiveDuration", "duration"]
|
|
},
|
|
exportAs: ["cdkAriaLive"]
|
|
});
|
|
var CdkAriaLive = _CdkAriaLive;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkAriaLive, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkAriaLive]",
|
|
exportAs: "cdkAriaLive"
|
|
}]
|
|
}], () => [{
|
|
type: ElementRef
|
|
}, {
|
|
type: LiveAnnouncer
|
|
}, {
|
|
type: ContentObserver
|
|
}, {
|
|
type: NgZone
|
|
}], {
|
|
politeness: [{
|
|
type: Input,
|
|
args: ["cdkAriaLive"]
|
|
}],
|
|
duration: [{
|
|
type: Input,
|
|
args: ["cdkAriaLiveDuration"]
|
|
}]
|
|
});
|
|
})();
|
|
var FOCUS_MONITOR_DEFAULT_OPTIONS = new InjectionToken("cdk-focus-monitor-default-options");
|
|
var captureEventListenerOptions = normalizePassiveListenerOptions({
|
|
passive: true,
|
|
capture: true
|
|
});
|
|
var _FocusMonitor = class _FocusMonitor {
|
|
constructor(_ngZone, _platform, _inputModalityDetector, document2, options) {
|
|
this._ngZone = _ngZone;
|
|
this._platform = _platform;
|
|
this._inputModalityDetector = _inputModalityDetector;
|
|
this._origin = null;
|
|
this._windowFocused = false;
|
|
this._originFromTouchInteraction = false;
|
|
this._elementInfo = /* @__PURE__ */ new Map();
|
|
this._monitoredElementCount = 0;
|
|
this._rootNodeFocusListenerCount = /* @__PURE__ */ new Map();
|
|
this._windowFocusListener = () => {
|
|
this._windowFocused = true;
|
|
this._windowFocusTimeoutId = window.setTimeout(() => this._windowFocused = false);
|
|
};
|
|
this._stopInputModalityDetector = new Subject();
|
|
this._rootNodeFocusAndBlurListener = (event) => {
|
|
const target = _getEventTarget(event);
|
|
for (let element = target; element; element = element.parentElement) {
|
|
if (event.type === "focus") {
|
|
this._onFocus(event, element);
|
|
} else {
|
|
this._onBlur(event, element);
|
|
}
|
|
}
|
|
};
|
|
this._document = document2;
|
|
this._detectionMode = options?.detectionMode || 0;
|
|
}
|
|
monitor(element, checkChildren = false) {
|
|
const nativeElement = coerceElement(element);
|
|
if (!this._platform.isBrowser || nativeElement.nodeType !== 1) {
|
|
return of();
|
|
}
|
|
const rootNode = _getShadowRoot(nativeElement) || this._getDocument();
|
|
const cachedInfo = this._elementInfo.get(nativeElement);
|
|
if (cachedInfo) {
|
|
if (checkChildren) {
|
|
cachedInfo.checkChildren = true;
|
|
}
|
|
return cachedInfo.subject;
|
|
}
|
|
const info = {
|
|
checkChildren,
|
|
subject: new Subject(),
|
|
rootNode
|
|
};
|
|
this._elementInfo.set(nativeElement, info);
|
|
this._registerGlobalListeners(info);
|
|
return info.subject;
|
|
}
|
|
stopMonitoring(element) {
|
|
const nativeElement = coerceElement(element);
|
|
const elementInfo = this._elementInfo.get(nativeElement);
|
|
if (elementInfo) {
|
|
elementInfo.subject.complete();
|
|
this._setClasses(nativeElement);
|
|
this._elementInfo.delete(nativeElement);
|
|
this._removeGlobalListeners(elementInfo);
|
|
}
|
|
}
|
|
focusVia(element, origin, options) {
|
|
const nativeElement = coerceElement(element);
|
|
const focusedElement = this._getDocument().activeElement;
|
|
if (nativeElement === focusedElement) {
|
|
this._getClosestElementsInfo(nativeElement).forEach(([currentElement, info]) => this._originChanged(currentElement, origin, info));
|
|
} else {
|
|
this._setOrigin(origin);
|
|
if (typeof nativeElement.focus === "function") {
|
|
nativeElement.focus(options);
|
|
}
|
|
}
|
|
}
|
|
ngOnDestroy() {
|
|
this._elementInfo.forEach((_info, element) => this.stopMonitoring(element));
|
|
}
|
|
/** Access injected document if available or fallback to global document reference */
|
|
_getDocument() {
|
|
return this._document || document;
|
|
}
|
|
/** Use defaultView of injected document if available or fallback to global window reference */
|
|
_getWindow() {
|
|
const doc = this._getDocument();
|
|
return doc.defaultView || window;
|
|
}
|
|
_getFocusOrigin(focusEventTarget) {
|
|
if (this._origin) {
|
|
if (this._originFromTouchInteraction) {
|
|
return this._shouldBeAttributedToTouch(focusEventTarget) ? "touch" : "program";
|
|
} else {
|
|
return this._origin;
|
|
}
|
|
}
|
|
if (this._windowFocused && this._lastFocusOrigin) {
|
|
return this._lastFocusOrigin;
|
|
}
|
|
if (focusEventTarget && this._isLastInteractionFromInputLabel(focusEventTarget)) {
|
|
return "mouse";
|
|
}
|
|
return "program";
|
|
}
|
|
/**
|
|
* Returns whether the focus event should be attributed to touch. Recall that in IMMEDIATE mode, a
|
|
* touch origin isn't immediately reset at the next tick (see _setOrigin). This means that when we
|
|
* handle a focus event following a touch interaction, we need to determine whether (1) the focus
|
|
* event was directly caused by the touch interaction or (2) the focus event was caused by a
|
|
* subsequent programmatic focus call triggered by the touch interaction.
|
|
* @param focusEventTarget The target of the focus event under examination.
|
|
*/
|
|
_shouldBeAttributedToTouch(focusEventTarget) {
|
|
return this._detectionMode === 1 || !!focusEventTarget?.contains(this._inputModalityDetector._mostRecentTarget);
|
|
}
|
|
/**
|
|
* Sets the focus classes on the element based on the given focus origin.
|
|
* @param element The element to update the classes on.
|
|
* @param origin The focus origin.
|
|
*/
|
|
_setClasses(element, origin) {
|
|
element.classList.toggle("cdk-focused", !!origin);
|
|
element.classList.toggle("cdk-touch-focused", origin === "touch");
|
|
element.classList.toggle("cdk-keyboard-focused", origin === "keyboard");
|
|
element.classList.toggle("cdk-mouse-focused", origin === "mouse");
|
|
element.classList.toggle("cdk-program-focused", origin === "program");
|
|
}
|
|
/**
|
|
* Updates the focus origin. If we're using immediate detection mode, we schedule an async
|
|
* function to clear the origin at the end of a timeout. The duration of the timeout depends on
|
|
* the origin being set.
|
|
* @param origin The origin to set.
|
|
* @param isFromInteraction Whether we are setting the origin from an interaction event.
|
|
*/
|
|
_setOrigin(origin, isFromInteraction = false) {
|
|
this._ngZone.runOutsideAngular(() => {
|
|
this._origin = origin;
|
|
this._originFromTouchInteraction = origin === "touch" && isFromInteraction;
|
|
if (this._detectionMode === 0) {
|
|
clearTimeout(this._originTimeoutId);
|
|
const ms = this._originFromTouchInteraction ? TOUCH_BUFFER_MS : 1;
|
|
this._originTimeoutId = setTimeout(() => this._origin = null, ms);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Handles focus events on a registered element.
|
|
* @param event The focus event.
|
|
* @param element The monitored element.
|
|
*/
|
|
_onFocus(event, element) {
|
|
const elementInfo = this._elementInfo.get(element);
|
|
const focusEventTarget = _getEventTarget(event);
|
|
if (!elementInfo || !elementInfo.checkChildren && element !== focusEventTarget) {
|
|
return;
|
|
}
|
|
this._originChanged(element, this._getFocusOrigin(focusEventTarget), elementInfo);
|
|
}
|
|
/**
|
|
* Handles blur events on a registered element.
|
|
* @param event The blur event.
|
|
* @param element The monitored element.
|
|
*/
|
|
_onBlur(event, element) {
|
|
const elementInfo = this._elementInfo.get(element);
|
|
if (!elementInfo || elementInfo.checkChildren && event.relatedTarget instanceof Node && element.contains(event.relatedTarget)) {
|
|
return;
|
|
}
|
|
this._setClasses(element);
|
|
this._emitOrigin(elementInfo, null);
|
|
}
|
|
_emitOrigin(info, origin) {
|
|
if (info.subject.observers.length) {
|
|
this._ngZone.run(() => info.subject.next(origin));
|
|
}
|
|
}
|
|
_registerGlobalListeners(elementInfo) {
|
|
if (!this._platform.isBrowser) {
|
|
return;
|
|
}
|
|
const rootNode = elementInfo.rootNode;
|
|
const rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode) || 0;
|
|
if (!rootNodeFocusListeners) {
|
|
this._ngZone.runOutsideAngular(() => {
|
|
rootNode.addEventListener("focus", this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
|
|
rootNode.addEventListener("blur", this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
|
|
});
|
|
}
|
|
this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners + 1);
|
|
if (++this._monitoredElementCount === 1) {
|
|
this._ngZone.runOutsideAngular(() => {
|
|
const window2 = this._getWindow();
|
|
window2.addEventListener("focus", this._windowFocusListener);
|
|
});
|
|
this._inputModalityDetector.modalityDetected.pipe(takeUntil(this._stopInputModalityDetector)).subscribe((modality) => {
|
|
this._setOrigin(
|
|
modality,
|
|
true
|
|
/* isFromInteraction */
|
|
);
|
|
});
|
|
}
|
|
}
|
|
_removeGlobalListeners(elementInfo) {
|
|
const rootNode = elementInfo.rootNode;
|
|
if (this._rootNodeFocusListenerCount.has(rootNode)) {
|
|
const rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode);
|
|
if (rootNodeFocusListeners > 1) {
|
|
this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners - 1);
|
|
} else {
|
|
rootNode.removeEventListener("focus", this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
|
|
rootNode.removeEventListener("blur", this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
|
|
this._rootNodeFocusListenerCount.delete(rootNode);
|
|
}
|
|
}
|
|
if (!--this._monitoredElementCount) {
|
|
const window2 = this._getWindow();
|
|
window2.removeEventListener("focus", this._windowFocusListener);
|
|
this._stopInputModalityDetector.next();
|
|
clearTimeout(this._windowFocusTimeoutId);
|
|
clearTimeout(this._originTimeoutId);
|
|
}
|
|
}
|
|
/** Updates all the state on an element once its focus origin has changed. */
|
|
_originChanged(element, origin, elementInfo) {
|
|
this._setClasses(element, origin);
|
|
this._emitOrigin(elementInfo, origin);
|
|
this._lastFocusOrigin = origin;
|
|
}
|
|
/**
|
|
* Collects the `MonitoredElementInfo` of a particular element and
|
|
* all of its ancestors that have enabled `checkChildren`.
|
|
* @param element Element from which to start the search.
|
|
*/
|
|
_getClosestElementsInfo(element) {
|
|
const results = [];
|
|
this._elementInfo.forEach((info, currentElement) => {
|
|
if (currentElement === element || info.checkChildren && currentElement.contains(element)) {
|
|
results.push([currentElement, info]);
|
|
}
|
|
});
|
|
return results;
|
|
}
|
|
/**
|
|
* Returns whether an interaction is likely to have come from the user clicking the `label` of
|
|
* an `input` or `textarea` in order to focus it.
|
|
* @param focusEventTarget Target currently receiving focus.
|
|
*/
|
|
_isLastInteractionFromInputLabel(focusEventTarget) {
|
|
const {
|
|
_mostRecentTarget: mostRecentTarget,
|
|
mostRecentModality
|
|
} = this._inputModalityDetector;
|
|
if (mostRecentModality !== "mouse" || !mostRecentTarget || mostRecentTarget === focusEventTarget || focusEventTarget.nodeName !== "INPUT" && focusEventTarget.nodeName !== "TEXTAREA" || focusEventTarget.disabled) {
|
|
return false;
|
|
}
|
|
const labels = focusEventTarget.labels;
|
|
if (labels) {
|
|
for (let i = 0; i < labels.length; i++) {
|
|
if (labels[i].contains(mostRecentTarget)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
_FocusMonitor.ɵfac = function FocusMonitor_Factory(t) {
|
|
return new (t || _FocusMonitor)(ɵɵinject(NgZone), ɵɵinject(Platform), ɵɵinject(InputModalityDetector), ɵɵinject(DOCUMENT, 8), ɵɵinject(FOCUS_MONITOR_DEFAULT_OPTIONS, 8));
|
|
};
|
|
_FocusMonitor.ɵprov = ɵɵdefineInjectable({
|
|
token: _FocusMonitor,
|
|
factory: _FocusMonitor.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var FocusMonitor = _FocusMonitor;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(FocusMonitor, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: NgZone
|
|
}, {
|
|
type: Platform
|
|
}, {
|
|
type: InputModalityDetector
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [FOCUS_MONITOR_DEFAULT_OPTIONS]
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _CdkMonitorFocus = class _CdkMonitorFocus {
|
|
constructor(_elementRef, _focusMonitor) {
|
|
this._elementRef = _elementRef;
|
|
this._focusMonitor = _focusMonitor;
|
|
this._focusOrigin = null;
|
|
this.cdkFocusChange = new EventEmitter();
|
|
}
|
|
get focusOrigin() {
|
|
return this._focusOrigin;
|
|
}
|
|
ngAfterViewInit() {
|
|
const element = this._elementRef.nativeElement;
|
|
this._monitorSubscription = this._focusMonitor.monitor(element, element.nodeType === 1 && element.hasAttribute("cdkMonitorSubtreeFocus")).subscribe((origin) => {
|
|
this._focusOrigin = origin;
|
|
this.cdkFocusChange.emit(origin);
|
|
});
|
|
}
|
|
ngOnDestroy() {
|
|
this._focusMonitor.stopMonitoring(this._elementRef);
|
|
if (this._monitorSubscription) {
|
|
this._monitorSubscription.unsubscribe();
|
|
}
|
|
}
|
|
};
|
|
_CdkMonitorFocus.ɵfac = function CdkMonitorFocus_Factory(t) {
|
|
return new (t || _CdkMonitorFocus)(ɵɵdirectiveInject(ElementRef), ɵɵdirectiveInject(FocusMonitor));
|
|
};
|
|
_CdkMonitorFocus.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkMonitorFocus,
|
|
selectors: [["", "cdkMonitorElementFocus", ""], ["", "cdkMonitorSubtreeFocus", ""]],
|
|
outputs: {
|
|
cdkFocusChange: "cdkFocusChange"
|
|
},
|
|
exportAs: ["cdkMonitorFocus"]
|
|
});
|
|
var CdkMonitorFocus = _CdkMonitorFocus;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkMonitorFocus, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]",
|
|
exportAs: "cdkMonitorFocus"
|
|
}]
|
|
}], () => [{
|
|
type: ElementRef
|
|
}, {
|
|
type: FocusMonitor
|
|
}], {
|
|
cdkFocusChange: [{
|
|
type: Output
|
|
}]
|
|
});
|
|
})();
|
|
var BLACK_ON_WHITE_CSS_CLASS = "cdk-high-contrast-black-on-white";
|
|
var WHITE_ON_BLACK_CSS_CLASS = "cdk-high-contrast-white-on-black";
|
|
var HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS = "cdk-high-contrast-active";
|
|
var _HighContrastModeDetector = class _HighContrastModeDetector {
|
|
constructor(_platform, document2) {
|
|
this._platform = _platform;
|
|
this._document = document2;
|
|
this._breakpointSubscription = inject(BreakpointObserver).observe("(forced-colors: active)").subscribe(() => {
|
|
if (this._hasCheckedHighContrastMode) {
|
|
this._hasCheckedHighContrastMode = false;
|
|
this._applyBodyHighContrastModeCssClasses();
|
|
}
|
|
});
|
|
}
|
|
/** Gets the current high-contrast-mode for the page. */
|
|
getHighContrastMode() {
|
|
if (!this._platform.isBrowser) {
|
|
return 0;
|
|
}
|
|
const testElement = this._document.createElement("div");
|
|
testElement.style.backgroundColor = "rgb(1,2,3)";
|
|
testElement.style.position = "absolute";
|
|
this._document.body.appendChild(testElement);
|
|
const documentWindow = this._document.defaultView || window;
|
|
const computedStyle = documentWindow && documentWindow.getComputedStyle ? documentWindow.getComputedStyle(testElement) : null;
|
|
const computedColor = (computedStyle && computedStyle.backgroundColor || "").replace(/ /g, "");
|
|
testElement.remove();
|
|
switch (computedColor) {
|
|
case "rgb(0,0,0)":
|
|
case "rgb(45,50,54)":
|
|
case "rgb(32,32,32)":
|
|
return 2;
|
|
case "rgb(255,255,255)":
|
|
case "rgb(255,250,239)":
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
ngOnDestroy() {
|
|
this._breakpointSubscription.unsubscribe();
|
|
}
|
|
/** Applies CSS classes indicating high-contrast mode to document body (browser-only). */
|
|
_applyBodyHighContrastModeCssClasses() {
|
|
if (!this._hasCheckedHighContrastMode && this._platform.isBrowser && this._document.body) {
|
|
const bodyClasses = this._document.body.classList;
|
|
bodyClasses.remove(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS, BLACK_ON_WHITE_CSS_CLASS, WHITE_ON_BLACK_CSS_CLASS);
|
|
this._hasCheckedHighContrastMode = true;
|
|
const mode = this.getHighContrastMode();
|
|
if (mode === 1) {
|
|
bodyClasses.add(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS, BLACK_ON_WHITE_CSS_CLASS);
|
|
} else if (mode === 2) {
|
|
bodyClasses.add(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS, WHITE_ON_BLACK_CSS_CLASS);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
_HighContrastModeDetector.ɵfac = function HighContrastModeDetector_Factory(t) {
|
|
return new (t || _HighContrastModeDetector)(ɵɵinject(Platform), ɵɵinject(DOCUMENT));
|
|
};
|
|
_HighContrastModeDetector.ɵprov = ɵɵdefineInjectable({
|
|
token: _HighContrastModeDetector,
|
|
factory: _HighContrastModeDetector.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var HighContrastModeDetector = _HighContrastModeDetector;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(HighContrastModeDetector, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: Platform
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _A11yModule = class _A11yModule {
|
|
constructor(highContrastModeDetector) {
|
|
highContrastModeDetector._applyBodyHighContrastModeCssClasses();
|
|
}
|
|
};
|
|
_A11yModule.ɵfac = function A11yModule_Factory(t) {
|
|
return new (t || _A11yModule)(ɵɵinject(HighContrastModeDetector));
|
|
};
|
|
_A11yModule.ɵmod = ɵɵdefineNgModule({
|
|
type: _A11yModule,
|
|
declarations: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus],
|
|
imports: [ObserversModule],
|
|
exports: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus]
|
|
});
|
|
_A11yModule.ɵinj = ɵɵdefineInjector({
|
|
imports: [ObserversModule]
|
|
});
|
|
var A11yModule = _A11yModule;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(A11yModule, [{
|
|
type: NgModule,
|
|
args: [{
|
|
imports: [ObserversModule],
|
|
declarations: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus],
|
|
exports: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus]
|
|
}]
|
|
}], () => [{
|
|
type: HighContrastModeDetector
|
|
}], null);
|
|
})();
|
|
|
|
export {
|
|
FocusMonitor
|
|
};
|
|
//# sourceMappingURL=chunk-C2DNBB57.js.map
|
|
|