[change] Update Touchable components

Reference RN 0.60 implementations with web-specific fixes

Ref #1172
Fix #1105
This commit is contained in:
Nicolas Gallagher
2019-10-07 14:01:05 -07:00
parent 33f1c3566c
commit 3ac0b96498
6 changed files with 782 additions and 338 deletions
+1
View File
@@ -34,6 +34,7 @@
"window": false,
// Flow global types,
"$Enum": false,
"$ReadOnly": false,
"CSSStyleSheet": false,
"HTMLInputElement": false,
"ReactClass": false,
+1
View File
@@ -5,6 +5,7 @@
<PROJECT_ROOT>/.*/__tests__/.*
<PROJECT_ROOT>/packages/.*/dist/.*
<PROJECT_ROOT>/packages/examples/.*
<PROJECT_ROOT>/packages/react-native-web/src/vendor/.*
<PROJECT_ROOT>/packages/website/.*
.*/node_modules/babel-plugin-transform-react-remove-prop-types/*
+180 -85
View File
@@ -1,26 +1,42 @@
/* eslint-disable react/prop-types */
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import AccessibilityUtil from '../../modules/AccessibilityUtil';
import BoundingDimensions from './BoundingDimensions';
import findNodeHandle from '../findNodeHandle';
import normalizeColor from 'normalize-css-color';
import Position from './Position';
import React from 'react';
import TouchEventUtils from 'fbjs/lib/TouchEventUtils';
import UIManager from '../UIManager';
import View from '../View';
type Event = Object;
type PressEvent = Object;
type EdgeInsetsProp = Object;
const extractSingleTouch = nativeEvent => {
const touches = nativeEvent.touches;
const changedTouches = nativeEvent.changedTouches;
const hasTouches = touches && touches.length > 0;
const hasChangedTouches = changedTouches && changedTouches.length > 0;
return !hasTouches && hasChangedTouches
? changedTouches[0]
: hasTouches
? touches[0]
: nativeEvent;
};
/**
* `Touchable`: Taps done right.
@@ -110,6 +126,7 @@ type Event = Object;
/**
* Touchable states.
*/
const States = {
NOT_RESPONDER: 'NOT_RESPONDER', // Not the responder
RESPONDER_INACTIVE_PRESS_IN: 'RESPONDER_INACTIVE_PRESS_IN', // Responder, inactive, in the `PressRect`
@@ -121,10 +138,33 @@ const States = {
ERROR: 'ERROR'
};
/**
type State =
| typeof States.NOT_RESPONDER
| typeof States.RESPONDER_INACTIVE_PRESS_IN
| typeof States.RESPONDER_INACTIVE_PRESS_OUT
| typeof States.RESPONDER_ACTIVE_PRESS_IN
| typeof States.RESPONDER_ACTIVE_PRESS_OUT
| typeof States.RESPONDER_ACTIVE_LONG_PRESS_IN
| typeof States.RESPONDER_ACTIVE_LONG_PRESS_OUT
| typeof States.ERROR;
/*
* Quick lookup map for states that are considered to be "active"
*/
const baseStatesConditions = {
NOT_RESPONDER: false,
RESPONDER_INACTIVE_PRESS_IN: false,
RESPONDER_INACTIVE_PRESS_OUT: false,
RESPONDER_ACTIVE_PRESS_IN: false,
RESPONDER_ACTIVE_PRESS_OUT: false,
RESPONDER_ACTIVE_LONG_PRESS_IN: false,
RESPONDER_ACTIVE_LONG_PRESS_OUT: false,
ERROR: false
};
const IsActive = {
...baseStatesConditions,
RESPONDER_ACTIVE_PRESS_OUT: true,
RESPONDER_ACTIVE_PRESS_IN: true
};
@@ -134,12 +174,14 @@ const IsActive = {
* therefore eligible to result in a "selection" if the press stops.
*/
const IsPressingIn = {
...baseStatesConditions,
RESPONDER_INACTIVE_PRESS_IN: true,
RESPONDER_ACTIVE_PRESS_IN: true,
RESPONDER_ACTIVE_LONG_PRESS_IN: true
};
const IsLongPressingIn = {
...baseStatesConditions,
RESPONDER_ACTIVE_LONG_PRESS_IN: true
};
@@ -156,6 +198,15 @@ const Signals = {
LONG_PRESS_DETECTED: 'LONG_PRESS_DETECTED'
};
type Signal =
| typeof Signals.DELAY
| typeof Signals.RESPONDER_GRANT
| typeof Signals.RESPONDER_RELEASE
| typeof Signals.RESPONDER_TERMINATED
| typeof Signals.ENTER_PRESS_RECT
| typeof Signals.LEAVE_PRESS_RECT
| typeof Signals.LONG_PRESS_DETECTED;
/**
* Mapping from States x Signals => States
*/
@@ -382,13 +433,16 @@ const TouchableMixin = {
/**
* Place as callback for a DOM element's `onResponderGrant` event.
* @param {SyntheticEvent} e Synthetic event from event system.
*
*/
touchableHandleResponderGrant: function(e: Event) {
touchableHandleResponderGrant: function(e: PressEvent) {
const dispatchID = e.currentTarget;
// Since e is used in a callback invoked on another event loop
// (as in setTimeout etc), we need to call e.persist() on the
// event to make sure it doesn't get reused in the event object pool.
e.persist();
this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout);
this.pressOutDelayTimeout = null;
@@ -403,7 +457,6 @@ const TouchableMixin = {
if (delayMS !== 0) {
this.touchableDelayTimeout = setTimeout(this._handleDelay.bind(this, e), delayMS);
} else {
this.state.touchable.positionOnActivate = null;
this._handleDelay(e);
}
@@ -421,27 +474,23 @@ const TouchableMixin = {
/**
* Place as callback for a DOM element's `onResponderRelease` event.
*/
touchableHandleResponderRelease: function(e: Event) {
touchableHandleResponderRelease: function(e: PressEvent) {
this.pressInLocation = null;
this._receiveSignal(Signals.RESPONDER_RELEASE, e);
},
/**
* Place as callback for a DOM element's `onResponderTerminate` event.
*/
touchableHandleResponderTerminate: function(e: Event) {
touchableHandleResponderTerminate: function(e: PressEvent) {
this.pressInLocation = null;
this._receiveSignal(Signals.RESPONDER_TERMINATED, e);
},
/**
* Place as callback for a DOM element's `onResponderMove` event.
*/
touchableHandleResponderMove: function(e: Event) {
// Not enough time elapsed yet, wait for highlight -
// this is just a perf optimization.
if (this.state.touchable.touchState === States.RESPONDER_INACTIVE_PRESS_IN) {
return;
}
touchableHandleResponderMove: function(e: PressEvent) {
// Measurement may not have returned yet.
if (!this.state.touchable.positionOnActivate) {
return;
@@ -466,13 +515,13 @@ const TouchableMixin = {
const hitSlop = this.touchableGetHitSlop ? this.touchableGetHitSlop() : null;
if (hitSlop) {
pressExpandLeft += hitSlop.left;
pressExpandTop += hitSlop.top;
pressExpandRight += hitSlop.right;
pressExpandBottom += hitSlop.bottom;
pressExpandLeft += hitSlop.left || 0;
pressExpandTop += hitSlop.top || 0;
pressExpandRight += hitSlop.right || 0;
pressExpandBottom += hitSlop.bottom || 0;
}
const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
const touch = extractSingleTouch(e.nativeEvent);
const pageX = touch && touch.pageX;
const pageY = touch && touch.pageY;
@@ -494,9 +543,13 @@ const TouchableMixin = {
pageX < positionOnActivate.left + dimensionsOnActivate.width + pressExpandRight &&
pageY < positionOnActivate.top + dimensionsOnActivate.height + pressExpandBottom;
if (isTouchWithinActive) {
const prevState = this.state.touchable.touchState;
this._receiveSignal(Signals.ENTER_PRESS_RECT, e);
const curState = this.state.touchable.touchState;
if (curState === States.RESPONDER_INACTIVE_PRESS_IN) {
if (
curState === States.RESPONDER_INACTIVE_PRESS_IN &&
prevState !== States.RESPONDER_INACTIVE_PRESS_IN
) {
// fix for t7967420
this._cancelLongPressDelayTimeout();
}
@@ -506,6 +559,30 @@ const TouchableMixin = {
}
},
/**
* Invoked when the item receives focus. Mixers might override this to
* visually distinguish the `VisualRect` so that the user knows that it
* 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
* element that was blurred just prior to this. This can be overridden when
* using `Touchable.Mixin.withoutDefaultFocusAndBlur`.
*/
touchableHandleFocus: function(e: Event) {
this.props.onFocus && this.props.onFocus(e);
},
/**
* Invoked when the item loses focus. Mixers might override this to
* visually distinguish the `VisualRect` so that the user knows that it
* 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.
* This can be overridden when using
* `Touchable.Mixin.withoutDefaultFocusAndBlur`.
*/
touchableHandleBlur: function(e: Event) {
this.props.onBlur && this.props.onBlur(e);
},
// ==== Abstract Application Callbacks ====
/**
@@ -592,33 +669,31 @@ const TouchableMixin = {
},
_handleQueryLayout: function(
x: number,
y: number,
width: number,
height: number,
l: number,
t: number,
w: number,
h: number,
globalX: number,
globalY: number
) {
// don't do anything if UIManager failed to measure node
if (!x && !y && !width && !height && !globalX && !globalY) {
//don't do anything UIManager failed to measure node
if (!l && !t && !w && !h && !globalX && !globalY) {
return;
}
this.state.touchable.positionOnActivate &&
Position.release(this.state.touchable.positionOnActivate);
this.state.touchable.dimensionsOnActivate &&
// $FlowFixMe
BoundingDimensions.release(this.state.touchable.dimensionsOnActivate);
this.state.touchable.positionOnActivate = Position.getPooled(globalX, globalY);
// $FlowFixMe
this.state.touchable.dimensionsOnActivate = BoundingDimensions.getPooled(width, height);
this.state.touchable.dimensionsOnActivate = BoundingDimensions.getPooled(w, h);
},
_handleDelay: function(e: Event) {
_handleDelay: function(e: PressEvent) {
this.touchableDelayTimeout = null;
this._receiveSignal(Signals.DELAY, e);
},
_handleLongDelay: function(e: Event) {
_handleLongDelay: function(e: PressEvent) {
this.longPressDelayTimeout = null;
const curState = this.state.touchable.touchState;
if (
@@ -646,7 +721,7 @@ const TouchableMixin = {
* @throws Error if invalid state transition or unrecognized signal.
* @sideeffects
*/
_receiveSignal: function(signal: string, e: Event) {
_receiveSignal: function(signal: Signal, e: PressEvent) {
const responderID = this.state.touchable.responderID;
const curState = this.state.touchable.touchState;
const nextState = Transitions[curState] && Transitions[curState][signal];
@@ -686,26 +761,19 @@ const TouchableMixin = {
this.longPressDelayTimeout = null;
},
_isHighlight: function(state: string) {
_isHighlight: function(state: State) {
return (
state === States.RESPONDER_ACTIVE_PRESS_IN || state === States.RESPONDER_ACTIVE_LONG_PRESS_IN
);
},
_savePressInLocation: function(e: Event) {
const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
_savePressInLocation: function(e: PressEvent) {
const touch = extractSingleTouch(e.nativeEvent);
const pageX = touch && touch.pageX;
const pageY = touch && touch.pageY;
this.pressInLocation = {
pageX,
pageY,
get locationX() {
return touch && touch.locationX;
},
get locationY() {
return touch && touch.locationY;
}
};
const locationX = touch && touch.locationX;
const locationY = touch && touch.locationY;
this.pressInLocation = { pageX, pageY, locationX, locationY };
},
_getDistanceBetweenPoints: function(aX: number, aY: number, bX: number, bY: number) {
@@ -726,10 +794,10 @@ const TouchableMixin = {
* @sideeffects
*/
_performSideEffectsForTransition: function(
curState: string,
nextState: string,
signal: string,
e: Event
curState: State,
nextState: State,
signal: Signal,
e: PressEvent
) {
const curIsHighlight = this._isHighlight(curState);
const newIsHighlight = this._isHighlight(nextState);
@@ -741,7 +809,11 @@ const TouchableMixin = {
this._cancelLongPressDelayTimeout();
}
if (!IsActive[curState] && IsActive[nextState]) {
const isInitialTransition =
curState === States.NOT_RESPONDER && nextState === States.RESPONDER_INACTIVE_PRESS_IN;
const isActiveTransition = !IsActive[curState] && IsActive[nextState];
if (isInitialTransition || isActiveTransition) {
this._remeasureMetricsOnActivation();
}
@@ -758,9 +830,8 @@ const TouchableMixin = {
if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) {
const hasLongPressHandler = !!this.props.onLongPress;
const pressIsLongButStillCallOnPress =
IsLongPressingIn[curState] && // We *are* long pressing..
(!hasLongPressHandler || // But either has no long handler
!this.touchableLongPressCancelsPress()); // or we're told to ignore it.
IsLongPressingIn[curState] && // We *are* long pressing.. // But either has no long handler
(!hasLongPressHandler || !this.touchableLongPressCancelsPress()); // or we're told to ignore it.
const shouldInvokePress = !IsLongPressingIn[curState] || pressIsLongButStillCallOnPress;
if (shouldInvokePress && this.touchableHandlePress) {
@@ -777,12 +848,16 @@ const TouchableMixin = {
this.touchableDelayTimeout = null;
},
_startHighlight: function(e: Event) {
_playTouchSound: function() {
UIManager.playTouchSound();
},
_startHighlight: function(e: PressEvent) {
this._savePressInLocation(e);
this.touchableHandleActivePressIn && this.touchableHandleActivePressIn(e);
},
_endHighlight: function(e: Event) {
_endHighlight: function(e: PressEvent) {
if (this.touchableHandleActivePressOut) {
if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) {
this.pressOutDelayTimeout = setTimeout(() => {
@@ -797,10 +872,8 @@ const TouchableMixin = {
// HACK (part 2): basic support for touchable interactions using a keyboard (including
// delays and longPress)
touchableHandleKeyEvent: function(e: Event) {
const ENTER = 13;
const SPACE = 32;
const { type, which } = e;
if (which === ENTER || which === SPACE) {
const { type, key } = e;
if (key === 'Enter' || key === ' ') {
if (type === 'keydown') {
if (!this._isTouchableKeyboardActive) {
if (
@@ -825,44 +898,66 @@ const TouchableMixin = {
e.stopPropagation();
// prevent the default behaviour unless the Touchable functions as a link
// and Enter is pressed
if (!(which === ENTER && AccessibilityUtil.propsToAriaRole(this.props) === 'link')) {
if (!(key === 'Enter' && AccessibilityUtil.propsToAriaRole(this.props) === 'link')) {
e.preventDefault();
}
}
}
},
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 {
// eslint-disable-next-line no-unused-vars
touchableHandleFocus,
// eslint-disable-next-line no-unused-vars
touchableHandleBlur,
...TouchableMixinWithoutDefaultFocusAndBlur
} = TouchableMixin;
TouchableMixin.withoutDefaultFocusAndBlur = TouchableMixinWithoutDefaultFocusAndBlur;
const Touchable = {
Mixin: TouchableMixin,
TOUCH_TARGET_DEBUG: false, // Highlights all touchable targets. Toggle with Inspector.
/**
* Renders a debugging overlay to visualize touch target with hitSlop (might not work on Android).
*/
renderDebugView: ({ color, hitSlop }: Object) => {
if (process.env.NODE_ENV !== 'production') {
if (!Touchable.TOUCH_TARGET_DEBUG) {
return null;
}
const debugHitSlopStyle = {};
hitSlop = hitSlop || { top: 0, bottom: 0, left: 0, right: 0 };
for (const key in hitSlop) {
debugHitSlopStyle[key] = -hitSlop[key];
}
const hexColor = '#' + ('00000000' + normalizeColor(color).toString(16)).substr(-8);
return (
<View
pointerEvents="none"
style={{
position: 'absolute',
borderColor: hexColor.slice(0, -2) + '55', // More opaque
borderWidth: 1,
borderStyle: 'dashed',
backgroundColor: hexColor.slice(0, -2) + '0F', // Less opaque
...debugHitSlopStyle
}}
/>
);
renderDebugView: ({ color, hitSlop }: { color: string | number, hitSlop: EdgeInsetsProp }) => {
if (!Touchable.TOUCH_TARGET_DEBUG) {
return null;
}
if (process.env.NODE_ENV !== 'production') {
throw Error('Touchable.TOUCH_TARGET_DEBUG should not be enabled in prod!');
}
const debugHitSlopStyle = {};
hitSlop = hitSlop || { top: 0, bottom: 0, left: 0, right: 0 };
for (const key in hitSlop) {
debugHitSlopStyle[key] = -hitSlop[key];
}
const normalizedColor = normalizeColor(color);
if (typeof normalizedColor !== 'number') {
return null;
}
const hexColor = '#' + ('00000000' + normalizedColor.toString(16)).substr(-8);
return (
<View
pointerEvents="none"
style={{
position: 'absolute',
borderColor: hexColor.slice(0, -2) + '55', // More opaque
borderWidth: 1,
borderStyle: 'dashed',
backgroundColor: hexColor.slice(0, -2) + '0F', // Less opaque
...debugHitSlopStyle
}}
/>
);
}
};
@@ -1,36 +1,50 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
* @flow
* @format
*/
'use strict';
import applyNativeMethods from '../../modules/applyNativeMethods';
import ColorPropType from '../ColorPropType';
import DeprecatedColorPropType from '../ColorPropType';
import createReactClass from 'create-react-class';
import ensureComponentIsNative from '../../modules/ensureComponentIsNative';
import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps';
import React from 'react';
import * as React from 'react';
import StyleSheet from '../StyleSheet';
import TimerMixin from 'react-timer-mixin';
import Touchable from '../Touchable';
import TouchableWithoutFeedback from '../TouchableWithoutFeedback';
import TouchableWithoutFeedback, {
type Props as TouchableWithoutFeedbackProps
} from '../TouchableWithoutFeedback';
import View from '../View';
import ViewPropTypes from '../ViewPropTypes';
import { func, number } from 'prop-types';
import DeprecatedViewPropTypes from '../ViewPropTypes';
import PropTypes from 'prop-types';
type Event = Object;
type PressEvent = Object;
const DEFAULT_PROPS = {
activeOpacity: 0.85,
delayPressOut: 100,
underlayColor: 'black'
};
const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
type Props = $ReadOnly<{|
...TouchableWithoutFeedbackProps,
activeOpacity?: ?number,
underlayColor?: ?any,
style?: ?any,
onShowUnderlay?: ?() => void,
onHideUnderlay?: ?() => void,
testOnly_pressed?: ?boolean
|}>;
/**
* A wrapper for making views respond properly to touches.
* On press down, the opacity of the wrapped view is decreased, which allows
@@ -58,114 +72,244 @@ const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
* );
* },
* ```
*
*
* ### Example
*
* ```ReactNativeWebPlayer
* import React, { Component } from 'react'
* import {
* AppRegistry,
* StyleSheet,
* TouchableHighlight,
* Text,
* View,
* } from 'react-native'
*
* class App extends Component {
* constructor(props) {
* super(props)
* this.state = { count: 0 }
* }
*
* onPress = () => {
* this.setState({
* count: this.state.count+1
* })
* }
*
* render() {
* return (
* <View style={styles.container}>
* <TouchableHighlight
* style={styles.button}
* onPress={this.onPress}
* >
* <Text> Touch Here </Text>
* </TouchableHighlight>
* <View style={[styles.countContainer]}>
* <Text style={[styles.countText]}>
* { this.state.count !== 0 ? this.state.count: null}
* </Text>
* </View>
* </View>
* )
* }
* }
*
* const styles = StyleSheet.create({
* container: {
* flex: 1,
* justifyContent: 'center',
* paddingHorizontal: 10
* },
* button: {
* alignItems: 'center',
* backgroundColor: '#DDDDDD',
* padding: 10
* },
* countContainer: {
* alignItems: 'center',
* padding: 10
* },
* countText: {
* color: '#FF00FF'
* }
* })
*
* AppRegistry.registerComponent('App', () => App)
* ```
*
*/
/* eslint-disable react/prefer-es6-class */
const TouchableHighlight = createReactClass({
// eslint-disable-next-line react/prefer-es6-class
const TouchableHighlight = ((createReactClass({
displayName: 'TouchableHighlight',
propTypes: {
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete this
* comment and run Flow. */
...TouchableWithoutFeedback.propTypes,
/**
* Determines what the opacity of the wrapped view should be when touch is
* active.
*/
activeOpacity: number,
/**
* Called immediately after the underlay is hidden
*/
onHideUnderlay: func,
/**
* Called immediately after the underlay is shown
*/
onShowUnderlay: func,
style: ViewPropTypes.style,
activeOpacity: PropTypes.number,
/**
* The color of the underlay that will show through when the touch is
* active.
*/
underlayColor: ColorPropType
hasTVPreferredFocus: PropTypes.bool,
/**
* Style to apply to the container/underlay. Most commonly used to make sure
* rounded corners match the wrapped component.
*/
nextFocusDown: PropTypes.number,
/**
* Called immediately after the underlay is shown
*/
nextFocusForward: PropTypes.number,
/**
* Called immediately after the underlay is hidden
*/
nextFocusLeft: PropTypes.number,
/**
* *(Apple TV only)* TV preferred focus (see documentation for the View component).
*
* @platform ios
*/
nextFocusRight: PropTypes.number,
/**
* TV next focus down (see documentation for the View component).
*
* @platform android
*/
nextFocusUp: PropTypes.number,
/**
* TV next focus forward (see documentation for the View component).
*
* @platform android
*/
onHideUnderlay: PropTypes.func,
/**
* TV next focus left (see documentation for the View component).
*
* @platform android
*/
onShowUnderlay: PropTypes.func,
/**
* TV next focus right (see documentation for the View component).
*
* @platform android
*/
style: DeprecatedViewPropTypes.style,
/**
* TV next focus up (see documentation for the View component).
*
* @platform android
*/
testOnly_pressed: PropTypes.bool,
/**
* *(Apple TV only)* Object with properties to control Apple TV parallax effects.
*
* enabled: If true, parallax effects are enabled. Defaults to true.
* shiftDistanceX: Defaults to 2.0.
* shiftDistanceY: Defaults to 2.0.
* tiltAngle: Defaults to 0.05.
* magnification: Defaults to 1.0.
* pressMagnification: Defaults to 1.0.
* pressDuration: Defaults to 0.3.
* pressDelay: Defaults to 0.0.
*
* @platform ios
*/
tvParallaxProperties: PropTypes.object,
/**
* Handy for snapshot tests.
*/
underlayColor: DeprecatedColorPropType
},
mixins: [TimerMixin, Touchable.Mixin],
mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur],
getDefaultProps: () => DEFAULT_PROPS,
// Performance optimization to avoid constantly re-generating these objects.
_computeSyntheticState: function(props) {
return {
activeProps: {
style: {
opacity: props.activeOpacity
}
},
activeUnderlayProps: {
style: {
backgroundColor: props.underlayColor
}
},
underlayStyle: [INACTIVE_UNDERLAY_PROPS.style, props.style]
};
},
getInitialState: function() {
this._isMounted = false;
return {
...this.touchableGetInitialState(),
...this._computeSyntheticState(this.props)
};
if (this.props.testOnly_pressed) {
return {
...this.touchableGetInitialState(),
extraChildStyle: {
opacity: this.props.activeOpacity
},
extraUnderlayStyle: {
backgroundColor: this.props.underlayColor
}
};
} else {
return {
...this.touchableGetInitialState(),
extraChildStyle: null,
extraUnderlayStyle: null
};
}
},
componentDidMount: function() {
this._isMounted = true;
ensurePositiveDelayProps(this.props);
ensureComponentIsNative(this._childRef);
},
componentWillUnmount: function() {
this._isMounted = false;
clearTimeout(this._hideTimeout);
},
componentDidUpdate: function() {
ensureComponentIsNative(this._childRef);
},
componentWillReceiveProps: function(nextProps) {
UNSAFE_componentWillReceiveProps: function(nextProps) {
ensurePositiveDelayProps(nextProps);
if (
nextProps.activeOpacity !== this.props.activeOpacity ||
nextProps.underlayColor !== this.props.underlayColor ||
nextProps.style !== this.props.style
) {
this.setState(this._computeSyntheticState(nextProps));
}
},
/*
viewConfig: {
uiViewClassName: 'RCTView',
validAttributes: ReactNativeViewAttributes.RCTView,
},
*/
/**
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
* defined on your component.
*/
touchableHandleActivePressIn: function(e: Event) {
this.clearTimeout(this._hideTimeout);
touchableHandleActivePressIn: function(e: PressEvent) {
clearTimeout(this._hideTimeout);
this._hideTimeout = null;
this._showUnderlay();
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: Event) {
touchableHandleActivePressOut: function(e: PressEvent) {
if (!this._hideTimeout) {
this._hideUnderlay();
}
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandlePress: function(e: Event) {
this.clearTimeout(this._hideTimeout);
touchableHandleFocus: function(e: Event) {
this.props.onFocus && this.props.onFocus(e);
},
touchableHandleBlur: function(e: Event) {
this.props.onBlur && this.props.onBlur(e);
},
touchableHandlePress: function(e: PressEvent) {
clearTimeout(this._hideTimeout);
this._showUnderlay();
this._hideTimeout = this.setTimeout(this._hideUnderlay, this.props.delayPressOut || 100);
this._hideTimeout = setTimeout(this._hideUnderlay, this.props.delayPressOut);
this.props.onPress && this.props.onPress(e);
},
touchableHandleLongPress: function(e: Event) {
touchableHandleLongPress: function(e: PressEvent) {
this.props.onLongPress && this.props.onLongPress(e);
},
@@ -193,20 +337,27 @@ const TouchableHighlight = createReactClass({
if (!this._isMounted || !this._hasPressHandler()) {
return;
}
this._underlayRef.setNativeProps(this.state.activeUnderlayProps);
this._childRef.setNativeProps(this.state.activeProps);
this.setState({
extraChildStyle: {
opacity: this.props.activeOpacity
},
extraUnderlayStyle: {
backgroundColor: this.props.underlayColor
}
});
this.props.onShowUnderlay && this.props.onShowUnderlay();
},
_hideUnderlay: function() {
this.clearTimeout(this._hideTimeout);
clearTimeout(this._hideTimeout);
this._hideTimeout = null;
if (this._hasPressHandler() && this._underlayRef) {
this._childRef.setNativeProps(INACTIVE_CHILD_PROPS);
this._underlayRef.setNativeProps({
...INACTIVE_UNDERLAY_PROPS,
style: this.state.underlayStyle
if (this.props.testOnly_pressed) {
return;
}
if (this._hasPressHandler()) {
this.setState({
extraChildStyle: null,
extraUnderlayStyle: null
});
this.props.onHideUnderlay && this.props.onHideUnderlay();
}
@@ -221,63 +372,61 @@ const TouchableHighlight = createReactClass({
);
},
_setChildRef(node) {
this._childRef = node;
},
_setUnderlayRef(node) {
this._underlayRef = node;
},
render: function() {
const {
/* eslint-disable */
activeOpacity,
onHideUnderlay,
onShowUnderlay,
underlayColor,
delayLongPress,
delayPressIn,
delayPressOut,
onLongPress,
onPress,
onPressIn,
onPressOut,
pressRetentionOffset,
/* eslint-enable */
...other
} = this.props;
const child = React.Children.only(this.props.children);
return (
<View
{...other}
{...this.props}
accessibilityHint={this.props.accessibilityHint}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole}
accessibilityState={this.props.accessibilityState}
accessible={this.props.accessible !== false}
hitSlop={this.props.hitSlop}
nativeID={this.props.nativeID}
onKeyDown={this.touchableHandleKeyEvent}
//isTVSelectable={true}
//tvParallaxProperties={this.props.tvParallaxProperties}
//hasTVPreferredFocus={this.props.hasTVPreferredFocus}
//nextFocusDown={this.props.nextFocusDown}
//nextFocusForward={this.props.nextFocusForward}
//nextFocusLeft={this.props.nextFocusLeft}
//nextFocusRight={this.props.nextFocusRight}
//nextFocusUp={this.props.nextFocusUp}
//clickable={
// this.props.clickable !== false && this.props.onPress !== undefined
//}
//onClick={this.touchableHandlePress}
onKeyUp={this.touchableHandleKeyEvent}
onLayout={this.props.onLayout}
onResponderGrant={this.touchableHandleResponderGrant}
onResponderMove={this.touchableHandleResponderMove}
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
ref={this._setUnderlayRef}
style={[styles.root, !this.props.disabled && styles.actionable, this.state.underlayStyle]}
style={[
styles.root,
this.props.style,
!this.props.disabled && styles.actionable,
this.state.extraUnderlayStyle
]}
testID={this.props.testID}
>
{React.cloneElement(React.Children.only(this.props.children), {
ref: this._setChildRef
{React.cloneElement(child, {
style: StyleSheet.compose(
child.props.style,
this.state.extraChildStyle
)
})}
{Touchable.renderDebugView({
color: 'green',
hitSlop: this.props.hitSlop
})}
{Touchable.renderDebugView({ color: 'green', hitSlop: this.props.hitSlop })}
</View>
);
}
});
const INACTIVE_CHILD_PROPS = {
style: StyleSheet.create({ x: { opacity: 1.0 } }).x
};
const INACTIVE_UNDERLAY_PROPS = {
style: StyleSheet.create({ x: { backgroundColor: 'transparent' } }).x
};
}): any): React.ComponentType<Props>);
const styles = StyleSheet.create({
root: {
@@ -1,35 +1,46 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
* @format
* @flow
*/
'use strict';
import applyNativeMethods from '../../modules/applyNativeMethods';
import createReactClass from 'create-react-class';
import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps';
import { number } from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import StyleSheet from '../StyleSheet';
import Touchable from '../Touchable';
import TouchableWithoutFeedback from '../TouchableWithoutFeedback';
import TouchableWithoutFeedback, {
type Props as TouchableWithoutFeedbackProps
} from '../TouchableWithoutFeedback';
import View from '../View';
const flattenStyle = StyleSheet.flatten;
type Event = Object;
type PressEvent = Object;
const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
type Props = $ReadOnly<{|
...TouchableWithoutFeedbackProps,
activeOpacity?: ?number,
style?: ?any
|}>;
/**
* A wrapper for making views respond properly to touches.
* On press down, the opacity of the wrapped view is decreased, dimming it.
*
* Opacity is controlled by wrapping the children in a View, which is
* added to the view hiearchy. Be aware that this can affect layout.
* Opacity is controlled by wrapping the children in an Animated.View, which is
* added to the view hiearchy. Be aware that this can affect layout.
*
* Example:
*
@@ -45,46 +56,160 @@ const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
* );
* },
* ```
* ### Example
*
* ```ReactNativeWebPlayer
* import React, { Component } from 'react'
* import {
* AppRegistry,
* StyleSheet,
* TouchableOpacity,
* Text,
* View,
* } from 'react-native'
*
* class App extends Component {
* constructor(props) {
* super(props)
* this.state = { count: 0 }
* }
*
* onPress = () => {
* this.setState({
* count: this.state.count+1
* })
* }
*
* render() {
* return (
* <View style={styles.container}>
* <TouchableOpacity
* style={styles.button}
* onPress={this.onPress}
* >
* <Text> Touch Here </Text>
* </TouchableOpacity>
* <View style={[styles.countContainer]}>
* <Text style={[styles.countText]}>
* { this.state.count !== 0 ? this.state.count: null}
* </Text>
* </View>
* </View>
* )
* }
* }
*
* const styles = StyleSheet.create({
* container: {
* flex: 1,
* justifyContent: 'center',
* paddingHorizontal: 10
* },
* button: {
* alignItems: 'center',
* backgroundColor: '#DDDDDD',
* padding: 10
* },
* countContainer: {
* alignItems: 'center',
* padding: 10
* },
* countText: {
* color: '#FF00FF'
* }
* })
*
* AppRegistry.registerComponent('App', () => App)
* ```
*
*/
/* eslint-disable react/prefer-es6-class */
const TouchableOpacity = createReactClass({
// eslint-disable-next-line react/prefer-es6-class
const TouchableOpacity = ((createReactClass({
displayName: 'TouchableOpacity',
mixins: [Touchable.Mixin],
mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur],
propTypes: {
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete this
* comment and run Flow. */
...TouchableWithoutFeedback.propTypes,
/**
* Determines what the opacity of the wrapped view should be when touch is
* active.
* active. Defaults to 0.2.
*/
activeOpacity: number,
focusedOpacity: number
activeOpacity: PropTypes.number,
/**
* TV preferred focus (see documentation for the View component).
*/
hasTVPreferredFocus: PropTypes.bool,
/**
* TV next focus down (see documentation for the View component).
*
* @platform android
*/
nextFocusDown: PropTypes.number,
/**
* TV next focus forward (see documentation for the View component).
*
* @platform android
*/
nextFocusForward: PropTypes.number,
/**
* TV next focus left (see documentation for the View component).
*
* @platform android
*/
nextFocusLeft: PropTypes.number,
/**
* TV next focus right (see documentation for the View component).
*
* @platform android
*/
nextFocusRight: PropTypes.number,
/**
* TV next focus up (see documentation for the View component).
*
* @platform android
*/
nextFocusUp: PropTypes.number,
/**
* Apple TV parallax effects
*/
tvParallaxProperties: PropTypes.object
},
getDefaultProps: function() {
return {
activeOpacity: 0.2,
focusedOpacity: 0.7
activeOpacity: 0.2
};
},
getInitialState: function() {
return this.touchableGetInitialState();
return {
...this.touchableGetInitialState(),
anim: this._getChildStyleOpacityWithDefault()
};
},
componentDidMount: function() {
ensurePositiveDelayProps(this.props);
},
componentWillReceiveProps: function(nextProps) {
UNSAFE_componentWillReceiveProps: function(nextProps) {
ensurePositiveDelayProps(nextProps);
},
componentDidUpdate: function(prevProps, prevState) {
if (this.props.disabled !== prevProps.disabled) {
this._opacityInactive(250);
}
},
/**
* Animate the touchable to a new opacity.
*/
setOpacityTo: function(value: number, duration: ?number) {
setOpacityTo: function(value: number, duration: number) {
this.setNativeProps({
style: {
opacity: value,
@@ -97,7 +222,7 @@ const TouchableOpacity = createReactClass({
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
* defined on your component.
*/
touchableHandleActivePressIn: function(e: Event) {
touchableHandleActivePressIn: function(e: PressEvent) {
if (e.dispatchConfig.registrationName === 'onResponderGrant') {
this._opacityActive(0);
} else {
@@ -106,16 +231,30 @@ const TouchableOpacity = createReactClass({
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: Event) {
touchableHandleActivePressOut: function(e: PressEvent) {
this._opacityInactive(250);
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandlePress: function(e: Event) {
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) {
this.props.onPress && this.props.onPress(e);
},
touchableHandleLongPress: function(e: Event) {
touchableHandleLongPress: function(e: PressEvent) {
this.props.onLongPress && this.props.onLongPress(e);
},
@@ -147,52 +286,60 @@ const TouchableOpacity = createReactClass({
this.setOpacityTo(this._getChildStyleOpacityWithDefault(), duration);
},
_opacityFocused: function() {
this.setOpacityTo(this.props.focusedOpacity);
},
_getChildStyleOpacityWithDefault: function() {
const childStyle = flattenStyle(this.props.style) || {};
return childStyle.opacity === undefined ? 1 : childStyle.opacity;
return childStyle.opacity == null ? 1 : childStyle.opacity;
},
render: function() {
const {
/* eslint-disable */
activeOpacity,
focusedOpacity,
delayLongPress,
delayPressIn,
delayPressOut,
onLongPress,
onPress,
onPressIn,
onPressOut,
pressRetentionOffset,
/* eslint-enable */
...other
} = this.props;
return (
<View
{...other}
{...this.props}
accessibilityHint={this.props.accessibilityHint}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole}
accessibilityState={this.props.accessibilityState}
accessible={this.props.accessible !== false}
hitSlop={this.props.hitSlop}
nativeID={this.props.nativeID}
onKeyDown={this.touchableHandleKeyEvent}
onKeyUp={this.touchableHandleKeyEvent}
onLayout={this.props.onLayout}
onResponderGrant={this.touchableHandleResponderGrant}
//isTVSelectable={true}
//nextFocusDown={this.props.nextFocusDown}
//nextFocusForward={this.props.nextFocusForward}
//nextFocusLeft={this.props.nextFocusLeft}
//nextFocusRight={this.props.nextFocusRight}
//nextFocusUp={this.props.nextFocusUp}
//hasTVPreferredFocus={this.props.hasTVPreferredFocus}
//tvParallaxProperties={this.props.tvParallaxProperties}
onResponderMove={this.touchableHandleResponderMove}
//clickable={
// this.props.clickable !== false && this.props.onPress !== undefined
//}
//onClick={this.touchableHandlePress}
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
style={[styles.root, !this.props.disabled && styles.actionable, this.props.style]}
style={[
styles.root,
!this.props.disabled && styles.actionable,
this.props.style,
{ opacity: this.state.anim }
]}
testID={this.props.testID}
>
{this.props.children}
{Touchable.renderDebugView({ color: 'blue', hitSlop: this.props.hitSlop })}
{Touchable.renderDebugView({
color: 'cyan',
hitSlop: this.props.hitSlop
})}
</View>
);
}
});
}): any): React.ComponentType<Props>);
const styles = StyleSheet.create({
root: {
@@ -1,28 +1,72 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
import createReactClass from 'create-react-class';
import EdgeInsetsPropType from '../EdgeInsetsPropType';
import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps';
import React, { type Element } from 'react';
import StyleSheet from '../StyleSheet';
import TimerMixin from 'react-timer-mixin';
import Touchable from '../Touchable';
import ViewPropTypes from '../ViewPropTypes';
import warning from 'fbjs/lib/warning';
import { any, bool, func, number, string } from 'prop-types';
'use strict';
type Event = Object;
import createReactClass from 'create-react-class';
import DeprecatedEdgeInsetsPropType from '../EdgeInsetsPropType';
import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps';
import * as React from 'react';
import PropTypes from 'prop-types';
import Touchable from '../Touchable';
import View from '../View';
type BlurEvent = Object;
type FocusEvent = Object;
type PressEvent = Object;
type LayoutEvent = Object;
type EdgeInsetsProp = Object;
const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
const OVERRIDE_PROPS = [
'accessibilityLabel',
'accessibilityHint',
'accessibilityIgnoresInvertColors',
'accessibilityRole',
'accessibilityState',
'hitSlop',
'nativeID',
'onBlur',
'onFocus',
'onLayout',
'testID'
];
export type Props = $ReadOnly<{|
accessible?: ?boolean,
accessibilityLabel?: ?string,
accessibilityHint?: ?string,
accessibilityIgnoresInvertColors?: ?boolean,
accessibilityRole?: ?string,
accessibilityState?: ?Object,
children?: ?React.Node,
delayLongPress?: ?number,
delayPressIn?: ?number,
delayPressOut?: ?number,
disabled?: ?boolean,
hitSlop?: ?EdgeInsetsProp,
nativeID?: ?string,
touchSoundDisabled?: ?boolean,
onBlur?: ?(e: BlurEvent) => void,
onFocus?: ?(e: FocusEvent) => void,
onLayout?: ?(event: LayoutEvent) => mixed,
onLongPress?: ?(event: PressEvent) => mixed,
onPress?: ?(event: PressEvent) => mixed,
onPressIn?: ?(event: PressEvent) => mixed,
onPressOut?: ?(event: PressEvent) => mixed,
pressRetentionOffset?: ?EdgeInsetsProp,
rejectResponderTermination?: ?boolean,
testID?: ?string
|}>;
/**
* Do not use unless you have a very good reason. All elements that
* respond to press should have a visual feedback when touched.
@@ -30,53 +74,76 @@ const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
* TouchableWithoutFeedback supports only one child.
* If you wish to have several child components, wrap them in a View.
*/
/* eslint-disable react/prefer-es6-class, react/prop-types */
const TouchableWithoutFeedback = createReactClass({
// eslint-disable-next-line react/prefer-es6-class
const TouchableWithoutFeedback = ((createReactClass({
displayName: 'TouchableWithoutFeedback',
mixins: [TimerMixin, Touchable.Mixin],
mixins: [Touchable.Mixin],
propTypes: {
accessibilityLabel: string,
accessibilityRole: ViewPropTypes.accessibilityRole,
accessible: bool,
children: any,
accessibilityHint: PropTypes.string,
accessibilityIgnoresInvertColors: PropTypes.bool,
accessibilityLabel: PropTypes.node,
accessibilityRole: PropTypes.string,
accessibilityState: PropTypes.object,
accessible: PropTypes.bool,
/**
* Delay in ms, from onPressIn, before onLongPress is called.
* When `accessible` is true (which is the default) this may be called when
* the OS-specific concept of "focus" occurs. Some platforms may not have
* the concept of focus.
*/
delayLongPress: number,
delayLongPress: PropTypes.number,
/**
* Delay in ms, from the start of the touch, before onPressIn is called.
* When `accessible` is true (which is the default) this may be called when
* the OS-specific concept of "blur" occurs, meaning the element lost focus.
* Some platforms may not have the concept of blur.
*/
delayPressIn: number,
/**
* Delay in ms, from the release of the touch, before onPressOut is called.
*/
delayPressOut: number,
delayPressIn: PropTypes.number,
/**
* If true, disable all interactions for this component.
*/
disabled: bool,
delayPressOut: PropTypes.number,
/**
* This defines how far your touch can start away from the button. This is
* added to `pressRetentionOffset` when moving off of the button.
* Called when the touch is released, but not if cancelled (e.g. by a scroll
* that steals the responder lock).
*/
// $FlowFixMe(>=0.41.0)
hitSlop: EdgeInsetsPropType,
disabled: PropTypes.bool,
/**
* Called as soon as the touchable element is pressed and invoked even before onPress.
* This can be useful when making network requests.
*/
hitSlop: DeprecatedEdgeInsetsPropType,
/**
* Called as soon as the touch is released even before onPress.
*/
nativeID: PropTypes.string,
/**
* Invoked on mount and layout changes with
*
* `{nativeEvent: {layout: {x, y, width, height}}}`
*/
onLayout: func,
onLongPress: func,
onBlur: PropTypes.func,
/**
* Called when the touch is released, but not if cancelled (e.g. by a scroll
* that steals the responder lock).
* If true, doesn't play system sound on touch (Android Only)
**/
onFocus: PropTypes.func,
onLayout: PropTypes.func,
onLongPress: PropTypes.func,
onPress: PropTypes.func,
/**
* Delay in ms, from the start of the touch, before onPressIn is called.
*/
onPress: func,
onPressIn: func,
onPressOut: func,
onPressIn: PropTypes.func,
/**
* Delay in ms, from the release of the touch, before onPressOut is called.
*/
onPressOut: PropTypes.func,
/**
* Delay in ms, from onPressIn, before onLongPress is called.
*/
pressRetentionOffset: DeprecatedEdgeInsetsPropType,
/**
* When the scroll view is disabled, this defines how far your touch may
* move off of the button, before deactivating the button. Once deactivated,
@@ -84,9 +151,16 @@ const TouchableWithoutFeedback = createReactClass({
* reactivated! Move it back and forth several times while the scroll view
* is disabled. Ensure you pass in a constant to reduce memory allocations.
*/
// $FlowFixMe
pressRetentionOffset: EdgeInsetsPropType,
testID: string
testID: PropTypes.string,
/**
* This defines how far your touch can start away from the button. This is
* added to `pressRetentionOffset` when moving off of the button.
* ** NOTE **
* The touch area never extends past the parent view bounds and the Z-index
* of sibling views always takes precedence if a touch hits two overlapping
* views.
*/
touchSoundDisabled: PropTypes.bool
},
getInitialState: function() {
@@ -97,7 +171,7 @@ const TouchableWithoutFeedback = createReactClass({
ensurePositiveDelayProps(this.props);
},
componentWillReceiveProps: function(nextProps: Object) {
UNSAFE_componentWillReceiveProps: function(nextProps: Object) {
ensurePositiveDelayProps(nextProps);
},
@@ -105,23 +179,24 @@ const TouchableWithoutFeedback = createReactClass({
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
* defined on your component.
*/
touchableHandlePress: function(e: Event) {
touchableHandlePress: function(e: PressEvent) {
this.props.onPress && this.props.onPress(e);
},
touchableHandleActivePressIn: function(e: Event) {
touchableHandleActivePressIn: function(e: PressEvent) {
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: Event) {
touchableHandleActivePressOut: function(e: PressEvent) {
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandleLongPress: function(e: Event) {
touchableHandleLongPress: function(e: PressEvent) {
this.props.onLongPress && this.props.onLongPress(e);
},
touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
// $FlowFixMe Invalid prop usage
return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
},
@@ -141,65 +216,41 @@ const TouchableWithoutFeedback = createReactClass({
return this.props.delayPressOut || 0;
},
render: function(): Element<any> {
const {
/* eslint-disable */
delayLongPress,
delayPressIn,
delayPressOut,
onLongPress,
onPress,
onPressIn,
onPressOut,
pressRetentionOffset,
/* eslint-enable */
...other
} = this.props;
render: function(): React.Element<any> {
// Note(avik): remove dynamic typecast once Flow has been upgraded
// $FlowFixMe
// $FlowFixMe(>=0.41.0)
// eslint-disable-next-line react/prop-types
const child = React.Children.only(this.props.children);
let children = child.props.children;
warning(
!child.type || child.type.displayName !== 'Text',
'TouchableWithoutFeedback does not work well with Text children. Wrap children in a View instead. See ' +
((child._owner && child._owner.getName && child._owner.getName()) || '<unknown>')
);
if (
process.env.NODE_ENV !== 'production' &&
Touchable.TOUCH_TARGET_DEBUG &&
child.type &&
child.type.displayName === 'View'
) {
if (Touchable.TOUCH_TARGET_DEBUG && child.type === View) {
children = React.Children.toArray(children);
children.push(Touchable.renderDebugView({ color: 'red', hitSlop: this.props.hitSlop }));
}
const style =
Touchable.TOUCH_TARGET_DEBUG && child.type && child.type.displayName === 'Text'
? [!this.props.disabled && styles.actionable, child.props.style, { color: 'red' }]
: [!this.props.disabled && styles.actionable, child.props.style];
const overrides = {};
for (const prop of OVERRIDE_PROPS) {
if (this.props[prop] !== undefined) {
overrides[prop] = this.props[prop];
}
}
return (React: any).cloneElement(child, {
...other,
...overrides,
accessible: this.props.accessible !== false,
children,
//clickable:
// this.props.clickable !== false && this.props.onPress !== undefined,
//onClick: this.touchableHandlePress,
onKeyDown: this.touchableHandleKeyEvent,
onKeyUp: this.touchableHandleKeyEvent,
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
onResponderGrant: this.touchableHandleResponderGrant,
onResponderMove: this.touchableHandleResponderMove,
onResponderRelease: this.touchableHandleResponderRelease,
onResponderTerminate: this.touchableHandleResponderTerminate,
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
style
children
});
}
});
const styles = StyleSheet.create({
actionable: {
cursor: 'pointer',
touchAction: 'manipulation'
}
});
}): any): React.ComponentType<Props>);
export default TouchableWithoutFeedback;