diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js
index 05ab80187..e7eb7f2b4 100644
--- a/Examples/Movies/SearchScreen.js
+++ b/Examples/Movies/SearchScreen.js
@@ -281,7 +281,7 @@ var SearchScreen = React.createClass({
renderRow={this.renderRow}
onEndReached={this.onEndReached}
automaticallyAdjustContentInsets={false}
- keyboardDismissMode="onDrag"
+ keyboardDismissMode="on-drag"
keyboardShouldPersistTaps={true}
showsVerticalScrollIndicator={false}
/>;
diff --git a/Examples/UIExplorer/ProgressViewIOSExample.js b/Examples/UIExplorer/ProgressViewIOSExample.js
new file mode 100644
index 000000000..f0a17a7c6
--- /dev/null
+++ b/Examples/UIExplorer/ProgressViewIOSExample.js
@@ -0,0 +1,83 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+ ProgressViewIOS,
+ StyleSheet,
+ View,
+} = React;
+var TimerMixin = require('react-timer-mixin');
+
+var ProgressViewExample = React.createClass({
+ mixins: [TimerMixin],
+
+ getInitialState() {
+ return {
+ progress: 0,
+ };
+ },
+
+ componentDidMount() {
+ this.updateProgress();
+ },
+
+ updateProgress() {
+ var progress = this.state.progress + 0.01;
+ this.setState({ progress });
+ this.requestAnimationFrame(() => this.updateProgress());
+ },
+
+ getProgress(offset) {
+ var progress = this.state.progress + offset;
+ return Math.sin(progress % Math.PI) % 1;
+ },
+
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ },
+});
+
+exports.framework = 'React';
+exports.title = 'ProgressViewIOS';
+exports.description = 'ProgressViewIOS';
+exports.examples = [{
+ title: 'ProgressViewIOS',
+ render() {
+ return (
+
+ );
+ }
+}];
+
+var styles = StyleSheet.create({
+ container: {
+ marginTop: -20,
+ backgroundColor: 'transparent',
+ },
+ progressView: {
+ marginTop: 20,
+ }
+});
diff --git a/Examples/UIExplorer/StatusBarIOSExample.js b/Examples/UIExplorer/StatusBarIOSExample.js
index 545136c49..ab649197e 100644
--- a/Examples/UIExplorer/StatusBarIOSExample.js
+++ b/Examples/UIExplorer/StatusBarIOSExample.js
@@ -32,11 +32,11 @@ exports.examples = [{
render() {
return (
- {Object.keys(StatusBarIOS.Style).map((key) =>
+ {['default', 'light-content'].map((style) =>
StatusBarIOS.setStyle(StatusBarIOS.Style[key])}>
+ onPress={() => StatusBarIOS.setStyle(style)}>
- setStyle(StatusBarIOS.Style.{key})
+ setStyle('{style}')
)}
@@ -48,11 +48,11 @@ exports.examples = [{
render() {
return (
- {Object.keys(StatusBarIOS.Style).map((key) =>
+ {['default', 'light-content'].map((style) =>
StatusBarIOS.setStyle(StatusBarIOS.Style[key], true)}>
+ onPress={() => StatusBarIOS.setStyle(style, true)}>
- setStyle(StatusBarIOS.Style.{key}, true)
+ setStyle('{style}', true)
)}
@@ -64,18 +64,18 @@ exports.examples = [{
render() {
return (
- {Object.keys(StatusBarIOS.Animation).map((key) =>
+ {['none', 'fade', 'slide'].map((animation) =>
StatusBarIOS.setHidden(true, StatusBarIOS.Animation[key])}>
+ onPress={() => StatusBarIOS.setHidden(true, animation)}>
- setHidden(true, StatusBarIOS.Animation.{key})
+ setHidden(true, '{animation}')
StatusBarIOS.setHidden(false, StatusBarIOS.Animation[key])}>
+ onPress={() => StatusBarIOS.setHidden(false, animation)}>
- setHidden(false, StatusBarIOS.Animation.{key})
+ setHidden(false, '{animation}')
diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js
index 45a679b31..acbba3629 100644
--- a/Examples/UIExplorer/TouchableExample.js
+++ b/Examples/UIExplorer/TouchableExample.js
@@ -75,6 +75,14 @@ exports.examples = [
render: function(): ReactElement {
return ;
},
+}, {
+ title: 'Touchable delay for events',
+ description: ' components also accept delayPressIn, ' +
+ 'delayPressOut, and delayLongPress as props. These props impact the ' +
+ 'timing of feedback events.',
+ render: function(): ReactElement {
+ return ;
+ },
}];
var TextOnPressBox = React.createClass({
@@ -148,6 +156,44 @@ var TouchableFeedbackEvents = React.createClass({
},
});
+var TouchableDelayEvents = React.createClass({
+ getInitialState: function() {
+ return {
+ eventLog: [],
+ };
+ },
+ render: function() {
+ return (
+
+
+ this._appendEvent('press')}
+ delayPressIn={400}
+ onPressIn={() => this._appendEvent('pressIn - 400ms delay')}
+ delayPressOut={1000}
+ onPressOut={() => this._appendEvent('pressOut - 1000ms delay')}
+ delayLongPress={800}
+ onLongPress={() => this._appendEvent('longPress - 800ms delay')}>
+
+ 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({
diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m
index d72262e78..9d3adb2ee 100644
--- a/Examples/UIExplorer/UIExplorer/AppDelegate.m
+++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m
@@ -50,6 +50,10 @@
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
+#if RUNNING_ON_CI
+ jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
+#endif
+
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"UIExplorerApp"
launchOptions:launchOptions];
diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist
index 245054621..349bd9a28 100644
--- a/Examples/UIExplorer/UIExplorer/Info.plist
+++ b/Examples/UIExplorer/UIExplorer/Info.plist
@@ -7,7 +7,7 @@
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
- com.facebook.$(PRODUCT_NAME:rfc1034identifier)
+ com.facebook.internal.uiexplorer.local
CFBundleInfoDictionaryVersion
6.0
CFBundleName
@@ -22,6 +22,8 @@
1
LSRequiresIPhoneOS
+ NSLocationWhenInUseUsageDescription
+ You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*!
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
@@ -34,8 +36,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- NSLocationWhenInUseUsageDescription
- You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*!
UIViewControllerBasedStatusBarAppearance
diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js
index a030220ca..cd73e6d06 100644
--- a/Examples/UIExplorer/UIExplorerList.js
+++ b/Examples/UIExplorer/UIExplorerList.js
@@ -45,6 +45,7 @@ var COMPONENTS = [
require('./NavigatorIOSColorsExample'),
require('./NavigatorIOSExample'),
require('./PickerIOSExample'),
+ require('./ProgressViewIOSExample'),
require('./ScrollViewExample'),
require('./SegmentedControlIOSExample'),
require('./SliderIOSExample'),
@@ -156,7 +157,7 @@ class UIExplorerList extends React.Component {
renderSectionHeader={this._renderSectionHeader}
keyboardShouldPersistTaps={true}
automaticallyAdjustContentInsets={false}
- keyboardDismissMode="onDrag"
+ keyboardDismissMode="on-drag"
/>
);
diff --git a/IntegrationTests/AsyncStorageTest.js b/IntegrationTests/AsyncStorageTest.js
index 6d13bb6e9..911887d3e 100644
--- a/IntegrationTests/AsyncStorageTest.js
+++ b/IntegrationTests/AsyncStorageTest.js
@@ -16,12 +16,19 @@ var {
View,
} = React;
+var deepDiffer = require('deepDiffer');
+
var DEBUG = false;
var KEY_1 = 'key_1';
var VAL_1 = 'val_1';
var KEY_2 = 'key_2';
var VAL_2 = 'val_2';
+var KEY_MERGE = 'key_merge';
+var VAL_MERGE_1 = {'foo': 1, 'bar': {'hoo': 1, 'boo': 1}, 'moo': {'a': 3}};
+var VAL_MERGE_2 = {'bar': {'hoo': 2}, 'baz': 2, 'moo': {'a': 3}};
+var VAL_MERGE_EXPECT =
+ {'foo': 1, 'bar': {'hoo': 2, 'boo': 1}, 'baz': 2, 'moo': {'a': 3}};
// setup in componentDidMount
var done;
@@ -40,8 +47,9 @@ function expectTrue(condition, message) {
function expectEqual(lhs, rhs, testname) {
expectTrue(
- lhs === rhs,
- 'Error in test ' + testname + ': expected ' + rhs + ', got ' + lhs
+ !deepDiffer(lhs, rhs),
+ 'Error in test ' + testname + ': expected\n' + JSON.stringify(rhs) +
+ '\ngot\n' + JSON.stringify(lhs)
);
}
@@ -93,25 +101,25 @@ function testRemoveItem() {
'Missing KEY_1 or KEY_2 in ' + '(' + result + ')'
);
updateMessage('testRemoveItem - add two items');
- AsyncStorage.removeItem(KEY_1, (err) => {
- expectAsyncNoError(err);
+ AsyncStorage.removeItem(KEY_1, (err2) => {
+ expectAsyncNoError(err2);
updateMessage('delete successful ');
- AsyncStorage.getItem(KEY_1, (err, result) => {
- expectAsyncNoError(err);
+ AsyncStorage.getItem(KEY_1, (err3, result2) => {
+ expectAsyncNoError(err3);
expectEqual(
- result,
+ result2,
null,
'testRemoveItem: key_1 present after delete'
);
updateMessage('key properly removed ');
- AsyncStorage.getAllKeys((err, result2) => {
- expectAsyncNoError(err);
+ AsyncStorage.getAllKeys((err4, result3) => {
+ expectAsyncNoError(err4);
expectTrue(
- result2.indexOf(KEY_1) === -1,
- 'Unexpected: KEY_1 present in ' + result2
+ result3.indexOf(KEY_1) === -1,
+ 'Unexpected: KEY_1 present in ' + result3
);
- updateMessage('proper length returned.\nDone!');
- done();
+ updateMessage('proper length returned.');
+ runTestCase('should merge values', testMerge);
});
});
});
@@ -120,6 +128,21 @@ function testRemoveItem() {
});
}
+function testMerge() {
+ AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), (err1) => {
+ expectAsyncNoError(err1);
+ AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), (err2) => {
+ expectAsyncNoError(err2);
+ AsyncStorage.getItem(KEY_MERGE, (err3, result) => {
+ expectAsyncNoError(err3);
+ expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge');
+ updateMessage('objects deeply merged\nDone!');
+ done();
+ });
+ });
+ });
+}
+
var AsyncStorageTest = React.createClass({
getInitialState() {
return {
diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js
index 0245cc144..c8e289431 100644
--- a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js
+++ b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js
@@ -7,7 +7,6 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule POPAnimation
- * @flow
*/
'use strict';
@@ -17,7 +16,7 @@ if (!RCTPOPAnimationManager) {
// workaround to enable its availability to be determined at runtime.
// For Flow let's pretend like we always export POPAnimation
// so all our users don't need to do null checks
- module.exports = ((null: any): typeof POPAnimation);
+ module.exports = null;
} else {
var ReactPropTypes = require('ReactPropTypes');
diff --git a/Libraries/Components/DatePicker/DatePickerIOS.ios.js b/Libraries/Components/DatePicker/DatePickerIOS.ios.js
index 41fc9b877..f184c6f79 100644
--- a/Libraries/Components/DatePicker/DatePickerIOS.ios.js
+++ b/Libraries/Components/DatePicker/DatePickerIOS.ios.js
@@ -120,7 +120,7 @@ var DatePickerIOS = React.createClass({
+
+ ProgressViewIOS is not supported on this platform!
+
+
+ );
+ },
+});
+
+var styles = StyleSheet.create({
+ dummy: {
+ width: 120,
+ height: 20,
+ backgroundColor: '#ffbcbc',
+ borderWidth: 1,
+ borderColor: 'red',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ text: {
+ color: '#333333',
+ margin: 5,
+ fontSize: 10,
+ }
+});
+
+module.exports = DummyProgressViewIOS;
diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js
new file mode 100644
index 000000000..cbdf43ae5
--- /dev/null
+++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js
@@ -0,0 +1,83 @@
+/**
+ * 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 ProgressViewIOS
+ * @flow
+ */
+'use strict';
+
+var Image = require('Image');
+var NativeMethodsMixin = require('NativeMethodsMixin');
+var NativeModules = require('NativeModules');
+var PropTypes = require('ReactPropTypes');
+var React = require('React');
+var StyleSheet = require('StyleSheet');
+
+var requireNativeComponent = require('requireNativeComponent');
+var verifyPropTypes = require('verifyPropTypes');
+
+/**
+ * Use `ProgressViewIOS` to render a UIProgressView on iOS.
+ */
+var ProgressViewIOS = React.createClass({
+ mixins: [NativeMethodsMixin],
+
+ propTypes: {
+ /**
+ * The progress bar style.
+ */
+ progressViewStyle: PropTypes.oneOf(['default', 'bar']),
+
+ /**
+ * The progress value (between 0 and 1).
+ */
+ progress: PropTypes.number,
+
+ /**
+ * The tint color of the progress bar itself.
+ */
+ progressTintColor: PropTypes.string,
+
+ /**
+ * The tint color of the progress bar track.
+ */
+ trackTintColor: PropTypes.string,
+
+ /**
+ * A stretchable image to display as the progress bar.
+ */
+ progressImage: Image.propTypes.source,
+
+ /**
+ * A stretchable image to display behind the progress bar.
+ */
+ trackImage: Image.propTypes.source,
+ },
+
+ render: function() {
+ return (
+
+ );
+ }
+});
+
+var styles = StyleSheet.create({
+ progressView: {
+ height: NativeModules.ProgressViewManager.ComponentHeight
+ },
+});
+
+var RCTProgressView = requireNativeComponent(
+ 'RCTProgressView',
+ ProgressViewIOS
+);
+
+module.exports = ProgressViewIOS;
diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js
index 3a87d05a8..8d274bdc5 100644
--- a/Libraries/Components/ScrollView/ScrollView.js
+++ b/Libraries/Components/ScrollView/ScrollView.js
@@ -38,12 +38,6 @@ var PropTypes = React.PropTypes;
var SCROLLVIEW = 'ScrollView';
var INNERVIEW = 'InnerScrollView';
-var keyboardDismissModeConstants = {
- 'none': RCTScrollViewConsts.KeyboardDismissMode.None, // default
- 'interactive': RCTScrollViewConsts.KeyboardDismissMode.Interactive,
- 'onDrag': RCTScrollViewConsts.KeyboardDismissMode.OnDrag,
-};
-
/**
* Component that wraps platform ScrollView while providing
* integration with touch locking "responder" system.
@@ -147,7 +141,7 @@ var ScrollView = React.createClass({
keyboardDismissMode: PropTypes.oneOf([
'none', // default
'interactive',
- 'onDrag',
+ 'on-drag',
]),
/**
* When false, tapping outside of the focused text input when the keyboard
@@ -287,9 +281,6 @@ var ScrollView = React.createClass({
...this.props,
alwaysBounceHorizontal,
alwaysBounceVertical,
- keyboardDismissMode: this.props.keyboardDismissMode ?
- keyboardDismissModeConstants[this.props.keyboardDismissMode] :
- undefined,
style: ([styles.base, this.props.style]: ?Array),
onTouchStart: this.scrollResponderHandleTouchStart,
onTouchMove: this.scrollResponderHandleTouchMove,
@@ -308,7 +299,7 @@ var ScrollView = React.createClass({
onResponderRelease: this.scrollResponderHandleResponderRelease,
onResponderReject: this.scrollResponderHandleResponderReject,
};
-
+
var ScrollViewClass;
if (Platform.OS === 'ios') {
ScrollViewClass = RCTScrollView;
@@ -318,6 +309,13 @@ var ScrollView = React.createClass({
} else {
ScrollViewClass = AndroidScrollView;
}
+ var keyboardDismissModeConstants = {
+ 'none': RCTScrollViewConsts.KeyboardDismissMode.None, // default
+ 'interactive': RCTScrollViewConsts.KeyboardDismissMode.Interactive,
+ 'on-drag': RCTScrollViewConsts.KeyboardDismissMode.OnDrag,
+ };
+ props.keyboardDismissMode = props.keyboardDismissMode ?
+ keyboardDismissModeConstants[props.keyboardDismissMode] : undefined;
}
invariant(
ScrollViewClass !== undefined,
diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js
index 28fbea027..848144bff 100644
--- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js
+++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js
@@ -17,7 +17,7 @@ var StyleSheet = require('StyleSheet');
var Text = require('Text');
var View = require('View');
-var Dummy = React.createClass({
+var DummySegmentedControlIOS = React.createClass({
render: function() {
return (
@@ -46,4 +46,4 @@ var styles = StyleSheet.create({
}
});
-module.exports = Dummy;
+module.exports = DummySegmentedControlIOS;
diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js
index 23d952776..ec3b6c614 100644
--- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js
+++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js
@@ -108,13 +108,7 @@ var styles = StyleSheet.create({
var RCTSegmentedControl = requireNativeComponent(
'RCTSegmentedControl',
- null
+ SegmentedControlIOS
);
-if (__DEV__) {
- verifyPropTypes(
- RCTSegmentedControl,
- RCTSegmentedControl.viewConfig
- );
-}
module.exports = SegmentedControlIOS;
diff --git a/Libraries/Components/StatusBar/StatusBarIOS.ios.js b/Libraries/Components/StatusBar/StatusBarIOS.ios.js
index 14a5cecf2..adfed78b7 100644
--- a/Libraries/Components/StatusBar/StatusBarIOS.ios.js
+++ b/Libraries/Components/StatusBar/StatusBarIOS.ios.js
@@ -13,26 +13,26 @@
var RCTStatusBarManager = require('NativeModules').StatusBarManager;
+type StatusBarStyle = $Enum<{
+ 'default': string,
+ 'light-content': string,
+}>;
+
+type StatusBarAnimation = $Enum<{
+ 'none': string,
+ 'fade': string,
+ 'slide': string,
+}>;
+
var StatusBarIOS = {
- Style: {
- default: RCTStatusBarManager.Style.default,
- lightContent: RCTStatusBarManager.Style.lightContent
- },
-
- Animation: {
- none: RCTStatusBarManager.Animation.none,
- fade: RCTStatusBarManager.Animation.fade,
- slide: RCTStatusBarManager.Animation.slide,
- },
-
- setStyle(style: number, animated?: boolean) {
+ setStyle(style: StatusBarStyle, animated?: boolean) {
animated = animated || false;
RCTStatusBarManager.setStyle(style, animated);
},
- setHidden(hidden: boolean, animation: number) {
- animation = animation || StatusBarIOS.Animation.none;
+ setHidden(hidden: boolean, animation?: StatusBarAnimation) {
+ animation = animation || 'none';
RCTStatusBarManager.setHidden(hidden, animation);
},
};
diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js
index 03f374b67..7d3f04b33 100644
--- a/Libraries/Components/TextInput/TextInput.js
+++ b/Libraries/Components/TextInput/TextInput.js
@@ -31,10 +31,6 @@ var emptyFunction = require('emptyFunction');
var invariant = require('invariant');
var merge = require('merge');
-var autoCapitalizeConsts = RCTUIManager.UIText.AutocapitalizationType;
-var keyboardTypeConsts = RCTUIManager.UIKeyboardType;
-var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType;
-
var RCTTextViewAttributes = merge(ReactNativeViewAttributes.UIView, {
autoCorrect: true,
autoCapitalize: true,
@@ -96,10 +92,6 @@ var viewConfigAndroid = {
validAttributes: AndroidTextInputAttributes,
};
-var crossPlatformKeyboardTypeMap = {
- 'numeric': 'decimal-pad',
-};
-
type DefaultProps = {
bufferDelay: number;
};
@@ -171,8 +163,11 @@ var TextInput = React.createClass({
* Determines which keyboard to open, e.g.`numeric`.
*/
keyboardType: PropTypes.oneOf([
- 'default',
- // iOS
+ // Cross-platform
+ 'default',
+ 'numeric',
+ 'email-address',
+ // iOS-only
'ascii-capable',
'numbers-and-punctuation',
'url',
@@ -182,9 +177,6 @@ var TextInput = React.createClass({
'decimal-pad',
'twitter',
'web-search',
- // Cross-platform
- 'numeric',
- 'email-address',
]),
/**
* Determines how the return key should look.
@@ -426,18 +418,12 @@ var TextInput = React.createClass({
_renderIOS: function() {
var textContainer;
- var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize];
- var clearButtonMode = RCTUIManager.UITextField.clearButtonMode[this.props.clearButtonMode];
+ var props = this.props;
+ props.style = [styles.input, this.props.style];
- var keyboardType = keyboardTypeConsts[
- crossPlatformKeyboardTypeMap[this.props.keyboardType] ||
- this.props.keyboardType
- ];
- var returnKeyType = returnKeyTypeConsts[this.props.returnKeyType];
-
- if (!this.props.multiline) {
+ if (!props.multiline) {
for (var propKey in onlyMultiline) {
- if (this.props[propKey]) {
+ if (props[propKey]) {
throw new Error(
'TextInput prop `' + propKey + '` is only supported with multiline.'
);
@@ -446,77 +432,48 @@ var TextInput = React.createClass({
textContainer =
true}
- onLayout={this.props.onLayout}
- placeholder={this.props.placeholder}
- placeholderTextColor={this.props.placeholderTextColor}
text={this.state.bufferedValue}
- autoCapitalize={autoCapitalize}
- autoCorrect={this.props.autoCorrect}
- clearButtonMode={clearButtonMode}
- clearTextOnFocus={this.props.clearTextOnFocus}
- selectTextOnFocus={this.props.selectTextOnFocus}
/>;
} else {
for (var propKey in notMultiline) {
- if (this.props[propKey]) {
+ if (props[propKey]) {
throw new Error(
'TextInput prop `' + propKey + '` cannot be used with multiline.'
);
}
}
- var children = this.props.children;
+ var children = props.children;
var childCount = 0;
ReactChildren.forEach(children, () => ++childCount);
invariant(
- !(this.props.value && childCount),
+ !(props.value && childCount),
'Cannot specify both value and children.'
);
if (childCount > 1) {
children = {children};
}
- if (this.props.inputView) {
- children = [children, this.props.inputView];
+ if (props.inputView) {
+ children = [children, props.inputView];
}
textContainer =
;
}
@@ -524,14 +481,14 @@ var TextInput = React.createClass({
+ testID={props.testID}>
{textContainer}
);
},
_renderAndroid: function() {
- var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize];
+ var autoCapitalize = RCTUIManager.UIText.AutocapitalizationType[this.props.autoCapitalize];
var children = this.props.children;
var childCount = 0;
ReactChildren.forEach(children, () => ++childCount);
diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js
index 533652f65..dcbfbeee1 100644
--- a/Libraries/Components/Touchable/TouchableHighlight.js
+++ b/Libraries/Components/Touchable/TouchableHighlight.js
@@ -23,6 +23,7 @@ var View = require('View');
var cloneWithProps = require('cloneWithProps');
var ensureComponentIsNative = require('ensureComponentIsNative');
+var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
var keyOf = require('keyOf');
var merge = require('merge');
var onlyChild = require('onlyChild');
@@ -111,6 +112,7 @@ var TouchableHighlight = React.createClass({
},
componentDidMount: function() {
+ ensurePositiveDelayProps(this.props);
ensureComponentIsNative(this.refs[CHILD_REF]);
},
@@ -119,6 +121,7 @@ var TouchableHighlight = React.createClass({
},
componentWillReceiveProps: function(nextProps) {
+ ensurePositiveDelayProps(nextProps);
if (nextProps.activeOpacity !== this.props.activeOpacity ||
nextProps.underlayColor !== this.props.underlayColor ||
nextProps.style !== this.props.style) {
@@ -152,7 +155,8 @@ var TouchableHighlight = React.createClass({
touchableHandlePress: function() {
this.clearTimeout(this._hideTimeout);
this._showUnderlay();
- this._hideTimeout = this.setTimeout(this._hideUnderlay, 100);
+ this._hideTimeout = this.setTimeout(this._hideUnderlay,
+ this.props.delayPressOut || 100);
this.props.onPress && this.props.onPress();
},
@@ -164,6 +168,18 @@ var TouchableHighlight = React.createClass({
return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant!
},
+ touchableGetHighlightDelayMS: function() {
+ return this.props.delayPressIn;
+ },
+
+ touchableGetLongPressDelayMS: function() {
+ return this.props.delayLongPress;
+ },
+
+ touchableGetPressOutDelayMS: function() {
+ return this.props.delayPressOut;
+ },
+
_showUnderlay: function() {
this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps);
this.refs[CHILD_REF].setNativeProps(this.state.activeProps);
diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js
index d99bf7380..a0891714f 100644
--- a/Libraries/Components/Touchable/TouchableOpacity.js
+++ b/Libraries/Components/Touchable/TouchableOpacity.js
@@ -15,11 +15,13 @@
var NativeMethodsMixin = require('NativeMethodsMixin');
var POPAnimationMixin = require('POPAnimationMixin');
var React = require('React');
+var TimerMixin = require('react-timer-mixin');
var Touchable = require('Touchable');
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
var cloneWithProps = require('cloneWithProps');
var ensureComponentIsNative = require('ensureComponentIsNative');
+var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
var flattenStyle = require('flattenStyle');
var keyOf = require('keyOf');
var onlyChild = require('onlyChild');
@@ -50,7 +52,7 @@ var onlyChild = require('onlyChild');
*/
var TouchableOpacity = React.createClass({
- mixins: [Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
+ mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
propTypes: {
...TouchableWithoutFeedback.propTypes,
@@ -72,6 +74,7 @@ var TouchableOpacity = React.createClass({
},
componentDidMount: function() {
+ ensurePositiveDelayProps(this.props);
ensureComponentIsNative(this.refs[CHILD_REF]);
},
@@ -79,6 +82,10 @@ var TouchableOpacity = React.createClass({
ensureComponentIsNative(this.refs[CHILD_REF]);
},
+ componentWillReceiveProps: function(nextProps) {
+ ensurePositiveDelayProps(nextProps);
+ },
+
setOpacityTo: function(value) {
if (POPAnimationMixin) {
// Reset with animation if POP is available
@@ -86,6 +93,7 @@ var TouchableOpacity = React.createClass({
var anim = {
type: this.AnimationTypes.linear,
property: this.AnimationProperties.opacity,
+ duration: 0.15,
toValue: value,
};
this.startAnimation(CHILD_REF, anim);
@@ -102,20 +110,26 @@ var TouchableOpacity = React.createClass({
* defined on your component.
*/
touchableHandleActivePressIn: function() {
- this.refs[CHILD_REF].setNativeProps({
- opacity: this.props.activeOpacity
- });
+ this.clearTimeout(this._hideTimeout);
+ this._hideTimeout = null;
+ this._opacityActive();
this.props.onPressIn && this.props.onPressIn();
},
touchableHandleActivePressOut: function() {
- var child = onlyChild(this.props.children);
- var childStyle = flattenStyle(child.props.style) || {};
- this.setOpacityTo(childStyle.opacity === undefined ? 1 : childStyle.opacity);
+ if (!this._hideTimeout) {
+ this._opacityInactive();
+ }
this.props.onPressOut && this.props.onPressOut();
},
touchableHandlePress: function() {
+ this.clearTimeout(this._hideTimeout);
+ this._opacityActive();
+ this._hideTimeout = this.setTimeout(
+ this._opacityInactive,
+ this.props.delayPressOut || 100
+ );
this.props.onPress && this.props.onPress();
},
@@ -128,7 +142,30 @@ var TouchableOpacity = React.createClass({
},
touchableGetHighlightDelayMS: function() {
- return 0;
+ 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 child = onlyChild(this.props.children);
+ var childStyle = flattenStyle(child.props.style) || {};
+ this.setOpacityTo(
+ childStyle.opacity === undefined ? 1 : childStyle.opacity
+ );
},
render: function() {
diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
index cd9ea02fd..227cbeae2 100755
--- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js
+++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
@@ -12,7 +12,9 @@
'use strict';
var React = require('React');
+var TimerMixin = require('react-timer-mixin');
var Touchable = require('Touchable');
+var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
var onlyChild = require('onlyChild');
/**
@@ -31,23 +33,44 @@ type Event = Object;
* one of the primary reason a "web" app doesn't feel "native".
*/
var TouchableWithoutFeedback = React.createClass({
- mixins: [Touchable.Mixin],
+ mixins: [TimerMixin, Touchable.Mixin],
propTypes: {
/**
* Called when the touch is released, but not if cancelled (e.g. by a scroll
* that steals the responder lock).
*/
+ accessible: React.PropTypes.bool,
onPress: React.PropTypes.func,
onPressIn: React.PropTypes.func,
onPressOut: 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,
},
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.
@@ -73,13 +96,22 @@ var TouchableWithoutFeedback = React.createClass({
},
touchableGetHighlightDelayMS: function(): number {
- return 0;
+ 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(onlyChild(this.props.children), {
- accessible: true,
+ accessible: this.props.accessible !== false,
testID: this.props.testID,
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
diff --git a/Libraries/Components/Touchable/ensurePositiveDelayProps.js b/Libraries/Components/Touchable/ensurePositiveDelayProps.js
new file mode 100644
index 000000000..4c6525a54
--- /dev/null
+++ b/Libraries/Components/Touchable/ensurePositiveDelayProps.js
@@ -0,0 +1,24 @@
+/**
+ * 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('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/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js
index 9b84937af..23e2c7a63 100644
--- a/Libraries/Image/Image.ios.js
+++ b/Libraries/Image/Image.ios.js
@@ -146,20 +146,11 @@ var Image = React.createClass({
if (this.props.style && this.props.style.tintColor) {
warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.');
}
- var resizeMode = this.props.resizeMode || style.resizeMode;
- var contentModes = NativeModules.UIManager.UIView.ContentMode;
- var contentMode;
- if (resizeMode === ImageResizeMode.stretch) {
- contentMode = contentModes.ScaleToFill;
- } else if (resizeMode === ImageResizeMode.contain) {
- contentMode = contentModes.ScaleAspectFit;
- } else { // ImageResizeMode.cover or undefined
- contentMode = contentModes.ScaleAspectFill;
- }
+ var resizeMode = this.props.resizeMode || style.resizeMode || 'cover';
var nativeProps = merge(this.props, {
style,
- contentMode,
+ resizeMode,
tintColor: style.tintColor,
});
if (isStored) {
@@ -187,7 +178,7 @@ var nativeOnlyProps = {
src: true,
defaultImageSrc: true,
imageTag: true,
- contentMode: true,
+ resizeMode: true,
};
if (__DEV__) {
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);
diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m
index aa524ef56..7ff8c6379 100644
--- a/Libraries/Image/RCTImageDownloader.m
+++ b/Libraries/Image/RCTImageDownloader.m
@@ -82,8 +82,8 @@ static NSString *RCTCacheKeyForURL(NSURL *url)
RCTImageDownloader *strongSelf = weakSelf;
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
- for (RCTCachedDataDownloadBlock block in blocks) {
- block(cached, data, error);
+ for (RCTCachedDataDownloadBlock cacheDownloadBlock in blocks) {
+ cacheDownloadBlock(cached, data, error);
}
});
};
diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m
index 2ecf69971..005b726cf 100644
--- a/Libraries/Image/RCTNetworkImageViewManager.m
+++ b/Libraries/Image/RCTNetworkImageViewManager.m
@@ -29,6 +29,6 @@ RCT_EXPORT_MODULE()
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL)
-RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode)
+RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
@end
diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m
index ce6aab187..bdc6f0596 100644
--- a/Libraries/Image/RCTStaticImageManager.m
+++ b/Libraries/Image/RCTStaticImageManager.m
@@ -26,7 +26,7 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
-RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode)
+RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage)
{
if (json) {
diff --git a/Libraries/Inspector.js b/Libraries/Inspector.js
index 5c70be2e8..e0017c3cf 100644
--- a/Libraries/Inspector.js
+++ b/Libraries/Inspector.js
@@ -50,6 +50,9 @@ function findInstanceByNativeTag(rootTag, nativeTag) {
var containerID = ReactNativeTagHandles.tagToRootNodeID[rootTag];
var rootInstance = ReactNativeMount._instancesByContainerID[containerID];
var targetID = ReactNativeTagHandles.tagToRootNodeID[nativeTag];
+ if (!targetID) {
+ return undefined;
+ }
return findInstance(rootInstance, targetID);
}
diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js
index b2c3c3927..42302fa26 100644
--- a/Libraries/Picker/PickerIOS.ios.js
+++ b/Libraries/Picker/PickerIOS.ios.js
@@ -20,8 +20,7 @@ var RCTPickerIOSConsts = require('NativeModules').UIManager.RCTPicker.Constants;
var StyleSheet = require('StyleSheet');
var View = require('View');
-var createReactNativeComponentClass =
- require('createReactNativeComponentClass');
+var requireNativeComponent = require('requireNativeComponent');
var merge = require('merge');
var PICKER = 'picker';
@@ -60,7 +59,7 @@ var PickerIOS = React.createClass({
{
- handler(new PushNotificationIOS(notifData));
- }
+ type === 'notification' || type === 'register',
+ 'PushNotificationIOS only supports `notification` and `register` events'
);
+ if (type === 'notification') {
+ _notifHandlers[handler] = RCTDeviceEventEmitter.addListener(
+ DEVICE_NOTIF_EVENT,
+ (notifData) => {
+ handler(new PushNotificationIOS(notifData));
+ }
+ );
+ } else if (type === 'register') {
+ _notifHandlers[handler] = RCTDeviceEventEmitter.addListener(
+ NOTIF_REGISTER_EVENT,
+ (registrationInfo) => {
+ handler(registrationInfo.deviceToken);
+ }
+ );
+ }
}
/**
- * Requests all notification permissions from iOS, prompting the user's
- * dialog box.
+ * Requests notification permissions from iOS, prompting the user's
+ * dialog box. By default, it will request all notification permissions, but
+ * a subset of these can be requested by passing a map of requested
+ * permissions.
+ * The following permissions are supported:
+ *
+ * - `alert`
+ * - `badge`
+ * - `sound`
+ *
+ * If a map is provided to the method, only the permissions with truthy values
+ * will be requested.
*/
- static requestPermissions() {
- RCTPushNotificationManager.requestPermissions();
+ static requestPermissions(permissions?: {
+ alert?: boolean,
+ badge?: boolean,
+ sound?: boolean
+ }) {
+ var requestedPermissions = {};
+ if (permissions) {
+ requestedPermissions = {
+ alert: !!permissions.alert,
+ badge: !!permissions.badge,
+ sound: !!permissions.sound
+ };
+ } else {
+ requestedPermissions = {
+ alert: true,
+ badge: true,
+ sound: true
+ };
+ }
+ RCTPushNotificationManager.requestPermissions(requestedPermissions);
}
/**
@@ -97,8 +140,8 @@ class PushNotificationIOS {
*/
static removeEventListener(type: string, handler: Function) {
invariant(
- type === 'notification',
- 'PushNotificationIOS only supports `notification` events'
+ type === 'notification' || type === 'register',
+ 'PushNotificationIOS only supports `notification` and `register` events'
);
if (!_notifHandlers[handler]) {
return;
diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h
index ef1ba1496..194bbc5dd 100644
--- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h
+++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h
@@ -14,6 +14,7 @@
@interface RCTPushNotificationManager : NSObject
+ (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;
++ (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification;
@end
diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
index 4846c885e..1966c6045 100644
--- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
+++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
@@ -12,7 +12,18 @@
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
+
+#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert
+#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge
+#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound
+#define UIUserNotificationTypeNone UIRemoteNotificationTypeNone
+#define UIUserNotificationType UIRemoteNotificationType
+
+#endif
+
NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived";
+NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered";
@implementation RCTPushNotificationManager
{
@@ -30,6 +41,10 @@ RCT_EXPORT_MODULE()
selector:@selector(handleRemoteNotificationReceived:)
name:RCTRemoteNotificationReceived
object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleRemoteNotificationsRegistered:)
+ name:RCTRemoteNotificationsRegistered
+ object:nil];
}
return self;
}
@@ -52,6 +67,21 @@ RCT_EXPORT_MODULE()
}
}
++ (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
+{
+ NSMutableString *hexString = [NSMutableString string];
+ const unsigned char *bytes = [deviceToken bytes];
+ for (int i = 0; i < [deviceToken length]; i++) {
+ [hexString appendFormat:@"%02x", bytes[i]];
+ }
+ NSDictionary *userInfo = @{
+ @"deviceToken" : [hexString copy]
+ };
+ [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationsRegistered
+ object:self
+ userInfo:userInfo];
+}
+
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived
@@ -65,6 +95,12 @@ RCT_EXPORT_MODULE()
body:[notification userInfo]];
}
+- (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
+{
+ [_bridge.eventDispatcher sendDeviceEventWithName:@"remoteNotificationsRegistered"
+ body:[notification userInfo]];
+}
+
/**
* Update the application icon badge number on the home screen
*/
@@ -83,36 +119,35 @@ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback
]);
}
-RCT_EXPORT_METHOD(requestPermissions)
+RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions)
{
- Class _UIUserNotificationSettings;
- if ((_UIUserNotificationSettings = NSClassFromString(@"UIUserNotificationSettings"))) {
- UIUserNotificationType types = UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert;
- UIUserNotificationSettings *notificationSettings = [_UIUserNotificationSettings settingsForTypes:types categories:nil];
- [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
+ UIUserNotificationType types = UIRemoteNotificationTypeNone;
+ if (permissions) {
+ if ([permissions[@"alert"] boolValue]) {
+ types |= UIUserNotificationTypeAlert;
+ }
+ if ([permissions[@"badge"] boolValue]) {
+ types |= UIUserNotificationTypeBadge;
+ }
+ if ([permissions[@"sound"] boolValue]) {
+ types |= UIUserNotificationTypeSound;
+ }
} else {
+ types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
+ }
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
-
- [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
- UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert];
-
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0
+ id notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
+ [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
+#else
+ [[UIApplication sharedApplication] registerForRemoteNotificationTypes:types];
#endif
- }
}
RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback)
{
-
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
-
-#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert
-#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge
-#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound
-
-#endif
-
NSUInteger types = 0;
if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) {
types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types];
diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m
index 9c0cacf70..8a7e739bb 100644
--- a/Libraries/RCTTest/RCTTestRunner.m
+++ b/Libraries/RCTTest/RCTTestRunner.m
@@ -35,7 +35,11 @@
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
_testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
_testController.referenceImagesDirectory = referenceDir;
+#if RUNNING_ON_CI
+ _scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
+#else
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]];
+#endif
}
return self;
}
diff --git a/Libraries/ReactIOS/InspectorOverlay/BorderBox.js b/Libraries/ReactIOS/InspectorOverlay/BorderBox.js
new file mode 100644
index 000000000..caee8ccd6
--- /dev/null
+++ b/Libraries/ReactIOS/InspectorOverlay/BorderBox.js
@@ -0,0 +1,38 @@
+/**
+ * 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 BorderBox
+ * @flow
+ */
+'use strict';
+
+var React = require('React');
+var View = require('View');
+
+class BorderBox extends React.Component {
+ render() {
+ var box = this.props.box;
+ if (!box) {
+ return this.props.children;
+ }
+ var style = {
+ borderTopWidth: box.top,
+ borderBottomWidth: box.bottom,
+ borderLeftWidth: box.left,
+ borderRightWidth: box.right,
+ };
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
+
+module.exports = BorderBox;
+
diff --git a/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js b/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js
new file mode 100644
index 000000000..e50d9869a
--- /dev/null
+++ b/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js
@@ -0,0 +1,113 @@
+/**
+ * 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 BoxInspector
+ * @flow
+ */
+'use strict';
+
+var React = require('React');
+var StyleSheet = require('StyleSheet');
+var Text = require('Text');
+var View = require('View');
+var resolveBoxStyle = require('resolveBoxStyle');
+
+var blank = {
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+};
+
+class BoxInspector extends React.Component {
+ render() {
+ var frame = this.props.frame;
+ var style = this.props.style;
+ var margin = style && resolveBoxStyle('margin', style) || blank;
+ var padding = style && resolveBoxStyle('padding', style) || blank;
+ return (
+
+
+
+
+ ({frame.left}, {frame.top})
+
+
+ {frame.width} × {frame.height}
+
+
+
+
+ );
+ }
+}
+
+class BoxContainer extends React.Component {
+ render() {
+ var box = this.props.box;
+ return (
+
+
+ {this.props.title}
+ {box.top}
+
+
+ {box.left}
+ {this.props.children}
+ {box.right}
+
+ {box.bottom}
+
+ );
+ }
+}
+
+var styles = StyleSheet.create({
+ row: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ },
+ marginLabel: {
+ width: 60,
+ },
+ label: {
+ fontSize: 10,
+ color: 'rgb(255,100,0)',
+ marginLeft: 5,
+ flex: 1,
+ textAlign: 'left',
+ top: -3,
+ },
+ buffer: {
+ fontSize: 10,
+ color: 'yellow',
+ flex: 1,
+ textAlign: 'center',
+ },
+ innerText: {
+ color: 'yellow',
+ fontSize: 12,
+ textAlign: 'center',
+ width: 70,
+ },
+ box: {
+ borderWidth: 1,
+ borderColor: 'grey',
+ },
+ boxText: {
+ color: 'white',
+ fontSize: 12,
+ marginHorizontal: 3,
+ marginVertical: 2,
+ textAlign: 'center',
+ },
+});
+
+module.exports = BoxInspector;
+
diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementBox.js b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js
new file mode 100644
index 000000000..a3851001c
--- /dev/null
+++ b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js
@@ -0,0 +1,74 @@
+/**
+ * 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 ElementBox
+ * @flow
+ */
+'use strict';
+
+var React = require('React');
+var View = require('View');
+var StyleSheet = require('StyleSheet');
+var BorderBox = require('BorderBox');
+var resolveBoxStyle = require('resolveBoxStyle');
+
+var flattenStyle = require('flattenStyle');
+
+class ElementBox extends React.Component {
+ render() {
+ var style = flattenStyle(this.props.style) || {};
+ var margin = resolveBoxStyle('margin', style);
+ var padding = resolveBoxStyle('padding', style);
+ var frameStyle = this.props.frame;
+ if (margin) {
+ frameStyle = {
+ top: frameStyle.top - margin.top,
+ left: frameStyle.left - margin.left,
+ height: frameStyle.height + margin.top + margin.bottom,
+ width: frameStyle.width + margin.left + margin.right,
+ };
+ }
+ var contentStyle = {
+ width: this.props.frame.width,
+ height: this.props.frame.height,
+ };
+ if (padding) {
+ contentStyle = {
+ width: contentStyle.width - padding.left - padding.right,
+ height: contentStyle.height - padding.top - padding.bottom,
+ };
+ }
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
+var styles = StyleSheet.create({
+ frame: {
+ position: 'absolute',
+ },
+ content: {
+ backgroundColor: 'rgba(200, 230, 255, 0.8)',
+ },
+ padding: {
+ borderColor: 'rgba(77, 255, 0, 0.3)',
+ },
+ margin: {
+ borderColor: 'rgba(255, 132, 0, 0.3)',
+ },
+});
+
+module.exports = ElementBox;
+
diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js
new file mode 100644
index 000000000..310374fb1
--- /dev/null
+++ b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js
@@ -0,0 +1,105 @@
+/**
+ * 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 ElementProperties
+ * @flow
+ */
+'use strict';
+
+var React = require('React');
+var StyleSheet = require('StyleSheet');
+var Text = require('Text');
+var View = require('View');
+var PropTypes = require('ReactPropTypes');
+var BoxInspector = require('BoxInspector');
+var StyleInspector = require('StyleInspector');
+var TouchableHighlight = require('TouchableHighlight');
+var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
+
+var flattenStyle = require('flattenStyle');
+var mapWithSeparator = require('mapWithSeparator');
+
+var ElementProperties = React.createClass({
+ propTypes: {
+ hierarchy: PropTypes.array.isRequired,
+ style: PropTypes.array.isRequired,
+ },
+
+ render: function() {
+ var style = flattenStyle(this.props.style);
+ var selection = this.props.selection;
+ // Without the `TouchableWithoutFeedback`, taps on this inspector pane
+ // would change the inspected element to whatever is under the inspector
+ return (
+
+
+
+ {mapWithSeparator(
+ this.props.hierarchy,
+ (item, i) => (
+ this.props.setSelection(i)}>
+
+ {item.getName ? item.getName() : 'Unknown'}
+
+
+ ),
+ () => ▸
+ )}
+
+
+
+
+
+
+
+ );
+ }
+});
+
+var styles = StyleSheet.create({
+ breadSep: {
+ fontSize: 8,
+ color: 'white',
+ },
+ breadcrumb: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ marginBottom: 5,
+ },
+ selected: {
+ borderColor: 'white',
+ borderRadius: 5,
+ },
+ breadItem: {
+ borderWidth: 1,
+ borderColor: 'transparent',
+ marginHorizontal: 2,
+ },
+ breadItemText: {
+ fontSize: 10,
+ color: 'white',
+ marginHorizontal: 5,
+ },
+ row: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ info: {
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
+ padding: 10,
+ },
+ path: {
+ color: 'white',
+ fontSize: 9,
+ },
+});
+
+module.exports = ElementProperties;
diff --git a/Libraries/ReactIOS/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js
similarity index 53%
rename from Libraries/ReactIOS/InspectorOverlay.js
rename to Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js
index eeb6e7965..3b391502f 100644
--- a/Libraries/ReactIOS/InspectorOverlay.js
+++ b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js
@@ -17,12 +17,16 @@ var StyleSheet = require('StyleSheet');
var Text = require('Text');
var UIManager = require('NativeModules').UIManager;
var View = require('View');
+var ElementBox = require('ElementBox');
+var ElementProperties = require('ElementProperties');
var InspectorOverlay = React.createClass({
getInitialState: function() {
return {
frame: null,
+ pointerY: 0,
hierarchy: [],
+ selection: -1,
};
},
@@ -33,15 +37,34 @@ var InspectorOverlay = React.createClass({
[locationX, locationY],
(nativeViewTag, left, top, width, height) => {
var instance = Inspector.findInstanceByNativeTag(this.props.rootTag, nativeViewTag);
+ if (!instance) {
+ return;
+ }
var hierarchy = Inspector.getOwnerHierarchy(instance);
+ var publicInstance = instance.getPublicInstance();
this.setState({
hierarchy,
- frame: {left, top, width, height}
+ pointerY: locationY,
+ selection: hierarchy.length - 1,
+ frame: {left, top, width, height},
+ style: publicInstance.props ? publicInstance.props.style : {},
});
}
);
},
+ setSelection(i) {
+ var instance = this.state.hierarchy[i];
+ var publicInstance = instance.getPublicInstance();
+ UIManager.measure(React.findNodeHandle(instance), (x, y, width, height, left, top) => {
+ this.setState({
+ frame: {left, top, width, height},
+ style: publicInstance.props ? publicInstance.props.style : {},
+ selection: i,
+ });
+ });
+ },
+
shouldSetResponser: function(e) {
this.findViewForTouchEvent(e);
return true;
@@ -49,18 +72,32 @@ var InspectorOverlay = React.createClass({
render: function() {
var content = [];
+ var justifyContent = 'flex-end';
if (this.state.frame) {
- var distanceToTop = this.state.frame.top;
- var distanceToBottom = Dimensions.get('window').height -
- (this.state.frame.top + this.state.frame.height);
+ var distanceToTop = this.state.pointerY;
+ var distanceToBottom = Dimensions.get('window').height - distanceToTop;
- var justifyContent = distanceToTop > distanceToBottom
+ justifyContent = distanceToTop > distanceToBottom
? 'flex-start'
: 'flex-end';
- content.push();
- content.push();
+ content.push();
+ content.push(
+
+ );
+ } else {
+ content.push(
+
+ Welcome to the inspector! Tap something to inspect it.
+
+ );
}
return (
{
- return instance.getName ? instance.getName() : 'Unknown';
- }).join(' > ');
- return (
-
-
- {path}
-
-
- );
- }
-});
-
var styles = StyleSheet.create({
+ welcomeMessage: {
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
+ padding: 10,
+ paddingVertical: 50,
+ },
+ welcomeText: {
+ color: 'white',
+ },
inspector: {
- backgroundColor: 'rgba(255,255,255,0.8)',
+ backgroundColor: 'rgba(255,255,255,0.0)',
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
},
- frame: {
- position: 'absolute',
- backgroundColor: 'rgba(155,155,255,0.3)',
- },
- info: {
- backgroundColor: 'rgba(0, 0, 0, 0.7)',
- padding: 10,
- },
- path: {
- color: 'white',
- fontSize: 9,
- }
});
module.exports = InspectorOverlay;
diff --git a/Libraries/ReactIOS/InspectorOverlay/StyleInspector.js b/Libraries/ReactIOS/InspectorOverlay/StyleInspector.js
new file mode 100644
index 000000000..702d01e1d
--- /dev/null
+++ b/Libraries/ReactIOS/InspectorOverlay/StyleInspector.js
@@ -0,0 +1,63 @@
+/**
+ * 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 StyleInspector
+ * @flow
+ */
+'use strict';
+
+var React = require('React');
+var StyleSheet = require('StyleSheet');
+var Text = require('Text');
+var View = require('View');
+
+class StyleInspector extends React.Component {
+ render() {
+ if (!this.props.style) {
+ return No style;
+ }
+ var names = Object.keys(this.props.style);
+ return (
+
+
+ {names.map(name => {name}:)}
+
+
+ {names.map(name => {this.props.style[name]})}
+
+
+ );
+ }
+}
+
+var styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ },
+ row: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ },
+ attr: {
+ fontSize: 10,
+ color: '#ccc',
+ },
+ value: {
+ fontSize: 10,
+ color: 'white',
+ marginLeft: 10,
+ },
+ noStyle: {
+ color: 'white',
+ fontSize: 10,
+ },
+});
+
+module.exports = StyleInspector;
+
diff --git a/Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js b/Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js
new file mode 100644
index 000000000..e0bfb601c
--- /dev/null
+++ b/Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js
@@ -0,0 +1,59 @@
+/**
+ * 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 resolveBoxStyle
+ * @flow
+ */
+'use strict';
+
+/**
+ * Resolve a style property into it's component parts, e.g.
+ *
+ * resolveProperties('margin', {margin: 5, marginBottom: 10})
+ * ->
+ * {top: 5, left: 5, right: 5, bottom: 10}
+ *
+ * If none are set, returns false.
+ */
+function resolveBoxStyle(prefix: String, style: Object): ?Object {
+ var res = {};
+ var subs = ['top', 'left', 'bottom', 'right'];
+ var set = false;
+ subs.forEach(sub => {
+ res[sub] = style[prefix] || 0;
+ });
+ if (style[prefix]) {
+ set = true;
+ }
+ if (style[prefix + 'Vertical']) {
+ res.top = res.bottom = style[prefix + 'Vertical'];
+ set = true;
+ }
+ if (style[prefix + 'Horizontal']) {
+ res.left = res.right = style[prefix + 'Horizontal'];
+ set = true;
+ }
+ subs.forEach(sub => {
+ var val = style[prefix + capFirst(sub)];
+ if (val) {
+ res[sub] = val;
+ set = true;
+ }
+ });
+ if (!set) {
+ return;
+ }
+ return res;
+}
+
+function capFirst(text) {
+ return text[0].toUpperCase() + text.slice(1);
+}
+
+module.exports = resolveBoxStyle;
+
diff --git a/Libraries/Utilities/mapWithSeparator.js b/Libraries/Utilities/mapWithSeparator.js
new file mode 100644
index 000000000..4aa8665c3
--- /dev/null
+++ b/Libraries/Utilities/mapWithSeparator.js
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule mapWithSeparator
+ */
+'use strict';
+
+function mapWithSeparator(array, valueFunction, separatorFunction) {
+ var results = [];
+ for (var i = 0; i < array.length; i++) {
+ results.push(valueFunction(array[i], i, array));
+ if (i !== array.length - 1) {
+ results.push(separatorFunction(i));
+ }
+ }
+ return results;
+}
+
+module.exports = mapWithSeparator;
diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js
index c81183b5b..6b1c992cc 100644
--- a/Libraries/react-native/react-native.js
+++ b/Libraries/react-native/react-native.js
@@ -24,11 +24,12 @@ var ReactNative = Object.assign(Object.create(require('React')), {
Image: require('Image'),
ListView: require('ListView'),
MapView: require('MapView'),
+ Navigator: require('Navigator'),
NavigatorIOS: require('NavigatorIOS'),
PickerIOS: require('PickerIOS'),
- Navigator: require('Navigator'),
- SegmentedControlIOS: require('SegmentedControlIOS'),
+ ProgressViewIOS: require('ProgressViewIOS'),
ScrollView: require('ScrollView'),
+ SegmentedControlIOS: require('SegmentedControlIOS'),
SliderIOS: require('SliderIOS'),
SwitchIOS: require('SwitchIOS'),
TabBarIOS: require('TabBarIOS'),
@@ -47,12 +48,12 @@ var ReactNative = Object.assign(Object.create(require('React')), {
AsyncStorage: require('AsyncStorage'),
CameraRoll: require('CameraRoll'),
InteractionManager: require('InteractionManager'),
- LinkingIOS: require('LinkingIOS'),
LayoutAnimation: require('LayoutAnimation'),
+ LinkingIOS: require('LinkingIOS'),
NetInfo: require('NetInfo'),
+ PanResponder: require('PanResponder'),
PixelRatio: require('PixelRatio'),
PushNotificationIOS: require('PushNotificationIOS'),
- PanResponder: require('PanResponder'),
StatusBarIOS: require('StatusBarIOS'),
StyleSheet: require('StyleSheet'),
VibrationIOS: require('VibrationIOS'),
diff --git a/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js b/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js
index 37c423827..2a0dd1813 100644
--- a/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js
+++ b/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js
@@ -232,6 +232,8 @@ var PRESS_EXPAND_PX = 20;
var LONG_PRESS_THRESHOLD = 500;
+var LONG_PRESS_DELAY_MS = LONG_PRESS_THRESHOLD - HIGHLIGHT_DELAY_MS;
+
var LONG_PRESS_ALLOWED_MOVEMENT = 10;
// Default amount "active" region protrudes beyond box
@@ -276,7 +278,7 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10;
* +
* | RESPONDER_GRANT (HitRect)
* v
- * +---------------------------+ DELAY +-------------------------+ T - DELAY +------------------------------+
+ * +---------------------------+ DELAY +-------------------------+ T + DELAY +------------------------------+
* |RESPONDER_INACTIVE_PRESS_IN|+-------->|RESPONDER_ACTIVE_PRESS_IN| +------------> |RESPONDER_ACTIVE_LONG_PRESS_IN|
* +---------------------------+ +-------------------------+ +------------------------------+
* + ^ + ^ + ^
@@ -288,7 +290,7 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10;
* |RESPONDER_INACTIVE_PRESS_OUT|+------->|RESPONDER_ACTIVE_PRESS_OUT| |RESPONDER_ACTIVE_LONG_PRESS_OUT|
* +----------------------------+ +--------------------------+ +-------------------------------+
*
- * T - DELAY => LONG_PRESS_THRESHOLD - DELAY
+ * T + DELAY => LONG_PRESS_DELAY_MS + DELAY
*
* Not drawn are the side effects of each transition. The most important side
* effect is the `touchableHandlePress` abstract method invocation that occurs
@@ -348,12 +350,16 @@ var TouchableMixin = {
// event to make sure it doesn't get reused in the event object pool.
e.persist();
+ this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout);
+ this.pressOutDelayTimeout = null;
+
this.state.touchable.touchState = States.NOT_RESPONDER;
this.state.touchable.responderID = dispatchID;
this._receiveSignal(Signals.RESPONDER_GRANT, e);
var delayMS =
this.touchableGetHighlightDelayMS !== undefined ?
- this.touchableGetHighlightDelayMS() : HIGHLIGHT_DELAY_MS;
+ Math.max(this.touchableGetHighlightDelayMS(), 0) : HIGHLIGHT_DELAY_MS;
+ delayMS = isNaN(delayMS) ? HIGHLIGHT_DELAY_MS : delayMS;
if (delayMS !== 0) {
this.touchableDelayTimeout = setTimeout(
this._handleDelay.bind(this, e),
@@ -363,9 +369,13 @@ var TouchableMixin = {
this._handleDelay(e);
}
+ var longDelayMS =
+ this.touchableGetLongPressDelayMS !== undefined ?
+ Math.max(this.touchableGetLongPressDelayMS(), 10) : LONG_PRESS_DELAY_MS;
+ longDelayMS = isNaN(longDelayMS) ? LONG_PRESS_DELAY_MS : longDelayMS;
this.longPressDelayTimeout = setTimeout(
this._handleLongDelay.bind(this, e),
- LONG_PRESS_THRESHOLD - delayMS
+ longDelayMS + delayMS
);
},
@@ -632,8 +642,14 @@ var TouchableMixin = {
if (newIsHighlight && !curIsHighlight) {
this._savePressInLocation(e);
this.touchableHandleActivePressIn && this.touchableHandleActivePressIn();
- } else if (!newIsHighlight && curIsHighlight) {
- this.touchableHandleActivePressOut && this.touchableHandleActivePressOut();
+ } else if (!newIsHighlight && curIsHighlight && this.touchableHandleActivePressOut) {
+ if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) {
+ this.pressOutDelayTimeout = this.setTimeout(function() {
+ this.touchableHandleActivePressOut();
+ }, this.touchableGetPressOutDelayMS());
+ } else {
+ this.touchableHandleActivePressOut();
+ }
}
if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) {
diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m
index 70868f721..98808fb3f 100644
--- a/React/Base/RCTBridge.m
+++ b/React/Base/RCTBridge.m
@@ -244,30 +244,19 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
@implementation RCTModuleMethod
{
- BOOL _isClassMethod;
Class _moduleClass;
SEL _selector;
NSMethodSignature *_methodSignature;
NSArray *_argumentBlocks;
- NSString *_methodName;
dispatch_block_t _methodQueue;
}
-static NSString *RCTStringUpToFirstArgument(NSString *methodName)
-{
- NSRange colonRange = [methodName rangeOfString:@":"];
- if (colonRange.length) {
- methodName = [methodName substringToIndex:colonRange.location];
- }
- return methodName;
-}
-
- (instancetype)initWithReactMethodName:(NSString *)reactMethodName
objCMethodName:(NSString *)objCMethodName
JSMethodName:(NSString *)JSMethodName
{
if ((self = [super init])) {
- _methodName = reactMethodName;
+
NSArray *parts = [[reactMethodName substringWithRange:(NSRange){2, reactMethodName.length - 3}] componentsSeparatedByString:@" "];
// Parse class and method
@@ -277,36 +266,33 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
_moduleClassName = [_moduleClassName substringToIndex:categoryRange.location];
}
- NSArray *argumentNames = nil;
- if ([parts[1] hasPrefix:@"__rct_export__"]) {
- // New format
- NSString *selectorString = [parts[1] substringFromIndex:14];
- _selector = NSSelectorFromString(selectorString);
- _JSMethodName = JSMethodName ?: RCTStringUpToFirstArgument(selectorString);
-
- static NSRegularExpression *regExp;
- if (!regExp) {
- NSString *unusedPattern = @"(?:(?:__unused|__attribute__\\(\\(unused\\)\\)))";
- NSString *constPattern = @"(?:const)";
- NSString *constUnusedPattern = [NSString stringWithFormat:@"(?:(?:%@|%@)\\s*)", unusedPattern, constPattern];
- NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", constUnusedPattern];
- regExp = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
+ NSString *selectorString = [parts[1] substringFromIndex:14];
+ _selector = NSSelectorFromString(selectorString);
+ _JSMethodName = JSMethodName ?: ({
+ NSString *methodName = selectorString;
+ NSRange colonRange = [methodName rangeOfString:@":"];
+ if (colonRange.length) {
+ methodName = [methodName substringToIndex:colonRange.location];
}
+ methodName;
+ });
- argumentNames = [NSMutableArray array];
- [regExp enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
- NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
- [(NSMutableArray *)argumentNames addObject:argumentName];
- }];
- } else {
- // Old format
- NSString *selectorString = parts[1];
- _selector = NSSelectorFromString(selectorString);
- _JSMethodName = JSMethodName ?: RCTStringUpToFirstArgument(selectorString);
+ static NSRegularExpression *regExp;
+ if (!regExp) {
+ NSString *unusedPattern = @"(?:(?:__unused|__attribute__\\(\\(unused\\)\\)))";
+ NSString *constPattern = @"(?:const)";
+ NSString *constUnusedPattern = [NSString stringWithFormat:@"(?:(?:%@|%@)\\s*)", unusedPattern, constPattern];
+ NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", constUnusedPattern];
+ regExp = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
}
+ NSMutableArray *argumentNames = [NSMutableArray array];
+ [regExp enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
+ NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
+ [argumentNames addObject:argumentName];
+ }];
+
// Extract class and method details
- _isClassMethod = [reactMethodName characterAtIndex:0] == '+';
_moduleClass = NSClassFromString(_moduleClassName);
if (RCT_DEBUG) {
@@ -318,9 +304,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
}
// Get method signature
- _methodSignature = _isClassMethod ?
- [_moduleClass methodSignatureForSelector:_selector] :
- [_moduleClass instanceMethodSignatureForSelector:_selector];
+ _methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
// Process arguments
NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
@@ -363,119 +347,64 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
- BOOL useFallback = YES;
- if (argumentNames) {
- NSString *argumentName = argumentNames[i - 2];
- SEL selector = NSSelectorFromString([argumentName stringByAppendingString:@":"]);
- if ([RCTConvert respondsToSelector:selector]) {
- useFallback = NO;
- switch (argumentType[0]) {
-
-#define RCT_CONVERT_CASE(_value, _type) \
- case _value: { \
- _type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
- RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
- break; \
- }
-
- RCT_CONVERT_CASE(':', SEL)
- RCT_CONVERT_CASE('*', const char *)
- RCT_CONVERT_CASE('c', char)
- RCT_CONVERT_CASE('C', unsigned char)
- RCT_CONVERT_CASE('s', short)
- RCT_CONVERT_CASE('S', unsigned short)
- RCT_CONVERT_CASE('i', int)
- RCT_CONVERT_CASE('I', unsigned int)
- RCT_CONVERT_CASE('l', long)
- RCT_CONVERT_CASE('L', unsigned long)
- RCT_CONVERT_CASE('q', long long)
- RCT_CONVERT_CASE('Q', unsigned long long)
- RCT_CONVERT_CASE('f', float)
- RCT_CONVERT_CASE('d', double)
- RCT_CONVERT_CASE('B', BOOL)
- RCT_CONVERT_CASE('@', id)
- RCT_CONVERT_CASE('^', void *)
-
- case '{': {
- [argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) {
- NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
- void *returnValue = malloc(methodSignature.methodReturnLength);
- NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
- [_invocation setTarget:[RCTConvert class]];
- [_invocation setSelector:selector];
- [_invocation setArgument:&json atIndex:2];
- [_invocation invoke];
- [_invocation getReturnValue:returnValue];
-
- [invocation setArgument:returnValue atIndex:index];
-
- free(returnValue);
- }];
- break;
- }
-
- default:
- defaultCase(argumentType);
- }
- } else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) {
- addBlockArgument();
- useFallback = NO;
- }
- }
-
- if (useFallback) {
+ NSString *argumentName = argumentNames[i - 2];
+ SEL selector = NSSelectorFromString([argumentName stringByAppendingString:@":"]);
+ if ([RCTConvert respondsToSelector:selector]) {
switch (argumentType[0]) {
-#define RCT_CASE(_value, _class, _logic) \
- case _value: { \
- RCT_ARG_BLOCK( \
- if (RCT_DEBUG && json && ![json isKindOfClass:[_class class]]) { \
- RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
- json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
- return; \
- } \
- _logic \
- ) \
- break; \
- }
+#define RCT_CONVERT_CASE(_value, _type) \
+case _value: { \
+ _type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
+ RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
+ break; \
+}
- RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); )
- RCT_CASE('*', NSString, const char *value = [json UTF8String]; )
+ RCT_CONVERT_CASE(':', SEL)
+ RCT_CONVERT_CASE('*', const char *)
+ RCT_CONVERT_CASE('c', char)
+ RCT_CONVERT_CASE('C', unsigned char)
+ RCT_CONVERT_CASE('s', short)
+ RCT_CONVERT_CASE('S', unsigned short)
+ RCT_CONVERT_CASE('i', int)
+ RCT_CONVERT_CASE('I', unsigned int)
+ RCT_CONVERT_CASE('l', long)
+ RCT_CONVERT_CASE('L', unsigned long)
+ RCT_CONVERT_CASE('q', long long)
+ RCT_CONVERT_CASE('Q', unsigned long long)
+ RCT_CONVERT_CASE('f', float)
+ RCT_CONVERT_CASE('d', double)
+ RCT_CONVERT_CASE('B', BOOL)
+ RCT_CONVERT_CASE('@', id)
+ RCT_CONVERT_CASE('^', void *)
-#define RCT_SIMPLE_CASE(_value, _type, _selector) \
- case _value: { \
- RCT_ARG_BLOCK( \
- if (RCT_DEBUG && json && ![json respondsToSelector:@selector(_selector)]) { \
- RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
- index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
- return; \
- } \
- _type value = [json _selector]; \
- ) \
- break; \
- }
+ case '{': {
+ [argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) {
+ NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
+ void *returnValue = malloc(methodSignature.methodReturnLength);
+ NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
+ [_invocation setTarget:[RCTConvert class]];
+ [_invocation setSelector:selector];
+ [_invocation setArgument:&json atIndex:2];
+ [_invocation invoke];
+ [_invocation getReturnValue:returnValue];
- RCT_SIMPLE_CASE('c', char, charValue)
- RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue)
- RCT_SIMPLE_CASE('s', short, shortValue)
- RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue)
- RCT_SIMPLE_CASE('i', int, intValue)
- RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue)
- RCT_SIMPLE_CASE('l', long, longValue)
- RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue)
- RCT_SIMPLE_CASE('q', long long, longLongValue)
- RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue)
- RCT_SIMPLE_CASE('f', float, floatValue)
- RCT_SIMPLE_CASE('d', double, doubleValue)
- RCT_SIMPLE_CASE('B', BOOL, boolValue)
+ [invocation setArgument:returnValue atIndex:index];
- case '{':
- RCTLogMustFix(@"Cannot convert JSON to struct %s", argumentType);
- break;
+ free(returnValue);
+ }];
+ break;
+ }
default:
defaultCase(argumentType);
}
+ } else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) {
+ addBlockArgument();
+ } else {
+
+ // Unknown argument type
+ RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
+ " to support this type.", argumentName, [self methodName]);
}
}
@@ -494,7 +423,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
// Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
- %@ on a module of class %@", _methodName, [module class]);
+ %@ on a module of class %@", [self methodName], [module class]);
// Safety check
if (arguments.count != _argumentBlocks.count) {
@@ -520,12 +449,19 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
}
// Invoke method
- [invocation invokeWithTarget:_isClassMethod ? [module class] : module];
+ [invocation invokeWithTarget:module];
+}
+
+- (NSString *)methodName
+{
+ return [NSString stringWithFormat:@"-[%@ %@]", _moduleClass,
+ NSStringFromSelector(_selector)];
}
- (NSString *)description
{
- return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", NSStringFromClass(self.class), self, _methodName, _JSMethodName];
+ return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@();>",
+ [self class], self, [self methodName], _JSMethodName];
}
@end
@@ -562,19 +498,10 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void)
const char **entries = (const char **)(mach_header + addr);
// Create method
- RCTModuleMethod *moduleMethod;
- if (entries[2] == NULL) {
-
- // Legacy support for RCT_EXPORT()
- moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
- objCMethodName:@(entries[0])
- JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
- } else {
- moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
- objCMethodName:strlen(entries[1]) ? @(entries[1]) : nil
- JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil];
- }
-
+ RCTModuleMethod *moduleMethod =
+ [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
+ objCMethodName:@(entries[1])
+ JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil];
// Cache method
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
methodsByModuleClassName[moduleMethod.moduleClassName] =
@@ -726,7 +653,7 @@ static NSDictionary *RCTLocalModulesConfig()
@"methodID": @(methods.count),
@"type": @"local"
};
- [RCTLocalMethodNames addObject:methodName];
+ [RCTLocalMethodNames addObject:methodName];
}
// Add module and method lookup
@@ -1313,7 +1240,12 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
#pragma mark - Payload Generation
-- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID
+- (void)dispatchBlock:(dispatch_block_t)block forModule:(id)module
+{
+ [self dispatchBlock:block forModuleID:RCTModuleIDsByName[RCTBridgeModuleNameForClass([module class])]];
+}
+
+- (void)dispatchBlock:(dispatch_block_t)block forModuleID:(NSNumber *)moduleID
{
RCTAssertJSThread();
@@ -1458,7 +1390,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
if ([module respondsToSelector:@selector(batchDidComplete)]) {
[self dispatchBlock:^{
[module batchDidComplete];
- } forModule:moduleID];
+ } forModuleID:moduleID];
}
}];
}
@@ -1526,7 +1458,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
@"selector": NSStringFromSelector(method.selector),
@"args": RCTJSONStringify(params ?: [NSNull null], NULL),
});
- } forModule:@(moduleID)];
+ } forModuleID:@(moduleID)];
return YES;
}
@@ -1546,7 +1478,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
RCTProfileBeginEvent();
[observer didUpdateFrame:frameUpdate];
RCTProfileEndEvent(name, @"objc_call,fps", nil);
- } forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]];
+ } forModule:(id)observer];
}
}
@@ -1591,35 +1523,39 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
- (void)startProfiling
{
- RCTAssertMainThread();
+ RCTAssertMainThread();
if (![_parentBridge.bundleURL.scheme isEqualToString:@"http"]) {
RCTLogError(@"To run the profiler you must be running from the dev server");
return;
}
- RCTProfileInit();
+ [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
+ RCTProfileInit(self);
+ }];
}
- (void)stopProfiling
{
- RCTAssertMainThread();
+ RCTAssertMainThread();
- NSString *log = RCTProfileEnd();
- NSURL *bundleURL = _parentBridge.bundleURL;
- NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
- NSURL *URL = [NSURL URLWithString:URLString];
- NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
- URLRequest.HTTPMethod = @"POST";
- [URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
- NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
- fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
- completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
- if (error) {
- RCTLogError(@"%@", error.localizedDescription);
- }
- }];
- [task resume];
+ [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
+ NSString *log = RCTProfileEnd(self);
+ NSURL *bundleURL = _parentBridge.bundleURL;
+ NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
+ NSURL *URL = [NSURL URLWithString:URLString];
+ NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
+ URLRequest.HTTPMethod = @"POST";
+ [URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
+ NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
+ fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
+ completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if (error) {
+ RCTLogError(@"%@", error.localizedDescription);
+ }
+ }];
+ [task resume];
+ }];
}
@end
diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h
index 34b861ff3..f3a9a5a3e 100644
--- a/React/Base/RCTBridgeModule.h
+++ b/React/Base/RCTBridgeModule.h
@@ -145,15 +145,6 @@ extern const dispatch_queue_t RCTJSThread;
static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \
}
-/**
- * Deprecated, do not use.
- */
-#define RCT_EXPORT(js_name) \
- _Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \
- __attribute__((used, section("__DATA,RCTExport"))) \
- __attribute__((__aligned__(1))) \
- static const char *__rct_export_entry__[] = { __func__, #js_name, NULL }
-
/**
* The queue that will be used to call all exported methods. If omitted, this
* will call on the default background queue, which is avoids blocking the main
diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h
index ee43c1159..145e88b21 100644
--- a/React/Base/RCTConvert.h
+++ b/React/Base/RCTConvert.h
@@ -23,6 +23,8 @@
*/
@interface RCTConvert : NSObject
++ (id)id:(id)json;
+
+ (BOOL)BOOL:(id)json;
+ (double)double:(id)json;
+ (float)float:(id)json;
@@ -52,7 +54,6 @@
+ (NSWritingDirection)NSWritingDirection:(id)json;
+ (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json;
+ (UITextFieldViewMode)UITextFieldViewMode:(id)json;
-+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json;
+ (UIKeyboardType)UIKeyboardType:(id)json;
+ (UIReturnKeyType)UIReturnKeyType:(id)json;
diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m
index 3c9143c17..3bdf59753 100644
--- a/React/Base/RCTConvert.m
+++ b/React/Base/RCTConvert.m
@@ -21,6 +21,8 @@ void RCTLogConvertError(id json, const char *type)
json, [json classForCoder], type);
}
+RCT_CONVERTER(id, id, self)
+
RCT_CONVERTER(BOOL, BOOL, boolValue)
RCT_NUMBER_CONVERTER(double, doubleValue)
RCT_NUMBER_CONVERTER(float, floatValue)
@@ -219,12 +221,6 @@ RCT_ENUM_CONVERTER(UITextFieldViewMode, (@{
@"always": @(UITextFieldViewModeAlways),
}), UITextFieldViewModeNever, integerValue)
-RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
- @"none": @(UIScrollViewKeyboardDismissModeNone),
- @"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag),
- @"interactive": @(UIScrollViewKeyboardDismissModeInteractive),
-}), UIScrollViewKeyboardDismissModeNone, integerValue)
-
RCT_ENUM_CONVERTER(UIKeyboardType, (@{
@"default": @(UIKeyboardTypeDefault),
@"ascii-capable": @(UIKeyboardTypeASCIICapable),
@@ -237,6 +233,8 @@ RCT_ENUM_CONVERTER(UIKeyboardType, (@{
@"decimal-pad": @(UIKeyboardTypeDecimalPad),
@"twitter": @(UIKeyboardTypeTwitter),
@"web-search": @(UIKeyboardTypeWebSearch),
+ // Added for Android compatibility
+ @"numeric": @(UIKeyboardTypeDecimalPad),
}), UIKeyboardTypeDefault, integerValue)
RCT_ENUM_CONVERTER(UIReturnKeyType, (@{
@@ -267,7 +265,11 @@ RCT_ENUM_CONVERTER(UIViewContentMode, (@{
@"top-right": @(UIViewContentModeTopRight),
@"bottom-left": @(UIViewContentModeBottomLeft),
@"bottom-right": @(UIViewContentModeBottomRight),
-}), UIViewContentModeScaleToFill, integerValue)
+ // Cross-platform values
+ @"cover": @(UIViewContentModeScaleAspectFill),
+ @"contain": @(UIViewContentModeScaleAspectFit),
+ @"stretch": @(UIViewContentModeScaleToFill),
+}), UIViewContentModeScaleAspectFill, integerValue)
RCT_ENUM_CONVERTER(UIBarStyle, (@{
@"default": @(UIBarStyleDefault),
diff --git a/React/Base/RCTDevMenu.h b/React/Base/RCTDevMenu.h
index b260fca4a..ed1ff90b8 100644
--- a/React/Base/RCTDevMenu.h
+++ b/React/Base/RCTDevMenu.h
@@ -51,6 +51,12 @@
*/
- (void)reload;
+/**
+ * Add custom item to the development menu. The handler will be called
+ * when user selects the item.
+ */
+- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler;
+
@end
/**
diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m
index 2416e974c..8a5f23f9d 100644
--- a/React/Base/RCTDevMenu.m
+++ b/React/Base/RCTDevMenu.m
@@ -43,6 +43,28 @@ static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu";
@end
+@interface RCTDevMenuItem : NSObject
+
+@property (nonatomic, copy) NSString *title;
+@property (nonatomic, copy) dispatch_block_t handler;
+
+- (instancetype)initWithTitle:(NSString *)title handler:(dispatch_block_t)handler;
+
+@end
+
+@implementation RCTDevMenuItem
+
+- (instancetype)initWithTitle:(NSString *)title handler:(dispatch_block_t)handler
+{
+ if (self = [super init]) {
+ self.title = title;
+ self.handler = handler;
+ }
+ return self;
+}
+
+@end
+
@interface RCTDevMenu ()
@property (nonatomic, strong) Class executorClass;
@@ -57,6 +79,8 @@ static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu";
NSURLSessionDataTask *_updateTask;
NSURL *_liveReloadURL;
BOOL _jsLoaded;
+ NSArray *_presentedItems;
+ NSMutableArray *_extraMenuItems;
}
@synthesize bridge = _bridge;
@@ -94,6 +118,7 @@ RCT_EXPORT_MODULE()
_defaults = [NSUserDefaults standardUserDefaults];
_settings = [[NSMutableDictionary alloc] init];
+ _extraMenuItems = [NSMutableArray array];
// Delay setup until after Bridge init
[self settingsDidChange];
@@ -110,6 +135,13 @@ RCT_EXPORT_MODULE()
[weakSelf toggle];
}];
+ // Toggle element inspector
+ [commands registerKeyCommandWithInput:@"i"
+ modifierFlags:UIKeyModifierCommand
+ action:^(UIKeyCommand *command) {
+ [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
+ }];
+
// Reload in normal mode
[commands registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand
@@ -225,32 +257,82 @@ RCT_EXPORT_MODULE()
}
}
+- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler
+{
+ [_extraMenuItems addObject:[[RCTDevMenuItem alloc] initWithTitle:title handler:handler]];
+}
+
+- (NSArray *)menuItems
+{
+ NSMutableArray *items = [NSMutableArray array];
+
+ [items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Reload" handler:^{
+ [self reload];
+ }]];
+
+ Class chromeExecutorClass = NSClassFromString(@"RCTWebSocketExecutor");
+ if (!chromeExecutorClass) {
+ [items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Chrome Debugger Unavailable" handler:^{
+ [[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable"
+ message:@"You need to include the RCTWebSocket library to enable Chrome debugging"
+ delegate:nil
+ cancelButtonTitle:@"OK"
+ otherButtonTitles:nil] show];
+ }]];
+ } else {
+ BOOL isDebuggingInChrome = _executorClass && _executorClass == chromeExecutorClass;
+ NSString *debugTitleChrome = isDebuggingInChrome ? @"Disable Chrome Debugging" : @"Debug in Chrome";
+ [items addObject:[[RCTDevMenuItem alloc] initWithTitle:debugTitleChrome handler:^{
+ self.executorClass = isDebuggingInChrome ? Nil : chromeExecutorClass;
+ }]];
+ }
+
+ Class safariExecutorClass = NSClassFromString(@"RCTWebViewExecutor");
+ BOOL isDebuggingInSafari = _executorClass && _executorClass == safariExecutorClass;
+ NSString *debugTitleSafari = isDebuggingInSafari ? @"Disable Safari Debugging" : @"Debug in Safari";
+ [items addObject:[[RCTDevMenuItem alloc] initWithTitle:debugTitleSafari handler:^{
+ self.executorClass = isDebuggingInSafari ? Nil : safariExecutorClass;
+ }]];
+
+ NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor";
+ [items addObject:[[RCTDevMenuItem alloc] initWithTitle:fpsMonitor handler:^{
+ self.showFPS = !_showFPS;
+ }]];
+
+ [items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Inspect Element" handler:^{
+ [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
+ }]];
+
+ if (_liveReloadURL) {
+ NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
+ [items addObject:[[RCTDevMenuItem alloc] initWithTitle:liveReloadTitle handler:^{
+ self.liveReloadEnabled = !_liveReloadEnabled;
+ }]];
+
+ NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
+ [items addObject:[[RCTDevMenuItem alloc] initWithTitle:profilingTitle handler:^{
+ self.profilingEnabled = !_profilingEnabled;
+ }]];
+ }
+
+ [items addObjectsFromArray:_extraMenuItems];
+
+ return items;
+}
+
RCT_EXPORT_METHOD(show)
{
if (_actionSheet || !_bridge) {
return;
}
- NSString *debugTitleChrome = _executorClass && _executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Debug in Chrome";
- NSString *debugTitleSafari = _executorClass && _executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Debug in Safari";
- NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor";
+ UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
+ actionSheet.title = @"React Native: Development";
+ actionSheet.delegate = self;
- UIActionSheet *actionSheet =
- [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
- delegate:self
- cancelButtonTitle:nil
- destructiveButtonTitle:nil
- otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil];
-
- [actionSheet addButtonWithTitle:@"Inspect Element"];
-
- if (_liveReloadURL) {
-
- NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
- NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
-
- [actionSheet addButtonWithTitle:liveReloadTitle];
- [actionSheet addButtonWithTitle:profilingTitle];
+ NSArray *items = [self menuItems];
+ for (RCTDevMenuItem *item in items) {
+ [actionSheet addButtonWithTitle:item.title];
}
[actionSheet addButtonWithTitle:@"Cancel"];
@@ -259,13 +341,7 @@ RCT_EXPORT_METHOD(show)
actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
_actionSheet = actionSheet;
-}
-
-RCT_EXPORT_METHOD(reload)
-{
- _jsLoaded = NO;
- _liveReloadURL = nil;
- [_bridge reload];
+ _presentedItems = items;
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
@@ -275,48 +351,16 @@ RCT_EXPORT_METHOD(reload)
return;
}
- switch (buttonIndex) {
- case 0: {
- [self reload];
- break;
- }
- case 1: {
- Class cls = NSClassFromString(@"RCTWebSocketExecutor");
- if (!cls) {
- [[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable"
- message:@"You need to include the RCTWebSocket library to enable Chrome debugging"
- delegate:nil
- cancelButtonTitle:@"OK"
- otherButtonTitles:nil] show];
- return;
- }
- self.executorClass = (_executorClass == cls) ? Nil : cls;
- break;
- }
- case 2: {
- Class cls = NSClassFromString(@"RCTWebViewExecutor");
- self.executorClass = (_executorClass == cls) ? Nil : cls;
- break;
- }
- case 3: {
- self.showFPS = !_showFPS;
- break;
- }
- case 4: {
- [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
- break;
- }
- case 5: {
- self.liveReloadEnabled = !_liveReloadEnabled;
- break;
- }
- case 6: {
- self.profilingEnabled = !_profilingEnabled;
- break;
- }
- default:
- break;
- }
+ RCTDevMenuItem *item = _presentedItems[buttonIndex];
+ item.handler();
+ return;
+}
+
+RCT_EXPORT_METHOD(reload)
+{
+ _jsLoaded = NO;
+ _liveReloadURL = nil;
+ [_bridge reload];
}
- (void)setShakeToShow:(BOOL)shakeToShow
@@ -438,6 +482,7 @@ RCT_EXPORT_METHOD(reload)
- (void)show {}
- (void)reload {}
+- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler {}
@end
diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h
index f722b0a02..2718871d2 100644
--- a/React/Base/RCTProfile.h
+++ b/React/Base/RCTProfile.h
@@ -25,6 +25,8 @@ NSString *const RCTProfileDidEndProfiling;
#if RCT_DEV
+@class RCTBridge;
+
#define RCTProfileBeginFlowEvent() \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
@@ -45,14 +47,14 @@ RCT_EXTERN BOOL RCTProfileIsProfiling(void);
/**
* Start collecting profiling information
*/
-RCT_EXTERN void RCTProfileInit(void);
+RCT_EXTERN void RCTProfileInit(RCTBridge *);
/**
* Stop profiling and return a JSON string of the collected data - The data
* returned is compliant with google's trace event format - the format used
* as input to trace-viewer
*/
-RCT_EXTERN NSString *RCTProfileEnd(void);
+RCT_EXTERN NSString *RCTProfileEnd(RCTBridge *);
/**
* Collects the initial event information for the event and returns a reference ID
diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m
index 09989d1cb..1269d7594 100644
--- a/React/Base/RCTProfile.m
+++ b/React/Base/RCTProfile.m
@@ -10,10 +10,13 @@
#import "RCTProfile.h"
#import
+#import
+#import
#import
#import "RCTAssert.h"
+#import "RCTBridge.h"
#import "RCTDefines.h"
#import "RCTUtils.h"
@@ -32,6 +35,7 @@ NSDictionary *RCTProfileGetMemoryUsage(void);
NSString const *RCTProfileTraceEvents = @"traceEvents";
NSString const *RCTProfileSamples = @"samples";
+NSString *const RCTProfilePrefix = @"rct_profile_";
#pragma mark - Variables
@@ -92,6 +96,111 @@ NSDictionary *RCTProfileGetMemoryUsage(void)
}
}
+#pragma mark - Module hooks
+
+@interface RCTBridge (Private)
+
+- (void)dispatchBlock:(dispatch_block_t)block forModule:(id)module;
+
+@end
+
+static const char *RCTProfileProxyClassName(Class);
+static const char *RCTProfileProxyClassName(Class class)
+{
+ return [RCTProfilePrefix stringByAppendingString:NSStringFromClass(class)].UTF8String;
+}
+
+static SEL RCTProfileProxySelector(SEL);
+static SEL RCTProfileProxySelector(SEL selector)
+{
+ NSString *selectorName = NSStringFromSelector(selector);
+ return NSSelectorFromString([RCTProfilePrefix stringByAppendingString:selectorName]);
+}
+
+static void RCTProfileForwardInvocation(NSObject *, SEL, NSInvocation *);
+static void RCTProfileForwardInvocation(NSObject *self, SEL cmd, NSInvocation *invocation)
+{
+ NSString *name = [NSString stringWithFormat:@"-[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(invocation.selector)];
+ SEL newSel = RCTProfileProxySelector(invocation.selector);
+
+ if ([object_getClass(self) instancesRespondToSelector:newSel]) {
+ invocation.selector = newSel;
+ RCTProfileBeginEvent();
+ [invocation invoke];
+ RCTProfileEndEvent(name, @"objc_call,modules,auto", nil);
+ } else {
+ // Use original selector to don't change error message
+ [self doesNotRecognizeSelector:invocation.selector];
+ }
+}
+
+static IMP RCTProfileMsgForward(NSObject *, SEL);
+static IMP RCTProfileMsgForward(NSObject *self, SEL selector)
+{
+ IMP imp = _objc_msgForward;
+#if !defined(__arm64__)
+ NSMethodSignature *signature = [self methodSignatureForSelector:selector];
+ if (signature.methodReturnType[0] == _C_STRUCT_B && signature.methodReturnLength > 8) {
+ imp = _objc_msgForward_stret;
+ }
+#endif
+ return imp;
+}
+
+static void RCTProfileHookModules(RCTBridge *);
+static void RCTProfileHookModules(RCTBridge *bridge)
+{
+ [bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id module, BOOL *stop) {
+ [bridge dispatchBlock:^{
+ Class moduleClass = object_getClass(module);
+ Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
+
+ unsigned int methodCount;
+ Method *methods = class_copyMethodList(moduleClass, &methodCount);
+ for (NSUInteger i = 0; i < methodCount; i++) {
+ Method method = methods[i];
+ SEL selector = method_getName(method);
+ if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector]) {
+ continue;
+ }
+ IMP originalIMP = method_getImplementation(method);
+ const char *returnType = method_getTypeEncoding(method);
+ class_addMethod(proxyClass, selector, RCTProfileMsgForward(module, selector), returnType);
+ class_addMethod(proxyClass, RCTProfileProxySelector(selector), originalIMP, returnType);
+ }
+ free(methods);
+
+ for (Class cls in @[proxyClass, object_getClass(proxyClass)]) {
+ Method oldImp = class_getInstanceMethod(cls, @selector(class));
+ class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp));
+ }
+
+ IMP originalFwd = class_replaceMethod(moduleClass, @selector(forwardInvocation:), (IMP)RCTProfileForwardInvocation, "v@:@");
+ if (originalFwd != NULL) {
+ class_addMethod(proxyClass, RCTProfileProxySelector(@selector(forwardInvocation:)), originalFwd, "v@:@");
+ }
+
+ objc_registerClassPair(proxyClass);
+ object_setClass(module, proxyClass);
+ } forModule:module];
+ }];
+}
+
+void RCTProfileUnhookModules(RCTBridge *);
+void RCTProfileUnhookModules(RCTBridge *bridge)
+{
+ [bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id module, BOOL *stop) {
+ [bridge dispatchBlock:^{
+ Class proxyClass = object_getClass(module);
+ if (module.class != proxyClass) {
+ object_setClass(module, module.class);
+ objc_disposeClassPair(proxyClass);
+ }
+ } forModule:module];
+ }];
+}
+
+
#pragma mark - Public Functions
BOOL RCTProfileIsProfiling(void)
@@ -102,8 +211,10 @@ BOOL RCTProfileIsProfiling(void)
return profiling;
}
-void RCTProfileInit(void)
+void RCTProfileInit(RCTBridge *bridge)
{
+ RCTProfileHookModules(bridge);
+
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_RCTProfileLock = [[NSLock alloc] init];
@@ -121,7 +232,7 @@ void RCTProfileInit(void)
object:nil];
}
-NSString *RCTProfileEnd(void)
+NSString *RCTProfileEnd(RCTBridge *bridge)
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling
object:nil];
@@ -132,6 +243,9 @@ NSString *RCTProfileEnd(void)
RCTProfileInfo = nil;
RCTProfileOngoingEvents = nil;
);
+
+ RCTProfileUnhookModules(bridge);
+
return log;
}
diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m
index 2d35dcd6f..6330454ab 100644
--- a/React/Base/RCTRedBox.m
+++ b/React/Base/RCTRedBox.m
@@ -85,11 +85,6 @@
selector:@selector(dismiss)
name:RCTReloadNotification
object:nil];
-
- [notificationCenter addObserver:self
- selector:@selector(dismiss)
- name:RCTJavaScriptDidLoadNotification
- object:nil];
}
return self;
}
diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m
index f95f134c3..0a2ca61a7 100644
--- a/React/Base/RCTTouchHandler.m
+++ b/React/Base/RCTTouchHandler.m
@@ -36,8 +36,6 @@
BOOL _recordingInteractionTiming;
CFTimeInterval _mostRecentEnqueueJS;
- NSMutableArray *_pendingTouches;
- NSMutableArray *_bridgeInteractionTiming;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@@ -52,9 +50,6 @@
_reactTouches = [[NSMutableArray alloc] init];
_touchViews = [[NSMutableArray alloc] init];
- _pendingTouches = [[NSMutableArray alloc] init];
- _bridgeInteractionTiming = [[NSMutableArray alloc] init];
-
// `cancelsTouchesInView` is needed in order to be used as a top level
// event delegated recognizer. Otherwise, lower-level components not built
// using RCT, will fail to recognize gestures.
@@ -94,11 +89,11 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
return;
}
- // Get new, unique touch id
+ // Get new, unique touch identifier for the react touch
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
- NSInteger touchID = ([_reactTouches.lastObject[@"target"] integerValue] + 1) % RCTMaxTouches;
+ NSInteger touchID = ([_reactTouches.lastObject[@"identifier"] integerValue] + 1) % RCTMaxTouches;
for (NSDictionary *reactTouch in _reactTouches) {
- NSInteger usedID = [reactTouch[@"target"] integerValue];
+ NSInteger usedID = [reactTouch[@"identifier"] integerValue];
if (usedID == touchID) {
// ID has already been used, try next value
touchID ++;
diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h
index 5c34d0e0a..641500b38 100644
--- a/React/Base/RCTUtils.h
+++ b/React/Base/RCTUtils.h
@@ -18,6 +18,8 @@
// Utility functions for JSON object <-> string serialization/deserialization
RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error);
RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error);
+RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error);
+RCT_EXTERN id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options);
// Strip non JSON-safe values from an object graph
RCT_EXTERN id RCTJSONClean(id object);
diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m
index 712e9724e..613b13163 100644
--- a/React/Base/RCTUtils.m
+++ b/React/Base/RCTUtils.m
@@ -24,7 +24,7 @@ NSString *RCTJSONStringify(id jsonObject, NSError **error)
return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
}
-id RCTJSONParse(NSString *jsonString, NSError **error)
+id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options)
{
if (!jsonString) {
return nil;
@@ -39,7 +39,15 @@ id RCTJSONParse(NSString *jsonString, NSError **error)
return nil;
}
}
- return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error];
+ return [NSJSONSerialization JSONObjectWithData:jsonData options:options error:error];
+}
+
+id RCTJSONParse(NSString *jsonString, NSError **error) {
+ return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingAllowFragments);
+}
+
+id RCTJSONParseMutable(NSString *jsonString, NSError **error) {
+ return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves);
}
id RCTJSONClean(id object)
diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m
index 2c01161d4..76f7fa885 100644
--- a/React/Modules/RCTAsyncLocalStorage.m
+++ b/React/Modules/RCTAsyncLocalStorage.m
@@ -61,6 +61,34 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut
return nil;
}
+// Only merges objects - all other types are just clobbered (including arrays)
+static void RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source)
+{
+ for (NSString *key in source) {
+ id sourceValue = source[key];
+ if ([sourceValue isKindOfClass:[NSDictionary class]]) {
+ id destinationValue = destination[key];
+ NSMutableDictionary *nestedDestination;
+ if ([destinationValue classForCoder] == [NSMutableDictionary class]) {
+ nestedDestination = destinationValue;
+ } else {
+ if ([destinationValue isKindOfClass:[NSDictionary class]]) {
+ // Ideally we wouldn't eagerly copy here...
+ nestedDestination = [destinationValue mutableCopy];
+ } else {
+ destination[key] = [sourceValue copy];
+ }
+ }
+ if (nestedDestination) {
+ RCTMergeRecursive(nestedDestination, sourceValue);
+ destination[key] = nestedDestination;
+ }
+ } else {
+ destination[key] = sourceValue;
+ }
+ }
+}
+
#pragma mark - RCTAsyncLocalStorage
@implementation RCTAsyncLocalStorage
@@ -135,13 +163,19 @@ RCT_EXPORT_MODULE()
if (errorOut) {
return errorOut;
}
+ id value = [self _getValueForKey:key errorOut:&errorOut];
+ [result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure.
+ return errorOut;
+}
+
+- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut
+{
id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value.
if (value == [NSNull null]) {
NSString *filePath = [self _filePathForKey:key];
- value = RCTReadFile(filePath, key, &errorOut);
+ value = RCTReadFile(filePath, key, errorOut);
}
- [result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure.
- return errorOut;
+ return value;
}
- (id)_writeEntry:(NSArray *)entry
@@ -198,7 +232,6 @@ RCT_EXPORT_METHOD(multiGet:(NSArray *)keys
id keyError = [self _appendItemForKey:key toArray:result];
RCTAppendError(keyError, &errors);
}
- [self _writeManifest:&errors];
callback(@[errors ?: [NSNull null], result]);
}
@@ -221,6 +254,38 @@ RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs
}
}
+RCT_EXPORT_METHOD(multiMerge:(NSArray *)kvPairs
+ callback:(RCTResponseSenderBlock)callback)
+{
+ id errorOut = [self _ensureSetup];
+ if (errorOut) {
+ callback(@[@[errorOut]]);
+ return;
+ }
+ NSMutableArray *errors;
+ for (__strong NSArray *entry in kvPairs) {
+ id keyError;
+ NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError];
+ if (keyError) {
+ RCTAppendError(keyError, &errors);
+ } else {
+ if (value) {
+ NSMutableDictionary *mergedVal = [RCTJSONParseMutable(value, &keyError) mutableCopy];
+ RCTMergeRecursive(mergedVal, RCTJSONParse(entry[1], &keyError));
+ entry = @[entry[0], RCTJSONStringify(mergedVal, &keyError)];
+ }
+ if (!keyError) {
+ keyError = [self _writeEntry:entry];
+ }
+ RCTAppendError(keyError, &errors);
+ }
+ }
+ [self _writeManifest:&errors];
+ if (callback) {
+ callback(@[errors ?: [NSNull null]]);
+ }
+}
+
RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys
callback:(RCTResponseSenderBlock)callback)
{
diff --git a/React/Modules/RCTStatusBarManager.h b/React/Modules/RCTStatusBarManager.h
index 40feee5c0..aee9b8642 100644
--- a/React/Modules/RCTStatusBarManager.h
+++ b/React/Modules/RCTStatusBarManager.h
@@ -10,6 +10,14 @@
#import
#import "RCTBridgeModule.h"
+#import "RCTConvert.h"
+
+@interface RCTConvert (UIStatusBar)
+
++ (UIStatusBarStyle)UIStatusBarStyle:(id)json;
++ (UIStatusBarAnimation)UIStatusBarAnimation:(id)json;
+
+@end
@interface RCTStatusBarManager : NSObject
diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m
index 04bb39038..cb9ddfe69 100644
--- a/React/Modules/RCTStatusBarManager.m
+++ b/React/Modules/RCTStatusBarManager.m
@@ -11,6 +11,21 @@
#import "RCTLog.h"
+@implementation RCTConvert (UIStatusBar)
+
+RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{
+ @"default": @(UIStatusBarStyleDefault),
+ @"light-content": @(UIStatusBarStyleLightContent),
+}), UIStatusBarStyleDefault, integerValue);
+
+RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{
+ @"none": @(UIStatusBarAnimationNone),
+ @"fade": @(UIStatusBarAnimationFade),
+ @"slide": @(UIStatusBarAnimationSlide),
+}), UIStatusBarAnimationNone, integerValue);
+
+@end
+
@implementation RCTStatusBarManager
static BOOL RCTViewControllerBasedStatusBarAppearance()
@@ -18,7 +33,8 @@ static BOOL RCTViewControllerBasedStatusBarAppearance()
static BOOL value;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
- value = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue];
+ value = [[[NSBundle mainBundle] objectForInfoDictionaryKey:
+ @"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue];
});
return value;
@@ -55,19 +71,4 @@ RCT_EXPORT_METHOD(setHidden:(BOOL)hidden
}
}
-- (NSDictionary *)constantsToExport
-{
- return @{
- @"Style": @{
- @"default": @(UIStatusBarStyleDefault),
- @"lightContent": @(UIStatusBarStyleLightContent),
- },
- @"Animation": @{
- @"none": @(UIStatusBarAnimationNone),
- @"fade": @(UIStatusBarAnimationFade),
- @"slide": @(UIStatusBarAnimationSlide),
- },
- };
-}
-
@end
diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m
index cc580e903..570bdfef7 100644
--- a/React/Modules/RCTUIManager.m
+++ b/React/Modules/RCTUIManager.m
@@ -1397,11 +1397,6 @@ RCT_EXPORT_METHOD(clearJSResponder)
NSMutableDictionary *allJSConstants = [@{
@"customBubblingEventTypes": [self customBubblingEventTypes],
@"customDirectEventTypes": [self customDirectEventTypes],
- @"NSTextAlignment": @{
- @"Left": @(NSTextAlignmentLeft),
- @"Center": @(NSTextAlignmentCenter),
- @"Right": @(NSTextAlignmentRight),
- },
@"Dimensions": @{
@"window": @{
@"width": @(RCTScreenSize().width),
@@ -1413,73 +1408,6 @@ RCT_EXPORT_METHOD(clearJSResponder)
@"height": @(RCTScreenSize().height),
},
},
- @"StyleConstants": @{
- @"PointerEventsValues": @{
- @"none": @(RCTPointerEventsNone),
- @"box-none": @(RCTPointerEventsBoxNone),
- @"box-only": @(RCTPointerEventsBoxOnly),
- @"auto": @(RCTPointerEventsUnspecified),
- },
- },
- @"UIText": @{
- @"AutocapitalizationType": @{
- @"characters": @(UITextAutocapitalizationTypeAllCharacters),
- @"sentences": @(UITextAutocapitalizationTypeSentences),
- @"words": @(UITextAutocapitalizationTypeWords),
- @"none": @(UITextAutocapitalizationTypeNone),
- },
- },
- @"UITextField": @{
- @"clearButtonMode": @{
- @"never": @(UITextFieldViewModeNever),
- @"while-editing": @(UITextFieldViewModeWhileEditing),
- @"unless-editing": @(UITextFieldViewModeUnlessEditing),
- @"always": @(UITextFieldViewModeAlways),
- },
- },
- @"UIKeyboardType": @{
- @"default": @(UIKeyboardTypeDefault),
- @"ascii-capable": @(UIKeyboardTypeASCIICapable),
- @"numbers-and-punctuation": @(UIKeyboardTypeNumbersAndPunctuation),
- @"url": @(UIKeyboardTypeURL),
- @"number-pad": @(UIKeyboardTypeNumberPad),
- @"phone-pad": @(UIKeyboardTypePhonePad),
- @"name-phone-pad": @(UIKeyboardTypeNamePhonePad),
- @"decimal-pad": @(UIKeyboardTypeDecimalPad),
- @"email-address": @(UIKeyboardTypeEmailAddress),
- @"twitter": @(UIKeyboardTypeTwitter),
- @"web-search": @(UIKeyboardTypeWebSearch),
- },
- @"UIReturnKeyType": @{
- @"default": @(UIReturnKeyDefault),
- @"go": @(UIReturnKeyGo),
- @"google": @(UIReturnKeyGoogle),
- @"join": @(UIReturnKeyJoin),
- @"next": @(UIReturnKeyNext),
- @"route": @(UIReturnKeyRoute),
- @"search": @(UIReturnKeySearch),
- @"send": @(UIReturnKeySend),
- @"yahoo": @(UIReturnKeyYahoo),
- @"done": @(UIReturnKeyDone),
- @"emergency-call": @(UIReturnKeyEmergencyCall),
- },
- @"UIView": @{
- @"ContentMode": @{
- @"ScaleToFill": @(UIViewContentModeScaleToFill),
- @"ScaleAspectFit": @(UIViewContentModeScaleAspectFit),
- @"ScaleAspectFill": @(UIViewContentModeScaleAspectFill),
- @"Redraw": @(UIViewContentModeRedraw),
- @"Center": @(UIViewContentModeCenter),
- @"Top": @(UIViewContentModeTop),
- @"Bottom": @(UIViewContentModeBottom),
- @"Left": @(UIViewContentModeLeft),
- @"Right": @(UIViewContentModeRight),
- @"TopLeft": @(UIViewContentModeTopLeft),
- @"TopRight": @(UIViewContentModeTopRight),
- @"BottomLeft": @(UIViewContentModeBottomLeft),
- @"BottomRight": @(UIViewContentModeBottomRight),
- },
- },
} mutableCopy];
[_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTViewManager *manager, BOOL *stop) {
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index c7309989b..3c0cfe722 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -16,6 +16,7 @@
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; };
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; };
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */; };
+ 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */; };
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */; };
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7091AB030C200659ED6 /* RCTAppState.m */; };
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E01AA5CF210034F82E /* RCTTabBar.m */; };
@@ -104,6 +105,8 @@
134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = ""; };
134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewExecutor.h; sourceTree = ""; };
134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewExecutor.m; sourceTree = ""; };
+ 13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTProgressViewManager.h; sourceTree = ""; };
+ 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTProgressViewManager.m; sourceTree = ""; };
13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStatusBarManager.h; sourceTree = ""; };
13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStatusBarManager.m; sourceTree = ""; };
1372B7081AB030C200659ED6 /* RCTAppState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAppState.h; sourceTree = ""; };
@@ -307,6 +310,8 @@
58114A141AAE854800E7D092 /* RCTPickerManager.h */,
58114A151AAE854800E7D092 /* RCTPickerManager.m */,
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */,
+ 13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */,
+ 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */,
131B6AF01AF1093D00FFC3E0 /* RCTSegmentedControl.h */,
131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */,
131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */,
@@ -524,6 +529,7 @@
58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */,
832348161A77A5AA00B55238 /* Layout.c in Sources */,
14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */,
+ 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */,
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */,
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */,
13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */,
diff --git a/React/Views/RCTDatePickerManager.h b/React/Views/RCTDatePickerManager.h
index eb424085d..73d88b6dd 100644
--- a/React/Views/RCTDatePickerManager.h
+++ b/React/Views/RCTDatePickerManager.h
@@ -8,6 +8,13 @@
*/
#import "RCTViewManager.h"
+#import "RCTConvert.h"
+
+@interface RCTConvert(UIDatePicker)
+
++ (UIDatePickerMode)UIDatePickerMode:(id)json;
+
+@end
@interface RCTDatePickerManager : RCTViewManager
diff --git a/React/Views/RCTDatePickerManager.m b/React/Views/RCTDatePickerManager.m
index 36397d6e5..d8bbde703 100644
--- a/React/Views/RCTDatePickerManager.m
+++ b/React/Views/RCTDatePickerManager.m
@@ -10,7 +10,6 @@
#import "RCTDatePickerManager.h"
#import "RCTBridge.h"
-#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "UIView+React.h"
@@ -20,7 +19,7 @@ RCT_ENUM_CONVERTER(UIDatePickerMode, (@{
@"time": @(UIDatePickerModeTime),
@"date": @(UIDatePickerModeDate),
@"datetime": @(UIDatePickerModeDateAndTime),
- //@"countdown": @(UIDatePickerModeCountDownTimer) // not supported yet
+ @"countdown": @(UIDatePickerModeCountDownTimer), // not supported yet
}), UIDatePickerModeTime, integerValue)
@end
@@ -31,9 +30,12 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
+ // TODO: we crash here if the RCTDatePickerManager is released
+ // while the UIDatePicker is still sending onChange events. To
+ // fix this we should maybe subclass UIDatePicker and make it
+ // be its own event target.
UIDatePicker *picker = [[UIDatePicker alloc] init];
- [picker addTarget:self
- action:@selector(onChange:)
+ [picker addTarget:self action:@selector(onChange:)
forControlEvents:UIControlEventValueChanged];
return picker;
}
@@ -56,17 +58,10 @@ RCT_REMAP_VIEW_PROPERTY(timeZoneOffsetInMinutes, timeZone, NSTimeZone)
- (NSDictionary *)constantsToExport
{
- UIDatePicker *dp = [[UIDatePicker alloc] init];
- [dp layoutIfNeeded];
-
+ UIDatePicker *view = [[UIDatePicker alloc] init];
return @{
- @"ComponentHeight": @(CGRectGetHeight(dp.frame)),
- @"ComponentWidth": @(CGRectGetWidth(dp.frame)),
- @"DatePickerModes": @{
- @"time": @(UIDatePickerModeTime),
- @"date": @(UIDatePickerModeDate),
- @"datetime": @(UIDatePickerModeDateAndTime),
- }
+ @"ComponentHeight": @(view.intrinsicContentSize.height),
+ @"ComponentWidth": @(view.intrinsicContentSize.width),
};
}
diff --git a/React/Views/RCTPickerManager.m b/React/Views/RCTPickerManager.m
index 3bbc60b94..de6c1f916 100644
--- a/React/Views/RCTPickerManager.m
+++ b/React/Views/RCTPickerManager.m
@@ -27,10 +27,10 @@ RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
- (NSDictionary *)constantsToExport
{
- RCTPicker *pv = [[RCTPicker alloc] init];
+ RCTPicker *view = [[RCTPicker alloc] init];
return @{
- @"ComponentHeight": @(CGRectGetHeight(pv.frame)),
- @"ComponentWidth": @(CGRectGetWidth(pv.frame))
+ @"ComponentHeight": @(view.intrinsicContentSize.height),
+ @"ComponentWidth": @(view.intrinsicContentSize.width)
};
}
diff --git a/React/Views/RCTProgressViewManager.h b/React/Views/RCTProgressViewManager.h
new file mode 100644
index 000000000..ae8a6a388
--- /dev/null
+++ b/React/Views/RCTProgressViewManager.h
@@ -0,0 +1,14 @@
+/**
+ * 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.
+ */
+
+#import "RCTViewManager.h"
+
+@interface RCTProgressViewManager : RCTViewManager
+
+@end
diff --git a/React/Views/RCTProgressViewManager.m b/React/Views/RCTProgressViewManager.m
new file mode 100644
index 000000000..deb6285a6
--- /dev/null
+++ b/React/Views/RCTProgressViewManager.m
@@ -0,0 +1,47 @@
+/**
+ * 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.
+ */
+
+#import "RCTProgressViewManager.h"
+
+#import "RCTConvert.h"
+
+@implementation RCTConvert (RCTProgressViewManager)
+
+RCT_ENUM_CONVERTER(UIProgressViewStyle, (@{
+ @"default": @(UIProgressViewStyleDefault),
+ @"bar": @(UIProgressViewStyleBar),
+}), UIProgressViewStyleDefault, integerValue)
+
+@end
+
+@implementation RCTProgressViewManager
+
+RCT_EXPORT_MODULE()
+
+- (UIView *)view
+{
+ return [[UIProgressView alloc] init];
+}
+
+RCT_EXPORT_VIEW_PROPERTY(progressViewStyle, UIProgressViewStyle)
+RCT_EXPORT_VIEW_PROPERTY(progress, float)
+RCT_EXPORT_VIEW_PROPERTY(progressTintColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(trackTintColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(progressImage, UIImage)
+RCT_EXPORT_VIEW_PROPERTY(trackImage, UIImage)
+
+- (NSDictionary *)constantsToExport
+{
+ UIProgressView *view = [[UIProgressView alloc] init];
+ return @{
+ @"ComponentHeight": @(view.intrinsicContentSize.height),
+ };
+}
+
+@end
diff --git a/React/Views/RCTScrollViewManager.h b/React/Views/RCTScrollViewManager.h
index 9fec3422d..83b3126e8 100644
--- a/React/Views/RCTScrollViewManager.h
+++ b/React/Views/RCTScrollViewManager.h
@@ -8,6 +8,13 @@
*/
#import "RCTViewManager.h"
+#import "RCTConvert.h"
+
+@interface RCTConvert (UIScrollView)
+
++ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json;
+
+@end
@interface RCTScrollViewManager : RCTViewManager
diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m
index 8441de74d..15803df1e 100644
--- a/React/Views/RCTScrollViewManager.m
+++ b/React/Views/RCTScrollViewManager.m
@@ -10,11 +10,22 @@
#import "RCTScrollViewManager.h"
#import "RCTBridge.h"
-#import "RCTConvert.h"
#import "RCTScrollView.h"
#import "RCTSparseArray.h"
#import "RCTUIManager.h"
+@implementation RCTConvert (UIScrollView)
+
+RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
+ @"none": @(UIScrollViewKeyboardDismissModeNone),
+ @"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag),
+ @"interactive": @(UIScrollViewKeyboardDismissModeInteractive),
+ // Backwards compatibility
+ @"onDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
+}), UIScrollViewKeyboardDismissModeNone, integerValue)
+
+@end
+
@implementation RCTScrollViewManager
RCT_EXPORT_MODULE()
@@ -53,14 +64,10 @@ RCT_DEPRECATED_VIEW_PROPERTY(throttleScrollCallbackMS, scrollEventThrottle)
- (NSDictionary *)constantsToExport
{
return @{
+ // TODO: unused - remove these?
@"DecelerationRate": @{
- @"Normal": @(UIScrollViewDecelerationRateNormal),
- @"Fast": @(UIScrollViewDecelerationRateFast),
- },
- @"KeyboardDismissMode": @{
- @"None": @(UIScrollViewKeyboardDismissModeNone),
- @"Interactive": @(UIScrollViewKeyboardDismissModeInteractive),
- @"OnDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
+ @"normal": @(UIScrollViewDecelerationRateNormal),
+ @"fast": @(UIScrollViewDecelerationRateFast),
},
};
}
diff --git a/React/Views/RCTTextFieldManager.m b/React/Views/RCTTextFieldManager.m
index ff401a719..7b867bd0d 100644
--- a/React/Views/RCTTextFieldManager.m
+++ b/React/Views/RCTTextFieldManager.m
@@ -25,7 +25,7 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
-RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL)
+RCT_REMAP_VIEW_PROPERTY(editable, enabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
@@ -36,6 +36,7 @@ RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType)
RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType)
RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL)
RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL)
+RCT_REMAP_VIEW_PROPERTY(password, secureTextEntry, BOOL) // backwards compatibility
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType)
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)
diff --git a/packager/README.md b/packager/README.md
index 8f9f649bf..c3fae4806 100644
--- a/packager/README.md
+++ b/packager/README.md
@@ -62,7 +62,7 @@ if the option is boolean `1/0` or `true/false` is accepted.
Here are the current options the packager accepts:
* `dev` boolean, defaults to true: sets a global `__DEV__` variable
- which will effect how the React Nativeg core libraries behave.
+ which will effect how the React Native core libraries behave.
* `minify` boolean, defaults to false: whether to minify the bundle.
* `runModule` boolean, defaults to true: whether to require your entry
point module. So if you requested `moduleName`, this option will add