diff --git a/.flowconfig b/.flowconfig
index 75c8ac2ac..88093d65f 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -7,12 +7,19 @@
# Some modules have their own node_modules with overlap
.*/node_modules/node-haste/.*
-# Ignore react-tools where there are overlaps, but don't ignore anything that
-# react-native relies on
-.*/node_modules/react-tools/src/React.js
-.*/node_modules/react-tools/src/renderers/shared/event/EventPropagators.js
-.*/node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js
-.*/node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js
+# Ignore react and fbjs where there are overlaps, but don't ignore
+# anything that react-native relies on
+.*/node_modules/fbjs-haste/.*/__tests__/.*
+.*/node_modules/fbjs-haste/__forks__/Map.js
+.*/node_modules/fbjs-haste/__forks__/Promise.js
+.*/node_modules/fbjs-haste/__forks__/fetch.js
+.*/node_modules/fbjs-haste/core/ExecutionEnvironment.js
+.*/node_modules/fbjs-haste/core/isEmpty.js
+.*/node_modules/fbjs-haste/crypto/crc32.js
+.*/node_modules/fbjs-haste/stubs/ErrorUtils.js
+.*/node_modules/react-haste/React.js
+.*/node_modules/react-haste/renderers/dom/ReactDOM.js
+.*/node_modules/react-haste/renderers/shared/event/eventPlugins/ResponderEventPlugin.js
# Ignore commoner tests
.*/node_modules/commoner/test/.*
@@ -43,9 +50,9 @@ suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FixMe
-suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-7]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
-suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-7]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
[version]
-0.17.0
+0.18.1
diff --git a/.travis.yml b/.travis.yml
index 6efd4fbb2..ea84c8bc8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -82,3 +82,4 @@ env:
branches:
only:
- master
+ - /^.*-stable$/
diff --git a/Examples/2048/2048/AppDelegate.m b/Examples/2048/2048/AppDelegate.m
index b4b1769ff..9975d7df3 100644
--- a/Examples/2048/2048/AppDelegate.m
+++ b/Examples/2048/2048/AppDelegate.m
@@ -56,7 +56,7 @@
launchOptions:launchOptions];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
- UIViewController *rootViewController = [[UIViewController alloc] init];
+ UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
diff --git a/Examples/Movies/Movies/AppDelegate.m b/Examples/Movies/Movies/AppDelegate.m
index 6b8a069fd..d81599fb7 100644
--- a/Examples/Movies/Movies/AppDelegate.m
+++ b/Examples/Movies/Movies/AppDelegate.m
@@ -57,7 +57,7 @@
launchOptions:launchOptions];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
- UIViewController *rootViewController = [[UIViewController alloc] init];
+ UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
diff --git a/Examples/Movies/android/app/src/main/java/com/facebook/react/movies/MoviesActivity.java b/Examples/Movies/android/app/src/main/java/com/facebook/react/movies/MoviesActivity.java
index 6499cea1f..3b2626601 100644
--- a/Examples/Movies/android/app/src/main/java/com/facebook/react/movies/MoviesActivity.java
+++ b/Examples/Movies/android/app/src/main/java/com/facebook/react/movies/MoviesActivity.java
@@ -69,7 +69,7 @@ public class MoviesActivity extends Activity implements DefaultHardwareBackBtnHa
super.onResume();
if (mReactInstanceManager != null) {
- mReactInstanceManager.onResume(this);
+ mReactInstanceManager.onResume(this, this);
}
}
diff --git a/Examples/TicTacToe/TicTacToe/AppDelegate.m b/Examples/TicTacToe/TicTacToe/AppDelegate.m
index f0199b6dd..4cca2217d 100644
--- a/Examples/TicTacToe/TicTacToe/AppDelegate.m
+++ b/Examples/TicTacToe/TicTacToe/AppDelegate.m
@@ -56,7 +56,7 @@
launchOptions:launchOptions];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
- UIViewController *rootViewController = [[UIViewController alloc] init];
+ UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js
index c8527657f..31aedca62 100644
--- a/Examples/UIExplorer/ActionSheetIOSExample.js
+++ b/Examples/UIExplorer/ActionSheetIOSExample.js
@@ -24,9 +24,9 @@ var {
} = React;
var BUTTONS = [
- 'Button Index: 0',
- 'Button Index: 1',
- 'Button Index: 2',
+ 'Option 0',
+ 'Option 1',
+ 'Option 2',
'Destruct',
'Cancel',
];
@@ -47,7 +47,7 @@ var ActionSheetExample = React.createClass({
Click to show the ActionSheet
- Clicked button at index: "{this.state.clicked}"
+ Clicked button: {this.state.clicked}
);
diff --git a/Examples/UIExplorer/AlertIOSExample.js b/Examples/UIExplorer/AlertIOSExample.js
index 2d57fd5fe..e09c883c1 100644
--- a/Examples/UIExplorer/AlertIOSExample.js
+++ b/Examples/UIExplorer/AlertIOSExample.js
@@ -132,7 +132,7 @@ class PromptExample extends React.Component {
+ onPress={this.prompt.bind(this, this.title, null, null, this.promptResponse)}>
@@ -143,7 +143,7 @@ class PromptExample extends React.Component {
+ onPress={this.prompt.bind(this, this.title, null, this.buttons, null)}>
@@ -154,7 +154,7 @@ class PromptExample extends React.Component {
+ onPress={this.prompt.bind(this, this.title, this.defaultValue, null, this.promptResponse)}>
@@ -165,7 +165,7 @@ class PromptExample extends React.Component {
+ onPress={this.prompt.bind(this, this.title, this.defaultValue, this.buttons, null)}>
diff --git a/Examples/UIExplorer/BorderExample.js b/Examples/UIExplorer/BorderExample.js
index 40c8f5992..ff3f73110 100644
--- a/Examples/UIExplorer/BorderExample.js
+++ b/Examples/UIExplorer/BorderExample.js
@@ -91,6 +91,13 @@ var styles = StyleSheet.create({
width: 100,
height: 100
},
+ border8: {
+ width: 60,
+ height: 60,
+ borderColor: 'black',
+ marginRight: 10,
+ backgroundColor: 'lightgrey',
+ },
});
exports.title = 'Border';
@@ -159,4 +166,18 @@ exports.examples = [
);
}
},
+ {
+ title: 'Single Borders',
+ description: 'top, left, bottom right',
+ render() {
+ return (
+
+
+
+
+
+
+ );
+ }
+ },
];
diff --git a/Examples/UIExplorer/ModalExample.js b/Examples/UIExplorer/ModalExample.js
index 3202cb622..eb925b5eb 100644
--- a/Examples/UIExplorer/ModalExample.js
+++ b/Examples/UIExplorer/ModalExample.js
@@ -143,6 +143,7 @@ var styles = StyleSheet.create({
},
innerContainer: {
borderRadius: 10,
+ alignItems: 'center',
},
row: {
alignItems: 'center',
@@ -158,6 +159,7 @@ var styles = StyleSheet.create({
borderRadius: 5,
flex: 1,
height: 44,
+ alignSelf: 'stretch',
justifyContent: 'center',
overflow: 'hidden',
},
diff --git a/Examples/UIExplorer/ProgressBarAndroidExample.android.js b/Examples/UIExplorer/ProgressBarAndroidExample.android.js
index 040ed00fd..b58cd73ca 100644
--- a/Examples/UIExplorer/ProgressBarAndroidExample.android.js
+++ b/Examples/UIExplorer/ProgressBarAndroidExample.android.js
@@ -20,6 +20,31 @@ var React = require('React');
var UIExplorerBlock = require('UIExplorerBlock');
var UIExplorerPage = require('UIExplorerPage');
+var TimerMixin = require('react-timer-mixin');
+
+var MovingBar = React.createClass({
+ mixins: [TimerMixin],
+
+ getInitialState: function() {
+ return {
+ progress: 0
+ };
+ },
+
+ componentDidMount: function() {
+ this.setInterval(
+ () => {
+ var progress = (this.state.progress + 0.02) % 1;
+ this.setState({progress: progress});
+ }, 50
+ );
+ },
+
+ render: function() {
+ return ;
+ },
+});
+
var ProgressBarAndroidExample = React.createClass({
statics: {
@@ -54,6 +79,26 @@ var ProgressBarAndroidExample = React.createClass({
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
},
diff --git a/Examples/UIExplorer/SliderIOSExample.js b/Examples/UIExplorer/SliderIOSExample.js
index 2dfda6667..245fe10ad 100644
--- a/Examples/UIExplorer/SliderIOSExample.js
+++ b/Examples/UIExplorer/SliderIOSExample.js
@@ -37,7 +37,7 @@ var SliderExample = React.createClass({
{this.state.value}
this.setState({value: value})} />
);
@@ -62,7 +62,26 @@ exports.displayName = 'SliderExample';
exports.description = 'Slider input for numeric values';
exports.examples = [
{
- title: 'SliderIOS',
- render(): ReactElement { return ; }
+ title: 'Default settings',
+ render(): ReactElement {
+ return ;
+ }
+ },
+ {
+ title: 'minimumValue: -1, maximumValue: 2',
+ render(): ReactElement {
+ return (
+
+ );
+ }
+ },
+ {
+ title: 'step: 0.25',
+ render(): ReactElement {
+ return ;
+ }
}
];
diff --git a/Examples/UIExplorer/TextExample.android.js b/Examples/UIExplorer/TextExample.android.js
index 42d17ed07..0275863a8 100644
--- a/Examples/UIExplorer/TextExample.android.js
+++ b/Examples/UIExplorer/TextExample.android.js
@@ -160,6 +160,22 @@ var TextExample = React.createClass({
+
+
+
+
+ NotoSerif Regular
+
+
+ NotoSerif Bold Italic
+
+
+ NotoSerif Italic (Missing Font file)
+
+
+
+
+
Size 23
diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js
index 0d6e20c10..57a2f032c 100644
--- a/Examples/UIExplorer/TextExample.ios.js
+++ b/Examples/UIExplorer/TextExample.ios.js
@@ -243,6 +243,21 @@ exports.examples = [
)
+
+ (opacity
+
+ (is inherited
+
+ (and accumulated
+
+ (and also applies to the background)
+
+ )
+
+ )
+
+ )
+
Entity Name
diff --git a/Examples/UIExplorer/TextInputExample.android.js b/Examples/UIExplorer/TextInputExample.android.js
index b70f2d933..31156b50d 100644
--- a/Examples/UIExplorer/TextInputExample.android.js
+++ b/Examples/UIExplorer/TextInputExample.android.js
@@ -78,15 +78,79 @@ class RewriteExample extends React.Component {
this.state = {text: ''};
}
render() {
+ var limit = 20;
+ var remainder = limit - this.state.text.length;
+ var remainderColor = remainder > 5 ? 'blue' : 'red';
return (
- {
- text = text.replace(/ /g, '_');
- this.setState({text});
- }}
- style={styles.singleLine}
- value={this.state.text}
- />
+
+ {
+ text = text.replace(/ /g, '_');
+ this.setState({text});
+ }}
+ style={styles.default}
+ value={this.state.text}
+ />
+
+ {remainder}
+
+
+ );
+ }
+}
+
+class TokenizedTextExample extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {text: 'Hello #World'};
+ }
+ render() {
+
+ //define delimiter
+ let delimiter = /\s+/;
+
+ //split string
+ let _text = this.state.text;
+ let token, index, parts = [];
+ while (_text) {
+ delimiter.lastIndex = 0;
+ token = delimiter.exec(_text);
+ if (token === null) {
+ break;
+ }
+ index = token.index;
+ if (token[0].length === 0) {
+ index = 1;
+ }
+ parts.push(_text.substr(0, index));
+ parts.push(token[0]);
+ index = index + token[0].length;
+ _text = _text.slice(index);
+ }
+ parts.push(_text);
+
+ //highlight hashtags
+ parts = parts.map((text) => {
+ if (/^#/.test(text)) {
+ return {text};
+ } else {
+ return text;
+ }
+ });
+
+ return (
+
+ {
+ this.setState({text});
+ }}>
+ {parts}
+
+
);
}
}
@@ -109,6 +173,10 @@ var styles = StyleSheet.create({
singleLineWithHeightTextInput: {
height: 30,
},
+ hashtag: {
+ color: 'blue',
+ fontWeight: 'bold',
+ },
});
exports.title = '';
@@ -322,4 +390,10 @@ exports.examples = [
);
}
},
+ {
+ title: 'Attributed text',
+ render: function() {
+ return ;
+ }
+ },
];
diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js
index d51a95e33..c7e196222 100644
--- a/Examples/UIExplorer/TextInputExample.ios.js
+++ b/Examples/UIExplorer/TextInputExample.ios.js
@@ -42,6 +42,7 @@ var TextEventsExample = React.createClass({
curText: '',
prevText: '',
prev2Text: '',
+ prev3Text: '',
};
},
@@ -51,6 +52,7 @@ var TextEventsExample = React.createClass({
curText: text,
prevText: state.curText,
prev2Text: state.prevText,
+ prev3Text: state.prev2Text,
};
});
},
@@ -73,12 +75,16 @@ var TextEventsExample = React.createClass({
onSubmitEditing={(event) => this.updateText(
'onSubmitEditing text: ' + event.nativeEvent.text
)}
+ onKeyPress={(event) => {
+ this.updateText('onKeyPress key: ' + event.nativeEvent.key);
+ }}
style={styles.default}
/>
{this.state.curText}{'\n'}
(prev: {this.state.prevText}){'\n'}
- (prev2: {this.state.prev2Text})
+ (prev2: {this.state.prev2Text}){'\n'}
+ (prev3: {this.state.prev3Text})
);
@@ -114,6 +120,114 @@ class RewriteExample extends React.Component {
}
}
+class TokenizedTextExample extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {text: 'Hello #World'};
+ }
+ render() {
+
+ //define delimiter
+ let delimiter = /\s+/;
+
+ //split string
+ let _text = this.state.text;
+ let token, index, parts = [];
+ while (_text) {
+ delimiter.lastIndex = 0;
+ token = delimiter.exec(_text);
+ if (token === null) {
+ break;
+ }
+ index = token.index;
+ if (token[0].length === 0) {
+ index = 1;
+ }
+ parts.push(_text.substr(0, index));
+ parts.push(token[0]);
+ index = index + token[0].length;
+ _text = _text.slice(index);
+ }
+ parts.push(_text);
+
+ //highlight hashtags
+ parts = parts.map((text) => {
+ if (/^#/.test(text)) {
+ return {text};
+ } else {
+ return text;
+ }
+ });
+
+ return (
+
+ {
+ this.setState({text});
+ }}>
+ {parts}
+
+
+ );
+ }
+}
+
+var BlurOnSubmitExample = React.createClass({
+ focusNextField(nextField) {
+ this.refs[nextField].focus()
+ },
+
+ render: function() {
+ return (
+
+ this.focusNextField('2')}
+ />
+ this.focusNextField('3')}
+ />
+ this.focusNextField('4')}
+ />
+ this.focusNextField('5')}
+ />
+
+
+ );
+ }
+});
+
var styles = StyleSheet.create({
page: {
paddingBottom: 300,
@@ -172,6 +286,10 @@ var styles = StyleSheet.create({
textAlign: 'right',
width: 24,
},
+ hashtag: {
+ color: 'blue',
+ fontWeight: 'bold',
+ },
});
exports.displayName = (undefined: ?string);
@@ -437,5 +555,15 @@ exports.examples = [
);
}
- }
+ },
+ {
+ title: 'Attributed text',
+ render: function() {
+ return ;
+ }
+ },
+ {
+ title: 'Blur on submit',
+ render: function(): ReactElement { return ; },
+ },
];
diff --git a/Examples/UIExplorer/ToastAndroidExample.android.js b/Examples/UIExplorer/ToastAndroidExample.android.js
index 61becd273..7f9cedf07 100644
--- a/Examples/UIExplorer/ToastAndroidExample.android.js
+++ b/Examples/UIExplorer/ToastAndroidExample.android.js
@@ -31,7 +31,7 @@ var ToastExample = React.createClass({
statics: {
title: 'Toast Example',
- description: 'Example that demostrates the use of an Android Toast to provide feedback.',
+ description: 'Example that demonstrates the use of an Android Toast to provide feedback.',
},
getInitialState: function() {
diff --git a/Examples/UIExplorer/ToolbarAndroidExample.android.js b/Examples/UIExplorer/ToolbarAndroidExample.android.js
index c621296d9..769737a33 100644
--- a/Examples/UIExplorer/ToolbarAndroidExample.android.js
+++ b/Examples/UIExplorer/ToolbarAndroidExample.android.js
@@ -101,6 +101,12 @@ var ToolbarAndroidExample = React.createClass({
title="Bunny and Hawk"
style={styles.toolbar} />
+
+
+
);
},
diff --git a/Examples/UIExplorer/TransparentHitTestExample.js b/Examples/UIExplorer/TransparentHitTestExample.js
new file mode 100644
index 000000000..755582da3
--- /dev/null
+++ b/Examples/UIExplorer/TransparentHitTestExample.js
@@ -0,0 +1,50 @@
+/**
+ * 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.
+ *
+ * @flow
+ */
+
+'use strict';
+
+var React = require('react-native');
+var {
+ Text,
+ View,
+ TouchableOpacity,
+} = React;
+
+var TransparentHitTestExample = React.createClass({
+ render: function() {
+ return (
+
+ alert('Hi!')}>
+ HELLO!
+
+
+
+
+ );
+ },
+});
+
+exports.title = '';
+exports.displayName = 'TransparentHitTestExample';
+exports.description = 'Transparent view receiving touch events';
+exports.examples = [
+ {
+ title: 'TransparentHitTestExample',
+ render(): ReactElement { return ; }
+ }
+];
diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
index 4c0bf2b19..40e9ef5f1 100644
--- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
+++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
@@ -51,6 +51,7 @@
14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; };
14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; };
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
+ 3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */; };
3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */; };
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
@@ -222,6 +223,7 @@
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = ../../Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj; sourceTree = ""; };
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; };
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; };
+ 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = uie_thumb_big.png; path = UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png; sourceTree = SOURCE_ROOT; };
3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = ""; };
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; };
83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = ""; };
@@ -436,6 +438,7 @@
143BC5971B21E3E100462512 /* Supporting Files */ = {
isa = PBXGroup;
children = (
+ 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */,
143BC5981B21E3E100462512 /* Info.plist */,
);
name = "Supporting Files";
@@ -817,6 +820,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -966,6 +970,7 @@
OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.internal.uiexplorer.local;
PRODUCT_NAME = UIExplorer;
+ TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -985,6 +990,7 @@
OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.internal.uiexplorer.local;
PRODUCT_NAME = UIExplorer;
+ TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@@ -1076,6 +1082,7 @@
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
@@ -1103,6 +1110,7 @@
WARNING_CFLAGS = (
"-Wextra",
"-Wall",
+ "-Wincompatible-pointer-types",
);
};
name = Debug;
@@ -1133,6 +1141,7 @@
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
@@ -1160,6 +1169,7 @@
WARNING_CFLAGS = (
"-Wextra",
"-Wall",
+ "-Wincompatible-pointer-types",
);
};
name = Release;
diff --git a/Examples/UIExplorer/UIExplorerApp.ios.js b/Examples/UIExplorer/UIExplorerApp.ios.js
index c2f4734e8..8daecbd42 100644
--- a/Examples/UIExplorer/UIExplorerApp.ios.js
+++ b/Examples/UIExplorer/UIExplorerApp.ios.js
@@ -73,5 +73,6 @@ var styles = StyleSheet.create({
});
AppRegistry.registerComponent('UIExplorerApp', () => UIExplorerApp);
+UIExplorerList.registerComponents();
module.exports = UIExplorerApp;
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png
index ff9eaeae1..5786896ee 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png
index 239761ed2..c38d68b52 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png
index efdc8fe05..bfd587267 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png
index 90874b135..cfde34a4a 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png
index 4055e789b..43718fb49 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png
index cc17b0dd2..8ec70eb0c 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m
index 1bbd981ce..fc9afeaf1 100644
--- a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m
+++ b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m
@@ -36,7 +36,7 @@
#endif
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
- RCTAssert(version.majorVersion == 8 || version.minorVersion >= 3, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
+ RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp", nil);
}
@@ -63,7 +63,7 @@ RCT_TEST(IntegrationTestHarnessTest)
RCT_TEST(TimersTest)
RCT_TEST(AsyncStorageTest)
RCT_TEST(AppEventsTest)
-RCT_TEST(ImageSnapshotTest)
+//RCT_TEST(ImageSnapshotTest) // Disabled: #8985988
RCT_TEST(SimpleSnapshotTest)
// Disable due to flakiness: #8686784
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m
index 1281a4796..2f3639cdc 100644
--- a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m
+++ b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m
@@ -37,7 +37,7 @@
#endif
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
- RCTAssert(version.majorVersion == 8 || version.minorVersion >= 3, @"Snapshot tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
+ RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp.ios", nil);
_runner.recordMode = NO;
}
@@ -53,7 +53,7 @@ RCT_TEST(LayoutExample)
RCT_TEST(TextExample)
RCT_TEST(SwitchExample)
RCT_TEST(SliderExample)
-RCT_TEST(TabBarExample)
+//RCT_TEST(TabBarExample) // Disabled: #8985988
- (void)testZZZNotInRecordMode
{
diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js
index 7dab3bf07..c8f07d971 100644
--- a/Examples/UIExplorer/UIExplorerList.ios.js
+++ b/Examples/UIExplorer/UIExplorerList.ios.js
@@ -50,6 +50,7 @@ var COMPONENTS = [
require('./TextExample.ios'),
require('./TextInputExample.ios'),
require('./TouchableExample'),
+ require('./TransparentHitTestExample'),
require('./ViewExample'),
require('./WebViewExample'),
];
@@ -78,23 +79,6 @@ var APIS = [
require('./ImageEditingExample'),
];
-// Register suitable examples for snapshot tests
-COMPONENTS.concat(APIS).forEach((Example) => {
- if (Example.displayName) {
- var Snapshotter = React.createClass({
- render: function() {
- var Renderable = UIExplorerListBase.makeRenderable(Example);
- return (
-
-
-
- );
- },
- });
- AppRegistry.registerComponent(Example.displayName, () => Snapshotter);
- }
-});
-
type Props = {
navigator: {
navigationContext: NavigationContext,
@@ -143,6 +127,25 @@ class UIExplorerList extends React.Component {
onPressRow(example: any) {
this._openExample(example);
}
+
+ // Register suitable examples for snapshot tests
+ static registerComponents() {
+ COMPONENTS.concat(APIS).forEach((Example) => {
+ if (Example.displayName) {
+ var Snapshotter = React.createClass({
+ render: function() {
+ var Renderable = UIExplorerListBase.makeRenderable(Example);
+ return (
+
+
+
+ );
+ },
+ });
+ AppRegistry.registerComponent(Example.displayName, () => Snapshotter);
+ }
+ });
+ }
}
var styles = StyleSheet.create({
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m
index c00eedcfd..fcd796114 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m
@@ -20,10 +20,10 @@
- (void)testLayoutSubviewsOrdering
{
// create some Views and ViewControllers
- UIViewController *parentVC = [[UIViewController alloc] init];
- UIView *parentView = [[UIView alloc] init];
- UIViewController *childVC = [[UIViewController alloc] init];
- UIView *childView = [[UIView alloc] init];
+ UIViewController *parentVC = [UIViewController new];
+ UIView *parentView = [UIView new];
+ UIViewController *childVC = [UIViewController new];
+ UIView *childView = [UIView new];
// The ordering we expect is:
// parentView::layoutSubviews
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
index a3c4c4354..7802f4ad1 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
@@ -136,19 +136,25 @@ _Pragma("clang diagnostic pop")
NSString *injectedStuff;
RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"]));
- NSDictionary *moduleConfig = RCTJSONParse(injectedStuff, NULL);
- NSDictionary *remoteModuleConfig = moduleConfig[@"remoteModuleConfig"];
- NSDictionary *testModuleConfig = remoteModuleConfig[@"TestModule"];
- NSDictionary *constants = testModuleConfig[@"constants"];
- NSDictionary *methods = testModuleConfig[@"methods"];
+ __block NSNumber *testModuleID = nil;
+ __block NSDictionary *testConstants = nil;
+ __block NSNumber *testMethodID = nil;
+
+ NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
+ [remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, BOOL *stop) {
+ if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) {
+ testModuleID = @(i);
+ testConstants = moduleConfig[1];
+ testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]);
+ *stop = YES;
+ }
+ }];
- XCTAssertNotNil(moduleConfig);
XCTAssertNotNil(remoteModuleConfig);
- XCTAssertNotNil(testModuleConfig);
- XCTAssertNotNil(constants);
- XCTAssertEqualObjects(constants[@"eleventyMillion"], @42);
- XCTAssertNotNil(methods);
- XCTAssertNotNil(methods[@"testMethod"]);
+ XCTAssertNotNil(testModuleID);
+ XCTAssertNotNil(testConstants);
+ XCTAssertEqualObjects(testConstants[@"eleventyMillion"], @42);
+ XCTAssertNotNil(testMethodID);
}
- (void)testCallNativeMethod
@@ -158,13 +164,19 @@ _Pragma("clang diagnostic pop")
NSString *injectedStuff;
RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"]));
- NSDictionary *moduleConfig = RCTJSONParse(injectedStuff, NULL);
- NSDictionary *remoteModuleConfig = moduleConfig[@"remoteModuleConfig"];
- NSDictionary *testModuleConfig = remoteModuleConfig[@"TestModule"];
- NSNumber *testModuleID = testModuleConfig[@"moduleID"];
- NSDictionary *methods = testModuleConfig[@"methods"];
- NSDictionary *testMethod = methods[@"testMethod"];
- NSNumber *testMethodID = testMethod[@"methodID"];
+ __block NSNumber *testModuleID = nil;
+ __block NSDictionary *testConstants = nil;
+ __block NSNumber *testMethodID = nil;
+
+ NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
+ [remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) {
+ if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) {
+ testModuleID = @(i);
+ testConstants = moduleConfig[1];
+ testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]);
+ *stop = YES;
+ }
+ }];
NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42];
NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args], @[], @1234567];
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m
index 0de322a88..a42d587b0 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m
@@ -36,7 +36,7 @@
} \
#define TEST_BUNDLE_PATH(name, _input, _expectedPath) \
-TEST_PATH(name, _input, [[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingPathComponent:_expectedPath])
+TEST_PATH(name, _input, [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:_expectedPath])
// Basic tests
TEST_URL(basic, @"http://example.com", @"http://example.com")
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m
index b11bffa26..a51319bb3 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m
@@ -64,6 +64,8 @@ static BOOL RCTLogsError(void (^block)(void))
- (void)doFooWithInteger:(__unused NSInteger)n { }
- (void)doFooWithCGRect:(CGRect)s { _s = s; }
+- (void)doFoo : (__unused NSString *)foo { }
+
- (void)testNumbersNonnull
{
{
@@ -121,4 +123,22 @@ static BOOL RCTLogsError(void (^block)(void))
XCTAssertTrue(CGRectEqualToRect(r, _s));
}
+- (void)testWhitespaceTolerance
+{
+ NSString *methodName = @"doFoo : \t (NSString *)foo";
+
+ __block RCTModuleMethod *method;
+ XCTAssertFalse(RCTLogsError(^{
+ method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
+ JSMethodName:nil
+ moduleClass:[self class]];
+ }));
+
+ XCTAssertEqualObjects(method.JSMethodName, @"doFoo");
+
+ XCTAssertFalse(RCTLogsError(^{
+ [method invokeWithBridge:nil module:self arguments:@[@"bar"]];
+ }));
+}
+
@end
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m
index 9bdda8a8d..b94622c8c 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m
@@ -83,7 +83,8 @@
[parentView insertReactSubview:mainView atIndex:1];
[parentView insertReactSubview:footerView atIndex:2];
- [parentView collectRootUpdatedFrames:nil parentConstraint:CGSizeZero];
+ parentView.reactTag = @1; // must be valid rootView tag
+ [parentView collectRootUpdatedFrames:nil];
XCTAssertTrue(CGRectEqualToRect([parentView measureLayoutRelativeToAncestor:parentView], CGRectMake(0, 0, 440, 440)));
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js
index 41f6b4d1a..f8183b12b 100644
--- a/Examples/UIExplorer/WebViewExample.js
+++ b/Examples/UIExplorer/WebViewExample.js
@@ -96,6 +96,7 @@ var WebViewExample = React.createClass({
url={this.state.url}
javaScriptEnabledAndroid={true}
onNavigationStateChange={this.onNavigationStateChange}
+ onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
startInLoadingState={true}
scalesPageToFit={this.state.scalesPageToFit}
/>
@@ -118,6 +119,11 @@ var WebViewExample = React.createClass({
this.refs[WEBVIEW_REF].reload();
},
+ onShouldStartLoadWithRequest: function(event) {
+ // Implement any custom loading logic here, don't forget to return!
+ return true;
+ },
+
onNavigationStateChange: function(navState) {
this.setState({
backButtonEnabled: navState.canGoBack,
diff --git a/Examples/UIExplorer/XHRExample.android.js b/Examples/UIExplorer/XHRExample.android.js
index 92344e72d..3696a2bfb 100644
--- a/Examples/UIExplorer/XHRExample.android.js
+++ b/Examples/UIExplorer/XHRExample.android.js
@@ -25,6 +25,8 @@ var {
View,
} = React;
+var XHRExampleHeaders = require('./XHRExampleHeaders');
+
// TODO t7093728 This is a simlified XHRExample.ios.js.
// Once we have Camera roll, Toast, Intent (for opening URLs)
// we should make this consistent with iOS.
@@ -259,10 +261,9 @@ class FormUploader extends React.Component {
}
}
-
exports.framework = 'React';
exports.title = 'XMLHttpRequest';
-exports.description = 'Example that demostrates upload and download requests ' +
+exports.description = 'Example that demonstrates upload and download requests ' +
'using XMLHttpRequest.';
exports.examples = [{
title: 'File Download',
@@ -274,6 +275,11 @@ exports.examples = [{
render() {
return ;
}
+}, {
+ title: 'Headers',
+ render() {
+ return ;
+ }
}];
var styles = StyleSheet.create({
diff --git a/Examples/UIExplorer/XHRExample.ios.js b/Examples/UIExplorer/XHRExample.ios.js
index 57f7fc31e..cc83ccf58 100644
--- a/Examples/UIExplorer/XHRExample.ios.js
+++ b/Examples/UIExplorer/XHRExample.ios.js
@@ -30,6 +30,8 @@ var {
View,
} = React;
+var XHRExampleHeaders = require('./XHRExampleHeaders');
+
class Downloader extends React.Component {
xhr: XMLHttpRequest;
@@ -368,6 +370,11 @@ exports.examples = [{
render() {
return ;
}
+}, {
+ title: 'Headers',
+ render() {
+ return ;
+ }
}];
var styles = StyleSheet.create({
diff --git a/Examples/UIExplorer/XHRExampleHeaders.js b/Examples/UIExplorer/XHRExampleHeaders.js
new file mode 100644
index 000000000..e9f5e2c4c
--- /dev/null
+++ b/Examples/UIExplorer/XHRExampleHeaders.js
@@ -0,0 +1,116 @@
+/**
+ * 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.
+ *
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+ StyleSheet,
+ Text,
+ TouchableHighlight,
+ View,
+} = React;
+
+class XHRExampleHeaders extends React.Component {
+
+ xhr: XMLHttpRequest;
+ cancelled: boolean;
+
+ constructor(props) {
+ super(props);
+ this.cancelled = false;
+ this.state = {
+ status: '',
+ headers: '',
+ contentSize: 1,
+ downloaded: 0,
+ };
+ }
+
+ download() {
+ this.xhr && this.xhr.abort();
+
+ var xhr = this.xhr || new XMLHttpRequest();
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState === xhr.DONE) {
+ if (this.cancelled) {
+ this.cancelled = false;
+ return;
+ }
+ if (xhr.status === 200) {
+ this.setState({
+ status: 'Download complete!',
+ headers: xhr.getAllResponseHeaders()
+ });
+ } else if (xhr.status !== 0) {
+ this.setState({
+ status: 'Error: Server returned HTTP status of ' + xhr.status + ' ' + xhr.responseText,
+ });
+ } else {
+ this.setState({
+ status: 'Error: ' + xhr.responseText,
+ });
+ }
+ }
+ };
+ xhr.open('GET', 'https://httpbin.org/response-headers?header1=value&header2=value1&header2=value2');
+ xhr.send();
+ this.xhr = xhr;
+
+ this.setState({status: 'Downloading...'});
+ }
+
+ componentWillUnmount() {
+ this.cancelled = true;
+ this.xhr && this.xhr.abort();
+ }
+
+ render() {
+ var button = this.state.status === 'Downloading...' ? (
+
+
+ ...
+
+
+ ) : (
+
+
+ Get headers
+
+
+ );
+
+ return (
+
+ {button}
+ {this.state.headers}
+
+ );
+ }
+}
+
+var styles = StyleSheet.create({
+ wrapper: {
+ borderRadius: 5,
+ marginBottom: 5,
+ },
+ button: {
+ backgroundColor: '#eeeeee',
+ padding: 8,
+ },
+});
+
+module.exports = XHRExampleHeaders;
\ No newline at end of file
diff --git a/Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif.ttf b/Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif.ttf
new file mode 100755
index 000000000..a1c6f1059
Binary files /dev/null and b/Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif.ttf differ
diff --git a/Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif_bold_italic.ttf b/Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif_bold_italic.ttf
new file mode 100755
index 000000000..32d38afee
Binary files /dev/null and b/Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif_bold_italic.ttf differ
diff --git a/Examples/UIExplorer/android/app/src/main/java/UIExplorerActivity.java b/Examples/UIExplorer/android/app/src/main/java/UIExplorerActivity.java
index 04c8e258c..c1dd73f6d 100644
--- a/Examples/UIExplorer/android/app/src/main/java/UIExplorerActivity.java
+++ b/Examples/UIExplorer/android/app/src/main/java/UIExplorerActivity.java
@@ -69,7 +69,7 @@ public class UIExplorerActivity extends Activity implements DefaultHardwareBackB
super.onResume();
if (mReactInstanceManager != null) {
- mReactInstanceManager.onResume(this);
+ mReactInstanceManager.onResume(this, this);
}
}
diff --git a/Libraries/ART/Brushes/ARTLinearGradient.m b/Libraries/ART/Brushes/ARTLinearGradient.m
index 8793ff07b..e34fd59fd 100644
--- a/Libraries/ART/Brushes/ARTLinearGradient.m
+++ b/Libraries/ART/Brushes/ARTLinearGradient.m
@@ -19,7 +19,7 @@
CGPoint _endPoint;
}
-- (instancetype)initWithArray:(NSArray *)array
+- (instancetype)initWithArray:(NSArray *)array
{
if ((self = [super initWithArray:array])) {
if (array.count < 5) {
diff --git a/Libraries/ART/Brushes/ARTPattern.m b/Libraries/ART/Brushes/ARTPattern.m
index 07dd86700..a0e6d5a5d 100644
--- a/Libraries/ART/Brushes/ARTPattern.m
+++ b/Libraries/ART/Brushes/ARTPattern.m
@@ -18,7 +18,7 @@
CGRect _rect;
}
-- (instancetype)initWithArray:(NSArray *)array
+- (instancetype)initWithArray:(NSArray *)array
{
if ((self = [super initWithArray:array])) {
if (array.count < 6) {
diff --git a/Libraries/ART/Brushes/ARTRadialGradient.m b/Libraries/ART/Brushes/ARTRadialGradient.m
index b59b17369..cb3ae65a2 100644
--- a/Libraries/ART/Brushes/ARTRadialGradient.m
+++ b/Libraries/ART/Brushes/ARTRadialGradient.m
@@ -21,7 +21,7 @@
CGFloat _radiusRatio;
}
-- (instancetype)initWithArray:(NSArray *)array
+- (instancetype)initWithArray:(NSArray *)array
{
if ((self = [super initWithArray:array])) {
if (array.count < 7) {
diff --git a/Libraries/ART/Brushes/ARTSolidColor.m b/Libraries/ART/Brushes/ARTSolidColor.m
index 229942dde..bfeff00bd 100644
--- a/Libraries/ART/Brushes/ARTSolidColor.m
+++ b/Libraries/ART/Brushes/ARTSolidColor.m
@@ -17,7 +17,7 @@
CGColorRef _color;
}
-- (instancetype)initWithArray:(NSArray *)array
+- (instancetype)initWithArray:(NSArray *)array
{
if ((self = [super initWithArray:array])) {
_color = CGColorRetain([RCTConvert CGColor:array offset:1]);
diff --git a/Libraries/ART/ReactNativeART.js b/Libraries/ART/ReactNativeART.js
index 3b5801d00..9bf9bc0b6 100644
--- a/Libraries/ART/ReactNativeART.js
+++ b/Libraries/ART/ReactNativeART.js
@@ -566,8 +566,14 @@ function Pattern(url, width, height, left, top) {
this._brush = [PATTERN, url, +left || 0, +top || 0, +width, +height];
}
-var ReactART = {
+// This doesn't work on iOS and is just a placeholder to get Spectrum running.
+// I will try to eliminate this dependency in Spectrum and remove it from
+// ReactART proper.
+function CSSBackgroundPattern() {
+ return new Color('rgba(0,0,0,0)');
+}
+var ReactART = {
LinearGradient: LinearGradient,
RadialGradient: RadialGradient,
Pattern: Pattern,
@@ -578,7 +584,7 @@ var ReactART = {
ClippingRectangle: ClippingRectangle,
Shape: Shape,
Text: Text,
-
+ CSSBackgroundPattern: CSSBackgroundPattern
};
module.exports = ReactART;
diff --git a/Libraries/ART/ViewManagers/ARTGroupManager.m b/Libraries/ART/ViewManagers/ARTGroupManager.m
index 15f55d4df..b958d11f9 100644
--- a/Libraries/ART/ViewManagers/ARTGroupManager.m
+++ b/Libraries/ART/ViewManagers/ARTGroupManager.m
@@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
- (ARTNode *)node
{
- return [[ARTGroup alloc] init];
+ return [ARTGroup new];
}
@end
diff --git a/Libraries/ART/ViewManagers/ARTNodeManager.m b/Libraries/ART/ViewManagers/ARTNodeManager.m
index c2f0dba35..3c697c129 100644
--- a/Libraries/ART/ViewManagers/ARTNodeManager.m
+++ b/Libraries/ART/ViewManagers/ARTNodeManager.m
@@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
- (ARTNode *)node
{
- return [[ARTNode alloc] init];
+ return [ARTNode new];
}
- (UIView *)view
diff --git a/Libraries/ART/ViewManagers/ARTRenderableManager.m b/Libraries/ART/ViewManagers/ARTRenderableManager.m
index 01b579dca..aaed2e31d 100644
--- a/Libraries/ART/ViewManagers/ARTRenderableManager.m
+++ b/Libraries/ART/ViewManagers/ARTRenderableManager.m
@@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
- (ARTRenderable *)node
{
- return [[ARTRenderable alloc] init];
+ return [ARTRenderable new];
}
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat)
diff --git a/Libraries/ART/ViewManagers/ARTShapeManager.m b/Libraries/ART/ViewManagers/ARTShapeManager.m
index 426237fa7..3997586d1 100644
--- a/Libraries/ART/ViewManagers/ARTShapeManager.m
+++ b/Libraries/ART/ViewManagers/ARTShapeManager.m
@@ -18,7 +18,7 @@ RCT_EXPORT_MODULE()
- (ARTRenderable *)node
{
- return [[ARTShape alloc] init];
+ return [ARTShape new];
}
RCT_EXPORT_VIEW_PROPERTY(d, CGPath)
diff --git a/Libraries/ART/ViewManagers/ARTSurfaceViewManager.m b/Libraries/ART/ViewManagers/ARTSurfaceViewManager.m
index ddfba6697..10772b72c 100644
--- a/Libraries/ART/ViewManagers/ARTSurfaceViewManager.m
+++ b/Libraries/ART/ViewManagers/ARTSurfaceViewManager.m
@@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
- return [[ARTSurfaceView alloc] init];
+ return [ARTSurfaceView new];
}
@end
diff --git a/Libraries/ART/ViewManagers/ARTTextManager.m b/Libraries/ART/ViewManagers/ARTTextManager.m
index 473d0cf4f..430f26db5 100644
--- a/Libraries/ART/ViewManagers/ARTTextManager.m
+++ b/Libraries/ART/ViewManagers/ARTTextManager.m
@@ -18,7 +18,7 @@ RCT_EXPORT_MODULE()
- (ARTRenderable *)node
{
- return [[ARTText alloc] init];
+ return [ARTText new];
}
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
diff --git a/Libraries/ActionSheetIOS/ActionSheetIOS.js b/Libraries/ActionSheetIOS/ActionSheetIOS.js
index 20150d6f6..155b7d74e 100644
--- a/Libraries/ActionSheetIOS/ActionSheetIOS.js
+++ b/Libraries/ActionSheetIOS/ActionSheetIOS.js
@@ -27,7 +27,6 @@ var ActionSheetIOS = {
);
RCTActionSheetManager.showActionSheetWithOptions(
options,
- () => {}, // RKActionSheet compatibility hack
callback
);
},
diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m
index dbaa9affd..626da966c 100644
--- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m
+++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m
@@ -21,65 +21,137 @@
@implementation RCTActionSheetManager
{
- NSMutableDictionary *_callbacks;
+ // Use NSMapTable, as UIAlertViews do not implement
+ // which is required for NSDictionary keys
+ NSMapTable *_callbacks;
}
RCT_EXPORT_MODULE()
-- (instancetype)init
-{
- if ((self = [super init])) {
- _callbacks = [NSMutableDictionary new];
- }
- return self;
-}
-
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
+/*
+ * The `anchor` option takes a view to set as the anchor for the share
+ * popup to point to, on iPads running iOS 8. If it is not passed, it
+ * defaults to centering the share popup on screen without any arrows.
+ */
+- (CGRect)sourceRectInView:(UIView *)sourceView
+ anchorViewTag:(NSNumber *)anchorViewTag
+{
+ if (anchorViewTag) {
+ UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
+ return [anchorView convertRect:anchorView.bounds toView:sourceView];
+ } else {
+ return (CGRect){sourceView.center, {1, 1}};
+ }
+}
+
RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options
- failureCallback:(__unused RCTResponseSenderBlock)failureCallback
+ callback:(RCTResponseSenderBlock)callback)
+{
+ if (RCTRunningInAppExtension()) {
+ RCTLogError(@"Unable to show action sheet from app extension");
+ return;
+ }
+
+ if (!_callbacks) {
+ _callbacks = [NSMapTable strongToStrongObjectsMapTable];
+ }
+
+ NSString *title = [RCTConvert NSString:options[@"title"]];
+ NSArray *buttons = [RCTConvert NSStringArray:options[@"options"]];
+ NSInteger destructiveButtonIndex = options[@"destructiveButtonIndex"] ? [RCTConvert NSInteger:options[@"destructiveButtonIndex"]] : -1;
+ NSInteger cancelButtonIndex = options[@"cancelButtonIndex"] ? [RCTConvert NSInteger:options[@"cancelButtonIndex"]] : -1;
+
+ UIViewController *controller = RCTKeyWindow().rootViewController;
+ if (controller == nil) {
+ RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
+ return;
+ }
+
+ /*
+ * The `anchor` option takes a view to set as the anchor for the share
+ * popup to point to, on iPads running iOS 8. If it is not passed, it
+ * defaults to centering the share popup on screen without any arrows.
+ */
+ NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
+ UIView *sourceView = controller.view;
+ CGRect sourceRect = [self sourceRectInView:sourceView anchorViewTag:anchorViewTag];
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
+
+ if ([UIAlertController class] == nil) {
+
+ UIActionSheet *actionSheet = [UIActionSheet new];
+
+ actionSheet.title = title;
+ for (NSString *option in buttons) {
+ [actionSheet addButtonWithTitle:option];
+ }
+ actionSheet.destructiveButtonIndex = destructiveButtonIndex;
+ actionSheet.cancelButtonIndex = cancelButtonIndex;
+ actionSheet.delegate = self;
+
+ [_callbacks setObject:callback forKey:actionSheet];
+
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
+ [actionSheet showFromRect:sourceRect inView:sourceView animated:YES];
+ } else {
+ [actionSheet showInView:sourceView];
+ }
+
+ } else
+
+#endif
+
+ {
+ UIAlertController *alertController =
+ [UIAlertController alertControllerWithTitle:title
+ message:nil
+ preferredStyle:UIAlertControllerStyleActionSheet];
+
+ NSInteger index = 0;
+ for (NSString *option in buttons) {
+ UIAlertActionStyle style = UIAlertActionStyleDefault;
+ if (index == destructiveButtonIndex) {
+ style = UIAlertActionStyleDestructive;
+ } else if (index == cancelButtonIndex) {
+ style = UIAlertActionStyleCancel;
+ }
+
+ NSInteger localIndex = index;
+ [alertController addAction:[UIAlertAction actionWithTitle:option
+ style:style
+ handler:^(__unused UIAlertAction *action){
+ callback(@[@(localIndex)]);
+ }]];
+
+ index++;
+ }
+
+ alertController.modalPresentationStyle = UIModalPresentationPopover;
+ alertController.popoverPresentationController.sourceView = sourceView;
+ alertController.popoverPresentationController.sourceRect = sourceRect;
+ if (!anchorViewTag) {
+ alertController.popoverPresentationController.permittedArrowDirections = 0;
+ }
+ [controller presentViewController:alertController animated:YES completion:nil];
+ }
+}
+
+RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
+ failureCallback:(RCTResponseErrorBlock)failureCallback
successCallback:(RCTResponseSenderBlock)successCallback)
{
if (RCTRunningInAppExtension()) {
RCTLogError(@"Unable to show action sheet from app extension");
return;
}
-
- UIActionSheet *actionSheet = [UIActionSheet new];
- actionSheet.title = options[@"title"];
-
- for (NSString *option in options[@"options"]) {
- [actionSheet addButtonWithTitle:option];
- }
-
- if (options[@"destructiveButtonIndex"]) {
- actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
- }
- if (options[@"cancelButtonIndex"]) {
- actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
- }
-
- actionSheet.delegate = self;
-
- _callbacks[RCTKeyForInstance(actionSheet)] = successCallback;
-
- UIWindow *appWindow = RCTSharedApplication().delegate.window;
- if (appWindow == nil) {
- RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
- return;
- }
- [actionSheet showInView:appWindow];
-}
-
-RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
- failureCallback:(RCTResponseSenderBlock)failureCallback
- successCallback:(RCTResponseSenderBlock)successCallback)
-{
- NSMutableArray *items = [NSMutableArray array];
+ NSMutableArray *items = [NSMutableArray array];
NSString *message = [RCTConvert NSString:options[@"message"]];
if (message) {
[items addObject:message];
@@ -89,22 +161,18 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
[items addObject:URL];
}
if (items.count == 0) {
- failureCallback(@[@"No `url` or `message` to share"]);
- return;
- }
- if (RCTRunningInAppExtension()) {
- failureCallback(@[@"Unable to show action sheet from app extension"]);
+ RCTLogError(@"No `url` or `message` to share");
return;
}
- UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
- UIViewController *ctrl = RCTSharedApplication().delegate.window.rootViewController;
+ UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
+ UIViewController *controller = RCTKeyWindow().rootViewController;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
if (![UIActivityViewController instancesRespondToSelector:@selector(setCompletionWithItemsHandler:)]) {
// Legacy iOS 7 implementation
- share.completionHandler = ^(NSString *activityType, BOOL completed) {
+ shareController.completionHandler = ^(NSString *activityType, BOOL completed) {
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
};
} else
@@ -113,57 +181,37 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
{
// iOS 8 version
- share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
+ shareController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
if (activityError) {
- failureCallback(@[RCTNullIfNil(activityError.localizedDescription)]);
+ failureCallback(activityError);
} else {
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
}
};
- }
- /*
- * The `anchor` option takes a view to set as the anchor for the share
- * popup to point to, on iPads running iOS 8. If it is not passed, it
- * defaults to centering the share popup on screen without any arrows.
- */
- if ([share respondsToSelector:@selector(popoverPresentationController)]) {
- share.popoverPresentationController.sourceView = ctrl.view;
+ shareController.modalPresentationStyle = UIModalPresentationPopover;
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
- if (anchorViewTag) {
- UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
- share.popoverPresentationController.sourceRect = [anchorView convertRect:anchorView.bounds toView:ctrl.view];
- } else {
- CGRect sourceRect = CGRectMake(ctrl.view.center.x, ctrl.view.center.y, 1, 1);
- share.popoverPresentationController.sourceRect = sourceRect;
- share.popoverPresentationController.permittedArrowDirections = 0;
+ if (!anchorViewTag) {
+ shareController.popoverPresentationController.permittedArrowDirections = 0;
}
+ shareController.popoverPresentationController.sourceView = controller.view;
+ shareController.popoverPresentationController.sourceRect = [self sourceRectInView:controller.view anchorViewTag:anchorViewTag];
}
- [ctrl presentViewController:share animated:YES completion:nil];
+ [controller presentViewController:shareController animated:YES completion:nil];
}
#pragma mark UIActionSheetDelegate Methods
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
- NSString *key = RCTKeyForInstance(actionSheet);
- RCTResponseSenderBlock callback = _callbacks[key];
+ RCTResponseSenderBlock callback = [_callbacks objectForKey:actionSheet];
if (callback) {
callback(@[@(buttonIndex)]);
- [_callbacks removeObjectForKey:key];
+ [_callbacks removeObjectForKey:actionSheet];
} else {
RCTLogWarn(@"No callback registered for action sheet: %@", actionSheet.title);
}
-
- [RCTSharedApplication().delegate.window makeKeyWindow];
-}
-
-#pragma mark Private
-
-static NSString *RCTKeyForInstance(id instance)
-{
- return [NSString stringWithFormat:@"%p", instance];
}
@end
diff --git a/Libraries/Animated/src/Interpolation.js b/Libraries/Animated/src/Interpolation.js
index dcb62ab70..da9671316 100644
--- a/Libraries/Animated/src/Interpolation.js
+++ b/Libraries/Animated/src/Interpolation.js
@@ -202,13 +202,22 @@ function createInterpolationFromStringOutputRange(
// [200, 250],
// [0, 0.5],
// ]
+ /* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
+ * guard against this possibility.
+ */
var outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
outputRange.forEach(value => {
+ /* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard
+ * against this possibility.
+ */
value.match(stringShapeRegex).forEach((number, i) => {
outputRanges[i].push(+number);
});
});
+ /* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
+ * guard against this possibility.
+ */
var interpolations = outputRange[0].match(stringShapeRegex).map((value, i) => {
return Interpolation.create({
...config,
diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js
index a710e7ec1..1eee46a3e 100644
--- a/Libraries/CameraRoll/CameraRoll.js
+++ b/Libraries/CameraRoll/CameraRoll.js
@@ -119,6 +119,8 @@ class CameraRoll {
/**
* Saves the image to the camera roll / gallery.
*
+ * The CameraRoll API is not yet implemented for Android.
+ *
* @param {string} tag On Android, this is a local URI, such
* as `"file:///sdcard/img.png"`.
*
diff --git a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.h b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.h
index f075325e1..0cc1c5353 100644
--- a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.h
+++ b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.h
@@ -15,11 +15,6 @@
@interface RCTBridge (RCTAssetsLibraryImageLoader)
-/**
- * The shared Assets Library image loader
- */
-@property (nonatomic, readonly) RCTAssetsLibraryImageLoader *assetsLibraryImageLoader;
-
/**
* The shared asset library instance.
*/
diff --git a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m
index 3ccbc34b4..ebec0764e 100644
--- a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m
+++ b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m
@@ -41,10 +41,15 @@ RCT_EXPORT_MODULE()
- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
- return [requestURL.scheme.lowercaseString isEqualToString:@"assets-library"];
+ return [requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame;
}
-- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
+- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
+ size:(CGSize)size
+ scale:(CGFloat)scale
+ resizeMode:(UIViewContentMode)resizeMode
+ progressHandler:(RCTImageLoaderProgressBlock)progressHandler
+ completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
__block volatile uint32_t cancelled = 0;
@@ -69,7 +74,8 @@ RCT_EXPORT_MODULE()
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
ALAssetRepresentation *representation = [asset defaultRepresentation];
- #if RCT_DEV
+#if RCT_DEV
+
CGSize sizeBeingLoaded = size;
if (useMaximumSize) {
CGSize pointSize = representation.dimensions;
@@ -78,7 +84,7 @@ RCT_EXPORT_MODULE()
CGSize screenSize;
if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] == NSOrderedDescending) {
- screenSize = UIScreen.mainScreen.nativeBounds.size;
+ screenSize = [UIScreen mainScreen].nativeBounds.size;
} else {
CGSize mainScreenSize = [UIScreen mainScreen].bounds.size;
CGFloat mainScreenScale = [[UIScreen mainScreen] scale];
@@ -87,9 +93,11 @@ RCT_EXPORT_MODULE()
CGFloat maximumPixelDimension = fmax(screenSize.width, screenSize.height);
if (sizeBeingLoaded.width > maximumPixelDimension || sizeBeingLoaded.height > maximumPixelDimension) {
- RCTLogInfo(@"[PERF ASSETS] Loading %@ at size %@, which is larger than screen size %@", representation.filename, NSStringFromCGSize(sizeBeingLoaded), NSStringFromCGSize(screenSize));
+ RCTLogInfo(@"[PERF ASSETS] Loading %@ at size %@, which is larger than screen size %@",
+ representation.filename, NSStringFromCGSize(sizeBeingLoaded), NSStringFromCGSize(screenSize));
}
- #endif
+
+#endif
UIImage *image;
NSError *error = nil;
@@ -106,8 +114,7 @@ RCT_EXPORT_MODULE()
});
} else {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageURL];
- NSError *error = RCTErrorWithMessage(errorText);
- completionHandler(error, nil);
+ completionHandler(RCTErrorWithMessage(errorText), nil);
}
} failureBlock:^(NSError *loadError) {
if (cancelled) {
@@ -115,8 +122,7 @@ RCT_EXPORT_MODULE()
}
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageURL, loadError];
- NSError *error = RCTErrorWithMessage(errorText);
- completionHandler(error, nil);
+ completionHandler(RCTErrorWithMessage(errorText), nil);
}];
return ^{
@@ -128,14 +134,9 @@ RCT_EXPORT_MODULE()
@implementation RCTBridge (RCTAssetsLibraryImageLoader)
-- (RCTAssetsLibraryImageLoader *)assetsLibraryImageLoader
-{
- return self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])];
-}
-
- (ALAssetsLibrary *)assetsLibrary
{
- return [self.assetsLibraryImageLoader assetsLibrary];
+ return [self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])] assetsLibrary];
}
@end
@@ -154,7 +155,11 @@ static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void)
// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
-static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error)
+static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
+ CGSize size,
+ CGFloat scale,
+ UIViewContentMode resizeMode,
+ NSError **error)
{
NSUInteger length = (NSUInteger)representation.size;
NSMutableData *data = [NSMutableData dataWithLength:length];
diff --git a/Libraries/CameraRoll/RCTCameraRollManager.h b/Libraries/CameraRoll/RCTCameraRollManager.h
index 51921b7ed..30407c9f6 100644
--- a/Libraries/CameraRoll/RCTCameraRollManager.h
+++ b/Libraries/CameraRoll/RCTCameraRollManager.h
@@ -7,7 +7,17 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
+#import
+
#import "RCTBridgeModule.h"
+#import "RCTConvert.h"
+
+@interface RCTConvert (ALAssetGroup)
+
++ (ALAssetsGroupType)ALAssetsGroupType:(id)json;
++ (ALAssetsFilter *)ALAssetsFilter:(id)json;
+
+@end
@interface RCTCameraRollManager : NSObject
diff --git a/Libraries/CameraRoll/RCTCameraRollManager.m b/Libraries/CameraRoll/RCTCameraRollManager.m
index 3d719f4ab..e2044f03c 100644
--- a/Libraries/CameraRoll/RCTCameraRollManager.m
+++ b/Libraries/CameraRoll/RCTCameraRollManager.m
@@ -9,17 +9,70 @@
#import "RCTCameraRollManager.h"
-#import
#import
#import
#import
#import "RCTAssetsLibraryImageLoader.h"
#import "RCTBridge.h"
+#import "RCTConvert.h"
#import "RCTImageLoader.h"
#import "RCTLog.h"
#import "RCTUtils.h"
+@implementation RCTConvert (ALAssetGroup)
+
+RCT_ENUM_CONVERTER(ALAssetsGroupType, (@{
+
+ // New values
+ @"album": @(ALAssetsGroupAlbum),
+ @"all": @(ALAssetsGroupAll),
+ @"event": @(ALAssetsGroupEvent),
+ @"faces": @(ALAssetsGroupFaces),
+ @"library": @(ALAssetsGroupLibrary),
+ @"photo-stream": @(ALAssetsGroupPhotoStream),
+ @"saved-photos": @(ALAssetsGroupSavedPhotos),
+
+ // Legacy values
+ @"Album": @(ALAssetsGroupAlbum),
+ @"All": @(ALAssetsGroupAll),
+ @"Event": @(ALAssetsGroupEvent),
+ @"Faces": @(ALAssetsGroupFaces),
+ @"Library": @(ALAssetsGroupLibrary),
+ @"PhotoStream": @(ALAssetsGroupPhotoStream),
+ @"SavedPhotos": @(ALAssetsGroupSavedPhotos),
+
+}), ALAssetsGroupSavedPhotos, integerValue)
+
++ (ALAssetsFilter *)ALAssetsFilter:(id)json
+{
+ static NSDictionary *options;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ options = @{
+
+ // New values
+ @"photos": [ALAssetsFilter allPhotos],
+ @"videos": [ALAssetsFilter allVideos],
+ @"all": [ALAssetsFilter allAssets],
+
+ // Legacy values
+ @"Photos": [ALAssetsFilter allPhotos],
+ @"Videos": [ALAssetsFilter allVideos],
+ @"All": [ALAssetsFilter allAssets],
+ };
+ });
+
+ ALAssetsFilter *filter = options[json ?: @"photos"];
+ if (!filter) {
+ RCTLogError(@"Invalid filter option: '%@'. Expected one of 'photos',"
+ "'videos' or 'all'.", json);
+ }
+ return filter ?: [ALAssetsFilter allPhotos];
+}
+
+@end
+
@implementation RCTCameraRollManager
RCT_EXPORT_MODULE()
@@ -35,18 +88,23 @@ RCT_EXPORT_METHOD(saveImageWithTag:(NSString *)imageTag
errorCallback(loadError);
return;
}
- [_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
- if (saveError) {
- RCTLogWarn(@"Error saving cropped image: %@", saveError);
- errorCallback(saveError);
- } else {
- successCallback(@[assetURL.absoluteString]);
- }
- }];
+ // It's unclear if writeImageToSavedPhotosAlbum is thread-safe
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
+ if (saveError) {
+ RCTLogWarn(@"Error saving cropped image: %@", saveError);
+ errorCallback(saveError);
+ } else {
+ successCallback(@[assetURL.absoluteString]);
+ }
+ }];
+ });
}];
}
-- (void)callCallback:(RCTResponseSenderBlock)callback withAssets:(NSArray *)assets hasNextPage:(BOOL)hasNextPage
+- (void)callCallback:(RCTResponseSenderBlock)callback
+ withAssets:(NSArray *)assets
+ hasNextPage:(BOOL)hasNextPage
{
if (!assets.count) {
callback(@[@{
@@ -69,45 +127,21 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
callback:(RCTResponseSenderBlock)callback
errorCallback:(RCTResponseErrorBlock)errorCallback)
{
- NSUInteger first = [params[@"first"] integerValue];
- NSString *afterCursor = params[@"after"];
- NSString *groupTypesStr = params[@"groupTypes"];
- NSString *groupName = params[@"groupName"];
- NSString *assetType = params[@"assetType"];
- ALAssetsGroupType groupTypes;
-
- if ([groupTypesStr isEqualToString:@"Album"]) {
- groupTypes = ALAssetsGroupAlbum;
- } else if ([groupTypesStr isEqualToString:@"All"]) {
- groupTypes = ALAssetsGroupAll;
- } else if ([groupTypesStr isEqualToString:@"Event"]) {
- groupTypes = ALAssetsGroupEvent;
- } else if ([groupTypesStr isEqualToString:@"Faces"]) {
- groupTypes = ALAssetsGroupFaces;
- } else if ([groupTypesStr isEqualToString:@"Library"]) {
- groupTypes = ALAssetsGroupLibrary;
- } else if ([groupTypesStr isEqualToString:@"PhotoStream"]) {
- groupTypes = ALAssetsGroupPhotoStream;
- } else {
- groupTypes = ALAssetsGroupSavedPhotos;
- }
+ NSUInteger first = [RCTConvert NSInteger:params[@"first"]];
+ NSString *afterCursor = [RCTConvert NSString:params[@"after"]];
+ NSString *groupName = [RCTConvert NSString:params[@"groupName"]];
+ ALAssetsFilter *assetType = [RCTConvert ALAssetsFilter:params[@"assetType"]];
+ ALAssetsGroupType groupTypes = [RCTConvert ALAssetsGroupType:params[@"groupTypes"]];
BOOL __block foundAfter = NO;
BOOL __block hasNextPage = NO;
BOOL __block calledCallback = NO;
- NSMutableArray *assets = [NSMutableArray new];
+ NSMutableArray *assets = [NSMutableArray new];
[_bridge.assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) {
if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) {
- if (assetType == nil || [assetType isEqualToString:@"Photos"]) {
- [group setAssetsFilter:ALAssetsFilter.allPhotos];
- } else if ([assetType isEqualToString:@"Videos"]) {
- [group setAssetsFilter:ALAssetsFilter.allVideos];
- } else if ([assetType isEqualToString:@"All"]) {
- [group setAssetsFilter:ALAssetsFilter.allAssets];
- }
-
+ [group setAssetsFilter:assetType];
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) {
if (result) {
NSString *uri = ((NSURL *)[result valueForProperty:ALAssetPropertyAssetURL]).absoluteString;
diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m
index 281149196..d6f8846c0 100644
--- a/Libraries/CameraRoll/RCTImagePickerManager.m
+++ b/Libraries/CameraRoll/RCTImagePickerManager.m
@@ -23,9 +23,9 @@
@implementation RCTImagePickerManager
{
- NSMutableArray *_pickers;
- NSMutableArray *_pickerCallbacks;
- NSMutableArray *_pickerCancelCallbacks;
+ NSMutableArray *_pickers;
+ NSMutableArray *_pickerCallbacks;
+ NSMutableArray *_pickerCancelCallbacks;
}
RCT_EXPORT_MODULE(ImagePickerIOS);
@@ -42,7 +42,7 @@ RCT_EXPORT_MODULE(ImagePickerIOS);
RCT_EXPORT_METHOD(canRecordVideos:(RCTResponseSenderBlock)callback)
{
- NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
+ NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
callback(@[@([availableMediaTypes containsObject:(NSString *)kUTTypeMovie])]);
}
@@ -59,9 +59,8 @@ RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config
cancelCallback(@[@"Camera access is unavailable in an app extension"]);
return;
}
-
- UIWindow *keyWindow = RCTSharedApplication().keyWindow;
- UIViewController *rootViewController = keyWindow.rootViewController;
+
+ UIViewController *rootViewController = RCTKeyWindow().rootViewController;
UIImagePickerController *imagePicker = [UIImagePickerController new];
imagePicker.delegate = self;
@@ -86,15 +85,14 @@ RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config
cancelCallback(@[@"Image picker is currently unavailable in an app extension"]);
return;
}
-
- UIWindow *keyWindow = RCTSharedApplication().keyWindow;
- UIViewController *rootViewController = keyWindow.rootViewController;
+
+ UIViewController *rootViewController = RCTKeyWindow().rootViewController;
UIImagePickerController *imagePicker = [UIImagePickerController new];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
- NSMutableArray *allowedTypes = [NSMutableArray new];
+ NSMutableArray *allowedTypes = [NSMutableArray new];
if ([config[@"showImages"] boolValue]) {
[allowedTypes addObject:(NSString *)kUTTypeImage];
}
@@ -121,8 +119,7 @@ didFinishPickingMediaWithInfo:(NSDictionary *)info
[_pickerCallbacks removeObjectAtIndex:index];
[_pickerCancelCallbacks removeObjectAtIndex:index];
- UIWindow *keyWindow = RCTSharedApplication().keyWindow;
- UIViewController *rootViewController = keyWindow.rootViewController;
+ UIViewController *rootViewController = RCTKeyWindow().rootViewController;
[rootViewController dismissViewControllerAnimated:YES completion:nil];
callback(@[[info[UIImagePickerControllerReferenceURL] absoluteString]]);
@@ -137,8 +134,7 @@ didFinishPickingMediaWithInfo:(NSDictionary *)info
[_pickerCallbacks removeObjectAtIndex:index];
[_pickerCancelCallbacks removeObjectAtIndex:index];
- UIWindow *keyWindow = RCTSharedApplication().keyWindow;
- UIViewController *rootViewController = keyWindow.rootViewController;
+ UIViewController *rootViewController = RCTKeyWindow().rootViewController;
[rootViewController dismissViewControllerAnimated:YES completion:nil];
callback(@[]);
diff --git a/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m b/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m
index 144f80805..d0e5b6459 100644
--- a/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m
+++ b/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m
@@ -24,35 +24,44 @@ RCT_EXPORT_MODULE()
- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
- return [requestURL.scheme.lowercaseString isEqualToString:@"ph"];
+ return [requestURL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame;
}
-- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
+- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
+ size:(CGSize)size
+ scale:(CGFloat)scale
+ resizeMode:(UIViewContentMode)resizeMode
+ progressHandler:(RCTImageLoaderProgressBlock)progressHandler
+ completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
// Using PhotoKit for iOS 8+
// The 'ph://' prefix is used by FBMediaKit to differentiate between
// assets-library. It is prepended to the local ID so that it is in the
// form of an, NSURL which is what assets-library uses.
- NSString *phAssetID = [imageURL.absoluteString substringFromIndex:[@"ph://" length]];
+ NSString *phAssetID = [imageURL.absoluteString substringFromIndex:@"ph://".length];
PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil];
if (results.count == 0) {
NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", phAssetID];
- NSError *error = RCTErrorWithMessage(errorText);
- completionHandler(error, nil);
+ completionHandler(RCTErrorWithMessage(errorText), nil);
return ^{};
}
PHAsset *asset = [results firstObject];
-
PHImageRequestOptions *imageOptions = [PHImageRequestOptions new];
- imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
- static const double multiplier = 1e6;
- progressHandler(progress * multiplier, multiplier);
- };
+
+ if (progressHandler) {
+ imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
+ static const double multiplier = 1e6;
+ progressHandler(progress * multiplier, multiplier);
+ };
+ }
+
+ // Note: PhotoKit defaults to a deliveryMode of PHImageRequestOptionsDeliveryModeOpportunistic
+ // which means it may call back multiple times - we probably don't want that
+ imageOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
CGSize targetSize;
-
if (useMaximumSize) {
targetSize = PHImageManagerMaximumSize;
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
@@ -66,7 +75,12 @@ RCT_EXPORT_MODULE()
contentMode = PHImageContentModeAspectFit;
}
- PHImageRequestID requestID = [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:imageOptions resultHandler:^(UIImage *result, NSDictionary *info) {
+ PHImageRequestID requestID =
+ [[PHImageManager defaultManager] requestImageForAsset:asset
+ targetSize:targetSize
+ contentMode:contentMode
+ options:imageOptions
+ resultHandler:^(UIImage *result, NSDictionary *info) {
if (result) {
completionHandler(nil, result);
} else {
diff --git a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js
index 7e0ea6936..ed1744a1c 100644
--- a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js
+++ b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js
@@ -74,8 +74,8 @@ var ActivityIndicatorIOS = React.createClass({
return (
-
+ style={[styles.container, style]}>
+
);
}
diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js
index ac715e896..37c11187e 100644
--- a/Libraries/Components/MapView/MapView.js
+++ b/Libraries/Components/MapView/MapView.js
@@ -80,6 +80,13 @@ var MapView = React.createClass({
*/
showsUserLocation: React.PropTypes.bool,
+ /**
+ * If `false` points of interest won't be displayed on the map.
+ * Default value is `true`.
+ * @platform ios
+ */
+ showsPointsOfInterest: React.PropTypes.bool,
+
/**
* If `false` the user won't be able to pinch/zoom the map.
* Default value is `true`.
diff --git a/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js b/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js
index 307a2bad4..1588d21eb 100644
--- a/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js
+++ b/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js
@@ -15,7 +15,7 @@ var React = require('React');
var ReactPropTypes = require('ReactPropTypes');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
-var createReactNativeComponentClass = require('createReactNativeComponentClass');
+var requireNativeComponent = require('requireNativeComponent');
var STYLE_ATTRIBUTES = [
'Horizontal',
@@ -26,6 +26,18 @@ var STYLE_ATTRIBUTES = [
'LargeInverse'
];
+var indeterminateType = function(props, propName, componentName) {
+ var checker = function() {
+ var indeterminate = props[propName];
+ var styleAttr = props.styleAttr;
+ if (!indeterminate && styleAttr !== 'Horizontal') {
+ return new Error('indeterminate=false is only valid for styleAttr=Horizontal');
+ }
+ };
+
+ return ReactPropTypes.bool(props, propName, componentName) || checker();
+};
+
/**
* React component that wraps the Android-only `ProgressBar`. This component is used to indicate
* that the app is loading or there is some activity in the app.
@@ -62,6 +74,19 @@ var ProgressBarAndroid = React.createClass({
* - LargeInverse
*/
styleAttr: ReactPropTypes.oneOf(STYLE_ATTRIBUTES),
+ /**
+ * If the progress bar will show indeterminate progress. Note that this
+ * can only be false if styleAttr is Horizontal.
+ */
+ indeterminate: indeterminateType,
+ /**
+ * The progress value (between 0 and 1).
+ */
+ progress: ReactPropTypes.number,
+ /**
+ * Color of the progress bar.
+ */
+ color: ReactPropTypes.string,
/**
* Used to locate this view in end-to-end tests.
*/
@@ -71,6 +96,7 @@ var ProgressBarAndroid = React.createClass({
getDefaultProps: function() {
return {
styleAttr: 'Large',
+ indeterminate: true
};
},
@@ -81,12 +107,6 @@ var ProgressBarAndroid = React.createClass({
},
});
-var AndroidProgressBar = createReactNativeComponentClass({
- validAttributes: {
- ...ReactNativeViewAttributes.UIView,
- styleAttr: true,
- },
- uiViewClassName: 'AndroidProgressBar',
-});
+var AndroidProgressBar = requireNativeComponent('AndroidProgressBar', ProgressBarAndroid);
module.exports = ProgressBarAndroid;
diff --git a/Libraries/Components/SliderIOS/SliderIOS.ios.js b/Libraries/Components/SliderIOS/SliderIOS.ios.js
index 0c06fbf6f..acc0ea842 100644
--- a/Libraries/Components/SliderIOS/SliderIOS.ios.js
+++ b/Libraries/Components/SliderIOS/SliderIOS.ios.js
@@ -41,6 +41,13 @@ var SliderIOS = React.createClass({
*/
value: PropTypes.number,
+ /**
+ * Step value of the slider. The value should be
+ * between 0 and (maximumValue - minimumValue).
+ * Default value is 0.
+ */
+ step: PropTypes.number,
+
/**
* Initial minimum value of the slider. Default value is 0.
*/
@@ -63,6 +70,12 @@ var SliderIOS = React.createClass({
*/
maximumTrackTintColor: PropTypes.string,
+ /**
+ * If true the user won't be able to move the slider.
+ * Default value is false.
+ */
+ disabled: PropTypes.bool,
+
/**
* Callback continuously called while the user is dragging the slider.
*/
@@ -75,27 +88,33 @@ var SliderIOS = React.createClass({
onSlidingComplete: PropTypes.func,
},
- _onValueChange: function(event: Event) {
- this.props.onChange && this.props.onChange(event);
- if (event.nativeEvent.continuous) {
- this.props.onValueChange &&
- this.props.onValueChange(event.nativeEvent.value);
- } else {
- this.props.onSlidingComplete && event.nativeEvent.value !== undefined &&
- this.props.onSlidingComplete(event.nativeEvent.value);
- }
+ getDefaultProps: function() : any {
+ return {
+ disabled: false,
+ };
},
render: function() {
+
+ let onValueChange = this.props.onValueChange && ((event: Event) => {
+ this.props.onValueChange &&
+ this.props.onValueChange(event.nativeEvent.value);
+ });
+
+ let onSlidingComplete = this.props.onSlidingComplete && ((event: Event) => {
+ this.props.onSlidingComplete &&
+ this.props.onSlidingComplete(event.nativeEvent.value);
+ });
+
+ let {style, ...props} = this.props;
+ style = [styles.slider, this.props.style];
+
return (
);
}
@@ -107,8 +126,6 @@ var styles = StyleSheet.create({
},
});
-var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS, {
- nativeOnly: { onChange: true },
-});
+var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
module.exports = SliderIOS;
diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js
index 52dc55222..f511a3804 100644
--- a/Libraries/Components/TextInput/TextInput.js
+++ b/Libraries/Components/TextInput/TextInput.js
@@ -47,6 +47,10 @@ if (Platform.OS === 'android') {
var RCTTextField = requireNativeComponent('RCTTextField', null);
}
+type DefaultProps = {
+ blurOnSubmit: boolean;
+};
+
type Event = Object;
/**
@@ -82,6 +86,11 @@ type Event = Object;
* ```
*/
var TextInput = React.createClass({
+ statics: {
+ /* TODO(brentvatne) docs are needed for this */
+ State: TextInputState,
+ },
+
propTypes: {
/**
* Can tell TextInput to automatically capitalize certain characters.
@@ -172,7 +181,6 @@ var TextInput = React.createClass({
/**
* Limits the maximum number of characters that can be entered. Use this
* instead of implementing the logic in JS to avoid flicker.
- * @platform ios
*/
maxLength: PropTypes.number,
/**
@@ -217,6 +225,13 @@ var TextInput = React.createClass({
* Callback that is called when the text input's submit button is pressed.
*/
onSubmitEditing: PropTypes.func,
+ /**
+ * Callback that is called when a key is pressed.
+ * Pressed key value is passed as an argument to the callback handler.
+ * Fires before onChange callbacks.
+ * @platform ios
+ */
+ onKeyPress: PropTypes.func,
/**
* Invoked on mount and layout changes with `{x, y, width, height}`.
*/
@@ -276,6 +291,12 @@ var TextInput = React.createClass({
* @platform ios
*/
selectTextOnFocus: PropTypes.bool,
+ /**
+ * If true, the text field will blur when submitted.
+ * The default value is true.
+ * @platform ios
+ */
+ blurOnSubmit: PropTypes.bool,
/**
* Styles
*/
@@ -291,6 +312,12 @@ var TextInput = React.createClass({
underlineColorAndroid: PropTypes.string,
},
+ getDefaultProps: function(): DefaultProps {
+ return {
+ blurOnSubmit: true,
+ };
+ },
+
/**
* `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We
* make `this` look like an actual native component class.
@@ -473,6 +500,7 @@ var TextInput = React.createClass({
mostRecentEventCount={this.state.mostRecentEventCount}
multiline={this.props.multiline}
numberOfLines={this.props.numberOfLines}
+ maxLength={this.props.maxLength}
onFocus={this._onFocus}
onBlur={this._onBlur}
onChange={this._onChange}
@@ -522,13 +550,16 @@ var TextInput = React.createClass({
this.props.onChange && this.props.onChange(event);
this.props.onChangeText && this.props.onChangeText(text);
this.setState({mostRecentEventCount: eventCount}, () => {
- // This is a controlled component, so make sure to force the native value
- // to match. Most usage shouldn't need this, but if it does this will be
- // more correct but might flicker a bit and/or cause the cursor to jump.
- if (text !== this.props.value && typeof this.props.value === 'string') {
- this.refs.input.setNativeProps({
- text: this.props.value,
- });
+ // NOTE: this doesn't seem to be needed on iOS - keeping for now in case it's required on Android
+ if (Platform.OS === 'android') {
+ // This is a controlled component, so make sure to force the native value
+ // to match. Most usage shouldn't need this, but if it does this will be
+ // more correct but might flicker a bit and/or cause the cursor to jump.
+ if (text !== this.props.value && typeof this.props.value === 'string') {
+ this.refs.input.setNativeProps({
+ text: this.props.value,
+ });
+ }
}
});
},
diff --git a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js
index 5692c9771..dcaed6b0c 100644
--- a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js
+++ b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js
@@ -103,6 +103,10 @@ var ToolbarAndroid = React.createClass({
* Callback called when the icon is selected.
*/
onIconClicked: ReactPropTypes.func,
+ /**
+ * Sets the overflow icon.
+ */
+ overflowIcon: optionalImageSource,
/**
* Sets the toolbar subtitle.
*/
@@ -135,6 +139,9 @@ var ToolbarAndroid = React.createClass({
if (this.props.navIcon) {
nativeProps.navIcon = resolveAssetSource(this.props.navIcon);
}
+ if (this.props.overflowIcon) {
+ nativeProps.overflowIcon = resolveAssetSource(this.props.overflowIcon);
+ }
if (this.props.actions) {
nativeProps.actions = [];
for (var i = 0; i < this.props.actions.length; i++) {
@@ -169,6 +176,7 @@ var toolbarAttributes = {
actions: true,
logo: true,
navIcon: true,
+ overflowIcon: true,
subtitle: true,
subtitleColor: true,
title: true,
diff --git a/Libraries/Components/Touchable/BoundingDimensions.js b/Libraries/Components/Touchable/BoundingDimensions.js
index 38934ea04..f4acffc02 100644
--- a/Libraries/Components/Touchable/BoundingDimensions.js
+++ b/Libraries/Components/Touchable/BoundingDimensions.js
@@ -20,6 +20,11 @@ function BoundingDimensions(width, height) {
this.height = height;
}
+BoundingDimensions.prototype.destructor = function() {
+ this.width = null;
+ this.height = null;
+};
+
/**
* @param {HTMLElement} element Element to return `BoundingDimensions` for.
* @return {BoundingDimensions} Bounding dimensions of `element`.
diff --git a/Libraries/Components/Touchable/Position.js b/Libraries/Components/Touchable/Position.js
index d6ae0b490..6175b6b9e 100644
--- a/Libraries/Components/Touchable/Position.js
+++ b/Libraries/Components/Touchable/Position.js
@@ -21,6 +21,11 @@ function Position(left, top) {
this.top = top;
}
+Position.prototype.destructor = function() {
+ this.left = null;
+ this.top = null;
+};
+
PooledClass.addPoolingTo(Position, twoArgumentPooler);
module.exports = Position;
diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js
index 7cb8d35f6..3f62f027c 100644
--- a/Libraries/Components/View/View.js
+++ b/Libraries/Components/View/View.js
@@ -178,7 +178,6 @@ var View = React.createClass({
* `TouchableHighlight` or `TouchableOpacity`. Check out `Touchable.js`,
* `ScrollResponder.js` and `ResponderEventPlugin.js` for more discussion.
*/
- onMoveShouldSetResponder: PropTypes.func,
onResponderGrant: PropTypes.func,
onResponderMove: PropTypes.func,
onResponderReject: PropTypes.func,
@@ -187,6 +186,8 @@ var View = React.createClass({
onResponderTerminationRequest: PropTypes.func,
onStartShouldSetResponder: PropTypes.func,
onStartShouldSetResponderCapture: PropTypes.func,
+ onMoveShouldSetResponder: PropTypes.func,
+ onMoveShouldSetResponderCapture: PropTypes.func,
/**
* Invoked on mount and layout changes with
diff --git a/Libraries/Components/ViewPager/ViewPagerAndroid.android.js b/Libraries/Components/ViewPager/ViewPagerAndroid.android.js
index 36c60820c..45b109134 100644
--- a/Libraries/Components/ViewPager/ViewPagerAndroid.android.js
+++ b/Libraries/Components/ViewPager/ViewPagerAndroid.android.js
@@ -125,7 +125,10 @@ var ViewPagerAndroid = React.createClass({
}],
collapsable: false,
};
- if (child.type && child.type.displayName && (child.type.displayName !== 'View')) {
+ if (child.type &&
+ child.type.displayName &&
+ (child.type.displayName !== 'RCTView') &&
+ (child.type.displayName !== 'View')) {
console.warn('Each ViewPager child must be a . Was ' + child.type.displayName);
}
return ReactElement.createElement(child.type, newProps);
diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js
index 274d76728..7975653a3 100644
--- a/Libraries/Components/WebView/WebView.android.js
+++ b/Libraries/Components/WebView/WebView.android.js
@@ -31,6 +31,10 @@ var WebViewState = keyMirror({
ERROR: null,
});
+/**
+ * Note that WebView is only supported on iOS for now,
+ * see https://facebook.github.io/react-native/docs/known-issues.html
+ */
var WebView = React.createClass({
propTypes: {
diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js
index a3d457bfd..9256c3c86 100644
--- a/Libraries/Components/WebView/WebView.ios.js
+++ b/Libraries/Components/WebView/WebView.ios.js
@@ -75,6 +75,12 @@ var defaultRenderError = (errorDomain, errorCode, errorDesc) => (
);
+/**
+ * Renders a native WebView.
+ *
+ * Note that WebView is only supported on iOS for now,
+ * see https://facebook.github.io/react-native/docs/known-issues.html
+ */
var WebView = React.createClass({
statics: {
JSNavigationScheme: JSNavigationScheme,
@@ -107,6 +113,12 @@ var WebView = React.createClass({
* user can change the scale
*/
scalesPageToFit: PropTypes.bool,
+
+ /**
+ * Allows custom handling of any webview requests by a JS handler. Return true
+ * or false from this method to continue loading the request.
+ */
+ onShouldStartLoadWithRequest: PropTypes.func,
},
getInitialState: function() {
@@ -152,6 +164,12 @@ var WebView = React.createClass({
webViewStyles.push(styles.hidden);
}
+ var onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => {
+ var shouldStart = this.props.onShouldStartLoadWithRequest &&
+ this.props.onShouldStartLoadWithRequest(event.nativeEvent);
+ RCTWebViewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
+ });
+
var webView =
;
diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js
index 98cf211d0..d8edc9dd6 100644
--- a/Libraries/CustomComponents/ListView/ListView.js
+++ b/Libraries/CustomComponents/ListView/ListView.js
@@ -277,6 +277,7 @@ var ListView = React.createClass({
componentWillReceiveProps: function(nextProps) {
if (this.props.dataSource !== nextProps.dataSource) {
+ this._sentEndForContentLength = null;
this.setState((state, props) => {
var rowsToRender = Math.min(
state.curRenderedRowsCount + props.pageSize,
@@ -500,9 +501,11 @@ var ListView = React.createClass({
},
_getDistanceFromEnd: function(scrollProperties) {
- return scrollProperties.contentLength -
- scrollProperties.visibleLength -
- scrollProperties.offset;
+ var maxLength = Math.max(
+ scrollProperties.contentLength,
+ scrollProperties.visibleLength
+ );
+ return maxLength - scrollProperties.visibleLength - scrollProperties.offset;
},
_updateVisibleRows: function(updatedFrames) {
@@ -590,6 +593,12 @@ var ListView = React.createClass({
this._renderMoreRowsIfNeeded();
}
+ if (this.props.onEndReached &&
+ this._getDistanceFromEnd(this.scrollProperties) > this.props.onEndReachedThreshold) {
+ // Scrolled out of the end zone, so it should be able to trigger again.
+ this._sentEndForContentLength = null;
+ }
+
this.props.onScroll && this.props.onScroll(e);
},
});
diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
index dfce209bf..5a0c35f37 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
@@ -30,6 +30,8 @@ var NavigationEvent = require('NavigationEvent');
var NavigationEventEmitter = require('NavigationEventEmitter');
var NavigationTreeNode = require('NavigationTreeNode');
+var Set = require('Set');
+
var emptyFunction = require('emptyFunction');
var invariant = require('invariant');
@@ -41,6 +43,13 @@ var {
CAPTURING_PHASE,
} = NavigationEvent;
+// Event types that do not support event bubbling, capturing and
+// reconciliation API (e.g event.preventDefault(), event.stopPropagation()).
+var LegacyEventTypes = new Set([
+ 'willfocus',
+ 'didfocus',
+]);
+
/**
* Class that contains the info and methods for app navigation.
*/
@@ -63,8 +72,8 @@ class NavigationContext {
this._emitCounter = 0;
this._emitQueue = [];
- this.addListener('willfocus', this._onFocus, this);
- this.addListener('didfocus', this._onFocus, this);
+ this.addListener('willfocus', this._onFocus);
+ this.addListener('didfocus', this._onFocus);
}
/* $FlowFixMe - get/set properties not yet supported */
@@ -73,6 +82,17 @@ class NavigationContext {
return parent ? parent.getValue() : null;
}
+ /* $FlowFixMe - get/set properties not yet supported */
+ get top(): ?NavigationContext {
+ var result = null;
+ var parentNode = this.__node.getParent();
+ while (parentNode) {
+ result = parentNode.getValue();
+ parentNode = parentNode.getParent();
+ }
+ return result;
+ }
+
/* $FlowFixMe - get/set properties not yet supported */
get currentRoute(): any {
return this._currentRoute;
@@ -85,14 +105,18 @@ class NavigationContext {
addListener(
eventType: string,
listener: Function,
- context: ?Object,
useCapture: ?boolean
): EventSubscription {
+ if (LegacyEventTypes.has(eventType)) {
+ useCapture = false;
+ }
+
var emitter = useCapture ?
this._captureEventEmitter :
this._bubbleEventEmitter;
+
if (emitter) {
- return emitter.addListener(eventType, listener, context);
+ return emitter.addListener(eventType, listener, this);
} else {
return {remove: emptyFunction};
}
@@ -109,50 +133,65 @@ class NavigationContext {
this._emitCounter++;
- var targets = [this];
- var parentTarget = this.parent;
- while (parentTarget) {
- targets.unshift(parentTarget);
- parentTarget = parentTarget.parent;
+ if (LegacyEventTypes.has(eventType)) {
+ // Legacy events does not support event bubbling and reconciliation.
+ this.__emit(
+ eventType,
+ data,
+ null,
+ {
+ defaultPrevented: false,
+ eventPhase: AT_TARGET,
+ propagationStopped: true,
+ target: this,
+ }
+ );
+ } else {
+ var targets = [this];
+ var parentTarget = this.parent;
+ while (parentTarget) {
+ targets.unshift(parentTarget);
+ parentTarget = parentTarget.parent;
+ }
+
+ var propagationStopped = false;
+ var defaultPrevented = false;
+ var callback = (event) => {
+ propagationStopped = propagationStopped || event.isPropagationStopped();
+ defaultPrevented = defaultPrevented || event.defaultPrevented;
+ };
+
+ // Capture phase
+ targets.some((currentTarget) => {
+ if (propagationStopped) {
+ return true;
+ }
+
+ var extraInfo = {
+ defaultPrevented,
+ eventPhase: CAPTURING_PHASE,
+ propagationStopped,
+ target: this,
+ };
+
+ currentTarget.__emit(eventType, data, callback, extraInfo);
+ }, this);
+
+ // bubble phase
+ targets.reverse().some((currentTarget) => {
+ if (propagationStopped) {
+ return true;
+ }
+ var extraInfo = {
+ defaultPrevented,
+ eventPhase: BUBBLING_PHASE,
+ propagationStopped,
+ target: this,
+ };
+ currentTarget.__emit(eventType, data, callback, extraInfo);
+ }, this);
}
- var propagationStopped = false;
- var defaultPrevented = false;
- var callback = (event) => {
- propagationStopped = propagationStopped || event.isPropagationStopped();
- defaultPrevented = defaultPrevented || event.defaultPrevented;
- };
-
- // capture phase
- targets.some((currentTarget) => {
- if (propagationStopped) {
- return true;
- }
-
- var extraInfo = {
- defaultPrevented,
- eventPhase: CAPTURING_PHASE,
- propagationStopped,
- target: this,
- };
-
- currentTarget.__emit(eventType, data, callback, extraInfo);
- }, this);
-
- // bubble phase
- targets.reverse().some((currentTarget) => {
- if (propagationStopped) {
- return true;
- }
- var extraInfo = {
- defaultPrevented,
- eventPhase: BUBBLING_PHASE,
- propagationStopped,
- target: this,
- };
- currentTarget.__emit(eventType, data, callback, extraInfo);
- }, this);
-
if (didEmitCallback) {
var event = NavigationEvent.pool(eventType, this, data);
propagationStopped && event.stopPropagation();
@@ -189,9 +228,15 @@ class NavigationContext {
case CAPTURING_PHASE: // phase = 1
emitter = this._captureEventEmitter;
break;
+
+ case AT_TARGET: // phase = 2
+ emitter = this._bubbleEventEmitter;
+ break;
+
case BUBBLING_PHASE: // phase = 3
emitter = this._bubbleEventEmitter;
break;
+
default:
invariant(false, 'invalid event phase %s', extraInfo.eventPhase);
}
@@ -214,8 +259,10 @@ class NavigationContext {
_onFocus(event: NavigationEvent): void {
invariant(
event.data && event.data.hasOwnProperty('route'),
- 'didfocus event should provide route'
+ 'event type "%s" should provide route',
+ event.type
);
+
this._currentRoute = event.data.route;
}
}
diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationContext-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationContext-test.js
index 37eb820f8..5d5ea4813 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationContext-test.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationContext-test.js
@@ -50,6 +50,15 @@ describe('NavigationContext', () => {
expect(child.parent).toBe(parent);
});
+ it('has `top`', () => {
+ var top = new NavigationContext();
+ var parent = new NavigationContext();
+ var child = new NavigationContext();
+ top.appendChild(parent);
+ parent.appendChild(child);
+ expect(child.top).toBe(top);
+ });
+
it('captures event', () => {
var parent = new NavigationContext();
var child = new NavigationContext();
@@ -67,8 +76,8 @@ describe('NavigationContext', () => {
});
};
- parent.addListener('yo', listener, null, true);
- child.addListener('yo', listener, null, true);
+ parent.addListener('yo', listener, true);
+ child.addListener('yo', listener, true);
child.emit('yo');
@@ -133,8 +142,8 @@ describe('NavigationContext', () => {
var counter = 0;
- parent.addListener('yo', event => event.stopPropagation(), null, true);
- child.addListener('yo', event => counter++, null, true);
+ parent.addListener('yo', event => event.stopPropagation(), true);
+ child.addListener('yo', event => counter++, true);
child.emit('yo');
@@ -162,8 +171,8 @@ describe('NavigationContext', () => {
parent.appendChild(child);
var val;
- parent.addListener('yo', event => event.preventDefault(), null, true);
- child.addListener('yo', event => val = event.defaultPrevented, null, true);
+ parent.addListener('yo', event => event.preventDefault(), true);
+ child.addListener('yo', event => val = event.defaultPrevented, true);
child.emit('yo');
@@ -205,9 +214,9 @@ describe('NavigationContext', () => {
child.emit('didyo');
});
- parent.addListener('yo', listener, null, true);
- parent.addListener('didyo', listener, null, true);
- child.addListener('yo', listener, null, true);
+ parent.addListener('yo', listener, true);
+ parent.addListener('didyo', listener, true);
+ child.addListener('yo', listener, true);
child.emit('yo');
diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js
index b00d99a5d..8bfd55457 100644
--- a/Libraries/CustomComponents/Navigator/Navigator.js
+++ b/Libraries/CustomComponents/Navigator/Navigator.js
@@ -273,6 +273,8 @@ var Navigator = React.createClass({
},
getInitialState: function() {
+ this._navigationBarNavigator = this.props.navigationBarNavigator || this;
+
this._renderedSceneMap = new Map();
var routeStack = this.props.initialRouteStack || [this.props.initialRoute];
@@ -346,6 +348,12 @@ var Navigator = React.createClass({
this._navigationContext.dispose();
this._navigationContext = null;
}
+
+ this.spring.destroy();
+
+ if (this._interactionHandle) {
+ this.clearInteractionHandle(this._interactionHandle);
+ }
},
/**
@@ -411,6 +419,9 @@ var Navigator = React.createClass({
* happening, we only set values for the transition and the gesture will catch up later
*/
_handleSpringUpdate: function() {
+ if (!this.isMounted()) {
+ return;
+ }
// Prioritize handling transition in progress over a gesture:
if (this.state.transitionFromIndex != null) {
this._transitionBetween(
@@ -432,6 +443,10 @@ var Navigator = React.createClass({
* This happens at the end of a transition started by transitionTo, and when the spring catches up to a pending gesture
*/
_completeTransition: function() {
+ if (!this.isMounted()) {
+ return;
+ }
+
if (this.spring.getCurrentValue() !== 1 && this.spring.getCurrentValue() !== 0) {
// The spring has finished catching up to a gesture in progress. Remove the pending progress
// and we will be in a normal activeGesture state
@@ -591,8 +606,11 @@ var Navigator = React.createClass({
_handleMoveShouldSetPanResponder: function(e, gestureState) {
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
+ if (!sceneConfig) {
+ return false;
+ }
this._expectingGestureGrant = this._matchGestureAction(this._eligibleGestures, sceneConfig.gestures, gestureState);
- return !! this._expectingGestureGrant;
+ return !!this._expectingGestureGrant;
},
_doesGestureOverswipe: function(gestureName) {
@@ -1070,7 +1088,7 @@ var Navigator = React.createClass({
}
return React.cloneElement(this.props.navigationBar, {
ref: (navBar) => { this._navBar = navBar; },
- navigator: this,
+ navigator: this._navigationBarNavigator,
navState: this.state,
});
},
diff --git a/Libraries/Devtools/setupDevtools.js b/Libraries/Devtools/setupDevtools.js
index 93ba87555..ed85e79d9 100644
--- a/Libraries/Devtools/setupDevtools.js
+++ b/Libraries/Devtools/setupDevtools.js
@@ -14,7 +14,7 @@
function setupDevtools() {
var messageListeners = [];
var closeListeners = [];
- var ws = new window.WebSocket('ws://localhost:8081/devtools');
+ var ws = new window.WebSocket('ws://localhost:8097/devtools');
// this is accessed by the eval'd backend code
var FOR_BACKEND = { // eslint-disable-line no-unused-vars
resolveRNStyle: require('flattenStyle'),
@@ -31,11 +31,11 @@ function setupDevtools() {
},
};
ws.onclose = () => {
- console.warn('devtools socket closed');
+ setTimeout(setupDevtools, 200);
closeListeners.forEach(fn => fn());
};
ws.onerror = error => {
- console.warn('devtools socket errored', error);
+ setTimeout(setupDevtools, 200);
closeListeners.forEach(fn => fn());
};
ws.onopen = function () {
@@ -58,7 +58,7 @@ function setupDevtools() {
// FOR_BACKEND is used by the eval'd code
eval(text); // eslint-disable-line no-eval
} catch (e) {
- console.error('Failed to eval' + e.message);
+ console.error('Failed to eval: ' + e.message);
return;
}
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
diff --git a/Libraries/Fetch/fetch.js b/Libraries/Fetch/fetch.js
index 68c98a90d..ff5ef29bb 100644
--- a/Libraries/Fetch/fetch.js
+++ b/Libraries/Fetch/fetch.js
@@ -278,6 +278,10 @@ var self = {};
}
this._initBody(body)
}
+
+ Request.prototype.clone = function() {
+ return new Request(this)
+ }
function decode(body) {
var form = new FormData()
@@ -320,6 +324,15 @@ var self = {};
this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
this.url = options.url || ''
}
+
+ Response.prototype.clone = function() {
+ return new Response(this._bodyInit, {
+ status: this.status,
+ statusText: this.statusText,
+ headers: new Headers(this.headers),
+ url: this.url
+ })
+ }
Body.call(Response.prototype)
diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m
index ebb66f4e1..b97011630 100644
--- a/Libraries/Geolocation/RCTLocationObserver.m
+++ b/Libraries/Geolocation/RCTLocationObserver.m
@@ -100,7 +100,7 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /
{
CLLocationManager *_locationManager;
NSDictionary *_lastLocationEvent;
- NSMutableArray *_pendingRequests;
+ NSMutableArray *_pendingRequests;
BOOL _observingLocation;
RCTLocationOptions _observerOptions;
}
@@ -249,7 +249,8 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
#pragma mark - CLLocationManagerDelegate
-- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
+- (void)locationManager:(CLLocationManager *)manager
+ didUpdateLocations:(NSArray *)locations
{
// Create event
CLLocation *location = locations.lastObject;
diff --git a/Libraries/Image/RCTGIFImageDecoder.m b/Libraries/Image/RCTGIFImageDecoder.m
index 899a266e8..473210025 100644
--- a/Libraries/Image/RCTGIFImageDecoder.m
+++ b/Libraries/Image/RCTGIFImageDecoder.m
@@ -42,8 +42,8 @@ RCT_EXPORT_MODULE()
if (imageCount > 1) {
NSTimeInterval duration = 0;
- NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
- NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
+ NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
+ NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
for (size_t i = 0; i < imageCount; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
@@ -75,7 +75,7 @@ RCT_EXPORT_MODULE()
}
CFRelease(imageSource);
- NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
+ NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
NSTimeInterval runningDuration = 0;
for (NSNumber *delayNumber in delays) {
[keyTimes addObject:@(runningDuration / duration)];
diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h
index f6bf9b3e8..770747452 100644
--- a/Libraries/Image/RCTImageLoader.h
+++ b/Libraries/Image/RCTImageLoader.h
@@ -28,7 +28,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
/**
* Loads the specified image at the highest available resolution.
- * Can be called from any thread, will always call callback on main thread.
+ * Can be called from any thread, will call back on an unspecified thread.
*/
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
callback:(RCTImageLoaderCompletionBlock)callback;
diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m
index ca1092dce..753f58b35 100644
--- a/Libraries/Image/RCTImageLoader.m
+++ b/Libraries/Image/RCTImageLoader.m
@@ -9,6 +9,7 @@
#import "RCTImageLoader.h"
+#import
#import
#import "RCTConvert.h"
@@ -18,17 +19,6 @@
#import "RCTNetworking.h"
#import "RCTUtils.h"
-static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSError *error, UIImage *image)
-{
- if ([NSThread isMainThread]) {
- callback(error, image);
- } else {
- dispatch_async(dispatch_get_main_queue(), ^{
- callback(error, image);
- });
- }
-}
-
@implementation UIImage (React)
- (CAKeyframeAnimation *)reactKeyframeAnimation
@@ -45,8 +35,8 @@ static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSEr
@implementation RCTImageLoader
{
- NSArray *_loaders;
- NSArray *_decoders;
+ NSArray> *_loaders;
+ NSArray> *_decoders;
NSURLCache *_cache;
}
@@ -57,14 +47,14 @@ RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge
{
// Get image loaders and decoders
- NSMutableArray *loaders = [NSMutableArray array];
- NSMutableArray *decoders = [NSMutableArray array];
+ NSMutableArray> *loaders = [NSMutableArray array];
+ NSMutableArray> *decoders = [NSMutableArray array];
for (id module in bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
- [loaders addObject:module];
+ [loaders addObject:(id)module];
}
if ([module conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
- [decoders addObject:module];
+ [decoders addObject:(id)module];
}
}
@@ -184,7 +174,7 @@ RCT_EXPORT_MODULE()
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
- progressBlock:(RCTImageLoaderProgressBlock)progressBlock
+ progressBlock:(RCTImageLoaderProgressBlock)progressHandler
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
if (imageTag.length == 0) {
@@ -192,23 +182,20 @@ RCT_EXPORT_MODULE()
return ^{};
}
- // Ensure progress is dispatched on main thread
- RCTImageLoaderProgressBlock progressHandler = nil;
- if (progressBlock) {
- progressHandler = ^(int64_t progress, int64_t total) {
- if ([NSThread isMainThread]) {
- progressBlock(progress, total);
- } else {
- dispatch_async(dispatch_get_main_queue(), ^{
- progressBlock(progress, total);
- });
- }
- };
- }
-
- // Ensure completion is dispatched on main thread
+ __block volatile uint32_t cancelled = 0;
RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *image) {
- RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
+ if ([NSThread isMainThread]) {
+
+ // Most loaders do not return on the main thread, so caller is probably not
+ // expecting it, and may do expensive post-processing in the callback
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ if (!cancelled) {
+ completionBlock(error, image);
+ }
+ });
+ } else if (!cancelled) {
+ completionBlock(error, image);
+ }
};
// Find suitable image URL loader
@@ -220,7 +207,7 @@ RCT_EXPORT_MODULE()
scale:scale
resizeMode:resizeMode
progressHandler:progressHandler
- completionHandler:completionHandler];
+ completionHandler:completionHandler] ?: ^{};
}
// Check if networking module is available
@@ -237,7 +224,16 @@ RCT_EXPORT_MODULE()
__weak RCTImageLoader *weakSelf = self;
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
RCTURLRequestCompletionBlock processResponse =
- ^(NSURLResponse *response, NSData *data, __unused NSError *error) {
+ ^(NSURLResponse *response, NSData *data, NSError *error) {
+
+ // Check for system errors
+ if (error) {
+ completionHandler(error, nil);
+ return;
+ } else if (!data) {
+ completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil);
+ return;
+ }
// Check for http errors
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
@@ -296,9 +292,7 @@ RCT_EXPORT_MODULE()
processResponse(response, data, nil);
}];
- if (progressBlock) {
- task.downloadProgressBlock = progressBlock;
- }
+ task.downloadProgressBlock = progressHandler;
[task start];
return ^{
@@ -306,6 +300,7 @@ RCT_EXPORT_MODULE()
if (decodeCancel) {
decodeCancel();
}
+ OSAtomicOr32Barrier(1, &cancelled);
};
}
@@ -317,27 +312,36 @@ RCT_EXPORT_MODULE()
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
- completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
+ completionBlock:(RCTImageLoaderCompletionBlock)completionHandler
{
id imageDecoder = [self imageDataDecoderForData:data];
if (imageDecoder) {
+
return [imageDecoder decodeImageData:data
size:size
scale:scale
resizeMode:resizeMode
- completionHandler:completionBlock];
+ completionHandler:completionHandler];
} else {
+
+ __block volatile uint32_t cancelled = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ if (cancelled) {
+ return;
+ }
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image) {
- completionBlock(nil, image);
+ completionHandler(nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data ", data, data.length];
NSError *finalError = RCTErrorWithMessage(errorMessage);
- completionBlock(finalError, nil);
+ completionHandler(finalError, nil);
}
});
- return ^{};
+
+ return ^{
+ OSAtomicOr32Barrier(1, &cancelled);
+ };
}
}
diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m
index 049eb36ac..4f4a65d33 100644
--- a/Libraries/Image/RCTImageView.m
+++ b/Libraries/Image/RCTImageView.m
@@ -183,24 +183,26 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
resizeMode:self.contentMode
progressBlock:progressHandler
completionBlock:^(NSError *error, UIImage *image) {
- if (image.reactKeyframeAnimation) {
- [self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
- } else {
- [self.layer removeAnimationForKey:@"contents"];
- self.image = image;
- }
- if (error) {
- if (_onError) {
- _onError(@{ @"error": error.localizedDescription });
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (image.reactKeyframeAnimation) {
+ [self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
+ } else {
+ [self.layer removeAnimationForKey:@"contents"];
+ self.image = image;
}
- } else {
- if (_onLoad) {
- _onLoad(nil);
+ if (error) {
+ if (_onError) {
+ _onError(@{ @"error": error.localizedDescription });
+ }
+ } else {
+ if (_onLoad) {
+ _onLoad(nil);
+ }
}
- }
- if (_onLoadEnd) {
- _onLoadEnd(nil);
- }
+ if (_onLoadEnd) {
+ _onLoadEnd(nil);
+ }
+ });
}];
} else {
[self clearImage];
diff --git a/Libraries/Image/RCTShadowVirtualImage.m b/Libraries/Image/RCTShadowVirtualImage.m
index c908b62df..431fbac07 100644
--- a/Libraries/Image/RCTShadowVirtualImage.m
+++ b/Libraries/Image/RCTShadowVirtualImage.m
@@ -38,7 +38,13 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
CGFloat scale = [RCTConvert CGFloat:_source[@"scale"]] ?: 1;
__weak RCTShadowVirtualImage *weakSelf = self;
- [_bridge.imageLoader loadImageWithTag:imageTag size:CGSizeZero scale:scale resizeMode:UIViewContentModeScaleToFill progressBlock:nil completionBlock:^(NSError *error, UIImage *image) {
+ [_bridge.imageLoader loadImageWithTag:imageTag
+ size:CGSizeZero
+ scale:scale
+ resizeMode:UIViewContentModeScaleToFill
+ progressBlock:nil
+ completionBlock:^(NSError *error, UIImage *image) {
+
dispatch_async(_bridge.uiManager.methodQueue, ^{
RCTShadowVirtualImage *strongSelf = weakSelf;
strongSelf->_image = image;
diff --git a/Libraries/Image/RCTXCAssetImageLoader.m b/Libraries/Image/RCTXCAssetImageLoader.m
index 93a02bce3..799798a36 100644
--- a/Libraries/Image/RCTXCAssetImageLoader.m
+++ b/Libraries/Image/RCTXCAssetImageLoader.m
@@ -9,6 +9,8 @@
#import "RCTXCAssetImageLoader.h"
+#import
+
#import "RCTUtils.h"
@implementation RCTXCAssetImageLoader
@@ -20,34 +22,34 @@ RCT_EXPORT_MODULE()
return RCTIsXCAssetURL(requestURL);
}
- - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
+ - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
+ size:(CGSize)size
+ scale:(CGFloat)scale
+ resizeMode:(UIViewContentMode)resizeMode
+ progressHandler:(RCTImageLoaderProgressBlock)progressHandler
+ completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
- __block BOOL cancelled = NO;
+ __block volatile uint32_t cancelled = 0;
dispatch_async(dispatch_get_main_queue(), ^{
+
if (cancelled) {
return;
}
-
NSString *imageName = RCTBundlePathForURL(imageURL);
UIImage *image = [UIImage imageNamed:imageName];
if (image) {
if (progressHandler) {
progressHandler(1, 1);
}
-
- if (completionHandler) {
- completionHandler(nil, image);
- }
+ completionHandler(nil, image);
} else {
- if (completionHandler) {
- NSString *message = [NSString stringWithFormat:@"Could not find image named %@", imageName];
- completionHandler(RCTErrorWithMessage(message), nil);
- }
+ NSString *message = [NSString stringWithFormat:@"Could not find image named %@", imageName];
+ completionHandler(RCTErrorWithMessage(message), nil);
}
});
return ^{
- cancelled = YES;
+ OSAtomicOr32Barrier(1, &cancelled);
};
}
diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js
index 6c1029ed7..81fdb2b08 100644
--- a/Libraries/Image/__tests__/resolveAssetSource-test.js
+++ b/Libraries/Image/__tests__/resolveAssetSource-test.js
@@ -107,7 +107,7 @@ describe('resolveAssetSource', () => {
describe('bundle was loaded from file on iOS', () => {
beforeEach(() => {
NativeModules.SourceCode.scriptURL =
- 'file:///Path/To/Simulator/main.bundle';
+ 'file:///Path/To/Sample.app/main.bundle';
Platform.OS = 'ios';
});
@@ -126,7 +126,7 @@ describe('resolveAssetSource', () => {
__packager_asset: true,
width: 100,
height: 200,
- uri: 'assets/module/a/logo.png',
+ uri: '/Path/To/Sample.app/assets/module/a/logo.png',
scale: 1,
});
});
diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js
index a278a708f..638b72ce0 100644
--- a/Libraries/Image/resolveAssetSource.js
+++ b/Libraries/Image/resolveAssetSource.js
@@ -26,7 +26,7 @@ var PixelRatio = require('PixelRatio');
var Platform = require('Platform');
var SourceCode = require('NativeModules').SourceCode;
-var _serverURL;
+var _serverURL, _offlinePath;
function getDevServerURL() {
if (_serverURL === undefined) {
@@ -44,6 +44,20 @@ function getDevServerURL() {
return _serverURL;
}
+function getOfflinePath() {
+ if (_offlinePath === undefined) {
+ var scriptURL = SourceCode.scriptURL;
+ var match = scriptURL && scriptURL.match(/^file:\/\/(\/.*\/)/);
+ if (match) {
+ _offlinePath = match[1];
+ } else {
+ _offlinePath = '';
+ }
+ }
+
+ return _offlinePath;
+}
+
/**
* Returns the path at which the asset can be found in the archive
*/
@@ -59,7 +73,7 @@ function getPathInArchive(asset) {
.replace(/^assets_/, ''); // Remove "assets_" prefix
} else {
// E.g. 'assets/AwesomeModule/icon@2x.png'
- return getScaledAssetPath(asset);
+ return getOfflinePath() + getScaledAssetPath(asset);
}
}
diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js
index 41b94be0b..8e652caf0 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js
@@ -21,12 +21,13 @@ var sourceMapPromise;
var exceptionID = 0;
-function reportException(e: Error, isFatal: bool, stack?: any) {
+/**
+ * Handles the developer-visible aspect of errors and exceptions
+ */
+function reportException(e: Error, isFatal: bool) {
var currentExceptionID = ++exceptionID;
if (RCTExceptionsManager) {
- if (!stack) {
- stack = parseErrorStack(e);
- }
+ var stack = parseErrorStack(e);
if (isFatal) {
RCTExceptionsManager.reportFatalException(e.message, stack, currentExceptionID);
} else {
@@ -47,6 +48,9 @@ function reportException(e: Error, isFatal: bool, stack?: any) {
}
}
+/**
+ * Logs exceptions to the (native) console and displays them
+ */
function handleException(e: Error, isFatal: boolean) {
// Workaround for reporting errors caused by `throw 'some string'`
// Unfortunately there is no way to figure out the stacktrace in this
@@ -55,19 +59,9 @@ function handleException(e: Error, isFatal: boolean) {
if (!e.message) {
e = new Error(e);
}
- var stack = parseErrorStack(e);
- var msg =
- 'Error: ' + e.message +
- '\n stack: \n' + stackToString(stack) +
- '\n URL: ' + (e: any).sourceURL +
- '\n line: ' + (e: any).line +
- '\n message: ' + e.message;
- if (console.errorOriginal) {
- console.errorOriginal(msg);
- } else {
- console.error(msg);
- }
- reportException(e, isFatal, stack);
+
+ (console._errorOriginal || console.error)(e.message);
+ reportException(e, isFatal);
}
/**
@@ -75,54 +69,35 @@ function handleException(e: Error, isFatal: boolean) {
* setting `console.reportErrorsAsExceptions = false;` in your app.
*/
function installConsoleErrorReporter() {
- if (console.reportException) {
+ // Enable reportErrorsAsExceptions
+ if (console._errorOriginal) {
return; // already installed
}
- console.reportException = reportException;
- console.errorOriginal = console.error.bind(console);
+ console._errorOriginal = console.error.bind(console);
console.error = function reactConsoleError() {
- // Note that when using the built-in context executor on iOS (i.e., not
- // Chrome debugging), console.error is already stubbed out to cause a
- // redbox via RCTNativeLoggingHook.
- console.errorOriginal.apply(null, arguments);
+ console._errorOriginal.apply(null, arguments);
if (!console.reportErrorsAsExceptions) {
return;
}
- var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
- if (str.slice(0, 10) === '"Warning: ') {
- // React warnings use console.error so that a stack trace is shown, but
- // we don't (currently) want these to show a redbox
- // (Note: Logic duplicated in polyfills/console.js.)
- return;
+
+ if (arguments[0] && arguments[0].stack) {
+ reportException(arguments[0], /* isFatal */ false);
+ } else {
+ var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
+ if (str.slice(0, 10) === '"Warning: ') {
+ // React warnings use console.error so that a stack trace is shown, but
+ // we don't (currently) want these to show a redbox
+ // (Note: Logic duplicated in polyfills/console.js.)
+ return;
+ }
+ var error : any = new Error('console.error: ' + str);
+ error.framesToPop = 1;
+ reportException(error, /* isFatal */ false);
}
- var error: any = new Error('console.error: ' + str);
- error.framesToPop = 1;
- reportException(error, /* isFatal */ false);
};
if (console.reportErrorsAsExceptions === undefined) {
console.reportErrorsAsExceptions = true; // Individual apps can disable this
}
}
-function stackToString(stack) {
- var maxLength = Math.max.apply(null, stack.map(frame => frame.methodName.length));
- return stack.map(frame => stackFrameToString(frame, maxLength)).join('\n');
-}
-
-function stackFrameToString(stackFrame, maxLength) {
- var fileNameParts = stackFrame.file.split('/');
- var fileName = fileNameParts[fileNameParts.length - 1];
-
- if (fileName.length > 18) {
- fileName = fileName.substr(0, 17) + '\u2026'; /* ... */
- }
-
- var spaces = fillSpaces(maxLength - stackFrame.methodName.length);
- return ' ' + stackFrame.methodName + spaces + ' ' + fileName + ':' + stackFrame.lineNumber;
-}
-
-function fillSpaces(n) {
- return new Array(n + 1).join(' ');
-}
-
module.exports = { handleException, installConsoleErrorReporter };
diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js
index bc10123ac..a38350e86 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js
@@ -22,10 +22,6 @@
/* eslint strict: 0 */
/* globals GLOBAL: true, window: true */
-// Just to make sure the JS gets packaged up.
-require('RCTDebugComponentOwnership');
-require('RCTDeviceEventEmitter');
-require('PerformanceLogger');
require('regenerator/runtime');
if (typeof GLOBAL === 'undefined') {
@@ -36,12 +32,10 @@ if (typeof window === 'undefined') {
window = GLOBAL;
}
-function handleError(e, isFatal) {
- try {
- require('ExceptionsManager').handleException(e, isFatal);
- } catch(ee) {
- console.log('Failed to print error: ', ee.message);
- }
+function setUpConsole() {
+ // ExceptionsManager transitively requires Promise so we install it after
+ var ExceptionsManager = require('ExceptionsManager');
+ ExceptionsManager.installConsoleErrorReporter();
}
/**
@@ -76,21 +70,19 @@ function polyfillGlobal(name, newValue, scope=GLOBAL) {
Object.defineProperty(scope, name, {...descriptor, value: newValue});
}
-function setUpRedBoxErrorHandler() {
+function setUpErrorHandler() {
+ function handleError(e, isFatal) {
+ try {
+ require('ExceptionsManager').handleException(e, isFatal);
+ } catch(ee) {
+ console.log('Failed to print error: ', ee.message);
+ }
+ }
+
var ErrorUtils = require('ErrorUtils');
ErrorUtils.setGlobalHandler(handleError);
}
-function setUpRedBoxConsoleErrorHandler() {
- // ExceptionsManager transitively requires Promise so we install it after
- var ExceptionsManager = require('ExceptionsManager');
- var Platform = require('Platform');
- // TODO (#6925182): Enable console.error redbox on Android
- if (__DEV__ && Platform.OS === 'ios') {
- ExceptionsManager.installConsoleErrorReporter();
- }
-}
-
function setUpFlowChecker() {
if (__DEV__) {
var checkFlowAtRuntime = require('checkFlowAtRuntime');
@@ -163,8 +155,6 @@ function setUpWebSockets() {
}
function setUpProfile() {
- console.profile = console.profile || GLOBAL.nativeTraceBeginSection || function () {};
- console.profileEnd = console.profileEnd || GLOBAL.nativeTraceEndSection || function () {};
if (__DEV__) {
require('BridgeProfiling').swizzleReactPerf();
}
@@ -184,15 +174,30 @@ function setUpNumber() {
Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -(Math.pow(2, 53) - 1);
}
-setUpRedBoxErrorHandler();
+function setUpDevTools() {
+ // not when debugging in chrome
+ if (__DEV__ && !window.document && require('Platform').OS === 'ios') {
+ var setupDevtools = require('setupDevtools');
+ setupDevtools();
+ }
+}
+
+setUpProcessEnv();
+setUpConsole();
setUpTimers();
setUpAlert();
setUpPromise();
+setUpErrorHandler();
setUpXHR();
-setUpRedBoxConsoleErrorHandler();
setUpGeolocation();
setUpWebSockets();
setUpProfile();
-setUpProcessEnv();
setUpFlowChecker();
setUpNumber();
+setUpDevTools();
+
+// Just to make sure the JS gets packaged up. Wait until the JS environment has
+// been initialized before requiring them.
+require('RCTDebugComponentOwnership');
+require('RCTDeviceEventEmitter');
+require('PerformanceLogger');
diff --git a/Libraries/JavaScriptAppEngine/Initialization/__tests__/parseErrorStack-test.js b/Libraries/JavaScriptAppEngine/Initialization/__tests__/parseErrorStack-test.js
index b32c5acfa..6a0855a2c 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/__tests__/parseErrorStack-test.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/__tests__/parseErrorStack-test.js
@@ -9,7 +9,7 @@
'use strict';
-require('mock-modules').autoMockOff();
+jest.autoMockOff();
var parseErrorStack = require('parseErrorStack');
diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js
index 1124b904a..dfbfdb685 100644
--- a/Libraries/Modal/Modal.js
+++ b/Libraries/Modal/Modal.js
@@ -29,7 +29,8 @@ var RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
*
* In apps written with React Native from the root view down, you should use
* Navigator instead of Modal. With a top-level Navigator, you have more control
- * over how to present the modal scene over the rest of your app.
+ * over how to present the modal scene over the rest of your app by using the
+ * configureScene property.
*/
class Modal extends React.Component {
render(): ?ReactElement {
@@ -58,9 +59,14 @@ class Modal extends React.Component {
Modal.propTypes = {
animated: PropTypes.bool,
transparent: PropTypes.bool,
+ visible: PropTypes.bool,
onDismiss: PropTypes.func,
};
+Modal.defaultProps = {
+ visible: true,
+};
+
var styles = StyleSheet.create({
modal: {
position: 'absolute',
diff --git a/Libraries/Network/FormData.js b/Libraries/Network/FormData.js
index ea5d8cc29..b2c4eda5a 100644
--- a/Libraries/Network/FormData.js
+++ b/Libraries/Network/FormData.js
@@ -47,26 +47,18 @@ type FormDataPart = {
*/
class FormData {
_parts: Array;
- _partsByKey: {[key: string]: FormDataNameValuePair};
constructor() {
this._parts = [];
- this._partsByKey = {};
}
append(key: string, value: FormDataValue) {
- var parts = this._partsByKey[key];
- if (parts) {
- // It's a bit unclear what the behaviour should be in this case.
- // The XMLHttpRequest spec doesn't specify it, while MDN says that
- // the any new values should appended to existing values. We're not
- // doing that for now -- it's tedious and doesn't seem worth the effort.
- parts[1] = value;
- return;
- }
- parts = [key, value];
- this._parts.push(parts);
- this._partsByKey[key] = parts;
+ // The XMLHttpRequest spec doesn't specify if duplicate keys are allowed.
+ // MDN says that any new values should be appended to existing values.
+ // In any case, major browsers allow duplicate keys, so that's what we'll do
+ // too. They'll simply get appended as additional form data parts in the
+ // request body, leaving the server to deal with them.
+ this._parts.push([key, value]);
}
getParts(): Array {
diff --git a/Libraries/Network/RCTDataRequestHandler.m b/Libraries/Network/RCTDataRequestHandler.m
index e80bcfc63..c71ce79a8 100644
--- a/Libraries/Network/RCTDataRequestHandler.m
+++ b/Libraries/Network/RCTDataRequestHandler.m
@@ -36,6 +36,7 @@ RCT_EXPORT_MODULE()
_queue.maxConcurrentOperationCount = 2;
}
+ __weak __block NSBlockOperation *weakOp;
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// Get mime type
@@ -48,7 +49,7 @@ RCT_EXPORT_MODULE()
expectedContentLength:-1
textEncodingName:nil];
- [delegate URLRequest:op didReceiveResponse:response];
+ [delegate URLRequest:weakOp didReceiveResponse:response];
// Load data
NSError *error;
@@ -56,11 +57,12 @@ RCT_EXPORT_MODULE()
options:NSDataReadingMappedIfSafe
error:&error];
if (data) {
- [delegate URLRequest:op didReceiveData:data];
+ [delegate URLRequest:weakOp didReceiveData:data];
}
- [delegate URLRequest:op didCompleteWithError:error];
+ [delegate URLRequest:weakOp didCompleteWithError:error];
}];
+ weakOp = op;
[_queue addOperation:op];
return op;
}
diff --git a/Libraries/Network/RCTFileRequestHandler.m b/Libraries/Network/RCTFileRequestHandler.m
index 797fab9e2..ad55ba0f4 100644
--- a/Libraries/Network/RCTFileRequestHandler.m
+++ b/Libraries/Network/RCTFileRequestHandler.m
@@ -42,6 +42,7 @@ RCT_EXPORT_MODULE()
_fileQueue.maxConcurrentOperationCount = 4;
}
+ __weak __block NSBlockOperation *weakOp;
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// Get content length
@@ -49,7 +50,7 @@ RCT_EXPORT_MODULE()
NSFileManager *fileManager = [NSFileManager new];
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error];
if (error) {
- [delegate URLRequest:op didCompleteWithError:error];
+ [delegate URLRequest:weakOp didCompleteWithError:error];
return;
}
@@ -66,18 +67,19 @@ RCT_EXPORT_MODULE()
expectedContentLength:[fileAttributes[NSFileSize] ?: @-1 integerValue]
textEncodingName:nil];
- [delegate URLRequest:op didReceiveResponse:response];
+ [delegate URLRequest:weakOp didReceiveResponse:response];
// Load data
NSData *data = [NSData dataWithContentsOfURL:request.URL
options:NSDataReadingMappedIfSafe
error:&error];
if (data) {
- [delegate URLRequest:op didReceiveData:data];
+ [delegate URLRequest:weakOp didReceiveData:data];
}
- [delegate URLRequest:op didCompleteWithError:error];
+ [delegate URLRequest:weakOp didCompleteWithError:error];
}];
+ weakOp = op;
[_fileQueue addOperation:op];
return op;
}
diff --git a/Libraries/Network/RCTHTTPRequestHandler.m b/Libraries/Network/RCTHTTPRequestHandler.m
index 961bedb22..f52a20426 100644
--- a/Libraries/Network/RCTHTTPRequestHandler.m
+++ b/Libraries/Network/RCTHTTPRequestHandler.m
@@ -47,7 +47,7 @@ RCT_EXPORT_MODULE()
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
- static NSSet *schemes = nil;
+ static NSSet *schemes = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// technically, RCTHTTPRequestHandler can handle file:// as well,
diff --git a/Libraries/Network/RCTNetworkTask.h b/Libraries/Network/RCTNetworkTask.h
index 268483670..5265d8ef3 100644
--- a/Libraries/Network/RCTNetworkTask.h
+++ b/Libraries/Network/RCTNetworkTask.h
@@ -22,7 +22,7 @@ typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
@property (nonatomic, readonly) NSURLRequest *request;
@property (nonatomic, readonly) NSNumber *requestID;
-@property (nonatomic, readonly) id requestToken;
+@property (nonatomic, readonly, weak) id requestToken;
@property (nonatomic, readonly) NSURLResponse *response;
@property (nonatomic, readonly) RCTURLRequestCompletionBlock completionBlock;
diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m
index 7d5e1fd2c..b1450a631 100644
--- a/Libraries/Network/RCTNetworking.m
+++ b/Libraries/Network/RCTNetworking.m
@@ -37,7 +37,7 @@ typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSD
@implementation RCTHTTPFormDataHelper
{
- NSMutableArray *_parts;
+ NSMutableArray *_parts;
NSMutableData *_multipartBody;
RCTHTTPQueryResult _callback;
NSString *_boundary;
@@ -122,7 +122,7 @@ static NSString *RCTGenerateFormBoundary()
@implementation RCTNetworking
{
NSMutableDictionary *_tasksByRequestID;
- NSArray *_handlers;
+ NSArray> *_handlers;
}
@synthesize bridge = _bridge;
@@ -133,10 +133,10 @@ RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge
{
// get handlers
- NSMutableArray *handlers = [NSMutableArray array];
+ NSMutableArray> *handlers = [NSMutableArray array];
for (id module in bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
- [handlers addObject:module];
+ [handlers addObject:(id)module];
}
}
diff --git a/Libraries/RCTTest/RCTTestRunner.h b/Libraries/RCTTest/RCTTestRunner.h
index 72a2b3127..097bef6bf 100644
--- a/Libraries/RCTTest/RCTTestRunner.h
+++ b/Libraries/RCTTest/RCTTestRunner.h
@@ -9,18 +9,21 @@
#import
+#ifndef FB_REFERENCE_IMAGE_DIR
+#define FB_REFERENCE_IMAGE_DIR ""
+#endif
+
/**
- * Use the RCTInitRunnerForApp macro for typical usage.
- *
- * Add this to your test target's gcc preprocessor macros:
- *
- * FB_REFERENCE_IMAGE_DIR="\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\""
+ * Use the RCTInitRunnerForApp macro for typical usage. See FBSnapshotTestCase.h for more information
+ * on how to configure the snapshotting system.
*/
#define RCTInitRunnerForApp(app__, moduleProvider__) \
[[RCTTestRunner alloc] initWithApp:(app__) \
referenceDirectory:@FB_REFERENCE_IMAGE_DIR \
moduleProvider:(moduleProvider__)]
+@protocol RCTBridgeModule;
+
@interface RCTTestRunner : NSObject
@property (nonatomic, assign) BOOL recordMode;
@@ -31,12 +34,12 @@
* macro instead of calling this directly.
*
* @param app The path to the app bundle without suffixes, e.g. IntegrationTests/IntegrationTestsApp
- * @param referenceDirectory The path for snapshot references images. The RCTInitRunnerForApp macro uses FB_REFERENCE_IMAGE_DIR for this automatically.
+ * @param referenceDirectory The path for snapshot references images.
* @param block A block that returns an array of extra modules to be used by the test runner.
*/
- (instancetype)initWithApp:(NSString *)app
referenceDirectory:(NSString *)referenceDirectory
- moduleProvider:(NSArray *(^)(void))block NS_DESIGNATED_INITIALIZER;
+ moduleProvider:(NSArray> *(^)(void))block NS_DESIGNATED_INITIALIZER;
/**
* Simplest runTest function simply mounts the specified JS module with no
diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m
index 0da2c8388..13d38247b 100644
--- a/Libraries/RCTTest/RCTTestRunner.m
+++ b/Libraries/RCTTest/RCTTestRunner.m
@@ -34,6 +34,9 @@ static const NSTimeInterval kTestTeardownTimeoutSeconds = 30;
RCTAssertParam(referenceDirectory);
if ((self = [super init])) {
+ if (!referenceDirectory.length) {
+ referenceDirectory = [[NSBundle bundleForClass:self.class].resourcePath stringByAppendingPathComponent:@"ReferenceImages"];
+ }
NSString *sanitizedAppName = [app stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
@@ -123,7 +126,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
RCTSetLogFunction(RCTDefaultLogFunction);
- NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
+ NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"];
}]];
RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews);
diff --git a/Libraries/ReactIOS/renderApplication.ios.js b/Libraries/ReactIOS/renderApplication.ios.js
index 6a4bb5f68..aca645324 100644
--- a/Libraries/ReactIOS/renderApplication.ios.js
+++ b/Libraries/ReactIOS/renderApplication.ios.js
@@ -69,11 +69,6 @@ function renderApplication(
rootTag,
'Expect to have a valid rootTag, instead got ', rootTag
);
- // not when debugging in chrome
- if (__DEV__ && !window.document) {
- var setupDevtools = require('setupDevtools');
- setupDevtools();
- }
React.render(
(
var styles = StyleSheet.create({
appContainer: {
- position: 'absolute',
- left: 0,
- top: 0,
- right: 0,
- bottom: 0,
+ flex: 1,
},
});
diff --git a/Libraries/ReactNative/ReactDOM.js b/Libraries/ReactNative/ReactDOM.js
new file mode 100644
index 000000000..7ac737c13
--- /dev/null
+++ b/Libraries/ReactNative/ReactDOM.js
@@ -0,0 +1,16 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule ReactDOM
+ */
+
+'use strict';
+
+var ReactUpdates = require('ReactUpdates');
+
+// Temporary shim required for ReactTestUtils and Relay.
+var ReactDOM = {
+ unstable_batchedUpdates: ReactUpdates.batchedUpdates,
+};
+
+module.exports = ReactDOM;
diff --git a/Libraries/ReactNative/ReactNative.js b/Libraries/ReactNative/ReactNative.js
index 4b09bb667..a5554c0f4 100644
--- a/Libraries/ReactNative/ReactNative.js
+++ b/Libraries/ReactNative/ReactNative.js
@@ -11,6 +11,10 @@
*/
'use strict';
+// Require ReactNativeDefaultInjection first for its side effects of setting up
+// the JS environment
+var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection');
+
var ReactChildren = require('ReactChildren');
var ReactClass = require('ReactClass');
var ReactComponent = require('ReactComponent');
@@ -18,7 +22,6 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactElement = require('ReactElement');
var ReactElementValidator = require('ReactElementValidator');
var ReactInstanceHandles = require('ReactInstanceHandles');
-var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection');
var ReactNativeMount = require('ReactNativeMount');
var ReactPropTypes = require('ReactPropTypes');
var ReactUpdates = require('ReactUpdates');
diff --git a/Libraries/ReactNative/ReactNativeDefaultInjection.js b/Libraries/ReactNative/ReactNativeDefaultInjection.js
index c95992402..5185af953 100644
--- a/Libraries/ReactNative/ReactNativeDefaultInjection.js
+++ b/Libraries/ReactNative/ReactNativeDefaultInjection.js
@@ -12,9 +12,12 @@
'use strict';
/**
- * Make sure `setTimeout`/`setInterval` are patched correctly.
+ * Make sure essential globals are available and are patched correctly. Please don't remove this
+ * line. Bundles created by react-packager `require` it before executing any application code. This
+ * ensures it exists in the dependency graph and can be `require`d.
*/
require('InitializeJavaScriptAppEngine');
+
var EventPluginHub = require('EventPluginHub');
var EventPluginUtils = require('EventPluginUtils');
var IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder');
diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js
index 60e48def1..02dbcb40f 100644
--- a/Libraries/ReactNative/ReactNativeMount.js
+++ b/Libraries/ReactNative/ReactNativeMount.js
@@ -34,6 +34,10 @@ function instanceNumberToChildRootID(rootNodeID, instanceNumber) {
* here.
*/
var TopLevelWrapper = function() {};
+TopLevelWrapper.prototype.isReactComponent = {};
+if (__DEV__) {
+ TopLevelWrapper.displayName = 'TopLevelWrapper';
+}
TopLevelWrapper.prototype.render = function() {
// this.props is actually a ReactElement
return this.props;
@@ -111,6 +115,8 @@ var ReactNativeMount = {
null,
null,
null,
+ null,
+ null,
nextElement
);
diff --git a/Libraries/ReactNative/ReactNativeTextComponent.js b/Libraries/ReactNative/ReactNativeTextComponent.js
index bb93b9c34..a7694c139 100644
--- a/Libraries/ReactNative/ReactNativeTextComponent.js
+++ b/Libraries/ReactNative/ReactNativeTextComponent.js
@@ -15,6 +15,7 @@ var ReactNativeTagHandles = require('ReactNativeTagHandles');
var RCTUIManager = require('NativeModules').UIManager;
var assign = require('Object.assign');
+var invariant = require('invariant');
var ReactNativeTextComponent = function(props) {
// This constructor and its argument is currently used by mocks.
@@ -30,6 +31,11 @@ assign(ReactNativeTextComponent.prototype, {
},
mountComponent: function(rootID, transaction, context) {
+ invariant(
+ context.isInAParentText,
+ 'RawText "' + this._stringText + '" must be wrapped in an explicit ' +
+ ' component.'
+ );
this._rootNodeID = rootID;
var tag = ReactNativeTagHandles.allocateTag();
var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID);
diff --git a/Libraries/Storage/AsyncStorage.android.js b/Libraries/Storage/AsyncStorage.android.js
deleted file mode 100644
index 8b2fa312a..000000000
--- a/Libraries/Storage/AsyncStorage.android.js
+++ /dev/null
@@ -1,233 +0,0 @@
-/**
- * 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 AsyncStorage
- * @flow
- */
-'use strict';
-
-var RCTAsyncStorage = require('NativeModules').AsyncSQLiteDBStorage;
-
-/**
- * AsyncStorage is a simple, asynchronous, persistent, global, key-value storage system.
- *
- * It is recommended that you use an abstraction on top of AsyncStorage instead of AsyncStorage
- * directly for anything more than light usage since it operates globally.
- *
- * This JS code is a simple facade over the native android implementation to provide a clear
- * JS API, real Error objects, and simple non-multi functions.
- */
-var AsyncStorage = {
- /**
- * Fetches `key` and passes the result to `callback`, along with an `Error` if
- * there is any. Returns a `Promise` object.
- */
- getItem: function(
- key: string,
- callback?: ?(error: ?Error, result: ?string) => void
- ) {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiGet([key], function(error, result) {
- var value = (result && result[0] && result[0][1]) ? result[0][1] : null;
- callback && callback((error && convertError(error)) || null, value);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(value);
- }
- });
- });
- },
- /**
- * Sets `value` for `key` and calls `callback` on completion, along with an
- * `Error` if there is any. Returns a `Promise` object.
- */
- setItem: function(
- key: string,
- value: string,
- callback?: ?(error: ?Error) => void
- ): Promise {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiSet([[key,value]], function(error) {
- callback && callback((error && convertError(error)) || null);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Returns a `Promise` object.
- */
- removeItem: function(
- key: string,
- callback?: ?(error: ?Error) => void
- ): Promise {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiRemove([key], function(error) {
- callback && callback((error && convertError(error)) || null);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Merges existing value with input value, assuming they are stringified json.
- * Returns a `Promise` object.
- */
- mergeItem: function(
- key: string,
- value: string,
- callback?: ?(error: ?Error) => void
- ): Promise {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiMerge([[key,value]], function(error) {
- callback && callback((error && convertError(error)) || null);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Erases *all* AsyncStorage for all clients, libraries, etc. You probably
- * don't want to call this - use removeItem or multiRemove to clear only your
- * own keys instead. Returns a `Promise` object.
- */
- clear: function(callback?: ?(error: ?Error) => void): Promise {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.clear(function(error) {
- callback && callback(convertError(error) || null);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Gets *all* keys known to the app, for all callers, libraries, etc. Returns a `Promise` object.
- */
- getAllKeys: function(callback?: ?(error: ?Error, keys: ?Array) => void): Promise {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.getAllKeys(function(error, keys) {
- callback && callback((error && convertError(error)) || null, keys);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(keys);
- }
- });
- });
- },
- /**
- * The following batched functions are useful for executing a lot of
- * operations at once, allowing for native optimizations and provide the
- * convenience of a single callback after all operations are complete.
- *
- * In case of errors, these functions return the first encountered error and abort.
- */
-
- /**
- * multiGet invokes callback with an array of key-value pair arrays that
- * matches the input format of multiSet. Returns a `Promise` object.
- *
- * multiGet(['k1', 'k2'], cb) -> cb([['k1', 'val1'], ['k2', 'val2']])
- */
- multiGet: function(
- keys: Array,
- callback?: ?(errors: ?Array, result: ?Array>) => void
- ): Promise {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiGet(keys, function(error, result) {
- callback && callback((error && convertError(error)) || null, result);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(result);
- }
- });
- });
- },
- /**
- * multiSet and multiMerge take arrays of key-value array pairs that match
- * the output of multiGet, e.g. Returns a `Promise` object.
- *
- * multiSet([['k1', 'val1'], ['k2', 'val2']], cb);
- */
- multiSet: function(
- keyValuePairs: Array>,
- callback?: ?(errors: ?Array) => void
- ): Promise {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiSet(keyValuePairs, function(error) {
- callback && callback((error && convertError(error)) || null);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Delete all the keys in the `keys` array. Returns a `Promise` object.
- */
- multiRemove: function(
- keys: Array,
- callback?: ?(errors: ?Array) => void
- ): Promise {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiRemove(keys, function(error) {
- callback && callback((error && convertError(error)) || null);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Merges existing values with input values, assuming they are stringified
- * json. Returns a `Promise` object.
- */
- multiMerge: function(
- keyValuePairs: Array>,
- callback?: ?(errors: ?Array) => void
- ): Promise {
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiMerge(keyValuePairs, function(error) {
- callback && callback((error && convertError(error)) || null);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(null);
- }
- });
- });
- },
-};
-
-function convertError(error) {
- if (!error) {
- return null;
- }
- var out = new Error(error.message);
- return [out];
-}
-
-module.exports = AsyncStorage;
diff --git a/Libraries/Storage/AsyncStorage.ios.js b/Libraries/Storage/AsyncStorage.js
similarity index 83%
rename from Libraries/Storage/AsyncStorage.ios.js
rename to Libraries/Storage/AsyncStorage.js
index ed028db4b..c5be992e2 100644
--- a/Libraries/Storage/AsyncStorage.ios.js
+++ b/Libraries/Storage/AsyncStorage.js
@@ -12,11 +12,12 @@
'use strict';
var NativeModules = require('NativeModules');
-var RCTAsyncLocalStorage = NativeModules.AsyncLocalStorage;
+var RCTAsyncSQLiteStorage = NativeModules.AsyncSQLiteDBStorage;
var RCTAsyncRocksDBStorage = NativeModules.AsyncRocksDBStorage;
+var RCTAsyncFileStorage = NativeModules.AsyncLocalStorage;
-// We use RocksDB if available.
-var RCTAsyncStorage = RCTAsyncRocksDBStorage || RCTAsyncLocalStorage;
+// Use RocksDB if available, then SQLite, then file storage.
+var RCTAsyncStorage = RCTAsyncRocksDBStorage || RCTAsyncSQLiteStorage || RCTAsyncFileStorage;
/**
* AsyncStorage is a simple, asynchronous, persistent, key-value storage
@@ -43,9 +44,10 @@ var AsyncStorage = {
RCTAsyncStorage.multiGet([key], function(errors, result) {
// Unpack result to get value from [[key,value]]
var value = (result && result[0] && result[0][1]) ? result[0][1] : null;
- callback && callback((errors && convertError(errors[0])) || null, value);
- if (errors) {
- reject(convertError(errors[0]));
+ var errs = convertErrors(errors);
+ callback && callback(errs && errs[0], value);
+ if (errs) {
+ reject(errs[0]);
} else {
resolve(value);
}
@@ -64,15 +66,17 @@ var AsyncStorage = {
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiSet([[key,value]], function(errors) {
- callback && callback((errors && convertError(errors[0])) || null);
- if (errors) {
- reject(convertError(errors[0]));
+ var errs = convertErrors(errors);
+ callback && callback(errs && errs[0]);
+ if (errs) {
+ reject(errs[0]);
} else {
resolve(null);
}
});
});
},
+
/**
* Returns a `Promise` object.
*/
@@ -82,9 +86,10 @@ var AsyncStorage = {
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiRemove([key], function(errors) {
- callback && callback((errors && convertError(errors[0])) || null);
- if (errors) {
- reject(convertError(errors[0]));
+ var errs = convertErrors(errors);
+ callback && callback(errs && errs[0]);
+ if (errs) {
+ reject(errs[0]);
} else {
resolve(null);
}
@@ -93,9 +98,8 @@ var AsyncStorage = {
},
/**
- * Merges existing value with input value, assuming they are stringified json. Returns a `Promise` object.
- *
- * Not supported by all native implementations.
+ * Merges existing value with input value, assuming they are stringified json.
+ * Returns a `Promise` object. Not supported by all native implementations.
*/
mergeItem: function(
key: string,
@@ -104,9 +108,10 @@ var AsyncStorage = {
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiMerge([[key,value]], function(errors) {
- callback && callback((errors && convertError(errors[0])) || null);
- if (errors) {
- reject(convertError(errors[0]));
+ var errs = convertErrors(errors);
+ callback && callback(errs && errs[0]);
+ if (errs) {
+ reject(errs[0]);
} else {
resolve(null);
}
@@ -170,9 +175,9 @@ var AsyncStorage = {
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiGet(keys, function(errors, result) {
- var error = (errors && errors.map((error) => convertError(error))) || null;
+ var error = convertErrors(errors);
callback && callback(error, result);
- if (errors) {
+ if (error) {
reject(error);
} else {
resolve(result);
@@ -193,9 +198,9 @@ var AsyncStorage = {
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiSet(keyValuePairs, function(errors) {
- var error = (errors && errors.map((error) => convertError(error))) || null;
+ var error = convertErrors(errors);
callback && callback(error);
- if (errors) {
+ if (error) {
reject(error);
} else {
resolve(null);
@@ -213,9 +218,9 @@ var AsyncStorage = {
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiRemove(keys, function(errors) {
- var error = (errors && errors.map((error) => convertError(error))) || null;
+ var error = convertErrors(errors);
callback && callback(error);
- if (errors) {
+ if (error) {
reject(error);
} else {
resolve(null);
@@ -236,9 +241,9 @@ var AsyncStorage = {
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) {
- var error = (errors && errors.map((error) => convertError(error))) || null;
+ var error = convertErrors(errors);
callback && callback(error);
- if (errors) {
+ if (error) {
reject(error);
} else {
resolve(null);
@@ -254,6 +259,13 @@ if (!RCTAsyncStorage.multiMerge) {
delete AsyncStorage.multiMerge;
}
+function convertErrors(errs) {
+ if (!errs) {
+ return null;
+ }
+ return (Array.isArray(errs) ? errs : [errs]).map((e) => convertError(e));
+}
+
function convertError(error) {
if (!error) {
return null;
diff --git a/Libraries/StyleSheet/StyleSheet.js b/Libraries/StyleSheet/StyleSheet.js
index 796470ced..aa997b0a4 100644
--- a/Libraries/StyleSheet/StyleSheet.js
+++ b/Libraries/StyleSheet/StyleSheet.js
@@ -13,6 +13,7 @@
var StyleSheetRegistry = require('StyleSheetRegistry');
var StyleSheetValidation = require('StyleSheetValidation');
+var flattenStyle = require('flattenStyle');
/**
* A StyleSheet is an abstraction similar to CSS StyleSheets
@@ -59,6 +60,8 @@ var StyleSheetValidation = require('StyleSheetValidation');
* subsequent uses are going to refer an id (not implemented yet).
*/
class StyleSheet {
+ static flatten: typeof flattenStyle;
+
static create(obj: {[key: string]: any}): {[key: string]: number} {
var result = {};
for (var key in obj) {
@@ -69,4 +72,7 @@ class StyleSheet {
}
}
+/* TODO(brentvatne) docs are needed for this */
+StyleSheet.flatten = flattenStyle;
+
module.exports = StyleSheet;
diff --git a/Libraries/Text/RCTShadowRawText.m b/Libraries/Text/RCTShadowRawText.m
index a76cc288b..6d1dd6d25 100644
--- a/Libraries/Text/RCTShadowRawText.m
+++ b/Libraries/Text/RCTShadowRawText.m
@@ -37,7 +37,7 @@
- (void)setText:(NSString *)text
{
- if (_text != text) {
+ if (_text != text && ![_text isEqualToString:text]) {
_text = [text copy];
[self dirtyLayout];
[self dirtyText];
diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h
index abb111879..1ded47c93 100644
--- a/Libraries/Text/RCTShadowText.h
+++ b/Libraries/Text/RCTShadowText.h
@@ -32,6 +32,7 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, assign) RCTTextDecorationLineType textDecorationLine;
@property (nonatomic, assign) CGFloat fontSizeMultiplier;
@property (nonatomic, assign) BOOL allowFontScaling;
+@property (nonatomic, assign) CGFloat opacity;
- (void)recomputeText;
diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m
index 33e8c8485..d6d6298ae 100644
--- a/Libraries/Text/RCTShadowText.m
+++ b/Libraries/Text/RCTShadowText.m
@@ -55,6 +55,7 @@ static css_dim_t RCTMeasure(void *context, float width)
_letterSpacing = NAN;
_isHighlighted = NO;
_textDecorationStyle = NSUnderlineStyleSingle;
+ _opacity = 1.0;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contentSizeMultiplierDidChange:)
name:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
@@ -80,7 +81,7 @@ static css_dim_t RCTMeasure(void *context, float width)
[self dirtyText];
}
-- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
+- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties
{
parentProperties = [super processUpdatedProperties:applierBlocks
@@ -99,7 +100,7 @@ static css_dim_t RCTMeasure(void *context, float width)
}
- (void)applyLayoutNode:(css_node_t *)node
- viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
+ viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
{
[super applyLayoutNode:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
@@ -152,7 +153,10 @@ static css_dim_t RCTMeasure(void *context, float width)
fontWeight:nil
fontStyle:nil
letterSpacing:nil
- useBackgroundColor:NO];
+ useBackgroundColor:NO
+ foregroundColor:self.color ?: [UIColor blackColor]
+ backgroundColor:self.backgroundColor
+ opacity:self.opacity];
}
- (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily
@@ -161,6 +165,9 @@ static css_dim_t RCTMeasure(void *context, float width)
fontStyle:(NSString *)fontStyle
letterSpacing:(NSNumber *)letterSpacing
useBackgroundColor:(BOOL)useBackgroundColor
+ foregroundColor:(UIColor *)foregroundColor
+ backgroundColor:(UIColor *)backgroundColor
+ opacity:(CGFloat)opacity
{
if (![self isTextDirty] && _cachedAttributedString) {
return _cachedAttributedString;
@@ -188,7 +195,16 @@ static css_dim_t RCTMeasure(void *context, float width)
for (RCTShadowView *child in [self reactSubviews]) {
if ([child isKindOfClass:[RCTShadowText class]]) {
RCTShadowText *shadowText = (RCTShadowText *)child;
- [attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight fontStyle:fontStyle letterSpacing:letterSpacing useBackgroundColor:YES]];
+ [attributedString appendAttributedString:
+ [shadowText _attributedStringWithFontFamily:fontFamily
+ fontSize:fontSize
+ fontWeight:fontWeight
+ fontStyle:fontStyle
+ letterSpacing:letterSpacing
+ useBackgroundColor:YES
+ foregroundColor:shadowText.color ?: foregroundColor
+ backgroundColor:shadowText.backgroundColor ?: backgroundColor
+ opacity:opacity * shadowText.opacity]];
} else if ([child isKindOfClass:[RCTShadowRawText class]]) {
RCTShadowRawText *shadowRawText = (RCTShadowRawText *)child;
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:shadowRawText.text ?: @""]];
@@ -208,14 +224,17 @@ static css_dim_t RCTMeasure(void *context, float width)
[child setTextComputed];
}
- if (_color) {
- [self _addAttribute:NSForegroundColorAttributeName withValue:_color toAttributedString:attributedString];
- }
+ [self _addAttribute:NSForegroundColorAttributeName
+ withValue:[foregroundColor colorWithAlphaComponent:CGColorGetAlpha(foregroundColor.CGColor) * opacity]
+ toAttributedString:attributedString];
+
if (_isHighlighted) {
[self _addAttribute:RCTIsHighlightedAttributeName withValue:@YES toAttributedString:attributedString];
}
- if (useBackgroundColor && self.backgroundColor) {
- [self _addAttribute:NSBackgroundColorAttributeName withValue:self.backgroundColor toAttributedString:attributedString];
+ if (useBackgroundColor && backgroundColor) {
+ [self _addAttribute:NSBackgroundColorAttributeName
+ withValue:[backgroundColor colorWithAlphaComponent:CGColorGetAlpha(backgroundColor.CGColor) * opacity]
+ toAttributedString:attributedString];
}
UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily
@@ -278,7 +297,7 @@ static css_dim_t RCTMeasure(void *context, float width)
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = _textAlign;
paragraphStyle.baseWritingDirection = _writingDirection;
- CGFloat lineHeight = round(_lineHeight * self.fontSizeMultiplier);
+ CGFloat lineHeight = round(_lineHeight * (_allowFontScaling && self.fontSizeMultiplier > 0.0 ? self.fontSizeMultiplier : 1.0));
paragraphStyle.minimumLineHeight = lineHeight;
paragraphStyle.maximumLineHeight = lineHeight;
[attributedString addAttribute:NSParagraphStyleAttributeName
@@ -352,6 +371,7 @@ RCT_TEXT_PROPERTY(TextDecorationColor, _textDecorationColor, UIColor *);
RCT_TEXT_PROPERTY(TextDecorationLine, _textDecorationLine, RCTTextDecorationLineType);
RCT_TEXT_PROPERTY(TextDecorationStyle, _textDecorationStyle, NSUnderlineStyle);
RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection)
+RCT_TEXT_PROPERTY(Opacity, _opacity, CGFloat)
- (void)setAllowFontScaling:(BOOL)allowFontScaling
{
diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m
index 47b7de6a5..c45287917 100644
--- a/Libraries/Text/RCTText.m
+++ b/Libraries/Text/RCTText.m
@@ -16,7 +16,7 @@
@implementation RCTText
{
NSTextStorage *_textStorage;
- NSMutableArray *_reactSubviews;
+ NSMutableArray *_reactSubviews;
CAShapeLayer *_highlightLayer;
}
@@ -62,7 +62,7 @@
[_reactSubviews removeObject:subview];
}
-- (NSArray *)reactSubviews
+- (NSArray *)reactSubviews
{
return _reactSubviews;
}
diff --git a/Libraries/Text/RCTTextField.h b/Libraries/Text/RCTTextField.h
index 3aba72bba..99eadce98 100644
--- a/Libraries/Text/RCTTextField.h
+++ b/Libraries/Text/RCTTextField.h
@@ -16,12 +16,17 @@
@property (nonatomic, assign) BOOL caretHidden;
@property (nonatomic, assign) BOOL autoCorrect;
@property (nonatomic, assign) BOOL selectTextOnFocus;
+@property (nonatomic, assign) BOOL blurOnSubmit;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, strong) UIColor *placeholderTextColor;
@property (nonatomic, assign) NSInteger mostRecentEventCount;
@property (nonatomic, strong) NSNumber *maxLength;
+@property (nonatomic, assign) BOOL textWasPasted;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
+
- (void)textFieldDidChange;
+- (void)sendKeyValueForString:(NSString *)string;
+- (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField;
@end
diff --git a/Libraries/Text/RCTTextField.m b/Libraries/Text/RCTTextField.m
index a75a268e9..6b11bdae5 100644
--- a/Libraries/Text/RCTTextField.m
+++ b/Libraries/Text/RCTTextField.m
@@ -17,9 +17,10 @@
@implementation RCTTextField
{
RCTEventDispatcher *_eventDispatcher;
- NSMutableArray *_reactSubviews;
+ NSMutableArray *_reactSubviews;
BOOL _jsRequestingFirstResponder;
NSInteger _nativeEventCount;
+ BOOL _submitted;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
@@ -39,6 +40,23 @@
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
+- (void)sendKeyValueForString:(NSString *)string
+{
+ [_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
+ reactTag:self.reactTag
+ text:nil
+ key:string
+ eventCount:_nativeEventCount];
+}
+
+// This method is overriden for `onKeyPress`. The manager
+// will not send a keyPress for text that was pasted.
+- (void)paste:(id)sender
+{
+ _textWasPasted = YES;
+ [super paste:sender];
+}
+
- (void)setText:(NSString *)text
{
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
@@ -75,7 +93,7 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
RCTUpdatePlaceholder(self);
}
-- (NSArray *)reactSubviews
+- (NSArray *)reactSubviews
{
// TODO: do we support subviews of textfield in React?
// In any case, we should have a better approach than manually
@@ -134,6 +152,7 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:self.reactTag
text:self.text
+ key:nil
eventCount:_nativeEventCount];
}
@@ -142,13 +161,16 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
reactTag:self.reactTag
text:self.text
+ key:nil
eventCount:_nativeEventCount];
}
- (void)textFieldSubmitEditing
{
+ _submitted = YES;
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
reactTag:self.reactTag
text:self.text
+ key:nil
eventCount:_nativeEventCount];
}
@@ -162,9 +184,19 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:self.reactTag
text:self.text
+ key:nil
eventCount:_nativeEventCount];
}
+- (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField
+{
+ if (_submitted) {
+ _submitted = NO;
+ return _blurOnSubmit;
+ }
+ return YES;
+}
+
- (BOOL)becomeFirstResponder
{
_jsRequestingFirstResponder = YES;
@@ -181,6 +213,7 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
reactTag:self.reactTag
text:self.text
+ key:nil
eventCount:_nativeEventCount];
}
return result;
diff --git a/Libraries/Text/RCTTextFieldManager.m b/Libraries/Text/RCTTextFieldManager.m
index 8ce0b1430..88a59e1bd 100644
--- a/Libraries/Text/RCTTextFieldManager.m
+++ b/Libraries/Text/RCTTextFieldManager.m
@@ -31,6 +31,13 @@ RCT_EXPORT_MODULE()
- (BOOL)textField:(RCTTextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
+ // Only allow single keypresses for onKeyPress, pasted text will not be sent.
+ if (textField.textWasPasted) {
+ textField.textWasPasted = NO;
+ } else {
+ [textField sendKeyValueForString:string];
+ }
+
if (textField.maxLength == nil || [string isEqualToString:@"\n"]) { // Make sure forms can be submitted via return
return YES;
}
@@ -54,6 +61,19 @@ RCT_EXPORT_MODULE()
}
}
+// This method allows us to detect a `Backspace` keyPress
+// even when there is no more text in the TextField
+- (BOOL)keyboardInputShouldDelete:(RCTTextField *)textField
+{
+ [self textField:textField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
+ return YES;
+}
+
+- (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField
+{
+ return [textField textFieldShouldEndEditing:textField];
+}
+
RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
RCT_REMAP_VIEW_PROPERTY(editable, enabled, BOOL)
@@ -64,6 +84,7 @@ RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(clearButtonMode, UITextFieldViewMode)
RCT_REMAP_VIEW_PROPERTY(clearTextOnFocus, clearsOnBeginEditing, BOOL)
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)
RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType)
RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType)
RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL)
diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m
index f7150b46b..be63428fc 100644
--- a/Libraries/Text/RCTTextManager.m
+++ b/Libraries/Text/RCTTextManager.m
@@ -17,8 +17,16 @@
#import "RCTShadowText.h"
#import "RCTSparseArray.h"
#import "RCTText.h"
+#import "RCTTextView.h"
#import "UIView+React.h"
+@interface RCTShadowText (Private)
+
+- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width;
+
+@end
+
+
@implementation RCTTextManager
RCT_EXPORT_MODULE()
@@ -51,9 +59,11 @@ RCT_EXPORT_SHADOW_PROPERTY(textDecorationColor, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(textDecorationLine, RCTTextDecorationLineType)
RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL)
+RCT_EXPORT_SHADOW_PROPERTY(opacity, CGFloat)
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
+ NSMutableSet *textViewTagsToUpdate = [NSMutableSet new];
for (RCTShadowView *rootView in shadowViewRegistry.allObjects) {
if (![rootView isReactRootView]) {
// This isn't a root view
@@ -65,7 +75,7 @@ RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL)
continue;
}
- NSMutableArray *queue = [NSMutableArray arrayWithObject:rootView];
+ NSMutableArray *queue = [NSMutableArray arrayWithObject:rootView];
for (NSInteger i = 0; i < queue.count; i++) {
RCTShadowView *shadowView = queue[i];
RCTAssert([shadowView isTextDirty], @"Don't process any nodes that don't have dirty text");
@@ -77,6 +87,19 @@ RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL)
RCTLogError(@"Raw text cannot be used outside of a tag. Not rendering string: '%@'",
[(RCTShadowRawText *)shadowView text]);
} else {
+ NSNumber *reactTag = shadowView.reactTag;
+ // This isn't pretty, but hopefully it's temporary
+ // the problem is, there's no easy way (besides the viewName)
+ // to tell from the shadowView if the view is an RKTextView
+ if ([shadowView.viewName hasSuffix:@"TextView"]) {
+ // Add to textViewTagsToUpdate only if has a RCTShadowText subview
+ for (RCTShadowView *subview in shadowView.reactSubviews) {
+ if ([subview isKindOfClass:[RCTShadowText class]]) {
+ [textViewTagsToUpdate addObject:reactTag];
+ break;
+ }
+ }
+ }
for (RCTShadowView *child in [shadowView reactSubviews]) {
if ([child isTextDirty]) {
[queue addObject:child];
@@ -88,7 +111,53 @@ RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL)
}
}
- return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {};
+ /**
+ * NOTE: this logic is included to support rich text editing inside multiline
+ * `` controls, a feature which is not yet supported in open source.
+ * It is required in order to ensure that the textStorage (aka attributed
+ * string) is copied over from the RCTShadowText to the RCTText view in time
+ * to be used to update the editable text content.
+ */
+ if (textViewTagsToUpdate.count) {
+
+ NSMutableArray *uiBlocks = [NSMutableArray new];
+ for (NSNumber *reactTag in textViewTagsToUpdate) {
+ RCTShadowView *shadowTextView = shadowViewRegistry[reactTag];
+ RCTShadowText *shadowText;
+ for (RCTShadowText *subview in shadowTextView.reactSubviews) {
+ if ([subview isKindOfClass:[RCTShadowText class]]) {
+ shadowText = subview;
+ break;
+ }
+ }
+
+ UIEdgeInsets padding = shadowText.paddingAsInsets;
+ CGFloat width = shadowText.frame.size.width - (padding.left + padding.right);
+ NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:width];
+
+ [uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
+ RCTTextView *textView = viewRegistry[reactTag];
+ RCTText *text;
+ for (RCTText *subview in textView.reactSubviews) {
+ if ([subview isKindOfClass:[RCTText class]]) {
+ text = subview;
+ break;
+ }
+ }
+
+ text.textStorage = textStorage;
+ [textView performTextUpdate];
+ }];
+ }
+
+ return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
+ for (RCTViewManagerUIBlock uiBlock in uiBlocks) {
+ uiBlock(uiManager, viewRegistry);
+ }
+ };
+ } else {
+ return nil;
+ }
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowView
diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h
index c5012ec09..8fc6d4c28 100644
--- a/Libraries/Text/RCTTextView.h
+++ b/Libraries/Text/RCTTextView.h
@@ -30,4 +30,6 @@
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
+- (void)performTextUpdate;
+
@end
diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m
index 5e24e3a1c..7ad01b0ce 100644
--- a/Libraries/Text/RCTTextView.m
+++ b/Libraries/Text/RCTTextView.m
@@ -11,9 +11,26 @@
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
+#import "RCTText.h"
#import "RCTUtils.h"
#import "UIView+React.h"
+@interface RCTUITextView : UITextView
+
+@property (nonatomic, assign) BOOL textWasPasted;
+
+@end
+
+@implementation RCTUITextView
+
+- (void)paste:(id)sender
+{
+ _textWasPasted = YES;
+ [super paste:sender];
+}
+
+@end
+
@implementation RCTTextView
{
RCTEventDispatcher *_eventDispatcher;
@@ -22,6 +39,10 @@
UITextView *_placeholderView;
UITextView *_textView;
NSInteger _nativeEventCount;
+ RCTText *_richTextView;
+ NSAttributedString *_pendingAttributedText;
+ NSMutableArray *> *_subviews;
+ BOOL _blockTextShouldChange;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
@@ -33,10 +54,12 @@
_eventDispatcher = eventDispatcher;
_placeholderTextColor = [self defaultPlaceholderTextColor];
- _textView = [[UITextView alloc] initWithFrame:self.bounds];
+ _textView = [[RCTUITextView alloc] initWithFrame:self.bounds];
_textView.backgroundColor = [UIColor clearColor];
_textView.scrollsToTop = NO;
_textView.delegate = self;
+
+ _subviews = [NSMutableArray new];
[self addSubview:_textView];
}
return self;
@@ -45,6 +68,90 @@
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
+- (NSArray *> *)reactSubviews
+{
+ return _subviews;
+}
+
+- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
+{
+ if ([subview isKindOfClass:[RCTText class]]) {
+ if (_richTextView) {
+ RCTLogError(@"Tried to insert a second into - there can only be one.");
+ }
+ _richTextView = (RCTText *)subview;
+ [_subviews insertObject:_richTextView atIndex:index];
+ } else {
+ [_subviews insertObject:subview atIndex:index];
+ [self insertSubview:subview atIndex:index];
+ }
+}
+
+- (void)removeReactSubview:(UIView *)subview
+{
+ if (_richTextView == subview) {
+ [_subviews removeObject:_richTextView];
+ _richTextView = nil;
+ } else {
+ [_subviews removeObject:subview];
+ [subview removeFromSuperview];
+ }
+}
+
+- (void)setMostRecentEventCount:(NSInteger)mostRecentEventCount
+{
+ _mostRecentEventCount = mostRecentEventCount;
+
+ // Props are set after uiBlockToAmendWithShadowViewRegistry, which means that
+ // at the time performTextUpdate is called, _mostRecentEventCount will be
+ // behind _eventCount, with the result that performPendingTextUpdate will do
+ // nothing. For that reason we call it again here after mostRecentEventCount
+ // has been set.
+ [self performPendingTextUpdate];
+}
+
+- (void)performTextUpdate
+{
+ if (_richTextView) {
+ _pendingAttributedText = _richTextView.textStorage;
+ [self performPendingTextUpdate];
+ } else if (!self.text) {
+ _textView.attributedText = nil;
+ }
+}
+
+- (void)performPendingTextUpdate
+{
+ if (!_pendingAttributedText || _mostRecentEventCount < _nativeEventCount) {
+ return;
+ }
+
+ if ([_textView.attributedText isEqualToAttributedString:_pendingAttributedText]) {
+ _pendingAttributedText = nil; // Don't try again.
+ return;
+ }
+
+ // When we update the attributed text, there might be pending autocorrections
+ // that will get accepted by default. In order for this to not garble our text,
+ // we temporarily block all textShouldChange events so they are not applied.
+ _blockTextShouldChange = YES;
+
+ // We compute the new selectedRange manually to make sure the cursor is at the
+ // end of the newly inserted/deleted text after update.
+ NSRange range = _textView.selectedRange;
+ CGPoint contentOffset = _textView.contentOffset;
+
+ _textView.attributedText = _pendingAttributedText;
+ _pendingAttributedText = nil;
+ _textView.selectedRange = range;
+ [_textView layoutIfNeeded];
+ _textView.contentOffset = contentOffset;
+
+ [self _setPlaceholderVisibility];
+
+ _blockTextShouldChange = NO;
+}
+
- (void)updateFrames
{
// Adjust the insets so that they are as close as possible to single-line
@@ -56,15 +163,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
// first focused.
UIEdgeInsets adjustedFrameInset = UIEdgeInsetsZero;
adjustedFrameInset.left = _contentInset.left - 5;
-
+
UIEdgeInsets adjustedTextContainerInset = _contentInset;
adjustedTextContainerInset.top += 5;
adjustedTextContainerInset.left = 0;
-
+
CGRect frame = UIEdgeInsetsInsetRect(self.bounds, adjustedFrameInset);
_textView.frame = frame;
_placeholderView.frame = frame;
-
+
_textView.textContainerInset = adjustedTextContainerInset;
_placeholderView.textContainerInset = adjustedTextContainerInset;
}
@@ -138,8 +245,22 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
return _textView.text;
}
-- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
+- (BOOL)textView:(RCTUITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
+ if (_blockTextShouldChange) {
+ return NO;
+ }
+
+ if (textView.textWasPasted) {
+ textView.textWasPasted = NO;
+ } else {
+ [_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
+ reactTag:self.reactTag
+ text:nil
+ key:text
+ eventCount:_nativeEventCount];
+ }
+
if (_maxLength == nil) {
return YES;
}
@@ -215,6 +336,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:self.reactTag
text:textView.text
+ key:nil
eventCount:_nativeEventCount];
}
@@ -225,6 +347,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:self.reactTag
text:textView.text
+ key:nil
eventCount:_nativeEventCount];
}
@@ -234,6 +357,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
reactTag:self.reactTag
text:textView.text
+ key:nil
eventCount:_nativeEventCount];
}
@@ -253,6 +377,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
reactTag:self.reactTag
text:_textView.text
+ key:nil
eventCount:_nativeEventCount];
}
return result;
diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js
index 8ff7583cd..16b6a7159 100644
--- a/Libraries/Utilities/BridgeProfiling.js
+++ b/Libraries/Utilities/BridgeProfiling.js
@@ -14,7 +14,7 @@
var GLOBAL = GLOBAL || this;
var TRACE_TAG_REACT_APPS = 1 << 17;
-var _enabled = false;
+var _enabled;
var _ReactPerf = null;
function ReactPerf() {
if (!_ReactPerf) {
@@ -34,13 +34,13 @@ var BridgeProfiling = {
if (_enabled) {
profileName = typeof profileName === 'function' ?
profileName() : profileName;
- console.profile(TRACE_TAG_REACT_APPS, profileName);
+ global.nativeTraceBeginSection(TRACE_TAG_REACT_APPS, profileName);
}
},
profileEnd() {
if (_enabled) {
- console.profileEnd(TRACE_TAG_REACT_APPS);
+ global.nativeTraceEndSection(TRACE_TAG_REACT_APPS);
}
},
@@ -63,4 +63,6 @@ var BridgeProfiling = {
},
};
+BridgeProfiling.setEnabled(global.__RCTProfileIsProfiling || false);
+
module.exports = BridgeProfiling;
diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js
index d34bda8b9..415df27c7 100644
--- a/Libraries/Utilities/MessageQueue.js
+++ b/Libraries/Utilities/MessageQueue.js
@@ -30,7 +30,6 @@ let MIN_TIME_BETWEEN_FLUSHES_MS = 5;
let SPY_MODE = false;
let MethodTypes = keyMirror({
- local: null,
remote: null,
remoteAsync: null,
});
@@ -62,15 +61,18 @@ class MessageQueue {
'flushedQueue',
].forEach((fn) => this[fn] = this[fn].bind(this));
- this._genModules(remoteModules);
+ let modulesConfig = this._genModulesConfig(remoteModules);
+ this._genModules(modulesConfig);
localModules && this._genLookupTables(
- localModules, this._moduleTable, this._methodTable);
+ this._genModulesConfig(localModules),this._moduleTable, this._methodTable
+ );
this._debugInfo = {};
this._remoteModuleTable = {};
this._remoteMethodTable = {};
this._genLookupTables(
- remoteModules, this._remoteModuleTable, this._remoteMethodTable);
+ modulesConfig, this._remoteModuleTable, this._remoteMethodTable
+ );
}
/**
@@ -182,50 +184,102 @@ class MessageQueue {
/**
* Private helper methods
*/
- _genLookupTables(localModules, moduleTable, methodTable) {
- let moduleNames = Object.keys(localModules);
- for (var i = 0, l = moduleNames.length; i < l; i++) {
- let moduleName = moduleNames[i];
- let methods = localModules[moduleName].methods;
- let moduleID = localModules[moduleName].moduleID;
- moduleTable[moduleID] = moduleName;
- methodTable[moduleID] = {};
- let methodNames = Object.keys(methods);
- for (var j = 0, k = methodNames.length; j < k; j++) {
- let methodName = methodNames[j];
- let methodConfig = methods[methodName];
- methodTable[moduleID][methodConfig.methodID] = methodName;
+ /**
+ * Converts the old, object-based module structure to the new
+ * array-based structure. TODO (t8823865) Removed this
+ * function once Android has been updated.
+ */
+ _genModulesConfig(modules /* array or object */) {
+ if (Array.isArray(modules)) {
+ return modules;
+ } else {
+ let moduleArray = [];
+ let moduleNames = Object.keys(modules);
+ for (var i = 0, l = moduleNames.length; i < l; i++) {
+ let moduleName = moduleNames[i];
+ let moduleConfig = modules[moduleName];
+ let module = [moduleName];
+ if (moduleConfig.constants) {
+ module.push(moduleConfig.constants);
+ }
+ let methodsConfig = moduleConfig.methods;
+ if (methodsConfig) {
+ let methods = [];
+ let asyncMethods = [];
+ let methodNames = Object.keys(methodsConfig);
+ for (var j = 0, ll = methodNames.length; j < ll; j++) {
+ let methodName = methodNames[j];
+ let methodConfig = methodsConfig[methodName];
+ methods[methodConfig.methodID] = methodName;
+ if (methodConfig.type === MethodTypes.remoteAsync) {
+ asyncMethods.push(methodConfig.methodID);
+ }
+ }
+ if (methods.length) {
+ module.push(methods);
+ if (asyncMethods.length) {
+ module.push(asyncMethods);
+ }
+ }
+ }
+ moduleArray[moduleConfig.moduleID] = module;
}
+ return moduleArray;
}
}
+ _genLookupTables(modulesConfig, moduleTable, methodTable) {
+ modulesConfig.forEach((module, moduleID) => {
+ if (!module) {
+ return;
+ }
+
+ let moduleName, methods;
+ if (moduleHasConstants(module)) {
+ [moduleName, , methods] = module;
+ } else {
+ [moduleName, methods] = module;
+ }
+
+ moduleTable[moduleID] = moduleName;
+ methodTable[moduleID] = Object.assign({}, methods);
+ });
+ }
+
_genModules(remoteModules) {
- let moduleNames = Object.keys(remoteModules);
- for (var i = 0, l = moduleNames.length; i < l; i++) {
- let moduleName = moduleNames[i];
- let moduleConfig = remoteModules[moduleName];
+ remoteModules.forEach((module, moduleID) => {
+ if (!module) {
+ return;
+ }
+
+ let moduleName, constants, methods, asyncMethods;
+ if (moduleHasConstants(module)) {
+ [moduleName, constants, methods, asyncMethods] = module;
+ } else {
+ [moduleName, methods, asyncMethods] = module;
+ }
+
+ const moduleConfig = {moduleID, constants, methods, asyncMethods};
this.RemoteModules[moduleName] = this._genModule({}, moduleConfig);
- }
+ });
}
_genModule(module, moduleConfig) {
- let methodNames = Object.keys(moduleConfig.methods);
- for (var i = 0, l = methodNames.length; i < l; i++) {
- let methodName = methodNames[i];
- let methodConfig = moduleConfig.methods[methodName];
- module[methodName] = this._genMethod(
- moduleConfig.moduleID, methodConfig.methodID, methodConfig.type);
- }
- Object.assign(module, moduleConfig.constants);
+ const {moduleID, constants, methods = [], asyncMethods = []} = moduleConfig;
+
+ methods.forEach((methodName, methodID) => {
+ const methodType =
+ arrayContains(asyncMethods, methodID) ?
+ MethodTypes.remoteAsync : MethodTypes.remote;
+ module[methodName] = this._genMethod(moduleID, methodID, methodType);
+ });
+ Object.assign(module, constants);
+
return module;
}
_genMethod(module, method, type) {
- if (type === MethodTypes.local) {
- return null;
- }
-
let fn = null;
let self = this;
if (type === MethodTypes.remoteAsync) {
@@ -260,7 +314,15 @@ class MessageQueue {
}
-function createErrorFromErrorData(errorData: ErrorData): Error {
+function moduleHasConstants(moduleArray: Array