Skip to content

Commit edb7612

Browse files
committed
Revert "refactor(multiple): use renderer for manually-bound events with options"
This reverts commit c92253d.
1 parent 177ee6c commit edb7612

File tree

13 files changed

+335
-460
lines changed

13 files changed

+335
-460
lines changed

src/cdk-experimental/popover-edit/table-directives.ts

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@ import {
1919
TemplateRef,
2020
ViewContainerRef,
2121
inject,
22-
Renderer2,
23-
ListenerOptions,
2422
} from '@angular/core';
25-
import {merge, Observable, Subject} from 'rxjs';
23+
import {fromEvent, fromEventPattern, merge, Subject} from 'rxjs';
2624
import {
2725
debounceTime,
2826
filter,
@@ -46,7 +44,6 @@ import {
4644
} from './focus-escape-notifier';
4745
import {closest} from './polyfill';
4846
import {EditRef} from './edit-ref';
49-
import {_bindEventWithOptions} from '@angular/cdk/platform';
5047

5148
/**
5249
* Describes the number of columns before and after the originating cell that the
@@ -76,7 +73,6 @@ export class CdkEditable implements AfterViewInit, OnDestroy {
7673
inject<EditEventDispatcher<EditRef<unknown>>>(EditEventDispatcher);
7774
protected readonly focusDispatcher = inject(FocusDispatcher);
7875
protected readonly ngZone = inject(NgZone);
79-
private readonly _renderer = inject(Renderer2);
8076

8177
protected readonly destroyed = new Subject<void>();
8278

@@ -98,37 +94,20 @@ export class CdkEditable implements AfterViewInit, OnDestroy {
9894
this._rendered.complete();
9995
}
10096

101-
private _observableFromEvent<T extends Event>(
102-
element: Element,
103-
name: string,
104-
options?: ListenerOptions,
105-
) {
106-
return new Observable<T>(subscriber => {
107-
const handler = (event: T) => subscriber.next(event);
108-
const cleanup = options
109-
? _bindEventWithOptions(this._renderer, element, name, handler, options)
110-
: this._renderer.listen(element, name, handler, options);
111-
return () => {
112-
cleanup();
113-
subscriber.complete();
114-
};
115-
});
116-
}
117-
11897
private _listenForTableEvents(): void {
11998
const element = this.elementRef.nativeElement;
12099
const toClosest = (selector: string) =>
121100
map((event: UIEvent) => closest(event.target, selector));
122101

123102
this.ngZone.runOutsideAngular(() => {
124103
// Track mouse movement over the table to hide/show hover content.
125-
this._observableFromEvent<MouseEvent>(element, 'mouseover')
104+
fromEvent<MouseEvent>(element, 'mouseover')
126105
.pipe(toClosest(ROW_SELECTOR), takeUntil(this.destroyed))
127106
.subscribe(this.editEventDispatcher.hovering);
128-
this._observableFromEvent<MouseEvent>(element, 'mouseleave')
107+
fromEvent<MouseEvent>(element, 'mouseleave')
129108
.pipe(mapTo(null), takeUntil(this.destroyed))
130109
.subscribe(this.editEventDispatcher.hovering);
131-
this._observableFromEvent<MouseEvent>(element, 'mousemove')
110+
fromEvent<MouseEvent>(element, 'mousemove')
132111
.pipe(
133112
throttleTime(MOUSE_MOVE_THROTTLE_TIME_MS),
134113
toClosest(ROW_SELECTOR),
@@ -137,15 +116,19 @@ export class CdkEditable implements AfterViewInit, OnDestroy {
137116
.subscribe(this.editEventDispatcher.mouseMove);
138117

139118
// Track focus within the table to hide/show/make focusable hover content.
140-
this._observableFromEvent<FocusEvent>(element, 'focus', {capture: true})
119+
fromEventPattern<FocusEvent>(
120+
handler => element.addEventListener('focus', handler, true),
121+
handler => element.removeEventListener('focus', handler, true),
122+
)
141123
.pipe(toClosest(ROW_SELECTOR), share(), takeUntil(this.destroyed))
142124
.subscribe(this.editEventDispatcher.focused);
143125

144126
merge(
145-
this._observableFromEvent(element, 'blur', {capture: true}),
146-
this._observableFromEvent<KeyboardEvent>(element, 'keydown').pipe(
147-
filter(event => event.key === 'Escape'),
127+
fromEventPattern<FocusEvent>(
128+
handler => element.addEventListener('blur', handler, true),
129+
handler => element.removeEventListener('blur', handler, true),
148130
),
131+
fromEvent<KeyboardEvent>(element, 'keydown').pipe(filter(event => event.key === 'Escape')),
149132
)
150133
.pipe(mapTo(null), share(), takeUntil(this.destroyed))
151134
.subscribe(this.editEventDispatcher.focused);
@@ -167,7 +150,7 @@ export class CdkEditable implements AfterViewInit, OnDestroy {
167150
)
168151
.subscribe(this.editEventDispatcher.allRows);
169152

170-
this._observableFromEvent<KeyboardEvent>(element, 'keydown')
153+
fromEvent<KeyboardEvent>(element, 'keydown')
171154
.pipe(
172155
filter(event => event.key === 'Enter'),
173156
toClosest(CELL_SELECTOR),
@@ -176,7 +159,7 @@ export class CdkEditable implements AfterViewInit, OnDestroy {
176159
.subscribe(this.editEventDispatcher.editing);
177160

178161
// Keydown must be used here or else key auto-repeat does not work properly on some platforms.
179-
this._observableFromEvent<KeyboardEvent>(element, 'keydown')
162+
fromEvent<KeyboardEvent>(element, 'keydown')
180163
.pipe(takeUntil(this.destroyed))
181164
.subscribe(this.focusDispatcher.keyObserver);
182165
});

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

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
import {
1010
Platform,
11+
normalizePassiveListenerOptions,
1112
_getShadowRoot,
1213
_getEventTarget,
13-
_bindEventWithOptions,
1414
} from '@angular/cdk/platform';
1515
import {
1616
Directive,
@@ -23,7 +23,6 @@ import {
2323
Output,
2424
AfterViewInit,
2525
inject,
26-
RendererFactory2,
2726
} from '@angular/core';
2827
import {Observable, of as observableOf, Subject, Subscription} from 'rxjs';
2928
import {takeUntil} from 'rxjs/operators';
@@ -77,18 +76,16 @@ type MonitoredElementInfo = {
7776
* Event listener options that enable capturing and also
7877
* mark the listener as passive if the browser supports it.
7978
*/
80-
const captureEventListenerOptions = {
79+
const captureEventListenerOptions = normalizePassiveListenerOptions({
8180
passive: true,
8281
capture: true,
83-
};
82+
});
8483

8584
/** Monitors mouse and keyboard events to determine the cause of focus events. */
8685
@Injectable({providedIn: 'root'})
8786
export class FocusMonitor implements OnDestroy {
8887
private _ngZone = inject(NgZone);
8988
private _platform = inject(Platform);
90-
private _renderer = inject(RendererFactory2).createRenderer(null, null);
91-
private _cleanupWindowFocus: (() => void) | undefined;
9289
private readonly _inputModalityDetector = inject(InputModalityDetector);
9390

9491
/** The focus origin that the next focus event is a result of. */
@@ -124,13 +121,7 @@ export class FocusMonitor implements OnDestroy {
124121
* handlers differently from the rest of the events, because the browser won't emit events
125122
* to the document when focus moves inside of a shadow root.
126123
*/
127-
private _rootNodeFocusListeners = new Map<
128-
HTMLElement | Document | ShadowRoot,
129-
{
130-
count: number;
131-
cleanups: (() => void)[];
132-
}
133-
>();
124+
private _rootNodeFocusListenerCount = new Map<HTMLElement | Document | ShadowRoot, number>();
134125

135126
/**
136127
* The specified detection mode, used for attributing the origin of a focus
@@ -316,6 +307,12 @@ export class FocusMonitor implements OnDestroy {
316307
return this._document || document;
317308
}
318309

310+
/** Use defaultView of injected document if available or fallback to global window reference */
311+
private _getWindow(): Window {
312+
const doc = this._getDocument();
313+
return doc.defaultView || window;
314+
}
315+
319316
private _getFocusOrigin(focusEventTarget: HTMLElement | null): FocusOrigin {
320317
if (this._origin) {
321318
// If the origin was realized via a touch interaction, we need to perform additional checks
@@ -471,45 +468,32 @@ export class FocusMonitor implements OnDestroy {
471468
}
472469

473470
const rootNode = elementInfo.rootNode;
474-
const listeners = this._rootNodeFocusListeners.get(rootNode);
471+
const rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode) || 0;
475472

476-
if (listeners) {
477-
listeners.count++;
478-
} else {
473+
if (!rootNodeFocusListeners) {
479474
this._ngZone.runOutsideAngular(() => {
480-
this._rootNodeFocusListeners.set(rootNode, {
481-
count: 1,
482-
cleanups: [
483-
_bindEventWithOptions(
484-
this._renderer,
485-
rootNode,
486-
'focus',
487-
this._rootNodeFocusAndBlurListener,
488-
captureEventListenerOptions,
489-
),
490-
_bindEventWithOptions(
491-
this._renderer,
492-
rootNode,
493-
'blur',
494-
this._rootNodeFocusAndBlurListener,
495-
captureEventListenerOptions,
496-
),
497-
],
498-
});
475+
rootNode.addEventListener(
476+
'focus',
477+
this._rootNodeFocusAndBlurListener,
478+
captureEventListenerOptions,
479+
);
480+
rootNode.addEventListener(
481+
'blur',
482+
this._rootNodeFocusAndBlurListener,
483+
captureEventListenerOptions,
484+
);
499485
});
500486
}
501487

488+
this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners + 1);
489+
502490
// Register global listeners when first element is monitored.
503491
if (++this._monitoredElementCount === 1) {
504492
// Note: we listen to events in the capture phase so we
505493
// can detect them even if the user stops propagation.
506494
this._ngZone.runOutsideAngular(() => {
507-
this._cleanupWindowFocus?.();
508-
this._cleanupWindowFocus = this._renderer.listen(
509-
'window',
510-
'focus',
511-
this._windowFocusListener,
512-
);
495+
const window = this._getWindow();
496+
window.addEventListener('focus', this._windowFocusListener);
513497
});
514498

515499
// The InputModalityDetector is also just a collection of global listeners.
@@ -522,20 +506,32 @@ export class FocusMonitor implements OnDestroy {
522506
}
523507

524508
private _removeGlobalListeners(elementInfo: MonitoredElementInfo) {
525-
const listeners = this._rootNodeFocusListeners.get(elementInfo.rootNode);
509+
const rootNode = elementInfo.rootNode;
526510

527-
if (listeners) {
528-
if (listeners.count > 1) {
529-
listeners.count--;
511+
if (this._rootNodeFocusListenerCount.has(rootNode)) {
512+
const rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode)!;
513+
514+
if (rootNodeFocusListeners > 1) {
515+
this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners - 1);
530516
} else {
531-
listeners.cleanups.forEach(cleanup => cleanup());
532-
this._rootNodeFocusListeners.delete(elementInfo.rootNode);
517+
rootNode.removeEventListener(
518+
'focus',
519+
this._rootNodeFocusAndBlurListener,
520+
captureEventListenerOptions,
521+
);
522+
rootNode.removeEventListener(
523+
'blur',
524+
this._rootNodeFocusAndBlurListener,
525+
captureEventListenerOptions,
526+
);
527+
this._rootNodeFocusListenerCount.delete(rootNode);
533528
}
534529
}
535530

536531
// Unregister global listeners when last element is unmonitored.
537532
if (!--this._monitoredElementCount) {
538-
this._cleanupWindowFocus?.();
533+
const window = this._getWindow();
534+
window.removeEventListener('focus', this._windowFocusListener);
539535

540536
// Equivalently, stop our InputModalityDetector subscription.
541537
this._stopInputModalityDetector.next();

src/cdk/a11y/input-modality/input-modality-detector.ts

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,8 @@
77
*/
88

99
import {ALT, CONTROL, MAC_META, META, SHIFT} from '@angular/cdk/keycodes';
10-
import {
11-
Injectable,
12-
InjectionToken,
13-
OnDestroy,
14-
NgZone,
15-
inject,
16-
RendererFactory2,
17-
} from '@angular/core';
18-
import {Platform, _bindEventWithOptions, _getEventTarget} from '@angular/cdk/platform';
10+
import {Injectable, InjectionToken, OnDestroy, NgZone, inject} from '@angular/core';
11+
import {normalizePassiveListenerOptions, Platform, _getEventTarget} from '@angular/cdk/platform';
1912
import {DOCUMENT} from '@angular/common';
2013
import {BehaviorSubject, Observable} from 'rxjs';
2114
import {distinctUntilChanged, skip} from 'rxjs/operators';
@@ -76,10 +69,10 @@ export const TOUCH_BUFFER_MS = 650;
7669
* Event listener options that enable capturing and also mark the listener as passive if the browser
7770
* supports it.
7871
*/
79-
const modalityEventListenerOptions = {
72+
const modalityEventListenerOptions = normalizePassiveListenerOptions({
8073
passive: true,
8174
capture: true,
82-
};
75+
});
8376

8477
/**
8578
* Service that detects the user's input modality.
@@ -98,7 +91,6 @@ const modalityEventListenerOptions = {
9891
@Injectable({providedIn: 'root'})
9992
export class InputModalityDetector implements OnDestroy {
10093
private readonly _platform = inject(Platform);
101-
private readonly _listenerCleanups: (() => void)[] | undefined;
10294

10395
/** Emits whenever an input modality is detected. */
10496
readonly modalityDetected: Observable<InputModality>;
@@ -201,38 +193,21 @@ export class InputModalityDetector implements OnDestroy {
201193
// If we're not in a browser, this service should do nothing, as there's no relevant input
202194
// modality to detect.
203195
if (this._platform.isBrowser) {
204-
const renderer = inject(RendererFactory2).createRenderer(null, null);
205-
206-
this._listenerCleanups = ngZone.runOutsideAngular(() => {
207-
return [
208-
_bindEventWithOptions(
209-
renderer,
210-
document,
211-
'keydown',
212-
this._onKeydown,
213-
modalityEventListenerOptions,
214-
),
215-
_bindEventWithOptions(
216-
renderer,
217-
document,
218-
'mousedown',
219-
this._onMousedown,
220-
modalityEventListenerOptions,
221-
),
222-
_bindEventWithOptions(
223-
renderer,
224-
document,
225-
'touchstart',
226-
this._onTouchstart,
227-
modalityEventListenerOptions,
228-
),
229-
];
196+
ngZone.runOutsideAngular(() => {
197+
document.addEventListener('keydown', this._onKeydown, modalityEventListenerOptions);
198+
document.addEventListener('mousedown', this._onMousedown, modalityEventListenerOptions);
199+
document.addEventListener('touchstart', this._onTouchstart, modalityEventListenerOptions);
230200
});
231201
}
232202
}
233203

234204
ngOnDestroy() {
235205
this._modality.complete();
236-
this._listenerCleanups?.forEach(cleanup => cleanup());
206+
207+
if (this._platform.isBrowser) {
208+
document.removeEventListener('keydown', this._onKeydown, modalityEventListenerOptions);
209+
document.removeEventListener('mousedown', this._onMousedown, modalityEventListenerOptions);
210+
document.removeEventListener('touchstart', this._onTouchstart, modalityEventListenerOptions);
211+
}
237212
}
238213
}

0 commit comments

Comments
 (0)