/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule TouchableHighlight * @flow * @format */ 'use strict'; const ColorPropType = require('ColorPropType'); const NativeMethodsMixin = require('NativeMethodsMixin'); const PropTypes = require('prop-types'); 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 {Event} from 'TouchableWithoutFeedback'; const DEFAULT_PROPS = { activeOpacity: 0.85, delayPressOut: 100, underlayColor: 'black', }; const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; /** * 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. * * @platform ios */ tvParallaxProperties: PropTypes.object, }, mixins: [NativeMethodsMixin, Touchable.Mixin], getDefaultProps: () => DEFAULT_PROPS, getInitialState: function() { this._isMounted = false; return { ...this.touchableGetInitialState(), extraChildStyle: null, extraUnderlayStyle: null, }; }, componentDidMount: function() { this._isMounted = true; ensurePositiveDelayProps(this.props); }, componentWillUnmount: function() { this._isMounted = false; clearTimeout(this._hideTimeout); }, 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: Event) { clearTimeout(this._hideTimeout); this._hideTimeout = null; this._showUnderlay(); this.props.onPressIn && this.props.onPressIn(e); }, touchableHandleActivePressOut: function(e: Event) { if (!this._hideTimeout) { this._hideUnderlay(); } this.props.onPressOut && this.props.onPressOut(e); }, touchableHandlePress: function(e: Event) { clearTimeout(this._hideTimeout); this._showUnderlay(); this._hideTimeout = setTimeout( this._hideUnderlay, this.props.delayPressOut, ); this.props.onPress && this.props.onPress(e); }, touchableHandleLongPress: function(e: Event) { 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._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, })} ); }, }); module.exports = TouchableHighlight;