Restore TouchableHighlight and TouchableOpacity behaviour on TV platforms (#21478)

Summary:
Since #18470, the default focus behaviour of `TouchableHighlight` and `TouchableOpacity` has been missing on tvOS. This uses the new `touchableHandleFocus` and `touchableHandleBlur` functions to restore the behaviour. Fixes #21295.
Pull Request resolved: https://github.com/facebook/react-native/pull/21478

Differential Revision: D13372959

Pulled By: cpojer

fbshipit-source-id: a5fa9d45214ac48a14a6573ccf014bba1ee0a103
This commit is contained in:
gtebbutt 2018-12-06 21:02:50 -08:00 committed by Facebook Github Bot
parent c090758c12
commit cc4211c72f
4 changed files with 66 additions and 4 deletions

View File

@ -583,7 +583,8 @@ const TouchableMixin = {
* visually distinguish the `VisualRect` so that the user knows that it * visually distinguish the `VisualRect` so that the user knows that it
* currently has the focus. Most platforms only support a single element being * currently has the focus. Most platforms only support a single element being
* focused at a time, in which case there may have been a previously focused * focused at a time, in which case there may have been a previously focused
* element that was blurred just prior to this. * element that was blurred just prior to this. This can be overridden when
* using `Touchable.Mixin.withoutDefaultFocusAndBlur`.
*/ */
touchableHandleFocus: function(e: Event) { touchableHandleFocus: function(e: Event) {
this.props.onFocus && this.props.onFocus(e); this.props.onFocus && this.props.onFocus(e);
@ -594,6 +595,8 @@ const TouchableMixin = {
* visually distinguish the `VisualRect` so that the user knows that it * visually distinguish the `VisualRect` so that the user knows that it
* no longer has focus. Most platforms only support a single element being * no longer has focus. Most platforms only support a single element being
* focused at a time, in which case the focus may have moved to another. * focused at a time, in which case the focus may have moved to another.
* This can be overridden when using
* `Touchable.Mixin.withoutDefaultFocusAndBlur`.
*/ */
touchableHandleBlur: function(e: Event) { touchableHandleBlur: function(e: Event) {
this.props.onBlur && this.props.onBlur(e); this.props.onBlur && this.props.onBlur(e);
@ -900,8 +903,23 @@ const TouchableMixin = {
} }
} }
}, },
withoutDefaultFocusAndBlur: {},
}; };
/**
* Provide an optional version of the mixin where `touchableHandleFocus` and
* `touchableHandleBlur` can be overridden. This allows appropriate defaults to
* be set on TV platforms, without breaking existing implementations of
* `Touchable`.
*/
const {
touchableHandleFocus,
touchableHandleBlur,
...TouchableMixinWithoutDefaultFocusAndBlur
} = TouchableMixin;
TouchableMixin.withoutDefaultFocusAndBlur = TouchableMixinWithoutDefaultFocusAndBlur;
const Touchable = { const Touchable = {
Mixin: TouchableMixin, Mixin: TouchableMixin,
TOUCH_TARGET_DEBUG: false, // Highlights all touchable targets. Toggle with Inspector. TOUCH_TARGET_DEBUG: false, // Highlights all touchable targets. Toggle with Inspector.

View File

@ -13,6 +13,7 @@ const Animated = require('Animated');
const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes'); const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes');
const DeprecatedEdgeInsetsPropType = require('DeprecatedEdgeInsetsPropType'); const DeprecatedEdgeInsetsPropType = require('DeprecatedEdgeInsetsPropType');
const NativeMethodsMixin = require('NativeMethodsMixin'); const NativeMethodsMixin = require('NativeMethodsMixin');
const Platform = require('Platform');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const React = require('React'); const React = require('React');
const Touchable = require('Touchable'); const Touchable = require('Touchable');
@ -52,7 +53,7 @@ type Props = $ReadOnly<{|
*/ */
const TouchableBounce = ((createReactClass({ const TouchableBounce = ((createReactClass({
displayName: 'TouchableBounce', displayName: 'TouchableBounce',
mixins: [Touchable.Mixin, NativeMethodsMixin], mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur, NativeMethodsMixin],
propTypes: { propTypes: {
...TouchableWithoutFeedback.propTypes, ...TouchableWithoutFeedback.propTypes,
@ -118,6 +119,20 @@ const TouchableBounce = ((createReactClass({
this.props.onPressOut && this.props.onPressOut(e); this.props.onPressOut && this.props.onPressOut(e);
}, },
touchableHandleFocus: function(e: Event) {
if (Platform.isTV) {
this.bounceTo(0.93, 0.1, 0);
}
this.props.onFocus && this.props.onFocus(e);
},
touchableHandleBlur: function(e: Event) {
if (Platform.isTV) {
this.bounceTo(1, 0.4, 0);
}
this.props.onBlur && this.props.onBlur(e);
},
touchableHandlePress: function(e: PressEvent) { touchableHandlePress: function(e: PressEvent) {
const onPressWithCompletion = this.props.onPressWithCompletion; const onPressWithCompletion = this.props.onPressWithCompletion;
if (onPressWithCompletion) { if (onPressWithCompletion) {

View File

@ -195,7 +195,7 @@ const TouchableHighlight = ((createReactClass({
testOnly_pressed: PropTypes.bool, testOnly_pressed: PropTypes.bool,
}, },
mixins: [NativeMethodsMixin, Touchable.Mixin], mixins: [NativeMethodsMixin, Touchable.Mixin.withoutDefaultFocusAndBlur],
getDefaultProps: () => DEFAULT_PROPS, getDefaultProps: () => DEFAULT_PROPS,
@ -257,6 +257,20 @@ const TouchableHighlight = ((createReactClass({
this.props.onPressOut && this.props.onPressOut(e); this.props.onPressOut && this.props.onPressOut(e);
}, },
touchableHandleFocus: function(e: Event) {
if (Platform.isTV) {
this._showUnderlay();
}
this.props.onFocus && this.props.onFocus(e);
},
touchableHandleBlur: function(e: Event) {
if (Platform.isTV) {
this._hideUnderlay();
}
this.props.onBlur && this.props.onBlur(e);
},
touchableHandlePress: function(e: PressEvent) { touchableHandlePress: function(e: PressEvent) {
clearTimeout(this._hideTimeout); clearTimeout(this._hideTimeout);
if (!Platform.isTV) { if (!Platform.isTV) {

View File

@ -13,6 +13,7 @@
const Animated = require('Animated'); const Animated = require('Animated');
const Easing = require('Easing'); const Easing = require('Easing');
const NativeMethodsMixin = require('NativeMethodsMixin'); const NativeMethodsMixin = require('NativeMethodsMixin');
const Platform = require('Platform');
const React = require('React'); const React = require('React');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const Touchable = require('Touchable'); const Touchable = require('Touchable');
@ -131,7 +132,7 @@ type Props = $ReadOnly<{|
*/ */
const TouchableOpacity = ((createReactClass({ const TouchableOpacity = ((createReactClass({
displayName: 'TouchableOpacity', displayName: 'TouchableOpacity',
mixins: [Touchable.Mixin, NativeMethodsMixin], mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur, NativeMethodsMixin],
propTypes: { propTypes: {
...TouchableWithoutFeedback.propTypes, ...TouchableWithoutFeedback.propTypes,
@ -207,6 +208,20 @@ const TouchableOpacity = ((createReactClass({
this.props.onPressOut && this.props.onPressOut(e); this.props.onPressOut && this.props.onPressOut(e);
}, },
touchableHandleFocus: function(e: Event) {
if (Platform.isTV) {
this._opacityActive(150);
}
this.props.onFocus && this.props.onFocus(e);
},
touchableHandleBlur: function(e: Event) {
if (Platform.isTV) {
this._opacityInactive(250);
}
this.props.onBlur && this.props.onBlur(e);
},
touchableHandlePress: function(e: PressEvent) { touchableHandlePress: function(e: PressEvent) {
this.props.onPress && this.props.onPress(e); this.props.onPress && this.props.onPress(e);
}, },