diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js
index 7aa2be01c68be3..66a919a3cdd29b 100644
--- a/Examples/UIExplorer/TouchableExample.js
+++ b/Examples/UIExplorer/TouchableExample.js
@@ -12,6 +12,7 @@ var {
StyleSheet,
Text,
TouchableHighlight,
+ TouchableOpacity,
View,
} = React;
@@ -57,6 +58,13 @@ exports.examples = [
render: function() {
return ;
},
+}, {
+ title: 'Touchable feedback events',
+ description: ' components accept onPress, onPressIn, ' +
+ 'onPressOut, and onLongPress as props.',
+ render: function() {
+ return ;
+ },
}];
var TextOnPressBox = React.createClass({
@@ -95,11 +103,46 @@ var TextOnPressBox = React.createClass({
}
});
+var TouchableFeedbackEvents = React.createClass({
+ getInitialState: function() {
+ return {
+ eventLog: [],
+ };
+ },
+ render: function() {
+ return (
+
+
+ this._appendEvent('press')}
+ onPressIn={() => this._appendEvent('pressIn')}
+ onPressOut={() => this._appendEvent('pressOut')}
+ onLongPress={() => this._appendEvent('longPress')}>
+
+ Press Me
+
+
+
+
+ {this.state.eventLog.map((e, ii) => {e})}
+
+
+ );
+ },
+ _appendEvent: function(eventName) {
+ var limit = 6;
+ var eventLog = this.state.eventLog.slice(0, limit - 1);
+ eventLog.unshift(eventName);
+ this.setState({eventLog});
+ },
+});
+
var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'};
var styles = StyleSheet.create({
row: {
- alignItems: 'center',
+ justifyContent: 'center',
flexDirection: 'row',
},
icon: {
@@ -113,6 +156,9 @@ var styles = StyleSheet.create({
text: {
fontSize: 16,
},
+ button: {
+ color: '#007AFF',
+ },
wrapper: {
borderRadius: 8,
},
@@ -127,6 +173,14 @@ var styles = StyleSheet.create({
borderColor: '#f0f0f0',
backgroundColor: '#f9f9f9',
},
+ eventLogBox: {
+ padding: 10,
+ margin: 10,
+ height: 120,
+ borderWidth: 1 / PixelRatio.get(),
+ borderColor: '#f0f0f0',
+ backgroundColor: '#f9f9f9',
+ },
textBlock: {
fontWeight: 'bold',
color: 'blue',
diff --git a/Libraries/Components/TextInput/TextInput.ios.js b/Libraries/Components/TextInput/TextInput.ios.js
index 2bd194d17ce310..fef9c16f01b80a 100644
--- a/Libraries/Components/TextInput/TextInput.ios.js
+++ b/Libraries/Components/TextInput/TextInput.ios.js
@@ -68,6 +68,15 @@ var autoCapitalizeMode = {
characters: nativeConstants.AllCharacters
};
+var clearButtonModeConstants = NativeModulesDeprecated.RKUIManager.UITextField.clearButtonMode;
+
+var clearButtonModeTypes = {
+ never: clearButtonModeConstants.Never,
+ whileEditing: clearButtonModeConstants.WhileEditing,
+ unlessEditing: clearButtonModeConstants.UnlessEditing,
+ always: clearButtonModeConstants.Always,
+};
+
var keyboardType = {
default: 'default',
numeric: 'numeric',
@@ -90,6 +99,7 @@ var RKTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
var RKTextFieldAttributes = merge(RKTextViewAttributes, {
caretHidden: true,
enabled: true,
+ clearButtonMode: true,
});
var onlyMultiline = {
@@ -105,6 +115,7 @@ var notMultiline = {
var TextInput = React.createClass({
statics: {
autoCapitalizeMode: autoCapitalizeMode,
+ clearButtonModeTypes: clearButtonModeTypes,
keyboardType: keyboardType,
},
@@ -188,6 +199,10 @@ var TextInput = React.createClass({
* and/or laggy typing, depending on how you process onChange events.
*/
controlled: PropTypes.bool,
+ /**
+ * When the clear button should appear on the right side of the text view
+ */
+ clearButtonMode: PropTypes.oneOf(getObjectValues(clearButtonModeTypes)),
style: Text.stylePropType,
},
@@ -316,6 +331,7 @@ var TextInput = React.createClass({
text={this.state.bufferedValue}
autoCapitalize={this.props.autoCapitalize}
autoCorrect={this.props.autoCorrect}
+ clearButtonMode={this.props.clearButtonMode}
/>;
} else {
for (var propKey in notMultiline) {
diff --git a/Libraries/Components/Touchable/TouchableFeedbackPropType.js b/Libraries/Components/Touchable/TouchableFeedbackPropType.js
new file mode 100644
index 00000000000000..336b091c629fdb
--- /dev/null
+++ b/Libraries/Components/Touchable/TouchableFeedbackPropType.js
@@ -0,0 +1,22 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule TouchableFeedbackPropType
+ * @flow
+ */
+'use strict';
+
+var { PropTypes } = require('React');
+
+var TouchableFeedbackPropType = {
+ /**
+ * Called when the touch is released, but not if cancelled (e.g. by a scroll
+ * that steals the responder lock).
+ */
+ onPress: PropTypes.func,
+ onPressIn: PropTypes.func,
+ onPressOut: PropTypes.func,
+ onLongPress: PropTypes.func,
+};
+
+module.exports = TouchableFeedbackPropType;
diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js
index da721cff8a6d3c..9381f52d628178 100644
--- a/Libraries/Components/Touchable/TouchableHighlight.js
+++ b/Libraries/Components/Touchable/TouchableHighlight.js
@@ -11,6 +11,7 @@ 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');
@@ -50,6 +51,7 @@ var DEFAULT_PROPS = {
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).
@@ -127,12 +129,14 @@ var TouchableHighlight = React.createClass({
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() {
@@ -142,6 +146,10 @@ var TouchableHighlight = React.createClass({
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!
},
diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js
index a1bd8f4e359eee..cb68d6df37cfd2 100644
--- a/Libraries/Components/Touchable/TouchableOpacity.js
+++ b/Libraries/Components/Touchable/TouchableOpacity.js
@@ -9,6 +9,7 @@ var NativeMethodsMixin = require('NativeMethodsMixin');
var POPAnimationMixin = require('POPAnimationMixin');
var React = require('React');
var Touchable = require('Touchable');
+var TouchableFeedbackPropType = require('TouchableFeedbackPropType');
var cloneWithProps = require('cloneWithProps');
var ensureComponentIsNative = require('ensureComponentIsNative');
@@ -41,11 +42,7 @@ var TouchableOpacity = React.createClass({
mixins: [Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
propTypes: {
- /**
- * Called when the touch is released, but not if cancelled (e.g. by
- * a scroll that steals the responder lock).
- */
- onPress: React.PropTypes.func,
+ ...TouchableFeedbackPropType,
/**
* Determines what the opacity of the wrapped view should be when touch is
* active.
@@ -97,10 +94,12 @@ var TouchableOpacity = React.createClass({
this.refs[CHILD_REF].setNativeProps({
opacity: this.props.activeOpacity
});
+ this.props.onPressIn && this.props.onPressIn();
},
touchableHandleActivePressOut: function() {
this.setOpacityTo(1.0);
+ this.props.onPressOut && this.props.onPressOut();
},
touchableHandlePress: function() {
@@ -108,6 +107,10 @@ var TouchableOpacity = React.createClass({
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!
},
diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
index 66a82d59c75cbb..74fbf43a1e1e24 100644
--- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js
+++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
@@ -7,7 +7,7 @@
var React = require('React');
var Touchable = require('Touchable');
-var View = require('View');
+var TouchableFeedbackPropType = require('TouchableFeedbackPropType');
var copyProperties = require('copyProperties');
var onlyChild = require('onlyChild');
@@ -29,12 +29,7 @@ var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
var TouchableWithoutFeedback = React.createClass({
mixins: [Touchable.Mixin],
- propTypes: {
- onPress: React.PropTypes.func,
- onPressIn: React.PropTypes.func,
- onPressOut: React.PropTypes.func,
- onLongPress: React.PropTypes.func,
- },
+ propTypes: TouchableFeedbackPropType,
getInitialState: function() {
return this.touchableGetInitialState();
diff --git a/Libraries/ReactIOS/ReactIOS.js b/Libraries/ReactIOS/ReactIOS.js
index f0a1ed782839ba..0022e09bd25e64 100644
--- a/Libraries/ReactIOS/ReactIOS.js
+++ b/Libraries/ReactIOS/ReactIOS.js
@@ -6,6 +6,7 @@
"use strict";
+var ReactChildren = require('ReactChildren');
var ReactComponent = require('ReactComponent');
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactContext = require('ReactContext');
@@ -20,6 +21,7 @@ var ReactPropTypes = require('ReactPropTypes');
var deprecated = require('deprecated');
var invariant = require('invariant');
+var onlyChild = require('onlyChild');
ReactIOSDefaultInjection.inject();
@@ -73,6 +75,12 @@ var render = function(component, mountInto) {
var ReactIOS = {
hasReactIOSInitialized: false,
+ Children: {
+ map: ReactChildren.map,
+ forEach: ReactChildren.forEach,
+ count: ReactChildren.count,
+ only: onlyChild
+ },
PropTypes: ReactPropTypes,
createClass: ReactCompositeComponent.createClass,
createElement: createElement,
diff --git a/ReactKit/Base/RCTConvert.m b/ReactKit/Base/RCTConvert.m
index 752d03ced0bde9..ab79cd8617742b 100644
--- a/ReactKit/Base/RCTConvert.m
+++ b/ReactKit/Base/RCTConvert.m
@@ -446,6 +446,9 @@ + (CAKeyframeAnimation *)GIF:(id)json
}
imageSource = CGImageSourceCreateWithData((CFDataRef)data, NULL);
+ } else {
+ RCTLogMustFix(@"Expected NSString or NSData for GIF, received %@: %@", [json class], json);
+ return nil;
}
if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) {
diff --git a/ReactKit/Base/RCTTouchHandler.m b/ReactKit/Base/RCTTouchHandler.m
index c56f996cc9b515..a23cd70c7deaac 100644
--- a/ReactKit/Base/RCTTouchHandler.m
+++ b/ReactKit/Base/RCTTouchHandler.m
@@ -77,9 +77,10 @@ - (void)_recordNewTouches:(NSSet *)touches
}
targetView = targetView.superview;
}
-
- RCTAssert(targetView.reactTag && targetView.userInteractionEnabled,
- @"No react view found for touch - something went wrong.");
+
+ if (!targetView.reactTag || !targetView.userInteractionEnabled) {
+ return;
+ }
// Get new, unique touch id
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
@@ -113,7 +114,10 @@ - (void)_recordRemovedTouches:(NSSet *)touches
{
for (UITouch *touch in touches) {
NSUInteger index = [_nativeTouches indexOfObject:touch];
- RCTAssert(index != NSNotFound, @"Touch is already removed. This is a critical bug.");
+ if(index == NSNotFound) {
+ continue;
+ }
+
[_touchViews removeObjectAtIndex:index];
[_nativeTouches removeObjectAtIndex:index];
[_reactTouches removeObjectAtIndex:index];
@@ -159,10 +163,17 @@ - (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventNa
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
for (UITouch *touch in touches) {
NSInteger index = [_nativeTouches indexOfObject:touch];
- RCTAssert(index != NSNotFound, @"Touch not found. This is a critical bug.");
+ if (index == NSNotFound) {
+ continue;
+ }
+
[self _updateReactTouchAtIndex:index];
[changedIndexes addObject:@(index)];
}
+
+ if (changedIndexes.count == 0) {
+ return;
+ }
// Deep copy the touches because they will be accessed from another thread
// TODO: would it be safer to do this in the bridge or executor, rather than trusting caller?