3289 lines
111 KiB
JavaScript
3289 lines
111 KiB
JavaScript
import {
|
|
isPlatformBrowser
|
|
} from "./chunk-NNL7YDF7.js";
|
|
import "./chunk-ZJ25XCV3.js";
|
|
import {
|
|
APP_ID,
|
|
ApplicationRef,
|
|
CSP_NONCE,
|
|
ChangeDetectionStrategy,
|
|
Component,
|
|
Directive,
|
|
ElementRef,
|
|
EventEmitter,
|
|
Injectable,
|
|
Input,
|
|
NgModule,
|
|
NgZone,
|
|
Output,
|
|
PLATFORM_ID,
|
|
QueryList,
|
|
RendererFactory2,
|
|
ViewEncapsulation,
|
|
afterNextRender,
|
|
booleanAttribute,
|
|
createComponent,
|
|
setClassMetadata,
|
|
ɵɵNgOnChangesFeature,
|
|
ɵɵdefineComponent,
|
|
ɵɵdefineDirective,
|
|
ɵɵdefineNgModule
|
|
} from "./chunk-A3W7CVOL.js";
|
|
import {
|
|
DOCUMENT,
|
|
EnvironmentInjector,
|
|
InjectionToken,
|
|
Injector,
|
|
effect,
|
|
inject,
|
|
isSignal,
|
|
signal,
|
|
ɵɵdefineInjectable,
|
|
ɵɵdefineInjector
|
|
} from "./chunk-M4YA2FTX.js";
|
|
import {
|
|
BehaviorSubject,
|
|
Observable,
|
|
Subject,
|
|
Subscription,
|
|
combineLatest,
|
|
concat,
|
|
debounceTime,
|
|
distinctUntilChanged,
|
|
filter,
|
|
isObservable,
|
|
map,
|
|
of,
|
|
skip,
|
|
startWith,
|
|
take,
|
|
takeUntil,
|
|
tap
|
|
} from "./chunk-RSS3ODKE.js";
|
|
import {
|
|
__spreadValues
|
|
} from "./chunk-TKSB4YUA.js";
|
|
|
|
// node_modules/@angular/cdk/fesm2022/fake-event-detection.mjs
|
|
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);
|
|
}
|
|
|
|
// node_modules/@angular/cdk/fesm2022/keycodes2.mjs
|
|
var TAB = 9;
|
|
var SHIFT = 16;
|
|
var CONTROL = 17;
|
|
var ALT = 18;
|
|
var PAGE_UP = 33;
|
|
var PAGE_DOWN = 34;
|
|
var END = 35;
|
|
var HOME = 36;
|
|
var LEFT_ARROW = 37;
|
|
var UP_ARROW = 38;
|
|
var RIGHT_ARROW = 39;
|
|
var DOWN_ARROW = 40;
|
|
var ZERO = 48;
|
|
var NINE = 57;
|
|
var A = 65;
|
|
var Z = 90;
|
|
var META = 91;
|
|
var MAC_META = 224;
|
|
|
|
// node_modules/@angular/cdk/fesm2022/shadow-dom.mjs
|
|
var shadowDomIsSupported;
|
|
function _supportsShadowDom() {
|
|
if (shadowDomIsSupported == null) {
|
|
const head = typeof document !== "undefined" ? document.head : null;
|
|
shadowDomIsSupported = !!(head && (head.createShadowRoot || head.attachShadow));
|
|
}
|
|
return shadowDomIsSupported;
|
|
}
|
|
function _getShadowRoot(element) {
|
|
if (_supportsShadowDom()) {
|
|
const rootNode = element.getRootNode ? element.getRootNode() : null;
|
|
if (typeof ShadowRoot !== "undefined" && ShadowRoot && rootNode instanceof ShadowRoot) {
|
|
return rootNode;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function _getFocusedElementPierceShadowDom() {
|
|
let activeElement = typeof document !== "undefined" && document ? document.activeElement : null;
|
|
while (activeElement && activeElement.shadowRoot) {
|
|
const newActiveElement = activeElement.shadowRoot.activeElement;
|
|
if (newActiveElement === activeElement) {
|
|
break;
|
|
} else {
|
|
activeElement = newActiveElement;
|
|
}
|
|
}
|
|
return activeElement;
|
|
}
|
|
function _getEventTarget(event) {
|
|
return event.composedPath ? event.composedPath()[0] : event.target;
|
|
}
|
|
|
|
// node_modules/@angular/cdk/fesm2022/platform2.mjs
|
|
var hasV8BreakIterator;
|
|
try {
|
|
hasV8BreakIterator = typeof Intl !== "undefined" && Intl.v8BreakIterator;
|
|
} catch {
|
|
hasV8BreakIterator = false;
|
|
}
|
|
var Platform = class _Platform {
|
|
_platformId = inject(PLATFORM_ID);
|
|
// We want to use the Angular platform check because if the Document is shimmed
|
|
// without the navigator, the following checks will fail. This is preferred because
|
|
// sometimes the Document may be shimmed without the user's knowledge or intention
|
|
/** Whether the Angular application is being rendered in the browser. */
|
|
isBrowser = this._platformId ? isPlatformBrowser(this._platformId) : typeof document === "object" && !!document;
|
|
/** Whether the current browser is Microsoft Edge. */
|
|
EDGE = this.isBrowser && /(edge)/i.test(navigator.userAgent);
|
|
/** Whether the current rendering engine is Microsoft Trident. */
|
|
TRIDENT = this.isBrowser && /(msie|trident)/i.test(navigator.userAgent);
|
|
// EdgeHTML and Trident mock Blink specific things and need to be excluded from this check.
|
|
/** Whether the current rendering engine is Blink. */
|
|
BLINK = this.isBrowser && !!(window.chrome || hasV8BreakIterator) && typeof CSS !== "undefined" && !this.EDGE && !this.TRIDENT;
|
|
// Webkit is part of the userAgent in EdgeHTML, Blink and Trident. Therefore we need to
|
|
// ensure that Webkit runs standalone and is not used as another engine's base.
|
|
/** Whether the current rendering engine is WebKit. */
|
|
WEBKIT = this.isBrowser && /AppleWebKit/i.test(navigator.userAgent) && !this.BLINK && !this.EDGE && !this.TRIDENT;
|
|
/** Whether the current platform is Apple iOS. */
|
|
IOS = this.isBrowser && /iPad|iPhone|iPod/.test(navigator.userAgent) && !("MSStream" in window);
|
|
// It's difficult to detect the plain Gecko engine, because most of the browsers identify
|
|
// them self as Gecko-like browsers and modify the userAgent's according to that.
|
|
// Since we only cover one explicit Firefox case, we can simply check for Firefox
|
|
// instead of having an unstable check for Gecko.
|
|
/** Whether the current browser is Firefox. */
|
|
FIREFOX = this.isBrowser && /(firefox|minefield)/i.test(navigator.userAgent);
|
|
/** Whether the current platform is Android. */
|
|
// Trident on mobile adds the android platform to the userAgent to trick detections.
|
|
ANDROID = this.isBrowser && /android/i.test(navigator.userAgent) && !this.TRIDENT;
|
|
// Safari browsers will include the Safari keyword in their userAgent. Some browsers may fake
|
|
// this and just place the Safari keyword in the userAgent. To be more safe about Safari every
|
|
// Safari browser should also use Webkit as its layout engine.
|
|
/** Whether the current browser is Safari. */
|
|
SAFARI = this.isBrowser && /safari/i.test(navigator.userAgent) && this.WEBKIT;
|
|
constructor() {
|
|
}
|
|
static ɵfac = function Platform_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _Platform)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _Platform,
|
|
factory: _Platform.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(Platform, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/passive-listeners.mjs
|
|
var supportsPassiveEvents;
|
|
function supportsPassiveEventListeners() {
|
|
if (supportsPassiveEvents == null && typeof window !== "undefined") {
|
|
try {
|
|
window.addEventListener("test", null, Object.defineProperty({}, "passive", {
|
|
get: () => supportsPassiveEvents = true
|
|
}));
|
|
} finally {
|
|
supportsPassiveEvents = supportsPassiveEvents || false;
|
|
}
|
|
}
|
|
return supportsPassiveEvents;
|
|
}
|
|
function normalizePassiveListenerOptions(options) {
|
|
return supportsPassiveEventListeners() ? options : !!options.capture;
|
|
}
|
|
|
|
// node_modules/@angular/cdk/fesm2022/element.mjs
|
|
function coerceNumberProperty(value, fallbackValue = 0) {
|
|
if (_isNumberValue(value)) {
|
|
return Number(value);
|
|
}
|
|
return arguments.length === 2 ? fallbackValue : 0;
|
|
}
|
|
function _isNumberValue(value) {
|
|
return !isNaN(parseFloat(value)) && !isNaN(Number(value));
|
|
}
|
|
function coerceElement(elementOrRef) {
|
|
return elementOrRef instanceof ElementRef ? elementOrRef.nativeElement : elementOrRef;
|
|
}
|
|
|
|
// node_modules/@angular/cdk/fesm2022/focus-monitor.mjs
|
|
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 = {
|
|
passive: true,
|
|
capture: true
|
|
};
|
|
var InputModalityDetector = class _InputModalityDetector {
|
|
_platform = inject(Platform);
|
|
_listenerCleanups;
|
|
/** Emits whenever an input modality is detected. */
|
|
modalityDetected;
|
|
/** Emits when the input modality changes. */
|
|
modalityChanged;
|
|
/** The most recently detected input modality. */
|
|
get mostRecentModality() {
|
|
return this._modality.value;
|
|
}
|
|
/**
|
|
* The most recently detected input modality event target. Is null if no input modality has been
|
|
* detected or if the associated event target is null for some unknown reason.
|
|
*/
|
|
_mostRecentTarget = null;
|
|
/** The underlying BehaviorSubject that emits whenever an input modality is detected. */
|
|
_modality = new BehaviorSubject(null);
|
|
/** Options for this InputModalityDetector. */
|
|
_options;
|
|
/**
|
|
* The timestamp of the last touch input modality. Used to determine whether mousedown events
|
|
* should be attributed to mouse or touch.
|
|
*/
|
|
_lastTouchMs = 0;
|
|
/**
|
|
* Handles keydown events. Must be an arrow function in order to preserve the context when it gets
|
|
* bound.
|
|
*/
|
|
_onKeydown = (event) => {
|
|
if (this._options?.ignoreKeys?.some((keyCode) => keyCode === event.keyCode)) {
|
|
return;
|
|
}
|
|
this._modality.next("keyboard");
|
|
this._mostRecentTarget = _getEventTarget(event);
|
|
};
|
|
/**
|
|
* Handles mousedown events. Must be an arrow function in order to preserve the context when it
|
|
* gets bound.
|
|
*/
|
|
_onMousedown = (event) => {
|
|
if (Date.now() - this._lastTouchMs < TOUCH_BUFFER_MS) {
|
|
return;
|
|
}
|
|
this._modality.next(isFakeMousedownFromScreenReader(event) ? "keyboard" : "mouse");
|
|
this._mostRecentTarget = _getEventTarget(event);
|
|
};
|
|
/**
|
|
* Handles touchstart events. Must be an arrow function in order to preserve the context when it
|
|
* gets bound.
|
|
*/
|
|
_onTouchstart = (event) => {
|
|
if (isFakeTouchstartFromScreenReader(event)) {
|
|
this._modality.next("keyboard");
|
|
return;
|
|
}
|
|
this._lastTouchMs = Date.now();
|
|
this._modality.next("touch");
|
|
this._mostRecentTarget = _getEventTarget(event);
|
|
};
|
|
constructor() {
|
|
const ngZone = inject(NgZone);
|
|
const document2 = inject(DOCUMENT);
|
|
const options = inject(INPUT_MODALITY_DETECTOR_OPTIONS, {
|
|
optional: true
|
|
});
|
|
this._options = __spreadValues(__spreadValues({}, INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS), options);
|
|
this.modalityDetected = this._modality.pipe(skip(1));
|
|
this.modalityChanged = this.modalityDetected.pipe(distinctUntilChanged());
|
|
if (this._platform.isBrowser) {
|
|
const renderer = inject(RendererFactory2).createRenderer(null, null);
|
|
this._listenerCleanups = ngZone.runOutsideAngular(() => {
|
|
return [renderer.listen(document2, "keydown", this._onKeydown, modalityEventListenerOptions), renderer.listen(document2, "mousedown", this._onMousedown, modalityEventListenerOptions), renderer.listen(document2, "touchstart", this._onTouchstart, modalityEventListenerOptions)];
|
|
});
|
|
}
|
|
}
|
|
ngOnDestroy() {
|
|
this._modality.complete();
|
|
this._listenerCleanups?.forEach((cleanup) => cleanup());
|
|
}
|
|
static ɵfac = function InputModalityDetector_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _InputModalityDetector)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _InputModalityDetector,
|
|
factory: _InputModalityDetector.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(InputModalityDetector, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
var FocusMonitorDetectionMode;
|
|
(function(FocusMonitorDetectionMode2) {
|
|
FocusMonitorDetectionMode2[FocusMonitorDetectionMode2["IMMEDIATE"] = 0] = "IMMEDIATE";
|
|
FocusMonitorDetectionMode2[FocusMonitorDetectionMode2["EVENTUAL"] = 1] = "EVENTUAL";
|
|
})(FocusMonitorDetectionMode || (FocusMonitorDetectionMode = {}));
|
|
var FOCUS_MONITOR_DEFAULT_OPTIONS = new InjectionToken("cdk-focus-monitor-default-options");
|
|
var captureEventListenerOptions = normalizePassiveListenerOptions({
|
|
passive: true,
|
|
capture: true
|
|
});
|
|
var FocusMonitor = class _FocusMonitor {
|
|
_ngZone = inject(NgZone);
|
|
_platform = inject(Platform);
|
|
_inputModalityDetector = inject(InputModalityDetector);
|
|
/** The focus origin that the next focus event is a result of. */
|
|
_origin = null;
|
|
/** The FocusOrigin of the last focus event tracked by the FocusMonitor. */
|
|
_lastFocusOrigin;
|
|
/** Whether the window has just been focused. */
|
|
_windowFocused = false;
|
|
/** The timeout id of the window focus timeout. */
|
|
_windowFocusTimeoutId;
|
|
/** The timeout id of the origin clearing timeout. */
|
|
_originTimeoutId;
|
|
/**
|
|
* Whether the origin was determined via a touch interaction. Necessary as properly attributing
|
|
* focus events to touch interactions requires special logic.
|
|
*/
|
|
_originFromTouchInteraction = false;
|
|
/** Map of elements being monitored to their info. */
|
|
_elementInfo = /* @__PURE__ */ new Map();
|
|
/** The number of elements currently being monitored. */
|
|
_monitoredElementCount = 0;
|
|
/**
|
|
* Keeps track of the root nodes to which we've currently bound a focus/blur handler,
|
|
* as well as the number of monitored elements that they contain. We have to treat focus/blur
|
|
* handlers differently from the rest of the events, because the browser won't emit events
|
|
* to the document when focus moves inside of a shadow root.
|
|
*/
|
|
_rootNodeFocusListenerCount = /* @__PURE__ */ new Map();
|
|
/**
|
|
* The specified detection mode, used for attributing the origin of a focus
|
|
* event.
|
|
*/
|
|
_detectionMode;
|
|
/**
|
|
* Event listener for `focus` events on the window.
|
|
* Needs to be an arrow function in order to preserve the context when it gets bound.
|
|
*/
|
|
_windowFocusListener = () => {
|
|
this._windowFocused = true;
|
|
this._windowFocusTimeoutId = setTimeout(() => this._windowFocused = false);
|
|
};
|
|
/** Used to reference correct document/window */
|
|
_document = inject(DOCUMENT);
|
|
/** Subject for stopping our InputModalityDetector subscription. */
|
|
_stopInputModalityDetector = new Subject();
|
|
constructor() {
|
|
const options = inject(FOCUS_MONITOR_DEFAULT_OPTIONS, {
|
|
optional: true
|
|
});
|
|
this._detectionMode = options?.detectionMode || FocusMonitorDetectionMode.IMMEDIATE;
|
|
}
|
|
/**
|
|
* Event listener for `focus` and 'blur' events on the document.
|
|
* Needs to be an arrow function in order to preserve the context when it gets bound.
|
|
*/
|
|
_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);
|
|
}
|
|
}
|
|
};
|
|
monitor(element, checkChildren = false) {
|
|
const nativeElement = coerceElement(element);
|
|
if (!this._platform.isBrowser || nativeElement.nodeType !== 1) {
|
|
return of();
|
|
}
|
|
const rootNode = _getShadowRoot(nativeElement) || this._document;
|
|
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._document.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));
|
|
}
|
|
/** Use defaultView of injected document if available or fallback to global window reference */
|
|
_getWindow() {
|
|
return this._document.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 === FocusMonitorDetectionMode.EVENTUAL || !!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 === FocusMonitorDetectionMode.IMMEDIATE) {
|
|
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;
|
|
}
|
|
static ɵfac = function FocusMonitor_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _FocusMonitor)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _FocusMonitor,
|
|
factory: _FocusMonitor.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(FocusMonitor, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
var CdkMonitorFocus = class _CdkMonitorFocus {
|
|
_elementRef = inject(ElementRef);
|
|
_focusMonitor = inject(FocusMonitor);
|
|
_monitorSubscription;
|
|
_focusOrigin = null;
|
|
cdkFocusChange = new EventEmitter();
|
|
constructor() {
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
static ɵfac = function CdkMonitorFocus_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _CdkMonitorFocus)();
|
|
};
|
|
static ɵdir = ɵɵdefineDirective({
|
|
type: _CdkMonitorFocus,
|
|
selectors: [["", "cdkMonitorElementFocus", ""], ["", "cdkMonitorSubtreeFocus", ""]],
|
|
outputs: {
|
|
cdkFocusChange: "cdkFocusChange"
|
|
},
|
|
exportAs: ["cdkMonitorFocus"]
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkMonitorFocus, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]",
|
|
exportAs: "cdkMonitorFocus"
|
|
}]
|
|
}], () => [], {
|
|
cdkFocusChange: [{
|
|
type: Output
|
|
}]
|
|
});
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/style-loader.mjs
|
|
var appsWithLoaders = /* @__PURE__ */ new WeakMap();
|
|
var _CdkPrivateStyleLoader = class __CdkPrivateStyleLoader {
|
|
_appRef;
|
|
_injector = inject(Injector);
|
|
_environmentInjector = inject(EnvironmentInjector);
|
|
/**
|
|
* Loads a set of styles.
|
|
* @param loader Component which will be instantiated to load the styles.
|
|
*/
|
|
load(loader) {
|
|
const appRef = this._appRef = this._appRef || this._injector.get(ApplicationRef);
|
|
let data = appsWithLoaders.get(appRef);
|
|
if (!data) {
|
|
data = {
|
|
loaders: /* @__PURE__ */ new Set(),
|
|
refs: []
|
|
};
|
|
appsWithLoaders.set(appRef, data);
|
|
appRef.onDestroy(() => {
|
|
appsWithLoaders.get(appRef)?.refs.forEach((ref) => ref.destroy());
|
|
appsWithLoaders.delete(appRef);
|
|
});
|
|
}
|
|
if (!data.loaders.has(loader)) {
|
|
data.loaders.add(loader);
|
|
data.refs.push(createComponent(loader, {
|
|
environmentInjector: this._environmentInjector
|
|
}));
|
|
}
|
|
}
|
|
static ɵfac = function _CdkPrivateStyleLoader_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || __CdkPrivateStyleLoader)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: __CdkPrivateStyleLoader,
|
|
factory: __CdkPrivateStyleLoader.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(_CdkPrivateStyleLoader, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/private.mjs
|
|
var _VisuallyHiddenLoader = class __VisuallyHiddenLoader {
|
|
static ɵfac = function _VisuallyHiddenLoader_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || __VisuallyHiddenLoader)();
|
|
};
|
|
static ɵcmp = ɵɵdefineComponent({
|
|
type: __VisuallyHiddenLoader,
|
|
selectors: [["ng-component"]],
|
|
exportAs: ["cdkVisuallyHidden"],
|
|
decls: 0,
|
|
vars: 0,
|
|
template: function _VisuallyHiddenLoader_Template(rf, ctx) {
|
|
},
|
|
styles: [".cdk-visually-hidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap;outline:0;-webkit-appearance:none;-moz-appearance:none;left:0}[dir=rtl] .cdk-visually-hidden{left:auto;right:0}\n"],
|
|
encapsulation: 2,
|
|
changeDetection: 0
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(_VisuallyHiddenLoader, [{
|
|
type: Component,
|
|
args: [{
|
|
exportAs: "cdkVisuallyHidden",
|
|
encapsulation: ViewEncapsulation.None,
|
|
template: "",
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
styles: [".cdk-visually-hidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap;outline:0;-webkit-appearance:none;-moz-appearance:none;left:0}[dir=rtl] .cdk-visually-hidden{left:auto;right:0}\n"]
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/array.mjs
|
|
function coerceArray(value) {
|
|
return Array.isArray(value) ? value : [value];
|
|
}
|
|
|
|
// node_modules/@angular/cdk/fesm2022/breakpoints-observer.mjs
|
|
var mediaQueriesForWebkitCompatibility = /* @__PURE__ */ new Set();
|
|
var mediaQueryStyleNode;
|
|
var MediaMatcher = class _MediaMatcher {
|
|
_platform = inject(Platform);
|
|
_nonce = inject(CSP_NONCE, {
|
|
optional: true
|
|
});
|
|
/** The internal matchMedia method to return back a MediaQueryList like object. */
|
|
_matchMedia;
|
|
constructor() {
|
|
this._matchMedia = this._platform.isBrowser && window.matchMedia ? (
|
|
// matchMedia is bound to the window scope intentionally as it is an illegal invocation to
|
|
// call it from a different scope.
|
|
window.matchMedia.bind(window)
|
|
) : noopMatchMedia;
|
|
}
|
|
/**
|
|
* Evaluates the given media query and returns the native MediaQueryList from which results
|
|
* can be retrieved.
|
|
* Confirms the layout engine will trigger for the selector query provided and returns the
|
|
* MediaQueryList for the query provided.
|
|
*/
|
|
matchMedia(query) {
|
|
if (this._platform.WEBKIT || this._platform.BLINK) {
|
|
createEmptyStyleRule(query, this._nonce);
|
|
}
|
|
return this._matchMedia(query);
|
|
}
|
|
static ɵfac = function MediaMatcher_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _MediaMatcher)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _MediaMatcher,
|
|
factory: _MediaMatcher.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(MediaMatcher, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
function createEmptyStyleRule(query, nonce) {
|
|
if (mediaQueriesForWebkitCompatibility.has(query)) {
|
|
return;
|
|
}
|
|
try {
|
|
if (!mediaQueryStyleNode) {
|
|
mediaQueryStyleNode = document.createElement("style");
|
|
if (nonce) {
|
|
mediaQueryStyleNode.setAttribute("nonce", nonce);
|
|
}
|
|
mediaQueryStyleNode.setAttribute("type", "text/css");
|
|
document.head.appendChild(mediaQueryStyleNode);
|
|
}
|
|
if (mediaQueryStyleNode.sheet) {
|
|
mediaQueryStyleNode.sheet.insertRule(`@media ${query} {body{ }}`, 0);
|
|
mediaQueriesForWebkitCompatibility.add(query);
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
function noopMatchMedia(query) {
|
|
return {
|
|
matches: query === "all" || query === "",
|
|
media: query,
|
|
addListener: () => {
|
|
},
|
|
removeListener: () => {
|
|
}
|
|
};
|
|
}
|
|
var BreakpointObserver = class _BreakpointObserver {
|
|
_mediaMatcher = inject(MediaMatcher);
|
|
_zone = inject(NgZone);
|
|
/** A map of all media queries currently being listened for. */
|
|
_queries = /* @__PURE__ */ new Map();
|
|
/** A subject for all other observables to takeUntil based on. */
|
|
_destroySubject = new Subject();
|
|
constructor() {
|
|
}
|
|
/** Completes the active subject, signalling to all other observables to complete. */
|
|
ngOnDestroy() {
|
|
this._destroySubject.next();
|
|
this._destroySubject.complete();
|
|
}
|
|
/**
|
|
* Whether one or more media queries match the current viewport size.
|
|
* @param value One or more media queries to check.
|
|
* @returns Whether any of the media queries match.
|
|
*/
|
|
isMatched(value) {
|
|
const queries = splitQueries(coerceArray(value));
|
|
return queries.some((mediaQuery) => this._registerQuery(mediaQuery).mql.matches);
|
|
}
|
|
/**
|
|
* Gets an observable of results for the given queries that will emit new results for any changes
|
|
* in matching of the given queries.
|
|
* @param value One or more media queries to check.
|
|
* @returns A stream of matches for the given queries.
|
|
*/
|
|
observe(value) {
|
|
const queries = splitQueries(coerceArray(value));
|
|
const observables = queries.map((query) => this._registerQuery(query).observable);
|
|
let stateObservable = combineLatest(observables);
|
|
stateObservable = concat(stateObservable.pipe(take(1)), stateObservable.pipe(skip(1), debounceTime(0)));
|
|
return stateObservable.pipe(map((breakpointStates) => {
|
|
const response = {
|
|
matches: false,
|
|
breakpoints: {}
|
|
};
|
|
breakpointStates.forEach(({
|
|
matches,
|
|
query
|
|
}) => {
|
|
response.matches = response.matches || matches;
|
|
response.breakpoints[query] = matches;
|
|
});
|
|
return response;
|
|
}));
|
|
}
|
|
/** Registers a specific query to be listened for. */
|
|
_registerQuery(query) {
|
|
if (this._queries.has(query)) {
|
|
return this._queries.get(query);
|
|
}
|
|
const mql = this._mediaMatcher.matchMedia(query);
|
|
const queryObservable = new Observable((observer) => {
|
|
const handler = (e) => this._zone.run(() => observer.next(e));
|
|
mql.addListener(handler);
|
|
return () => {
|
|
mql.removeListener(handler);
|
|
};
|
|
}).pipe(startWith(mql), map(({
|
|
matches
|
|
}) => ({
|
|
query,
|
|
matches
|
|
})), takeUntil(this._destroySubject));
|
|
const output = {
|
|
observable: queryObservable,
|
|
mql
|
|
};
|
|
this._queries.set(query, output);
|
|
return output;
|
|
}
|
|
static ɵfac = function BreakpointObserver_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _BreakpointObserver)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _BreakpointObserver,
|
|
factory: _BreakpointObserver.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(BreakpointObserver, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
function splitQueries(queries) {
|
|
return queries.map((query) => query.split(",")).reduce((a1, a2) => a1.concat(a2)).map((query) => query.trim());
|
|
}
|
|
|
|
// node_modules/@angular/cdk/fesm2022/observers.mjs
|
|
function shouldIgnoreRecord(record) {
|
|
if (record.type === "characterData" && record.target instanceof Comment) {
|
|
return true;
|
|
}
|
|
if (record.type === "childList") {
|
|
for (let i = 0; i < record.addedNodes.length; i++) {
|
|
if (!(record.addedNodes[i] instanceof Comment)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (let i = 0; i < record.removedNodes.length; i++) {
|
|
if (!(record.removedNodes[i] instanceof Comment)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
var MutationObserverFactory = class _MutationObserverFactory {
|
|
create(callback) {
|
|
return typeof MutationObserver === "undefined" ? null : new MutationObserver(callback);
|
|
}
|
|
static ɵfac = function MutationObserverFactory_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _MutationObserverFactory)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _MutationObserverFactory,
|
|
factory: _MutationObserverFactory.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(MutationObserverFactory, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
var ContentObserver = class _ContentObserver {
|
|
_mutationObserverFactory = inject(MutationObserverFactory);
|
|
/** Keeps track of the existing MutationObservers so they can be reused. */
|
|
_observedElements = /* @__PURE__ */ new Map();
|
|
_ngZone = inject(NgZone);
|
|
constructor() {
|
|
}
|
|
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.pipe(map((records) => records.filter((record) => !shouldIgnoreRecord(record))), filter((records) => !!records.length)).subscribe((records) => {
|
|
this._ngZone.run(() => {
|
|
observer.next(records);
|
|
});
|
|
});
|
|
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) {
|
|
return this._ngZone.runOutsideAngular(() => {
|
|
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);
|
|
}
|
|
}
|
|
static ɵfac = function ContentObserver_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _ContentObserver)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _ContentObserver,
|
|
factory: _ContentObserver.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ContentObserver, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
var CdkObserveContent = class _CdkObserveContent {
|
|
_contentObserver = inject(ContentObserver);
|
|
_elementRef = inject(ElementRef);
|
|
/** Event emitted for each change in the element's content. */
|
|
event = new EventEmitter();
|
|
/**
|
|
* 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();
|
|
}
|
|
_disabled = false;
|
|
/** Debounce interval for emitting the changes. */
|
|
get debounce() {
|
|
return this._debounce;
|
|
}
|
|
set debounce(value) {
|
|
this._debounce = coerceNumberProperty(value);
|
|
this._subscribe();
|
|
}
|
|
_debounce;
|
|
_currentSubscription = null;
|
|
constructor() {
|
|
}
|
|
ngAfterContentInit() {
|
|
if (!this._currentSubscription && !this.disabled) {
|
|
this._subscribe();
|
|
}
|
|
}
|
|
ngOnDestroy() {
|
|
this._unsubscribe();
|
|
}
|
|
_subscribe() {
|
|
this._unsubscribe();
|
|
const stream = this._contentObserver.observe(this._elementRef);
|
|
this._currentSubscription = (this.debounce ? stream.pipe(debounceTime(this.debounce)) : stream).subscribe(this.event);
|
|
}
|
|
_unsubscribe() {
|
|
this._currentSubscription?.unsubscribe();
|
|
}
|
|
static ɵfac = function CdkObserveContent_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _CdkObserveContent)();
|
|
};
|
|
static ɵdir = ɵɵdefineDirective({
|
|
type: _CdkObserveContent,
|
|
selectors: [["", "cdkObserveContent", ""]],
|
|
inputs: {
|
|
disabled: [2, "cdkObserveContentDisabled", "disabled", booleanAttribute],
|
|
debounce: "debounce"
|
|
},
|
|
outputs: {
|
|
event: "cdkObserveContent"
|
|
},
|
|
exportAs: ["cdkObserveContent"]
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkObserveContent, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkObserveContent]",
|
|
exportAs: "cdkObserveContent"
|
|
}]
|
|
}], () => [], {
|
|
event: [{
|
|
type: Output,
|
|
args: ["cdkObserveContent"]
|
|
}],
|
|
disabled: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkObserveContentDisabled",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
debounce: [{
|
|
type: Input
|
|
}]
|
|
});
|
|
})();
|
|
var ObserversModule = class _ObserversModule {
|
|
static ɵfac = function ObserversModule_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _ObserversModule)();
|
|
};
|
|
static ɵmod = ɵɵdefineNgModule({
|
|
type: _ObserversModule,
|
|
imports: [CdkObserveContent],
|
|
exports: [CdkObserveContent]
|
|
});
|
|
static ɵinj = ɵɵdefineInjector({
|
|
providers: [MutationObserverFactory]
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ObserversModule, [{
|
|
type: NgModule,
|
|
args: [{
|
|
imports: [CdkObserveContent],
|
|
exports: [CdkObserveContent],
|
|
providers: [MutationObserverFactory]
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/a11y-module.mjs
|
|
var IsFocusableConfig = class {
|
|
/**
|
|
* Whether to count an element as focusable even if it is not currently visible.
|
|
*/
|
|
ignoreVisibility = false;
|
|
};
|
|
var InteractivityChecker = class _InteractivityChecker {
|
|
_platform = inject(Platform);
|
|
constructor() {
|
|
}
|
|
/**
|
|
* 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));
|
|
}
|
|
static ɵfac = function InteractivityChecker_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _InteractivityChecker)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _InteractivityChecker,
|
|
factory: _InteractivityChecker.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(InteractivityChecker, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], 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 {
|
|
_element;
|
|
_checker;
|
|
_ngZone;
|
|
_document;
|
|
_injector;
|
|
_startAnchor;
|
|
_endAnchor;
|
|
_hasAttached = false;
|
|
// Event listeners for the anchors. Need to be regular functions so that we can unbind them later.
|
|
startAnchorListener = () => this.focusLastTabbableElement();
|
|
endAnchorListener = () => this.focusFirstTabbableElement();
|
|
/** 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);
|
|
}
|
|
}
|
|
_enabled = true;
|
|
constructor(_element, _checker, _ngZone, _document, deferAnchors = false, _injector) {
|
|
this._element = _element;
|
|
this._checker = _checker;
|
|
this._ngZone = _ngZone;
|
|
this._document = _document;
|
|
this._injector = _injector;
|
|
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._injector) {
|
|
afterNextRender(fn, {
|
|
injector: this._injector
|
|
});
|
|
} else {
|
|
setTimeout(fn);
|
|
}
|
|
}
|
|
};
|
|
var FocusTrapFactory = class _FocusTrapFactory {
|
|
_checker = inject(InteractivityChecker);
|
|
_ngZone = inject(NgZone);
|
|
_document = inject(DOCUMENT);
|
|
_injector = inject(Injector);
|
|
constructor() {
|
|
inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader);
|
|
}
|
|
/**
|
|
* 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, this._injector);
|
|
}
|
|
static ɵfac = function FocusTrapFactory_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _FocusTrapFactory)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _FocusTrapFactory,
|
|
factory: _FocusTrapFactory.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(FocusTrapFactory, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
var CdkTrapFocus = class _CdkTrapFocus {
|
|
_elementRef = inject(ElementRef);
|
|
_focusTrapFactory = inject(FocusTrapFactory);
|
|
/** Underlying FocusTrap instance. */
|
|
focusTrap;
|
|
/** Previously focused element to restore focus to upon destroy when using autoCapture. */
|
|
_previouslyFocusedElement = null;
|
|
/** Whether the focus trap is active. */
|
|
get enabled() {
|
|
return this.focusTrap?.enabled || false;
|
|
}
|
|
set enabled(value) {
|
|
if (this.focusTrap) {
|
|
this.focusTrap.enabled = value;
|
|
}
|
|
}
|
|
/**
|
|
* Whether the directive should automatically move focus into the trapped region upon
|
|
* initialization and return focus to the previous activeElement upon destruction.
|
|
*/
|
|
autoCapture;
|
|
constructor() {
|
|
const platform = inject(Platform);
|
|
if (platform.isBrowser) {
|
|
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 && !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();
|
|
}
|
|
static ɵfac = function CdkTrapFocus_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _CdkTrapFocus)();
|
|
};
|
|
static ɵdir = ɵɵdefineDirective({
|
|
type: _CdkTrapFocus,
|
|
selectors: [["", "cdkTrapFocus", ""]],
|
|
inputs: {
|
|
enabled: [2, "cdkTrapFocus", "enabled", booleanAttribute],
|
|
autoCapture: [2, "cdkTrapFocusAutoCapture", "autoCapture", booleanAttribute]
|
|
},
|
|
exportAs: ["cdkTrapFocus"],
|
|
features: [ɵɵNgOnChangesFeature]
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkTrapFocus, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkTrapFocus]",
|
|
exportAs: "cdkTrapFocus"
|
|
}]
|
|
}], () => [], {
|
|
enabled: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkTrapFocus",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
autoCapture: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkTrapFocusAutoCapture",
|
|
transform: booleanAttribute
|
|
}]
|
|
}]
|
|
});
|
|
})();
|
|
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 {
|
|
_ngZone = inject(NgZone);
|
|
_defaultOptions = inject(LIVE_ANNOUNCER_DEFAULT_OPTIONS, {
|
|
optional: true
|
|
});
|
|
_liveElement;
|
|
_document = inject(DOCUMENT);
|
|
_previousTimeout;
|
|
_currentPromise;
|
|
_currentResolve;
|
|
constructor() {
|
|
const elementToken = inject(LIVE_ANNOUNCER_ELEMENT_TOKEN, {
|
|
optional: true
|
|
});
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
static ɵfac = function LiveAnnouncer_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _LiveAnnouncer)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _LiveAnnouncer,
|
|
factory: _LiveAnnouncer.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(LiveAnnouncer, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
var CdkAriaLive = class _CdkAriaLive {
|
|
_elementRef = inject(ElementRef);
|
|
_liveAnnouncer = inject(LiveAnnouncer);
|
|
_contentObserver = inject(ContentObserver);
|
|
_ngZone = inject(NgZone);
|
|
/** 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;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
_politeness = "polite";
|
|
/** Time in milliseconds after which to clear out the announcer element. */
|
|
duration;
|
|
_previousAnnouncedText;
|
|
_subscription;
|
|
constructor() {
|
|
inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader);
|
|
}
|
|
ngOnDestroy() {
|
|
if (this._subscription) {
|
|
this._subscription.unsubscribe();
|
|
}
|
|
}
|
|
static ɵfac = function CdkAriaLive_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _CdkAriaLive)();
|
|
};
|
|
static ɵdir = ɵɵdefineDirective({
|
|
type: _CdkAriaLive,
|
|
selectors: [["", "cdkAriaLive", ""]],
|
|
inputs: {
|
|
politeness: [0, "cdkAriaLive", "politeness"],
|
|
duration: [0, "cdkAriaLiveDuration", "duration"]
|
|
},
|
|
exportAs: ["cdkAriaLive"]
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkAriaLive, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkAriaLive]",
|
|
exportAs: "cdkAriaLive"
|
|
}]
|
|
}], () => [], {
|
|
politeness: [{
|
|
type: Input,
|
|
args: ["cdkAriaLive"]
|
|
}],
|
|
duration: [{
|
|
type: Input,
|
|
args: ["cdkAriaLiveDuration"]
|
|
}]
|
|
});
|
|
})();
|
|
var HighContrastMode;
|
|
(function(HighContrastMode2) {
|
|
HighContrastMode2[HighContrastMode2["NONE"] = 0] = "NONE";
|
|
HighContrastMode2[HighContrastMode2["BLACK_ON_WHITE"] = 1] = "BLACK_ON_WHITE";
|
|
HighContrastMode2[HighContrastMode2["WHITE_ON_BLACK"] = 2] = "WHITE_ON_BLACK";
|
|
})(HighContrastMode || (HighContrastMode = {}));
|
|
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 {
|
|
_platform = inject(Platform);
|
|
/**
|
|
* Figuring out the high contrast mode and adding the body classes can cause
|
|
* some expensive layouts. This flag is used to ensure that we only do it once.
|
|
*/
|
|
_hasCheckedHighContrastMode;
|
|
_document = inject(DOCUMENT);
|
|
_breakpointSubscription;
|
|
constructor() {
|
|
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 HighContrastMode.NONE;
|
|
}
|
|
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) {
|
|
// Pre Windows 11 dark theme.
|
|
case "rgb(0,0,0)":
|
|
// Windows 11 dark themes.
|
|
case "rgb(45,50,54)":
|
|
case "rgb(32,32,32)":
|
|
return HighContrastMode.WHITE_ON_BLACK;
|
|
// Pre Windows 11 light theme.
|
|
case "rgb(255,255,255)":
|
|
// Windows 11 light theme.
|
|
case "rgb(255,250,239)":
|
|
return HighContrastMode.BLACK_ON_WHITE;
|
|
}
|
|
return HighContrastMode.NONE;
|
|
}
|
|
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 === HighContrastMode.BLACK_ON_WHITE) {
|
|
bodyClasses.add(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS, BLACK_ON_WHITE_CSS_CLASS);
|
|
} else if (mode === HighContrastMode.WHITE_ON_BLACK) {
|
|
bodyClasses.add(HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS, WHITE_ON_BLACK_CSS_CLASS);
|
|
}
|
|
}
|
|
}
|
|
static ɵfac = function HighContrastModeDetector_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _HighContrastModeDetector)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _HighContrastModeDetector,
|
|
factory: _HighContrastModeDetector.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(HighContrastModeDetector, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
var A11yModule = class _A11yModule {
|
|
constructor() {
|
|
inject(HighContrastModeDetector)._applyBodyHighContrastModeCssClasses();
|
|
}
|
|
static ɵfac = function A11yModule_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _A11yModule)();
|
|
};
|
|
static ɵmod = ɵɵdefineNgModule({
|
|
type: _A11yModule,
|
|
imports: [ObserversModule, CdkAriaLive, CdkTrapFocus, CdkMonitorFocus],
|
|
exports: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus]
|
|
});
|
|
static ɵinj = ɵɵdefineInjector({
|
|
imports: [ObserversModule]
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(A11yModule, [{
|
|
type: NgModule,
|
|
args: [{
|
|
imports: [ObserversModule, CdkAriaLive, CdkTrapFocus, CdkMonitorFocus],
|
|
exports: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus]
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/id-generator.mjs
|
|
var counters = {};
|
|
var _IdGenerator = class __IdGenerator {
|
|
_appId = inject(APP_ID);
|
|
/**
|
|
* Generates a unique ID with a specific prefix.
|
|
* @param prefix Prefix to add to the ID.
|
|
*/
|
|
getId(prefix) {
|
|
if (this._appId !== "ng") {
|
|
prefix += this._appId;
|
|
}
|
|
if (!counters.hasOwnProperty(prefix)) {
|
|
counters[prefix] = 0;
|
|
}
|
|
return `${prefix}${counters[prefix]++}`;
|
|
}
|
|
static ɵfac = function _IdGenerator_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || __IdGenerator)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: __IdGenerator,
|
|
factory: __IdGenerator.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(_IdGenerator, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/typeahead.mjs
|
|
var DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL_MS = 200;
|
|
var Typeahead = class {
|
|
_letterKeyStream = new Subject();
|
|
_items = [];
|
|
_selectedItemIndex = -1;
|
|
/** Buffer for the letters that the user has pressed */
|
|
_pressedLetters = [];
|
|
_skipPredicateFn;
|
|
_selectedItem = new Subject();
|
|
selectedItem = this._selectedItem;
|
|
constructor(initialItems, config) {
|
|
const typeAheadInterval = typeof config?.debounceInterval === "number" ? config.debounceInterval : DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL_MS;
|
|
if (config?.skipPredicate) {
|
|
this._skipPredicateFn = config.skipPredicate;
|
|
}
|
|
if ((typeof ngDevMode === "undefined" || ngDevMode) && initialItems.length && initialItems.some((item) => typeof item.getLabel !== "function")) {
|
|
throw new Error("KeyManager items in typeahead mode must implement the `getLabel` method.");
|
|
}
|
|
this.setItems(initialItems);
|
|
this._setupKeyHandler(typeAheadInterval);
|
|
}
|
|
destroy() {
|
|
this._pressedLetters = [];
|
|
this._letterKeyStream.complete();
|
|
this._selectedItem.complete();
|
|
}
|
|
setCurrentSelectedItemIndex(index) {
|
|
this._selectedItemIndex = index;
|
|
}
|
|
setItems(items) {
|
|
this._items = items;
|
|
}
|
|
handleKey(event) {
|
|
const keyCode = event.keyCode;
|
|
if (event.key && event.key.length === 1) {
|
|
this._letterKeyStream.next(event.key.toLocaleUpperCase());
|
|
} else if (keyCode >= A && keyCode <= Z || keyCode >= ZERO && keyCode <= NINE) {
|
|
this._letterKeyStream.next(String.fromCharCode(keyCode));
|
|
}
|
|
}
|
|
/** Gets whether the user is currently typing into the manager using the typeahead feature. */
|
|
isTyping() {
|
|
return this._pressedLetters.length > 0;
|
|
}
|
|
/** Resets the currently stored sequence of typed letters. */
|
|
reset() {
|
|
this._pressedLetters = [];
|
|
}
|
|
_setupKeyHandler(typeAheadInterval) {
|
|
this._letterKeyStream.pipe(tap((letter) => this._pressedLetters.push(letter)), debounceTime(typeAheadInterval), filter(() => this._pressedLetters.length > 0), map(() => this._pressedLetters.join("").toLocaleUpperCase())).subscribe((inputString) => {
|
|
for (let i = 1; i < this._items.length + 1; i++) {
|
|
const index = (this._selectedItemIndex + i) % this._items.length;
|
|
const item = this._items[index];
|
|
if (!this._skipPredicateFn?.(item) && item.getLabel?.().toLocaleUpperCase().trim().indexOf(inputString) === 0) {
|
|
this._selectedItem.next(item);
|
|
break;
|
|
}
|
|
}
|
|
this._pressedLetters = [];
|
|
});
|
|
}
|
|
};
|
|
|
|
// node_modules/@angular/cdk/fesm2022/keycodes.mjs
|
|
function hasModifierKey(event, ...modifiers) {
|
|
if (modifiers.length) {
|
|
return modifiers.some((modifier) => event[modifier]);
|
|
}
|
|
return event.altKey || event.shiftKey || event.ctrlKey || event.metaKey;
|
|
}
|
|
|
|
// node_modules/@angular/cdk/fesm2022/list-key-manager.mjs
|
|
var ListKeyManager = class {
|
|
_items;
|
|
_activeItemIndex = signal(-1, ...ngDevMode ? [{ debugName: "_activeItemIndex" }] : []);
|
|
_activeItem = signal(null, ...ngDevMode ? [{ debugName: "_activeItem" }] : []);
|
|
_wrap = false;
|
|
_typeaheadSubscription = Subscription.EMPTY;
|
|
_itemChangesSubscription;
|
|
_vertical = true;
|
|
_horizontal;
|
|
_allowedModifierKeys = [];
|
|
_homeAndEnd = false;
|
|
_pageUpAndDown = { enabled: false, delta: 10 };
|
|
_effectRef;
|
|
_typeahead;
|
|
/**
|
|
* Predicate function that can be used to check whether an item should be skipped
|
|
* by the key manager. By default, disabled items are skipped.
|
|
*/
|
|
_skipPredicateFn = (item) => item.disabled;
|
|
constructor(_items, injector) {
|
|
this._items = _items;
|
|
if (_items instanceof QueryList) {
|
|
this._itemChangesSubscription = _items.changes.subscribe((newItems) => this._itemsChanged(newItems.toArray()));
|
|
} else if (isSignal(_items)) {
|
|
if (!injector && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw new Error("ListKeyManager constructed with a signal must receive an injector");
|
|
}
|
|
this._effectRef = effect(() => this._itemsChanged(_items()), ...ngDevMode ? [{ debugName: "_effectRef", injector }] : [{ injector }]);
|
|
}
|
|
}
|
|
/**
|
|
* Stream that emits any time the TAB key is pressed, so components can react
|
|
* when focus is shifted off of the list.
|
|
*/
|
|
tabOut = new Subject();
|
|
/** Stream that emits whenever the active item of the list manager changes. */
|
|
change = new Subject();
|
|
/**
|
|
* Sets the predicate function that determines which items should be skipped by the
|
|
* list key manager.
|
|
* @param predicate Function that determines whether the given item should be skipped.
|
|
*/
|
|
skipPredicate(predicate) {
|
|
this._skipPredicateFn = predicate;
|
|
return this;
|
|
}
|
|
/**
|
|
* Configures wrapping mode, which determines whether the active item will wrap to
|
|
* the other end of list when there are no more items in the given direction.
|
|
* @param shouldWrap Whether the list should wrap when reaching the end.
|
|
*/
|
|
withWrap(shouldWrap = true) {
|
|
this._wrap = shouldWrap;
|
|
return this;
|
|
}
|
|
/**
|
|
* Configures whether the key manager should be able to move the selection vertically.
|
|
* @param enabled Whether vertical selection should be enabled.
|
|
*/
|
|
withVerticalOrientation(enabled = true) {
|
|
this._vertical = enabled;
|
|
return this;
|
|
}
|
|
/**
|
|
* Configures the key manager to move the selection horizontally.
|
|
* Passing in `null` will disable horizontal movement.
|
|
* @param direction Direction in which the selection can be moved.
|
|
*/
|
|
withHorizontalOrientation(direction) {
|
|
this._horizontal = direction;
|
|
return this;
|
|
}
|
|
/**
|
|
* Modifier keys which are allowed to be held down and whose default actions will be prevented
|
|
* as the user is pressing the arrow keys. Defaults to not allowing any modifier keys.
|
|
*/
|
|
withAllowedModifierKeys(keys) {
|
|
this._allowedModifierKeys = keys;
|
|
return this;
|
|
}
|
|
/**
|
|
* Turns on typeahead mode which allows users to set the active item by typing.
|
|
* @param debounceInterval Time to wait after the last keystroke before setting the active item.
|
|
*/
|
|
withTypeAhead(debounceInterval = 200) {
|
|
if (typeof ngDevMode === "undefined" || ngDevMode) {
|
|
const items2 = this._getItemsArray();
|
|
if (items2.length > 0 && items2.some((item) => typeof item.getLabel !== "function")) {
|
|
throw Error("ListKeyManager items in typeahead mode must implement the `getLabel` method.");
|
|
}
|
|
}
|
|
this._typeaheadSubscription.unsubscribe();
|
|
const items = this._getItemsArray();
|
|
this._typeahead = new Typeahead(items, {
|
|
debounceInterval: typeof debounceInterval === "number" ? debounceInterval : void 0,
|
|
skipPredicate: (item) => this._skipPredicateFn(item)
|
|
});
|
|
this._typeaheadSubscription = this._typeahead.selectedItem.subscribe((item) => {
|
|
this.setActiveItem(item);
|
|
});
|
|
return this;
|
|
}
|
|
/** Cancels the current typeahead sequence. */
|
|
cancelTypeahead() {
|
|
this._typeahead?.reset();
|
|
return this;
|
|
}
|
|
/**
|
|
* Configures the key manager to activate the first and last items
|
|
* respectively when the Home or End key is pressed.
|
|
* @param enabled Whether pressing the Home or End key activates the first/last item.
|
|
*/
|
|
withHomeAndEnd(enabled = true) {
|
|
this._homeAndEnd = enabled;
|
|
return this;
|
|
}
|
|
/**
|
|
* Configures the key manager to activate every 10th, configured or first/last element in up/down direction
|
|
* respectively when the Page-Up or Page-Down key is pressed.
|
|
* @param enabled Whether pressing the Page-Up or Page-Down key activates the first/last item.
|
|
* @param delta Whether pressing the Home or End key activates the first/last item.
|
|
*/
|
|
withPageUpDown(enabled = true, delta = 10) {
|
|
this._pageUpAndDown = { enabled, delta };
|
|
return this;
|
|
}
|
|
setActiveItem(item) {
|
|
const previousActiveItem = this._activeItem();
|
|
this.updateActiveItem(item);
|
|
if (this._activeItem() !== previousActiveItem) {
|
|
this.change.next(this._activeItemIndex());
|
|
}
|
|
}
|
|
/**
|
|
* Sets the active item depending on the key event passed in.
|
|
* @param event Keyboard event to be used for determining which element should be active.
|
|
*/
|
|
onKeydown(event) {
|
|
const keyCode = event.keyCode;
|
|
const modifiers = ["altKey", "ctrlKey", "metaKey", "shiftKey"];
|
|
const isModifierAllowed = modifiers.every((modifier) => {
|
|
return !event[modifier] || this._allowedModifierKeys.indexOf(modifier) > -1;
|
|
});
|
|
switch (keyCode) {
|
|
case TAB:
|
|
this.tabOut.next();
|
|
return;
|
|
case DOWN_ARROW:
|
|
if (this._vertical && isModifierAllowed) {
|
|
this.setNextItemActive();
|
|
break;
|
|
} else {
|
|
return;
|
|
}
|
|
case UP_ARROW:
|
|
if (this._vertical && isModifierAllowed) {
|
|
this.setPreviousItemActive();
|
|
break;
|
|
} else {
|
|
return;
|
|
}
|
|
case RIGHT_ARROW:
|
|
if (this._horizontal && isModifierAllowed) {
|
|
this._horizontal === "rtl" ? this.setPreviousItemActive() : this.setNextItemActive();
|
|
break;
|
|
} else {
|
|
return;
|
|
}
|
|
case LEFT_ARROW:
|
|
if (this._horizontal && isModifierAllowed) {
|
|
this._horizontal === "rtl" ? this.setNextItemActive() : this.setPreviousItemActive();
|
|
break;
|
|
} else {
|
|
return;
|
|
}
|
|
case HOME:
|
|
if (this._homeAndEnd && isModifierAllowed) {
|
|
this.setFirstItemActive();
|
|
break;
|
|
} else {
|
|
return;
|
|
}
|
|
case END:
|
|
if (this._homeAndEnd && isModifierAllowed) {
|
|
this.setLastItemActive();
|
|
break;
|
|
} else {
|
|
return;
|
|
}
|
|
case PAGE_UP:
|
|
if (this._pageUpAndDown.enabled && isModifierAllowed) {
|
|
const targetIndex = this._activeItemIndex() - this._pageUpAndDown.delta;
|
|
this._setActiveItemByIndex(targetIndex > 0 ? targetIndex : 0, 1);
|
|
break;
|
|
} else {
|
|
return;
|
|
}
|
|
case PAGE_DOWN:
|
|
if (this._pageUpAndDown.enabled && isModifierAllowed) {
|
|
const targetIndex = this._activeItemIndex() + this._pageUpAndDown.delta;
|
|
const itemsLength = this._getItemsArray().length;
|
|
this._setActiveItemByIndex(targetIndex < itemsLength ? targetIndex : itemsLength - 1, -1);
|
|
break;
|
|
} else {
|
|
return;
|
|
}
|
|
default:
|
|
if (isModifierAllowed || hasModifierKey(event, "shiftKey")) {
|
|
this._typeahead?.handleKey(event);
|
|
}
|
|
return;
|
|
}
|
|
this._typeahead?.reset();
|
|
event.preventDefault();
|
|
}
|
|
/** Index of the currently active item. */
|
|
get activeItemIndex() {
|
|
return this._activeItemIndex();
|
|
}
|
|
/** The active item. */
|
|
get activeItem() {
|
|
return this._activeItem();
|
|
}
|
|
/** Gets whether the user is currently typing into the manager using the typeahead feature. */
|
|
isTyping() {
|
|
return !!this._typeahead && this._typeahead.isTyping();
|
|
}
|
|
/** Sets the active item to the first enabled item in the list. */
|
|
setFirstItemActive() {
|
|
this._setActiveItemByIndex(0, 1);
|
|
}
|
|
/** Sets the active item to the last enabled item in the list. */
|
|
setLastItemActive() {
|
|
this._setActiveItemByIndex(this._getItemsArray().length - 1, -1);
|
|
}
|
|
/** Sets the active item to the next enabled item in the list. */
|
|
setNextItemActive() {
|
|
this._activeItemIndex() < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1);
|
|
}
|
|
/** Sets the active item to a previous enabled item in the list. */
|
|
setPreviousItemActive() {
|
|
this._activeItemIndex() < 0 && this._wrap ? this.setLastItemActive() : this._setActiveItemByDelta(-1);
|
|
}
|
|
updateActiveItem(item) {
|
|
const itemArray = this._getItemsArray();
|
|
const index = typeof item === "number" ? item : itemArray.indexOf(item);
|
|
const activeItem = itemArray[index];
|
|
this._activeItem.set(activeItem == null ? null : activeItem);
|
|
this._activeItemIndex.set(index);
|
|
this._typeahead?.setCurrentSelectedItemIndex(index);
|
|
}
|
|
/** Cleans up the key manager. */
|
|
destroy() {
|
|
this._typeaheadSubscription.unsubscribe();
|
|
this._itemChangesSubscription?.unsubscribe();
|
|
this._effectRef?.destroy();
|
|
this._typeahead?.destroy();
|
|
this.tabOut.complete();
|
|
this.change.complete();
|
|
}
|
|
/**
|
|
* This method sets the active item, given a list of items and the delta between the
|
|
* currently active item and the new active item. It will calculate differently
|
|
* depending on whether wrap mode is turned on.
|
|
*/
|
|
_setActiveItemByDelta(delta) {
|
|
this._wrap ? this._setActiveInWrapMode(delta) : this._setActiveInDefaultMode(delta);
|
|
}
|
|
/**
|
|
* Sets the active item properly given "wrap" mode. In other words, it will continue to move
|
|
* down the list until it finds an item that is not disabled, and it will wrap if it
|
|
* encounters either end of the list.
|
|
*/
|
|
_setActiveInWrapMode(delta) {
|
|
const items = this._getItemsArray();
|
|
for (let i = 1; i <= items.length; i++) {
|
|
const index = (this._activeItemIndex() + delta * i + items.length) % items.length;
|
|
const item = items[index];
|
|
if (!this._skipPredicateFn(item)) {
|
|
this.setActiveItem(index);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Sets the active item properly given the default mode. In other words, it will
|
|
* continue to move down the list until it finds an item that is not disabled. If
|
|
* it encounters either end of the list, it will stop and not wrap.
|
|
*/
|
|
_setActiveInDefaultMode(delta) {
|
|
this._setActiveItemByIndex(this._activeItemIndex() + delta, delta);
|
|
}
|
|
/**
|
|
* Sets the active item to the first enabled item starting at the index specified. If the
|
|
* item is disabled, it will move in the fallbackDelta direction until it either
|
|
* finds an enabled item or encounters the end of the list.
|
|
*/
|
|
_setActiveItemByIndex(index, fallbackDelta) {
|
|
const items = this._getItemsArray();
|
|
if (!items[index]) {
|
|
return;
|
|
}
|
|
while (this._skipPredicateFn(items[index])) {
|
|
index += fallbackDelta;
|
|
if (!items[index]) {
|
|
return;
|
|
}
|
|
}
|
|
this.setActiveItem(index);
|
|
}
|
|
/** Returns the items as an array. */
|
|
_getItemsArray() {
|
|
if (isSignal(this._items)) {
|
|
return this._items();
|
|
}
|
|
return this._items instanceof QueryList ? this._items.toArray() : this._items;
|
|
}
|
|
/** Callback for when the items have changed. */
|
|
_itemsChanged(newItems) {
|
|
this._typeahead?.setItems(newItems);
|
|
const activeItem = this._activeItem();
|
|
if (activeItem) {
|
|
const newIndex = newItems.indexOf(activeItem);
|
|
if (newIndex > -1 && newIndex !== this._activeItemIndex()) {
|
|
this._activeItemIndex.set(newIndex);
|
|
this._typeahead?.setCurrentSelectedItemIndex(newIndex);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// node_modules/@angular/cdk/fesm2022/activedescendant-key-manager.mjs
|
|
var ActiveDescendantKeyManager = class extends ListKeyManager {
|
|
setActiveItem(index) {
|
|
if (this.activeItem) {
|
|
this.activeItem.setInactiveStyles();
|
|
}
|
|
super.setActiveItem(index);
|
|
if (this.activeItem) {
|
|
this.activeItem.setActiveStyles();
|
|
}
|
|
}
|
|
};
|
|
|
|
// node_modules/@angular/cdk/fesm2022/focus-key-manager.mjs
|
|
var FocusKeyManager = class extends ListKeyManager {
|
|
_origin = "program";
|
|
/**
|
|
* Sets the focus origin that will be passed in to the items for any subsequent `focus` calls.
|
|
* @param origin Focus origin to be used when focusing items.
|
|
*/
|
|
setFocusOrigin(origin) {
|
|
this._origin = origin;
|
|
return this;
|
|
}
|
|
setActiveItem(item) {
|
|
super.setActiveItem(item);
|
|
if (this.activeItem) {
|
|
this.activeItem.focus(this._origin);
|
|
}
|
|
}
|
|
};
|
|
|
|
// node_modules/@angular/cdk/fesm2022/coercion/private.mjs
|
|
function coerceObservable(data) {
|
|
if (!isObservable(data)) {
|
|
return of(data);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
// node_modules/@angular/cdk/fesm2022/tree-key-manager.mjs
|
|
var TreeKeyManager = class {
|
|
/** The index of the currently active (focused) item. */
|
|
_activeItemIndex = -1;
|
|
/** The currently active (focused) item. */
|
|
_activeItem = null;
|
|
/** Whether or not we activate the item when it's focused. */
|
|
_shouldActivationFollowFocus = false;
|
|
/**
|
|
* The orientation that the tree is laid out in. In `rtl` mode, the behavior of Left and
|
|
* Right arrow are switched.
|
|
*/
|
|
_horizontalOrientation = "ltr";
|
|
/**
|
|
* Predicate function that can be used to check whether an item should be skipped
|
|
* by the key manager.
|
|
*
|
|
* The default value for this doesn't skip any elements in order to keep tree items focusable
|
|
* when disabled. This aligns with ARIA guidelines:
|
|
* https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols.
|
|
*/
|
|
_skipPredicateFn = (_item) => false;
|
|
/** Function to determine equivalent items. */
|
|
_trackByFn = (item) => item;
|
|
/** Synchronous cache of the items to manage. */
|
|
_items = [];
|
|
_typeahead;
|
|
_typeaheadSubscription = Subscription.EMPTY;
|
|
_hasInitialFocused = false;
|
|
_initializeFocus() {
|
|
if (this._hasInitialFocused || this._items.length === 0) {
|
|
return;
|
|
}
|
|
let activeIndex = 0;
|
|
for (let i = 0; i < this._items.length; i++) {
|
|
if (!this._skipPredicateFn(this._items[i]) && !this._isItemDisabled(this._items[i])) {
|
|
activeIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
const activeItem = this._items[activeIndex];
|
|
if (activeItem.makeFocusable) {
|
|
this._activeItem?.unfocus();
|
|
this._activeItemIndex = activeIndex;
|
|
this._activeItem = activeItem;
|
|
this._typeahead?.setCurrentSelectedItemIndex(activeIndex);
|
|
activeItem.makeFocusable();
|
|
} else {
|
|
this.focusItem(activeIndex);
|
|
}
|
|
this._hasInitialFocused = true;
|
|
}
|
|
/**
|
|
*
|
|
* @param items List of TreeKeyManager options. Can be synchronous or asynchronous.
|
|
* @param config Optional configuration options. By default, use 'ltr' horizontal orientation. By
|
|
* default, do not skip any nodes. By default, key manager only calls `focus` method when items
|
|
* are focused and does not call `activate`. If `typeaheadDefaultInterval` is `true`, use a
|
|
* default interval of 200ms.
|
|
*/
|
|
constructor(items, config) {
|
|
if (items instanceof QueryList) {
|
|
this._items = items.toArray();
|
|
items.changes.subscribe((newItems) => {
|
|
this._items = newItems.toArray();
|
|
this._typeahead?.setItems(this._items);
|
|
this._updateActiveItemIndex(this._items);
|
|
this._initializeFocus();
|
|
});
|
|
} else if (isObservable(items)) {
|
|
items.subscribe((newItems) => {
|
|
this._items = newItems;
|
|
this._typeahead?.setItems(newItems);
|
|
this._updateActiveItemIndex(newItems);
|
|
this._initializeFocus();
|
|
});
|
|
} else {
|
|
this._items = items;
|
|
this._initializeFocus();
|
|
}
|
|
if (typeof config.shouldActivationFollowFocus === "boolean") {
|
|
this._shouldActivationFollowFocus = config.shouldActivationFollowFocus;
|
|
}
|
|
if (config.horizontalOrientation) {
|
|
this._horizontalOrientation = config.horizontalOrientation;
|
|
}
|
|
if (config.skipPredicate) {
|
|
this._skipPredicateFn = config.skipPredicate;
|
|
}
|
|
if (config.trackBy) {
|
|
this._trackByFn = config.trackBy;
|
|
}
|
|
if (typeof config.typeAheadDebounceInterval !== "undefined") {
|
|
this._setTypeAhead(config.typeAheadDebounceInterval);
|
|
}
|
|
}
|
|
/** Stream that emits any time the focused item changes. */
|
|
change = new Subject();
|
|
/** Cleans up the key manager. */
|
|
destroy() {
|
|
this._typeaheadSubscription.unsubscribe();
|
|
this._typeahead?.destroy();
|
|
this.change.complete();
|
|
}
|
|
/**
|
|
* Handles a keyboard event on the tree.
|
|
* @param event Keyboard event that represents the user interaction with the tree.
|
|
*/
|
|
onKeydown(event) {
|
|
const key = event.key;
|
|
switch (key) {
|
|
case "Tab":
|
|
return;
|
|
case "ArrowDown":
|
|
this._focusNextItem();
|
|
break;
|
|
case "ArrowUp":
|
|
this._focusPreviousItem();
|
|
break;
|
|
case "ArrowRight":
|
|
this._horizontalOrientation === "rtl" ? this._collapseCurrentItem() : this._expandCurrentItem();
|
|
break;
|
|
case "ArrowLeft":
|
|
this._horizontalOrientation === "rtl" ? this._expandCurrentItem() : this._collapseCurrentItem();
|
|
break;
|
|
case "Home":
|
|
this._focusFirstItem();
|
|
break;
|
|
case "End":
|
|
this._focusLastItem();
|
|
break;
|
|
case "Enter":
|
|
case " ":
|
|
this._activateCurrentItem();
|
|
break;
|
|
default:
|
|
if (event.key === "*") {
|
|
this._expandAllItemsAtCurrentItemLevel();
|
|
break;
|
|
}
|
|
this._typeahead?.handleKey(event);
|
|
return;
|
|
}
|
|
this._typeahead?.reset();
|
|
event.preventDefault();
|
|
}
|
|
/** Index of the currently active item. */
|
|
getActiveItemIndex() {
|
|
return this._activeItemIndex;
|
|
}
|
|
/** The currently active item. */
|
|
getActiveItem() {
|
|
return this._activeItem;
|
|
}
|
|
/** Focus the first available item. */
|
|
_focusFirstItem() {
|
|
this.focusItem(this._findNextAvailableItemIndex(-1));
|
|
}
|
|
/** Focus the last available item. */
|
|
_focusLastItem() {
|
|
this.focusItem(this._findPreviousAvailableItemIndex(this._items.length));
|
|
}
|
|
/** Focus the next available item. */
|
|
_focusNextItem() {
|
|
this.focusItem(this._findNextAvailableItemIndex(this._activeItemIndex));
|
|
}
|
|
/** Focus the previous available item. */
|
|
_focusPreviousItem() {
|
|
this.focusItem(this._findPreviousAvailableItemIndex(this._activeItemIndex));
|
|
}
|
|
focusItem(itemOrIndex, options = {}) {
|
|
options.emitChangeEvent ??= true;
|
|
let index = typeof itemOrIndex === "number" ? itemOrIndex : this._items.findIndex((item) => this._trackByFn(item) === this._trackByFn(itemOrIndex));
|
|
if (index < 0 || index >= this._items.length) {
|
|
return;
|
|
}
|
|
const activeItem = this._items[index];
|
|
if (this._activeItem !== null && this._trackByFn(activeItem) === this._trackByFn(this._activeItem)) {
|
|
return;
|
|
}
|
|
const previousActiveItem = this._activeItem;
|
|
this._activeItem = activeItem ?? null;
|
|
this._activeItemIndex = index;
|
|
this._typeahead?.setCurrentSelectedItemIndex(index);
|
|
this._activeItem?.focus();
|
|
previousActiveItem?.unfocus();
|
|
if (options.emitChangeEvent) {
|
|
this.change.next(this._activeItem);
|
|
}
|
|
if (this._shouldActivationFollowFocus) {
|
|
this._activateCurrentItem();
|
|
}
|
|
}
|
|
_updateActiveItemIndex(newItems) {
|
|
const activeItem = this._activeItem;
|
|
if (!activeItem) {
|
|
return;
|
|
}
|
|
const newIndex = newItems.findIndex((item) => this._trackByFn(item) === this._trackByFn(activeItem));
|
|
if (newIndex > -1 && newIndex !== this._activeItemIndex) {
|
|
this._activeItemIndex = newIndex;
|
|
this._typeahead?.setCurrentSelectedItemIndex(newIndex);
|
|
}
|
|
}
|
|
_setTypeAhead(debounceInterval) {
|
|
this._typeahead = new Typeahead(this._items, {
|
|
debounceInterval: typeof debounceInterval === "number" ? debounceInterval : void 0,
|
|
skipPredicate: (item) => this._skipPredicateFn(item)
|
|
});
|
|
this._typeaheadSubscription = this._typeahead.selectedItem.subscribe((item) => {
|
|
this.focusItem(item);
|
|
});
|
|
}
|
|
_findNextAvailableItemIndex(startingIndex) {
|
|
for (let i = startingIndex + 1; i < this._items.length; i++) {
|
|
if (!this._skipPredicateFn(this._items[i])) {
|
|
return i;
|
|
}
|
|
}
|
|
return startingIndex;
|
|
}
|
|
_findPreviousAvailableItemIndex(startingIndex) {
|
|
for (let i = startingIndex - 1; i >= 0; i--) {
|
|
if (!this._skipPredicateFn(this._items[i])) {
|
|
return i;
|
|
}
|
|
}
|
|
return startingIndex;
|
|
}
|
|
/**
|
|
* If the item is already expanded, we collapse the item. Otherwise, we will focus the parent.
|
|
*/
|
|
_collapseCurrentItem() {
|
|
if (!this._activeItem) {
|
|
return;
|
|
}
|
|
if (this._isCurrentItemExpanded()) {
|
|
this._activeItem.collapse();
|
|
} else {
|
|
const parent = this._activeItem.getParent();
|
|
if (!parent || this._skipPredicateFn(parent)) {
|
|
return;
|
|
}
|
|
this.focusItem(parent);
|
|
}
|
|
}
|
|
/**
|
|
* If the item is already collapsed, we expand the item. Otherwise, we will focus the first child.
|
|
*/
|
|
_expandCurrentItem() {
|
|
if (!this._activeItem) {
|
|
return;
|
|
}
|
|
if (!this._isCurrentItemExpanded()) {
|
|
this._activeItem.expand();
|
|
} else {
|
|
coerceObservable(this._activeItem.getChildren()).pipe(take(1)).subscribe((children) => {
|
|
const firstChild = children.find((child) => !this._skipPredicateFn(child));
|
|
if (!firstChild) {
|
|
return;
|
|
}
|
|
this.focusItem(firstChild);
|
|
});
|
|
}
|
|
}
|
|
_isCurrentItemExpanded() {
|
|
if (!this._activeItem) {
|
|
return false;
|
|
}
|
|
return typeof this._activeItem.isExpanded === "boolean" ? this._activeItem.isExpanded : this._activeItem.isExpanded();
|
|
}
|
|
_isItemDisabled(item) {
|
|
return typeof item.isDisabled === "boolean" ? item.isDisabled : item.isDisabled?.();
|
|
}
|
|
/** For all items that are the same level as the current item, we expand those items. */
|
|
_expandAllItemsAtCurrentItemLevel() {
|
|
if (!this._activeItem) {
|
|
return;
|
|
}
|
|
const parent = this._activeItem.getParent();
|
|
let itemsToExpand;
|
|
if (!parent) {
|
|
itemsToExpand = of(this._items.filter((item) => item.getParent() === null));
|
|
} else {
|
|
itemsToExpand = coerceObservable(parent.getChildren());
|
|
}
|
|
itemsToExpand.pipe(take(1)).subscribe((items) => {
|
|
for (const item of items) {
|
|
item.expand();
|
|
}
|
|
});
|
|
}
|
|
_activateCurrentItem() {
|
|
this._activeItem?.activate();
|
|
}
|
|
};
|
|
function TREE_KEY_MANAGER_FACTORY() {
|
|
return (items, options) => new TreeKeyManager(items, options);
|
|
}
|
|
var TREE_KEY_MANAGER = new InjectionToken("tree-key-manager", {
|
|
providedIn: "root",
|
|
factory: TREE_KEY_MANAGER_FACTORY
|
|
});
|
|
var TREE_KEY_MANAGER_FACTORY_PROVIDER = {
|
|
provide: TREE_KEY_MANAGER,
|
|
useFactory: TREE_KEY_MANAGER_FACTORY
|
|
};
|
|
|
|
// node_modules/@angular/cdk/fesm2022/a11y.mjs
|
|
var ID_DELIMITER = " ";
|
|
function addAriaReferencedId(el, attr, id) {
|
|
const ids = getAriaReferenceIds(el, attr);
|
|
id = id.trim();
|
|
if (ids.some((existingId) => existingId.trim() === id)) {
|
|
return;
|
|
}
|
|
ids.push(id);
|
|
el.setAttribute(attr, ids.join(ID_DELIMITER));
|
|
}
|
|
function removeAriaReferencedId(el, attr, id) {
|
|
const ids = getAriaReferenceIds(el, attr);
|
|
id = id.trim();
|
|
const filteredIds = ids.filter((val) => val !== id);
|
|
if (filteredIds.length) {
|
|
el.setAttribute(attr, filteredIds.join(ID_DELIMITER));
|
|
} else {
|
|
el.removeAttribute(attr);
|
|
}
|
|
}
|
|
function getAriaReferenceIds(el, attr) {
|
|
const attrValue = el.getAttribute(attr);
|
|
return attrValue?.match(/\S+/g) ?? [];
|
|
}
|
|
var MESSAGES_CONTAINER_ID = "cdk-describedby-message-container";
|
|
var CDK_DESCRIBEDBY_ID_PREFIX = "cdk-describedby-message";
|
|
var CDK_DESCRIBEDBY_HOST_ATTRIBUTE = "cdk-describedby-host";
|
|
var nextId = 0;
|
|
var AriaDescriber = class _AriaDescriber {
|
|
_platform = inject(Platform);
|
|
_document = inject(DOCUMENT);
|
|
/** Map of all registered message elements that have been placed into the document. */
|
|
_messageRegistry = /* @__PURE__ */ new Map();
|
|
/** Container for all registered messages. */
|
|
_messagesContainer = null;
|
|
/** Unique ID for the service. */
|
|
_id = `${nextId++}`;
|
|
constructor() {
|
|
inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader);
|
|
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.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;
|
|
}
|
|
static ɵfac = function AriaDescriber_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _AriaDescriber)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _AriaDescriber,
|
|
factory: _AriaDescriber.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(AriaDescriber, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], 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 NoopTreeKeyManager = class {
|
|
_isNoopTreeKeyManager = true;
|
|
// Provide change as required by TreeKeyManagerStrategy. NoopTreeKeyManager is a "noop"
|
|
// implementation that does not emit to streams.
|
|
change = new Subject();
|
|
destroy() {
|
|
this.change.complete();
|
|
}
|
|
onKeydown() {
|
|
}
|
|
getActiveItemIndex() {
|
|
return null;
|
|
}
|
|
getActiveItem() {
|
|
return null;
|
|
}
|
|
focusItem() {
|
|
}
|
|
};
|
|
function NOOP_TREE_KEY_MANAGER_FACTORY() {
|
|
return () => new NoopTreeKeyManager();
|
|
}
|
|
var NOOP_TREE_KEY_MANAGER_FACTORY_PROVIDER = {
|
|
provide: TREE_KEY_MANAGER,
|
|
useFactory: NOOP_TREE_KEY_MANAGER_FACTORY
|
|
};
|
|
var ConfigurableFocusTrap = class extends FocusTrap {
|
|
_focusTrapManager;
|
|
_inertStrategy;
|
|
/** 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, injector) {
|
|
super(_element, _checker, _ngZone, _document, config.defer, injector);
|
|
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 EventListenerFocusTrapInertStrategy = class {
|
|
/** Focus event handler. */
|
|
_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 FOCUS_TRAP_INERT_STRATEGY = new InjectionToken("FOCUS_TRAP_INERT_STRATEGY");
|
|
var FocusTrapManager = class _FocusTrapManager {
|
|
// A stack of the FocusTraps on the page. Only the FocusTrap at the
|
|
// top of the stack is active.
|
|
_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();
|
|
}
|
|
}
|
|
}
|
|
static ɵfac = function FocusTrapManager_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _FocusTrapManager)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _FocusTrapManager,
|
|
factory: _FocusTrapManager.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(FocusTrapManager, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
var ConfigurableFocusTrapFactory = class _ConfigurableFocusTrapFactory {
|
|
_checker = inject(InteractivityChecker);
|
|
_ngZone = inject(NgZone);
|
|
_focusTrapManager = inject(FocusTrapManager);
|
|
_document = inject(DOCUMENT);
|
|
_inertStrategy;
|
|
_injector = inject(Injector);
|
|
constructor() {
|
|
const inertStrategy = inject(FOCUS_TRAP_INERT_STRATEGY, {
|
|
optional: true
|
|
});
|
|
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, this._injector);
|
|
}
|
|
static ɵfac = function ConfigurableFocusTrapFactory_Factory(__ngFactoryType__) {
|
|
return new (__ngFactoryType__ || _ConfigurableFocusTrapFactory)();
|
|
};
|
|
static ɵprov = ɵɵdefineInjectable({
|
|
token: _ConfigurableFocusTrapFactory,
|
|
factory: _ConfigurableFocusTrapFactory.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
};
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ConfigurableFocusTrapFactory, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [], null);
|
|
})();
|
|
export {
|
|
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,
|
|
FocusMonitorDetectionMode,
|
|
FocusTrap,
|
|
FocusTrapFactory,
|
|
HighContrastMode,
|
|
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,
|
|
NOOP_TREE_KEY_MANAGER_FACTORY,
|
|
NOOP_TREE_KEY_MANAGER_FACTORY_PROVIDER,
|
|
NoopTreeKeyManager,
|
|
TREE_KEY_MANAGER,
|
|
TREE_KEY_MANAGER_FACTORY,
|
|
TREE_KEY_MANAGER_FACTORY_PROVIDER,
|
|
TreeKeyManager,
|
|
_IdGenerator,
|
|
addAriaReferencedId,
|
|
getAriaReferenceIds,
|
|
isFakeMousedownFromScreenReader,
|
|
isFakeTouchstartFromScreenReader,
|
|
removeAriaReferencedId
|
|
};
|
|
//# sourceMappingURL=@angular_cdk_a11y.js.map
|