forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathevent_delegation_utils.ts
More file actions
130 lines (115 loc) · 4.13 KB
/
event_delegation_utils.ts
File metadata and controls
130 lines (115 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
// tslint:disable:no-duplicate-imports
import {
EventContract,
EventContractContainer,
EventDispatcher,
isEarlyEventType,
getActionCache,
registerDispatcher,
} from '@angular/core/primitives/event-dispatch';
import {Attribute} from '@angular/core/primitives/event-dispatch';
import {Injectable, InjectionToken, Injector, inject} from './di';
import {RElement} from './render3/interfaces/renderer_dom';
import {EVENT_REPLAY_ENABLED_DEFAULT, IS_EVENT_REPLAY_ENABLED} from './hydration/tokens';
import {OnDestroy} from './interface/lifecycle_hooks';
declare global {
interface Element {
__jsaction_fns: Map<string, Function[]> | undefined;
}
}
export function invokeRegisteredListeners(event: Event) {
const handlerFns = (event.currentTarget as Element)?.__jsaction_fns?.get(event.type);
if (!handlerFns) {
return;
}
for (const handler of handlerFns) {
handler(event);
}
}
export function setJSActionAttributes(nativeElement: Element, eventTypes: string[]) {
if (!eventTypes.length) {
return;
}
const parts = eventTypes.reduce((prev, curr) => prev + curr + ':;', '');
const existingAttr = nativeElement.getAttribute(Attribute.JSACTION);
nativeElement.setAttribute(Attribute.JSACTION, `${existingAttr ?? ''}${parts}`);
}
export const sharedStashFunction = (rEl: RElement, eventType: string, listenerFn: Function) => {
const el = rEl as unknown as Element;
const eventListenerMap = el.__jsaction_fns ?? new Map();
const eventListeners = eventListenerMap.get(eventType) ?? [];
eventListeners.push(listenerFn);
eventListenerMap.set(eventType, eventListeners);
el.__jsaction_fns = eventListenerMap;
};
export const removeListeners = (el: Element) => {
el.removeAttribute(Attribute.JSACTION);
el.__jsaction_fns = undefined;
};
export interface EventContractDetails {
instance?: EventContract;
}
export const JSACTION_EVENT_CONTRACT = new InjectionToken<EventContractDetails>(
ngDevMode ? 'EVENT_CONTRACT_DETAILS' : '',
{
providedIn: 'root',
factory: () => ({}),
},
);
export const GLOBAL_EVENT_DELEGATION = new InjectionToken<GlobalEventDelegation>(
ngDevMode ? 'GLOBAL_EVENT_DELEGATION' : '',
);
/**
* This class is the delegate for `EventDelegationPlugin`. It represents the
* noop version of this class, with the enabled version set when
* `provideGlobalEventDelegation` is called.
*/
@Injectable()
export class GlobalEventDelegation implements OnDestroy {
private eventContractDetails = inject(JSACTION_EVENT_CONTRACT);
ngOnDestroy() {
this.eventContractDetails.instance?.cleanUp();
}
supports(eventType: string): boolean {
return isEarlyEventType(eventType);
}
addEventListener(element: HTMLElement, eventType: string, handler: Function): Function {
// Note: contrary to the type, Window and Document can be passed in
// as well.
if (element.nodeType === Node.ELEMENT_NODE) {
this.eventContractDetails.instance!.addEvent(eventType);
getActionCache(element)[eventType] = '';
sharedStashFunction(element, eventType, handler);
} else {
element.addEventListener(eventType, handler as EventListener);
}
return () => this.removeEventListener(element, eventType, handler);
}
removeEventListener(element: HTMLElement, eventType: string, callback: Function): void {
if (element.nodeType === Node.ELEMENT_NODE) {
getActionCache(element)[eventType] = undefined;
} else {
element.removeEventListener(eventType, callback as EventListener);
}
}
}
export const initGlobalEventDelegation = (
eventContractDetails: EventContractDetails,
injector: Injector,
) => {
if (injector.get(IS_EVENT_REPLAY_ENABLED, EVENT_REPLAY_ENABLED_DEFAULT)) {
return;
}
const eventContract = (eventContractDetails.instance = new EventContract(
new EventContractContainer(document.body),
));
const dispatcher = new EventDispatcher(invokeRegisteredListeners, /** clickModSupport */ false);
registerDispatcher(eventContract, dispatcher);
};