diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js index a6e041ceb61c8d..9fd6052c968e1b 100644 --- a/Examples/2048/Game2048.js +++ b/Examples/2048/Game2048.js @@ -150,7 +150,7 @@ class Game2048 extends React.Component { startX: number; startY: number; - constructor(props) { + constructor(props: {}) { super(props); this.state = { board: new GameBoard(), diff --git a/Examples/UIExplorer/AlertIOSExample.js b/Examples/UIExplorer/AlertIOSExample.js index a25a319e7d91e6..2d57fd5fe88194 100644 --- a/Examples/UIExplorer/AlertIOSExample.js +++ b/Examples/UIExplorer/AlertIOSExample.js @@ -95,9 +95,98 @@ exports.examples = [{ ); - }, + } +}, +{ + title: 'Prompt', + render(): React.Component { + return + } }]; +class PromptExample extends React.Component { + constructor(props) { + super(props); + + this.promptResponse = this.promptResponse.bind(this); + this.state = { + promptValue: undefined, + }; + + this.title = 'Type a value'; + this.defaultValue = 'Default value'; + this.buttons = [{ + text: 'Custom cancel', + }, { + text: 'Custom OK', + onPress: this.promptResponse + }]; + } + + render() { + return ( + + + Prompt value: {this.state.promptValue} + + + + + + + prompt with title & callback + + + + + + + + + prompt with title & custom buttons + + + + + + + + + prompt with title, default value & callback + + + + + + + + + prompt with title, default value & custom buttons + + + + + ); + } + + prompt() { + // Flow's apply support is broken: #7035621 + ((AlertIOS.prompt: any).apply: any)(AlertIOS, arguments); + } + + promptResponse(promptValue) { + this.setState({ promptValue }); + } +} + var styles = StyleSheet.create({ wrapper: { borderRadius: 5, diff --git a/Examples/UIExplorer/BorderExample.js b/Examples/UIExplorer/BorderExample.js index 1790dc49164d46..f49329faef0118 100644 --- a/Examples/UIExplorer/BorderExample.js +++ b/Examples/UIExplorer/BorderExample.js @@ -68,6 +68,18 @@ var styles = StyleSheet.create({ borderLeftWidth: 40, borderLeftColor: 'blue', }, + border6: { + borderTopWidth: 10, + borderTopColor: 'red', + borderRightWidth: 20, + borderRightColor: 'yellow', + borderBottomWidth: 30, + borderBottomColor: 'green', + borderLeftWidth: 40, + borderLeftColor: 'blue', + + borderTopLeftRadius: 100, + }, }); exports.title = 'Border'; @@ -115,4 +127,11 @@ exports.examples = [ return ; } }, + { + title: 'Custom Borders', + description: 'border*Width & border*Color', + render() { + return ; + } + }, ]; diff --git a/Examples/UIExplorer/ExampleTypes.js b/Examples/UIExplorer/ExampleTypes.js new file mode 100644 index 00000000000000..7f6ff08d80aa67 --- /dev/null +++ b/Examples/UIExplorer/ExampleTypes.js @@ -0,0 +1,30 @@ +/** + * 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. + * + * @providesModule ExampleTypes + * @flow + */ + +export type Example = { + title: string, + render: () => ?ReactElement, + description?: string, +}; + +export type ExampleModule = { + title: string; + description: string; + examples: Array; + external?: bool; +}; + diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index ab6bd0717d8ec4..e8dae25ac30c59 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -168,7 +168,7 @@ var MapViewExample = React.createClass({ /> ); diff --git a/Examples/UIExplorer/PointerEventsExample.js b/Examples/UIExplorer/PointerEventsExample.js index 035f75814fd852..cd402c471d7ae8 100644 --- a/Examples/UIExplorer/PointerEventsExample.js +++ b/Examples/UIExplorer/PointerEventsExample.js @@ -181,7 +181,13 @@ var BoxOnlyExample = React.createClass({ } }); -var exampleClasses = [ +type ExampleClass = { + Component: ReactClass, + title: string, + description: string, +}; + +var exampleClasses: Array = [ { Component: NoneExample, title: '`none`', diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js index 4889bc4725529b..10323c7a50ba7a 100644 --- a/Examples/UIExplorer/TextExample.ios.js +++ b/Examples/UIExplorer/TextExample.ios.js @@ -218,6 +218,26 @@ exports.examples = [ ); }, +}, { + title: 'Letter Spacing', + render: function() { + return ( + + + letterSpacing = 0 + + + letterSpacing = 2 + + + letterSpacing = 9 + + + letterSpacing = -1 + + + ); + }, }, { title: 'Spaces', render: function() { diff --git a/Examples/UIExplorer/UIExplorerBlock.js b/Examples/UIExplorer/UIExplorerBlock.js index e7c2a2a8ea9d16..10a2b57cdc8a78 100644 --- a/Examples/UIExplorer/UIExplorerBlock.js +++ b/Examples/UIExplorer/UIExplorerBlock.js @@ -69,9 +69,10 @@ var styles = StyleSheet.create({ overflow: 'hidden', }, titleContainer: { - borderWidth: 0.5, - borderRadius: 2.5, - borderColor: '#d6d7da', + borderBottomWidth: 0.5, + borderTopLeftRadius: 3, + borderTopRightRadius: 2.5, + borderBottomColor: '#d6d7da', backgroundColor: '#f6f7f8', paddingHorizontal: 10, paddingVertical: 5, diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index dd2336df59dff0..948ac6f083b7af 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -30,6 +30,8 @@ var { var { TestModule } = React.addons; var Settings = require('Settings'); +import type { Example, ExampleModule } from 'ExampleTypes'; + var createExamplePage = require('./createExamplePage'); var COMPONENTS = [ @@ -107,9 +109,17 @@ COMPONENTS.concat(APIS).forEach((Example) => { } }); +type Props = { + navigator: Array<{title: string, component: ReactClass}>, + onExternalExampleRequested: Function, +}; + + + class UIExplorerList extends React.Component { + props: Props; - constructor(props) { + constructor(props: Props) { super(props); this.state = { dataSource: ds.cloneWithRowsAndSections({ @@ -149,7 +159,7 @@ class UIExplorerList extends React.Component { ); } - _renderSectionHeader(data, section) { + _renderSectionHeader(data: any, section: string) { return ( @@ -159,7 +169,7 @@ class UIExplorerList extends React.Component { ); } - _renderRow(example, i) { + _renderRow(example: ExampleModule, i: number) { return ( this._onPressRow(example)}> @@ -177,7 +187,7 @@ class UIExplorerList extends React.Component { ); } - _search(text) { + _search(text: mixed) { var regex = new RegExp(text, 'i'); var filter = (component) => regex.test(component.title); @@ -191,7 +201,7 @@ class UIExplorerList extends React.Component { Settings.set({searchText: text}); } - _onPressRow(example) { + _onPressRow(example: ExampleModule) { if (example.external) { this.props.onExternalExampleRequested(example); return; diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png index 2c6675b7b4eeba..edde59bcc1c07f 100644 Binary files a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png and b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m index df748ef0376ec2..974e3281c14426 100644 --- a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m +++ b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m @@ -88,35 +88,19 @@ - (void)testAAA_RootViewLoadsAndRenders XCTAssertTrue(foundElement, @"Cound't find element with '' text in %d seconds", TIMEOUT_SECONDS); } -- (void)testViewExampleSnapshot -{ - [_runner runTest:_cmd module:@"ViewExample"]; -} - -- (void)testLayoutExampleSnapshot -{ - [_runner runTest:_cmd module:@"LayoutExample"]; +#define RCT_SNAPSHOT_TEST(name, reRecord) \ +- (void)test##name##Snapshot \ +{ \ + _runner.recordMode |= reRecord; \ + [_runner runTest:_cmd module:@#name]; \ } -- (void)testTextExampleSnapshot -{ - [_runner runTest:_cmd module:@"TextExample"]; -} - -- (void)testSwitchExampleSnapshot -{ - [_runner runTest:_cmd module:@"SwitchExample"]; -} - -- (void)testSliderExampleSnapshot -{ - [_runner runTest:_cmd module:@"SliderExample"]; -} - -- (void)testTabBarExampleSnapshot -{ - [_runner runTest:_cmd module:@"TabBarExample"]; -} +RCT_SNAPSHOT_TEST(ViewExample, NO) +RCT_SNAPSHOT_TEST(LayoutExample, NO) +RCT_SNAPSHOT_TEST(TextExample, NO) +RCT_SNAPSHOT_TEST(SwitchExample, NO) +RCT_SNAPSHOT_TEST(SliderExample, NO) +RCT_SNAPSHOT_TEST(TabBarExample, NO) // Make sure this test runs last - (void)testZZZ_NotInRecordMode diff --git a/Examples/UIExplorer/createExamplePage.js b/Examples/UIExplorer/createExamplePage.js index 4bc933ebaa11b9..3d5a1ac88c4c98 100644 --- a/Examples/UIExplorer/createExamplePage.js +++ b/Examples/UIExplorer/createExamplePage.js @@ -17,22 +17,13 @@ 'use strict'; var React = require('react-native'); -var ReactIOS = require('ReactIOS'); +var ReactNative = require('ReactNative'); var UIExplorerBlock = require('./UIExplorerBlock'); var UIExplorerPage = require('./UIExplorerPage'); var invariant = require('invariant'); -class Example extends React.Component { - title: string; - description: string; -} - -type ExampleModule = { - title: string; - description: string; - examples: Array; -}; +import type { Example, ExampleModule } from 'ExampleTypes'; var createExamplePage = function(title: ?string, exampleModule: ExampleModule) : ReactClass { @@ -44,20 +35,20 @@ var createExamplePage = function(title: ?string, exampleModule: ExampleModule) description: exampleModule.description, }, - getBlock: function(example, i) { + getBlock: function(example: Example, i) { // Hack warning: This is a hack because the www UI explorer requires // renderComponent to be called. var originalRender = React.render; var originalRenderComponent = React.renderComponent; - var originalIOSRender = ReactIOS.render; - var originalIOSRenderComponent = ReactIOS.renderComponent; + var originalIOSRender = ReactNative.render; + var originalIOSRenderComponent = ReactNative.renderComponent; var renderedComponent; // TODO remove typecasts when Flow bug #6560135 is fixed // and workaround is removed from react-native.js (React: Object).render = (React: Object).renderComponent = - (ReactIOS: Object).render = - (ReactIOS: Object).renderComponent = + (ReactNative: Object).render = + (ReactNative: Object).renderComponent = function(element, container) { renderedComponent = element; }; @@ -67,8 +58,8 @@ var createExamplePage = function(title: ?string, exampleModule: ExampleModule) } (React: Object).render = originalRender; (React: Object).renderComponent = originalRenderComponent; - (ReactIOS: Object).render = originalIOSRender; - (ReactIOS: Object).renderComponent = originalIOSRenderComponent; + (ReactNative: Object).render = originalIOSRender; + (ReactNative: Object).renderComponent = originalIOSRenderComponent; return ( number; @@ -47,7 +48,7 @@ var AnimationExperimental = { }, callback?: ?(finished: bool) => void ): number { - var nodeHandle = anim.node.getNodeHandle(); + var nodeHandle = React.findNodeHandle(anim.node); var easingSample = AnimationUtils.evaluateEasingFunction( anim.duration, anim.easing diff --git a/Libraries/Animation/POPAnimationMixin.js b/Libraries/Animation/POPAnimationMixin.js index 213023290b3696..115e58a01cb482 100644 --- a/Libraries/Animation/POPAnimationMixin.js +++ b/Libraries/Animation/POPAnimationMixin.js @@ -12,6 +12,7 @@ 'use strict'; var POPAnimationOrNull = require('POPAnimation'); +var React = require('React'); if (!POPAnimationOrNull) { // POP animation isn't available in the OSS fork - this is a temporary @@ -83,7 +84,7 @@ var POPAnimationMixin = { 'Invalid refKey ' + refKey + ' for anim:\n' + JSON.stringify(anim) + '\nvalid refs: ' + JSON.stringify(Object.keys(this.refs)) ); - var refNodeHandle = this.refs[refKey].getNodeHandle(); + var refNodeHandle = React.findNodeHandle(this.refs[refKey]); this.startAnimationWithNodeHandle(refNodeHandle, animID, doneCallback); }, @@ -192,7 +193,7 @@ var POPAnimationMixin = { */ stopAnimations: function(refKey: string) { invariant(this.refs[refKey], 'invalid ref'); - this.stopNodeHandleAnimations(this.refs[refKey].getNodeHandle()); + this.stopNodeHandleAnimations(React.findNodeHandle(this.refs[refKey])); }, /** diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js index 959bf238304f72..0245cc14415ce0 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js @@ -15,7 +15,9 @@ var RCTPOPAnimationManager = require('NativeModules').POPAnimationManager; if (!RCTPOPAnimationManager) { // POP animation isn't available in the OSS fork - this is a temporary // workaround to enable its availability to be determined at runtime. - module.exports = (null: ?Object); + // For Flow let's pretend like we always export POPAnimation + // so all our users don't need to do null checks + module.exports = ((null: any): typeof POPAnimation); } else { var ReactPropTypes = require('ReactPropTypes'); diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js b/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js index 7f875946e44a4c..8c66aac9aa7b19 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js @@ -11,7 +11,7 @@ */ 'use strict'; -var ReactIOSEventEmitter = require('ReactIOSEventEmitter'); +var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); // Completely locally implemented - no native hooks. -module.exports = ReactIOSEventEmitter; +module.exports = ReactNativeEventEmitter; diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js index 6acc949e5449c3..0d008ae75a7abe 100644 --- a/Libraries/CameraRoll/CameraRoll.js +++ b/Libraries/CameraRoll/CameraRoll.js @@ -92,6 +92,8 @@ var getPhotosReturnChecker = createStrictShapeTypeChecker({ }); class CameraRoll { + + static GroupTypesOptions: Array; /** * Saves the image with tag `tag` to the camera roll. * diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index e38dd9564bbc3c..3e387835c35a84 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -15,10 +15,10 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var NativeMethodsMixin = require('NativeMethodsMixin'); var Platform = require('Platform'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var View = require('View'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = require('createReactNativeComponentClass'); var deepDiffer = require('deepDiffer'); var insetsDiffer = require('insetsDiffer'); var merge = require('merge'); @@ -163,9 +163,9 @@ var MapView = React.createClass({ }); if (Platform.OS === 'android') { - var RCTMap = createReactIOSNativeComponentClass({ + var RCTMap = createReactNativeComponentClass({ validAttributes: merge( - ReactIOSViewAttributes.UIView, { + ReactNativeViewAttributes.UIView, { showsUserLocation: true, zoomEnabled: true, rotateEnabled: true, diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 103e749f78b237..ef3ecd1585dcf1 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -14,14 +14,14 @@ var EventEmitter = require('EventEmitter'); var Image = require('Image'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var RCTNavigatorManager = require('NativeModules').NavigatorManager; var StyleSheet = require('StyleSheet'); var StaticContainer = require('StaticContainer.react'); var View = require('View'); -var createReactIOSNativeComponentClass = - require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = + require('createReactNativeComponentClass'); var invariant = require('invariant'); var logError = require('logError'); var merge = require('merge'); @@ -35,14 +35,14 @@ function getuid() { return __uid++; } -var RCTNavigator = createReactIOSNativeComponentClass({ - validAttributes: merge(ReactIOSViewAttributes.UIView, { +var RCTNavigator = createReactNativeComponentClass({ + validAttributes: merge(ReactNativeViewAttributes.UIView, { requestedTopOfStack: true }), uiViewClassName: 'RCTNavigator', }); -var RCTNavigatorItem = createReactIOSNativeComponentClass({ +var RCTNavigatorItem = createReactNativeComponentClass({ validAttributes: { // TODO: Remove or fix the attributes that are not fully functional. // NavigatorIOS does not use them all, because some are problematic @@ -67,7 +67,7 @@ var RCTNavigatorItem = createReactIOSNativeComponentClass({ var NavigatorTransitionerIOS = React.createClass({ requestSchedulingNavigation: function(cb) { RCTNavigatorManager.requestSchedulingJavaScriptNavigation( - (this: any).getNodeHandle(), + React.findNodeHandle(this), logError, cb ); diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 74be17a46ea104..0818a0bf91cfde 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -13,6 +13,7 @@ var NativeModules = require('NativeModules'); var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +var React = require('React'); var Subscribable = require('Subscribable'); var TextInputState = require('TextInputState'); @@ -350,7 +351,7 @@ var ScrollResponderMixin = { * can also be used to quickly scroll to any element we want to focus */ scrollResponderScrollTo: function(offsetX: number, offsetY: number) { - RCTUIManagerDeprecated.scrollTo(this.getNodeHandle(), offsetX, offsetY); + RCTUIManagerDeprecated.scrollTo(React.findNodeHandle(this), offsetX, offsetY); }, /** @@ -358,7 +359,7 @@ var ScrollResponderMixin = { * @param {object} rect Should have shape {x, y, width, height} */ scrollResponderZoomTo: function(rect: { x: number; y: number; width: number; height: number; }) { - RCTUIManagerDeprecated.zoomToRect(this.getNodeHandle(), rect); + RCTUIManagerDeprecated.zoomToRect(React.findNodeHandle(this), rect); }, /** @@ -376,7 +377,7 @@ var ScrollResponderMixin = { this.preventNegativeScrollOffset = !!preventNegativeScrollOffset; RCTUIManager.measureLayout( nodeHandle, - this.getNodeHandle(), + React.findNodeHandle(this), this.scrollResponderTextInputFocusError, this.scrollResponderInputMeasureAndScrollToKeyboard ); diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 3b5122b24774aa..3a87d05a8a4296 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -17,7 +17,7 @@ var PointPropType = require('PointPropType'); var RCTScrollView = require('NativeModules').UIManager.RCTScrollView; var RCTScrollViewConsts = RCTScrollView.Constants; var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var RCTUIManager = require('NativeModules').UIManager; var ScrollResponder = require('ScrollResponder'); var StyleSheet = require('StyleSheet'); @@ -25,7 +25,7 @@ var StyleSheetPropType = require('StyleSheetPropType'); var View = require('View'); var ViewStylePropTypes = require('ViewStylePropTypes'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = require('createReactNativeComponentClass'); var deepDiffer = require('deepDiffer'); var flattenStyle = require('flattenStyle'); var insetsDiffer = require('insetsDiffer'); @@ -207,19 +207,19 @@ var ScrollView = React.createClass({ }, getInnerViewNode: function(): any { - return this.refs[INNERVIEW].getNodeHandle(); + return React.findNodeHandle(this.refs[INNERVIEW]); }, scrollTo: function(destY?: number, destX?: number) { if (Platform.OS === 'android') { RCTUIManager.dispatchViewManagerCommand( - this.getNodeHandle(), + React.findNodeHandle(this), RCTUIManager.RCTScrollView.Commands.scrollTo, [destX || 0, destY || 0] ); } else { RCTUIManager.scrollTo( - this.getNodeHandle(), + React.findNodeHandle(this), destX || 0, destY || 0 ); @@ -228,7 +228,7 @@ var ScrollView = React.createClass({ scrollWithoutAnimationTo: function(destY?: number, destX?: number) { RCTUIManager.scrollWithoutAnimationTo( - this.getNodeHandle(), + React.findNodeHandle(this), destX || 0, destY || 0 ); @@ -343,7 +343,7 @@ var styles = StyleSheet.create({ }); var validAttributes = { - ...ReactIOSViewAttributes.UIView, + ...ReactNativeViewAttributes.UIView, alwaysBounceHorizontal: true, alwaysBounceVertical: true, automaticallyAdjustContentInsets: true, @@ -370,11 +370,11 @@ var validAttributes = { }; if (Platform.OS === 'android') { - var AndroidScrollView = createReactIOSNativeComponentClass({ + var AndroidScrollView = createReactNativeComponentClass({ validAttributes: validAttributes, uiViewClassName: 'RCTScrollView', }); - var AndroidHorizontalScrollView = createReactIOSNativeComponentClass({ + var AndroidHorizontalScrollView = createReactNativeComponentClass({ validAttributes: validAttributes, uiViewClassName: 'AndroidHorizontalScrollView', }); diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index fa87beefc7b513..7d44754f341623 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -19,14 +19,14 @@ var Platform = require('Platform'); var PropTypes = require('ReactPropTypes'); var React = require('React'); var ReactChildren = require('ReactChildren'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheet = require('StyleSheet'); var Text = require('Text'); var TextInputState = require('TextInputState'); var TimerMixin = require('react-timer-mixin'); var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = require('createReactNativeComponentClass'); var emptyFunction = require('emptyFunction'); var invariant = require('invariant'); var merge = require('merge'); @@ -35,7 +35,7 @@ var autoCapitalizeConsts = RCTUIManager.UIText.AutocapitalizationType; var keyboardTypeConsts = RCTUIManager.UIKeyboardType; var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType; -var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, { +var RCTTextViewAttributes = merge(ReactNativeViewAttributes.UIView, { autoCorrect: true, autoCapitalize: true, clearTextOnFocus: true, @@ -305,7 +305,7 @@ var TextInput = React.createClass({ isFocused: function(): boolean { return TextInputState.currentlyFocusedField() === - this.refs.input.getNativeNode(); + React.findNodeHandle(this.refs.input); }, getDefaultProps: function(): DefaultProps { @@ -582,7 +582,7 @@ var TextInput = React.createClass({ var counter = event.nativeEvent.eventCounter; if (counter > this.state.mostRecentEventCounter) { this.setState({mostRecentEventCounter: counter}); - } + } }, }); @@ -592,17 +592,17 @@ var styles = StyleSheet.create({ }, }); -var RCTTextView = createReactIOSNativeComponentClass({ +var RCTTextView = createReactNativeComponentClass({ validAttributes: RCTTextViewAttributes, uiViewClassName: 'RCTTextView', }); -var RCTTextField = createReactIOSNativeComponentClass({ +var RCTTextField = createReactNativeComponentClass({ validAttributes: RCTTextFieldAttributes, uiViewClassName: 'RCTTextField', }); -var AndroidTextInput = createReactIOSNativeComponentClass({ +var AndroidTextInput = createReactNativeComponentClass({ validAttributes: AndroidTextInputAttributes, uiViewClassName: 'AndroidTextInput', }); diff --git a/Libraries/Components/TextInput/TextInputState.js b/Libraries/Components/TextInput/TextInputState.js index f22ac79274154e..da1f5cf60df247 100644 --- a/Libraries/Components/TextInput/TextInputState.js +++ b/Libraries/Components/TextInput/TextInputState.js @@ -21,22 +21,22 @@ var TextInputState = { /** * Internal state */ - _currentlyFocusedID: (null: ?string), + _currentlyFocusedID: (null: ?number), /** * Returns the ID of the currently focused text field, if one exists * If no text field is focused it returns null */ - currentlyFocusedField: function(): ?string { + currentlyFocusedField: function(): ?number { return this._currentlyFocusedID; }, /** - * @param {string} TextInputID id of the text field to focus + * @param {number} TextInputID id of the text field to focus * Focuses the specified text field * noop if the text field was already focused */ - focusTextInput: function(textFieldID: string) { + focusTextInput: function(textFieldID: ?number) { if (this._currentlyFocusedID !== textFieldID && textFieldID !== null) { this._currentlyFocusedID = textFieldID; RCTUIManager.focus(textFieldID); @@ -44,11 +44,11 @@ var TextInputState = { }, /** - * @param {string} textFieldID id of the text field to focus + * @param {number} textFieldID id of the text field to focus * Unfocuses the specified text field * noop if it wasn't focused */ - blurTextInput: function(textFieldID: string) { + blurTextInput: function(textFieldID: ?number) { if (this._currentlyFocusedID === textFieldID && textFieldID !== null) { this._currentlyFocusedID = null; RCTUIManager.blur(textFieldID); diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index 080a6750c2666b..b96d8d0d0656f7 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -14,7 +14,7 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheet = require('StyleSheet'); var TimerMixin = require('react-timer-mixin'); var Touchable = require('Touchable'); @@ -120,7 +120,7 @@ var TouchableHighlight = React.createClass({ viewConfig: { uiViewClassName: 'RCTView', - validAttributes: ReactIOSViewAttributes.RCTView + validAttributes: ReactNativeViewAttributes.RCTView }, /** diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index aa69ab0eb70c66..bd7529e58fe725 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -15,12 +15,12 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var PropTypes = require('ReactPropTypes'); var RCTUIManager = require('NativeModules').UIManager; var React = require('React'); -var ReactIOSStyleAttributes = require('ReactIOSStyleAttributes'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheetPropType = require('StyleSheetPropType'); var ViewStylePropTypes = require('ViewStylePropTypes'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = require('createReactNativeComponentClass'); var stylePropType = StyleSheetPropType(ViewStylePropTypes); @@ -53,7 +53,7 @@ var View = React.createClass({ */ viewConfig: { uiViewClassName: 'RCTView', - validAttributes: ReactIOSViewAttributes.RCTView + validAttributes: ReactNativeViewAttributes.RCTView }, propTypes: { @@ -163,8 +163,8 @@ var View = React.createClass({ }, }); -var RCTView = createReactIOSNativeComponentClass({ - validAttributes: ReactIOSViewAttributes.RCTView, +var RCTView = createReactNativeComponentClass({ + validAttributes: ReactNativeViewAttributes.RCTView, uiViewClassName: 'RCTView', }); RCTView.propTypes = View.propTypes; @@ -172,7 +172,7 @@ if (__DEV__) { var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs.RCTView || {}; for (var prop in viewConfig.nativeProps) { var viewAny: any = View; // Appease flow - if (!viewAny.propTypes[prop] && !ReactIOSStyleAttributes[prop]) { + if (!viewAny.propTypes[prop] && !ReactNativeStyleAttributes[prop]) { throw new Error( 'View is missing propType for native prop `' + prop + '`' ); diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index c1f6b4b1ccf7d5..f2271f4d89ac18 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -28,6 +28,10 @@ var ViewStylePropTypes = { borderBottomColor: ReactPropTypes.string, borderLeftColor: ReactPropTypes.string, borderRadius: ReactPropTypes.number, + borderTopLeftRadius: ReactPropTypes.number, + borderTopRightRadius: ReactPropTypes.number, + borderBottomLeftRadius: ReactPropTypes.number, + borderBottomRightRadius: ReactPropTypes.number, opacity: ReactPropTypes.number, overflow: ReactPropTypes.oneOf(['visible', 'hidden']), shadowColor: ReactPropTypes.string, diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js index 79ded650658a2a..a9ee724fa1b0c5 100644 --- a/Libraries/Components/WebView/WebView.android.js +++ b/Libraries/Components/WebView/WebView.android.js @@ -12,11 +12,11 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheet = require('StyleSheet'); var View = require('View'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = require('createReactNativeComponentClass'); var keyMirror = require('keyMirror'); var merge = require('merge'); @@ -143,7 +143,7 @@ var WebView = React.createClass({ }, getWebWiewHandle: function() { - return this.refs[RCT_WEBVIEW_REF].getNodeHandle(); + return React.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); }, onLoadingStart: function(event) { @@ -168,8 +168,8 @@ var WebView = React.createClass({ }, }); -var RCTWebView = createReactIOSNativeComponentClass({ - validAttributes: merge(ReactIOSViewAttributes.UIView, { +var RCTWebView = createReactNativeComponentClass({ + validAttributes: merge(ReactNativeViewAttributes.UIView, { url: true, javaScriptEnabledAndroid: true, }), diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index ed2c98fae02f5a..de9ff2ef14cbb3 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -14,7 +14,7 @@ var ActivityIndicatorIOS = require('ActivityIndicatorIOS'); var EdgeInsetsPropType = require('EdgeInsetsPropType'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheet = require('StyleSheet'); var Text = require('Text'); var View = require('View'); @@ -189,7 +189,7 @@ var WebView = React.createClass({ }, getWebWiewHandle: function(): any { - return this.refs[RCT_WEBVIEW_REF].getNodeHandle(); + return React.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); }, onLoadingStart: function(event: Event) { diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 3fdb980b54b83b..0a03bd38bf9558 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -346,12 +346,12 @@ var ListView = React.createClass({ _measureAndUpdateScrollProps: function() { RCTUIManager.measureLayout( this.refs[SCROLLVIEW_REF].getInnerViewNode(), - this.refs[SCROLLVIEW_REF].getNodeHandle(), + React.findNodeHandle(this.refs[SCROLLVIEW_REF]), logError, this._setScrollContentHeight ); RCTUIManager.measureLayoutRelativeToParent( - this.refs[SCROLLVIEW_REF].getNodeHandle(), + React.findNodeHandle(this.refs[SCROLLVIEW_REF]), logError, this._setScrollVisibleHeight ); diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index c009e8086f10d9..bc044f1b957986 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -253,7 +253,7 @@ var Navigator = React.createClass({ onDidFocus: PropTypes.func, /** - * Will be called with (ref, indexInStack) when the scene ref changes + * Will be called with (ref, indexInStack, route) when the scene ref changes */ onItemRef: PropTypes.func, @@ -582,7 +582,7 @@ 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.spring.getCurrentValue() !== 1) { + 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 if (this.state.pendingGestureProgress) { @@ -659,11 +659,17 @@ var Navigator = React.createClass({ }, /** - * Hides scenes that we are not currently on or transitioning from + * Hides all scenes that we are not currently on, gesturing to, or transitioning from */ _hideScenes: function() { + var gesturingToIndex = null; + if (this.state.activeGesture) { + gesturingToIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture); + } for (var i = 0; i < this.state.routeStack.length; i++) { - if (i === this.state.presentedIndex || i === this.state.transitionFromIndex) { + if (i === this.state.presentedIndex || + i === this.state.transitionFromIndex || + i === gesturingToIndex) { continue; } this._disableScene(i); @@ -734,10 +740,14 @@ var Navigator = React.createClass({ viewAtIndex.setNativeProps({renderToHardwareTextureAndroid: shouldRenderToHardwareTexture}); }, + _handleTouchStart: function() { + this._eligibleGestures = GESTURE_ACTIONS; + }, + _handleMoveShouldSetPanResponder: function(e, gestureState) { var currentRoute = this.state.routeStack[this.state.presentedIndex]; var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex]; - this._expectingGestureGrant = this._matchGestureAction(sceneConfig.gestures, gestureState); + this._expectingGestureGrant = this._matchGestureAction(this._eligibleGestures, sceneConfig.gestures, gestureState); return !! this._expectingGestureGrant; }, @@ -855,7 +865,7 @@ var Navigator = React.createClass({ var gesture = sceneConfig.gestures[this.state.activeGesture]; return this._moveAttachedGesture(gesture, gestureState); } - var matchedGesture = this._matchGestureAction(sceneConfig.gestures, gestureState); + var matchedGesture = this._matchGestureAction(GESTURE_ACTIONS, sceneConfig.gestures, gestureState); if (matchedGesture) { this._attachGesture(matchedGesture); } @@ -870,8 +880,13 @@ var Navigator = React.createClass({ var nextProgress = (distance - gestureDetectMovement) / (gesture.fullDistance - gestureDetectMovement); if (nextProgress < 0 && gesture.isDetachable) { + var gesturingToIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture); + this._transitionBetween(this.state.presentedIndex, gesturingToIndex, 0); this._detachGesture(); - this.spring.setCurrentValue(0); + if (this.state.pendingGestureProgress != null) { + this.spring.setCurrentValue(0); + } + return; } if (this._doesGestureOverswipe(this.state.activeGesture)) { var frictionConstant = gesture.overswipe.frictionConstant; @@ -889,12 +904,12 @@ var Navigator = React.createClass({ } }, - _matchGestureAction: function(gestures, gestureState) { + _matchGestureAction: function(eligibleGestures, gestures, gestureState) { if (!gestures) { return null; } var matchedGesture = null; - GESTURE_ACTIONS.some((gestureName) => { + eligibleGestures.some((gestureName, gestureIndex) => { var gesture = gestures[gestureName]; if (!gesture) { return; @@ -920,12 +935,19 @@ var Navigator = React.createClass({ } var moveStartedInRegion = gesture.edgeHitWidth == null || currentLoc < edgeHitWidth; - var moveTravelledFarEnough = - travelDist >= gesture.gestureDetectMovement && - travelDist > oppositeAxisTravelDist * gesture.directionRatio; - if (moveStartedInRegion && moveTravelledFarEnough) { + if (!moveStartedInRegion) { + return false; + } + var moveTravelledFarEnough = travelDist >= gesture.gestureDetectMovement; + if (!moveTravelledFarEnough) { + return false; + } + var directionIsCorrect = Math.abs(travelDist) > Math.abs(oppositeAxisTravelDist) * gesture.directionRatio; + if (directionIsCorrect) { matchedGesture = gestureName; return true; + } else { + this._eligibleGestures = this._eligibleGestures.slice().splice(gestureIndex, 1); } }); return matchedGesture; @@ -1159,13 +1181,13 @@ var Navigator = React.createClass({ return this.state.routeStack; }, - _handleItemRef: function(itemId, ref) { + _handleItemRef: function(itemId, route, ref) { this._itemRefs[itemId] = ref; var itemIndex = this.state.idStack.indexOf(itemId); if (itemIndex === -1) { return; } - this.props.onItemRef && this.props.onItemRef(ref, itemIndex); + this.props.onItemRef && this.props.onItemRef(ref, itemIndex, route); }, _cleanScenesPastIndex: function(index) { @@ -1202,6 +1224,7 @@ var Navigator = React.createClass({ @@ -1273,7 +1296,7 @@ var Navigator = React.createClass({ }} style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}> {React.cloneElement(child, { - ref: this._handleItemRef.bind(null, this.state.idStack[i]), + ref: this._handleItemRef.bind(null, this.state.idStack[i], route), })} ); diff --git a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js index 3b4666b9388ce5..ed295346d048b5 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js +++ b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js @@ -101,6 +101,18 @@ var FadeToTheLeft = { }, }; +var FadeToTheRight = { + ...FadeToTheLeft, + transformTranslate: { + from: {x: 0, y: 0, z: 0}, + to: {x: Math.round(SCREEN_WIDTH * 0.3), y: 0, z: 0}, + }, + translateX: { + from: 0, + to: Math.round(SCREEN_WIDTH * 0.3), + } +}; + var FadeIn = { opacity: { from: 0, @@ -187,6 +199,27 @@ var FromTheRight = { }, }; +var FromTheLeft = { + ...FromTheRight, + transformTranslate: { + from: {x: -SCREEN_WIDTH, y: 0, z: 0}, + to: {x: 0, y: 0, z: 0}, + min: 0, + max: 1, + type: 'linear', + extrapolate: true, + round: PixelRatio.get(), + }, + translateX: { + from: -SCREEN_WIDTH, + to: 0, + min: 0, + max: 1, + type: 'linear', + extrapolate: true, + round: PixelRatio.get(), + }, +}; var ToTheBack = { // Rotate *requires* you to break out each individual component of @@ -374,6 +407,13 @@ var NavigatorSceneConfigs = { ...BaseConfig, // We will want to customize this soon }, + FloatFromLeft: { + ...BaseConfig, + animationInterpolators: { + into: buildStyleInterpolator(FromTheLeft), + out: buildStyleInterpolator(FadeToTheRight), + }, + }, FloatFromBottom: { ...BaseConfig, gestures: { diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 32965c213ac449..f388468c45c6a0 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -18,7 +18,7 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var NativeModules = require('NativeModules'); var PropTypes = require('ReactPropTypes'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheet = require('StyleSheet'); var StyleSheetPropType = require('StyleSheetPropType'); @@ -113,7 +113,7 @@ var Image = React.createClass({ */ viewConfig: { uiViewClassName: 'UIView', - validAttributes: ReactIOSViewAttributes.UIView + validAttributes: ReactNativeViewAttributes.UIView }, render: function() { diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h index e607acabbf22ca..186a53cd1046b0 100644 --- a/Libraries/Image/RCTImageLoader.h +++ b/Libraries/Image/RCTImageLoader.h @@ -15,6 +15,12 @@ @interface RCTImageLoader : NSObject + (ALAssetsLibrary *)assetsLibrary; -+ (void)loadImageWithTag:(NSString *)tag callback:(void (^)(NSError *error, UIImage *image))callback; + +/** + * Can be called from any thread. + * Will always call callback on main thread. + */ ++ (void)loadImageWithTag:(NSString *)tag + callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback; @end diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 34158f6920da4e..948503ccededc6 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -16,6 +16,7 @@ #import #import "RCTConvert.h" +#import "RCTGIFImage.h" #import "RCTImageDownloader.h" #import "RCTLog.h" @@ -32,13 +33,24 @@ static dispatch_queue_t RCTImageLoaderQueue(void) return queue; } -NSError *errorWithMessage(NSString *message) +static NSError *RCTErrorWithMessage(NSString *message) { NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message}; NSError *error = [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]; return error; } +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 RCTImageLoader + (ALAssetsLibrary *)assetsLibrary @@ -51,7 +63,11 @@ + (ALAssetsLibrary *)assetsLibrary return assetsLibrary; } -+ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, UIImage *image))callback +/** + * Can be called from any thread. + * Will always call callback on main thread. + */ ++ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, id image))callback { if ([imageTag hasPrefix:@"assets-library"]) { [[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) { @@ -67,18 +83,18 @@ + (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, ALAssetRepresentation *representation = [asset defaultRepresentation]; ALAssetOrientation orientation = [representation orientation]; UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0f orientation:(UIImageOrientation)orientation]; - callback(nil, image); + RCTDispatchCallbackOnMainQueue(callback, nil, image); } }); } else { NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag]; - NSError *error = errorWithMessage(errorText); - callback(error, nil); + NSError *error = RCTErrorWithMessage(errorText); + RCTDispatchCallbackOnMainQueue(callback, error, nil); } } failureBlock:^(NSError *loadError) { NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError]; - NSError *error = errorWithMessage(errorText); - callback(error, nil); + NSError *error = RCTErrorWithMessage(errorText); + RCTDispatchCallbackOnMainQueue(callback, error, nil); }]; } else if ([imageTag hasPrefix:@"ph://"]) { // Using PhotoKit for iOS 8+ @@ -89,19 +105,19 @@ + (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, 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 = errorWithMessage(errorText); - callback(error, nil); + NSError *error = RCTErrorWithMessage(errorText); + RCTDispatchCallbackOnMainQueue(callback, error, nil); return; } PHAsset *asset = [results firstObject]; [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:nil resultHandler:^(UIImage *result, NSDictionary *info) { if (result) { - callback(nil, result); + RCTDispatchCallbackOnMainQueue(callback, nil, result); } else { NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID]; - NSError *error = errorWithMessage(errorText); - callback(error, nil); + NSError *error = RCTErrorWithMessage(errorText); + RCTDispatchCallbackOnMainQueue(callback, error, nil); return; } }]; @@ -109,20 +125,34 @@ + (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, NSURL *url = [NSURL URLWithString:imageTag]; if (!url) { NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag]; - callback(errorWithMessage(errorMessage), nil); + RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil); return; } [[RCTImageDownloader sharedInstance] downloadDataForURL:url block:^(NSData *data, NSError *error) { if (error) { - callback(error, nil); + RCTDispatchCallbackOnMainQueue(callback, error, nil); } else { - callback(nil, [UIImage imageWithData:data]); + RCTDispatchCallbackOnMainQueue(callback, nil, [UIImage imageWithData:data]); } }]; + } else if ([[imageTag lowercaseString] hasSuffix:@".gif"]) { + id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]); + if (image) { + RCTDispatchCallbackOnMainQueue(callback, nil, image); + } else { + NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag]; + NSError *error = RCTErrorWithMessage(errorMessage); + RCTDispatchCallbackOnMainQueue(callback, error, nil); + } } else { - NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag]; - NSError *error = errorWithMessage(errorMessage); - callback(error, nil); + UIImage *image = [RCTConvert UIImage:imageTag]; + if (image) { + RCTDispatchCallbackOnMainQueue(callback, nil, image); + } else { + NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag]; + NSError *error = RCTErrorWithMessage(errorMessage); + RCTDispatchCallbackOnMainQueue(callback, error, nil); + } } } diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m index 87a50d8fe8512e..ce6aab187aedb2 100644 --- a/Libraries/Image/RCTStaticImageManager.m +++ b/Libraries/Image/RCTStaticImageManager.m @@ -33,9 +33,11 @@ - (UIView *)view if ([[[json description] pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) { [view.layer addAnimation:RCTGIFImageWithFileURL([RCTConvert NSURL:json]) forKey:@"contents"]; } else { + [view.layer removeAnimationForKey:@"contents"]; view.image = [RCTConvert UIImage:json]; } } else { + [view.layer removeAnimationForKey:@"contents"]; view.image = defaultView.image; } } @@ -52,13 +54,19 @@ - (UIView *)view RCT_CUSTOM_VIEW_PROPERTY(imageTag, NSString, RCTStaticImage) { if (json) { - [RCTImageLoader loadImageWithTag:[RCTConvert NSString:json] callback:^(NSError *error, UIImage *image) { + [RCTImageLoader loadImageWithTag:[RCTConvert NSString:json] callback:^(NSError *error, id image) { if (error) { RCTLogWarn(@"%@", error.localizedDescription); } - view.image = image; + if ([image isKindOfClass:[CAAnimation class]]) { + [view.layer addAnimation:image forKey:@"contents"]; + } else { + [view.layer removeAnimationForKey:@"contents"]; + view.image = image; + } }]; } else { + [view.layer removeAnimationForKey:@"contents"]; view.image = defaultView.image; } } diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js index c5fc3bbe1ea3dc..866cf036880a19 100644 --- a/Libraries/Image/__tests__/resolveAssetSource-test.js +++ b/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -12,9 +12,10 @@ jest .dontMock('AssetRegistry') .dontMock('../resolveAssetSource'); -var resolveAssetSource; -var SourceCode; var AssetRegistry; +var Platform; +var SourceCode; +var resolveAssetSource; function expectResolvesAsset(input, expectedSource) { var assetId = AssetRegistry.registerAsset(input); @@ -24,8 +25,9 @@ function expectResolvesAsset(input, expectedSource) { describe('resolveAssetSource', () => { beforeEach(() => { jest.resetModuleRegistry(); - SourceCode = require('NativeModules').SourceCode; AssetRegistry = require('AssetRegistry'); + Platform = require('Platform'); + SourceCode = require('NativeModules').SourceCode; resolveAssetSource = require('../resolveAssetSource'); }); @@ -59,7 +61,7 @@ describe('resolveAssetSource', () => { expect(resolveAssetSource('nonsense')).toBe(null); }); - describe('bundle was loaded from network', () => { + describe('bundle was loaded from network (DEV)', () => { beforeEach(() => { SourceCode.scriptURL = 'http://10.0.0.1:8081/main.bundle'; }); @@ -104,9 +106,21 @@ describe('resolveAssetSource', () => { }); - describe('bundle was loaded from file', () => { + describe('bundle was loaded from file (PROD) on iOS', () => { + var originalDevMode; + var originalPlatform; + beforeEach(() => { SourceCode.scriptURL = 'file:///Path/To/Simulator/main.bundle'; + originalDevMode = __DEV__; + originalPlatform = Platform.OS; + __DEV__ = false; + Platform.OS = 'ios'; + }); + + afterEach(() => { + __DEV__ = originalDevMode; + Platform.OS = originalPlatform; }); it('uses pre-packed image', () => { @@ -129,6 +143,43 @@ describe('resolveAssetSource', () => { }); }); + describe('bundle was loaded from file (PROD) on Android', () => { + var originalDevMode; + var originalPlatform; + + beforeEach(() => { + SourceCode.scriptURL = 'file:///Path/To/Simulator/main.bundle'; + originalDevMode = __DEV__; + originalPlatform = Platform.OS; + __DEV__ = false; + Platform.OS = 'android'; + }); + + afterEach(() => { + __DEV__ = originalDevMode; + Platform.OS = originalPlatform; + }); + + it('uses pre-packed image', () => { + expectResolvesAsset({ + __packager_asset: true, + fileSystemLocation: '/root/app/module/a', + httpServerLocation: '/assets/AwesomeModule/Subdir', + width: 100, + height: 200, + scales: [1], + hash: '5b6f00f', + name: '!@Logo#1_€', // Invalid chars shouldn't get passed to native + type: 'png', + }, { + isStatic: true, + width: 100, + height: 200, + uri: 'assets_awesomemodule_subdir_logo1_', + }); + }); + }); + }); describe('resolveAssetSource.pickScale', () => { diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js index 29d59a9a5dfc5b..26592195d0a4a8 100644 --- a/Libraries/Image/resolveAssetSource.js +++ b/Libraries/Image/resolveAssetSource.js @@ -7,22 +7,31 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule resolveAssetSource + * + * Resolves an asset into a `source` for `Image`. */ 'use strict'; var AssetRegistry = require('AssetRegistry'); var PixelRatio = require('PixelRatio'); +var Platform = require('Platform'); var SourceCode = require('NativeModules').SourceCode; var _serverURL; -function getServerURL() { +function getDevServerURL() { + if (!__DEV__) { + // In prod we want assets to be loaded from the archive + return null; + } if (_serverURL === undefined) { var scriptURL = SourceCode.scriptURL; var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//); if (match) { + // Bundle was loaded from network _serverURL = match[0]; } else { + // Bundle was loaded from file _serverURL = null; } } @@ -30,6 +39,55 @@ function getServerURL() { return _serverURL; } +/** + * Returns the path at which the asset can be found in the archive + */ +function getPathInArchive(asset) { + if (Platform.OS === 'android') { + var assetDir = getBasePath(asset); + // E.g. 'assets_awesomemodule_icon' + // The Android resource system picks the correct scale. + return (assetDir + '/' + asset.name) + .toLowerCase() + .replace(/\//g, '_') // Encode folder structure in file name + .replace(/([^a-z0-9_])/g, ''); // Remove illegal chars + } else { + // E.g. 'assets/AwesomeModule/icon@2x.png' + return getScaledAssetPath(asset); + } +} + +/** + * Returns an absolute URL which can be used to fetch the asset + * from the devserver + */ +function getPathOnDevserver(devServerUrl, asset) { + return devServerUrl + getScaledAssetPath(asset) + '?hash=' + asset.hash; +} + +/** + * Returns a path like 'assets/AwesomeModule' + */ +function getBasePath(asset) { + // TODO(frantic): currently httpServerLocation is used both as + // path in http URL and path within IPA. Should we have zipArchiveLocation? + var path = asset.httpServerLocation; + if (path[0] === '/') { + path = path.substr(1); + } + return path; +} + +/** + * Returns a path like 'assets/AwesomeModule/icon@2x.png' + */ +function getScaledAssetPath(asset) { + var scale = pickScale(asset.scales, PixelRatio.get()); + var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x'; + var assetDir = getBasePath(asset); + return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type; +} + function pickScale(scales, deviceScale) { // Packager guarantees that `scales` array is sorted for (var i = 0; i < scales.length; i++) { @@ -58,31 +116,19 @@ function resolveAssetSource(source) { } function assetToImageSource(asset) { - // TODO(frantic): currently httpServerLocation is used both as - // path in http URL and path within IPA. Should we have zipArchiveLocation? - var path = asset.httpServerLocation; - if (path[0] === '/') { - path = path.substr(1); - } - - var scale = pickScale(asset.scales, PixelRatio.get()); - var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x'; - - var fileName = asset.name + scaleSuffix + '.' + asset.type; - var serverURL = getServerURL(); - if (serverURL) { + var devServerURL = getDevServerURL(); + if (devServerURL) { return { width: asset.width, height: asset.height, - uri: serverURL + path + '/' + fileName + - '?hash=' + asset.hash, + uri: getPathOnDevserver(devServerURL, asset), isStatic: false, }; } else { return { width: asset.width, height: asset.height, - uri: path + '/' + fileName, + uri: getPathInArchive(asset), isStatic: true, }; } diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js index 2677a0029c5dfe..91afe200816f18 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js +++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js @@ -25,12 +25,23 @@ type Exception = { message: string; } -function reportException(e: Exception, stack?: any) { +function reportException(e: Exception, isFatal: bool, stack?: any) { if (RCTExceptionsManager) { if (!stack) { stack = parseErrorStack(e); } - RCTExceptionsManager.reportUnhandledException(e.message, stack); + if (!RCTExceptionsManager.reportFatalException || + !RCTExceptionsManager.reportSoftException) { + // Backwards compatibility - no differentiation + // TODO(#7049989): deprecate reportUnhandledException on Android + RCTExceptionsManager.reportUnhandledException(e.message, stack); + } else { + if (isFatal) { + RCTExceptionsManager.reportFatalException(e.message, stack); + } else { + RCTExceptionsManager.reportSoftException(e.message, stack); + } + } if (__DEV__) { (sourceMapPromise = sourceMapPromise || loadSourceMap()) .then(map => { @@ -44,7 +55,7 @@ function reportException(e: Exception, stack?: any) { } } -function handleException(e: Exception) { +function handleException(e: Exception, isFatal: boolean) { var stack = parseErrorStack(e); var msg = 'Error: ' + e.message + @@ -57,7 +68,7 @@ function handleException(e: Exception) { } else { console.error(msg); } - reportException(e, stack); + reportException(e, isFatal, stack); } /** @@ -78,7 +89,7 @@ function installConsoleErrorReporter() { var str = Array.prototype.map.call(arguments, stringifySafe).join(', '); var error: any = new Error('console.error: ' + str); error.framesToPop = 1; - reportException(error); + reportException(error, /* isFatal */ false); }; if (console.reportErrorsAsExceptions === undefined) { console.reportErrorsAsExceptions = true; // Individual apps can disable this diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 7bddf87b69c786..217fd93e71c952 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -66,9 +66,9 @@ function setupDocumentShim() { GLOBAL.MutationObserver = undefined; } -function handleErrorWithRedBox(e) { +function handleErrorWithRedBox(e, isFatal) { try { - require('ExceptionsManager').handleException(e); + require('ExceptionsManager').handleException(e, isFatal); } catch(ee) { console.log('Failed to print error: ', ee.message); } diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js index 189b507c80a544..b2c3c3927717a5 100644 --- a/Libraries/Picker/PickerIOS.ios.js +++ b/Libraries/Picker/PickerIOS.ios.js @@ -15,13 +15,13 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var React = require('React'); var ReactChildren = require('ReactChildren'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var RCTPickerIOSConsts = require('NativeModules').UIManager.RCTPicker.Constants; var StyleSheet = require('StyleSheet'); var View = require('View'); -var createReactIOSNativeComponentClass = - require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = + require('createReactNativeComponentClass'); var merge = require('merge'); var PICKER = 'picker'; @@ -112,12 +112,12 @@ var styles = StyleSheet.create({ }, }); -var rkPickerIOSAttributes = merge(ReactIOSViewAttributes.UIView, { +var rkPickerIOSAttributes = merge(ReactNativeViewAttributes.UIView, { items: true, selectedIndex: true, }); -var RCTPickerIOS = createReactIOSNativeComponentClass({ +var RCTPickerIOS = createReactNativeComponentClass({ validAttributes: rkPickerIOSAttributes, uiViewClassName: 'RCTPicker', }); diff --git a/Libraries/RKBackendNode/queryLayoutByID.js b/Libraries/RKBackendNode/queryLayoutByID.js index dcb74e47440a4d..d9b1ed8cceab96 100644 --- a/Libraries/RKBackendNode/queryLayoutByID.js +++ b/Libraries/RKBackendNode/queryLayoutByID.js @@ -11,7 +11,7 @@ */ 'use strict'; -var ReactIOSTagHandles = require('ReactIOSTagHandles'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); var RCTUIManager = require('NativeModules').UIManager; type OnSuccessCallback = ( @@ -52,7 +52,7 @@ var queryLayoutByID = function( ): void { // Native bridge doesn't *yet* surface errors. RCTUIManager.measure( - ReactIOSTagHandles.rootNodeIDToTag[rootNodeID], + ReactNativeTagHandles.rootNodeIDToTag[rootNodeID], onSuccess ); }; diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index 9d413e5c7ca7a1..1bbf30b06498c9 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -16,6 +16,7 @@ var RCTPOPAnimationManager = NativeModules.POPAnimationManager; var RCTUIManager = NativeModules.UIManager; var TextInputState = require('TextInputState'); +var findNodeHandle = require('findNodeHandle'); var flattenStyle = require('flattenStyle'); var invariant = require('invariant'); var mergeFast = require('mergeFast'); @@ -51,16 +52,16 @@ var animationIDInvariant = function( var NativeMethodsMixin = { addAnimation: function(anim: number, callback?: (finished: bool) => void) { animationIDInvariant('addAnimation', anim); - RCTPOPAnimationManager.addAnimation(this.getNodeHandle(), anim, callback); + RCTPOPAnimationManager.addAnimation(findNodeHandle(this), anim, callback); }, removeAnimation: function(anim: number) { animationIDInvariant('removeAnimation', anim); - RCTPOPAnimationManager.removeAnimation(this.getNodeHandle(), anim); + RCTPOPAnimationManager.removeAnimation(findNodeHandle(this), anim); }, measure: function(callback: MeasureOnSuccessCallback) { - RCTUIManager.measure(this.getNodeHandle(), callback); + RCTUIManager.measure(findNodeHandle(this), callback); }, measureLayout: function( @@ -69,7 +70,7 @@ var NativeMethodsMixin = { onFail: () => void /* currently unused */ ) { RCTUIManager.measureLayout( - this.getNodeHandle(), + findNodeHandle(this), relativeToNativeNode, onFail, onSuccess @@ -106,18 +107,18 @@ var NativeMethodsMixin = { } RCTUIManager.updateView( - this.getNodeHandle(), + findNodeHandle(this), this.viewConfig.uiViewClassName, props ); }, focus: function() { - TextInputState.focusTextInput(this.getNodeHandle()); + TextInputState.focusTextInput(findNodeHandle(this)); }, blur: function() { - TextInputState.blurTextInput(this.getNodeHandle()); + TextInputState.blurTextInput(findNodeHandle(this)); } }; diff --git a/Libraries/ReactIOS/ReactIOSComponentMixin.js b/Libraries/ReactIOS/ReactIOSComponentMixin.js deleted file mode 100644 index 03d7f5ad04fc3b..00000000000000 --- a/Libraries/ReactIOS/ReactIOSComponentMixin.js +++ /dev/null @@ -1,79 +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 ReactIOSComponentMixin - * @flow - */ -'use strict'; - -var ReactIOSTagHandles = require('ReactIOSTagHandles'); -var ReactInstanceMap = require('ReactInstanceMap'); - -/** - * ReactNative vs ReactWeb - * ----------------------- - * React treats some pieces of data opaquely. This means that the information - * is first class (it can be passed around), but cannot be inspected. This - * allows us to build infrastructure that reasons about resources, without - * making assumptions about the nature of those resources, and this allows that - * infra to be shared across multiple platforms, where the resources are very - * different. General infra (such as `ReactMultiChild`) reasons opaquely about - * the data, but platform specific code (such as `ReactIOSNativeComponent`) can - * make assumptions about the data. - * - * - * `rootNodeID`, uniquely identifies a position in the generated native view - * tree. Many layers of composite components (created with `React.createClass`) - * can all share the same `rootNodeID`. - * - * `nodeHandle`: A sufficiently unambiguous way to refer to a lower level - * resource (dom node, native view etc). The `rootNodeID` is sufficient for web - * `nodeHandle`s, because the position in a tree is always enough to uniquely - * identify a DOM node (we never have nodes in some bank outside of the - * document). The same would be true for `ReactNative`, but we must maintain a - * mapping that we can send efficiently serializable - * strings across native boundaries. - * - * Opaque name TodaysWebReact FutureWebWorkerReact ReactNative - * ---------------------------------------------------------------------------- - * nodeHandle N/A rootNodeID tag - * - * - * `mountImage`: A way to represent the potential to create lower level - * resources whos `nodeHandle` can be discovered immediately by knowing the - * `rootNodeID`. Today's web React represents this with `innerHTML` annotated - * with DOM ids that match the `rootNodeID`. - * - * Opaque name TodaysWebReact FutureWebWorkerReact ReactNative - * ---------------------------------------------------------------------------- - * mountImage innerHTML innerHTML {rootNodeID, tag} - * - */ -var ReactIOSComponentMixin = { - /** - * This has no particular meaning in ReactIOS. If this were in the DOM, this - * would return the DOM node. There should be nothing that invokes this - * method. Any current callers of this are mistaken - they should be invoking - * `getNodeHandle`. - */ - getNativeNode: function() { - // TODO (balpert): Wrap iOS native components in a composite wrapper, then - // ReactInstanceMap.get here will always succeed - return ReactIOSTagHandles.rootNodeIDToTag[ - (ReactInstanceMap.get(this) || this)._rootNodeID - ]; - }, - - getNodeHandle: function() { - return ReactIOSTagHandles.rootNodeIDToTag[ - (ReactInstanceMap.get(this) || this)._rootNodeID - ]; - } -}; - -module.exports = ReactIOSComponentMixin; diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js index 4d9241853626a4..c1c3b137662698 100644 --- a/Libraries/ReactIOS/requireNativeComponent.js +++ b/Libraries/ReactIOS/requireNativeComponent.js @@ -14,7 +14,7 @@ var RCTUIManager = require('NativeModules').UIManager; var UnimplementedView = require('UnimplementedView'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = require('createReactNativeComponentClass'); var deepDiffer = require('deepDiffer'); var insetsDiffer = require('insetsDiffer'); var pointsDiffer = require('pointsDiffer'); @@ -59,7 +59,7 @@ function requireNativeComponent( if (__DEV__) { wrapperComponent && verifyPropTypes(wrapperComponent, viewConfig); } - return createReactIOSNativeComponentClass(viewConfig); + return createReactNativeComponentClass(viewConfig); } var TypeToDifferMap = { diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js index ab1d61728895fd..6ee23cda8ad25e 100644 --- a/Libraries/ReactIOS/verifyPropTypes.js +++ b/Libraries/ReactIOS/verifyPropTypes.js @@ -11,7 +11,7 @@ */ 'use strict'; -var ReactIOSStyleAttributes = require('ReactIOSStyleAttributes'); +var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var View = require('View'); function verifyPropTypes( @@ -26,7 +26,7 @@ function verifyPropTypes( for (var prop in nativeProps) { if (!component.propTypes[prop] && !View.propTypes[prop] && - !ReactIOSStyleAttributes[prop] && + !ReactNativeStyleAttributes[prop] && (!nativePropsToIgnore || !nativePropsToIgnore[prop])) { throw new Error( '`' + component.displayName + '` has no propType for native prop `' + diff --git a/Libraries/ReactIOS/React.js b/Libraries/ReactNative/React.js similarity index 89% rename from Libraries/ReactIOS/React.js rename to Libraries/ReactNative/React.js index ce9de085221fdd..1eeeb32c37b659 100644 --- a/Libraries/ReactIOS/React.js +++ b/Libraries/ReactNative/React.js @@ -12,4 +12,4 @@ "use strict"; -module.exports = require('ReactIOS'); +module.exports = require('ReactNative'); diff --git a/Libraries/ReactIOS/ReactIOS.js b/Libraries/ReactNative/ReactNative.js similarity index 83% rename from Libraries/ReactIOS/ReactIOS.js rename to Libraries/ReactNative/ReactNative.js index 56faaa30b6adc3..abe31a3613f49d 100644 --- a/Libraries/ReactIOS/ReactIOS.js +++ b/Libraries/ReactNative/ReactNative.js @@ -6,7 +6,7 @@ * 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 ReactIOS + * @providesModule ReactNative * @flow */ "use strict"; @@ -19,15 +19,16 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactElement = require('ReactElement'); var ReactElementValidator = require('ReactElementValidator'); var ReactInstanceHandles = require('ReactInstanceHandles'); -var ReactIOSDefaultInjection = require('ReactIOSDefaultInjection'); -var ReactIOSMount = require('ReactIOSMount'); +var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection'); +var ReactNativeMount = require('ReactNativeMount'); var ReactPropTypes = require('ReactPropTypes'); var deprecated = require('deprecated'); +var findNodeHandle = require('findNodeHandle'); var invariant = require('invariant'); var onlyChild = require('onlyChild'); -ReactIOSDefaultInjection.inject(); +ReactNativeDefaultInjection.inject(); var createElement = ReactElement.createElement; var createFactory = ReactElement.createFactory; @@ -72,11 +73,11 @@ var render = function( mountInto: number, callback?: ?(() => void) ): ?ReactComponent { - return ReactIOSMount.renderComponent(element, mountInto, callback); + return ReactNativeMount.renderComponent(element, mountInto, callback); }; -var ReactIOS = { - hasReactIOSInitialized: false, +var ReactNative = { + hasReactNativeInitialized: false, Children: { map: ReactChildren.map, forEach: ReactChildren.forEach, @@ -90,13 +91,14 @@ var ReactIOS = { createFactory: createFactory, cloneElement: cloneElement, _augmentElement: augmentElement, + findNodeHandle: findNodeHandle, render: render, - unmountComponentAtNode: ReactIOSMount.unmountComponentAtNode, + unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode, // Hook for JSX spread, don't use this for anything else. __spread: Object.assign, - unmountComponentAtNodeAndRemoveContainer: ReactIOSMount.unmountComponentAtNodeAndRemoveContainer, + unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer, isValidClass: ReactElement.isValidFactory, isValidElement: ReactElement.isValidElement, @@ -126,10 +128,10 @@ if ( __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ CurrentOwner: ReactCurrentOwner, InstanceHandles: ReactInstanceHandles, - Mount: ReactIOSMount, + Mount: ReactNativeMount, Reconciler: require('ReactReconciler'), - TextComponent: require('ReactIOSTextComponent'), + TextComponent: require('ReactNativeTextComponent'), }); } -module.exports = ReactIOS; +module.exports = ReactNative; diff --git a/Libraries/ReactIOS/ReactIOSNativeComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js similarity index 86% rename from Libraries/ReactIOS/ReactIOSNativeComponent.js rename to Libraries/ReactNative/ReactNativeBaseComponent.js index 7f27ae0ea7dae3..1db02652eab5cb 100644 --- a/Libraries/ReactIOS/ReactIOSNativeComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -6,16 +6,16 @@ * 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 ReactIOSNativeComponent + * @providesModule ReactNativeBaseComponent * @flow */ 'use strict'; var NativeMethodsMixin = require('NativeMethodsMixin'); -var ReactIOSComponentMixin = require('ReactIOSComponentMixin'); -var ReactIOSEventEmitter = require('ReactIOSEventEmitter'); -var ReactIOSStyleAttributes = require('ReactIOSStyleAttributes'); -var ReactIOSTagHandles = require('ReactIOSTagHandles'); +var ReactNativeComponentMixin = require('ReactNativeComponentMixin'); +var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); +var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChild = require('ReactMultiChild'); var RCTUIManager = require('NativeModules').UIManager; @@ -26,23 +26,23 @@ var flattenStyle = require('flattenStyle'); var precomputeStyle = require('precomputeStyle'); var warning = require('warning'); -var registrationNames = ReactIOSEventEmitter.registrationNames; -var putListener = ReactIOSEventEmitter.putListener; -var deleteAllListeners = ReactIOSEventEmitter.deleteAllListeners; +var registrationNames = ReactNativeEventEmitter.registrationNames; +var putListener = ReactNativeEventEmitter.putListener; +var deleteAllListeners = ReactNativeEventEmitter.deleteAllListeners; -type ReactIOSNativeComponentViewConfig = { +type ReactNativeBaseComponentViewConfig = { validAttributes: Object; uiViewClassName: string; } /** - * @constructor ReactIOSNativeComponent + * @constructor ReactNativeBaseComponent * @extends ReactComponent * @extends ReactMultiChild * @param {!object} UIKit View Configuration. */ -var ReactIOSNativeComponent = function( - viewConfig: ReactIOSNativeComponentViewConfig +var ReactNativeBaseComponent = function( + viewConfig: ReactNativeBaseComponentViewConfig ) { this.viewConfig = viewConfig; }; @@ -75,7 +75,7 @@ cachedIndexArray._cache = {}; * Mixin for containers that contain UIViews. NOTE: markup is rendered markup * which is a `viewID` ... see the return value for `mountComponent` ! */ -ReactIOSNativeComponent.Mixin = { +ReactNativeBaseComponent.Mixin = { getPublicInstance: function() { // TODO: This should probably use a composite wrapper return this; @@ -97,7 +97,7 @@ ReactIOSNativeComponent.Mixin = { * recording the fact that its own `rootNodeID` is associated with a * `nodeHandle`. Only the code that actually adds its `nodeHandle` (`tag`) as * a child of a container can confidently record that in - * `ReactIOSTagHandles`. + * `ReactNativeTagHandles`. */ initializeChildren: function(children, containerTag, transaction, context) { var mountImages = this.mountChildren(children, transaction, context); @@ -117,7 +117,7 @@ ReactIOSNativeComponent.Mixin = { mountImage && mountImage.rootNodeID && mountImage.tag, 'Mount image returned does not have required data' ); - ReactIOSTagHandles.associateRootNodeIDWithMountedNodeHandle( + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle( childID, childTag ); @@ -166,7 +166,7 @@ ReactIOSNativeComponent.Mixin = { updatePayload, this.previousFlattenedStyle, nextFlattenedStyle, - ReactIOSStyleAttributes + ReactNativeStyleAttributes ); this.previousFlattenedStyle = nextFlattenedStyle; } @@ -195,7 +195,7 @@ ReactIOSNativeComponent.Mixin = { if (updatePayload) { RCTUIManager.updateView( - ReactIOSTagHandles.mostRecentMountedNodeHandleForRootNodeID(this._rootNodeID), + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(this._rootNodeID), this.viewConfig.uiViewClassName, updatePayload ); @@ -243,7 +243,7 @@ ReactIOSNativeComponent.Mixin = { mountComponent: function(rootID, transaction, context) { this._rootNodeID = rootID; - var tag = ReactIOSTagHandles.allocateTag(); + var tag = ReactNativeTagHandles.allocateTag(); this.previousFlattenedStyle = {}; var updatePayload = this.computeUpdatedProperties( @@ -268,15 +268,15 @@ ReactIOSNativeComponent.Mixin = { }; /** - * Order of mixins is important. ReactIOSNativeComponent overrides methods in + * Order of mixins is important. ReactNativeBaseComponent overrides methods in * ReactMultiChild. */ Object.assign( - ReactIOSNativeComponent.prototype, + ReactNativeBaseComponent.prototype, ReactMultiChild.Mixin, - ReactIOSNativeComponent.Mixin, + ReactNativeBaseComponent.Mixin, NativeMethodsMixin, - ReactIOSComponentMixin + ReactNativeComponentMixin ); -module.exports = ReactIOSNativeComponent; +module.exports = ReactNativeBaseComponent; diff --git a/Libraries/ReactIOS/ReactIOSComponentEnvironment.js b/Libraries/ReactNative/ReactNativeBaseComponentEnvironment.js similarity index 51% rename from Libraries/ReactIOS/ReactIOSComponentEnvironment.js rename to Libraries/ReactNative/ReactNativeBaseComponentEnvironment.js index 0dc0b1d1c929e6..6e038620d66b8a 100644 --- a/Libraries/ReactIOS/ReactIOSComponentEnvironment.js +++ b/Libraries/ReactNative/ReactNativeBaseComponentEnvironment.js @@ -6,19 +6,19 @@ * 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 ReactIOSComponentEnvironment + * @providesModule ReactNativeComponentEnvironment * @flow */ 'use strict'; -var ReactIOSDOMIDOperations = require('ReactIOSDOMIDOperations'); -var ReactIOSReconcileTransaction = require('ReactIOSReconcileTransaction'); +var ReactNativeDOMIDOperations = require('ReactNativeDOMIDOperations'); +var ReactNativeReconcileTransaction = require('ReactNativeReconcileTransaction'); -var ReactIOSComponentEnvironment = { +var ReactNativeComponentEnvironment = { - processChildrenUpdates: ReactIOSDOMIDOperations.dangerouslyProcessChildrenUpdates, + processChildrenUpdates: ReactNativeDOMIDOperations.dangerouslyProcessChildrenUpdates, - replaceNodeWithMarkupByID: ReactIOSDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, + replaceNodeWithMarkupByID: ReactNativeDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, /** * Nothing to do for UIKit bridge. @@ -36,7 +36,7 @@ var ReactIOSComponentEnvironment = { }, - ReactReconcileTransaction: ReactIOSReconcileTransaction, + ReactReconcileTransaction: ReactNativeReconcileTransaction, }; -module.exports = ReactIOSComponentEnvironment; +module.exports = ReactNativeComponentEnvironment; diff --git a/Libraries/ReactNative/ReactNativeBaseComponentMixin.js b/Libraries/ReactNative/ReactNativeBaseComponentMixin.js new file mode 100644 index 00000000000000..7cbf9707730b9d --- /dev/null +++ b/Libraries/ReactNative/ReactNativeBaseComponentMixin.js @@ -0,0 +1,32 @@ +/** + * 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 ReactNativeComponentMixin + * @flow + */ +'use strict'; + +var findNodeHandle = require('findNodeHandle'); + +var ReactNativeComponentMixin = { + /** + * This method is deprecated; use `React.findNodeHandle` instead. + */ + getNativeNode: function() { + return findNodeHandle(this); + }, + + /** + * This method is deprecated; use `React.findNodeHandle` instead. + */ + getNodeHandle: function() { + return findNodeHandle(this); + } +}; + +module.exports = ReactNativeComponentMixin; diff --git a/Libraries/ReactIOS/ReactIOSDOMIDOperations.js b/Libraries/ReactNative/ReactNativeDOMIDOperations.js similarity index 87% rename from Libraries/ReactIOS/ReactIOSDOMIDOperations.js rename to Libraries/ReactNative/ReactNativeDOMIDOperations.js index 7d421442f5325b..3b47d4d8f8ca3b 100644 --- a/Libraries/ReactIOS/ReactIOSDOMIDOperations.js +++ b/Libraries/ReactNative/ReactNativeDOMIDOperations.js @@ -6,13 +6,13 @@ * 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 ReactIOSDOMIDOperations + * @providesModule ReactNativeDOMIDOperations * @flow */ "use strict"; -var ReactIOSTagHandles = require('ReactIOSTagHandles'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var RCTUIManager = require('NativeModules').UIManager; var ReactPerf = require('ReactPerf'); @@ -38,7 +38,7 @@ var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { // containerID. for (var i = 0; i < childrenUpdates.length; i++) { var update = childrenUpdates[i]; - var containerTag = ReactIOSTagHandles.mostRecentMountedNodeHandleForRootNodeID(update.parentID); + var containerTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(update.parentID); var updates = byContainerTag[containerTag] || (byContainerTag[containerTag] = {}); if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING) { (updates.moveFromIndices || (updates.moveFromIndices = [])).push(update.fromIndex); @@ -49,7 +49,7 @@ var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { var mountImage = markupList[update.markupIndex]; var tag = mountImage.tag; var rootNodeID = mountImage.rootNodeID; - ReactIOSTagHandles.associateRootNodeIDWithMountedNodeHandle(rootNodeID, tag); + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(rootNodeID, tag); (updates.addAtIndices || (updates.addAtIndices = [])).push(update.toIndex); (updates.addChildTags || (updates.addChildTags = [])).push(tag); } @@ -75,7 +75,7 @@ var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { * Operations used to process updates to DOM nodes. This is made injectable via * `ReactComponent.DOMIDOperations`. */ -var ReactIOSDOMIDOperations = { +var ReactNativeDOMIDOperations = { dangerouslyProcessChildrenUpdates: ReactPerf.measure( // FIXME(frantic): #4441289 Hack to avoid modifying react-tools 'ReactDOMIDOperations', @@ -93,11 +93,11 @@ var ReactIOSDOMIDOperations = { 'ReactDOMIDOperations', 'dangerouslyReplaceNodeWithMarkupByID', function(id, mountImage) { - var oldTag = ReactIOSTagHandles.mostRecentMountedNodeHandleForRootNodeID(id); + var oldTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(id); RCTUIManager.replaceExistingNonRootView(oldTag, mountImage.tag); - ReactIOSTagHandles.associateRootNodeIDWithMountedNodeHandle(id, mountImage.tag); + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(id, mountImage.tag); } ), }; -module.exports = ReactIOSDOMIDOperations; +module.exports = ReactNativeDOMIDOperations; diff --git a/Libraries/ReactIOS/ReactIOSDefaultInjection.js b/Libraries/ReactNative/ReactNativeDefaultInjection.js similarity index 76% rename from Libraries/ReactIOS/ReactIOSDefaultInjection.js rename to Libraries/ReactNative/ReactNativeDefaultInjection.js index 7b3df0d8515a80..93c2612805cf15 100644 --- a/Libraries/ReactIOS/ReactIOSDefaultInjection.js +++ b/Libraries/ReactNative/ReactNativeDefaultInjection.js @@ -6,7 +6,7 @@ * 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 ReactIOSDefaultInjection + * @providesModule ReactNativeDefaultInjection * @flow */ @@ -26,18 +26,18 @@ var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); var ReactEmptyComponent = require('ReactEmptyComponent'); var ReactInstanceHandles = require('ReactInstanceHandles'); -var ReactIOSComponentEnvironment = require('ReactIOSComponentEnvironment'); -var ReactIOSComponentMixin = require('ReactIOSComponentMixin'); -var ReactIOSGlobalInteractionHandler = require('ReactIOSGlobalInteractionHandler'); -var ReactIOSGlobalResponderHandler = require('ReactIOSGlobalResponderHandler'); -var ReactIOSMount = require('ReactIOSMount'); -var ReactIOSTextComponent = require('ReactIOSTextComponent'); +var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment'); +var ReactNativeComponentMixin = require('ReactNativeComponentMixin'); +var ReactNativeGlobalInteractionHandler = require('ReactNativeGlobalInteractionHandler'); +var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler'); +var ReactNativeMount = require('ReactNativeMount'); +var ReactNativeTextComponent = require('ReactNativeTextComponent'); var ReactNativeComponent = require('ReactNativeComponent'); var ReactUpdates = require('ReactUpdates'); var ResponderEventPlugin = require('ResponderEventPlugin'); var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = require('createReactNativeComponentClass'); var invariant = require('invariant'); // Just to ensure this gets packaged, since its only caller is from Native. @@ -53,11 +53,11 @@ function inject() { EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles); ResponderEventPlugin.injection.injectGlobalResponderHandler( - ReactIOSGlobalResponderHandler + ReactNativeGlobalResponderHandler ); ResponderEventPlugin.injection.injectGlobalInteractionHandler( - ReactIOSGlobalInteractionHandler + ReactNativeGlobalInteractionHandler ); /** @@ -70,7 +70,7 @@ function inject() { }); ReactUpdates.injection.injectReconcileTransaction( - ReactIOSComponentEnvironment.ReactReconcileTransaction + ReactNativeComponentEnvironment.ReactReconcileTransaction ); ReactUpdates.injection.injectBatchingStrategy( @@ -78,22 +78,22 @@ function inject() { ); ReactComponentEnvironment.injection.injectEnvironment( - ReactIOSComponentEnvironment + ReactNativeComponentEnvironment ); // Can't import View here because it depends on React to make its composite - var RCTView = createReactIOSNativeComponentClass({ + var RCTView = createReactNativeComponentClass({ validAttributes: {}, uiViewClassName: 'RCTView', }); ReactEmptyComponent.injection.injectEmptyComponent(RCTView); - EventPluginUtils.injection.injectMount(ReactIOSMount); + EventPluginUtils.injection.injectMount(ReactNativeMount); - ReactClass.injection.injectMixin(ReactIOSComponentMixin); + ReactClass.injection.injectMixin(ReactNativeComponentMixin); ReactNativeComponent.injection.injectTextComponentClass( - ReactIOSTextComponent + ReactNativeTextComponent ); ReactNativeComponent.injection.injectAutoWrapper(function(tag) { // Show a nicer error message for non-function tags diff --git a/Libraries/ReactIOS/ReactIOSEventEmitter.js b/Libraries/ReactNative/ReactNativeEventEmitter.js similarity index 90% rename from Libraries/ReactIOS/ReactIOSEventEmitter.js rename to Libraries/ReactNative/ReactNativeEventEmitter.js index 017a0fb0a4a5d2..9bd344e792dceb 100644 --- a/Libraries/ReactIOS/ReactIOSEventEmitter.js +++ b/Libraries/ReactNative/ReactNativeEventEmitter.js @@ -6,7 +6,7 @@ * 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 ReactIOSEventEmitter + * @providesModule ReactNativeEventEmitter * @flow */ @@ -14,7 +14,7 @@ var EventPluginHub = require('EventPluginHub'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); -var ReactIOSTagHandles = require('ReactIOSTagHandles'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); var NodeHandle = require('NodeHandle'); var EventConstants = require('EventConstants'); @@ -82,15 +82,15 @@ var removeTouchesAtIndices = function( }; /** - * `ReactIOSEventEmitter` is used to attach top-level event listeners. For example: + * `ReactNativeEventEmitter` is used to attach top-level event listeners. For example: * - * ReactIOSEventEmitter.putListener('myID', 'onClick', myFunction); + * ReactNativeEventEmitter.putListener('myID', 'onClick', myFunction); * * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'. * * @internal */ -var ReactIOSEventEmitter = merge(ReactEventEmitterMixin, { +var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, { registrationNames: EventPluginHub.registrationNameModules, @@ -118,7 +118,7 @@ var ReactIOSEventEmitter = merge(ReactEventEmitterMixin, { nativeEventParam: Object ) { var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT; - ReactIOSEventEmitter.handleTopLevel( + ReactNativeEventEmitter.handleTopLevel( topLevelType, rootNodeID, rootNodeID, @@ -138,8 +138,8 @@ var ReactIOSEventEmitter = merge(ReactEventEmitterMixin, { topLevelType: string, nativeEventParam: Object ) { - var rootNodeID = ReactIOSTagHandles.tagToRootNodeID[tag]; - ReactIOSEventEmitter._receiveRootNodeIDEvent( + var rootNodeID = ReactNativeTagHandles.tagToRootNodeID[tag]; + ReactNativeEventEmitter._receiveRootNodeIDEvent( rootNodeID, topLevelType, nativeEventParam @@ -191,7 +191,7 @@ var ReactIOSEventEmitter = merge(ReactEventEmitterMixin, { var rootNodeID = null; var target = nativeEvent.target; if (target !== null && target !== undefined) { - if (target < ReactIOSTagHandles.tagsStartAt) { + if (target < ReactNativeTagHandles.tagsStartAt) { if (__DEV__) { warning( false, @@ -202,7 +202,7 @@ var ReactIOSEventEmitter = merge(ReactEventEmitterMixin, { rootNodeID = NodeHandle.getRootNodeID(target); } } - ReactIOSEventEmitter._receiveRootNodeIDEvent( + ReactNativeEventEmitter._receiveRootNodeIDEvent( rootNodeID, eventTopLevelType, nativeEvent @@ -211,4 +211,4 @@ var ReactIOSEventEmitter = merge(ReactEventEmitterMixin, { } }); -module.exports = ReactIOSEventEmitter; +module.exports = ReactNativeEventEmitter; diff --git a/Libraries/ReactIOS/ReactIOSGlobalInteractionHandler.js b/Libraries/ReactNative/ReactNativeGlobalInteractionHandler.js similarity index 85% rename from Libraries/ReactIOS/ReactIOSGlobalInteractionHandler.js rename to Libraries/ReactNative/ReactNativeGlobalInteractionHandler.js index 568acab8a13d50..d5c9b954477910 100644 --- a/Libraries/ReactIOS/ReactIOSGlobalInteractionHandler.js +++ b/Libraries/ReactNative/ReactNativeGlobalInteractionHandler.js @@ -6,7 +6,7 @@ * 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 ReactIOSGlobalInteractionHandler + * @providesModule ReactNativeGlobalInteractionHandler * @flow */ 'use strict'; @@ -17,7 +17,7 @@ var InteractionManager = require('InteractionManager'); // released/terminated. var interactionHandle = null; -var ReactIOSGlobalInteractionHandler = { +var ReactNativeGlobalInteractionHandler = { onChange: function(numberActiveTouches: number) { if (numberActiveTouches === 0) { if (interactionHandle) { @@ -30,4 +30,4 @@ var ReactIOSGlobalInteractionHandler = { } }; -module.exports = ReactIOSGlobalInteractionHandler; +module.exports = ReactNativeGlobalInteractionHandler; diff --git a/Libraries/ReactIOS/ReactIOSGlobalResponderHandler.js b/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js similarity index 66% rename from Libraries/ReactIOS/ReactIOSGlobalResponderHandler.js rename to Libraries/ReactNative/ReactNativeGlobalResponderHandler.js index e5c16d05a00d30..3ba933e8ce30f4 100644 --- a/Libraries/ReactIOS/ReactIOSGlobalResponderHandler.js +++ b/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js @@ -6,19 +6,19 @@ * 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 ReactIOSGlobalResponderHandler + * @providesModule ReactNativeGlobalResponderHandler * @flow */ 'use strict'; var RCTUIManager = require('NativeModules').UIManager; -var ReactIOSTagHandles = require('ReactIOSTagHandles'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var ReactIOSGlobalResponderHandler = { +var ReactNativeGlobalResponderHandler = { onChange: function(from: string, to: string) { if (to !== null) { RCTUIManager.setJSResponder( - ReactIOSTagHandles.mostRecentMountedNodeHandleForRootNodeID(to) + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to) ); } else { RCTUIManager.clearJSResponder(); @@ -26,4 +26,4 @@ var ReactIOSGlobalResponderHandler = { } }; -module.exports = ReactIOSGlobalResponderHandler; +module.exports = ReactNativeGlobalResponderHandler; diff --git a/Libraries/ReactIOS/ReactIOSMount.js b/Libraries/ReactNative/ReactNativeMount.js similarity index 79% rename from Libraries/ReactIOS/ReactIOSMount.js rename to Libraries/ReactNative/ReactNativeMount.js index 9b1428fdd6d27a..004a3fbda227fc 100644 --- a/Libraries/ReactIOS/ReactIOSMount.js +++ b/Libraries/ReactNative/ReactNativeMount.js @@ -6,14 +6,14 @@ * 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 ReactIOSMount + * @providesModule ReactNativeMount * @flow */ 'use strict'; var RCTUIManager = require('NativeModules').UIManager; -var ReactIOSTagHandles = require('ReactIOSTagHandles'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); var ReactUpdateQueue = require('ReactUpdateQueue'); @@ -45,7 +45,7 @@ function mountComponentIntoNode( componentInstance, rootID, transaction, emptyObject ); componentInstance._isTopLevel = true; - ReactIOSMount._mountImageIntoNode(markup, container); + ReactNativeMount._mountImageIntoNode(markup, container); } /** @@ -75,7 +75,7 @@ function batchedMountComponentIntoNode( * As soon as `ReactMount` is refactored to not rely on the DOM, we can share * code between the two. For now, we'll hard code the ID logic. */ -var ReactIOSMount = { +var ReactNativeMount = { instanceCount: 0, _instancesByContainerID: {}, @@ -89,9 +89,9 @@ var ReactIOSMount = { containerTag: number, callback?: ?(() => void) ): ?ReactComponent { - var topRootNodeID = ReactIOSTagHandles.tagToRootNodeID[containerTag]; + var topRootNodeID = ReactNativeTagHandles.tagToRootNodeID[containerTag]; if (topRootNodeID) { - var prevComponent = ReactIOSMount._instancesByContainerID[topRootNodeID]; + var prevComponent = ReactNativeMount._instancesByContainerID[topRootNodeID]; if (prevComponent) { var prevElement = prevComponent._currentElement; if (shouldUpdateReactComponent(prevElement, nextElement)) { @@ -101,28 +101,28 @@ var ReactIOSMount = { } return prevComponent; } else { - ReactIOSMount.unmountComponentAtNode(containerTag); + ReactNativeMount.unmountComponentAtNode(containerTag); } } } - if (!ReactIOSTagHandles.reactTagIsNativeTopRootID(containerTag)) { + if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) { console.error('You cannot render into anything but a top root'); return; } - var topRootNodeID = ReactIOSTagHandles.allocateRootNodeIDForTag(containerTag); - ReactIOSTagHandles.associateRootNodeIDWithMountedNodeHandle( + var topRootNodeID = ReactNativeTagHandles.allocateRootNodeIDForTag(containerTag); + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle( topRootNodeID, containerTag ); var instance = instantiateReactComponent(nextElement); - ReactIOSMount._instancesByContainerID[topRootNodeID] = instance; + ReactNativeMount._instancesByContainerID[topRootNodeID] = instance; var childRootNodeID = instanceNumberToChildRootID( topRootNodeID, - ReactIOSMount.instanceCount++ + ReactNativeMount.instanceCount++ ); // The initial render is synchronous but any updates that happen during @@ -153,14 +153,14 @@ var ReactIOSMount = { function(mountImage, containerID) { // Since we now know that the `mountImage` has been mounted, we can // mark it as such. - ReactIOSTagHandles.associateRootNodeIDWithMountedNodeHandle( + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle( mountImage.rootNodeID, mountImage.tag ); var addChildTags = [mountImage.tag]; var addAtIndices = [0]; RCTUIManager.manageChildren( - ReactIOSTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID), + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID), null, // moveFromIndices null, // moveToIndices addChildTags, @@ -181,7 +181,7 @@ var ReactIOSMount = { unmountComponentAtNodeAndRemoveContainer: function( containerTag: number ) { - ReactIOSMount.unmountComponentAtNode(containerTag); + ReactNativeMount.unmountComponentAtNode(containerTag); // call back into native to remove all of the subviews from this container RCTUIManager.removeRootView(containerTag); }, @@ -192,18 +192,18 @@ var ReactIOSMount = { * component at this time. */ unmountComponentAtNode: function(containerTag: number): boolean { - if (!ReactIOSTagHandles.reactTagIsNativeTopRootID(containerTag)) { + if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) { console.error('You cannot render into anything but a top root'); return false; } - var containerID = ReactIOSTagHandles.tagToRootNodeID[containerTag]; - var instance = ReactIOSMount._instancesByContainerID[containerID]; + var containerID = ReactNativeTagHandles.tagToRootNodeID[containerTag]; + var instance = ReactNativeMount._instancesByContainerID[containerID]; if (!instance) { return false; } - ReactIOSMount.unmountComponentFromNode(instance, containerID); - delete ReactIOSMount._instancesByContainerID[containerID]; + ReactNativeMount.unmountComponentFromNode(instance, containerID); + delete ReactNativeMount._instancesByContainerID[containerID]; return true; }, @@ -214,7 +214,7 @@ var ReactIOSMount = { * @param {string} containerID ID of container we're removing from. * @final * @internal - * @see {ReactIOSMount.unmountComponentAtNode} + * @see {ReactNativeMount.unmountComponentAtNode} */ unmountComponentFromNode: function( instance: ReactComponent, @@ -223,7 +223,7 @@ var ReactIOSMount = { // Call back into native to remove all of the subviews from this container ReactReconciler.unmountComponent(instance); var containerTag = - ReactIOSTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID); + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID); RCTUIManager.removeSubviewsFromContainerWithID(containerTag); }, @@ -232,10 +232,10 @@ var ReactIOSMount = { } }; -ReactIOSMount.renderComponent = ReactPerf.measure( +ReactNativeMount.renderComponent = ReactPerf.measure( 'ReactMount', '_renderNewRootComponent', - ReactIOSMount.renderComponent + ReactNativeMount.renderComponent ); -module.exports = ReactIOSMount; +module.exports = ReactNativeMount; diff --git a/Libraries/ReactIOS/ReactIOSReconcileTransaction.js b/Libraries/ReactNative/ReactNativeReconcileTransaction.js similarity index 88% rename from Libraries/ReactIOS/ReactIOSReconcileTransaction.js rename to Libraries/ReactNative/ReactNativeReconcileTransaction.js index 229b1ba67bec55..ac9ed657b5069f 100644 --- a/Libraries/ReactIOS/ReactIOSReconcileTransaction.js +++ b/Libraries/ReactNative/ReactNativeReconcileTransaction.js @@ -6,7 +6,7 @@ * 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 ReactIOSReconcileTransaction + * @providesModule ReactNativeReconcileTransaction * @flow */ @@ -55,9 +55,9 @@ var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING]; * - Implement/integrate with customized constraint based layout system and keep * track of which dimensions must be remeasured. * - * @class ReactIOSReconcileTransaction + * @class ReactNativeReconcileTransaction */ -function ReactIOSReconcileTransaction() { +function ReactNativeReconcileTransaction() { this.reinitializeTransaction(); this.reactMountReady = CallbackQueue.getPooled(null); } @@ -93,12 +93,12 @@ var Mixin = { }; Object.assign( - ReactIOSReconcileTransaction.prototype, + ReactNativeReconcileTransaction.prototype, Transaction.Mixin, - ReactIOSReconcileTransaction, + ReactNativeReconcileTransaction, Mixin ); -PooledClass.addPoolingTo(ReactIOSReconcileTransaction); +PooledClass.addPoolingTo(ReactNativeReconcileTransaction); -module.exports = ReactIOSReconcileTransaction; +module.exports = ReactNativeReconcileTransaction; diff --git a/Libraries/ReactIOS/ReactIOSStyleAttributes.js b/Libraries/ReactNative/ReactNativeStyleAttributes.js similarity index 74% rename from Libraries/ReactIOS/ReactIOSStyleAttributes.js rename to Libraries/ReactNative/ReactNativeStyleAttributes.js index 5f83523e281f60..9adaf342adc55a 100644 --- a/Libraries/ReactIOS/ReactIOSStyleAttributes.js +++ b/Libraries/ReactNative/ReactNativeStyleAttributes.js @@ -6,7 +6,7 @@ * 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 ReactIOSStyleAttributes + * @providesModule ReactNativeStyleAttributes * @flow */ @@ -20,13 +20,13 @@ var keyMirror = require('keyMirror'); var matricesDiffer = require('matricesDiffer'); var sizesDiffer = require('sizesDiffer'); -var ReactIOSStyleAttributes = { +var ReactNativeStyleAttributes = { ...keyMirror(ViewStylePropTypes), ...keyMirror(TextStylePropTypes), ...keyMirror(ImageStylePropTypes), }; -ReactIOSStyleAttributes.transformMatrix = { diff: matricesDiffer }; -ReactIOSStyleAttributes.shadowOffset = { diff: sizesDiffer }; +ReactNativeStyleAttributes.transformMatrix = { diff: matricesDiffer }; +ReactNativeStyleAttributes.shadowOffset = { diff: sizesDiffer }; -module.exports = ReactIOSStyleAttributes; +module.exports = ReactNativeStyleAttributes; diff --git a/Libraries/ReactIOS/ReactIOSTagHandles.js b/Libraries/ReactNative/ReactNativeTagHandles.js similarity index 86% rename from Libraries/ReactIOS/ReactIOSTagHandles.js rename to Libraries/ReactNative/ReactNativeTagHandles.js index 7ef7d8329693cc..bf1dc59f25736a 100644 --- a/Libraries/ReactIOS/ReactIOSTagHandles.js +++ b/Libraries/ReactNative/ReactNativeTagHandles.js @@ -6,7 +6,7 @@ * 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 ReactIOSTagHandles + * @providesModule ReactNativeTagHandles * @flow */ 'use strict'; @@ -28,17 +28,17 @@ var warning = require('warning'); * unmount a component with a `rootNodeID`, then mount a new one in its place, */ var INITIAL_TAG_COUNT = 1; -var ReactIOSTagHandles = { +var ReactNativeTagHandles = { tagsStartAt: INITIAL_TAG_COUNT, tagCount: INITIAL_TAG_COUNT, allocateTag: function(): number { // Skip over root IDs as those are reserved for native - while (this.reactTagIsNativeTopRootID(ReactIOSTagHandles.tagCount)) { - ReactIOSTagHandles.tagCount++; + while (this.reactTagIsNativeTopRootID(ReactNativeTagHandles.tagCount)) { + ReactNativeTagHandles.tagCount++; } - var tag = ReactIOSTagHandles.tagCount; - ReactIOSTagHandles.tagCount++; + var tag = ReactNativeTagHandles.tagCount; + ReactNativeTagHandles.tagCount++; return tag; }, @@ -57,8 +57,8 @@ var ReactIOSTagHandles = { ) { warning(rootNodeID && tag, 'Root node or tag is null when associating'); if (rootNodeID && tag) { - ReactIOSTagHandles.tagToRootNodeID[tag] = rootNodeID; - ReactIOSTagHandles.rootNodeIDToTag[rootNodeID] = tag; + ReactNativeTagHandles.tagToRootNodeID[tag] = rootNodeID; + ReactNativeTagHandles.rootNodeIDToTag[rootNodeID] = tag; } }, @@ -90,7 +90,7 @@ var ReactIOSTagHandles = { mostRecentMountedNodeHandleForRootNodeID: function( rootNodeID: string ): number { - return ReactIOSTagHandles.rootNodeIDToTag[rootNodeID]; + return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID]; }, tagToRootNodeID: ([] : Array), @@ -98,4 +98,4 @@ var ReactIOSTagHandles = { rootNodeIDToTag: ({} : {[key: string]: number}) }; -module.exports = ReactIOSTagHandles; +module.exports = ReactNativeTagHandles; diff --git a/Libraries/ReactIOS/ReactIOSTextComponent.js b/Libraries/ReactNative/ReactNativeTextComponent.js similarity index 80% rename from Libraries/ReactIOS/ReactIOSTextComponent.js rename to Libraries/ReactNative/ReactNativeTextComponent.js index 1d246310bbced8..bdca141c06424f 100644 --- a/Libraries/ReactIOS/ReactIOSTextComponent.js +++ b/Libraries/ReactNative/ReactNativeTextComponent.js @@ -6,21 +6,21 @@ * 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 ReactIOSTextComponent + * @providesModule ReactNativeTextComponent */ 'use strict'; -var ReactIOSTagHandles = require('ReactIOSTagHandles'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); var RCTUIManager = require('NativeModules').UIManager; var assign = require('Object.assign'); -var ReactIOSTextComponent = function(props) { +var ReactNativeTextComponent = function(props) { // This constructor and its argument is currently used by mocks. }; -assign(ReactIOSTextComponent.prototype, { +assign(ReactNativeTextComponent.prototype, { construct: function(text) { // This is really a ReactText (ReactNode), not a ReactElement @@ -31,7 +31,7 @@ assign(ReactIOSTextComponent.prototype, { mountComponent: function(rootID, transaction, context) { this._rootNodeID = rootID; - var tag = ReactIOSTagHandles.allocateTag(); + var tag = ReactNativeTagHandles.allocateTag(); RCTUIManager.createView(tag, 'RCTRawText', {text: this._stringText}); return { rootNodeID: rootID, @@ -46,7 +46,7 @@ assign(ReactIOSTextComponent.prototype, { if (nextStringText !== this._stringText) { this._stringText = nextStringText; RCTUIManager.updateView( - ReactIOSTagHandles.mostRecentMountedNodeHandleForRootNodeID( + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID( this._rootNodeID ), 'RCTRawText', @@ -64,4 +64,4 @@ assign(ReactIOSTextComponent.prototype, { }); -module.exports = ReactIOSTextComponent; +module.exports = ReactNativeTextComponent; diff --git a/Libraries/ReactIOS/ReactIOSViewAttributes.js b/Libraries/ReactNative/ReactNativeViewAttributes.js similarity index 79% rename from Libraries/ReactIOS/ReactIOSViewAttributes.js rename to Libraries/ReactNative/ReactNativeViewAttributes.js index 489741b053b5ea..d154d045fad2eb 100644 --- a/Libraries/ReactIOS/ReactIOSViewAttributes.js +++ b/Libraries/ReactNative/ReactNativeViewAttributes.js @@ -6,16 +6,16 @@ * 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 ReactIOSViewAttributes + * @providesModule ReactNativeViewAttributes * @flow */ 'use strict'; var merge = require('merge'); -var ReactIOSViewAttributes = {}; +var ReactNativeViewAttributes = {}; -ReactIOSViewAttributes.UIView = { +ReactNativeViewAttributes.UIView = { pointerEvents: true, accessible: true, accessibilityLabel: true, @@ -23,8 +23,8 @@ ReactIOSViewAttributes.UIView = { onLayout: true, }; -ReactIOSViewAttributes.RCTView = merge( - ReactIOSViewAttributes.UIView, { +ReactNativeViewAttributes.RCTView = merge( + ReactNativeViewAttributes.UIView, { // This is a special performance property exposed by RCTView and useful for // scrolling content when there are many subviews, most of which are offscreen. @@ -34,4 +34,4 @@ ReactIOSViewAttributes.RCTView = merge( removeClippedSubviews: true, }); -module.exports = ReactIOSViewAttributes; +module.exports = ReactNativeViewAttributes; diff --git a/Libraries/ReactIOS/createReactIOSNativeComponentClass.js b/Libraries/ReactNative/createReactNativeComponentClass.js similarity index 66% rename from Libraries/ReactIOS/createReactIOSNativeComponentClass.js rename to Libraries/ReactNative/createReactNativeComponentClass.js index 5a5af04dc815da..3a63089b6628e5 100644 --- a/Libraries/ReactIOS/createReactIOSNativeComponentClass.js +++ b/Libraries/ReactNative/createReactNativeComponentClass.js @@ -6,17 +6,17 @@ * 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 createReactIOSNativeComponentClass + * @providesModule createReactNativeComponentClass * @flow */ "use strict"; var ReactElement = require('ReactElement'); -var ReactIOSNativeComponent = require('ReactIOSNativeComponent'); +var ReactNativeBaseComponent = require('ReactNativeBaseComponent'); -// See also ReactIOSNativeComponent -type ReactIOSNativeComponentViewConfig = { +// See also ReactNativeBaseComponent +type ReactNativeBaseComponentViewConfig = { validAttributes: Object; uiViewClassName: string; } @@ -25,8 +25,8 @@ type ReactIOSNativeComponentViewConfig = { * @param {string} config iOS View configuration. * @private */ -var createReactIOSNativeComponentClass = function( - viewConfig: ReactIOSNativeComponentViewConfig +var createReactNativeComponentClass = function( + viewConfig: ReactNativeBaseComponentViewConfig ): Function { // returning Function is lossy :/ var Constructor = function(element) { this._currentElement = element; @@ -36,9 +36,9 @@ var createReactIOSNativeComponentClass = function( this.previousFlattenedStyle = null; }; Constructor.displayName = viewConfig.uiViewClassName; - Constructor.prototype = new ReactIOSNativeComponent(viewConfig); + Constructor.prototype = new ReactNativeBaseComponent(viewConfig); return Constructor; }; -module.exports = createReactIOSNativeComponentClass; +module.exports = createReactNativeComponentClass; diff --git a/Libraries/ReactNative/findNodeHandle.js b/Libraries/ReactNative/findNodeHandle.js new file mode 100644 index 00000000000000..37c772760c503f --- /dev/null +++ b/Libraries/ReactNative/findNodeHandle.js @@ -0,0 +1,112 @@ +/** + * 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 findNodeHandle + * @flow + */ + +'use strict'; + +var ReactCurrentOwner = require('ReactCurrentOwner'); +var ReactInstanceMap = require('ReactInstanceMap'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); + +var invariant = require('invariant'); +var warning = require('warning'); + +/** + * ReactNative vs ReactWeb + * ----------------------- + * React treats some pieces of data opaquely. This means that the information + * is first class (it can be passed around), but cannot be inspected. This + * allows us to build infrastructure that reasons about resources, without + * making assumptions about the nature of those resources, and this allows that + * infra to be shared across multiple platforms, where the resources are very + * different. General infra (such as `ReactMultiChild`) reasons opaquely about + * the data, but platform specific code (such as `ReactNativeBaseComponent`) can + * make assumptions about the data. + * + * + * `rootNodeID`, uniquely identifies a position in the generated native view + * tree. Many layers of composite components (created with `React.createClass`) + * can all share the same `rootNodeID`. + * + * `nodeHandle`: A sufficiently unambiguous way to refer to a lower level + * resource (dom node, native view etc). The `rootNodeID` is sufficient for web + * `nodeHandle`s, because the position in a tree is always enough to uniquely + * identify a DOM node (we never have nodes in some bank outside of the + * document). The same would be true for `ReactNative`, but we must maintain a + * mapping that we can send efficiently serializable + * strings across native boundaries. + * + * Opaque name TodaysWebReact FutureWebWorkerReact ReactNative + * ---------------------------------------------------------------------------- + * nodeHandle N/A rootNodeID tag + */ + +function findNodeHandle(componentOrHandle: any): ?number { + if (__DEV__) { + var owner = ReactCurrentOwner.current; + if (owner !== null) { + warning( + owner._warnedAboutRefsInRender, + '%s is accessing findNodeHandle inside its render(). ' + + 'render() should be a pure function of props and state. It should ' + + 'never access something that requires stale data from the previous ' + + 'render, such as refs. Move this logic to componentDidMount and ' + + 'componentDidUpdate instead.', + owner.getName() || 'A component' + ); + owner._warnedAboutRefsInRender = true; + } + } + if (componentOrHandle == null) { + return null; + } + if (typeof componentOrHandle === 'number') { + // Already a node handle + return componentOrHandle; + } + + var component = componentOrHandle; + + // TODO (balpert): Wrap iOS native components in a composite wrapper, then + // ReactInstanceMap.get here will always succeed for mounted components + var internalInstance = ReactInstanceMap.get(component); + if (internalInstance) { + return ReactNativeTagHandles.rootNodeIDToTag[internalInstance._rootNodeID]; + } else { + var rootNodeID = component._rootNodeID; + if (rootNodeID) { + return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID]; + } else { + invariant( + ( + // Native + typeof component === 'object' && + '_rootNodeID' in component + ) || ( + // Composite + component.render != null && + typeof component.render === 'function' + ), + 'findNodeHandle(...): Argument is not a component ' + + '(type: %s, keys: %s)', + typeof component, + Object.keys(component) + ); + invariant( + false, + 'findNodeHandle(...): Unable to find node handle for unmounted ' + + 'component.' + ); + } + } +} + +module.exports = findNodeHandle; diff --git a/Libraries/StyleSheet/precomputeStyle.js b/Libraries/StyleSheet/precomputeStyle.js index ea2990b94e3049..b875cc86d25987 100644 --- a/Libraries/StyleSheet/precomputeStyle.js +++ b/Libraries/StyleSheet/precomputeStyle.js @@ -12,6 +12,7 @@ 'use strict'; var MatrixMath = require('MatrixMath'); +var Platform = require('Platform'); var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); var invariant = require('invariant'); @@ -36,8 +37,9 @@ function precomputeStyle(style: ?Object): ?Object { * Generate a transform matrix based on the provided transforms, and use that * within the style object instead. * - * This allows us to provide an API that is similar to CSS and to have a - * universal, singular interface to native code. + * This allows us to provide an API that is similar to CSS, where transforms may + * be applied in an arbitrary order, and yet have a universal, singular + * interface to native code. */ function _precomputeTransforms(style: Object): Object { var {transform} = style; @@ -80,6 +82,17 @@ function _precomputeTransforms(style: Object): Object { } }); + // Android does not support the direct application of a transform matrix to + // a view, so we need to decompose the result matrix into transforms that can + // get applied in the specific order of (1) translate (2) scale (3) rotate. + // Once we can directly apply a matrix, we can remove this decomposition. + if (Platform.OS === 'android') { + return { + ...style, + transformMatrix: result, + decomposedMatrix: MatrixMath.decomposeMatrix(result), + }; + } return { ...style, transformMatrix: result, diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h index 286edb53a7aad5..7a7b44cf8233d8 100644 --- a/Libraries/Text/RCTShadowText.h +++ b/Libraries/Text/RCTShadowText.h @@ -22,6 +22,7 @@ extern NSString *const RCTReactTagAttributeName; @property (nonatomic, copy) NSString *fontWeight; @property (nonatomic, copy) NSString *fontStyle; @property (nonatomic, assign) BOOL isHighlighted; +@property (nonatomic, assign) CGFloat letterSpacing; @property (nonatomic, assign) CGFloat lineHeight; @property (nonatomic, assign) NSUInteger maximumNumberOfLines; @property (nonatomic, assign) CGSize shadowOffset; @@ -30,6 +31,7 @@ extern NSString *const RCTReactTagAttributeName; // Not exposed to JS @property (nonatomic, strong) UIFont *font; @property (nonatomic, assign) NSLineBreakMode truncationMode; +@property (nonatomic, assign) CGFloat effectiveLetterSpacing; @property (nonatomic, copy, readonly) NSAttributedString *attributedString; @property (nonatomic, strong, readonly) NSLayoutManager *layoutManager; diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 4ea9d3bd6fc093..b7e6f997ed110c 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -40,11 +40,15 @@ static css_dim_t RCTMeasure(void *context, float width) css_dim_t result; result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width); + if (shadowText.effectiveLetterSpacing < 0) { + result.dimensions[CSS_WIDTH] -= shadowText.effectiveLetterSpacing; + } result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(computedSize.height); return result; } -@implementation RCTShadowText { +@implementation RCTShadowText +{ NSLayoutManager *_layoutManager; NSTextContainer *_textContainer; NSAttributedString *_cachedAttributedString; @@ -55,6 +59,7 @@ - (instancetype)init { if ((self = [super init])) { _fontSize = NAN; + _letterSpacing = NAN; _isHighlighted = NO; _textContainer = [[NSTextContainer alloc] init]; @@ -71,22 +76,24 @@ - (instancetype)init - (NSAttributedString *)attributedString { return [self _attributedStringWithFontFamily:nil - fontSize:0 + fontSize:nil fontWeight:nil - fontStyle:nil]; + fontStyle:nil + letterSpacing:nil]; } - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily - fontSize:(CGFloat)fontSize + fontSize:(NSNumber *)fontSize fontWeight:(NSString *)fontWeight fontStyle:(NSString *)fontStyle + letterSpacing:(NSNumber *)letterSpacing { if (![self isTextDirty] && _cachedAttributedString) { return _cachedAttributedString; } if (_fontSize && !isnan(_fontSize)) { - fontSize = _fontSize; + fontSize = @(_fontSize); } if (_fontWeight) { fontWeight = _fontWeight; @@ -97,12 +104,17 @@ - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily if (_fontFamily) { fontFamily = _fontFamily; } + if (!isnan(_letterSpacing)) { + letterSpacing = @(_letterSpacing); + } + + _effectiveLetterSpacing = letterSpacing.doubleValue; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; 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]]; + [attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight fontStyle:fontStyle letterSpacing:letterSpacing]]; } else if ([child isKindOfClass:[RCTShadowRawText class]]) { RCTShadowRawText *shadowRawText = (RCTShadowRawText *)child; [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[shadowRawText text] ?: @""]]; @@ -123,8 +135,9 @@ - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily [self _addAttribute:NSBackgroundColorAttributeName withValue:self.textBackgroundColor toAttributedString:attributedString]; } - _font = [RCTConvert UIFont:nil withFamily:fontFamily size:@(fontSize) weight:fontWeight style:fontStyle]; + _font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle]; [self _addAttribute:NSFontAttributeName withValue:_font toAttributedString:attributedString]; + [self _addAttribute:NSKernAttributeName withValue:letterSpacing toAttributedString:attributedString]; [self _addAttribute:RCTReactTagAttributeName withValue:self.reactTag toAttributedString:attributedString]; [self _setParagraphStyleOnAttributedString:attributedString]; @@ -143,7 +156,7 @@ - (UIFont *)font - (void)_addAttribute:(NSString *)attribute withValue:(id)attributeValue toAttributedString:(NSMutableAttributedString *)attributedString { [attributedString enumerateAttribute:attribute inRange:NSMakeRange(0, [attributedString length]) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) { - if (!value) { + if (!value && attributeValue) { [attributedString addAttribute:attribute value:attributeValue range:range]; } }]; @@ -223,6 +236,7 @@ - (void)set##setProp:(type)value; \ RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *); RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat); RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *); +RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat); RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat); RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize); RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment); diff --git a/Libraries/Text/RCTText.h b/Libraries/Text/RCTText.h index 24b98e991cb01f..fe7803eb5dcf5b 100644 --- a/Libraries/Text/RCTText.h +++ b/Libraries/Text/RCTText.h @@ -14,7 +14,6 @@ @property (nonatomic, strong) NSLayoutManager *layoutManager; @property (nonatomic, strong) NSTextContainer *textContainer; @property (nonatomic, copy) NSAttributedString *attributedText; - @property (nonatomic, assign) UIEdgeInsets contentInset; @end diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index ef518d20483d70..b2832a80066a9c 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -45,6 +45,7 @@ - (RCTShadowView *)shadowView RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString) RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString) RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL) +RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger) RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize) diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index a67abacb087ec5..dee0d854341c6b 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -13,19 +13,19 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheetPropType = require('StyleSheetPropType'); var TextStylePropTypes = require('TextStylePropTypes'); var Touchable = require('Touchable'); -var createReactIOSNativeComponentClass = - require('createReactIOSNativeComponentClass'); +var createReactNativeComponentClass = + require('createReactNativeComponentClass'); var merge = require('merge'); var stylePropType = StyleSheetPropType(TextStylePropTypes); var viewConfig = { - validAttributes: merge(ReactIOSViewAttributes.UIView, { + validAttributes: merge(ReactNativeViewAttributes.UIView, { isHighlighted: true, numberOfLines: true, }), @@ -176,7 +176,6 @@ var Text = React.createClass({ for (var key in this.props) { props[key] = this.props[key]; } - props.ref = this.getNodeHandle(); // Text is accessible by default if (props.accessible !== false) { props.accessible = true; @@ -202,6 +201,6 @@ type RectOffset = { var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; -var RCTText = createReactIOSNativeComponentClass(viewConfig); +var RCTText = createReactNativeComponentClass(viewConfig); module.exports = Text; diff --git a/Libraries/Text/TextStylePropTypes.js b/Libraries/Text/TextStylePropTypes.js index 632bc9e9dbc679..95a0f4cb892c3f 100644 --- a/Libraries/Text/TextStylePropTypes.js +++ b/Libraries/Text/TextStylePropTypes.js @@ -32,6 +32,7 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), { writingDirection: ReactPropTypes.oneOf( ['auto' /*default*/, 'ltr', 'rtl'] ), + letterSpacing: ReactPropTypes.number, }); // Text doesn't support padding correctly (#4841912) diff --git a/Libraries/Utilities/AlertIOS.js b/Libraries/Utilities/AlertIOS.js index 0d1612bc9e0d9e..7fd15d254ae609 100644 --- a/Libraries/Utilities/AlertIOS.js +++ b/Libraries/Utilities/AlertIOS.js @@ -12,6 +12,7 @@ 'use strict'; var RCTAlertManager = require('NativeModules').AlertManager; +var invariant = require('invariant'); var DEFAULT_BUTTON_TEXT = 'OK'; var DEFAULT_BUTTON = { @@ -47,14 +48,17 @@ class AlertIOS { message?: ?string, buttons?: Array<{ text: ?string; - onPress: ?Function; - }> + onPress?: ?Function; + }>, + type?: ?string ): void { var callbacks = []; var buttonsSpec = []; title = title || ''; message = message || ''; buttons = buttons || [DEFAULT_BUTTON]; + type = type || ''; + buttons.forEach((btn, index) => { callbacks[index] = btn.onPress; var btnDef = {}; @@ -65,12 +69,50 @@ class AlertIOS { title, message, buttons: buttonsSpec, - }, (id) => { + type, + }, (id, value) => { var cb = callbacks[id]; - cb && cb(); + cb && cb(value); }); } + static prompt( + title: string, + value?: string, + buttons?: Array<{ + text: ?string; + onPress?: ?Function; + }>, + callback?: ?Function + ): void { + if (arguments.length === 2) { + if (typeof value === 'object') { + buttons = value; + value = undefined; + } else if (typeof value === 'function') { + callback = value; + value = undefined; + } + } else if (arguments.length === 3 && typeof buttons === 'function') { + callback = buttons; + buttons = undefined; + } + + invariant( + !(callback && buttons) && (callback || buttons), + 'Must provide either a button list or a callback, but not both' + ); + + if (!buttons) { + buttons = [{ + text: 'Cancel', + }, { + text: 'OK', + onPress: callback + }]; + } + this.alert(title, value, buttons, 'plain-text'); + } } module.exports = AlertIOS; diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js index 7f3d17c461b70a..a7f08260492b98 100755 --- a/Libraries/Utilities/MatrixMath.js +++ b/Libraries/Utilities/MatrixMath.js @@ -5,6 +5,8 @@ */ 'use strict'; +var invariant = require('invariant'); + /** * Memory conservative (mutative) matrix math utilities. Uses "command" * matrices, which are reusable. @@ -74,19 +76,20 @@ var MatrixMath = { matrixCommand[10] = factor; }, + reuseRotateXCommand: function(matrixCommand, radians) { + matrixCommand[5] = Math.cos(radians); + matrixCommand[6] = Math.sin(radians); + matrixCommand[9] = -Math.sin(radians); + matrixCommand[10] = Math.cos(radians); + }, + reuseRotateYCommand: function(matrixCommand, amount) { matrixCommand[0] = Math.cos(amount); - matrixCommand[2] = Math.sin(amount); - matrixCommand[8] = Math.sin(-amount); + matrixCommand[2] = -Math.sin(amount); + matrixCommand[8] = Math.sin(amount); matrixCommand[10] = Math.cos(amount); }, - createRotateZ: function(radians) { - var mat = MatrixMath.createIdentityMatrix(); - MatrixMath.reuseRotateZCommand(mat, radians); - return mat; - }, - // http://www.w3.org/TR/css3-transforms/#recomposing-to-a-2d-matrix reuseRotateZCommand: function(matrixCommand, radians) { matrixCommand[0] = Math.cos(radians); @@ -95,6 +98,12 @@ var MatrixMath = { matrixCommand[5] = Math.cos(radians); }, + createRotateZ: function(radians) { + var mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateZCommand(mat, radians); + return mat; + }, + multiplyInto: function(out, a, b) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], @@ -124,7 +133,398 @@ var MatrixMath = { out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; - } + }, + + determinant(matrix: Array): number { + var [ + m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33 + ] = matrix; + return ( + m03 * m12 * m21 * m30 - m02 * m13 * m21 * m30 - + m03 * m11 * m22 * m30 + m01 * m13 * m22 * m30 + + m02 * m11 * m23 * m30 - m01 * m12 * m23 * m30 - + m03 * m12 * m20 * m31 + m02 * m13 * m20 * m31 + + m03 * m10 * m22 * m31 - m00 * m13 * m22 * m31 - + m02 * m10 * m23 * m31 + m00 * m12 * m23 * m31 + + m03 * m11 * m20 * m32 - m01 * m13 * m20 * m32 - + m03 * m10 * m21 * m32 + m00 * m13 * m21 * m32 + + m01 * m10 * m23 * m32 - m00 * m11 * m23 * m32 - + m02 * m11 * m20 * m33 + m01 * m12 * m20 * m33 + + m02 * m10 * m21 * m33 - m00 * m12 * m21 * m33 - + m01 * m10 * m22 * m33 + m00 * m11 * m22 * m33 + ); + }, + + /** + * Inverse of a matrix. Multiplying by the inverse is used in matrix math + * instead of division. + * + * Formula from: + * http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm + */ + inverse(matrix: Array): Array { + var det = MatrixMath.determinant(matrix); + if (!det) { + return matrix; + } + var [ + m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33 + ] = matrix; + return [ + (m12*m23*m31 - m13*m22*m31 + m13*m21*m32 - m11*m23*m32 - m12*m21*m33 + m11*m22*m33) / det, + (m03*m22*m31 - m02*m23*m31 - m03*m21*m32 + m01*m23*m32 + m02*m21*m33 - m01*m22*m33) / det, + (m02*m13*m31 - m03*m12*m31 + m03*m11*m32 - m01*m13*m32 - m02*m11*m33 + m01*m12*m33) / det, + (m03*m12*m21 - m02*m13*m21 - m03*m11*m22 + m01*m13*m22 + m02*m11*m23 - m01*m12*m23) / det, + (m13*m22*m30 - m12*m23*m30 - m13*m20*m32 + m10*m23*m32 + m12*m20*m33 - m10*m22*m33) / det, + (m02*m23*m30 - m03*m22*m30 + m03*m20*m32 - m00*m23*m32 - m02*m20*m33 + m00*m22*m33) / det, + (m03*m12*m30 - m02*m13*m30 - m03*m10*m32 + m00*m13*m32 + m02*m10*m33 - m00*m12*m33) / det, + (m02*m13*m20 - m03*m12*m20 + m03*m10*m22 - m00*m13*m22 - m02*m10*m23 + m00*m12*m23) / det, + (m11*m23*m30 - m13*m21*m30 + m13*m20*m31 - m10*m23*m31 - m11*m20*m33 + m10*m21*m33) / det, + (m03*m21*m30 - m01*m23*m30 - m03*m20*m31 + m00*m23*m31 + m01*m20*m33 - m00*m21*m33) / det, + (m01*m13*m30 - m03*m11*m30 + m03*m10*m31 - m00*m13*m31 - m01*m10*m33 + m00*m11*m33) / det, + (m03*m11*m20 - m01*m13*m20 - m03*m10*m21 + m00*m13*m21 + m01*m10*m23 - m00*m11*m23) / det, + (m12*m21*m30 - m11*m22*m30 - m12*m20*m31 + m10*m22*m31 + m11*m20*m32 - m10*m21*m32) / det, + (m01*m22*m30 - m02*m21*m30 + m02*m20*m31 - m00*m22*m31 - m01*m20*m32 + m00*m21*m32) / det, + (m02*m11*m30 - m01*m12*m30 - m02*m10*m31 + m00*m12*m31 + m01*m10*m32 - m00*m11*m32) / det, + (m01*m12*m20 - m02*m11*m20 + m02*m10*m21 - m00*m12*m21 - m01*m10*m22 + m00*m11*m22) / det + ]; + }, + + /** + * Turns columns into rows and rows into columns. + */ + transpose(m: Array): Array { + return [ + m[0], m[4], m[8], m[12], + m[1], m[5], m[9], m[13], + m[2], m[6], m[10], m[14], + m[3], m[7], m[11], m[15] + ]; + }, + + /** + * Based on: http://tog.acm.org/resources/GraphicsGems/gemsii/unmatrix.c + */ + multiplyVectorByMatrix( + v: Array, + m: Array + ): Array { + var [vx, vy, vz, vw] = v; + return [ + vx * m[0] + vy * m[4] + vz * m[8] + vw * m[12], + vx * m[1] + vy * m[5] + vz * m[9] + vw * m[13], + vx * m[2] + vy * m[6] + vz * m[10] + vw * m[14], + vx * m[3] + vy * m[7] + vz * m[11] + vw * m[15] + ]; + }, + + /** + * From: https://code.google.com/p/webgl-mjs/source/browse/mjs.js + */ + v3Length(a: Array): number { + return Math.sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2]); + }, + + /** + * Based on: https://code.google.com/p/webgl-mjs/source/browse/mjs.js + */ + v3Normalize( + vector: Array, + v3Length: number + ): Array { + var im = 1 / (v3Length || MatrixMath.v3Length(vector)); + return [ + vector[0] * im, + vector[1] * im, + vector[2] * im + ]; + }, + + /** + * The dot product of a and b, two 3-element vectors. + * From: https://code.google.com/p/webgl-mjs/source/browse/mjs.js + */ + v3Dot(a, b) { + return a[0] * b[0] + + a[1] * b[1] + + a[2] * b[2]; + }, + + /** + * From: + * http://www.opensource.apple.com/source/WebCore/WebCore-514/platform/graphics/transforms/TransformationMatrix.cpp + */ + v3Combine( + a: Array, + b: Array, + aScale: number, + bScale: number + ): Array { + return [ + aScale * a[0] + bScale * b[0], + aScale * a[1] + bScale * b[1], + aScale * a[2] + bScale * b[2] + ]; + }, + + /** + * From: + * http://www.opensource.apple.com/source/WebCore/WebCore-514/platform/graphics/transforms/TransformationMatrix.cpp + */ + v3Cross(a: Array, b: Array): Array { + return [ + a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0] + ]; + }, + + /** + * Based on: + * http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/ + * and: + * http://quat.zachbennett.com/ + * + * Note that this rounds degrees to the thousandth of a degree, due to + * floating point errors in the creation of the quaternion. + * + * Also note that this expects the qw value to be last, not first. + * + * Also, when researching this, remember that: + * yaw === heading === z-axis + * pitch === elevation/attitude === y-axis + * roll === bank === x-axis + */ + quaternionToDegreesXYZ(q: Array, matrix, row): Array { + var [qx, qy, qz, qw] = q; + var qw2 = qw * qw; + var qx2 = qx * qx; + var qy2 = qy * qy; + var qz2 = qz * qz; + var test = qx * qy + qz * qw; + var unit = qw2 + qx2 + qy2 + qz2; + var conv = 180 / Math.PI; + + if (test > 0.49999 * unit) { + return [0, 2 * Math.atan2(qx, qw) * conv, 90]; + } + if (test < -0.49999 * unit) { + return [0, -2 * Math.atan2(qx, qw) * conv, -90]; + } + + return [ + MatrixMath.roundTo3Places( + Math.atan2(2*qx*qw-2*qy*qz,1-2*qx2-2*qz2) * conv + ), + MatrixMath.roundTo3Places( + Math.atan2(2*qy*qw-2*qx*qz,1-2*qy2-2*qz2) * conv + ), + MatrixMath.roundTo3Places( + Math.asin(2*qx*qy+2*qz*qw) * conv + ) + ]; + }, + + /** + * Based on: + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round + */ + roundTo3Places(n: number): number { + var arr = n.toString().split('e'); + return Math.round(arr[0] + 'e' + (arr[1] ? (+arr[1] - 3) : 3)) * 0.001; + }, + + /** + * Decompose a matrix into separate transform values, for use on platforms + * where applying a precomposed matrix is not possible, and transforms are + * applied in an inflexible ordering (e.g. Android). + * + * Implementation based on + * http://www.w3.org/TR/css3-transforms/#decomposing-a-2d-matrix + * http://www.w3.org/TR/css3-transforms/#decomposing-a-3d-matrix + * which was based on + * http://tog.acm.org/resources/GraphicsGems/gemsii/unmatrix.c + */ + decomposeMatrix(transformMatrix: Array): ?Object { + + invariant( + transformMatrix.length === 16, + 'Matrix decomposition needs a list of 3d matrix values, received %s', + transformMatrix + ); + + // output values + var perspective = []; + var quaternion = []; + var scale = []; + var skew = []; + var translation = []; + + // create normalized, 2d array matrix + // and normalized 1d array perspectiveMatrix with redefined 4th column + if (!transformMatrix[15]) { + return; + } + var matrix = []; + var perspectiveMatrix = []; + for (var i = 0; i < 4; i++) { + matrix.push([]); + for (var j = 0; j < 4; j++) { + var value = transformMatrix[(i * 4) + j] / transformMatrix[15]; + matrix[i].push(value); + perspectiveMatrix.push(j === 3 ? 0 : value); + } + } + perspectiveMatrix[15] = 1; + + // test for singularity of upper 3x3 part of the perspective matrix + if (!MatrixMath.determinant(perspectiveMatrix)) { + return; + } + + // isolate perspective + if (matrix[0][3] !== 0 || matrix[1][3] !== 0 || matrix[2][3] !== 0) { + // rightHandSide is the right hand side of the equation. + // rightHandSide is a vector, or point in 3d space relative to the origin. + var rightHandSide = [ + matrix[0][3], + matrix[1][3], + matrix[2][3], + matrix[3][3] + ]; + + // Solve the equation by inverting perspectiveMatrix and multiplying + // rightHandSide by the inverse. + var inversePerspectiveMatrix = MatrixMath.inverse3x3( + perspectiveMatrix + ); + var transposedInversePerspectiveMatrix = MatrixMath.transpose4x4( + inversePerspectiveMatrix + ); + var perspective = MatrixMath.multiplyVectorByMatrix( + rightHandSide, + transposedInversePerspectiveMatrix + ); + } else { + // no perspective + perspective[0] = perspective[1] = perspective[2] = 0; + perspective[3] = 1; + } + + // translation is simple + for (var i = 0; i < 3; i++) { + translation[i] = matrix[3][i]; + } + + // Now get scale and shear. + // 'row' is a 3 element array of 3 component vectors + var row = []; + for (i = 0; i < 3; i++) { + row[i] = [ + matrix[i][0], + matrix[i][1], + matrix[i][2] + ]; + } + + // Compute X scale factor and normalize first row. + scale[0] = MatrixMath.v3Length(row[0]); + row[0] = MatrixMath.v3Normalize(row[0], scale[0]); + + // Compute XY shear factor and make 2nd row orthogonal to 1st. + skew[0] = MatrixMath.v3Dot(row[0], row[1]); + row[1] = MatrixMath.v3Combine(row[1], row[0], 1.0, -skew[0]); + + // Compute XY shear factor and make 2nd row orthogonal to 1st. + skew[0] = MatrixMath.v3Dot(row[0], row[1]); + row[1] = MatrixMath.v3Combine(row[1], row[0], 1.0, -skew[0]); + + // Now, compute Y scale and normalize 2nd row. + scale[1] = MatrixMath.v3Length(row[1]); + row[1] = MatrixMath.v3Normalize(row[1], scale[1]); + skew[0] /= scale[1]; + + // Compute XZ and YZ shears, orthogonalize 3rd row + skew[1] = MatrixMath.v3Dot(row[0], row[2]); + row[2] = MatrixMath.v3Combine(row[2], row[0], 1.0, -skew[1]); + skew[2] = MatrixMath.v3Dot(row[1], row[2]); + row[2] = MatrixMath.v3Combine(row[2], row[1], 1.0, -skew[2]); + + // Next, get Z scale and normalize 3rd row. + scale[2] = MatrixMath.v3Length(row[2]); + row[2] = MatrixMath.v3Normalize(row[2], scale[2]); + skew[1] /= scale[2]; + skew[2] /= scale[2]; + + // At this point, the matrix (in rows) is orthonormal. + // Check for a coordinate system flip. If the determinant + // is -1, then negate the matrix and the scaling factors. + var pdum3 = MatrixMath.v3Cross(row[1], row[2]); + if (MatrixMath.v3Dot(row[0], pdum3) < 0) { + for (i = 0; i < 3; i++) { + scale[i] *= -1; + row[i][0] *= -1; + row[i][1] *= -1; + row[i][2] *= -1; + } + } + + // Now, get the rotations out + quaternion[0] = + 0.5 * Math.sqrt(Math.max(1 + row[0][0] - row[1][1] - row[2][2], 0)); + quaternion[1] = + 0.5 * Math.sqrt(Math.max(1 - row[0][0] + row[1][1] - row[2][2], 0)); + quaternion[2] = + 0.5 * Math.sqrt(Math.max(1 - row[0][0] - row[1][1] + row[2][2], 0)); + quaternion[3] = + 0.5 * Math.sqrt(Math.max(1 + row[0][0] + row[1][1] + row[2][2], 0)); + + if (row[2][1] > row[1][2]) { + quaternion[0] = -quaternion[0]; + } + if (row[0][2] > row[2][0]) { + quaternion[1] = -quaternion[1]; + } + if (row[1][0] > row[0][1]) { + quaternion[2] = -quaternion[2]; + } + + // correct for occasional, weird Euler synonyms for 2d rotation + var rotationDegrees; + if ( + quaternion[0] < 0.001 && quaternion[0] >= 0 && + quaternion[1] < 0.001 && quaternion[1] >= 0 + ) { + // this is a 2d rotation on the z-axis + rotationDegrees = [0, 0, MatrixMath.roundTo3Places( + Math.atan2(row[0][1], row[0][0]) * 180 / Math.PI + )]; + } else { + rotationDegrees = MatrixMath.quaternionToDegreesXYZ(quaternion, matrix, row); + } + + // expose both base data and convenience names + return { + rotationDegrees, + perspective, + quaternion, + scale, + skew, + translation, + + rotate: rotationDegrees[2], + scaleX: scale[0], + scaleY: scale[1], + translateX: translation[0], + translateY: translation[1], + }; + }, }; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index f15dd70e0d9b84..df34dde0629f31 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -81,6 +81,14 @@ var REQUEST_PARAMSS = 2; var RESPONSE_CBIDS = 3; var RESPONSE_RETURN_VALUES = 4; +var applyWithErrorReporter = function(fun: Function, context: ?any, args: ?any) { + try { + return fun.apply(context, args); + } catch (e) { + ErrorUtils.reportFatalError(e); + } +}; + /** * Utility to catch errors and prevent having to bind, or execute a bound * function, while catching errors in a process and returning a resulting @@ -97,10 +105,10 @@ var RESPONSE_RETURN_VALUES = 4; */ var guardReturn = function(operation, operationArguments, getReturnValue, context) { if (operation) { - ErrorUtils.applyWithGuard(operation, context, operationArguments); + applyWithErrorReporter(operation, context, operationArguments); } if (getReturnValue) { - return ErrorUtils.applyWithGuard(getReturnValue, context, null); + return applyWithErrorReporter(getReturnValue, context, null); } return null; }; diff --git a/Libraries/Utilities/PixelRatio.js b/Libraries/Utilities/PixelRatio.js index e6c96b7fa6b30e..a3e4d9e7703aaf 100644 --- a/Libraries/Utilities/PixelRatio.js +++ b/Libraries/Utilities/PixelRatio.js @@ -67,9 +67,9 @@ class PixelRatio { static getPixelSizeForLayoutSize(layoutSize: number): number { return Math.round(layoutSize * PixelRatio.get()); } -} -// No-op for iOS, but used on the web. Should not be documented. -PixelRatio.startDetecting = function() {}; + // No-op for iOS, but used on the web. Should not be documented. + static startDetecting() {} +} module.exports = PixelRatio; diff --git a/Libraries/Utilities/__tests__/MatrixMath-test.js b/Libraries/Utilities/__tests__/MatrixMath-test.js new file mode 100644 index 00000000000000..4ca108e3925104 --- /dev/null +++ b/Libraries/Utilities/__tests__/MatrixMath-test.js @@ -0,0 +1,140 @@ +/** + * 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. + */ +'use strict'; + +jest.dontMock('MatrixMath'); +jest.dontMock('invariant'); + +var MatrixMath = require('MatrixMath'); + +function degreesToRadians(degrees) { + return degrees * Math.PI / 180; +} + +describe('MatrixMath', () => { + + it('decomposes a 4x4 matrix to produce accurate Z-axis angles', () => { + expect(MatrixMath.decomposeMatrix([ + 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1 + ]).rotationDegrees).toEqual([0, 0, 0]); + + [30, 45, 60, 75, 90, 100, 115, 120, 133, 167].forEach(angle => { + var mat = MatrixMath.createRotateZ(degreesToRadians(angle)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([0, 0, angle]); + + mat = MatrixMath.createRotateZ(degreesToRadians(-angle)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([0, 0, -angle]); + }); + + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(180)) + ).rotationDegrees).toEqual([0, 0, 180]); + + // all values are between 0 and 180; + // change of sign and direction in the third and fourth quadrant + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(222)) + ).rotationDegrees).toEqual([0, 0, -138]); + + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(270)) + ).rotationDegrees).toEqual([0, 0, -90]); + + // 360 is expressed as 0 + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(360)) + ).rotationDegrees).toEqual([0, 0, 0]); + + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(33.33333333)) + ).rotationDegrees).toEqual([0, 0, 33.333]); + + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(86.75309)) + ).rotationDegrees).toEqual([0, 0, 86.753]); + + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(42.00000000001)) + ).rotationDegrees).toEqual([0, 0, 42]); + + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(42.99999999999)) + ).rotationDegrees).toEqual([0, 0, 43]); + + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(42.49999999999)) + ).rotationDegrees).toEqual([0, 0, 42.5]); + + expect(MatrixMath.decomposeMatrix( + MatrixMath.createRotateZ(degreesToRadians(42.55555555555)) + ).rotationDegrees).toEqual([0, 0, 42.556]); + }); + + it('decomposes a 4x4 matrix to produce accurate Y-axis angles', () => { + var mat; + [30, 45, 60, 75, 90, 100, 110, 120, 133, 167].forEach(angle => { + mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateYCommand(mat, degreesToRadians(angle)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([0, angle, 0]); + + mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateYCommand(mat, degreesToRadians(-angle)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([0, -angle, 0]); + }); + + // all values are between 0 and 180; + // change of sign and direction in the third and fourth quadrant + mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateYCommand(mat, degreesToRadians(222)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([0, -138, 0]); + + mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateYCommand(mat, degreesToRadians(270)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([0, -90, 0]); + + mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateYCommand(mat, degreesToRadians(360)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([0, 0, 0]); + }); + + it('decomposes a 4x4 matrix to produce accurate X-axis angles', () => { + var mat; + [30, 45, 60, 75, 90, 100, 110, 120, 133, 167].forEach(angle => { + mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateXCommand(mat, degreesToRadians(angle)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([angle, 0, 0]); + }); + + // all values are between 0 and 180; + // change of sign and direction in the third and fourth quadrant + mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateXCommand(mat, degreesToRadians(222)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([-138, 0, 0]); + + mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateXCommand(mat, degreesToRadians(270)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([-90, 0, 0]); + + mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateXCommand(mat, degreesToRadians(360)); + expect(MatrixMath.decomposeMatrix(mat).rotationDegrees) + .toEqual([0, 0, 0]); + }); + +}); diff --git a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js index d2782cc370be56..721da744a79dbb 100644 --- a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js +++ b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js @@ -183,7 +183,7 @@ var PanResponder = { * changes* in the centroid of recently moved touches. * * There is also some nuance with how we handle multiple moved touches in a - * single event. With the way `ReactIOSEventEmitter` dispatches touches as + * single event. With the way `ReactNativeEventEmitter` dispatches touches as * individual events, multiple touches generate two 'move' events, each of * them triggering `onResponderMove`. But with the way `PanResponder` works, * all of the gesture inference is performed on the first dispatch, since it diff --git a/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js b/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js index ab43a08f69df9b..b91feba9b1e2a2 100644 --- a/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js +++ b/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js @@ -2,7 +2,7 @@ * @providesModule UniversalWorkerNodeHandle */ -var ReactIOSTagHandles = require('ReactIOSTagHandles'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); var invariant = require('invariant'); @@ -12,7 +12,7 @@ var UniversalWorkerNodeHandle = { nodeHandle !== undefined && nodeHandle !== null && nodeHandle !== 0, 'No node handle defined' ); - return ReactIOSTagHandles.tagToRootNodeID[nodeHandle]; + return ReactNativeTagHandles.tagToRootNodeID[nodeHandle]; } }; diff --git a/React.podspec b/React.podspec index 7cb6c1c1f8ffc5..159072b396c4ff 100644 --- a/React.podspec +++ b/React.podspec @@ -32,6 +32,12 @@ Pod::Spec.new do |s| ss.frameworks = "JavaScriptCore" end + s.subspec 'ART' do |ss| + ss.dependency 'React/Core' + ss.source_files = "Libraries/ART/**/*.{h,m}" + ss.preserve_paths = "Libraries/ART/**/*.js" + end + s.subspec 'RCTActionSheet' do |ss| ss.dependency 'React/Core' ss.source_files = "Libraries/ActionSheetIOS/*.{h,m}" diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index c98e3648b56b8b..750cb422fddfef 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -789,6 +789,7 @@ - (void)dealloc * This runs only on the main thread, but crashes the subclass * RCTAssertMainThread(); */ + [[NSNotificationCenter defaultCenter] removeObserver:self]; [self invalidate]; } @@ -819,9 +820,9 @@ - (void)bindKeys - (void)reload { -/** - * AnyThread - */ + /** + * AnyThread + */ dispatch_async(dispatch_get_main_queue(), ^{ [self invalidate]; [self setUp]; @@ -873,15 +874,19 @@ - (RCTEventDispatcher *)eventDispatcher return _eventDispatcher ?: _batchedBridge.eventDispatcher; } -#define RCT_BRIDGE_WARN(...) \ +#define RCT_INNER_BRIDGE_ONLY(...) \ - (void)__VA_ARGS__ \ { \ RCTLogMustFix(@"Called method \"%@\" on top level bridge. This method should \ only be called from bridge instance in a bridge module", @(__func__)); \ } -RCT_BRIDGE_WARN(enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args) -RCT_BRIDGE_WARN(_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context) +- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args +{ + [self.batchedBridge enqueueJSCall:moduleDotMethod args:args]; +} + +RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context) @end @@ -1084,7 +1089,16 @@ - (void)initJS */ _loading = NO; - } else if (bundleURL) { // Allow testing without a script + } else if (!bundleURL) { + + // Allow testing without a script + dispatch_async(dispatch_get_main_queue(), ^{ + _loading = NO; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification + object:_parentBridge + userInfo:@{ @"bridge": self }]; + }); + } else { RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; [loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) { @@ -1108,9 +1122,9 @@ - (void)initJS withDetails:[error localizedFailureReason]]; } - NSDictionary *userInfo = @{@"error": error}; + NSDictionary *userInfo = @{@"bridge": self, @"error": error}; [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification - object:self + object:_parentBridge userInfo:userInfo]; } else { @@ -1161,7 +1175,6 @@ - (void)invalidate void (^mainThreadInvalidate)(void) = ^{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; [_mainDisplayLink invalidate]; _mainDisplayLink = nil; @@ -1499,6 +1512,7 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i @"module": method.moduleClassName, @"method": method.JSMethodName, @"selector": NSStringFromSelector(method.selector), + @"args": RCTJSONStringify(params ?: [NSNull null], NULL), }); } forModule:@(moduleID)]; diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index f7e688df560258..80964e9382f6bf 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -86,7 +86,7 @@ - (instancetype)init object:nil]; [notificationCenter addObserver:self - selector:@selector(jsLoaded) + selector:@selector(jsLoaded:) name:RCTJavaScriptDidLoadNotification object:nil]; @@ -141,8 +141,12 @@ - (void)updateSettings self.executorClass = NSClassFromString(_settings[@"executorClass"]); } -- (void)jsLoaded +- (void)jsLoaded:(NSNotification *)notification { + if (notification.userInfo[@"bridge"] != _bridge) { + return; + } + _jsLoaded = YES; // Check if live reloading is available diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 9ee09d495ee447..980d253d317ff4 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -11,6 +11,7 @@ #import +#import "RCTAssert.h" #import "RCTBridge.h" #import "RCTContextExecutor.h" #import "RCTEventDispatcher.h" @@ -53,6 +54,7 @@ @implementation RCTRootView - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName { + RCTAssertMainThread(); RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView"); RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView"); @@ -96,7 +98,7 @@ - (BOOL)canBecomeFirstResponder } RCT_IMPORT_METHOD(AppRegistry, runApplication) -RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer) +RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer) - (void)javaScriptDidLoad:(NSNotification *)notification @@ -150,7 +152,7 @@ - (NSNumber *)reactTag - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - [_contentView removeFromSuperview]; + [_contentView invalidate]; } @end @@ -212,13 +214,12 @@ - (BOOL)isValid - (void)invalidate { - self.userInteractionEnabled = NO; -} - -- (void)dealloc -{ - [_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer" - args:@[self.reactTag]]; + if (self.isValid) { + self.userInteractionEnabled = NO; + [self removeFromSuperview]; + [_bridge enqueueJSCall:@"ReactNative.unmountComponentAtNodeAndRemoveContainer" + args:@[self.reactTag]]; + } } @end diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index 2af5c428c3de74..f95f134c34f16b 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -199,7 +199,7 @@ - (void)_updateAndDispatchTouches:(NSSet *)touches #pragma mark - Gesture Recognizer Delegate Callbacks -static BOOL RCTAllTouchesAreCancelldOrEnded(NSSet *touches) +static BOOL RCTAllTouchesAreCancelledOrEnded(NSSet *touches) { for (UITouch *touch in touches) { if (touch.phase == UITouchPhaseBegan || @@ -254,7 +254,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event [self _updateAndDispatchTouches:touches eventName:@"topTouchEnd" originatingTime:event.timestamp]; [self _recordRemovedTouches:touches]; - if (RCTAllTouchesAreCancelldOrEnded(event.allTouches)) { + if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) { self.state = UIGestureRecognizerStateEnded; } else if (RCTAnyTouchesChanged(event.allTouches)) { self.state = UIGestureRecognizerStateChanged; @@ -267,7 +267,7 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event [self _updateAndDispatchTouches:touches eventName:@"topTouchCancel" originatingTime:event.timestamp]; [self _recordRemovedTouches:touches]; - if (RCTAllTouchesAreCancelldOrEnded(event.allTouches)) { + if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) { self.state = UIGestureRecognizerStateCancelled; } else if (RCTAnyTouchesChanged(event.allTouches)) { self.state = UIGestureRecognizerStateChanged; diff --git a/React/Layout/Layout.c b/React/Layout/Layout.c index 9ed711cd07be52..fb50d1fdceee1c 100644 --- a/React/Layout/Layout.c +++ b/React/Layout/Layout.c @@ -1,15 +1,20 @@ /** + * + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !! This file is a check-in from github! !! + * !! !! + * !! You should not modify this file directly. Instead: !! + * !! 1) Go to https://github.com/facebook/css-layout !! + * !! 2) Make a pull request and get it merged !! + * !! 3) Execute ./import.sh to pull in the latest version !! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * * Copyright (c) 2014, 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. - * - * WARNING: You should not modify this file directly. Instead: - * 1) Go to https://github.com/facebook/css-layout - * 2) Make a pull request and get it merged - * 3) Run import.sh to copy Layout.* to react-native-github */ #include @@ -38,12 +43,6 @@ void init_css_node(css_node_t *node) { node->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED; node->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; - node->style.minDimensions[CSS_WIDTH] = CSS_UNDEFINED; - node->style.minDimensions[CSS_HEIGHT] = CSS_UNDEFINED; - - node->style.maxDimensions[CSS_WIDTH] = CSS_UNDEFINED; - node->style.maxDimensions[CSS_HEIGHT] = CSS_UNDEFINED; - node->style.position[CSS_LEFT] = CSS_UNDEFINED; node->style.position[CSS_TOP] = CSS_UNDEFINED; node->style.position[CSS_RIGHT] = CSS_UNDEFINED; @@ -249,10 +248,6 @@ static float getPaddingAndBorder(css_node_t *node, int location) { return getPadding(node, location) + getBorder(node, location); } -static float getBorderAxis(css_node_t *node, css_flex_direction_t axis) { - return getBorder(node, leading[axis]) + getBorder(node, trailing[axis]); -} - static float getMarginAxis(css_node_t *node, css_flex_direction_t axis) { return getMargin(node, leading[axis]) + getMargin(node, trailing[axis]); } @@ -302,8 +297,7 @@ static float getDimWithMargin(css_node_t *node, css_flex_direction_t axis) { } static bool isDimDefined(css_node_t *node, css_flex_direction_t axis) { - float value = node->style.dimensions[dim[axis]]; - return !isUndefined(value) && value > 0.0; + return !isUndefined(node->style.dimensions[dim[axis]]); } static bool isPosDefined(css_node_t *node, css_position_t position) { @@ -322,30 +316,6 @@ static float getPosition(css_node_t *node, css_position_t position) { return 0; } -static float boundAxis(css_node_t *node, css_flex_direction_t axis, float value) { - float min = CSS_UNDEFINED; - float max = CSS_UNDEFINED; - - if (axis == CSS_FLEX_DIRECTION_COLUMN) { - min = node->style.minDimensions[CSS_HEIGHT]; - max = node->style.maxDimensions[CSS_HEIGHT]; - } else if (axis == CSS_FLEX_DIRECTION_ROW) { - min = node->style.minDimensions[CSS_WIDTH]; - max = node->style.maxDimensions[CSS_WIDTH]; - } - - float boundValue = value; - - if (!isUndefined(max) && max >= 0.0 && boundValue > max) { - boundValue = max; - } - if (!isUndefined(min) && min >= 0.0 && boundValue < min) { - boundValue = min; - } - - return boundValue; -} - // When the user specifically sets a value for width or height static void setDimensionFromStyle(css_node_t *node, css_flex_direction_t axis) { // The parent already computed us a width or height. We just skip it @@ -359,7 +329,7 @@ static void setDimensionFromStyle(css_node_t *node, css_flex_direction_t axis) { // The dimensions can never be smaller than the padding and border node->layout.dimensions[dim[axis]] = fmaxf( - boundAxis(node, axis, node->style.dimensions[dim[axis]]), + node->style.dimensions[dim[axis]], getPaddingAndBorderAxis(node, axis) ); } @@ -376,7 +346,6 @@ static float getRelativePosition(css_node_t *node, css_flex_direction_t axis) { static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { /** START_GENERATED **/ - css_flex_direction_t mainAxis = getFlexDirection(node); css_flex_direction_t crossAxis = mainAxis == CSS_FLEX_DIRECTION_ROW ? CSS_FLEX_DIRECTION_COLUMN : @@ -415,31 +384,25 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // Let's not measure the text if we already know both dimensions if (isRowUndefined || isColumnUndefined) { - css_dim_t measureDim = node->measure( + css_dim_t measure_dim = node->measure( node->context, - width ); if (isRowUndefined) { - node->layout.dimensions[CSS_WIDTH] = measureDim.dimensions[CSS_WIDTH] + + node->layout.dimensions[CSS_WIDTH] = measure_dim.dimensions[CSS_WIDTH] + getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); } if (isColumnUndefined) { - node->layout.dimensions[CSS_HEIGHT] = measureDim.dimensions[CSS_HEIGHT] + + node->layout.dimensions[CSS_HEIGHT] = measure_dim.dimensions[CSS_HEIGHT] + getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); } } return; } - int i; - int ii; - css_node_t* child; - css_flex_direction_t axis; - // Pre-fill some dimensions straight from the parent - for (i = 0; i < node->children_count; ++i) { - child = node->get_child(node->context, i); + for (int i = 0; i < node->children_count; ++i) { + css_node_t* child = node->get_child(node->context, i); // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass if (getAlignItem(node, child) == CSS_ALIGN_STRETCH && @@ -447,27 +410,27 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { !isUndefined(node->layout.dimensions[dim[crossAxis]]) && !isDimDefined(child, crossAxis)) { child->layout.dimensions[dim[crossAxis]] = fmaxf( - boundAxis(child, crossAxis, node->layout.dimensions[dim[crossAxis]] - + node->layout.dimensions[dim[crossAxis]] - getPaddingAndBorderAxis(node, crossAxis) - - getMarginAxis(child, crossAxis)), + getMarginAxis(child, crossAxis), // You never want to go smaller than padding getPaddingAndBorderAxis(child, crossAxis) ); } else if (getPositionType(child) == CSS_POSITION_ABSOLUTE) { // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both // left and right or top and bottom). - for (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + for (int ii = 0; ii < 2; ii++) { + css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; if (!isUndefined(node->layout.dimensions[dim[axis]]) && !isDimDefined(child, axis) && isPosDefined(child, leading[axis]) && isPosDefined(child, trailing[axis])) { child->layout.dimensions[dim[axis]] = fmaxf( - boundAxis(child, axis, node->layout.dimensions[dim[axis]] - - getPaddingAndBorderAxis(node, axis) - - getMarginAxis(child, axis) - - getPosition(child, leading[axis]) - - getPosition(child, trailing[axis])), + node->layout.dimensions[dim[axis]] - + getPaddingAndBorderAxis(node, axis) - + getMarginAxis(child, axis) - + getPosition(child, leading[axis]) - + getPosition(child, trailing[axis]), // You never want to go smaller than padding getPaddingAndBorderAxis(child, axis) ); @@ -485,12 +448,11 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // We want to execute the next two loops one per line with flex-wrap int startLine = 0; int endLine = 0; - // int nextOffset = 0; - int alreadyComputedNextLayout = 0; + int nextLine = 0; // We aggregate the total dimensions of the container in those two variables float linesCrossDim = 0; float linesMainDim = 0; - while (endLine < node->children_count) { + while (endLine != node->children_count) { // Layout non flexible children and count children by type // mainContentDim is accumulation of the dimensions and margin of all the @@ -504,10 +466,8 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { int flexibleChildrenCount = 0; float totalFlexible = 0; int nonFlexibleChildrenCount = 0; - - float maxWidth; - for (i = startLine; i < node->children_count; ++i) { - child = node->get_child(node->context, i); + for (int i = startLine; i < node->children_count; ++i) { + css_node_t* child = node->get_child(node->context, i); float nextContentDim = 0; // It only makes sense to consider a child flexible if we have a computed @@ -517,27 +477,26 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { totalFlexible += getFlex(child); // Even if we don't know its exact size yet, we already know the padding, - // border and margin. We'll use this partial information, which represents - // the smallest possible size for the child, to compute the remaining - // available space. + // border and margin. We'll use this partial information to compute the + // remaining space. nextContentDim = getPaddingAndBorderAxis(child, mainAxis) + getMarginAxis(child, mainAxis); } else { - maxWidth = CSS_UNDEFINED; - if (mainAxis != CSS_FLEX_DIRECTION_ROW) { + float maxWidth = CSS_UNDEFINED; + if (mainAxis == CSS_FLEX_DIRECTION_ROW) { + // do nothing + } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { + maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - + getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + } else { maxWidth = parentMaxWidth - getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - - if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { - maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - } } // This is the main recursive call. We layout non flexible children. - if (alreadyComputedNextLayout == 0) { + if (nextLine == 0) { layoutNode(child, maxWidth); } @@ -553,14 +512,12 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // The element we are about to add would make us go to the next line if (isFlexWrap(node) && !isUndefined(node->layout.dimensions[dim[mainAxis]]) && - mainContentDim + nextContentDim > definedMainDim && - // If there's only one element, then it's bigger than the content - // and needs its own line - i != startLine) { - alreadyComputedNextLayout = 1; + mainContentDim + nextContentDim > definedMainDim) { + nonFlexibleChildrenCount--; + nextLine = i + 1; break; } - alreadyComputedNextLayout = 0; + nextLine = 0; mainContentDim += nextContentDim; endLine = i + 1; } @@ -585,26 +542,6 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // remaining space if (flexibleChildrenCount != 0) { float flexibleMainDim = remainingMainDim / totalFlexible; - float baseMainDim; - float boundMainDim; - - // Iterate over every child in the axis. If the flex share of remaining - // space doesn't meet min/max bounds, remove this child from flex - // calculations. - for (i = startLine; i < endLine; ++i) { - child = node->get_child(node->context, i); - if (isFlex(child)) { - baseMainDim = flexibleMainDim * getFlex(child) + - getPaddingAndBorderAxis(child, mainAxis); - boundMainDim = boundAxis(child, mainAxis, baseMainDim); - - if (baseMainDim != boundMainDim) { - remainingMainDim -= boundMainDim; - totalFlexible -= getFlex(child); - } - } - } - flexibleMainDim = remainingMainDim / totalFlexible; // The non flexible children can overflow the container, in this case // we should just assume that there is no space available. @@ -614,20 +551,21 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // We iterate over the full array and only apply the action on flexible // children. This is faster than actually allocating a new array that // contains only flexible children. - for (i = startLine; i < endLine; ++i) { - child = node->get_child(node->context, i); + for (int i = startLine; i < endLine; ++i) { + css_node_t* child = node->get_child(node->context, i); if (isFlex(child)) { // At this point we know the final size of the element in the main // dimension - child->layout.dimensions[dim[mainAxis]] = boundAxis(child, mainAxis, - flexibleMainDim * getFlex(child) + getPaddingAndBorderAxis(child, mainAxis) - ); + child->layout.dimensions[dim[mainAxis]] = flexibleMainDim * getFlex(child) + + getPaddingAndBorderAxis(child, mainAxis); - maxWidth = CSS_UNDEFINED; - if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { + float maxWidth = CSS_UNDEFINED; + if (mainAxis == CSS_FLEX_DIRECTION_ROW) { + // do nothing + } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - } else if (mainAxis != CSS_FLEX_DIRECTION_ROW) { + } else { maxWidth = parentMaxWidth - getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); @@ -642,7 +580,9 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // space available } else { css_justify_t justifyContent = getJustifyContent(node); - if (justifyContent == CSS_JUSTIFY_CENTER) { + if (justifyContent == CSS_JUSTIFY_FLEX_START) { + // Do nothing + } else if (justifyContent == CSS_JUSTIFY_CENTER) { leadingMainDim = remainingMainDim / 2; } else if (justifyContent == CSS_JUSTIFY_FLEX_END) { leadingMainDim = remainingMainDim; @@ -672,8 +612,8 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { float mainDim = leadingMainDim + getPaddingAndBorder(node, leading[mainAxis]); - for (i = startLine; i < endLine; ++i) { - child = node->get_child(node->context, i); + for (int i = startLine; i < endLine; ++i) { + css_node_t* child = node->get_child(node->context, i); if (getPositionType(child) == CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[mainAxis])) { @@ -698,7 +638,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { mainDim += betweenMainDim + getDimWithMargin(child, mainAxis); // The cross dimension is the max of the elements dimension since there // can only be one element in that cross dimension. - crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis))); + crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis)); } } @@ -708,15 +648,15 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // For the cross dim, we add both sides at the end because the value // is aggregate via a max function. Intermediate negative values // can mess this computation otherwise - boundAxis(node, crossAxis, crossDim + getPaddingAndBorderAxis(node, crossAxis)), + crossDim + getPaddingAndBorderAxis(node, crossAxis), getPaddingAndBorderAxis(node, crossAxis) ); } // Position elements in the cross axis - for (i = startLine; i < endLine; ++i) { - child = node->get_child(node->context, i); + for (int i = startLine; i < endLine; ++i) { + css_node_t* child = node->get_child(node->context, i); if (getPositionType(child) == CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[crossAxis])) { @@ -734,19 +674,21 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // alignSelf (child) in order to determine the position in the cross axis if (getPositionType(child) == CSS_POSITION_RELATIVE) { css_align_t alignItem = getAlignItem(node, child); - if (alignItem == CSS_ALIGN_STRETCH) { + if (alignItem == CSS_ALIGN_FLEX_START) { + // Do nothing + } else if (alignItem == CSS_ALIGN_STRETCH) { // You can only stretch if the dimension has not already been set // previously. if (!isDimDefined(child, crossAxis)) { child->layout.dimensions[dim[crossAxis]] = fmaxf( - boundAxis(child, crossAxis, containerCrossAxis - + containerCrossAxis - getPaddingAndBorderAxis(node, crossAxis) - - getMarginAxis(child, crossAxis)), + getMarginAxis(child, crossAxis), // You never want to go smaller than padding getPaddingAndBorderAxis(child, crossAxis) ); } - } else if (alignItem != CSS_ALIGN_FLEX_START) { + } else { // The remaining space between the parent dimensions+padding and child // dimensions+margin. float remainingCrossDim = containerCrossAxis - @@ -777,7 +719,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { node->layout.dimensions[dim[mainAxis]] = fmaxf( // We're missing the last padding at this point to get the final // dimension - boundAxis(node, mainAxis, linesMainDim + getPaddingAndBorder(node, trailing[mainAxis])), + linesMainDim + getPaddingAndBorder(node, trailing[mainAxis]), // We can never assign a width smaller than the padding and borders getPaddingAndBorderAxis(node, mainAxis) ); @@ -788,38 +730,37 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // For the cross dim, we add both sides at the end because the value // is aggregate via a max function. Intermediate negative values // can mess this computation otherwise - boundAxis(node, crossAxis, linesCrossDim + getPaddingAndBorderAxis(node, crossAxis)), + linesCrossDim + getPaddingAndBorderAxis(node, crossAxis), getPaddingAndBorderAxis(node, crossAxis) ); } // Calculate dimensions for absolutely positioned elements - for (i = 0; i < node->children_count; ++i) { - child = node->get_child(node->context, i); + for (int i = 0; i < node->children_count; ++i) { + css_node_t* child = node->get_child(node->context, i); if (getPositionType(child) == CSS_POSITION_ABSOLUTE) { // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both // left and right or top and bottom). - for (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + for (int ii = 0; ii < 2; ii++) { + css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; if (!isUndefined(node->layout.dimensions[dim[axis]]) && !isDimDefined(child, axis) && isPosDefined(child, leading[axis]) && isPosDefined(child, trailing[axis])) { child->layout.dimensions[dim[axis]] = fmaxf( - boundAxis(child, axis, node->layout.dimensions[dim[axis]] - - getBorderAxis(node, axis) - - getMarginAxis(child, axis) - - getPosition(child, leading[axis]) - - getPosition(child, trailing[axis]) - ), + node->layout.dimensions[dim[axis]] - + getPaddingAndBorderAxis(node, axis) - + getMarginAxis(child, axis) - + getPosition(child, leading[axis]) - + getPosition(child, trailing[axis]), // You never want to go smaller than padding getPaddingAndBorderAxis(child, axis) ); } } - for (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + for (int ii = 0; ii < 2; ii++) { + css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; if (isPosDefined(child, trailing[axis]) && !isPosDefined(child, leading[axis])) { child->layout.position[leading[axis]] = diff --git a/React/Layout/Layout.h b/React/Layout/Layout.h index fe383ea5728ed1..51f72493bb5b2f 100644 --- a/React/Layout/Layout.h +++ b/React/Layout/Layout.h @@ -1,15 +1,21 @@ /** + * @generated SignedSource<<58298c7a8815a8675e970b0347dedfed>> + * + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !! This file is a check-in from github! !! + * !! !! + * !! You should not modify this file directly. Instead: !! + * !! 1) Go to https://github.com/facebook/css-layout !! + * !! 2) Make a pull request and get it merged !! + * !! 3) Execute ./import.sh to pull in the latest version !! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * * Copyright (c) 2014, 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. - * - * WARNING: You should not modify this file directly. Instead: - * 1) Go to https://github.com/facebook/css-layout - * 2) Make a pull request and get it merged - * 3) Run import.sh to copy Layout.* to react-native-github */ #ifndef __LAYOUT_H @@ -107,8 +113,6 @@ typedef struct { float padding[4]; float border[4]; float dimensions[2]; - float minDimensions[2]; - float maxDimensions[2]; } css_style_t; typedef struct css_node { diff --git a/React/Layout/import.sh b/React/Layout/import.sh deleted file mode 100755 index 7e69403f3a44a3..00000000000000 --- a/React/Layout/import.sh +++ /dev/null @@ -1,15 +0,0 @@ -LAYOUT_C=`curl https://raw.githubusercontent.com/facebook/css-layout/master/src/Layout.c` -LAYOUT_H=`curl https://raw.githubusercontent.com/facebook/css-layout/master/src/Layout.h` - -REPLACE_STRING="* - * WARNING: You should not modify this file directly. Instead: - * 1) Go to https://github.com/facebook/css-layout - * 2) Make a pull request and get it merged - * 3) Run import.sh to copy Layout.* to react-native-github - */" - -LAYOUT_C=${LAYOUT_C/\*\//$REPLACE_STRING} -LAYOUT_H=${LAYOUT_H/\*\//$REPLACE_STRING} - -echo "$LAYOUT_C" > Layout.c -echo "$LAYOUT_H" > Layout.h diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index 2690de1dfea65e..3bdb035fc4eba8 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -59,6 +59,7 @@ - (dispatch_queue_t)methodQueue { NSString *title = args[@"title"]; NSString *message = args[@"message"]; + NSString *type = args[@"type"]; NSArray *buttons = args[@"buttons"]; if (!title && !message) { @@ -70,13 +71,20 @@ - (dispatch_queue_t)methodQueue } UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title - message:message + message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:nil]; NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count]; + if ([type isEqualToString:@"plain-text"]) { + alertView.alertViewStyle = UIAlertViewStylePlainTextInput; + [alertView textFieldAtIndex:0].text = message; + } else { + alertView.message = message; + } + NSInteger index = 0; for (NSDictionary *button in buttons) { if (button.count != 1) { @@ -108,7 +116,15 @@ - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)butto RCTResponseSenderBlock callback = _alertCallbacks[index]; NSArray *buttonKeys = _alertButtonKeys[index]; - callback(@[buttonKeys[buttonIndex]]); + NSArray *args; + + if (alertView.alertViewStyle == UIAlertViewStylePlainTextInput) { + args = @[buttonKeys[buttonIndex], [alertView textFieldAtIndex:0].text]; + } else { + args = @[buttonKeys[buttonIndex]]; + } + + callback(args); [_alerts removeObjectAtIndex:index]; [_alertCallbacks removeObjectAtIndex:index]; diff --git a/React/Modules/RCTExceptionsManager.h b/React/Modules/RCTExceptionsManager.h index 25e0fbefced4fc..79fd59eaca3eca 100644 --- a/React/Modules/RCTExceptionsManager.h +++ b/React/Modules/RCTExceptionsManager.h @@ -13,7 +13,9 @@ @protocol RCTExceptionsManagerDelegate -- (void)unhandledJSExceptionWithMessage:(NSString *)message stack:(NSArray *)stack; +- (void)handleSoftJSExceptionWithMessage:(NSString *)message stack:(NSArray *)stack; +- (void)handleFatalJSExceptionWithMessage:(NSString *)message stack:(NSArray *)stack; +- (void)updateJSExceptionWithMessage:(NSString *)message stack:(NSArray *)stack; @end diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index ddea1275acabd3..7512c540d6f5d5 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -36,11 +36,23 @@ - (instancetype)init return [self initWithDelegate:nil]; } -RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message +RCT_EXPORT_METHOD(reportSoftException:(NSString *)message + stack:(NSArray *)stack) +{ + // TODO(#7070533): report a soft error to the server + if (_delegate) { + [_delegate handleSoftJSExceptionWithMessage:message stack:stack]; + return; + } + + [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; +} + +RCT_EXPORT_METHOD(reportFatalException:(NSString *)message stack:(NSArray *)stack) { if (_delegate) { - [_delegate unhandledJSExceptionWithMessage:message stack:stack]; + [_delegate handleFatalJSExceptionWithMessage:message stack:stack]; return; } @@ -78,11 +90,17 @@ - (instancetype)init stack:(NSArray *)stack) { if (_delegate) { - [_delegate unhandledJSExceptionWithMessage:message stack:stack]; + [_delegate updateJSExceptionWithMessage:message stack:stack]; return; } [[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack]; } +// Deprecated. Use reportFatalException directly instead. +RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message + stack:(NSArray *)stack) +{ + [self reportFatalException:message stack:stack]; +} @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index df90ff1505c7bb..7d6925b120da67 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -281,7 +281,7 @@ - (void)invalidate dispatch_async(dispatch_get_main_queue(), ^{ for (NSNumber *rootViewTag in _rootViewTags) { - ((UIView *)_viewRegistry[rootViewTag]).userInteractionEnabled = NO; + [_viewRegistry[rootViewTag] invalidate]; } _rootViewTags = nil; diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 1373d8bd16bbbb..d4a2ad5e95a3cf 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -294,6 +294,12 @@ - (void)setStickyHeaderIndices:(NSIndexSet *)headerIndices _scrollView.stickyHeaderIndices = headerIndices; } +- (void)setClipsToBounds:(BOOL)clipsToBounds +{ + [super setClipsToBounds:clipsToBounds]; + [_scrollView setClipsToBounds:clipsToBounds]; +} + - (void)dealloc { _scrollView.delegate = nil; diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h index 1a4bcb40007e75..b8da37b15860ea 100644 --- a/React/Views/RCTView.h +++ b/React/Views/RCTView.h @@ -48,6 +48,15 @@ */ - (void)updateClippedSubviews; +/** + * Border radii. + */ +@property (nonatomic, assign) CGFloat borderRadius; +@property (nonatomic, assign) CGFloat borderTopLeftRadius; +@property (nonatomic, assign) CGFloat borderTopRightRadius; +@property (nonatomic, assign) CGFloat borderBottomLeftRadius; +@property (nonatomic, assign) CGFloat borderBottomRightRadius; + /** * Border colors. */ diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index c0786b5abfa863..c2cd04703d71fc 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -15,8 +15,6 @@ #import "RCTUtils.h" #import "UIView+React.h" -static void *RCTViewCornerRadiusKVOContext = &RCTViewCornerRadiusKVOContext; - static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event) { for (UIView *subview in [view.subviews reverseObjectEnumerator]) { @@ -123,30 +121,18 @@ - (instancetype)initWithFrame:(CGRect)frame _borderRightWidth = -1; _borderBottomWidth = -1; _borderLeftWidth = -1; + _borderTopLeftRadius = -1; + _borderTopRightRadius = -1; + _borderBottomLeftRadius = -1; + _borderBottomRightRadius = -1; _backgroundColor = [super backgroundColor]; [super setBackgroundColor:[UIColor clearColor]]; - - [self.layer addObserver:self forKeyPath:@"cornerRadius" options:0 context:RCTViewCornerRadiusKVOContext]; } return self; } -- (void)dealloc -{ - [self.layer removeObserver:self forKeyPath:@"cornerRadius" context:RCTViewCornerRadiusKVOContext]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if (context == RCTViewCornerRadiusKVOContext) { - [self.layer setNeedsDisplay]; - } else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - - (NSString *)accessibilityLabel { if (super.accessibilityLabel) { @@ -437,8 +423,12 @@ - (void)setBackgroundColor:(UIColor *)backgroundColor - (UIImage *)generateBorderImage:(out CGRect *)contentsCenter { - const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width) / 2.0; - const CGFloat radius = MAX(0, MIN(self.layer.cornerRadius, maxRadius)); + const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width); + const CGFloat radius = MAX(0, _borderRadius); + const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius); + const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius); + const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius); + const CGFloat bottomRightRadius = MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius); const CGFloat borderWidth = MAX(0, _borderWidth); const CGFloat topWidth = _borderTopWidth >= 0 ? _borderTopWidth : borderWidth; @@ -446,20 +436,26 @@ - (UIImage *)generateBorderImage:(out CGRect *)contentsCenter const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth; const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth; - const CGFloat topRadius = MAX(0, radius - topWidth); - const CGFloat rightRadius = MAX(0, radius - rightWidth); - const CGFloat bottomRadius = MAX(0, radius - bottomWidth); - const CGFloat leftRadius = MAX(0, radius - leftWidth); + const CGFloat innerTopLeftRadiusX = MAX(0, topLeftRadius - leftWidth); + const CGFloat innerTopLeftRadiusY = MAX(0, topLeftRadius - topWidth); + + const CGFloat innerTopRightRadiusX = MAX(0, topRightRadius - rightWidth); + const CGFloat innerTopRightRadiusY = MAX(0, topRightRadius - topWidth); - const UIEdgeInsets edgeInsets = UIEdgeInsetsMake(topWidth + topRadius, leftWidth + leftRadius, bottomWidth + bottomRadius, rightWidth + rightRadius); + const CGFloat innerBottomLeftRadiusX = MAX(0, bottomLeftRadius - leftWidth); + const CGFloat innerBottomLeftRadiusY = MAX(0, bottomLeftRadius - bottomWidth); + + const CGFloat innerBottomRightRadiusX = MAX(0, bottomRightRadius - rightWidth); + const CGFloat innerBottomRightRadiusY = MAX(0, bottomRightRadius - bottomWidth); + + const UIEdgeInsets edgeInsets = UIEdgeInsetsMake(topWidth + MAX(innerTopLeftRadiusY, innerTopRightRadiusY), leftWidth + MAX(innerTopLeftRadiusX, innerBottomLeftRadiusX), bottomWidth + MAX(innerBottomLeftRadiusY, innerBottomRightRadiusY), rightWidth + + MAX(innerBottomRightRadiusX, innerTopRightRadiusX)); const CGSize size = CGSizeMake(edgeInsets.left + 1 + edgeInsets.right, edgeInsets.top + 1 + edgeInsets.bottom); - UIScreen *screen = self.window.screen ?: [UIScreen mainScreen]; - UIGraphicsBeginImageContextWithOptions(size, NO, screen.scale * 2); + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); CGContextRef ctx = UIGraphicsGetCurrentContext(); const CGRect rect = {CGPointZero, size}; - CGPathRef path = CGPathCreateWithRoundedRect(rect, radius, radius, NULL); + CGPathRef path = RCTPathCreateWithRoundedRect(rect, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL); if (_backgroundColor) { CGContextSaveGState(ctx); @@ -474,10 +470,11 @@ - (UIImage *)generateBorderImage:(out CGRect *)contentsCenter CGContextAddPath(ctx, path); CGPathRelease(path); - if (radius > 0 && topWidth > 0 && rightWidth > 0 && bottomWidth > 0 && leftWidth > 0) { + BOOL hasRadius = topLeftRadius > 0 || topRightRadius > 0 || bottomLeftRadius > 0 || bottomRightRadius > 0; + if (hasRadius && topWidth > 0 && rightWidth > 0 && bottomWidth > 0 && leftWidth > 0) { const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth); const CGRect insetRect = UIEdgeInsetsInsetRect(rect, insetEdgeInsets); - CGPathRef insetPath = RCTPathCreateWithRoundedRect(insetRect, leftRadius, topRadius, rightRadius, topRadius, leftRadius, bottomRadius, rightRadius, bottomRadius, NULL); + CGPathRef insetPath = RCTPathCreateWithRoundedRect(insetRect, innerTopLeftRadiusX, innerTopLeftRadiusY, innerTopRightRadiusX, innerTopRightRadiusY, innerBottomLeftRadiusX, innerBottomLeftRadiusY, innerBottomRightRadiusX, innerBottomRightRadiusY, NULL); CGContextAddPath(ctx, insetPath); CGPathRelease(insetPath); } @@ -486,12 +483,12 @@ - (UIImage *)generateBorderImage:(out CGRect *)contentsCenter BOOL hasEqualColor = !_borderTopColor && !_borderRightColor && !_borderBottomColor && !_borderLeftColor; BOOL hasEqualBorder = _borderWidth >= 0 && _borderTopWidth < 0 && _borderRightWidth < 0 && _borderBottomWidth < 0 && _borderLeftWidth < 0; - if (radius <= 0 && hasEqualBorder && hasEqualColor) { + if (!hasRadius && hasEqualBorder && hasEqualColor) { CGContextSetStrokeColorWithColor(ctx, _borderColor); CGContextSetLineWidth(ctx, 2 * _borderWidth); CGContextClipToRect(ctx, rect); CGContextStrokeRect(ctx, rect); - } else if (radius <= 0 && hasEqualColor) { + } else if (!hasRadius && hasEqualColor) { CGContextSetFillColorWithColor(ctx, _borderColor); CGContextAddRect(ctx, rect); const CGRect insetRect = UIEdgeInsetsInsetRect(rect, edgeInsets); @@ -500,9 +497,9 @@ - (UIImage *)generateBorderImage:(out CGRect *)contentsCenter } else { BOOL didSet = NO; CGPoint topLeft; - if (topRadius > 0 && leftRadius > 0) { + if (innerTopLeftRadiusX > 0 && innerTopLeftRadiusY > 0) { CGPoint points[2]; - RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, topWidth, 2 * leftRadius, 2 * topRadius), CGPointMake(0, 0), CGPointMake(leftWidth, topWidth), points); + RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, topWidth, 2 * innerTopLeftRadiusX, 2 * innerTopLeftRadiusY), CGPointMake(0, 0), CGPointMake(leftWidth, topWidth), points); if (!isnan(points[1].x) && !isnan(points[1].y)) { topLeft = points[1]; didSet = YES; @@ -515,9 +512,9 @@ - (UIImage *)generateBorderImage:(out CGRect *)contentsCenter didSet = NO; CGPoint bottomLeft; - if (bottomRadius > 0 && leftRadius > 0) { + if (innerBottomLeftRadiusX > 0 && innerBottomLeftRadiusY > 0) { CGPoint points[2]; - RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, (size.height - bottomWidth) - 2 * bottomRadius, 2 * leftRadius, 2 * bottomRadius), CGPointMake(0, size.height), CGPointMake(leftWidth, size.height - bottomWidth), points); + RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, (size.height - bottomWidth) - 2 * innerBottomLeftRadiusY, 2 * innerBottomLeftRadiusX, 2 * innerBottomLeftRadiusY), CGPointMake(0, size.height), CGPointMake(leftWidth, size.height - bottomWidth), points); if (!isnan(points[1].x) && !isnan(points[1].y)) { bottomLeft = points[1]; didSet = YES; @@ -530,9 +527,9 @@ - (UIImage *)generateBorderImage:(out CGRect *)contentsCenter didSet = NO; CGPoint topRight; - if (topRadius > 0 && rightRadius > 0) { + if (innerTopRightRadiusX > 0 && innerTopRightRadiusY > 0) { CGPoint points[2]; - RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * rightRadius, topWidth, 2 * rightRadius, 2 * topRadius), CGPointMake(size.width, 0), CGPointMake(size.width - rightWidth, topWidth), points); + RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * innerTopRightRadiusX, topWidth, 2 * innerTopRightRadiusX, 2 * innerTopRightRadiusY), CGPointMake(size.width, 0), CGPointMake(size.width - rightWidth, topWidth), points); if (!isnan(points[0].x) && !isnan(points[0].y)) { topRight = points[0]; didSet = YES; @@ -545,9 +542,9 @@ - (UIImage *)generateBorderImage:(out CGRect *)contentsCenter didSet = NO; CGPoint bottomRight; - if (bottomRadius > 0 && rightRadius > 0) { + if (innerBottomRightRadiusX > 0 && innerBottomRightRadiusY > 0) { CGPoint points[2]; - RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * rightRadius, (size.height - bottomWidth) - 2 * bottomRadius, 2 * rightRadius, 2 * bottomRadius), CGPointMake(size.width, size.height), CGPointMake(size.width - rightWidth, size.height - bottomWidth), points); + RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * innerBottomRightRadiusX, (size.height - bottomWidth) - 2 * innerBottomRightRadiusY, 2 * innerBottomRightRadiusX, 2 * innerBottomRightRadiusY), CGPointMake(size.width, size.height), CGPointMake(size.width - rightWidth, size.height - bottomWidth), points); if (!isnan(points[0].x) && !isnan(points[0].y)) { bottomRight = points[0]; didSet = YES; @@ -695,6 +692,22 @@ - (void)setBorder##side##Width:(CGFloat)border##side##Width \ setBorderWidth(Bottom) setBorderWidth(Left) +#define setBorderRadius(side) \ + - (void)setBorder##side##Radius:(CGFloat)border##side##Radius \ + { \ + if (_border##side##Radius == border##side##Radius) { \ + return; \ + } \ + _border##side##Radius = border##side##Radius; \ + [self.layer setNeedsDisplay]; \ + } + +setBorderRadius() +setBorderRadius(TopLeft) +setBorderRadius(TopRight) +setBorderRadius(BottomLeft) +setBorderRadius(BottomRight) + @end static void RCTPathAddEllipticArc(CGMutablePathRef path, const CGAffineTransform *m, CGFloat x, CGFloat y, CGFloat xRadius, CGFloat yRadius, CGFloat startAngle, CGFloat endAngle, bool clockwise) diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 4dfb296fda35de..cb42c7ec51b19d 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -113,7 +113,23 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *) view.removeClippedSubviews = json ? [RCTConvert BOOL:json] : defaultView.removeClippedSubviews; } } -RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius, CGFloat) +RCT_CUSTOM_VIEW_PROPERTY(borderRadius, CGFloat, RCTView) { + if ([view respondsToSelector:@selector(setBorderRadius:)]) { + if (json) { + view.borderRadius = [RCTConvert CGFloat:json]; + } else if ([view respondsToSelector:@selector(borderRadius)]) { + view.borderRadius = [defaultView borderRadius]; + } else { + view.borderRadius = defaultView.layer.cornerRadius; + } + } else { + if (json) { + view.layer.cornerRadius = [RCTConvert CGFloat:json]; + } else { + view.layer.cornerRadius = defaultView.layer.cornerRadius; + } + } +} RCT_CUSTOM_VIEW_PROPERTY(borderColor, CGColor, RCTView) { if ([view respondsToSelector:@selector(setBorderColor:)]) { @@ -150,6 +166,19 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *) RCT_VIEW_BORDER_PROPERTY(Bottom) RCT_VIEW_BORDER_PROPERTY(Left) +#define RCT_VIEW_BORDER_RADIUS_PROPERTY(SIDE) \ +RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Radius, CGFloat, RCTView) \ +{ \ + if ([view respondsToSelector:@selector(setBorder##SIDE##Radius:)]) { \ + view.border##SIDE##Radius = json ? [RCTConvert CGFloat:json] : defaultView.border##SIDE##Radius; \ + } \ +} \ + +RCT_VIEW_BORDER_RADIUS_PROPERTY(TopLeft) +RCT_VIEW_BORDER_RADIUS_PROPERTY(TopRight) +RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomLeft) +RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomRight) + #pragma mark - ShadowView properties RCT_EXPORT_SHADOW_PROPERTY(top, CGFloat); diff --git a/package.json b/package.json index 4b76d688abfa21..0421c96a850a51 100644 --- a/package.json +++ b/package.json @@ -63,12 +63,12 @@ "stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638", "uglify-js": "~2.4.16", "underscore": "1.7.0", - "worker-farm": "1.1.0", + "worker-farm": "^1.3.0", "ws": "0.4.31", "yargs": "1.3.2" }, "devDependencies": { - "jest-cli": "0.2.1", + "jest-cli": "0.4.3", "eslint": "0.9.2" } } diff --git a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js index ba804b5f19e004..43f2234534955c 100644 --- a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -1,10 +1,13 @@ 'use strict'; jest - .dontMock('path') .dontMock('../../lib/getAssetDataFromName') .dontMock('../'); +jest + .mock('crypto') + .mock('fs'); + var Promise = require('bluebird'); describe('AssetServer', function() { diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 9cb08122c23fcb..f75445d099a33b 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,13 +10,14 @@ jest .dontMock('../index') - .dontMock('path') .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') .dontMock('../../../../lib/getAssetDataFromName') .setMock('../../../ModuleDescriptor', function(data) {return data;}); +jest.mock('fs'); + describe('DependencyGraph', function() { var DependencyGraph; var fileWatcher; diff --git a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index b3063cb57c3897..9bc8b8b95c3766 100644 --- a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -13,6 +13,8 @@ jest.dontMock('../') .dontMock('../replacePatterns') .setMock('../../ModuleDescriptor', function(data) {return data;}); +jest.mock('path'); + var Promise = require('bluebird'); describe('HasteDependencyResolver', function() { diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/packager/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js index 3816617f2210be..d4aa41225d8435 100644 --- a/packager/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js +++ b/packager/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js @@ -22,14 +22,17 @@ ErrorUtils._globalHandler = fun; }, reportError: function(error) { - Error._globalHandler && ErrorUtils._globalHandler(error); + ErrorUtils._globalHandler && ErrorUtils._globalHandler(error); + }, + reportFatalError: function(error) { + ErrorUtils._globalHandler && ErrorUtils._globalHandler(error, true); }, applyWithGuard: function(fun, context, args) { try { ErrorUtils._inGuard++; return fun.apply(context, args); } catch (e) { - ErrorUtils._globalHandler && ErrorUtils._globalHandler(e); + ErrorUtils.reportError(e); } finally { ErrorUtils._inGuard--; } diff --git a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js index 51f7ec05ec9d9a..7ad658183345d8 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -10,11 +10,13 @@ jest .dontMock('underscore') - .dontMock('path') .dontMock('absolute-path') - .dontMock('crypto') .dontMock('../Cache'); +jest + .mock('os') + .mock('fs'); + var Promise = require('bluebird'); describe('JSTransformer Cache', function() { diff --git a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js index eb307a02eb558a..5948916a7353d8 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -10,10 +10,11 @@ jest .dontMock('worker-farm') - .dontMock('os') .dontMock('../../lib/ModuleTransport') .dontMock('../index'); +jest.mock('fs'); + var OPTIONS = { transformModulePath: '/foo/bar' }; diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index d4a1c0f36e3ac4..9babda547e7ad6 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -10,13 +10,13 @@ jest .setMock('worker-farm', function() { return function() {};}) - .dontMock('path') - .dontMock('os') .dontMock('underscore') .dontMock('../../lib/ModuleTransport') .setMock('uglify-js') .dontMock('../'); +jest.mock('fs'); + var Promise = require('bluebird'); describe('Packager', function() { diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 79022b21181166..d2c15717785e7c 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -349,7 +349,7 @@ function getOptionsFromUrl(reqUrl) { // node v0.11.14 bug see https://github.com/facebook/react-native/issues/218 urlObj.query = urlObj.query || {}; - var pathname = urlObj.pathname; + var pathname = decodeURIComponent(urlObj.pathname); // Backwards compatibility. Options used to be as added as '.' to the // entry module name. We can safely remove these options.