123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- import * as i0 from '@angular/core';
- import { InjectionToken, inject, NgZone, RendererFactory2, Injectable, ElementRef, EventEmitter, Directive, Output } from '@angular/core';
- import { BehaviorSubject, Subject, of } from 'rxjs';
- import { skip, distinctUntilChanged, takeUntil } from 'rxjs/operators';
- import { DOCUMENT } from '@angular/common';
- import { i as isFakeMousedownFromScreenReader, a as isFakeTouchstartFromScreenReader } from './fake-event-detection-DWOdFTFz.mjs';
- import { d as ALT, C as CONTROL, M as MAC_META, e as META, f as SHIFT } from './keycodes-CpHkExLC.mjs';
- import { _ as _getEventTarget, a as _getShadowRoot } from './shadow-dom-B0oHn41l.mjs';
- import { _ as _bindEventWithOptions } from './backwards-compatibility-DHR38MsD.mjs';
- import { P as Platform } from './platform-DmdVEw_C.mjs';
- import { n as normalizePassiveListenerOptions } from './passive-listeners-esHZRgIN.mjs';
- import { a as coerceElement } from './element-x4z00URv.mjs';
- /**
- * Injectable options for the InputModalityDetector. These are shallowly merged with the default
- * options.
- */
- const INPUT_MODALITY_DETECTOR_OPTIONS = new InjectionToken('cdk-input-modality-detector-options');
- /**
- * Default options for the InputModalityDetector.
- *
- * Modifier keys are ignored by default (i.e. when pressed won't cause the service to detect
- * keyboard input modality) for two reasons:
- *
- * 1. Modifier keys are commonly used with mouse to perform actions such as 'right click' or 'open
- * in new tab', and are thus less representative of actual keyboard interaction.
- * 2. VoiceOver triggers some keyboard events when linearly navigating with Control + Option (but
- * confusingly not with Caps Lock). Thus, to have parity with other screen readers, we ignore
- * these keys so as to not update the input modality.
- *
- * Note that we do not by default ignore the right Meta key on Safari because it has the same key
- * code as the ContextMenu key on other browsers. When we switch to using event.key, we can
- * distinguish between the two.
- */
- const INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS = {
- ignoreKeys: [ALT, CONTROL, MAC_META, META, SHIFT],
- };
- /**
- * The amount of time needed to pass after a touchstart event in order for a subsequent mousedown
- * event to be attributed as mouse and not touch.
- *
- * This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found
- * that a value of around 650ms seems appropriate.
- */
- const TOUCH_BUFFER_MS = 650;
- /**
- * Event listener options that enable capturing and also mark the listener as passive if the browser
- * supports it.
- */
- const modalityEventListenerOptions = {
- passive: true,
- capture: true,
- };
- /**
- * Service that detects the user's input modality.
- *
- * This service does not update the input modality when a user navigates with a screen reader
- * (e.g. linear navigation with VoiceOver, object navigation / browse mode with NVDA, virtual PC
- * cursor mode with JAWS). This is in part due to technical limitations (i.e. keyboard events do not
- * fire as expected in these modes) but is also arguably the correct behavior. Navigating with a
- * screen reader is akin to visually scanning a page, and should not be interpreted as actual user
- * input interaction.
- *
- * When a user is not navigating but *interacting* with a screen reader, this service attempts to
- * update the input modality to keyboard, but in general this service's behavior is largely
- * undefined.
- */
- 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 is one of the keys we should ignore, then ignore it and don't update the input
- // modality to keyboard.
- 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) => {
- // Touches trigger both touch and mouse events, so we need to distinguish between mouse events
- // that were triggered via mouse vs touch. To do so, check if the mouse event occurs closely
- // after the previous touch event.
- if (Date.now() - this._lastTouchMs < TOUCH_BUFFER_MS) {
- return;
- }
- // Fake mousedown events are fired by some screen readers when controls are activated by the
- // screen reader. Attribute them to keyboard input modality.
- 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) => {
- // Same scenario as mentioned in _onMousedown, but on touch screen devices, fake touchstart
- // events are fired. Again, attribute to keyboard input modality.
- if (isFakeTouchstartFromScreenReader(event)) {
- this._modality.next('keyboard');
- return;
- }
- // Store the timestamp of this touch event, as it's used to distinguish between mouse events
- // triggered via mouse vs touch.
- this._lastTouchMs = Date.now();
- this._modality.next('touch');
- this._mostRecentTarget = _getEventTarget(event);
- };
- constructor() {
- const ngZone = inject(NgZone);
- const document = inject(DOCUMENT);
- const options = inject(INPUT_MODALITY_DETECTOR_OPTIONS, { optional: true });
- this._options = {
- ...INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS,
- ...options,
- };
- // Skip the first emission as it's null.
- this.modalityDetected = this._modality.pipe(skip(1));
- this.modalityChanged = this.modalityDetected.pipe(distinctUntilChanged());
- // If we're not in a browser, this service should do nothing, as there's no relevant input
- // modality to detect.
- if (this._platform.isBrowser) {
- const renderer = inject(RendererFactory2).createRenderer(null, null);
- this._listenerCleanups = ngZone.runOutsideAngular(() => {
- return [
- _bindEventWithOptions(renderer, document, 'keydown', this._onKeydown, modalityEventListenerOptions),
- _bindEventWithOptions(renderer, document, 'mousedown', this._onMousedown, modalityEventListenerOptions),
- _bindEventWithOptions(renderer, document, 'touchstart', this._onTouchstart, modalityEventListenerOptions),
- ];
- });
- }
- }
- ngOnDestroy() {
- this._modality.complete();
- this._listenerCleanups?.forEach(cleanup => cleanup());
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: InputModalityDetector, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: InputModalityDetector, providedIn: 'root' });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: InputModalityDetector, decorators: [{
- type: Injectable,
- args: [{ providedIn: 'root' }]
- }], ctorParameters: () => [] });
- /** Detection mode used for attributing the origin of a focus event. */
- var FocusMonitorDetectionMode;
- (function (FocusMonitorDetectionMode) {
- /**
- * Any mousedown, keydown, or touchstart event that happened in the previous
- * tick or the current tick will be used to assign a focus event's origin (to
- * either mouse, keyboard, or touch). This is the default option.
- */
- FocusMonitorDetectionMode[FocusMonitorDetectionMode["IMMEDIATE"] = 0] = "IMMEDIATE";
- /**
- * A focus event's origin is always attributed to the last corresponding
- * mousedown, keydown, or touchstart event, no matter how long ago it occurred.
- */
- FocusMonitorDetectionMode[FocusMonitorDetectionMode["EVENTUAL"] = 1] = "EVENTUAL";
- })(FocusMonitorDetectionMode || (FocusMonitorDetectionMode = {}));
- /** InjectionToken for FocusMonitorOptions. */
- const FOCUS_MONITOR_DEFAULT_OPTIONS = new InjectionToken('cdk-focus-monitor-default-options');
- /**
- * Event listener options that enable capturing and also
- * mark the listener as passive if the browser supports it.
- */
- const captureEventListenerOptions = normalizePassiveListenerOptions({
- passive: true,
- capture: true,
- });
- /** Monitors mouse and keyboard events to determine the cause of focus events. */
- 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 = 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 = 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 = () => {
- // Make a note of when the window regains focus, so we can
- // restore the origin info for the focused element.
- this._windowFocused = true;
- this._windowFocusTimeoutId = setTimeout(() => (this._windowFocused = false));
- };
- /** Used to reference correct document/window */
- _document = inject(DOCUMENT, { optional: true });
- /** 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);
- // We need to walk up the ancestor chain in order to support `checkChildren`.
- 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);
- // Do nothing if we're not on the browser platform or the passed in node isn't an element.
- if (!this._platform.isBrowser || nativeElement.nodeType !== 1) {
- // Note: we don't want the observable to emit at all so we don't pass any parameters.
- return of();
- }
- // If the element is inside the shadow DOM, we need to bind our focus/blur listeners to
- // the shadow root, rather than the `document`, because the browser won't emit focus events
- // to the `document`, if focus is moving within the same shadow root.
- const rootNode = _getShadowRoot(nativeElement) || this._getDocument();
- const cachedInfo = this._elementInfo.get(nativeElement);
- // Check if we're already monitoring this element.
- if (cachedInfo) {
- if (checkChildren) {
- // TODO(COMP-318): this can be problematic, because it'll turn all non-checkChildren
- // observers into ones that behave as if `checkChildren` was turned on. We need a more
- // robust solution.
- cachedInfo.checkChildren = true;
- }
- return cachedInfo.subject;
- }
- // Create monitored element info.
- const info = {
- checkChildren: checkChildren,
- subject: new Subject(),
- rootNode,
- };
- this._elementInfo.set(nativeElement, info);
- this._registerGlobalListeners(info);
- return info.subject;
- }
- stopMonitoring(element) {
- const nativeElement = coerceElement(element);
- const elementInfo = this._elementInfo.get(nativeElement);
- if (elementInfo) {
- elementInfo.subject.complete();
- this._setClasses(nativeElement);
- this._elementInfo.delete(nativeElement);
- this._removeGlobalListeners(elementInfo);
- }
- }
- focusVia(element, origin, options) {
- const nativeElement = coerceElement(element);
- const focusedElement = this._getDocument().activeElement;
- // If the element is focused already, calling `focus` again won't trigger the event listener
- // which means that the focus classes won't be updated. If that's the case, update the classes
- // directly without waiting for an event.
- if (nativeElement === focusedElement) {
- this._getClosestElementsInfo(nativeElement).forEach(([currentElement, info]) => this._originChanged(currentElement, origin, info));
- }
- else {
- this._setOrigin(origin);
- // `focus` isn't available on the server
- if (typeof nativeElement.focus === 'function') {
- nativeElement.focus(options);
- }
- }
- }
- ngOnDestroy() {
- this._elementInfo.forEach((_info, element) => this.stopMonitoring(element));
- }
- /** Access injected document if available or fallback to global document reference */
- _getDocument() {
- return this._document || document;
- }
- /** Use defaultView of injected document if available or fallback to global window reference */
- _getWindow() {
- const doc = this._getDocument();
- return doc.defaultView || window;
- }
- _getFocusOrigin(focusEventTarget) {
- if (this._origin) {
- // If the origin was realized via a touch interaction, we need to perform additional checks
- // to determine whether the focus origin should be attributed to touch or program.
- if (this._originFromTouchInteraction) {
- return this._shouldBeAttributedToTouch(focusEventTarget) ? 'touch' : 'program';
- }
- else {
- return this._origin;
- }
- }
- // If the window has just regained focus, we can restore the most recent origin from before the
- // window blurred. Otherwise, we've reached the point where we can't identify the source of the
- // focus. This typically means one of two things happened:
- //
- // 1) The element was programmatically focused, or
- // 2) The element was focused via screen reader navigation (which generally doesn't fire
- // events).
- //
- // Because we can't distinguish between these two cases, we default to setting `program`.
- if (this._windowFocused && this._lastFocusOrigin) {
- return this._lastFocusOrigin;
- }
- // If the interaction is coming from an input label, we consider it a mouse interactions.
- // This is a special case where focus moves on `click`, rather than `mousedown` which breaks
- // our detection, because all our assumptions are for `mousedown`. We need to handle this
- // special case, because it's very common for checkboxes and radio buttons.
- 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) {
- // Please note that this check is not perfect. Consider the following edge case:
- //
- // <div #parent tabindex="0">
- // <div #child tabindex="0" (click)="#parent.focus()"></div>
- // </div>
- //
- // Suppose there is a FocusMonitor in IMMEDIATE mode attached to #parent. When the user touches
- // #child, #parent is programmatically focused. This code will attribute the focus to touch
- // instead of program. This is a relatively minor edge-case that can be worked around by using
- // focusVia(parent, 'program') to focus #parent.
- 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 we're in IMMEDIATE mode, reset the origin at the next tick (or in `TOUCH_BUFFER_MS` ms
- // for a touch event). We reset the origin at the next tick because Firefox focuses one tick
- // after the interaction event. We wait `TOUCH_BUFFER_MS` ms before resetting the origin for
- // a touch event because when a touch event is fired, the associated focus event isn't yet in
- // the event queue. Before doing so, clear any pending timeouts.
- 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) {
- // NOTE(mmalerba): We currently set the classes based on the focus origin of the most recent
- // focus event affecting the monitored element. If we want to use the origin of the first event
- // instead we should check for the cdk-focused class here and return if the element already has
- // it. (This only matters for elements that have includesChildren = true).
- // If we are not counting child-element-focus as focused, make sure that the event target is the
- // monitored element itself.
- 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) {
- // If we are counting child-element-focus as focused, make sure that we aren't just blurring in
- // order to focus another child of the monitored element.
- 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);
- // Register global listeners when first element is monitored.
- if (++this._monitoredElementCount === 1) {
- // Note: we listen to events in the capture phase so we
- // can detect them even if the user stops propagation.
- this._ngZone.runOutsideAngular(() => {
- const window = this._getWindow();
- window.addEventListener('focus', this._windowFocusListener);
- });
- // The InputModalityDetector is also just a collection of global listeners.
- 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);
- }
- }
- // Unregister global listeners when last element is unmonitored.
- if (!--this._monitoredElementCount) {
- const window = this._getWindow();
- window.removeEventListener('focus', this._windowFocusListener);
- // Equivalently, stop our InputModalityDetector subscription.
- this._stopInputModalityDetector.next();
- // Clear timeouts for all potentially pending timeouts to prevent the leaks.
- 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 the last interaction used the mouse on an element contained by one of the labels
- // of an `input`/`textarea` that is currently focused, it is very likely that the
- // user redirected focus using the label.
- 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 = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FocusMonitor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FocusMonitor, providedIn: 'root' });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FocusMonitor, decorators: [{
- type: Injectable,
- args: [{ providedIn: 'root' }]
- }], ctorParameters: () => [] });
- /**
- * Directive that determines how a particular element was focused (via keyboard, mouse, touch, or
- * programmatically) and adds corresponding classes to the element.
- *
- * There are two variants of this directive:
- * 1) cdkMonitorElementFocus: does not consider an element to be focused if one of its children is
- * focused.
- * 2) cdkMonitorSubtreeFocus: considers an element focused if it or any of its children are focused.
- */
- 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 = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMonitorFocus, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkMonitorFocus, isStandalone: true, selector: "[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]", outputs: { cdkFocusChange: "cdkFocusChange" }, exportAs: ["cdkMonitorFocus"], ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMonitorFocus, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]',
- exportAs: 'cdkMonitorFocus',
- }]
- }], ctorParameters: () => [], propDecorators: { cdkFocusChange: [{
- type: Output
- }] } });
- export { CdkMonitorFocus as C, FocusMonitor as F, InputModalityDetector as I, INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS as a, INPUT_MODALITY_DETECTOR_OPTIONS as b, FocusMonitorDetectionMode as c, FOCUS_MONITOR_DEFAULT_OPTIONS as d };
- //# sourceMappingURL=focus-monitor-e2l_RpN3.mjs.map
|