[fix] Touchable: adapt RN touchables for Web

This commit is contained in:
Nicolas Gallagher
2016-03-15 14:18:08 -07:00
parent 190966f411
commit 3c4d7655db
9 changed files with 869 additions and 168 deletions

View File

@@ -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) {

View File

@@ -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 (
<Animated.View
accessible={true}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole || 'button'}
testID={this.props.testID}
hitSlop={this.props.hitSlop}
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
onResponderGrant={this.touchableHandleResponderGrant}
onResponderMove={this.touchableHandleResponderMove}
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
style={[styles.root, this.props.style, { transform }]}
tabIndex='0'
>
{this.props.children}
</Animated.View>
);
}
});
const styles = StyleSheet.create({
root: {
cursor: 'pointer',
userSelect: 'none'
}
});
module.exports = TouchableBounce;

View File

@@ -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 (
* <TouchableHighlight onPress={this._onPressButton}>
* <Image
* style={styles.button}
* source={require('image!myButton')}
* />
* </TouchableHighlight>
* );
* },
* ```
* > **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 (
<View
accessible={true}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole || this.props.accessibilityTraits || 'button'}
ref={UNDERLAY_REF}
style={[styles.root, this.state.underlayStyle, this.props.style]}
onLayout={this.props.onLayout}
hitSlop={this.props.hitSlop}
onKeyDown={(e) => { 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,
}
)}
</View>
);
}
});
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;

View File

@@ -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 (
* <TouchableOpacity onPress={this._onPressButton}>
* <Image
* style={styles.button}
* source={require('image!myButton')}
* />
* </TouchableOpacity>
* );
* },
* ```
*/
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 (
<Animated.View
accessible={true}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole || 'button'}
style={[styles.root, this.props.style, {opacity: this.state.anim}]}
testID={this.props.testID}
onLayout={this.props.onLayout}
hitSlop={this.props.hitSlop}
onKeyDown={(e) => { 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}
</Animated.View>
);
},
});
var styles = StyleSheet.create({
root: {
cursor: 'pointer',
userSelect: 'none'
}
});
module.exports = TouchableOpacity;

View File

@@ -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;

View File

@@ -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 = <span style={{}}>children</span>
const requiredProps = { children }
suite('components/Touchable', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<Touchable {...requiredProps} accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'accessibilityRole'
const result = utils.shallowRender(<Touchable {...requiredProps} accessibilityRole={accessibilityRole} />)
assert.equal(result.props.accessibilityRole, accessibilityRole)
})
test('prop "accessible"', () => {
const accessible = false
const result = utils.shallowRender(<Touchable {...requiredProps} accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test('prop "children"', () => {
const result = utils.shallowRender(<Touchable {...requiredProps} />)
assert.deepEqual(result.props.children, <span style={[{}, false]}>children</span>)
})
test.skip('NO TEST COVERAGE', () => {})
})

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 (
<Tappable
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityRole}
accessible={accessible}
children={this._getChildren()}
component={View}
onKeyDown={(e) => { 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
}
})