From c0bd63bb4c33248b477b90229f5257c6171baefe Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Fri, 14 Aug 2020 13:53:07 -0700 Subject: [PATCH] Restore Previous Behavior Allowing onMouseEnter and onMouseLeave on Touchables (#5736) * Restore Previous Behavior Allowing onMouseEnter and onMouseLeave on Touchables Fixes #5715 See issue for some discussion. Pointer handling in RNW is something of a mess right now, but we need to preserve existing compatibility after conflicting Pressability changes were introuced. Fixup MouseEvent typings, add raw MouseEvent handlers to Pressability, and hook those to Touchables to allow previous behavior. We will need to reconcile this with other platforms eventually. I don't hook this up to Pressable. This leaves it impossible to add hover styling to Pressable, but I would rather we wait until we have a more generalized solution for MouseEvents before forking more component APIs we will need to worry about breaking later. Moved a mouse sample that existed in Playground to RNTester to try to catch mouse regressions in the future. Discovered our forked CoreEventTypes wasn't using the ".windows.js" platform suffix and fixed that. * Change files * Fix overrides and feedback --- ...20-08-14-09-57-33-fix-touchable-mouse.json | 8 ++++ .../windows/playground/MainPage.xaml | 1 - vnext/.flowconfig | 2 + .../Touchable/TouchableHighlight.windows.js | 2 + .../Touchable/TouchableOpacity.windows.js | 2 + .../TouchableWithoutFeedback.windows.js | 7 +++- .../Components/View/ViewPropTypes.windows.js | 4 +- .../Pressability/Pressability.windows.js | 30 +++++++++++++- ...ventTypes.js => CoreEventTypes.windows.js} | 21 +++++++++- .../Mouse/MouseExample.windows.js | 41 ++++++++++++------- .../RNTester/js/utils/RNTesterList.windows.ts | 4 ++ vnext/src/overrides.json | 2 +- 12 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 change/react-native-windows-2020-08-14-09-57-33-fix-touchable-mouse.json rename vnext/src/Libraries/Types/{CoreEventTypes.js => CoreEventTypes.windows.js} (86%) rename packages/playground/Samples/mouse.tsx => vnext/src/RNTester/js/examples-win/Mouse/MouseExample.windows.js (88%) diff --git a/change/react-native-windows-2020-08-14-09-57-33-fix-touchable-mouse.json b/change/react-native-windows-2020-08-14-09-57-33-fix-touchable-mouse.json new file mode 100644 index 00000000000..956ab190ced --- /dev/null +++ b/change/react-native-windows-2020-08-14-09-57-33-fix-touchable-mouse.json @@ -0,0 +1,8 @@ +{ + "type": "patch", + "comment": "Restore Previous Behavior Allowing onMouseEnter and onMouseLeave on Touchables", + "packageName": "react-native-windows", + "email": "ngerlem@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-08-14T16:57:33.938Z" +} diff --git a/packages/playground/windows/playground/MainPage.xaml b/packages/playground/windows/playground/MainPage.xaml index a4824be9fba..3c055e45a2c 100644 --- a/packages/playground/windows/playground/MainPage.xaml +++ b/packages/playground/windows/playground/MainPage.xaml @@ -68,7 +68,6 @@ - diff --git a/vnext/.flowconfig b/vnext/.flowconfig index a4b73ab4f67..227e661cd79 100644 --- a/vnext/.flowconfig +++ b/vnext/.flowconfig @@ -43,6 +43,8 @@ /Libraries/StyleSheet/processColor.js /Libraries/StyleSheet/processColorArray.js /Libraries/StyleSheet/StyleSheetTypes.js +/Libraries/Pressability/Pressability.js +/Libraries/Types/CoreEventTypes.js /RNTester/js/components/ListExampleShared.js /RNTester/js/components/RNTesterExampleList.js /RNTester/js/examples/ScrollView/ScrollViewExample.js diff --git a/vnext/src/Libraries/Components/Touchable/TouchableHighlight.windows.js b/vnext/src/Libraries/Components/Touchable/TouchableHighlight.windows.js index 2e14386084a..f465c4641fd 100644 --- a/vnext/src/Libraries/Components/Touchable/TouchableHighlight.windows.js +++ b/vnext/src/Libraries/Components/Touchable/TouchableHighlight.windows.js @@ -176,6 +176,8 @@ class TouchableHighlight extends React.Component { getPressOutDelayMS: () => this.props.delayPressOut, getPressRectOffset: () => this.props.pressRetentionOffset, getTouchSoundDisabled: () => this.props.touchSoundDisabled, + onMouseEnter: this.props.onMouseEnter, // [Windows] + onMouseLeave: this.props.onMouseLeave, // [Windows] onBlur: event => { if (Platform.isTV) { this._hideUnderlay(); diff --git a/vnext/src/Libraries/Components/Touchable/TouchableOpacity.windows.js b/vnext/src/Libraries/Components/Touchable/TouchableOpacity.windows.js index 35d5339180f..f0a4f286382 100644 --- a/vnext/src/Libraries/Components/Touchable/TouchableOpacity.windows.js +++ b/vnext/src/Libraries/Components/Touchable/TouchableOpacity.windows.js @@ -150,6 +150,8 @@ class TouchableOpacity extends React.Component { getPressDelayMS: () => this.props.delayPressIn, getPressOutDelayMS: () => this.props.delayPressOut, getPressRectOffset: () => this.props.pressRetentionOffset, + onMouseEnter: this.props.onMouseEnter, // [Windows] + onMouseLeave: this.props.onMouseLeave, // [Windows] onBlur: event => { if (Platform.isTV) { this._opacityInactive(250); diff --git a/vnext/src/Libraries/Components/Touchable/TouchableWithoutFeedback.windows.js b/vnext/src/Libraries/Components/Touchable/TouchableWithoutFeedback.windows.js index bcd4de3adba..b94fa9cbed8 100644 --- a/vnext/src/Libraries/Components/Touchable/TouchableWithoutFeedback.windows.js +++ b/vnext/src/Libraries/Components/Touchable/TouchableWithoutFeedback.windows.js @@ -27,6 +27,7 @@ import type { BlurEvent, FocusEvent, LayoutEvent, + MouseEvent, // [Windows] PressEvent, } from '../../Types/CoreEventTypes'; import Platform from '../../Utilities/Platform'; @@ -70,8 +71,8 @@ type Props = $ReadOnly<{| accessibilityPosInSet?: ?number, // [Windows] accessibilitySetSize?: ?number, // [Windows] onAccessibilityTap?: ?() => void, // [Windows] - onMouseEnter?: ?(event: SyntheticEvent<{}>) => void, // [Windows] - onMouseLeave?: ?(event: SyntheticEvent<{}>) => void, // [Windows] + onMouseEnter?: ?(event: MouseEvent) => void, // [Windows] + onMouseLeave?: ?(event: MouseEvent) => void, // [Windows] tabIndex?: ?number, // [Windows] tooltip?: ?Stringish, // [Windows] |}>; @@ -127,6 +128,8 @@ class TouchableWithoutFeedback extends React.Component { getPressOutDelayMS: () => this.props.delayPressOut, getPressRectOffset: () => this.props.pressRetentionOffset, getTouchSoundDisabled: () => this.props.touchSoundDisabled, + onMouseEnter: this.props.onMouseEnter, // [Windows] + onMouseLeave: this.props.onMouseLeave, // [Windows] onBlur: event => { if (this.props.onBlur != null) { this.props.onBlur(event); diff --git a/vnext/src/Libraries/Components/View/ViewPropTypes.windows.js b/vnext/src/Libraries/Components/View/ViewPropTypes.windows.js index 8a50a858811..f3f09713c9c 100644 --- a/vnext/src/Libraries/Components/View/ViewPropTypes.windows.js +++ b/vnext/src/Libraries/Components/View/ViewPropTypes.windows.js @@ -434,8 +434,8 @@ type WindowsViewProps = $ReadOnly<{| onFocus?: ?(event: FocusEvent) => mixed, onBlur?: ?(event: FocusEvent) => mixed, - onMouseLeave?: ?(event: SyntheticEvent<{}>) => mixed, - onMouseEnter?: ?(event: SyntheticEvent<{}>) => mixed, + onMouseLeave?: ?(event: MouseEvent) => mixed, + onMouseEnter?: ?(event: MouseEvent) => mixed, |}>; // Windows] diff --git a/vnext/src/Libraries/Pressability/Pressability.windows.js b/vnext/src/Libraries/Pressability/Pressability.windows.js index 31883d6d352..4f64a0a20c1 100644 --- a/vnext/src/Libraries/Pressability/Pressability.windows.js +++ b/vnext/src/Libraries/Pressability/Pressability.windows.js @@ -20,7 +20,7 @@ import type { PressEvent, MouseEvent, KeyEvent, // [Windows] -} from '../Types/CoreEventTypes.js'; +} from '../Types/CoreEventTypes'; // [Windows] remove explicit .js import Platform from '../Utilities/Platform'; import UIManager from '../ReactNative/UIManager'; import type {HostComponent} from '../Renderer/shims/ReactNativeTypes'; @@ -129,6 +129,22 @@ export type PressabilityConfig = $ReadOnly<{| * Returns whether to start a press gesture. */ onStartShouldSetResponder?: ?() => boolean, + + // [Windows + /** + * Raw handler for onMouseEnter that will be preferred if set over hover + * events. This is to preserve compatibility with pre-0.62 behavior which + * allowed attaching mouse event handlers to Touchables + */ + onMouseEnter?: ?(event: MouseEvent) => mixed, + + /** + * Raw handler for onMouseLeave that will be preferred if set over hover + * events. This is to preserve compatibility with pre-0.62 behavior which + * allowed attaching mouse event handlers to Touchables + */ + onMouseLeave?: ?(event: MouseEvent) => mixed, + // Windows] |}>; type EventHandlers = $ReadOnly<{| @@ -528,6 +544,12 @@ export default class Pressability { ? null : { onMouseEnter: (event: MouseEvent): void => { + // [Windows Add attached raw mouse event handler for compat + if (this._config.onMouseEnter) { + this._config.onMouseEnter(event); + } + // Windows] + if (isHoverEnabled()) { this._isHovered = true; this._cancelHoverOutDelayTimeout(); @@ -546,6 +568,12 @@ export default class Pressability { }, onMouseLeave: (event: MouseEvent): void => { + // [Windows Add attached raw mouse event handler for compat + if (this._config.onMouseLeave) { + this._config.onMouseLeave(event); + } + // Windows] + if (this._isHovered) { this._isHovered = false; this._cancelHoverInDelayTimeout(); diff --git a/vnext/src/Libraries/Types/CoreEventTypes.js b/vnext/src/Libraries/Types/CoreEventTypes.windows.js similarity index 86% rename from vnext/src/Libraries/Types/CoreEventTypes.js rename to vnext/src/Libraries/Types/CoreEventTypes.windows.js index 3758e8356fc..cdb4d305025 100644 --- a/vnext/src/Libraries/Types/CoreEventTypes.js +++ b/vnext/src/Libraries/Types/CoreEventTypes.windows.js @@ -151,15 +151,32 @@ export type FocusEvent = SyntheticEvent< |}>, >; +// [Windows Mouse events on Windows don't match up with the version in core +// introduced for react-native-web. Replace typings with our values to catch +// anything dependent on react-native-web specific values export type MouseEvent = SyntheticEvent< $ReadOnly<{| - clientX: number, - clientY: number, + target: number, + identifier: number, pageX: number, pageY: number, + locationX: number, + locationY: number, timestamp: number, + pointerType: string, + force: number, + isLeftButton: boolean, + isRightButton: boolean, + isMiddleButton: boolean, + isBarrelButtonPressed: boolean, + isHorizontalScrollWheel: boolean, + isEraser: boolean, + shiftKey: boolean, + ctrlKey: boolean, + altKey: boolean, |}>, >; +// Windows] // [Windows export type KeyEvent = SyntheticEvent< diff --git a/packages/playground/Samples/mouse.tsx b/vnext/src/RNTester/js/examples-win/Mouse/MouseExample.windows.js similarity index 88% rename from packages/playground/Samples/mouse.tsx rename to vnext/src/RNTester/js/examples-win/Mouse/MouseExample.windows.js index b2a4f0f6902..993887fa2e7 100644 --- a/packages/playground/Samples/mouse.tsx +++ b/vnext/src/RNTester/js/examples-win/Mouse/MouseExample.windows.js @@ -3,16 +3,31 @@ * Licensed under the MIT License. * @format */ -import * as React from 'react'; -import { - AppRegistry, + +'use strict'; + +const React = require('react'); + +const { StyleSheet, View, Text, GestureResponderEvent, TouchableHighlight, BackHandler, -} from 'react-native'; +} = require('react-native'); + +exports.displayName = 'MouseExample'; +exports.title = 'Mouse Events'; +exports.description = 'Tests that mouse events can be observed'; +exports.examples = [ + { + title: 'onMouseEnter and onMouseLeave affect style\n', + render: function(): React.Node { + return ; + }, + }, +]; const styles = StyleSheet.create({ page: { @@ -68,16 +83,16 @@ const styles = StyleSheet.create({ }, }); -export default class Bootstrap extends React.Component< +export default class ExampleComponent extends React.Component< {}, { - clicked: number; - pageHover: boolean; - contentHover: boolean; - contentChildHover: boolean; - overlayHover: boolean; - overlayChildHover: boolean; - } + clicked: number, + pageHover: boolean, + contentHover: boolean, + contentChildHover: boolean, + overlayHover: boolean, + overlayChildHover: boolean, + }, > { constructor(props: {}) { super(props); @@ -225,5 +240,3 @@ export default class Bootstrap extends React.Component< return true; }; } - -AppRegistry.registerComponent('Bootstrap', () => Bootstrap); diff --git a/vnext/src/RNTester/js/utils/RNTesterList.windows.ts b/vnext/src/RNTester/js/utils/RNTesterList.windows.ts index a554aaeb707..d472f6c8736 100644 --- a/vnext/src/RNTester/js/utils/RNTesterList.windows.ts +++ b/vnext/src/RNTester/js/utils/RNTesterList.windows.ts @@ -203,6 +203,10 @@ const APIExamples: Array = [ key: 'LayoutExample', module: require('react-native/RNTester/js/examples/Layout/LayoutExample'), }, + { + key: 'MouseExample', + module: require('./../examples-win/Mouse/MouseExample'), + }, { key: 'NativeAnimationsExample', module: require('react-native/RNTester/js/examples/NativeAnimation/NativeAnimationsExample'), diff --git a/vnext/src/overrides.json b/vnext/src/overrides.json index c02cfcdb585..cbc34ac6551 100644 --- a/vnext/src/overrides.json +++ b/vnext/src/overrides.json @@ -674,7 +674,7 @@ }, { "type": "patch", - "file": "Libraries\\Types\\CoreEventTypes.js", + "file": "Libraries\\Types\\CoreEventTypes.windows.js", "baseFile": "Libraries\\Types\\CoreEventTypes.js", "baseVersion": "0.62.0-rc.3", "baseHash": "83b203d547d9bdc57a8f3346ecee6f6e34b25f5d",