Skip to content

Commit 932211e

Browse files
crisbetojelbourn
authored andcommitted
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.
1 parent 636d27e commit 932211e

File tree

15 files changed

+74
-46
lines changed

15 files changed

+74
-46
lines changed

src/cdk/a11y/focus-monitor/focus-monitor.ts

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,29 @@ export class FocusMonitor implements OnDestroy {
8888
* @returns An observable that emits when the focus state of the element changes.
8989
* When the element is blurred, null will be emitted.
9090
*/
91-
monitor(element: HTMLElement, checkChildren: boolean = false): Observable<FocusOrigin> {
91+
monitor(element: HTMLElement, checkChildren?: boolean): Observable<FocusOrigin>;
92+
93+
/**
94+
* Monitors focus on an element and applies appropriate CSS classes.
95+
* @param element The element to monitor
96+
* @param checkChildren Whether to count the element as focused when its children are focused.
97+
* @returns An observable that emits when the focus state of the element changes.
98+
* When the element is blurred, null will be emitted.
99+
*/
100+
monitor(element: ElementRef<HTMLElement>, checkChildren?: boolean): Observable<FocusOrigin>;
101+
102+
monitor(element: HTMLElement | ElementRef<HTMLElement>,
103+
checkChildren: boolean = false): Observable<FocusOrigin> {
92104
// Do nothing if we're not on the browser platform.
93105
if (!this._platform.isBrowser) {
94106
return observableOf(null);
95107
}
108+
109+
const nativeElement = this._getNativeElement(element);
110+
96111
// Check if we're already monitoring this element.
97-
if (this._elementInfo.has(element)) {
98-
let cachedInfo = this._elementInfo.get(element);
112+
if (this._elementInfo.has(nativeElement)) {
113+
let cachedInfo = this._elementInfo.get(nativeElement);
99114
cachedInfo!.checkChildren = checkChildren;
100115
return cachedInfo!.subject.asObservable();
101116
}
@@ -106,21 +121,21 @@ export class FocusMonitor implements OnDestroy {
106121
checkChildren: checkChildren,
107122
subject: new Subject<FocusOrigin>()
108123
};
109-
this._elementInfo.set(element, info);
124+
this._elementInfo.set(nativeElement, info);
110125
this._incrementMonitoredElementCount();
111126

112127
// Start listening. We need to listen in capture phase since focus events don't bubble.
113-
let focusListener = (event: FocusEvent) => this._onFocus(event, element);
114-
let blurListener = (event: FocusEvent) => this._onBlur(event, element);
128+
let focusListener = (event: FocusEvent) => this._onFocus(event, nativeElement);
129+
let blurListener = (event: FocusEvent) => this._onBlur(event, nativeElement);
115130
this._ngZone.runOutsideAngular(() => {
116-
element.addEventListener('focus', focusListener, true);
117-
element.addEventListener('blur', blurListener, true);
131+
nativeElement.addEventListener('focus', focusListener, true);
132+
nativeElement.addEventListener('blur', blurListener, true);
118133
});
119134

120135
// Create an unlisten function for later.
121136
info.unlisten = () => {
122-
element.removeEventListener('focus', focusListener, true);
123-
element.removeEventListener('blur', blurListener, true);
137+
nativeElement.removeEventListener('focus', focusListener, true);
138+
nativeElement.removeEventListener('blur', blurListener, true);
124139
};
125140

126141
return info.subject.asObservable();
@@ -130,15 +145,24 @@ export class FocusMonitor implements OnDestroy {
130145
* Stops monitoring an element and removes all focus classes.
131146
* @param element The element to stop monitoring.
132147
*/
133-
stopMonitoring(element: HTMLElement): void {
134-
const elementInfo = this._elementInfo.get(element);
148+
stopMonitoring(element: HTMLElement): void;
149+
150+
/**
151+
* Stops monitoring an element and removes all focus classes.
152+
* @param element The element to stop monitoring.
153+
*/
154+
stopMonitoring(element: ElementRef<HTMLElement>): void;
155+
156+
stopMonitoring(element: HTMLElement | ElementRef<HTMLElement>): void {
157+
const nativeElement = this._getNativeElement(element);
158+
const elementInfo = this._elementInfo.get(nativeElement);
135159

136160
if (elementInfo) {
137161
elementInfo.unlisten();
138162
elementInfo.subject.complete();
139163

140-
this._setClasses(element);
141-
this._elementInfo.delete(element);
164+
this._setClasses(nativeElement);
165+
this._elementInfo.delete(nativeElement);
142166
this._decrementMonitoredElementCount();
143167
}
144168
}
@@ -370,6 +394,10 @@ export class FocusMonitor implements OnDestroy {
370394
this._unregisterGlobalListeners = () => {};
371395
}
372396
}
397+
398+
private _getNativeElement(element: HTMLElement | ElementRef<HTMLElement>): HTMLElement {
399+
return element instanceof ElementRef ? element.nativeElement : element;
400+
}
373401
}
374402

375403

@@ -391,13 +419,13 @@ export class CdkMonitorFocus implements OnDestroy {
391419

392420
constructor(private _elementRef: ElementRef, private _focusMonitor: FocusMonitor) {
393421
this._monitorSubscription = this._focusMonitor.monitor(
394-
this._elementRef.nativeElement,
422+
this._elementRef,
395423
this._elementRef.nativeElement.hasAttribute('cdkMonitorSubtreeFocus'))
396424
.subscribe(origin => this.cdkFocusChange.emit(origin));
397425
}
398426

399427
ngOnDestroy() {
400-
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
428+
this._focusMonitor.stopMonitoring(this._elementRef);
401429
this._monitorSubscription.unsubscribe();
402430
}
403431
}

src/lib/button-toggle/button-toggle.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,11 +433,11 @@ export class MatButtonToggle extends _MatButtonToggleMixinBase implements OnInit
433433
this.checked = true;
434434
}
435435

436-
this._focusMonitor.monitor(this._elementRef.nativeElement, true);
436+
this._focusMonitor.monitor(this._elementRef, true);
437437
}
438438

439439
ngOnDestroy() {
440-
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
440+
this._focusMonitor.stopMonitoring(this._elementRef);
441441
}
442442

443443
/** Focuses the button. */

src/lib/button/button.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@ export class MatButton extends _MatButtonMixinBase
106106
}
107107
}
108108

109-
this._focusMonitor.monitor(this._elementRef.nativeElement, true);
109+
this._focusMonitor.monitor(this._elementRef, true);
110110

111111
if (this.isRoundButton) {
112112
this.color = DEFAULT_ROUND_BUTTON_COLOR;
113113
}
114114
}
115115

116116
ngOnDestroy() {
117-
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
117+
this._focusMonitor.stopMonitoring(this._elementRef);
118118
}
119119

120120
/** Focuses the button. */

src/lib/checkbox/checkbox.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,12 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
197197

198198
ngAfterViewInit() {
199199
this._focusMonitor
200-
.monitor(this._inputElement.nativeElement)
200+
.monitor(this._inputElement)
201201
.subscribe(focusOrigin => this._onInputFocusChange(focusOrigin));
202202
}
203203

204204
ngOnDestroy() {
205-
this._focusMonitor.stopMonitoring(this._inputElement.nativeElement);
205+
this._focusMonitor.stopMonitoring(this._inputElement);
206206
}
207207

208208
/**

src/lib/expansion/expansion-panel-header.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class MatExpansionPanelHeader implements OnDestroy {
8383
)
8484
.subscribe(() => this._changeDetectorRef.markForCheck());
8585

86-
_focusMonitor.monitor(_element.nativeElement);
86+
_focusMonitor.monitor(_element);
8787
}
8888

8989
/** Height of the header while the panel is expanded. */
@@ -133,7 +133,7 @@ export class MatExpansionPanelHeader implements OnDestroy {
133133

134134
ngOnDestroy() {
135135
this._parentChangeSubscription.unsubscribe();
136-
this._focusMonitor.stopMonitoring(this._element.nativeElement);
136+
this._focusMonitor.stopMonitoring(this._element);
137137
}
138138
}
139139

src/lib/menu/menu-item.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export class MatMenuItem extends _MatMenuItemMixinBase
8282
// Start monitoring the element so it gets the appropriate focused classes. We want
8383
// to show the focus style for menu items only when the focus was not caused by a
8484
// mouse or touch interaction.
85-
_focusMonitor.monitor(this._getHostElement(), false);
85+
_focusMonitor.monitor(this._elementRef, false);
8686
}
8787

8888
if (_parentMenu && _parentMenu.addItem) {
@@ -103,7 +103,7 @@ export class MatMenuItem extends _MatMenuItemMixinBase
103103

104104
ngOnDestroy() {
105105
if (this._focusMonitor) {
106-
this._focusMonitor.stopMonitoring(this._getHostElement());
106+
this._focusMonitor.stopMonitoring(this._elementRef);
107107
}
108108

109109
if (this._parentMenu && this._parentMenu.removeItem) {

src/lib/radio/radio.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,12 +518,12 @@ export class MatRadioButton extends _MatRadioButtonMixinBase
518518

519519
ngAfterViewInit() {
520520
this._focusMonitor
521-
.monitor(this._inputElement.nativeElement)
521+
.monitor(this._inputElement)
522522
.subscribe(focusOrigin => this._onInputFocusChange(focusOrigin));
523523
}
524524

525525
ngOnDestroy() {
526-
this._focusMonitor.stopMonitoring(this._inputElement.nativeElement);
526+
this._focusMonitor.stopMonitoring(this._inputElement);
527527
this._removeUniqueSelectionListener();
528528
}
529529

src/lib/slide-toggle/slide-toggle.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
194194

195195
ngAfterContentInit() {
196196
this._focusMonitor
197-
.monitor(this._elementRef.nativeElement, true)
197+
.monitor(this._elementRef, true)
198198
.subscribe(focusOrigin => {
199199
if (!focusOrigin) {
200200
// When a focused element becomes disabled, the browser *immediately* fires a blur event.
@@ -208,7 +208,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
208208
}
209209

210210
ngOnDestroy() {
211-
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
211+
this._focusMonitor.stopMonitoring(this._elementRef);
212212
}
213213

214214
/** Method being called whenever the underlying input emits a change event. */

src/lib/slider/slider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ export class MatSlider extends _MatSliderMixinBase
465465

466466
ngOnInit() {
467467
this._focusMonitor
468-
.monitor(this._elementRef.nativeElement, true)
468+
.monitor(this._elementRef, true)
469469
.subscribe((origin: FocusOrigin) => {
470470
this._isActive = !!origin && origin !== 'keyboard';
471471
this._changeDetectorRef.detectChanges();
@@ -478,7 +478,7 @@ export class MatSlider extends _MatSliderMixinBase
478478
}
479479

480480
ngOnDestroy() {
481-
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
481+
this._focusMonitor.stopMonitoring(this._elementRef);
482482
this._dirChangeSubscription.unsubscribe();
483483
}
484484

src/lib/stepper/step-header.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ export class MatStepHeader implements OnDestroy {
6464
private _focusMonitor: FocusMonitor,
6565
private _element: ElementRef,
6666
changeDetectorRef: ChangeDetectorRef) {
67-
_focusMonitor.monitor(_element.nativeElement, true);
67+
_focusMonitor.monitor(_element, true);
6868
this._intlSubscription = _intl.changes.subscribe(() => changeDetectorRef.markForCheck());
6969
}
7070

7171
ngOnDestroy() {
7272
this._intlSubscription.unsubscribe();
73-
this._focusMonitor.stopMonitoring(this._element.nativeElement);
73+
this._focusMonitor.stopMonitoring(this._element);
7474
}
7575

7676
/** Returns string label of given step if it is a text label. */

src/lib/tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,15 @@ export class MatTabLink extends _MatTabLinkMixinBase
247247
}
248248

249249
if (_focusMonitor) {
250-
_focusMonitor.monitor(_elementRef.nativeElement);
250+
_focusMonitor.monitor(_elementRef);
251251
}
252252
}
253253

254254
ngOnDestroy() {
255255
this._tabLinkRipple._removeTriggerEvents();
256256

257257
if (this._focusMonitor) {
258-
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
258+
this._focusMonitor.stopMonitoring(this._elementRef);
259259
}
260260
}
261261

src/lib/tooltip/tooltip.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ export class MatTooltip implements OnDestroy {
234234
element.style['webkitUserDrag'] = '';
235235
}
236236

237-
_focusMonitor.monitor(element).pipe(takeUntil(this._destroyed)).subscribe(origin => {
237+
_focusMonitor.monitor(_elementRef).pipe(takeUntil(this._destroyed)).subscribe(origin => {
238238
// Note that the focus monitor runs outside the Angular zone.
239239
if (!origin) {
240240
_ngZone.run(() => this.hide(0));
@@ -265,7 +265,7 @@ export class MatTooltip implements OnDestroy {
265265
this._destroyed.complete();
266266

267267
this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this.message);
268-
this._focusMonitor.stopMonitoring(this._elementRef.nativeElement);
268+
this._focusMonitor.stopMonitoring(this._elementRef);
269269
}
270270

271271
/** Shows the tooltip after the delay in ms, defaults to tooltip-delay-show or 0ms if no input */

src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via-example.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ export class FocusMonitorFocusViaExample implements OnDestroy, OnInit {
2525
private ngZone: NgZone) {}
2626

2727
ngOnInit() {
28-
this.focusMonitor.monitor(this.monitoredEl.nativeElement)
28+
this.focusMonitor.monitor(this.monitoredEl)
2929
.subscribe(origin => this.ngZone.run(() => {
3030
this.origin = this.formatOrigin(origin);
3131
this.cdr.markForCheck();
3232
}));
3333
}
3434

3535
ngOnDestroy() {
36-
this.focusMonitor.stopMonitoring(this.monitoredEl.nativeElement);
36+
this.focusMonitor.stopMonitoring(this.monitoredEl);
3737
}
3838

3939
formatOrigin(origin: FocusOrigin): string {

src/material-examples/focus-monitor-overview/focus-monitor-overview-example.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,21 @@ export class FocusMonitorOverviewExample implements OnDestroy, OnInit {
2727
private ngZone: NgZone) {}
2828

2929
ngOnInit() {
30-
this.focusMonitor.monitor(this.element.nativeElement)
30+
this.focusMonitor.monitor(this.element)
3131
.subscribe(origin => this.ngZone.run(() => {
3232
this.elementOrigin = this.formatOrigin(origin);
3333
this.cdr.markForCheck();
3434
}));
35-
this.focusMonitor.monitor(this.subtree.nativeElement, true)
35+
this.focusMonitor.monitor(this.subtree, true)
3636
.subscribe(origin => this.ngZone.run(() => {
3737
this.subtreeOrigin = this.formatOrigin(origin);
3838
this.cdr.markForCheck();
3939
}));
4040
}
4141

4242
ngOnDestroy() {
43-
this.focusMonitor.stopMonitoring(this.element.nativeElement);
44-
this.focusMonitor.stopMonitoring(this.subtree.nativeElement);
43+
this.focusMonitor.stopMonitoring(this.element);
44+
this.focusMonitor.stopMonitoring(this.subtree);
4545
}
4646

4747
formatOrigin(origin: FocusOrigin): string {

src/material-examples/form-field-custom-control/form-field-custom-control-example.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,15 @@ export class MyTelInput implements MatFormFieldControl<MyTel>, OnDestroy {
8888
subscriber: '',
8989
});
9090

91-
fm.monitor(elRef.nativeElement, true).subscribe(origin => {
91+
fm.monitor(elRef, true).subscribe(origin => {
9292
this.focused = !!origin;
9393
this.stateChanges.next();
9494
});
9595
}
9696

9797
ngOnDestroy() {
9898
this.stateChanges.complete();
99-
this.fm.stopMonitoring(this.elRef.nativeElement);
99+
this.fm.stopMonitoring(this.elRef);
100100
}
101101

102102
setDescribedByIds(ids: string[]) {

0 commit comments

Comments
 (0)