@reckona/mreact-compat 0.0.65 → 0.0.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/events.ts ADDED
@@ -0,0 +1,384 @@
1
+ import { runWithEventPriority } from "./hooks.js";
2
+ import { getAppliedEventHandler } from "./event-listeners.js";
3
+ import type { SyntheticEvent } from "./event-types.js";
4
+
5
+ const delegatedRootListeners = new WeakMap<Element, Set<string>>();
6
+ const logicalEventParents = new WeakMap<Element, ParentNode>();
7
+
8
+ const reactPropToNativeEvent = new Map<string, string[]>([
9
+ ["onAnimationEnd", ["animationend"]],
10
+ ["onAnimationIteration", ["animationiteration"]],
11
+ ["onAnimationStart", ["animationstart"]],
12
+ ["onBeforeInput", ["beforeinput"]],
13
+ ["onCompositionEnd", ["compositionend"]],
14
+ ["onCompositionStart", ["compositionstart"]],
15
+ ["onCompositionUpdate", ["compositionupdate"]],
16
+ ["onContextMenu", ["contextmenu"]],
17
+ ["onDoubleClick", ["dblclick"]],
18
+ ["onDragEnd", ["dragend"]],
19
+ ["onDragEnter", ["dragenter"]],
20
+ ["onDragExit", ["dragexit"]],
21
+ ["onDragLeave", ["dragleave"]],
22
+ ["onDragOver", ["dragover"]],
23
+ ["onDragStart", ["dragstart"]],
24
+ ["onDrop", ["drop"]],
25
+ ["onFocus", ["focusin"]],
26
+ ["onBlur", ["focusout"]],
27
+ ["onGotPointerCapture", ["gotpointercapture"]],
28
+ ["onLostPointerCapture", ["lostpointercapture"]],
29
+ ["onMouseEnter", ["mouseover"]],
30
+ ["onMouseLeave", ["mouseout"]],
31
+ ["onPointerCancel", ["pointercancel"]],
32
+ ["onPointerDown", ["pointerdown"]],
33
+ ["onPointerEnter", ["pointerover"]],
34
+ ["onPointerLeave", ["pointerout"]],
35
+ ["onPointerMove", ["pointermove"]],
36
+ ["onPointerOut", ["pointerout"]],
37
+ ["onPointerOver", ["pointerover"]],
38
+ ["onPointerUp", ["pointerup"]],
39
+ ["onTouchCancel", ["touchcancel"]],
40
+ ["onTouchEnd", ["touchend"]],
41
+ ["onTouchMove", ["touchmove"]],
42
+ ["onTouchStart", ["touchstart"]],
43
+ ["onTransitionEnd", ["transitionend"]],
44
+ ["onChange", ["change", "input"]],
45
+ ]);
46
+
47
+ const nativeEventToReactProps = new Map<string, string[]>([
48
+ ["animationend", ["onAnimationEnd"]],
49
+ ["animationiteration", ["onAnimationIteration"]],
50
+ ["animationstart", ["onAnimationStart"]],
51
+ ["beforeinput", ["onBeforeInput"]],
52
+ ["compositionend", ["onCompositionEnd"]],
53
+ ["compositionstart", ["onCompositionStart"]],
54
+ ["compositionupdate", ["onCompositionUpdate"]],
55
+ ["contextmenu", ["onContextMenu"]],
56
+ ["dblclick", ["onDoubleClick"]],
57
+ ["dragend", ["onDragEnd"]],
58
+ ["dragenter", ["onDragEnter"]],
59
+ ["dragexit", ["onDragExit"]],
60
+ ["dragleave", ["onDragLeave"]],
61
+ ["dragover", ["onDragOver"]],
62
+ ["dragstart", ["onDragStart"]],
63
+ ["drop", ["onDrop"]],
64
+ ["focusin", ["onFocus"]],
65
+ ["focusout", ["onBlur"]],
66
+ ["gotpointercapture", ["onGotPointerCapture"]],
67
+ ["lostpointercapture", ["onLostPointerCapture"]],
68
+ ["input", ["onInput", "onChange"]],
69
+ ["keydown", ["onKeyDown"]],
70
+ ["keyup", ["onKeyUp"]],
71
+ ["mousedown", ["onMouseDown"]],
72
+ ["mousemove", ["onMouseMove"]],
73
+ ["mouseup", ["onMouseUp"]],
74
+ ["mouseout", ["onMouseOut"]],
75
+ ["mouseover", ["onMouseOver"]],
76
+ ["pointercancel", ["onPointerCancel"]],
77
+ ["pointerdown", ["onPointerDown"]],
78
+ ["pointermove", ["onPointerMove"]],
79
+ ["pointerout", ["onPointerOut"]],
80
+ ["pointerover", ["onPointerOver"]],
81
+ ["pointerup", ["onPointerUp"]],
82
+ ["touchcancel", ["onTouchCancel"]],
83
+ ["touchend", ["onTouchEnd"]],
84
+ ["touchmove", ["onTouchMove"]],
85
+ ["touchstart", ["onTouchStart"]],
86
+ ["transitionend", ["onTransitionEnd"]],
87
+ ]);
88
+
89
+ export function toEventNames(propName: string): string[] {
90
+ const basePropName = propName.endsWith("Capture")
91
+ ? propName.slice(0, -"Capture".length)
92
+ : propName;
93
+ return reactPropToNativeEvent.get(basePropName) ?? [
94
+ basePropName.slice(2).toLowerCase(),
95
+ ];
96
+ }
97
+
98
+ export function toEventPropNames(eventName: string): string[] {
99
+ const propNames = nativeEventToReactProps.get(eventName);
100
+ if (propNames !== undefined) {
101
+ return propNames;
102
+ }
103
+ const propName = `on${eventName.slice(0, 1).toUpperCase()}${eventName.slice(1)}`;
104
+ return [propName];
105
+ }
106
+
107
+ export function setLogicalEventParent(
108
+ container: Element,
109
+ parent: ParentNode,
110
+ ): void {
111
+ logicalEventParents.set(container, parent);
112
+ }
113
+
114
+ export function getEventPriority(
115
+ eventName: string,
116
+ ): "discrete" | "continuous" | "default" {
117
+ if (discreteEventNames.has(eventName)) {
118
+ return "discrete";
119
+ }
120
+
121
+ if (continuousEventNames.has(eventName)) {
122
+ return "continuous";
123
+ }
124
+
125
+ return "default";
126
+ }
127
+
128
+ const discreteEventNames = new Set([
129
+ "beforeinput",
130
+ "change",
131
+ "click",
132
+ "dblclick",
133
+ "focusin",
134
+ "focusout",
135
+ "input",
136
+ "keydown",
137
+ "keyup",
138
+ "mousedown",
139
+ "mouseup",
140
+ "pointerdown",
141
+ "pointerup",
142
+ "submit",
143
+ "touchcancel",
144
+ "touchend",
145
+ "touchstart",
146
+ ]);
147
+
148
+ const continuousEventNames = new Set([
149
+ "drag",
150
+ "dragenter",
151
+ "dragleave",
152
+ "dragover",
153
+ "mousemove",
154
+ "mouseout",
155
+ "mouseover",
156
+ "pointermove",
157
+ "pointerout",
158
+ "pointerover",
159
+ "scroll",
160
+ "touchmove",
161
+ "wheel",
162
+ ]);
163
+
164
+ export function ensureDelegatedEventListener(
165
+ root: Element,
166
+ eventName: string,
167
+ ): void {
168
+ const listeners = delegatedRootListeners.get(root) ?? new Set<string>();
169
+
170
+ if (listeners.has(eventName)) {
171
+ return;
172
+ }
173
+
174
+ listeners.add(eventName);
175
+ delegatedRootListeners.set(root, listeners);
176
+ root.addEventListener(eventName, (event) => {
177
+ runWithEventPriority(getEventPriority(eventName), () => {
178
+ dispatchDelegatedEvent(root, eventName, event);
179
+ });
180
+ });
181
+ }
182
+
183
+ function dispatchDelegatedEvent(
184
+ root: Element,
185
+ eventName: string,
186
+ event: Event,
187
+ ): void {
188
+ const path = getEventPath(root, event);
189
+ const propNames = toEventPropNames(eventName);
190
+ const state = {
191
+ defaultPrevented: event.defaultPrevented,
192
+ propagationStopped: false,
193
+ };
194
+
195
+ for (let index = path.length - 1; index >= 0; index -= 1) {
196
+ const target = path[index] as HTMLElement;
197
+ dispatchEventPropNames(propNames, "capture", event, target, state);
198
+
199
+ if (state.propagationStopped) {
200
+ return;
201
+ }
202
+ }
203
+
204
+ if (eventName === "mouseover") {
205
+ for (let index = path.length - 1; index >= 0; index -= 1) {
206
+ const target = path[index] as HTMLElement;
207
+ dispatchMouseTransitionEvent("onMouseEnter", event, target, state);
208
+
209
+ if (state.propagationStopped) {
210
+ return;
211
+ }
212
+ }
213
+ }
214
+
215
+ if (eventName === "mouseout") {
216
+ for (const target of path) {
217
+ dispatchMouseTransitionEvent("onMouseLeave", event, target, state);
218
+
219
+ if (state.propagationStopped) {
220
+ return;
221
+ }
222
+ }
223
+ }
224
+
225
+ if (eventName === "pointerover") {
226
+ for (let index = path.length - 1; index >= 0; index -= 1) {
227
+ const target = path[index] as HTMLElement;
228
+ dispatchPointerTransitionEvent("onPointerEnter", event, target, state);
229
+
230
+ if (state.propagationStopped) {
231
+ return;
232
+ }
233
+ }
234
+ }
235
+
236
+ if (eventName === "pointerout") {
237
+ for (const target of path) {
238
+ dispatchPointerTransitionEvent("onPointerLeave", event, target, state);
239
+
240
+ if (state.propagationStopped) {
241
+ return;
242
+ }
243
+ }
244
+ }
245
+
246
+ for (const target of path) {
247
+ dispatchEventPropNames(propNames, "bubble", event, target, state);
248
+
249
+ if (state.propagationStopped) {
250
+ return;
251
+ }
252
+ }
253
+ }
254
+
255
+ function dispatchPointerTransitionEvent(
256
+ propName: "onPointerEnter" | "onPointerLeave",
257
+ event: Event,
258
+ target: HTMLElement,
259
+ state: { defaultPrevented: boolean; propagationStopped: boolean },
260
+ ): void {
261
+ if (isInternalMouseTransition(event, target)) {
262
+ return;
263
+ }
264
+
265
+ const handler = getAppliedEventHandler(target, propName);
266
+
267
+ if (handler !== undefined) {
268
+ handler(createSyntheticEvent(event, target, state, propName.slice(2).toLowerCase()));
269
+ }
270
+ }
271
+
272
+ function dispatchEventPropNames(
273
+ propNames: readonly string[],
274
+ phase: "capture" | "bubble",
275
+ event: Event,
276
+ target: HTMLElement,
277
+ state: { defaultPrevented: boolean; propagationStopped: boolean },
278
+ ): void {
279
+ for (const propName of propNames) {
280
+ const listenerName = phase === "capture" ? `${propName}Capture` : propName;
281
+ const handler = getAppliedEventHandler(target, listenerName);
282
+
283
+ if (handler !== undefined) {
284
+ handler(createSyntheticEvent(event, target, state));
285
+ }
286
+
287
+ if (state.propagationStopped) {
288
+ return;
289
+ }
290
+ }
291
+ }
292
+
293
+ function dispatchMouseTransitionEvent(
294
+ propName: "onMouseEnter" | "onMouseLeave",
295
+ event: Event,
296
+ target: HTMLElement,
297
+ state: { defaultPrevented: boolean; propagationStopped: boolean },
298
+ ): void {
299
+ if (isInternalMouseTransition(event, target)) {
300
+ return;
301
+ }
302
+
303
+ const handler = getAppliedEventHandler(target, propName);
304
+
305
+ if (handler !== undefined) {
306
+ handler(createSyntheticEvent(event, target, state));
307
+ }
308
+ }
309
+
310
+ function isInternalMouseTransition(event: Event, target: HTMLElement): boolean {
311
+ const relatedTarget =
312
+ event instanceof MouseEvent && event.relatedTarget instanceof Node
313
+ ? event.relatedTarget
314
+ : null;
315
+
316
+ return relatedTarget !== null && target.contains(relatedTarget);
317
+ }
318
+
319
+ function getEventPath(root: Element, event: Event): HTMLElement[] {
320
+ const path: HTMLElement[] = [];
321
+ let cursor = event.target instanceof Node ? event.target : null;
322
+
323
+ while (cursor !== null) {
324
+ if (cursor instanceof HTMLElement) {
325
+ path.push(cursor);
326
+ }
327
+
328
+ if (cursor === root) {
329
+ const logicalParent = logicalEventParents.get(root);
330
+ if (logicalParent === undefined) {
331
+ break;
332
+ }
333
+ cursor = logicalParent;
334
+ continue;
335
+ }
336
+
337
+ cursor = cursor.parentNode;
338
+ }
339
+
340
+ return path;
341
+ }
342
+
343
+ function createSyntheticEvent(
344
+ nativeEvent: Event,
345
+ currentTarget: EventTarget,
346
+ state: { defaultPrevented: boolean; propagationStopped: boolean } = {
347
+ defaultPrevented: nativeEvent.defaultPrevented,
348
+ propagationStopped: false,
349
+ },
350
+ syntheticType = nativeEvent.type,
351
+ ): SyntheticEvent {
352
+ return {
353
+ bubbles: nativeEvent.bubbles,
354
+ cancelable: nativeEvent.cancelable,
355
+ get defaultPrevented() {
356
+ return state.defaultPrevented;
357
+ },
358
+ eventPhase: nativeEvent.eventPhase,
359
+ isTrusted: nativeEvent.isTrusted ?? false,
360
+ nativeEvent,
361
+ timeStamp: nativeEvent.timeStamp,
362
+ type: syntheticType,
363
+ target: nativeEvent.target,
364
+ currentTarget,
365
+ persist() {},
366
+ preventDefault() {
367
+ state.defaultPrevented = true;
368
+ nativeEvent.preventDefault();
369
+ },
370
+ stopPropagation() {
371
+ state.propagationStopped = true;
372
+ nativeEvent.stopPropagation();
373
+ },
374
+ isDefaultPrevented() {
375
+ return state.defaultPrevented;
376
+ },
377
+ isPersistent() {
378
+ return true;
379
+ },
380
+ isPropagationStopped() {
381
+ return state.propagationStopped;
382
+ },
383
+ };
384
+ }