diff --git a/src/components/Touchable/Touchable.js b/src/components/Touchable/Touchable.js
index 5f07831d..d9903e1a 100644
--- a/src/components/Touchable/Touchable.js
+++ b/src/components/Touchable/Touchable.js
@@ -340,7 +340,7 @@ var TouchableMixin = {
* Must return true to start the process of `Touchable`.
*/
touchableHandleStartShouldSetResponder: function() {
- return true;
+ return !this.props.disabled;
},
/**
@@ -558,10 +558,10 @@ var TouchableMixin = {
* @sideeffects
* @private
*/
- _remeasureMetricsOnActivation: function() {
+ _remeasureMetricsOnActivation: function(e) {
/* @edit begin */
UIManager.measure(
- this.state.touchable.responderID,
+ e.nativeEvent.target,
this._handleQueryLayout
);
/* @edit end */
@@ -603,18 +603,22 @@ var TouchableMixin = {
* @sideeffects
*/
_receiveSignal: function(signal, e) {
+ var responderID = this.state.touchable.responderID;
var curState = this.state.touchable.touchState;
var nextState = Transitions[curState] && Transitions[curState][signal];
+ if (!responderID && signal === Signals.RESPONDER_RELEASE) {
+ return;
+ }
if (!nextState) {
throw new Error(
'Unrecognized signal `' + signal + '` or state `' + curState +
- '` for Touchable responder `' + this.state.touchable.responderID + '`'
+ '` for Touchable responder `' + responderID + '`'
);
}
if (nextState === States.ERROR) {
throw new Error(
'Touchable cannot transition from `' + curState + '` to `' + signal +
- '` for responder `' + this.state.touchable.responderID + '`'
+ '` for responder `' + responderID + '`'
);
}
if (curState !== nextState) {
@@ -672,7 +676,7 @@ var TouchableMixin = {
}
if (!IsActive[curState] && IsActive[nextState]) {
- this._remeasureMetricsOnActivation();
+ this._remeasureMetricsOnActivation(e);
}
if (IsPressingIn[curState] && signal === Signals.LONG_PRESS_DETECTED) {
diff --git a/src/components/Touchable/TouchableBounce.js b/src/components/Touchable/TouchableBounce.js
new file mode 100644
index 00000000..7aec0b39
--- /dev/null
+++ b/src/components/Touchable/TouchableBounce.js
@@ -0,0 +1,164 @@
+/* eslint-disable */
+/**
+ * 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 TouchableBounce
+ * @flow
+ */
+'use strict';
+
+var Animated = require('../../apis/Animated');
+var EdgeInsetsPropType = require('../../apis/StyleSheet/EdgeInsetsPropType');
+var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
+var React = require('react');
+var StyleSheet = require('../../apis/StyleSheet');
+var Touchable = require('./Touchable');
+
+type Event = Object;
+
+type State = {
+ animationID: ?number;
+ scale: Animated.Value;
+};
+
+var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
+
+/**
+ * Example of using the `TouchableMixin` to play well with other responder
+ * locking views including `ScrollView`. `TouchableMixin` provides touchable
+ * hooks (`this.touchableHandle*`) that we forward events to. In turn,
+ * `TouchableMixin` expects us to implement some abstract methods to handle
+ * interesting interactions such as `handleTouchablePress`.
+ */
+var TouchableBounce = React.createClass({
+ mixins: [Touchable.Mixin, NativeMethodsMixin],
+
+ propTypes: {
+ onPress: React.PropTypes.func,
+ onPressIn: React.PropTypes.func,
+ onPressOut: React.PropTypes.func,
+ // The function passed takes a callback to start the animation which should
+ // be run after this onPress handler is done. You can use this (for example)
+ // to update UI before starting the animation.
+ onPressWithCompletion: React.PropTypes.func,
+ // the function passed is called after the animation is complete
+ onPressAnimationComplete: React.PropTypes.func,
+ /**
+ * When the scroll view is disabled, this defines how far your touch may
+ * move off of the button, before deactivating the button. Once deactivated,
+ * try moving it back and you'll see that the button is once again
+ * reactivated! Move it back and forth several times while the scroll view
+ * is disabled. Ensure you pass in a constant to reduce memory allocations.
+ */
+ pressRetentionOffset: EdgeInsetsPropType,
+ /**
+ * 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.
+ */
+ hitSlop: EdgeInsetsPropType,
+ },
+
+ getInitialState: function(): State {
+ return {
+ ...this.touchableGetInitialState(),
+ scale: new Animated.Value(1),
+ };
+ },
+
+ bounceTo: function(
+ value: number,
+ velocity: number,
+ bounciness: number,
+ callback?: ?Function
+ ) {
+ Animated.spring(this.state.scale, {
+ toValue: value,
+ velocity,
+ bounciness,
+ }).start(callback);
+ },
+
+ /**
+ * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
+ * defined on your component.
+ */
+ touchableHandleActivePressIn: function(e: Event) {
+ this.bounceTo(0.93, 0.1, 0);
+ this.props.onPressIn && this.props.onPressIn(e);
+ },
+
+ touchableHandleActivePressOut: function(e: Event) {
+ this.bounceTo(1, 0.4, 0);
+ this.props.onPressOut && this.props.onPressOut(e);
+ },
+
+ touchableHandlePress: function(e: Event) {
+ var onPressWithCompletion = this.props.onPressWithCompletion;
+ if (onPressWithCompletion) {
+ onPressWithCompletion(() => {
+ this.state.scale.setValue(0.93);
+ this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
+ });
+ return;
+ }
+
+ this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
+ this.props.onPress && this.props.onPress(e);
+ },
+
+ touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
+ return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
+ },
+
+ touchableGetHitSlop: function(): ?Object {
+ return this.props.hitSlop;
+ },
+
+ touchableGetHighlightDelayMS: function(): number {
+ return 0;
+ },
+
+ render: function(): ReactElement {
+ const scaleTransform = [{ scale: this.state.scale }];
+ const propsTransform = this.props.style.transform;
+ const transform = propsTransform && Array.isArray(propsTransform) ? propsTransform.concat(scaleTransform) : scaleTransform;
+
+ return (
+
+ {this.props.children}
+
+ );
+ }
+});
+
+const styles = StyleSheet.create({
+ root: {
+ cursor: 'pointer',
+ userSelect: 'none'
+ }
+});
+
+module.exports = TouchableBounce;
diff --git a/src/components/Touchable/TouchableHighlight.js b/src/components/Touchable/TouchableHighlight.js
new file mode 100644
index 00000000..5f2594c1
--- /dev/null
+++ b/src/components/Touchable/TouchableHighlight.js
@@ -0,0 +1,278 @@
+/* eslint-disable */
+/**
+ * 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
+ * @noflow
+ */
+'use strict';
+
+// Note (avik): add @flow when Flow supports spread properties in propTypes
+
+var ColorPropType = require('../../apis/StyleSheet/ColorPropType');
+var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
+var React = require('react');
+var StyleSheet = require('../../apis/StyleSheet');
+var TimerMixin = require('react-timer-mixin');
+var Touchable = require('./Touchable');
+var TouchableWithoutFeedback = require('./TouchableWithoutFeedback');
+var View = require('../View');
+
+var ensureComponentIsNative = require('./ensureComponentIsNative');
+var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
+var keyOf = require('fbjs/lib/keyOf');
+var merge = require('../../modules/merge');
+
+type Event = Object;
+
+var DEFAULT_PROPS = {
+ activeOpacity: 0.8,
+ underlayColor: 'black',
+};
+
+var 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 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 (
+ *
+ *
+ *
+ * );
+ * },
+ * ```
+ * > **NOTE**: TouchableHighlight supports only one child
+ * >
+ * > If you wish to have several child components, wrap them in a View.
+ */
+
+var TouchableHighlight = React.createClass({
+ propTypes: {
+ ...TouchableWithoutFeedback.propTypes,
+ /**
+ * 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: ColorPropType,
+ style: View.propTypes.style,
+ /**
+ * Called immediately after the underlay is shown
+ */
+ onShowUnderlay: React.PropTypes.func,
+ /**
+ * Called immediately after the underlay is hidden
+ */
+ onHideUnderlay: React.PropTypes.func,
+ },
+
+ 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
+ ]
+ };
+ },
+
+ getInitialState: function() {
+ return merge(this.touchableGetInitialState(), this.computeSyntheticState(this.props))
+ },
+
+ componentDidMount: function() {
+ ensurePositiveDelayProps(this.props);
+ ensureComponentIsNative(this.refs[CHILD_REF]);
+ },
+
+ componentDidUpdate: function() {
+ ensureComponentIsNative(this.refs[CHILD_REF]);
+ },
+
+ 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);
+ 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) {
+ this.clearTimeout(this._hideTimeout);
+ this._showUnderlay();
+ this._hideTimeout = this.setTimeout(this._hideUnderlay,
+ this.props.delayPressOut || 100);
+ 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.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps);
+ this.refs[CHILD_REF].setNativeProps(this.state.activeProps);
+ this.props.onShowUnderlay && this.props.onShowUnderlay();
+ },
+
+ _hideUnderlay: function() {
+ this.clearTimeout(this._hideTimeout);
+ this._hideTimeout = null;
+ if (this._hasPressHandler() && this.refs[UNDERLAY_REF]) {
+ this.refs[CHILD_REF].setNativeProps(INACTIVE_CHILD_PROPS);
+ this.refs[UNDERLAY_REF].setNativeProps({
+ ...INACTIVE_UNDERLAY_PROPS,
+ style: this.state.underlayStyle,
+ });
+ this.props.onHideUnderlay && this.props.onHideUnderlay();
+ }
+ },
+
+ _hasPressHandler: function() {
+ return !!(
+ this.props.onPress ||
+ this.props.onPressIn ||
+ this.props.onPressOut ||
+ this.props.onLongPress
+ );
+ },
+
+ _onKeyEnter(e, callback) {
+ var ENTER = 13
+ if (e.keyCode === ENTER) {
+ callback && callback(e)
+ }
+ },
+
+ render: function() {
+ return (
+ { this._onKeyEnter(e, this.touchableHandleActivePressIn) }}
+ onKeyPress={(e) => { this._onKeyEnter(e, this.touchableHandlePress) }}
+ onKeyUp={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressOut) }}
+ onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
+ onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
+ onResponderGrant={this.touchableHandleResponderGrant}
+ onResponderMove={this.touchableHandleResponderMove}
+ onResponderRelease={this.touchableHandleResponderRelease}
+ onResponderTerminate={this.touchableHandleResponderTerminate}
+ tabIndex='0'
+ testID={this.props.testID}>
+ {React.cloneElement(
+ React.Children.only(this.props.children),
+ {
+ ref: CHILD_REF,
+ }
+ )}
+
+ );
+ }
+});
+
+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: {backgroundColor: null}
+};
+
+var styles = StyleSheet.create({
+ root: {
+ cursor: 'pointer',
+ userSelect: 'none'
+ }
+});
+
+module.exports = TouchableHighlight;
diff --git a/src/components/Touchable/TouchableOpacity.js b/src/components/Touchable/TouchableOpacity.js
new file mode 100644
index 00000000..2f6c6d91
--- /dev/null
+++ b/src/components/Touchable/TouchableOpacity.js
@@ -0,0 +1,200 @@
+/* eslint-disable */
+/**
+ * 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 TouchableOpacity
+ * @noflow
+ */
+'use strict';
+
+// Note (avik): add @flow when Flow supports spread properties in propTypes
+
+var Animated = require('../../apis/Animated');
+var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
+var React = require('react');
+var StyleSheet = require('../../apis/StyleSheet');
+var TimerMixin = require('react-timer-mixin');
+var Touchable = require('./Touchable');
+var TouchableWithoutFeedback = require('./TouchableWithoutFeedback');
+
+var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
+var flattenStyle = require('../../apis/StyleSheet/flattenStyle');
+
+type Event = Object;
+
+var 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, dimming it.
+ * This is done without actually changing the view hierarchy, and in general is
+ * easy to add to an app without weird side-effects.
+ *
+ * Example:
+ *
+ * ```
+ * renderButton: function() {
+ * return (
+ *
+ *
+ *
+ * );
+ * },
+ * ```
+ */
+var TouchableOpacity = React.createClass({
+ mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin],
+
+ propTypes: {
+ ...TouchableWithoutFeedback.propTypes,
+ /**
+ * Determines what the opacity of the wrapped view should be when touch is
+ * active.
+ */
+ activeOpacity: React.PropTypes.number,
+ },
+
+ getDefaultProps: function() {
+ return {
+ activeOpacity: 0.2,
+ };
+ },
+
+ getInitialState: function() {
+ return {
+ ...this.touchableGetInitialState(),
+ anim: new Animated.Value(1),
+ };
+ },
+
+ componentDidMount: function() {
+ ensurePositiveDelayProps(this.props);
+ },
+
+ componentWillReceiveProps: function(nextProps) {
+ ensurePositiveDelayProps(nextProps);
+ },
+
+ setOpacityTo: function(value) {
+ Animated.timing(
+ this.state.anim,
+ {toValue: value, duration: 150}
+ ).start();
+ },
+
+ /**
+ * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
+ * defined on your component.
+ */
+ touchableHandleActivePressIn: function(e: Event) {
+ this.clearTimeout(this._hideTimeout);
+ this._hideTimeout = null;
+ this._opacityActive();
+ this.props.onPressIn && this.props.onPressIn(e);
+ },
+
+ touchableHandleActivePressOut: function(e: Event) {
+ if (!this._hideTimeout) {
+ this._opacityInactive();
+ }
+ this.props.onPressOut && this.props.onPressOut(e);
+ },
+
+ touchableHandlePress: function(e: Event) {
+ this.clearTimeout(this._hideTimeout);
+ this._opacityActive();
+ this._hideTimeout = this.setTimeout(
+ this._opacityInactive,
+ this.props.delayPressOut || 100
+ );
+ 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 || 0;
+ },
+
+ touchableGetLongPressDelayMS: function() {
+ return this.props.delayLongPress === 0 ? 0 :
+ this.props.delayLongPress || 500;
+ },
+
+ touchableGetPressOutDelayMS: function() {
+ return this.props.delayPressOut;
+ },
+
+ _opacityActive: function() {
+ this.setOpacityTo(this.props.activeOpacity);
+ },
+
+ _opacityInactive: function() {
+ this.clearTimeout(this._hideTimeout);
+ this._hideTimeout = null;
+ var childStyle = flattenStyle(this.props.style) || {};
+ this.setOpacityTo(
+ childStyle.opacity === undefined ? 1 : childStyle.opacity
+ );
+ },
+
+ _onKeyEnter(e, callback) {
+ var ENTER = 13
+ if (e.keyCode === ENTER) {
+ callback && callback(e)
+ }
+ },
+
+ render: function() {
+ return (
+ { this._onKeyEnter(e, this.touchableHandleActivePressIn) }}
+ onKeyPress={(e) => { this._onKeyEnter(e, this.touchableHandlePress) }}
+ onKeyUp={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressOut) }}
+ onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
+ onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
+ onResponderGrant={this.touchableHandleResponderGrant}
+ onResponderMove={this.touchableHandleResponderMove}
+ onResponderRelease={this.touchableHandleResponderRelease}
+ onResponderTerminate={this.touchableHandleResponderTerminate}
+ tabIndex='0'
+ >
+ {this.props.children}
+
+ );
+ },
+});
+
+var styles = StyleSheet.create({
+ root: {
+ cursor: 'pointer',
+ userSelect: 'none'
+ }
+});
+
+module.exports = TouchableOpacity;
diff --git a/src/components/Touchable/TouchableWithoutFeedback.js b/src/components/Touchable/TouchableWithoutFeedback.js
new file mode 100644
index 00000000..6bfa4151
--- /dev/null
+++ b/src/components/Touchable/TouchableWithoutFeedback.js
@@ -0,0 +1,166 @@
+/* eslint-disable */
+/**
+ * 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 TouchableWithoutFeedback
+ * @flow
+ */
+'use strict';
+
+var EdgeInsetsPropType = require('../../apis/StyleSheet/EdgeInsetsPropType');
+var React = require('react');
+var TimerMixin = require('react-timer-mixin');
+var Touchable = require('./Touchable');
+var View = require('../View');
+var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
+
+type Event = Object;
+
+var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
+
+/**
+ * Do not use unless you have a very good reason. All the elements that
+ * respond to press should have a visual feedback when touched. This is
+ * one of the primary reason a "web" app doesn't feel "native".
+ *
+ * > **NOTE**: TouchableWithoutFeedback supports only one child
+ * >
+ * > If you wish to have several child components, wrap them in a View.
+ */
+var TouchableWithoutFeedback = React.createClass({
+ mixins: [TimerMixin, Touchable.Mixin],
+
+ propTypes: {
+ accessible: React.PropTypes.bool,
+ accessibilityLabel: View.propTypes.accessibilityLabel,
+ accessibilityRole: View.propTypes.accessibilityRole,
+ /**
+ * If true, disable all interactions for this component.
+ */
+ disabled: React.PropTypes.bool,
+ /**
+ * Called when the touch is released, but not if cancelled (e.g. by a scroll
+ * that steals the responder lock).
+ */
+ onPress: React.PropTypes.func,
+ onPressIn: React.PropTypes.func,
+ onPressOut: React.PropTypes.func,
+ /**
+ * Invoked on mount and layout changes with
+ *
+ * `{nativeEvent: {layout: {x, y, width, height}}}`
+ */
+ onLayout: React.PropTypes.func,
+
+ onLongPress: React.PropTypes.func,
+
+ /**
+ * Delay in ms, from the start of the touch, before onPressIn is called.
+ */
+ delayPressIn: React.PropTypes.number,
+ /**
+ * Delay in ms, from the release of the touch, before onPressOut is called.
+ */
+ delayPressOut: React.PropTypes.number,
+ /**
+ * Delay in ms, from onPressIn, before onLongPress is called.
+ */
+ delayLongPress: React.PropTypes.number,
+ /**
+ * When the scroll view is disabled, this defines how far your touch may
+ * move off of the button, before deactivating the button. Once deactivated,
+ * try moving it back and you'll see that the button is once again
+ * reactivated! Move it back and forth several times while the scroll view
+ * is disabled. Ensure you pass in a constant to reduce memory allocations.
+ */
+ pressRetentionOffset: EdgeInsetsPropType,
+ /**
+ * 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.
+ */
+ hitSlop: EdgeInsetsPropType,
+ },
+
+ getInitialState: function() {
+ return this.touchableGetInitialState();
+ },
+
+ componentDidMount: function() {
+ ensurePositiveDelayProps(this.props);
+ },
+
+ componentWillReceiveProps: function(nextProps: Object) {
+ ensurePositiveDelayProps(nextProps);
+ },
+
+ /**
+ * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
+ * defined on your component.
+ */
+ touchableHandlePress: function(e: Event) {
+ this.props.onPress && this.props.onPress(e);
+ },
+
+ touchableHandleActivePressIn: function(e: Event) {
+ this.props.onPressIn && this.props.onPressIn(e);
+ },
+
+ touchableHandleActivePressOut: function(e: Event) {
+ this.props.onPressOut && this.props.onPressOut(e);
+ },
+
+ touchableHandleLongPress: function(e: Event) {
+ this.props.onLongPress && this.props.onLongPress(e);
+ },
+
+ touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
+ return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
+ },
+
+ touchableGetHitSlop: function(): ?Object {
+ return this.props.hitSlop;
+ },
+
+ touchableGetHighlightDelayMS: function(): number {
+ return this.props.delayPressIn || 0;
+ },
+
+ touchableGetLongPressDelayMS: function(): number {
+ return this.props.delayLongPress === 0 ? 0 :
+ this.props.delayLongPress || 500;
+ },
+
+ touchableGetPressOutDelayMS: function(): number {
+ return this.props.delayPressOut || 0;
+ },
+
+ render: function(): ReactElement {
+ // Note(avik): remove dynamic typecast once Flow has been upgraded
+ return (React: any).cloneElement(React.children.only(this.props.children), {
+ accessible: this.props.accessible !== false,
+ accessibilityLabel: this.props.accessibilityLabel,
+ accessibilityRole: this.props.accessibilityRole,
+ testID: this.props.testID,
+ onLayout: this.props.onLayout,
+ hitSlop: this.props.hitSlop,
+ onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
+ onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
+ onResponderGrant: this.touchableHandleResponderGrant,
+ onResponderMove: this.touchableHandleResponderMove,
+ onResponderRelease: this.touchableHandleResponderRelease,
+ onResponderTerminate: this.touchableHandleResponderTerminate,
+ tabIndex: '0'
+ });
+ }
+});
+
+module.exports = TouchableWithoutFeedback;
diff --git a/src/components/Touchable/__tests__/index-test.js b/src/components/Touchable/__tests__/index-test.js
index 1c4e489f..aac36bfc 100644
--- a/src/components/Touchable/__tests__/index-test.js
+++ b/src/components/Touchable/__tests__/index-test.js
@@ -1,35 +1,5 @@
/* eslint-env mocha */
-import * as utils from '../../../modules/specHelpers'
-import assert from 'assert'
-import React from 'react'
-
-import Touchable from '../'
-
-const children = children
-const requiredProps = { children }
-
suite('components/Touchable', () => {
- test('prop "accessibilityLabel"', () => {
- const accessibilityLabel = 'accessibilityLabel'
- const result = utils.shallowRender()
- assert.equal(result.props.accessibilityLabel, accessibilityLabel)
- })
-
- test('prop "accessibilityRole"', () => {
- const accessibilityRole = 'accessibilityRole'
- const result = utils.shallowRender()
- assert.equal(result.props.accessibilityRole, accessibilityRole)
- })
-
- test('prop "accessible"', () => {
- const accessible = false
- const result = utils.shallowRender()
- assert.equal(result.props.accessible, accessible)
- })
-
- test('prop "children"', () => {
- const result = utils.shallowRender()
- assert.deepEqual(result.props.children, children)
- })
+ test.skip('NO TEST COVERAGE', () => {})
})
diff --git a/src/components/Touchable/ensureComponentIsNative.js b/src/components/Touchable/ensureComponentIsNative.js
new file mode 100644
index 00000000..f8eee4b4
--- /dev/null
+++ b/src/components/Touchable/ensureComponentIsNative.js
@@ -0,0 +1,25 @@
+/* eslint-disable */
+/**
+ * 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 ensureComponentIsNative
+ * @flow
+ */
+'use strict';
+
+var invariant = require('fbjs/lib/invariant');
+
+var ensureComponentIsNative = function(component: any) {
+ invariant(
+ component && typeof component.setNativeProps === 'function',
+ 'Touchable child must either be native or forward setNativeProps to a ' +
+ 'native component'
+ );
+};
+
+module.exports = ensureComponentIsNative;
diff --git a/src/components/Touchable/ensurePositiveDelayProps.js b/src/components/Touchable/ensurePositiveDelayProps.js
new file mode 100644
index 00000000..2be15328
--- /dev/null
+++ b/src/components/Touchable/ensurePositiveDelayProps.js
@@ -0,0 +1,25 @@
+/* eslint-disable */
+/**
+ * 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 ensurePositiveDelayProps
+ * @flow
+ */
+'use strict';
+
+var invariant = require('fbjs/lib/invariant');
+
+var ensurePositiveDelayProps = function(props: any) {
+ invariant(
+ !(props.delayPressIn < 0 || props.delayPressOut < 0 ||
+ props.delayLongPress < 0),
+ 'Touchable components cannot have negative delay properties'
+ );
+};
+
+module.exports = ensurePositiveDelayProps;
diff --git a/src/components/Touchable/index.js b/src/components/Touchable/index.js
deleted file mode 100644
index 8f68fa27..00000000
--- a/src/components/Touchable/index.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import React, { Component, PropTypes } from 'react'
-import StyleSheet from '../../apis/StyleSheet'
-import Tappable from 'react-tappable'
-import View from '../View'
-
-export default class Touchable extends Component {
- constructor(props, context) {
- super(props, context)
- this.state = {
- isActive: false
- }
-
- this._onLongPress = this._onLongPress.bind(this)
- this._onPress = this._onPress.bind(this)
- this._onPressIn = this._onPressIn.bind(this)
- this._onPressOut = this._onPressOut.bind(this)
- }
-
- static propTypes = {
- accessibilityLabel: View.propTypes.accessibilityLabel,
- accessibilityRole: View.propTypes.accessibilityRole,
- accessible: View.propTypes.accessible,
- activeOpacity: PropTypes.number,
- activeUnderlayColor: PropTypes.string,
- children: PropTypes.element,
- delayLongPress: PropTypes.number,
- delayPressIn: PropTypes.number,
- delayPressOut: PropTypes.number,
- onLongPress: PropTypes.func,
- onPress: PropTypes.func,
- onPressIn: PropTypes.func,
- onPressOut: PropTypes.func,
- style: View.propTypes.style
- };
-
- static defaultProps = {
- accessibilityRole: 'button',
- activeOpacity: 0.8,
- activeUnderlayColor: 'black',
- delayLongPress: 500,
- delayPressIn: 0,
- delayPressOut: 100,
- style: {}
- };
-
- _getChildren() {
- const { activeOpacity, children } = this.props
- return React.cloneElement(React.Children.only(children), {
- style: [
- children.props.style,
- this.state.isActive && { opacity: activeOpacity }
- ]
- })
- }
-
- _onKeyEnter(e, callback) {
- var ENTER = 13
- if (e.keyCode === ENTER) {
- callback(e)
- }
- }
-
- _onLongPress(e) {
- if (this.props.onLongPress) this.props.onLongPress(e)
- }
-
- _onPress(e) {
- if (this.props.onPress) this.props.onPress(e)
- }
-
- _onPressIn(e) {
- this.setState({ isActive: true })
- if (this.props.onPressIn) this.props.onPressIn(e)
- }
-
- _onPressOut(e) {
- this.setState({ isActive: false })
- if (this.props.onPressOut) this.props.onPressOut(e)
- }
-
- render() {
- const {
- accessibilityLabel,
- accessibilityRole,
- accessible,
- activeUnderlayColor,
- delayLongPress,
- style
- } = this.props
-
- /**
- * Creates a wrapping element that can receive keyboard focus. The
- * highlight is applied as a background color on this wrapper. The opacity
- * is set on the child element, allowing it to have its own background
- * color.
- */
- return (
- { this._onKeyEnter(e, this._onPressIn) }}
- onKeyPress={this._onPress}
- onKeyUp={(e) => { this._onKeyEnter(e, this._onPressOut) }}
- onMouseDown={this._onPressIn}
- onMouseUp={this._onPressOut}
- onPress={this._onLongPress}
- onTap={this._onPress}
- onTouchEnd={this._onPressOut}
- onTouchStart={this._onPressIn}
- pressDelay={delayLongPress}
- pressMoveThreshold={5}
- style={StyleSheet.flatten([
- styles.initial,
- style,
- activeUnderlayColor && this.state.isActive && { backgroundColor: activeUnderlayColor }
- ])}
- tabIndex='0'
- />
- )
- }
-}
-
-const styles = StyleSheet.create({
- initial: {
- cursor: 'pointer',
- userSelect: undefined
- }
-})