From 135e82094f9e30ed926616ac486bc4df0fb26652 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Wed, 16 May 2018 13:38:41 +0200 Subject: [PATCH 1/2] Support Pointer Events --- .../__snapshots__/ReactTestUtils-test.js.snap | 10 +++ .../src/events/DOMTopLevelEventTypes.js | 26 ++++++++ .../src/events/EnterLeaveEventPlugin.js | 62 ++++++++++++++----- .../react-dom/src/events/SimpleEventPlugin.js | 19 ++++++ .../src/events/SyntheticPointerEvent.js | 25 ++++++++ .../react-dom/src/events/TapEventPlugin.js | 24 ++++++- 6 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 packages/react-dom/src/events/SyntheticPointerEvent.js diff --git a/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap b/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap index 95d2593f752..ff776881ef5 100644 --- a/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap +++ b/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap @@ -35,6 +35,7 @@ Array [ "ended", "error", "focus", + "gotPointerCapture", "input", "invalid", "keyDown", @@ -44,6 +45,7 @@ Array [ "loadStart", "loadedData", "loadedMetadata", + "lostPointerCapture", "mouseDown", "mouseEnter", "mouseLeave", @@ -55,6 +57,14 @@ Array [ "pause", "play", "playing", + "pointerCancel", + "pointerDown", + "pointerEnter", + "pointerLeave", + "pointerMove", + "pointerOut", + "pointerOver", + "pointerUp", "progress", "rateChange", "reset", diff --git a/packages/react-dom/src/events/DOMTopLevelEventTypes.js b/packages/react-dom/src/events/DOMTopLevelEventTypes.js index 1898b0f9658..298afec9c99 100644 --- a/packages/react-dom/src/events/DOMTopLevelEventTypes.js +++ b/packages/react-dom/src/events/DOMTopLevelEventTypes.js @@ -72,6 +72,9 @@ export const TOP_ENCRYPTED = unsafeCastStringToDOMTopLevelType('encrypted'); export const TOP_ENDED = unsafeCastStringToDOMTopLevelType('ended'); export const TOP_ERROR = unsafeCastStringToDOMTopLevelType('error'); export const TOP_FOCUS = unsafeCastStringToDOMTopLevelType('focus'); +export const TOP_GOT_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType( + 'gotpointercapture', +); export const TOP_INPUT = unsafeCastStringToDOMTopLevelType('input'); export const TOP_INVALID = unsafeCastStringToDOMTopLevelType('invalid'); export const TOP_KEY_DOWN = unsafeCastStringToDOMTopLevelType('keydown'); @@ -83,6 +86,9 @@ export const TOP_LOADED_DATA = unsafeCastStringToDOMTopLevelType('loadeddata'); export const TOP_LOADED_METADATA = unsafeCastStringToDOMTopLevelType( 'loadedmetadata', ); +export const TOP_LOST_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType( + 'lostpointercapture', +); export const TOP_MOUSE_DOWN = unsafeCastStringToDOMTopLevelType('mousedown'); export const TOP_MOUSE_MOVE = unsafeCastStringToDOMTopLevelType('mousemove'); export const TOP_MOUSE_OUT = unsafeCastStringToDOMTopLevelType('mouseout'); @@ -92,6 +98,26 @@ export const TOP_PASTE = unsafeCastStringToDOMTopLevelType('paste'); export const TOP_PAUSE = unsafeCastStringToDOMTopLevelType('pause'); export const TOP_PLAY = unsafeCastStringToDOMTopLevelType('play'); export const TOP_PLAYING = unsafeCastStringToDOMTopLevelType('playing'); +export const TOP_POINTER_CANCEL = unsafeCastStringToDOMTopLevelType( + 'pointercancel', +); +export const TOP_POINTER_DOWN = unsafeCastStringToDOMTopLevelType( + 'pointerdown', +); +export const TOP_POINTER_ENTER = unsafeCastStringToDOMTopLevelType( + 'pointerenter', +); +export const TOP_POINTER_LEAVE = unsafeCastStringToDOMTopLevelType( + 'pointerleave', +); +export const TOP_POINTER_MOVE = unsafeCastStringToDOMTopLevelType( + 'pointermove', +); +export const TOP_POINTER_OUT = unsafeCastStringToDOMTopLevelType('pointerout'); +export const TOP_POINTER_OVER = unsafeCastStringToDOMTopLevelType( + 'pointerover', +); +export const TOP_POINTER_UP = unsafeCastStringToDOMTopLevelType('pointerup'); export const TOP_PROGRESS = unsafeCastStringToDOMTopLevelType('progress'); export const TOP_RATE_CHANGE = unsafeCastStringToDOMTopLevelType('ratechange'); export const TOP_RESET = unsafeCastStringToDOMTopLevelType('reset'); diff --git a/packages/react-dom/src/events/EnterLeaveEventPlugin.js b/packages/react-dom/src/events/EnterLeaveEventPlugin.js index 3e15dd8a90c..d0ca03e6a3d 100644 --- a/packages/react-dom/src/events/EnterLeaveEventPlugin.js +++ b/packages/react-dom/src/events/EnterLeaveEventPlugin.js @@ -7,8 +7,14 @@ import {accumulateEnterLeaveDispatches} from 'events/EventPropagators'; -import {TOP_MOUSE_OUT, TOP_MOUSE_OVER} from './DOMTopLevelEventTypes'; +import { + TOP_MOUSE_OUT, + TOP_MOUSE_OVER, + TOP_POINTER_OUT, + TOP_POINTER_OVER, +} from './DOMTopLevelEventTypes'; import SyntheticMouseEvent from './SyntheticMouseEvent'; +import SyntheticPointerEvent from './SyntheticPointerEvent'; import { getClosestInstanceFromNode, getNodeFromInstance, @@ -23,6 +29,14 @@ const eventTypes = { registrationName: 'onMouseLeave', dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER], }, + pointerEnter: { + registrationName: 'onPointerEnter', + dependencies: [TOP_POINTER_OUT, TOP_POINTER_OVER], + }, + pointerLeave: { + registrationName: 'onPointerLeave', + dependencies: [TOP_POINTER_OUT, TOP_POINTER_OVER], + }, }; const EnterLeaveEventPlugin = { @@ -41,14 +55,17 @@ const EnterLeaveEventPlugin = { nativeEvent, nativeEventTarget, ) { - if ( - topLevelType === TOP_MOUSE_OVER && - (nativeEvent.relatedTarget || nativeEvent.fromElement) - ) { + const isOverEvent = + topLevelType === TOP_MOUSE_OVER || topLevelType === TOP_POINTER_OVER; + const isOutEvent = + topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_POINTER_OUT; + + if (isOverEvent && (nativeEvent.relatedTarget || nativeEvent.fromElement)) { return null; } - if (topLevelType !== TOP_MOUSE_OUT && topLevelType !== TOP_MOUSE_OVER) { - // Must not be a mouse in or mouse out - ignoring. + + if (!isOutEvent && !isOverEvent) { + // Must not be a mouse or pointer in or out - ignoring. return null; } @@ -68,7 +85,7 @@ const EnterLeaveEventPlugin = { let from; let to; - if (topLevelType === TOP_MOUSE_OUT) { + if (isOutEvent) { from = targetInst; const related = nativeEvent.relatedTarget || nativeEvent.toElement; to = related ? getClosestInstanceFromNode(related) : null; @@ -83,26 +100,43 @@ const EnterLeaveEventPlugin = { return null; } + let eventInterface, leaveEventType, enterEventType, eventTypePrefix; + + if (topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_MOUSE_OVER) { + eventInterface = SyntheticMouseEvent; + leaveEventType = eventTypes.mouseLeave; + enterEventType = eventTypes.mouseEnter; + eventTypePrefix = 'mouse'; + } else if ( + topLevelType === TOP_POINTER_OUT || + topLevelType === TOP_POINTER_OVER + ) { + eventInterface = SyntheticPointerEvent; + leaveEventType = eventTypes.pointerLeave; + enterEventType = eventTypes.pointerEnter; + eventTypePrefix = 'pointer'; + } + const fromNode = from == null ? win : getNodeFromInstance(from); const toNode = to == null ? win : getNodeFromInstance(to); - const leave = SyntheticMouseEvent.getPooled( - eventTypes.mouseLeave, + const leave = eventInterface.getPooled( + leaveEventType, from, nativeEvent, nativeEventTarget, ); - leave.type = 'mouseleave'; + leave.type = eventTypePrefix + 'leave'; leave.target = fromNode; leave.relatedTarget = toNode; - const enter = SyntheticMouseEvent.getPooled( - eventTypes.mouseEnter, + const enter = eventInterface.getPooled( + enterEventType, to, nativeEvent, nativeEventTarget, ); - enter.type = 'mouseenter'; + enter.type = eventTypePrefix + 'enter'; enter.target = toNode; enter.relatedTarget = fromNode; diff --git a/packages/react-dom/src/events/SimpleEventPlugin.js b/packages/react-dom/src/events/SimpleEventPlugin.js index 193aff57b55..8473b546fb6 100644 --- a/packages/react-dom/src/events/SimpleEventPlugin.js +++ b/packages/react-dom/src/events/SimpleEventPlugin.js @@ -29,6 +29,7 @@ import SyntheticClipboardEvent from './SyntheticClipboardEvent'; import SyntheticFocusEvent from './SyntheticFocusEvent'; import SyntheticKeyboardEvent from './SyntheticKeyboardEvent'; import SyntheticMouseEvent from './SyntheticMouseEvent'; +import SyntheticPointerEvent from './SyntheticPointerEvent'; import SyntheticDragEvent from './SyntheticDragEvent'; import SyntheticTouchEvent from './SyntheticTouchEvent'; import SyntheticTransitionEvent from './SyntheticTransitionEvent'; @@ -78,6 +79,9 @@ const interactiveEventTypeNames: Array = [ [DOMTopLevelEventTypes.TOP_PASTE, 'paste'], [DOMTopLevelEventTypes.TOP_PAUSE, 'pause'], [DOMTopLevelEventTypes.TOP_PLAY, 'play'], + [DOMTopLevelEventTypes.TOP_POINTER_CANCEL, 'pointerCancel'], + [DOMTopLevelEventTypes.TOP_POINTER_DOWN, 'pointerDown'], + [DOMTopLevelEventTypes.TOP_POINTER_UP, 'pointerUp'], [DOMTopLevelEventTypes.TOP_RATE_CHANGE, 'rateChange'], [DOMTopLevelEventTypes.TOP_RESET, 'reset'], [DOMTopLevelEventTypes.TOP_SEEKED, 'seeked'], @@ -104,14 +108,19 @@ const nonInteractiveEventTypeNames: Array = [ [DOMTopLevelEventTypes.TOP_ENCRYPTED, 'encrypted'], [DOMTopLevelEventTypes.TOP_ENDED, 'ended'], [DOMTopLevelEventTypes.TOP_ERROR, 'error'], + [DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE, 'gotPointerCapture'], [DOMTopLevelEventTypes.TOP_LOAD, 'load'], [DOMTopLevelEventTypes.TOP_LOADED_DATA, 'loadedData'], [DOMTopLevelEventTypes.TOP_LOADED_METADATA, 'loadedMetadata'], [DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart'], + [DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE, 'lostPointerCapture'], [DOMTopLevelEventTypes.TOP_MOUSE_MOVE, 'mouseMove'], [DOMTopLevelEventTypes.TOP_MOUSE_OUT, 'mouseOut'], [DOMTopLevelEventTypes.TOP_MOUSE_OVER, 'mouseOver'], [DOMTopLevelEventTypes.TOP_PLAYING, 'playing'], + [DOMTopLevelEventTypes.TOP_POINTER_MOVE, 'pointerMove'], + [DOMTopLevelEventTypes.TOP_POINTER_OUT, 'pointerOut'], + [DOMTopLevelEventTypes.TOP_POINTER_OVER, 'pointerOver'], [DOMTopLevelEventTypes.TOP_PROGRESS, 'progress'], [DOMTopLevelEventTypes.TOP_SCROLL, 'scroll'], [DOMTopLevelEventTypes.TOP_SEEKING, 'seeking'], @@ -282,6 +291,16 @@ const SimpleEventPlugin: PluginModule & { case DOMTopLevelEventTypes.TOP_PASTE: EventConstructor = SyntheticClipboardEvent; break; + case DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE: + case DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE: + case DOMTopLevelEventTypes.TOP_POINTER_CANCEL: + case DOMTopLevelEventTypes.TOP_POINTER_DOWN: + case DOMTopLevelEventTypes.TOP_POINTER_MOVE: + case DOMTopLevelEventTypes.TOP_POINTER_OUT: + case DOMTopLevelEventTypes.TOP_POINTER_OVER: + case DOMTopLevelEventTypes.TOP_POINTER_UP: + EventConstructor = SyntheticPointerEvent; + break; default: if (__DEV__) { if (knownHTMLTopLevelTypes.indexOf(topLevelType) === -1) { diff --git a/packages/react-dom/src/events/SyntheticPointerEvent.js b/packages/react-dom/src/events/SyntheticPointerEvent.js new file mode 100644 index 00000000000..96a488f1863 --- /dev/null +++ b/packages/react-dom/src/events/SyntheticPointerEvent.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import SyntheticMouseEvent from './SyntheticMouseEvent'; + +/** + * @interface PointerEvent + * @see http://www.w3.org/TR/pointerevents/ + */ +const SyntheticPointerEvent = SyntheticMouseEvent.extend({ + pointerId: null, + width: null, + height: null, + pressure: null, + tiltX: null, + tiltY: null, + pointerType: null, + isPrimary: null, +}); + +export default SyntheticPointerEvent; diff --git a/packages/react-dom/src/events/TapEventPlugin.js b/packages/react-dom/src/events/TapEventPlugin.js index 772e5d405fa..e1baab05eac 100644 --- a/packages/react-dom/src/events/TapEventPlugin.js +++ b/packages/react-dom/src/events/TapEventPlugin.js @@ -15,6 +15,10 @@ import { TOP_MOUSE_DOWN, TOP_MOUSE_MOVE, TOP_MOUSE_UP, + TOP_POINTER_CANCEL, + TOP_POINTER_DOWN, + TOP_POINTER_UP, + TOP_POINTER_MOVE, TOP_TOUCH_CANCEL, TOP_TOUCH_END, TOP_TOUCH_MOVE, @@ -23,14 +27,20 @@ import { import SyntheticUIEvent from './SyntheticUIEvent'; function isStartish(topLevelType) { - return topLevelType === TOP_MOUSE_DOWN || topLevelType === TOP_TOUCH_START; + return ( + topLevelType === TOP_MOUSE_DOWN || + topLevelType === TOP_TOUCH_START || + topLevelType === TOP_POINTER_DOWN + ); } function isEndish(topLevelType) { return ( topLevelType === TOP_MOUSE_UP || - topLevelType === TOP_TOUCH_END || - topLevelType === TOP_TOUCH_CANCEL + topLevelType === TOP_POINTER_CANCEL || + topLevelType === TOP_POINTER_UP || + topLevelType === TOP_TOUCH_CANCEL || + topLevelType === TOP_TOUCH_END ); } @@ -102,8 +112,16 @@ const touchEvents = [ TOP_TOUCH_MOVE, ]; +const pointerEvents = [ + TOP_POINTER_CANCEL, + TOP_POINTER_DOWN, + TOP_POINTER_MOVE, + TOP_POINTER_UP, +]; + const dependencies = [TOP_MOUSE_DOWN, TOP_MOUSE_MOVE, TOP_MOUSE_UP].concat( touchEvents, + pointerEvents, ); const eventTypes = { From 5f3167d80da46e1088d839c7dc66a61514fcea6a Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Wed, 16 May 2018 15:01:48 +0200 Subject: [PATCH 2/2] Add Pointer Events DOM Fixture --- fixtures/dom/src/components/Header.js | 1 + fixtures/dom/src/components/fixtures/index.js | 3 + .../fixtures/pointer-events/drag-box.js | 90 +++++++++++++++++++ .../fixtures/pointer-events/drag.js | 25 ++++++ .../fixtures/pointer-events/hover-box.js | 34 +++++++ .../fixtures/pointer-events/hover.js | 51 +++++++++++ .../fixtures/pointer-events/index.js | 20 +++++ 7 files changed, 224 insertions(+) create mode 100644 fixtures/dom/src/components/fixtures/pointer-events/drag-box.js create mode 100644 fixtures/dom/src/components/fixtures/pointer-events/drag.js create mode 100644 fixtures/dom/src/components/fixtures/pointer-events/hover-box.js create mode 100644 fixtures/dom/src/components/fixtures/pointer-events/hover.js create mode 100644 fixtures/dom/src/components/fixtures/pointer-events/index.js diff --git a/fixtures/dom/src/components/Header.js b/fixtures/dom/src/components/Header.js index fbb2e6e505b..4a7b1513e66 100644 --- a/fixtures/dom/src/components/Header.js +++ b/fixtures/dom/src/components/Header.js @@ -64,6 +64,7 @@ class Header extends React.Component { +