Skip to content

Commit 6abbef1

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
CustomEvent and Event polyfills for React Native
Summary: In preparation for upcoming changes, it is useful / necessary to have a CustomEvent and Event polyfill for React Native. In browser environments, both of those are expected to be accessible in the global scope, so we do the same here. Changelog: [Added][JS] Event and CustomEvent W3C-compatible polyfills Reviewed By: necolas Differential Revision: D34462447 fbshipit-source-id: 5efdad6f24c268a6d248d4e3351fc96715ee3fdf
1 parent 2fdbf6a commit 6abbef1

File tree

3 files changed

+275
-0
lines changed

3 files changed

+275
-0
lines changed

Libraries/Events/CustomEvent.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow strict-local
9+
*/
10+
11+
// Make sure global Event is defined
12+
import EventPolyfill from './EventPolyfill';
13+
14+
type CustomEvent$Options = $ReadOnly<{|
15+
bubbles?: boolean,
16+
cancelable?: boolean,
17+
composed?: boolean,
18+
detail?: {...},
19+
|}>;
20+
21+
class CustomEvent extends EventPolyfill {
22+
detail: ?{...};
23+
24+
constructor(typeArg: string, options: CustomEvent$Options) {
25+
const {bubbles, cancelable, composed} = options;
26+
super(typeArg, {bubbles, cancelable, composed});
27+
28+
this.detail = options.detail; // this would correspond to `NativeEvent` in SyntheticEvent
29+
}
30+
}
31+
32+
export default CustomEvent;

Libraries/Events/EventPolyfill.js

+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow strict-local
9+
*/
10+
11+
// https://dom.spec.whatwg.org/#dictdef-eventinit
12+
type Event$Init = {
13+
bubbles?: boolean,
14+
cancelable?: boolean,
15+
composed?: boolean,
16+
/** Non-standard. See `composed` instead. */
17+
scoped?: boolean,
18+
...
19+
};
20+
21+
/**
22+
* This is a copy of the Event interface defined in Flow:
23+
* https://github.com/facebook/flow/blob/741104e69c43057ebd32804dd6bcc1b5e97548ea/lib/dom.js
24+
* which is itself a faithful interface of the W3 spec:
25+
* https://dom.spec.whatwg.org/#interface-event
26+
*
27+
* Since Flow assumes that Event is provided and is on the global object,
28+
* we must provide an implementation of Event for CustomEvent (and future
29+
* alignment of React Native's event system with the W3 spec).
30+
*/
31+
interface IEvent {
32+
constructor(type: string, eventInitDict?: Event$Init): void;
33+
/**
34+
* Returns the type of event, e.g. "click", "hashchange", or "submit".
35+
*/
36+
+type: string;
37+
/**
38+
* Returns the object to which event is dispatched (its target).
39+
*/
40+
+target: EventTarget; // TODO: nullable
41+
/** @deprecated */
42+
+srcElement: Element; // TODO: nullable
43+
/**
44+
* Returns the object whose event listener's callback is currently being invoked.
45+
*/
46+
+currentTarget: EventTarget; // TODO: nullable
47+
/**
48+
* Returns the invocation target objects of event's path (objects on which
49+
* listeners will be invoked), except for any nodes in shadow trees of which
50+
* the shadow root's mode is "closed" that are not reachable from event's
51+
* currentTarget.
52+
*/
53+
composedPath(): Array<EventTarget>;
54+
55+
+NONE: number;
56+
+AT_TARGET: number;
57+
+BUBBLING_PHASE: number;
58+
+CAPTURING_PHASE: number;
59+
/**
60+
* Returns the event's phase, which is one of NONE, CAPTURING_PHASE, AT_TARGET,
61+
* and BUBBLING_PHASE.
62+
*/
63+
+eventPhase: number;
64+
65+
/**
66+
* When dispatched in a tree, invoking this method prevents event from reaching
67+
* any objects other than the current object.
68+
*/
69+
stopPropagation(): void;
70+
/**
71+
* Invoking this method prevents event from reaching any registered event
72+
* listeners after the current one finishes running and, when dispatched in a
73+
* tree, also prevents event from reaching any other objects.
74+
*/
75+
stopImmediatePropagation(): void;
76+
77+
/**
78+
* Returns true or false depending on how event was initialized. True if
79+
* event goes through its target's ancestors in reverse tree order, and
80+
* false otherwise.
81+
*/
82+
+bubbles: boolean;
83+
/**
84+
* Returns true or false depending on how event was initialized. Its
85+
* return value does not always carry meaning, but true can indicate
86+
* that part of the operation during which event was dispatched, can
87+
* be canceled by invoking the preventDefault() method.
88+
*/
89+
+cancelable: boolean;
90+
// returnValue: boolean; // legacy, and some subclasses still define it as a string!
91+
/**
92+
* If invoked when the cancelable attribute value is true, and while
93+
* executing a listener for the event with passive set to false, signals to
94+
* the operation that caused event to be dispatched that it needs to be
95+
* canceled.
96+
*/
97+
preventDefault(): void;
98+
/**
99+
* Returns true if preventDefault() was invoked successfully to indicate
100+
* cancelation, and false otherwise.
101+
*/
102+
+defaultPrevented: boolean;
103+
/**
104+
* Returns true or false depending on how event was initialized. True if
105+
* event invokes listeners past a ShadowRoot node that is the root of its
106+
* target, and false otherwise.
107+
*/
108+
+composed: boolean;
109+
110+
/**
111+
* Returns true if event was dispatched by the user agent, and false otherwise.
112+
*/
113+
+isTrusted: boolean;
114+
/**
115+
* Returns the event's timestamp as the number of milliseconds measured relative
116+
* to the time origin.
117+
*/
118+
+timeStamp: number;
119+
120+
/** Non-standard. See Event.prototype.composedPath */
121+
+deepPath?: () => EventTarget[];
122+
/** Non-standard. See Event.prototype.composed */
123+
+scoped: boolean;
124+
125+
/**
126+
* @deprecated
127+
*/
128+
initEvent(type: string, bubbles: boolean, cancelable: boolean): void;
129+
}
130+
131+
class EventPolyfill implements IEvent {
132+
type: string;
133+
bubbles: boolean;
134+
cancelable: boolean;
135+
composed: boolean;
136+
// Non-standard. See `composed` instead.
137+
scoped: boolean;
138+
isTrusted: boolean;
139+
defaultPrevented: boolean;
140+
timeStamp: number;
141+
142+
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
143+
NONE: number;
144+
AT_TARGET: number;
145+
BUBBLING_PHASE: number;
146+
CAPTURING_PHASE: number;
147+
148+
eventPhase: number;
149+
150+
currentTarget: EventTarget; // TODO: nullable
151+
target: EventTarget; // TODO: nullable
152+
/** @deprecated */
153+
srcElement: Element; // TODO: nullable
154+
155+
// React Native-specific: proxy data to a SyntheticEvent when
156+
// certain methods are called.
157+
// SyntheticEvent will also have a reference to this instance -
158+
// it is circular - and both classes use this reference to keep
159+
// data with the other in sync.
160+
_syntheticEvent: mixed;
161+
162+
constructor(type: string, eventInitDict?: Event$Init): void {
163+
this.type = type;
164+
this.bubbles = !!(eventInitDict?.bubbles || false);
165+
this.cancelable = !!(eventInitDict?.cancelable || false);
166+
this.composed = !!(eventInitDict?.composed || false);
167+
this.scoped = !!(eventInitDict?.scoped || false);
168+
169+
// TODO: somehow guarantee that only "private" instantiations of Event
170+
// can set this to true
171+
this.isTrusted = false;
172+
173+
// TODO: in the future we'll want to make sure this has the same
174+
// time-basis as events originating from native
175+
this.timeStamp = Date.now();
176+
177+
this.defaultPrevented = false;
178+
179+
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
180+
this.NONE = 0;
181+
this.AT_TARGET = 1;
182+
this.BUBBLING_PHASE = 2;
183+
this.CAPTURING_PHASE = 3;
184+
this.eventPhase = this.NONE;
185+
186+
// $FlowFixMe
187+
this.currentTarget = null;
188+
// $FlowFixMe
189+
this.target = null;
190+
// $FlowFixMe
191+
this.srcElement = null;
192+
}
193+
194+
composedPath(): Array<EventTarget> {
195+
throw new Error('TODO: not yet implemented');
196+
}
197+
198+
preventDefault(): void {
199+
this.defaultPrevented = true;
200+
201+
if (this._syntheticEvent != null) {
202+
// $FlowFixMe
203+
this._syntheticEvent.preventDefault();
204+
}
205+
}
206+
207+
initEvent(type: string, bubbles: boolean, cancelable: boolean): void {
208+
throw new Error(
209+
'TODO: not yet implemented. This method is also deprecated.',
210+
);
211+
}
212+
213+
stopImmediatePropagation(): void {
214+
throw new Error('TODO: not yet implemented');
215+
}
216+
217+
stopPropagation(): void {
218+
if (this._syntheticEvent != null) {
219+
// $FlowFixMe
220+
this._syntheticEvent.stopPropagation();
221+
}
222+
}
223+
224+
setSyntheticEvent(value: mixed): void {
225+
this._syntheticEvent = value;
226+
}
227+
}
228+
229+
// Assertion magic for polyfill follows.
230+
declare var checkEvent: Event;
231+
232+
/*::
233+
// This can be a strict mode error at runtime so put it in a Flow comment.
234+
(checkEvent: IEvent);
235+
*/
236+
237+
global.Event = EventPolyfill;
238+
239+
export default EventPolyfill;

Libraries/ReactPrivate/ReactNativePrivateInterface.js

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {type DangerouslyImpreciseStyleProp} from '../StyleSheet/StyleSheet';
2222
import typeof ReactFiberErrorDialog from '../Core/ReactFiberErrorDialog';
2323
import typeof legacySendAccessibilityEvent from '../Components/AccessibilityInfo/legacySendAccessibilityEvent';
2424
import typeof RawEventEmitter from '../Core/RawEventEmitter';
25+
import typeof CustomEvent from '../Events/CustomEvent';
2526

2627
// flowlint unsafe-getters-setters:off
2728
module.exports = {
@@ -66,4 +67,7 @@ module.exports = {
6667
get RawEventEmitter(): RawEventEmitter {
6768
return require('../Core/RawEventEmitter').default;
6869
},
70+
get CustomEvent(): CustomEvent {
71+
return require('../Events/CustomEvent').default;
72+
},
6973
};

0 commit comments

Comments
 (0)