/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react-dom-bindings/src/events/SyntheticEvent.js
603 строки
17 KB
Jan Kassens
Update Flow to 0.273 (#34274)
22 авг 2025, 23:58
Не верифицирован
22 авг 2025, 23:58
4049cfe
Код
Авторство
О чём код?
/** * 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: EventInterfaceType = { 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: ?{[propName: string]: mixed}; 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: EventInterfaceType = { ...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: EventInterfaceType = { ...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: EventInterfaceType = { ...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: EventInterfaceType = { ...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: EventInterfaceType = { ...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); const ToggleEventInterface: EventInterfaceType = { ...EventInterface, newState: 0, oldState: 0, }; export const SyntheticToggleEvent: $FlowFixMe = createSyntheticEvent(ToggleEventInterface);