mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(focus-monitor): support monitoring ElementRef (#12712)
Allows for an `ElementRef` to be monitored by the `FocusMonitor`. This makes it more convenient, because most of the time we're dealing with `ElementRef` anyway.
This commit is contained in:
committed by
Jeremy Elbourn
parent
636d27e3ae
commit
932211e26f
@@ -88,14 +88,29 @@ export class FocusMonitor implements OnDestroy {
|
||||
* @returns An observable that emits when the focus state of the element changes.
|
||||
* When the element is blurred, null will be emitted.
|
||||
*/
|
||||
monitor(element: HTMLElement, checkChildren: boolean = false): Observable<FocusOrigin> {
|
||||
monitor(element: HTMLElement, checkChildren?: boolean): Observable<FocusOrigin>;
|
||||
|
||||
/**
|
||||
* Monitors focus on an element and applies appropriate CSS classes.
|
||||
* @param element The element to monitor
|
||||
* @param checkChildren Whether to count the element as focused when its children are focused.
|
||||
* @returns An observable that emits when the focus state of the element changes.
|
||||
* When the element is blurred, null will be emitted.
|
||||
*/
|
||||
monitor(element: ElementRef<HTMLElement>, checkChildren?: boolean): Observable<FocusOrigin>;
|
||||
|
||||
monitor(element: HTMLElement | ElementRef<HTMLElement>,
|
||||
checkChildren: boolean = false): Observable<FocusOrigin> {
|
||||
// Do nothing if we're not on the browser platform.
|
||||
if (!this._platform.isBrowser) {
|
||||
return observableOf(null);
|
||||
}
|
||||
|
||||
const nativeElement = this._getNativeElement(element);
|
||||
|
||||
// Check if we're already monitoring this element.
|
||||
if (this._elementInfo.has(element)) {
|
||||
let cachedInfo = this._elementInfo.get(element);
|
||||
if (this._elementInfo.has(nativeElement)) {
|
||||
let cachedInfo = this._elementInfo.get(nativeElement);
|
||||
cachedInfo!.checkChildren = checkChildren;
|
||||
return cachedInfo!.subject.asObservable();
|
||||
}
|
||||
@@ -106,21 +121,21 @@ export class FocusMonitor implements OnDestroy {
|
||||
checkChildren: checkChildren,
|
||||
subject: new Subject<FocusOrigin>()
|
||||
};
|
||||
this._elementInfo.set(element, info);
|
||||
this._elementInfo.set(nativeElement, info);
|
||||
this._incrementMonitoredElementCount();
|
||||
|
||||
// Start listening. We need to listen in capture phase since focus events don't bubble.
|
||||
let focusListener = (event: FocusEvent) => this._onFocus(event, element);
|
||||
let blurListener = (event: FocusEvent) => this._onBlur(event, element);
|
||||
let focusListener = (event: FocusEvent) => this._onFocus(event, nativeElement);
|
||||
let blurListener = (event: FocusEvent) => this._onBlur(event, nativeElement);
|
||||
this._ngZone.runOutsideAngular(() => {
|
||||
element.addEventListener('focus', focusListener, true);
|
||||
element.addEventListener('blur', blurListener, true);
|
||||
nativeElement.addEventListener('focus', focusListener, true);
|
||||
nativeElement.addEventListener('blur', blurListener, true);
|
||||
});
|
||||
|
||||
// Create an unlisten function for later.
|
||||
info.unlisten = () => {
|
||||
element.removeEventListener('focus', focusListener, true);
|
||||
element.removeEventListener('blur', blurListener, true);
|
||||
nativeElement.removeEventListener('focus', focusListener, true);
|
||||
nativeElement.removeEventListener('blur', blurListener, true);
|
||||
};
|
||||
|
||||
return info.subject.asObservable();
|
||||
@@ -130,15 +145,24 @@ export class FocusMonitor implements OnDestroy {
|
||||
* Stops monitoring an element and removes all focus classes.
|
||||
* @param element The element to stop monitoring.
|
||||
*/
|
||||
stopMonitoring(element: HTMLElement): void {
|
||||
const elementInfo = this._elementInfo.get(element);
|
||||
stopMonitoring(element: HTMLElement): void;
|
||||
|
||||
/**
|
||||
* Stops monitoring an element and removes all focus classes.
|
||||
* @param element The element to stop monitoring.
|
||||
*/
|
||||
stopMonitoring(element: ElementRef<HTMLElement>): void;
|
||||
|
||||
stopMonitoring(element: HTMLElement | ElementRef<HTMLElement>): void {
|
||||
const nativeElement = this._getNativeElement(element);
|
||||
const elementInfo = this._elementInfo.get(nativeElement);
|
||||
|
||||
if (elementInfo) {
|
||||
elementInfo.unlisten();
|
||||
elementInfo.subject.complete();
|
||||
|
||||
this._setClasses(element);
|
||||
this._elementInfo.delete(element);
|
||||
this._setClasses(nativeElement);
|
||||
this._elementInfo.delete(nativeElement);
|
||||
this._decrementMonitoredElementCount();
|
||||
}
|
||||
}
|
||||
@@ -370,6 +394,10 @@ export class FocusMonitor implements OnDestroy {
|
||||
this._unregisterGlobalListeners = () => {};
|
||||
}
|
||||
}
|
||||
|
||||
private _getNativeElement(element: HTMLElement | ElementRef<HTMLElement>): HTMLElement {
|
||||
return element instanceof ElementRef ? element.nativeElement : element;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -391,13 +419,13 @@ export class CdkMonitorFocus implements OnDestroy {
|
||||
|
||||
constructor(private _elementRef: ElementRef, private _focusMonitor: FocusMonitor) {
|
||||
this._monitorSubscription = this._focusMonitor.monitor(
|
||||
this._elementRef.nativeElement,
|
||||
this._elementRef,
|
||||
this._elementRef.nativeElement.hasAttribute('cdkMonitorSubtreeFocus'))
|
||||
.subscribe(origin => this.cdkFocusChange.emit(origin));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._elementRef);
|
||||
this._monitorSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,11 +433,11 @@ export class MatButtonToggle extends _MatButtonToggleMixinBase implements OnInit
|
||||
this.checked = true;
|
||||
}
|
||||
|
||||
this._focusMonitor.monitor(this._elementRef.nativeElement, true);
|
||||
this._focusMonitor.monitor(this._elementRef, true);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._elementRef);
|
||||
}
|
||||
|
||||
/** Focuses the button. */
|
||||
|
||||
@@ -106,7 +106,7 @@ export class MatButton extends _MatButtonMixinBase
|
||||
}
|
||||
}
|
||||
|
||||
this._focusMonitor.monitor(this._elementRef.nativeElement, true);
|
||||
this._focusMonitor.monitor(this._elementRef, true);
|
||||
|
||||
if (this.isRoundButton) {
|
||||
this.color = DEFAULT_ROUND_BUTTON_COLOR;
|
||||
@@ -114,7 +114,7 @@ export class MatButton extends _MatButtonMixinBase
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._elementRef);
|
||||
}
|
||||
|
||||
/** Focuses the button. */
|
||||
|
||||
@@ -197,12 +197,12 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
|
||||
|
||||
ngAfterViewInit() {
|
||||
this._focusMonitor
|
||||
.monitor(this._inputElement.nativeElement)
|
||||
.monitor(this._inputElement)
|
||||
.subscribe(focusOrigin => this._onInputFocusChange(focusOrigin));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._focusMonitor.stopMonitoring(this._inputElement.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._inputElement);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,7 +83,7 @@ export class MatExpansionPanelHeader implements OnDestroy {
|
||||
)
|
||||
.subscribe(() => this._changeDetectorRef.markForCheck());
|
||||
|
||||
_focusMonitor.monitor(_element.nativeElement);
|
||||
_focusMonitor.monitor(_element);
|
||||
}
|
||||
|
||||
/** Height of the header while the panel is expanded. */
|
||||
@@ -133,7 +133,7 @@ export class MatExpansionPanelHeader implements OnDestroy {
|
||||
|
||||
ngOnDestroy() {
|
||||
this._parentChangeSubscription.unsubscribe();
|
||||
this._focusMonitor.stopMonitoring(this._element.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._element);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export class MatMenuItem extends _MatMenuItemMixinBase
|
||||
// Start monitoring the element so it gets the appropriate focused classes. We want
|
||||
// to show the focus style for menu items only when the focus was not caused by a
|
||||
// mouse or touch interaction.
|
||||
_focusMonitor.monitor(this._getHostElement(), false);
|
||||
_focusMonitor.monitor(this._elementRef, false);
|
||||
}
|
||||
|
||||
if (_parentMenu && _parentMenu.addItem) {
|
||||
@@ -103,7 +103,7 @@ export class MatMenuItem extends _MatMenuItemMixinBase
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this._focusMonitor) {
|
||||
this._focusMonitor.stopMonitoring(this._getHostElement());
|
||||
this._focusMonitor.stopMonitoring(this._elementRef);
|
||||
}
|
||||
|
||||
if (this._parentMenu && this._parentMenu.removeItem) {
|
||||
|
||||
@@ -518,12 +518,12 @@ export class MatRadioButton extends _MatRadioButtonMixinBase
|
||||
|
||||
ngAfterViewInit() {
|
||||
this._focusMonitor
|
||||
.monitor(this._inputElement.nativeElement)
|
||||
.monitor(this._inputElement)
|
||||
.subscribe(focusOrigin => this._onInputFocusChange(focusOrigin));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._focusMonitor.stopMonitoring(this._inputElement.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._inputElement);
|
||||
this._removeUniqueSelectionListener();
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._focusMonitor
|
||||
.monitor(this._elementRef.nativeElement, true)
|
||||
.monitor(this._elementRef, true)
|
||||
.subscribe(focusOrigin => {
|
||||
if (!focusOrigin) {
|
||||
// When a focused element becomes disabled, the browser *immediately* fires a blur event.
|
||||
@@ -208,7 +208,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._elementRef);
|
||||
}
|
||||
|
||||
/** Method being called whenever the underlying input emits a change event. */
|
||||
|
||||
@@ -465,7 +465,7 @@ export class MatSlider extends _MatSliderMixinBase
|
||||
|
||||
ngOnInit() {
|
||||
this._focusMonitor
|
||||
.monitor(this._elementRef.nativeElement, true)
|
||||
.monitor(this._elementRef, true)
|
||||
.subscribe((origin: FocusOrigin) => {
|
||||
this._isActive = !!origin && origin !== 'keyboard';
|
||||
this._changeDetectorRef.detectChanges();
|
||||
@@ -478,7 +478,7 @@ export class MatSlider extends _MatSliderMixinBase
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._elementRef);
|
||||
this._dirChangeSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
|
||||
@@ -64,13 +64,13 @@ export class MatStepHeader implements OnDestroy {
|
||||
private _focusMonitor: FocusMonitor,
|
||||
private _element: ElementRef,
|
||||
changeDetectorRef: ChangeDetectorRef) {
|
||||
_focusMonitor.monitor(_element.nativeElement, true);
|
||||
_focusMonitor.monitor(_element, true);
|
||||
this._intlSubscription = _intl.changes.subscribe(() => changeDetectorRef.markForCheck());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._intlSubscription.unsubscribe();
|
||||
this._focusMonitor.stopMonitoring(this._element.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._element);
|
||||
}
|
||||
|
||||
/** Returns string label of given step if it is a text label. */
|
||||
|
||||
@@ -247,7 +247,7 @@ export class MatTabLink extends _MatTabLinkMixinBase
|
||||
}
|
||||
|
||||
if (_focusMonitor) {
|
||||
_focusMonitor.monitor(_elementRef.nativeElement);
|
||||
_focusMonitor.monitor(_elementRef);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ export class MatTabLink extends _MatTabLinkMixinBase
|
||||
this._tabLinkRipple._removeTriggerEvents();
|
||||
|
||||
if (this._focusMonitor) {
|
||||
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._elementRef);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ export class MatTooltip implements OnDestroy {
|
||||
element.style['webkitUserDrag'] = '';
|
||||
}
|
||||
|
||||
_focusMonitor.monitor(element).pipe(takeUntil(this._destroyed)).subscribe(origin => {
|
||||
_focusMonitor.monitor(_elementRef).pipe(takeUntil(this._destroyed)).subscribe(origin => {
|
||||
// Note that the focus monitor runs outside the Angular zone.
|
||||
if (!origin) {
|
||||
_ngZone.run(() => this.hide(0));
|
||||
@@ -265,7 +265,7 @@ export class MatTooltip implements OnDestroy {
|
||||
this._destroyed.complete();
|
||||
|
||||
this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this.message);
|
||||
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
|
||||
this._focusMonitor.stopMonitoring(this._elementRef);
|
||||
}
|
||||
|
||||
/** Shows the tooltip after the delay in ms, defaults to tooltip-delay-show or 0ms if no input */
|
||||
|
||||
@@ -25,7 +25,7 @@ export class FocusMonitorFocusViaExample implements OnDestroy, OnInit {
|
||||
private ngZone: NgZone) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.focusMonitor.monitor(this.monitoredEl.nativeElement)
|
||||
this.focusMonitor.monitor(this.monitoredEl)
|
||||
.subscribe(origin => this.ngZone.run(() => {
|
||||
this.origin = this.formatOrigin(origin);
|
||||
this.cdr.markForCheck();
|
||||
@@ -33,7 +33,7 @@ export class FocusMonitorFocusViaExample implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.focusMonitor.stopMonitoring(this.monitoredEl.nativeElement);
|
||||
this.focusMonitor.stopMonitoring(this.monitoredEl);
|
||||
}
|
||||
|
||||
formatOrigin(origin: FocusOrigin): string {
|
||||
|
||||
@@ -27,12 +27,12 @@ export class FocusMonitorOverviewExample implements OnDestroy, OnInit {
|
||||
private ngZone: NgZone) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.focusMonitor.monitor(this.element.nativeElement)
|
||||
this.focusMonitor.monitor(this.element)
|
||||
.subscribe(origin => this.ngZone.run(() => {
|
||||
this.elementOrigin = this.formatOrigin(origin);
|
||||
this.cdr.markForCheck();
|
||||
}));
|
||||
this.focusMonitor.monitor(this.subtree.nativeElement, true)
|
||||
this.focusMonitor.monitor(this.subtree, true)
|
||||
.subscribe(origin => this.ngZone.run(() => {
|
||||
this.subtreeOrigin = this.formatOrigin(origin);
|
||||
this.cdr.markForCheck();
|
||||
@@ -40,8 +40,8 @@ export class FocusMonitorOverviewExample implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.focusMonitor.stopMonitoring(this.element.nativeElement);
|
||||
this.focusMonitor.stopMonitoring(this.subtree.nativeElement);
|
||||
this.focusMonitor.stopMonitoring(this.element);
|
||||
this.focusMonitor.stopMonitoring(this.subtree);
|
||||
}
|
||||
|
||||
formatOrigin(origin: FocusOrigin): string {
|
||||
|
||||
+2
-2
@@ -88,7 +88,7 @@ export class MyTelInput implements MatFormFieldControl<MyTel>, OnDestroy {
|
||||
subscriber: '',
|
||||
});
|
||||
|
||||
fm.monitor(elRef.nativeElement, true).subscribe(origin => {
|
||||
fm.monitor(elRef, true).subscribe(origin => {
|
||||
this.focused = !!origin;
|
||||
this.stateChanges.next();
|
||||
});
|
||||
@@ -96,7 +96,7 @@ export class MyTelInput implements MatFormFieldControl<MyTel>, OnDestroy {
|
||||
|
||||
ngOnDestroy() {
|
||||
this.stateChanges.complete();
|
||||
this.fm.stopMonitoring(this.elRef.nativeElement);
|
||||
this.fm.stopMonitoring(this.elRef);
|
||||
}
|
||||
|
||||
setDescribedByIds(ids: string[]) {
|
||||
|
||||
Reference in New Issue
Block a user