/** * Copyright (c) 2015-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. * * @flow * @format */ 'use strict'; const ColorPropType = require('ColorPropType'); const NativeMethodsMixin = require('NativeMethodsMixin'); const PropTypes = require('prop-types'); const Platform = require('Platform'); const React = require('React'); const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); const StyleSheet = require('StyleSheet'); const Touchable = require('Touchable'); const TouchableWithoutFeedback = require('TouchableWithoutFeedback'); const View = require('View'); const ViewPropTypes = require('ViewPropTypes'); const createReactClass = require('create-react-class'); const ensurePositiveDelayProps = require('ensurePositiveDelayProps'); import type {PressEvent} from 'CoreEventTypes'; import type {Props as TouchableWithoutFeedbackProps} from 'TouchableWithoutFeedback'; import type {ViewStyleProp} from 'StyleSheet'; import type {ColorValue} from 'StyleSheetTypes'; const DEFAULT_PROPS = { activeOpacity: 0.85, delayPressOut: 100, underlayColor: 'black', }; const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; type IOSProps = $ReadOnly<{| hasTVPreferredFocus?: ?boolean, tvParallaxProperties?: ?Object, |}>; type Props = $ReadOnly<{| ...TouchableWithoutFeedbackProps, ...IOSProps, activeOpacity?: ?number, underlayColor?: ?ColorValue, style?: ?ViewStyleProp, onShowUnderlay?: ?Function, onHideUnderlay?: ?Function, 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 * the underlay color to show through, darkening or tinting the view. * * The underlay comes from wrapping the child in a new View, which can affect * layout, and sometimes cause unwanted visual artifacts if not used correctly, * for example if the backgroundColor of the wrapped view isn't explicitly set * to an opaque color. * * TouchableHighlight must have one child (not zero or more than one). * If you wish to have several child components, wrap them in a View. * * Example: * * ``` * renderButton: function() { * return ( * * * * ); * }, * ``` * * * ### 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 ( * * * Touch Here * * * * { this.state.count !== 0 ? this.state.count: null} * * * * ) * } * } * * 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) * ``` * */ const TouchableHighlight = ((createReactClass({ displayName: 'TouchableHighlight', propTypes: { ...TouchableWithoutFeedback.propTypes, /** * Determines what the opacity of the wrapped view should be when touch is * active. */ activeOpacity: PropTypes.number, /** * The color of the underlay that will show through when the touch is * active. */ underlayColor: ColorPropType, /** * Style to apply to the container/underlay. Most commonly used to make sure * rounded corners match the wrapped component. */ style: ViewPropTypes.style, /** * Called immediately after the underlay is shown */ onShowUnderlay: PropTypes.func, /** * Called immediately after the underlay is hidden */ onHideUnderlay: PropTypes.func, /** * *(Apple TV only)* TV preferred focus (see documentation for the View component). * * @platform ios */ hasTVPreferredFocus: 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. */ testOnly_pressed: PropTypes.bool, }, mixins: [NativeMethodsMixin, Touchable.Mixin], getDefaultProps: () => DEFAULT_PROPS, getInitialState: function() { this._isMounted = false; 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); }, componentWillUnmount: function() { this._isMounted = false; clearTimeout(this._hideTimeout); }, UNSAFE_componentWillReceiveProps: function(nextProps) { ensurePositiveDelayProps(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: PressEvent) { clearTimeout(this._hideTimeout); this._hideTimeout = null; this._showUnderlay(); this.props.onPressIn && this.props.onPressIn(e); }, touchableHandleActivePressOut: function(e: PressEvent) { if (!this._hideTimeout) { this._hideUnderlay(); } this.props.onPressOut && this.props.onPressOut(e); }, touchableHandlePress: function(e: PressEvent) { clearTimeout(this._hideTimeout); if (!Platform.isTVOS) { this._showUnderlay(); this._hideTimeout = setTimeout( this._hideUnderlay, this.props.delayPressOut, ); } this.props.onPress && this.props.onPress(e); }, touchableHandleLongPress: function(e: PressEvent) { this.props.onLongPress && this.props.onLongPress(e); }, touchableGetPressRectOffset: function() { return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET; }, touchableGetHitSlop: function() { return this.props.hitSlop; }, touchableGetHighlightDelayMS: function() { return this.props.delayPressIn; }, touchableGetLongPressDelayMS: function() { return this.props.delayLongPress; }, touchableGetPressOutDelayMS: function() { return this.props.delayPressOut; }, _showUnderlay: function() { if (!this._isMounted || !this._hasPressHandler()) { return; } this.setState({ extraChildStyle: { opacity: this.props.activeOpacity, }, extraUnderlayStyle: { backgroundColor: this.props.underlayColor, }, }); this.props.onShowUnderlay && this.props.onShowUnderlay(); }, _hideUnderlay: function() { clearTimeout(this._hideTimeout); this._hideTimeout = null; if (this.props.testOnly_pressed) { return; } if (this._hasPressHandler()) { this.setState({ extraChildStyle: null, extraUnderlayStyle: null, }); this.props.onHideUnderlay && this.props.onHideUnderlay(); } }, _hasPressHandler: function() { return !!( this.props.onPress || this.props.onPressIn || this.props.onPressOut || this.props.onLongPress ); }, render: function() { const child = React.Children.only(this.props.children); return ( {React.cloneElement(child, { style: StyleSheet.compose( child.props.style, this.state.extraChildStyle, ), })} {Touchable.renderDebugView({ color: 'green', hitSlop: this.props.hitSlop, })} ); }, }): any): React.ComponentType); module.exports = TouchableHighlight;