Skip to content

Commit

Permalink
[skip ci] Implement Event and EventTarget (facebook#48429)
Browse files Browse the repository at this point in the history
Summary:

Changelog: [internal]

This implements a (mostly) spec-compliant version of the [`Event`](https://dom.spec.whatwg.org/#interface-event) and [`EventTarget`](https://dom.spec.whatwg.org/#interface-eventtarget) Web interfaces.

It does not implement legacy methods in either of the interfaces, and ignores the parts of the spec that are related to Web-specific quirks (shadow roots, re-mapping of animation events with webkit prefixes, etc.).

IMPORTANT: This only creates the interfaces and does not expose them externally yet (no `Event` or `EventTarget` in the global scope).

Differential Revision: D67738145
  • Loading branch information
rubennorte authored and facebook-github-bot committed Jan 3, 2025
1 parent 546d21c commit 1b1434a
Show file tree
Hide file tree
Showing 6 changed files with 1,816 additions and 0 deletions.
177 changes: 177 additions & 0 deletions packages/react-native/src/private/webapis/dom/events/Event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/

/**
* This module implements the `Event` interface from the DOM.
* See https://dom.spec.whatwg.org/#interface-event.
*/

// flowlint unsafe-getters-setters:off

import type EventTarget from './EventTarget';

import {
COMPOSED_PATH_KEY,
CURRENT_TARGET_KEY,
EVENT_PHASE_KEY,
IN_PASSIVE_LISTENER_FLAG_KEY,
IS_TRUSTED_KEY,
STOP_IMMEDIATE_PROPAGATION_FLAG_KEY,
STOP_PROPAGATION_FLAG_KEY,
TARGET_KEY,
getComposedPath,
getCurrentTarget,
getEventPhase,
getInPassiveListenerFlag,
getIsTrusted,
getTarget,
setStopImmediatePropagationFlag,
setStopPropagationFlag,
} from './internals/EventInternals';

type EventInit = {
bubbles?: boolean,
cancelable?: boolean,
composed?: boolean,
};

export default class Event {
static NONE: 0 = 0;
static CAPTURING_PHASE: 1 = 1;
static AT_TARGET: 2 = 2;
static BUBBLING_PHASE: 3 = 3;

#bubbles: boolean;
#cancelable: boolean;
#composed: boolean;
#type: string;

#defaultPrevented: boolean = false;
#timeStamp: number = performance.now();

// $FlowExpectedError[unsupported-syntax]
[COMPOSED_PATH_KEY]: boolean = [];

// $FlowExpectedError[unsupported-syntax]
[CURRENT_TARGET_KEY]: EventTarget | null = null;

// $FlowExpectedError[unsupported-syntax]
[EVENT_PHASE_KEY]: boolean = Event.NONE;

// $FlowExpectedError[unsupported-syntax]
[IN_PASSIVE_LISTENER_FLAG_KEY]: boolean = false;

// $FlowExpectedError[unsupported-syntax]
[IS_TRUSTED_KEY]: boolean = false;

// $FlowExpectedError[unsupported-syntax]
[STOP_IMMEDIATE_PROPAGATION_FLAG_KEY]: boolean = false;

// $FlowExpectedError[unsupported-syntax]
[STOP_PROPAGATION_FLAG_KEY]: boolean = false;

// $FlowExpectedError[unsupported-syntax]
[TARGET_KEY]: EventTarget | null = null;

constructor(type: string, options?: ?EventInit) {
if (arguments.length < 1) {
throw new TypeError(
"Failed to construct 'Event': 1 argument required, but only 0 present.",
);
}

if (options != null && typeof options !== 'object') {
throw new TypeError(
"Failed to construct 'Event': The provided value is not of type 'EventInit'.",
);
}

this.#type = String(type);
this.#bubbles = Boolean(options?.bubbles);
this.#cancelable = Boolean(options?.cancelable);
this.#composed = Boolean(options?.composed);
}

get bubbles(): boolean {
return this.#bubbles;
}

get cancelable(): boolean {
return this.#cancelable;
}

get composed(): boolean {
return this.#composed;
}

get currentTarget(): EventTarget | null {
return getCurrentTarget(this);
}

get defaultPrevented(): boolean {
return this.#defaultPrevented;
}

get eventPhase(): EventPhase {
return getEventPhase(this);
}

get isTrusted(): boolean {
return getIsTrusted(this);
}

get target(): EventTarget | null {
return getTarget(this);
}

get timeStamp(): number {
return this.#timeStamp;
}

get type(): string {
return this.#type;
}

composedPath(): $ReadOnlyArray<EventTarget> {
return getComposedPath(this).slice();
}

preventDefault(): void {
if (!this.#cancelable) {
return;
}

if (getInPassiveListenerFlag(this)) {
console.error(
new Error(
'Unable to preventDefault inside passive event listener invocation.',
),
);
return;
}

this.#defaultPrevented = true;
}

stopImmediatePropagation(): void {
setStopPropagationFlag(this, true);
setStopImmediatePropagationFlag(this, true);
}

stopPropagation(): void {
setStopPropagationFlag(this, true);
}
}

export type EventPhase =
| (typeof Event)['NONE']
| (typeof Event)['CAPTURING_PHASE']
| (typeof Event)['AT_TARGET']
| (typeof Event)['BUBBLING_PHASE'];
Loading

0 comments on commit 1b1434a

Please sign in to comment.