|
| 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; |
0 commit comments