/*** 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*//* eslint valid-typeof: 0 */import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import assign from 'shared/assign';
import getEventCharCode from './getEventCharCode';
type EventInterfaceType = {
[propName: string]: 0 | ((event: {[propName: string]: mixed, ...}) => mixed),
};function functionThatReturnsTrue() {
return true;
}function functionThatReturnsFalse() {
return false;
}// This is intentionally a factory so that we have different returned constructors.// If we had a single constructor, it would be megamorphic and engines would deopt.function createSyntheticEvent(Interface: EventInterfaceType) {
/**
* Synthetic events are dispatched by event plugins, typically in response to a* top-level event delegation handler.** These systems should generally use pooling to reduce the frequency of garbage* collection. The system should check `isPersistent` to determine whether the* event should be released into the pool after being dispatched. Users that* need a persisted event should invoke `persist`.** Synthetic events (and subclasses) implement the DOM Level 3 Events API by* normalizing browser quirks. Subclasses do not necessarily have to implement a* DOM interface; custom application-specific events can also subclass this.*/// $FlowFixMe[missing-this-annot]
function SyntheticBaseEvent(
reactName: string | null,reactEventType: string,targetInst: Fiber | null,nativeEvent: {[propName: string]: mixed, ...},nativeEventTarget: null | EventTarget,) {this._reactName = reactName;
this._targetInst = targetInst;
this.type = reactEventType;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
this.currentTarget = null;
for (const propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}const normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
this[propName] = nativeEvent[propName];
}}const defaultPrevented =
nativeEvent.defaultPrevented != null
? nativeEvent.defaultPrevented
: nativeEvent.returnValue === false;
if (defaultPrevented) {
this.isDefaultPrevented = functionThatReturnsTrue;
} else {
this.isDefaultPrevented = functionThatReturnsFalse;
}this.isPropagationStopped = functionThatReturnsFalse;
return this;
}// $FlowFixMe[prop-missing] found when upgrading Flow
assign(SyntheticBaseEvent.prototype, {
// $FlowFixMe[missing-this-annot]
preventDefault: function () {
this.defaultPrevented = true;
const event = this.nativeEvent;
if (!event) {
return;
}if (event.preventDefault) {
event.preventDefault();
// $FlowFixMe[illegal-typeof] - flow is not aware of `unknown` in IE
} else if (typeof event.returnValue !== 'unknown') {
event.returnValue = false;
}this.isDefaultPrevented = functionThatReturnsTrue;
},// $FlowFixMe[missing-this-annot]
stopPropagation: function () {
const event = this.nativeEvent;
if (!event) {
return;
}if (event.stopPropagation) {
event.stopPropagation();
// $FlowFixMe[illegal-typeof] - flow is not aware of `unknown` in IE
} else if (typeof event.cancelBubble !== 'unknown') {
// The ChangeEventPlugin registers a "propertychange" event for
// IE. This event does not support bubbling or cancelling, and
// any references to cancelBubble throw "Member not found". A
// typeof check of "unknown" circumvents this issue (and is also
// IE specific).
event.cancelBubble = true;
}this.isPropagationStopped = functionThatReturnsTrue;
},/**
* We release all dispatched `SyntheticEvent`s after each event loop, adding* them back into the pool. This allows a way to hold onto a reference that* won't be added back into the pool.*/persist: function () {
// Modern event system doesn't use pooling.
},/**
* Checks if this event should be released back into the pool.** @return {boolean} True if this should not be released, false otherwise.*/isPersistent: functionThatReturnsTrue,
});return SyntheticBaseEvent;
}/*** @interface Event* @see http://www.w3.org/TR/DOM-Level-3-Events/*/const EventInterface = {
eventPhase: 0,
bubbles: 0,
cancelable: 0,
timeStamp: function (event: {[propName: string]: mixed}) {
return event.timeStamp || Date.now();
},defaultPrevented: 0,
isTrusted: 0,
};export const SyntheticEvent: $FlowFixMe = createSyntheticEvent(EventInterface);
const UIEventInterface: EventInterfaceType = {
...EventInterface,
view: 0,
detail: 0,
};export const SyntheticUIEvent: $FlowFixMe =
createSyntheticEvent(UIEventInterface);
let lastMovementX;
let lastMovementY;
let lastMouseEvent;
function updateMouseMovementPolyfillState(event: {[propName: string]: mixed}) {
if (event !== lastMouseEvent) {
if (lastMouseEvent && event.type === 'mousemove') {
// $FlowFixMe[unsafe-arithmetic] assuming this is a number
lastMovementX = event.screenX - lastMouseEvent.screenX;
// $FlowFixMe[unsafe-arithmetic] assuming this is a number
lastMovementY = event.screenY - lastMouseEvent.screenY;
} else {
lastMovementX = 0;
lastMovementY = 0;
}lastMouseEvent = event;
}}/*** @interface MouseEvent* @see http://www.w3.org/TR/DOM-Level-3-Events/*/const MouseEventInterface: EventInterfaceType = {
...UIEventInterface,
screenX: 0,
screenY: 0,
clientX: 0,
clientY: 0,
pageX: 0,
pageY: 0,
ctrlKey: 0,
shiftKey: 0,
altKey: 0,
metaKey: 0,
getModifierState: getEventModifierState,
button: 0,
buttons: 0,
relatedTarget: function (event) {
if (event.relatedTarget === undefined)
return event.fromElement === event.srcElement
? event.toElement
: event.fromElement;
return event.relatedTarget;
},movementX: function (event) {
if ('movementX' in event) {
return event.movementX;
}updateMouseMovementPolyfillState(event);
return lastMovementX;
},movementY: function (event) {
if ('movementY' in event) {
return event.movementY;
}// Don't need to call updateMouseMovementPolyfillState() here
// because it's guaranteed to have already run when movementX
// was copied.
return lastMovementY;
},};export const SyntheticMouseEvent: $FlowFixMe =
createSyntheticEvent(MouseEventInterface);
/*** @interface DragEvent* @see http://www.w3.org/TR/DOM-Level-3-Events/*/const DragEventInterface: EventInterfaceType = {
...MouseEventInterface,
dataTransfer: 0,
};export const SyntheticDragEvent: $FlowFixMe =
createSyntheticEvent(DragEventInterface);
/*** @interface FocusEvent* @see http://www.w3.org/TR/DOM-Level-3-Events/*/const FocusEventInterface: EventInterfaceType = {
...UIEventInterface,
relatedTarget: 0,
};export const SyntheticFocusEvent: $FlowFixMe =
createSyntheticEvent(FocusEventInterface);
/*** @interface Event* @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface* @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent*/const AnimationEventInterface: EventInterfaceType = {
...EventInterface,
animationName: 0,
elapsedTime: 0,
pseudoElement: 0,
};export const SyntheticAnimationEvent: $FlowFixMe = createSyntheticEvent(
AnimationEventInterface,
);/*** @interface Event* @see http://www.w3.org/TR/clipboard-apis/*/const ClipboardEventInterface: EventInterfaceType = {
...EventInterface,
clipboardData: function (event) {
return 'clipboardData' in event
? event.clipboardData
: window.clipboardData;},};export const SyntheticClipboardEvent: $FlowFixMe = createSyntheticEvent(
ClipboardEventInterface,
);/*** @interface Event* @see http://www.w3.org/TR/DOM-Level-3-Events/#events-compositionevents*/const CompositionEventInterface: EventInterfaceType = {
...EventInterface,
data: 0,
};export const SyntheticCompositionEvent: $FlowFixMe = createSyntheticEvent(
CompositionEventInterface,
);/*** @interface Event* @see http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105* /#events-inputevents*/// Happens to share the same list for now.export const SyntheticInputEvent = SyntheticCompositionEvent;
/*** Normalization of deprecated HTML5 `key` values* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names*/const normalizeKey = {
Esc: 'Escape',
Spacebar: ' ',Left: 'ArrowLeft',
Up: 'ArrowUp',
Right: 'ArrowRight',
Down: 'ArrowDown',
Del: 'Delete',
Win: 'OS',
Menu: 'ContextMenu',
Apps: 'ContextMenu',
Scroll: 'ScrollLock',
MozPrintableKey: 'Unidentified',
};/*** Translation from legacy `keyCode` to HTML5 `key`* Only special keys supported, all others depend on keyboard layout or browser* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names*/const translateToKey = {
'8': 'Backspace',
'9': 'Tab',
'12': 'Clear',
'13': 'Enter',
'16': 'Shift',
'17': 'Control',
'18': 'Alt',
'19': 'Pause',
'20': 'CapsLock',
'27': 'Escape',
'32': ' ',
'33': 'PageUp',
'34': 'PageDown',
'35': 'End',
'36': 'Home',
'37': 'ArrowLeft',
'38': 'ArrowUp',
'39': 'ArrowRight',
'40': 'ArrowDown',
'45': 'Insert',
'46': 'Delete',
'112': 'F1',
'113': 'F2',
'114': 'F3',
'115': 'F4',
'116': 'F5',
'117': 'F6',
'118': 'F7',
'119': 'F8',
'120': 'F9',
'121': 'F10',
'122': 'F11',
'123': 'F12',
'144': 'NumLock',
'145': 'ScrollLock',
'224': 'Meta',
};/*** @param {object} nativeEvent Native browser event.* @return {string} Normalized `key` property.*/function getEventKey(nativeEvent: {[propName: string]: mixed}) {
if (nativeEvent.key) {
// Normalize inconsistent values reported by browsers due to
// implementations of a working draft specification.
// FireFox implements `key` but returns `MozPrintableKey` for all
// printable characters (normalized to `Unidentified`), ignore it.
const key =
// $FlowFixMe[invalid-computed-prop] unable to index with a `mixed` value
normalizeKey[nativeEvent.key] || nativeEvent.key;
if (key !== 'Unidentified') {
return key;
}}// Browser does not implement `key`, polyfill as much of it as we can.
if (nativeEvent.type === 'keypress') {
const charCode = getEventCharCode(
// $FlowFixMe[incompatible-call] unable to narrow to `KeyboardEvent`
nativeEvent,
);// The enter-key is technically both printable and non-printable and can
// thus be captured by `keypress`, no other non-printable key should.
return charCode === 13 ? 'Enter' : String.fromCharCode(charCode);
}if (nativeEvent.type === 'keydown' || nativeEvent.type === 'keyup') {
// While user keyboard layout determines the actual meaning of each
// `keyCode` value, almost all function keys have a universal value.
// $FlowFixMe[invalid-computed-prop] unable to index with a `mixed` value
return translateToKey[nativeEvent.keyCode] || 'Unidentified';
}return '';
}/*** Translation from modifier key to the associated property in the event.* @see http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers*/const modifierKeyToProp = {
Alt: 'altKey',
Control: 'ctrlKey',
Meta: 'metaKey',
Shift: 'shiftKey',
};// Older browsers (Safari <= 10, iOS Safari <= 10.2) do not support// getModifierState. If getModifierState is not supported, we map it to a set of// modifier keys exposed by the event. In this case, Lock-keys are not supported.// $FlowFixMe[missing-local-annot]// $FlowFixMe[missing-this-annot]function modifierStateGetter(keyArg) {
const syntheticEvent = this;
const nativeEvent = syntheticEvent.nativeEvent;
if (nativeEvent.getModifierState) {
return nativeEvent.getModifierState(keyArg);
}const keyProp = modifierKeyToProp[keyArg];
return keyProp ? !!nativeEvent[keyProp] : false;
}function getEventModifierState(nativeEvent: {[propName: string]: mixed}) {
return modifierStateGetter;
}/*** @interface KeyboardEvent* @see http://www.w3.org/TR/DOM-Level-3-Events/*/const KeyboardEventInterface = {
...UIEventInterface,
key: getEventKey,
code: 0,
location: 0,
ctrlKey: 0,
shiftKey: 0,
altKey: 0,
metaKey: 0,
repeat: 0,
locale: 0,
getModifierState: getEventModifierState,
// Legacy Interface
charCode: function (event: {[propName: string]: mixed}) {
// `charCode` is the result of a KeyPress event and represents the value of
// the actual printable character.
// KeyPress is deprecated, but its replacement is not yet final and not
// implemented in any major browser. Only KeyPress has charCode.
if (event.type === 'keypress') {
return getEventCharCode(
// $FlowFixMe[incompatible-call] unable to narrow to `KeyboardEvent`
event,
);}return 0;
},keyCode: function (event: {[propName: string]: mixed}) {
// `keyCode` is the result of a KeyDown/Up event and represents the value of
// physical keyboard key.
// The actual meaning of the value depends on the users' keyboard layout
// which cannot be detected. Assuming that it is a US keyboard layout
// provides a surprisingly accurate mapping for US and European users.
// Due to this, it is left to the user to implement at this time.
if (event.type === 'keydown' || event.type === 'keyup') {
return event.keyCode;
}return 0;
},which: function (event: {[propName: string]: mixed}) {
// `which` is an alias for either `keyCode` or `charCode` depending on the
// type of the event.
if (event.type === 'keypress') {
return getEventCharCode(
// $FlowFixMe[incompatible-call] unable to narrow to `KeyboardEvent`
event,
);}if (event.type === 'keydown' || event.type === 'keyup') {
return event.keyCode;
}return 0;
},};export const SyntheticKeyboardEvent: $FlowFixMe = createSyntheticEvent(
KeyboardEventInterface,
);/*** @interface PointerEvent* @see http://www.w3.org/TR/pointerevents/*/const PointerEventInterface = {
...MouseEventInterface,
pointerId: 0,
width: 0,
height: 0,
pressure: 0,
tangentialPressure: 0,
tiltX: 0,
tiltY: 0,
twist: 0,
pointerType: 0,
isPrimary: 0,
};export const SyntheticPointerEvent: $FlowFixMe = createSyntheticEvent(
PointerEventInterface,
);/*** @interface TouchEvent* @see http://www.w3.org/TR/touch-events/*/const TouchEventInterface = {
...UIEventInterface,
touches: 0,
targetTouches: 0,
changedTouches: 0,
altKey: 0,
metaKey: 0,
ctrlKey: 0,
shiftKey: 0,
getModifierState: getEventModifierState,
};export const SyntheticTouchEvent: $FlowFixMe =
createSyntheticEvent(TouchEventInterface);
/*** @interface Event* @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-* @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent*/const TransitionEventInterface = {
...EventInterface,
propertyName: 0,
elapsedTime: 0,
pseudoElement: 0,
};export const SyntheticTransitionEvent: $FlowFixMe = createSyntheticEvent(
TransitionEventInterface,
);/*** @interface WheelEvent* @see http://www.w3.org/TR/DOM-Level-3-Events/*/const WheelEventInterface = {
...MouseEventInterface,
deltaX(event: {[propName: string]: mixed}) {
return 'deltaX' in event
? event.deltaX
: // Fallback to `wheelDeltaX` for Webkit and normalize (right is positive).
'wheelDeltaX' in event
? // $FlowFixMe[unsafe-arithmetic] assuming this is a number
-event.wheelDeltaX
: 0;
},deltaY(event: {[propName: string]: mixed}) {
return 'deltaY' in event
? event.deltaY
: // Fallback to `wheelDeltaY` for Webkit and normalize (down is positive).
'wheelDeltaY' in event
? // $FlowFixMe[unsafe-arithmetic] assuming this is a number
-event.wheelDeltaY
: // Fallback to `wheelDelta` for IE<9 and normalize (down is positive).
'wheelDelta' in event
? // $FlowFixMe[unsafe-arithmetic] assuming this is a number
-event.wheelDelta
: 0;
},deltaZ: 0,
// Browsers without "deltaMode" is reporting in raw wheel delta where one
// notch on the scroll is always +/- 120, roughly equivalent to pixels.
// A good approximation of DOM_DELTA_LINE (1) is 5% of viewport size or
// ~40 pixels, for DOM_DELTA_SCREEN (2) it is 87.5% of viewport size.
deltaMode: 0,
};export const SyntheticWheelEvent: $FlowFixMe =
createSyntheticEvent(WheelEventInterface);