/** * Copyright 2004-present Facebook. All Rights Reserved. * * @providesModule TouchableHighlight */ 'use strict'; var NativeMethodsMixin = require('NativeMethodsMixin'); var React = require('React'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StyleSheet = require('StyleSheet'); var TimerMixin = require('TimerMixin'); var Touchable = require('Touchable'); var TouchableFeedbackPropType = require('TouchableFeedbackPropType'); var View = require('View'); var cloneWithProps = require('cloneWithProps'); var ensureComponentIsNative = require('ensureComponentIsNative'); var keyOf = require('keyOf'); var merge = require('merge'); var onlyChild = require('onlyChild'); /** * TouchableHighlight - 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 adding a view to the view hierarchy, which can 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. * Example: * * renderButton: function() { * return ( * * * * ); * }, * * More example code in TouchableExample.js, and more in-depth discussion in * Touchable.js. See also TouchableWithoutFeedback.js. */ var DEFAULT_PROPS = { activeOpacity: 0.8, underlayColor: 'black', }; var TouchableHighlight = React.createClass({ propTypes: { ...TouchableFeedbackPropType, /** * Called when the touch is released, but not if cancelled (e.g. by * a scroll that steals the responder lock). */ onPress: React.PropTypes.func.isRequired, /** * Determines what the opacity of the wrapped view should be when touch is * active. */ activeOpacity: React.PropTypes.number, /** * The color of the underlay that will show through when the touch is * active. */ underlayColor: React.PropTypes.string, style: View.stylePropType, }, mixins: [NativeMethodsMixin, TimerMixin, Touchable.Mixin], 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() { return merge( this.touchableGetInitialState(), this.computeSyntheticState(this.props) ); }, componentDidMount: function() { ensureComponentIsNative(this.refs[CHILD_REF]); }, componentDidUpdate: function() { ensureComponentIsNative(this.refs[CHILD_REF]); }, componentWillReceiveProps: function(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: ReactIOSViewAttributes.RKView }, /** * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are * defined on your component. */ touchableHandleActivePressIn: function() { this.clearTimeout(this._hideTimeout); this._hideTimeout = null; this._showUnderlay(); this.props.onPressIn && this.props.onPressIn(); }, touchableHandleActivePressOut: function() { if (!this._hideTimeout) { this._hideUnderlay(); } this.props.onPressOut && this.props.onPressOut(); }, touchableHandlePress: function() { this.clearTimeout(this._hideTimeout); this._showUnderlay(); this._hideTimeout = this.setTimeout(this._hideUnderlay, 100); this.props.onPress && this.props.onPress(); }, touchableHandleLongPress: function() { this.props.onLongPress && this.props.onLongPress(); }, touchableGetPressRectOffset: function() { return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant! }, _showUnderlay: function() { this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps); this.refs[CHILD_REF].setNativeProps(this.state.activeProps); }, _hideUnderlay: function() { this.clearTimeout(this._hideTimeout); this._hideTimeout = null; if (this.refs[UNDERLAY_REF]) { this.refs[CHILD_REF].setNativeProps(INACTIVE_CHILD_PROPS); this.refs[UNDERLAY_REF].setNativeProps(INACTIVE_UNDERLAY_PROPS); } }, render: function() { return ( {cloneWithProps( onlyChild(this.props.children), { ref: CHILD_REF, accessible: true, testID: this.props.testID, } )} ); } }); var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; var CHILD_REF = keyOf({childRef: null}); var UNDERLAY_REF = keyOf({underlayRef: null}); var INACTIVE_CHILD_PROPS = { style: StyleSheet.create({x: {opacity: 1.0}}).x, }; var INACTIVE_UNDERLAY_PROPS = { style: StyleSheet.create({x: {backgroundColor: 'transparent'}}).x, }; module.exports = TouchableHighlight;