From c7efc4dd11a26cfb2a546da90fe8ad5b808b34b7 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 25 Mar 2015 18:19:28 -0700 Subject: [PATCH] Updates from Wed 25 Mar - [RFC][ReactNative] Integrate dev menu directly into RootView | Alex Kotliarskyi - flowify Libraries/ReactIOS | Marshall Roch - [WIP] Added support for italics and additional font weights | Nick Lockwood - [ReactNative] Improve View documentation | Christopher Chedeau - [react-packager] Readme | Amjad Masad - Fix for incorrect contentSize reported by RCTScrollView | Nick Lockwood - [ReactNative] Flow and doc formatting for NetInfo | Eric Vicenti - [ReactNative] Document AppStateIOS | Eric Vicenti --- .flowconfig | 3 +- Examples/2048/Game2048.js | 4 +- Examples/Movies/MovieCell.js | 2 +- Examples/Movies/MovieScreen.js | 8 +- Examples/UIExplorer/ActionSheetIOSExample.js | 2 +- Examples/UIExplorer/AdSupportIOSExample.js | 2 +- Examples/UIExplorer/DatePickerExample.js | 4 +- Examples/UIExplorer/GeolocationExample.js | 2 +- Examples/UIExplorer/NavigatorIOSExample.js | 2 +- .../ReactNavigator/BreadcrumbNavSample.js | 2 +- .../ReactNavigator/NavigationBarSample.js | 2 +- .../NestedBreadcrumbNavSample.js | 2 +- Examples/UIExplorer/SliderIOSExample.js | 2 +- Examples/UIExplorer/TextExample.ios.js | 38 +++- Examples/UIExplorer/TouchableExample.js | 2 +- Examples/UIExplorer/UIExplorer/AppDelegate.m | 3 +- Examples/UIExplorer/UIExplorerBlock.js | 2 +- Examples/UIExplorer/UIExplorerList.js | 4 +- Examples/UIExplorer/UIExplorerTitle.js | 2 +- Examples/UIExplorer/WebViewExample.js | 2 +- IntegrationTests/IntegrationTestsApp.js | 2 +- Libraries/AppStateIOS/AppStateIOS.ios.js | 78 ++++++- Libraries/Components/View/View.js | 61 ++--- Libraries/Network/NetInfo.js | 92 +++++--- .../ReactIOS/IOSDefaultEventPluginOrder.js | 1 + .../ReactIOS/IOSNativeBridgeEventPlugin.js | 10 +- Libraries/ReactIOS/NativeMethodsMixin.js | 49 ++++- Libraries/ReactIOS/React.js | 1 + Libraries/ReactIOS/ReactIOS.js | 6 +- .../ReactIOS/ReactIOSComponentEnvironment.js | 1 + Libraries/ReactIOS/ReactIOSComponentMixin.js | 1 + Libraries/ReactIOS/ReactIOSDOMIDOperations.js | 2 +- .../ReactIOS/ReactIOSDefaultInjection.js | 1 + .../createReactIOSNativeComponentClass.js | 11 +- Libraries/ReactIOS/diffRawProperties.js | 100 +++++---- Libraries/Text/RCTShadowText.h | 1 + Libraries/Text/RCTShadowText.m | 13 +- .../react-native/react-native-interface.js | 4 + ReactKit/Base/RCTConvert.h | 8 +- ReactKit/Base/RCTConvert.m | 208 ++++++++++++------ ...velopmentViewController.h => RCTDevMenu.h} | 7 +- ReactKit/Base/RCTDevMenu.m | 84 +++++++ ReactKit/Base/RCTDevelopmentViewController.m | 89 -------- ReactKit/Base/RCTRootView.h | 6 + ReactKit/Base/RCTRootView.m | 20 ++ ReactKit/Base/RCTUtils.m | 10 +- ReactKit/ReactKit.xcodeproj/project.pbxproj | 12 +- ReactKit/Views/RCTScrollView.h | 14 +- ReactKit/Views/RCTScrollView.m | 51 ++--- ReactKit/Views/RCTTextFieldManager.m | 6 +- packager/README.md | 138 ++++++++++++ 51 files changed, 802 insertions(+), 375 deletions(-) rename ReactKit/Base/{RCTDevelopmentViewController.h => RCTDevMenu.h} (72%) create mode 100644 ReactKit/Base/RCTDevMenu.m delete mode 100644 ReactKit/Base/RCTDevelopmentViewController.m create mode 100644 packager/README.md diff --git a/.flowconfig b/.flowconfig index 28625456d..c4265b9b2 100644 --- a/.flowconfig +++ b/.flowconfig @@ -10,7 +10,8 @@ # Ignore react-tools where there are overlaps, but don't ignore anything that # react-native relies on .*/node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js -.*/node_modules/react-tools/src/browser/.* +.*/node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js +.*/node_modules/react-tools/src/browser/ui/React.js .*/node_modules/react-tools/src/core/ReactInstanceHandles.js .*/node_modules/react-tools/src/event/EventPropagators.js diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js index 9ffaf42ba..009ba34a7 100644 --- a/Examples/2048/Game2048.js +++ b/Examples/2048/Game2048.js @@ -233,7 +233,7 @@ var styles = StyleSheet.create({ tryAgainText: { color: '#ffffff', fontSize: 20, - fontWeight: 'bold', + fontWeight: '500', }, cell: { width: CELL_SIZE, @@ -259,7 +259,7 @@ var styles = StyleSheet.create({ fontSize: 24, color: '#776666', fontFamily: 'Verdana', - fontWeight: 'bold', + fontWeight: '500', }, tile2: { backgroundColor: '#eeeeee', diff --git a/Examples/Movies/MovieCell.js b/Examples/Movies/MovieCell.js index 21c90ec24..42781ec24 100644 --- a/Examples/Movies/MovieCell.js +++ b/Examples/Movies/MovieCell.js @@ -62,7 +62,7 @@ var styles = StyleSheet.create({ movieTitle: { flex: 1, fontSize: 16, - fontWeight: 'bold', + fontWeight: '500', marginBottom: 2, }, movieYear: { diff --git a/Examples/Movies/MovieScreen.js b/Examples/Movies/MovieScreen.js index 980d51065..7c3ed316b 100644 --- a/Examples/Movies/MovieScreen.js +++ b/Examples/Movies/MovieScreen.js @@ -109,7 +109,7 @@ var styles = StyleSheet.create({ movieTitle: { flex: 1, fontSize: 16, - fontWeight: 'bold', + fontWeight: '500', }, rating: { marginTop: 10, @@ -119,7 +119,7 @@ var styles = StyleSheet.create({ }, ratingValue: { fontSize: 28, - fontWeight: 'bold', + fontWeight: '500', }, mpaaWrapper: { alignSelf: 'flex-start', @@ -131,7 +131,7 @@ var styles = StyleSheet.create({ mpaaText: { fontFamily: 'Palatino', fontSize: 13, - fontWeight: 'bold', + fontWeight: '500', }, mainSection: { flexDirection: 'row', @@ -148,7 +148,7 @@ var styles = StyleSheet.create({ marginVertical: 10, }, castTitle: { - fontWeight: 'bold', + fontWeight: '500', marginBottom: 3, }, castActor: { diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js index b6425c507..00e6830af 100644 --- a/Examples/UIExplorer/ActionSheetIOSExample.js +++ b/Examples/UIExplorer/ActionSheetIOSExample.js @@ -101,7 +101,7 @@ var ShareActionSheetExample = React.createClass({ var style = StyleSheet.create({ button: { marginBottom: 10, - fontWeight: 'bold', + fontWeight: '500', } }); diff --git a/Examples/UIExplorer/AdSupportIOSExample.js b/Examples/UIExplorer/AdSupportIOSExample.js index 3c5ada96c..952cb7c8f 100644 --- a/Examples/UIExplorer/AdSupportIOSExample.js +++ b/Examples/UIExplorer/AdSupportIOSExample.js @@ -94,6 +94,6 @@ var AdSupportIOSExample = React.createClass({ var styles = StyleSheet.create({ title: { - fontWeight: 'bold', + fontWeight: '500', }, }); diff --git a/Examples/UIExplorer/DatePickerExample.js b/Examples/UIExplorer/DatePickerExample.js index 35fa869f3..cf4fa83d9 100644 --- a/Examples/UIExplorer/DatePickerExample.js +++ b/Examples/UIExplorer/DatePickerExample.js @@ -149,14 +149,14 @@ var styles = StyleSheet.create({ paddingVertical: 2, }, label: { - fontWeight: 'bold', + fontWeight: '500', }, headingContainer: { padding: 4, backgroundColor: '#f6f7f8', }, heading: { - fontWeight: 'bold', + fontWeight: '500', fontSize: 14, }, }); diff --git a/Examples/UIExplorer/GeolocationExample.js b/Examples/UIExplorer/GeolocationExample.js index 82f1c83eb..5d49f341e 100644 --- a/Examples/UIExplorer/GeolocationExample.js +++ b/Examples/UIExplorer/GeolocationExample.js @@ -74,6 +74,6 @@ var GeolocationExample = React.createClass({ var styles = StyleSheet.create({ title: { - fontWeight: 'bold', + fontWeight: '500', }, }); diff --git a/Examples/UIExplorer/NavigatorIOSExample.js b/Examples/UIExplorer/NavigatorIOSExample.js index 56bb6ab78..fd05caf14 100644 --- a/Examples/UIExplorer/NavigatorIOSExample.js +++ b/Examples/UIExplorer/NavigatorIOSExample.js @@ -214,7 +214,7 @@ var styles = StyleSheet.create({ }, rowText: { fontSize: 17, - fontWeight: 'bold', + fontWeight: '500', }, }); diff --git a/Examples/UIExplorer/ReactNavigator/BreadcrumbNavSample.js b/Examples/UIExplorer/ReactNavigator/BreadcrumbNavSample.js index 451da6656..17be12f8d 100644 --- a/Examples/UIExplorer/ReactNavigator/BreadcrumbNavSample.js +++ b/Examples/UIExplorer/ReactNavigator/BreadcrumbNavSample.js @@ -254,7 +254,7 @@ var styles = StyleSheet.create({ fontSize: 18, color: '#666666', textAlign: 'center', - fontWeight: 'bold', + fontWeight: '500', lineHeight: 32, }, filterText: { diff --git a/Examples/UIExplorer/ReactNavigator/NavigationBarSample.js b/Examples/UIExplorer/ReactNavigator/NavigationBarSample.js index ddb225552..5096cab71 100644 --- a/Examples/UIExplorer/ReactNavigator/NavigationBarSample.js +++ b/Examples/UIExplorer/ReactNavigator/NavigationBarSample.js @@ -107,7 +107,7 @@ var styles = StyleSheet.create({ }, navBarTitleText: { color: cssVar('fbui-bluegray-60'), - fontWeight: 'bold', + fontWeight: '500', marginVertical: 9, }, navBarButtonText: { diff --git a/Examples/UIExplorer/ReactNavigator/NestedBreadcrumbNavSample.js b/Examples/UIExplorer/ReactNavigator/NestedBreadcrumbNavSample.js index 263d8b738..20d857472 100644 --- a/Examples/UIExplorer/ReactNavigator/NestedBreadcrumbNavSample.js +++ b/Examples/UIExplorer/ReactNavigator/NestedBreadcrumbNavSample.js @@ -176,7 +176,7 @@ var styles = StyleSheet.create({ fontSize: 18, color: '#666666', textAlign: 'center', - fontWeight: 'bold', + fontWeight: '500', lineHeight: 32, }, filterText: { diff --git a/Examples/UIExplorer/SliderIOSExample.js b/Examples/UIExplorer/SliderIOSExample.js index ae6aca144..13eee593a 100644 --- a/Examples/UIExplorer/SliderIOSExample.js +++ b/Examples/UIExplorer/SliderIOSExample.js @@ -47,7 +47,7 @@ var styles = StyleSheet.create({ text: { fontSize: 14, textAlign: 'center', - fontWeight: 'bold', + fontWeight: '500', margin: 10, }, }); diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js index e29b07da4..e88bc4a0c 100644 --- a/Examples/UIExplorer/TextExample.ios.js +++ b/Examples/UIExplorer/TextExample.ios.js @@ -29,7 +29,7 @@ var Entity = React.createClass({ var AttributeToggler = React.createClass({ getInitialState: function() { - return {fontWeight: 'bold', fontSize: 15}; + return {fontWeight: '500', fontSize: 15}; }, increaseSize: function() { this.setState({ @@ -129,9 +129,37 @@ exports.examples = [ title: 'Font Weight', render: function() { return ( - - Move fast and be bold - + + + Move fast and be ultralight + + + Move fast and be light + + + Move fast and be normal + + + Move fast and be bold + + + Move fast and be ultrabold + + + ); + }, +}, { + title: 'Font Style', + render: function() { + return ( + + + Normal text + + + Italic text + + ); }, }, { @@ -279,7 +307,7 @@ var styles = StyleSheet.create({ backgroundColor: 'rgba(100, 100, 100, 0.3)' }, entity: { - fontWeight: 'bold', + fontWeight: '500', color: '#527fe4', }, }); diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js index 35d86aaf8..6b3c611ed 100644 --- a/Examples/UIExplorer/TouchableExample.js +++ b/Examples/UIExplorer/TouchableExample.js @@ -187,7 +187,7 @@ var styles = StyleSheet.create({ backgroundColor: '#f9f9f9', }, textBlock: { - fontWeight: 'bold', + fontWeight: '500', color: 'blue', }, }); diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 9b6bc835f..c25414370 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -9,7 +9,6 @@ #import "AppDelegate.h" -#import "RCTDevelopmentViewController.h" #import "RCTRootView.h" @implementation AppDelegate @@ -42,7 +41,7 @@ rootView.moduleName = @"UIExplorerApp"; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [[RCTDevelopmentViewController alloc] init]; + UIViewController *rootViewController = [[UIViewController alloc] init]; rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; diff --git a/Examples/UIExplorer/UIExplorerBlock.js b/Examples/UIExplorer/UIExplorerBlock.js index 66222fd05..7d2de002f 100644 --- a/Examples/UIExplorer/UIExplorerBlock.js +++ b/Examples/UIExplorer/UIExplorerBlock.js @@ -76,7 +76,7 @@ var styles = StyleSheet.create({ }, titleText: { fontSize: 14, - fontWeight: 'bold', + fontWeight: '500', }, descriptionText: { fontSize: 14, diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index 73f016cdd..a13da34dc 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -204,7 +204,7 @@ var styles = StyleSheet.create({ backgroundColor: 'white', }, sectionHeaderTitle: { - fontWeight: 'bold', + fontWeight: '500', fontSize: 11, }, row: { @@ -220,7 +220,7 @@ var styles = StyleSheet.create({ }, rowTitleText: { fontSize: 17, - fontWeight: 'bold', + fontWeight: '500', }, rowDetailText: { fontSize: 15, diff --git a/Examples/UIExplorer/UIExplorerTitle.js b/Examples/UIExplorer/UIExplorerTitle.js index 2016ee085..7a8895413 100644 --- a/Examples/UIExplorer/UIExplorerTitle.js +++ b/Examples/UIExplorer/UIExplorerTitle.js @@ -42,7 +42,7 @@ var styles = StyleSheet.create({ }, text: { fontSize: 19, - fontWeight: 'bold', + fontWeight: '500', }, }); diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js index 3dcf7c8b7..a471a7807 100644 --- a/Examples/UIExplorer/WebViewExample.js +++ b/Examples/UIExplorer/WebViewExample.js @@ -239,7 +239,7 @@ var styles = StyleSheet.create({ }, errorTextTitle: { fontSize: 15, - fontWeight: 'bold', + fontWeight: '500', marginBottom: 10, }, errorText: { diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js index de1703528..dbb5dde83 100644 --- a/IntegrationTests/IntegrationTestsApp.js +++ b/IntegrationTests/IntegrationTestsApp.js @@ -81,7 +81,7 @@ var styles = StyleSheet.create({ padding: 10, }, testName: { - fontWeight: 'bold', + fontWeight: '500', }, separator: { height: 1, diff --git a/Libraries/AppStateIOS/AppStateIOS.ios.js b/Libraries/AppStateIOS/AppStateIOS.ios.js index af6893534..48db8415b 100644 --- a/Libraries/AppStateIOS/AppStateIOS.ios.js +++ b/Libraries/AppStateIOS/AppStateIOS.ios.js @@ -21,28 +21,92 @@ var DEVICE_APPSTATE_EVENT = 'appStateDidChange'; var _appStateHandlers = {}; -class AppStateIOS { +/** + * `AppStateIOS` can tell you if the app is in the foreground or background, + * and notify you when the state changes. + * + * AppStateIOS is frequently used to determine the intent and proper behavior when + * handling push notifications. + * + * ### iOS App States + * + * - `active` - The app is running in the foreground + * - `background` - The app is running in the background. The user is either + * in another app or on the home screen + * - `inactive` - This is a transition state that currently never happens for + * typical React Native apps. + * + * For more information, see + * [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html) + * + * ### Basic Usage + * + * To see the current state, you can check `AppStateIOS.currentState`, which + * will be kept up-to-date. However, `currentState` will be null at launch + * while `AppStateIOS` retrieves it over the bridge. + * + * ``` + * getInitialState: function() { + * return { + * currentAppState: AppStateIOS.currentState, + * }; + * }, + * componentDidMount: function() { + * AppStateIOS.addEventListener('change', this._handleAppStateChange); + * }, + * componentWillUnmount: function() { + * AppStateIOS.removeEventListener('change', this._handleAppStateChange); + * }, + * _handleAppStateChange: function(currentAppState) { + * this.setState({ currentAppState, }); + * }, + * render: function() { + * return ( + * Current state is: {this.state.currentAppState} + * ); + * }, + * ``` + * + * This example will only ever appear to say "Current state is: active" because + * the app is only visible to the user when in the `active` state, and the null + * state will happen only momentarily. + */ - static addEventListener(type, handler) { +var AppStateIOS = { + + /** + * Add a handler to AppState changes by listening to the `change` event type + * and providing the handler + */ + addEventListener: function( + type: string, + handler: Function + ) { _appStateHandlers[handler] = RCTDeviceEventEmitter.addListener( DEVICE_APPSTATE_EVENT, (appStateData) => { handler(appStateData.app_state); } ); - } + }, - static removeEventListener(type, handler) { + /** + * Remove a handler by passing the `change` event type and the handler + */ + removeEventListener: function( + type: string, + handler: Function + ) { if (!_appStateHandlers[handler]) { return; } _appStateHandlers[handler].remove(); _appStateHandlers[handler] = null; - } + }, -} + currentState: (null : ?String), -AppStateIOS.currentState = null; +}; RCTDeviceEventEmitter.addListener( DEVICE_APPSTATE_EVENT, diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 882adf420..e34c13883 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -7,7 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule View - * @flow */ 'use strict'; @@ -40,18 +39,8 @@ var stylePropType = StyleSheetPropType(ViewStylePropTypes); * * ``` * - * By default, `View`s have a primary flex direction of 'column', so children - * will stack up vertically by default. `View`s also expand to fill the parent - * in the direction of the parent's flex direction by default, so in the case of - * a default parent (flexDirection: 'column'), the children will fill the width, - * but not the height. - * - * Many library components can be treated like plain `Views` in many cases, for - * example passing them children, setting style, etc. - * * `View`s are designed to be used with `StyleSheet`s for clarity and - * performance, although inline styles are also supported. It is common for - * `StyleSheet`s to be combined dynamically. See `StyleSheet.js` for more info. + * performance, although inline styles are also supported. */ var View = React.createClass({ mixins: [NativeMethodsMixin], @@ -67,10 +56,18 @@ var View = React.createClass({ propTypes: { /** - * When true, indicates that the view is an accessibility element + * When true, indicates that the view is an accessibility element. By default, + * all the touchable elements are accessible. */ accessible: PropTypes.bool, + /** + * Overrides the text that's read by the screen reader when the user interacts + * with the element. By default, the label is constructed by traversing all the + * children and accumulating all the Text nodes separated by space. + */ + accessibilityLabel: PropTypes.string, + /** * Used to locate this view in end-to-end tests. */ @@ -78,16 +75,16 @@ var View = React.createClass({ /** * For most touch interactions, you'll simply want to wrap your component in - * `TouchableHighlight.js`. Check out `Touchable.js` and - * `ScrollResponder.js` for more discussion. + * `TouchableHighlight` or `TouchableOpacity`. Check out `Touchable.js`, + * `ScrollResponder.js` and `ResponderEventPlugin.js` for more discussion. */ + onMoveShouldSetResponder: PropTypes.func, onResponderGrant: PropTypes.func, - onResponderReject: PropTypes.func, onResponderMove: PropTypes.func, + onResponderReject: PropTypes.func, onResponderRelease: PropTypes.func, onResponderTerminate: PropTypes.func, onResponderTerminationRequest: PropTypes.func, - onMoveShouldSetResponder: PropTypes.func, onStartShouldSetResponder: PropTypes.func, onStartShouldSetResponderCapture: PropTypes.func, @@ -95,12 +92,25 @@ var View = React.createClass({ * In the absence of `auto` property, `none` is much like `CSS`'s `none` * value. `box-none` is as if you had applied the `CSS` class: * - * .cantTouchThis * { - * pointer-events: auto; - * } - * .cantTouchThis { - * pointer-events: none; - * } + * ``` + * .box-none { + * pointer-events: none; + * } + * .box-none * { + * pointer-events: all; + * } + * ``` + * + * `box-only` is the equivalent of + * + * ``` + * .box-only { + * pointer-events: all; + * } + * .box-only * { + * pointer-events: none; + * } + * ``` * * But since `pointerEvents` does not affect layout/appearance, and we are * already deviating from the spec by adding additional modes, we opt to not @@ -114,11 +124,6 @@ var View = React.createClass({ 'box-only', 'auto', ]), - - /** - * Used to style and layout the `View`. See `StyleSheet.js` and - * `ViewStylePropTypes.js` for more info. - */ style: stylePropType, /** diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js index 3b15cf541..d98b997ca 100644 --- a/Libraries/Network/NetInfo.js +++ b/Libraries/Network/NetInfo.js @@ -21,18 +21,25 @@ type ChangeEventName = $Enum<{ change: string; }>; +type ReachabilityStateIOS = $Enum<{ + cell: string; + none: string; + unknown: string; + wifi: string; +}>; + /** * NetInfo exposes info about online/offline status * - * == iOS Reachability + * ### reachabilityIOS * * Asyncronously determine if the device is online and on a cellular network. * - * - "none" - device is offline - * - "wifi" - device is online and connected via wifi, or is the iOS simulator - * - "cell" - device is connected via Edge, 3G, WiMax, or LTE - * - "unknown" - error case and the network status is unknown + * - `none` - device is offline + * - `wifi` - device is online and connected via wifi, or is the iOS simulator + * - `cell` - device is connected via Edge, 3G, WiMax, or LTE + * - `unknown` - error case and the network status is unknown * * ``` * NetInfo.reachabilityIOS.fetch().done((reach) => { @@ -50,11 +57,37 @@ type ChangeEventName = $Enum<{ * handleFirstReachabilityChange * ); * ``` + * + * ### isConnected + * + * Available on all platforms. Asyncronously fetch a boolean to determine + * internet connectivity. + * + * ``` + * NetInfo.isConnected.fetch().done((isConnected) => { + * console.log('First, is ' + (isConnected ? 'online' : 'offline')); + * }); + * function handleFirstConnectivityChange(isConnected) { + * console.log('Then, is ' + (isConnected ? 'online' : 'offline')); + * NetInfo.isConnected.removeEventListener( + * 'change', + * handleFirstConnectivityChange + * ); + * } + * NetInfo.isConnected.addEventListener( + * 'change', + * handleFirstConnectivityChange + * ); + * ``` */ var NetInfo = {}; if (RCTReachability) { + + // RCTReachability is exposed, so this is an iOS-like environment and we will + // expose reachabilityIOS + var _reachabilitySubscriptions = {}; NetInfo.reachabilityIOS = { @@ -84,7 +117,7 @@ if (RCTReachability) { fetch: function(): Promise { return new Promise((resolve, reject) => { RCTReachability.getCurrentReachability( - (resp) => { + function(resp) { resolve(resp.network_reachability); }, reject @@ -93,53 +126,42 @@ if (RCTReachability) { }, }; - /** - * - * == NetInfo.isConnected - * - * Available on all platforms. Asyncronously fetch a boolean to determine - * internet connectivity. - * - * ``` - * NetInfo.isConnected.fetch().done((isConnected) => { - * console.log('First, is ' + (isConnected ? 'online' : 'offline')); - * }); - * function handleFirstConnectivityChange(isConnected) { - * console.log('Then, is ' + (isConnected ? 'online' : 'offline')); - * NetInfo.isConnected.removeEventListener( - * 'change', - * handleFirstConnectivityChange - * ); - * } - * NetInfo.isConnected.addEventListener( - * 'change', - * handleFirstConnectivityChange - * ); - * ``` - * - */ var _isConnectedSubscriptions = {}; + + var _iosReachabilityIsConnected = function( + reachability: ReachabilityStateIOS + ): bool { + return reachability !== 'none' && + reachability !== 'unknown'; + }; + NetInfo.isConnected = { addEventListener: function ( eventName: ChangeEventName, handler: Function ): void { _isConnectedSubscriptions[handler] = (reachability) => { - handler(reachability !== 'none'); + handler(_iosReachabilityIsConnected(reachability)); }; - NetInfo.reachabilityIOS.addEventListener(eventName, _isConnectedSubscriptions[handler]); + NetInfo.reachabilityIOS.addEventListener( + eventName, + _isConnectedSubscriptions[handler] + ); }, removeEventListener: function( eventName: ChangeEventName, handler: Function ): void { - NetInfo.reachabilityIOS.removeEventListener(eventName, _isConnectedSubscriptions[handler]); + NetInfo.reachabilityIOS.removeEventListener( + eventName, + _isConnectedSubscriptions[handler] + ); }, fetch: function(): Promise { return NetInfo.reachabilityIOS.fetch().then( - (reachability) => reachability !== 'none' + (reachability) => _iosReachabilityIsConnected(reachability) ); }, }; diff --git a/Libraries/ReactIOS/IOSDefaultEventPluginOrder.js b/Libraries/ReactIOS/IOSDefaultEventPluginOrder.js index b23366920..80098fc41 100644 --- a/Libraries/ReactIOS/IOSDefaultEventPluginOrder.js +++ b/Libraries/ReactIOS/IOSDefaultEventPluginOrder.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule IOSDefaultEventPluginOrder + * @flow */ 'use strict'; diff --git a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js index c41ae82ab..b585db5a7 100644 --- a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js +++ b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule IOSNativeBridgeEventPlugin + * @flow */ "use strict"; @@ -51,10 +52,11 @@ var IOSNativeBridgeEventPlugin = { * @see {EventPluginHub.extractEvents} */ extractEvents: function( - topLevelType, - topLevelTarget, - topLevelTargetID, - nativeEvent) { + topLevelType: string, + topLevelTarget: EventTarget, + topLevelTargetID: string, + nativeEvent: Event + ): ?Object { var bubbleDispatchConfig = customBubblingEventTypes[topLevelType]; var directDispatchConfig = customDirectEventTypes[topLevelType]; var event = SyntheticEvent.getPooled( diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index 3dbe233ee..ec72a0b4f 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -7,10 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule NativeMethodsMixin + * @flow */ 'use strict'; -var NativeModules = require('NativeModules'); var NativeModules = require('NativeModules'); var RCTPOPAnimationManager = NativeModules.POPAnimationManager; var RCTUIManager = NativeModules.UIManager; @@ -20,7 +20,26 @@ var flattenStyle = require('flattenStyle'); var invariant = require('invariant'); var mergeFast = require('mergeFast'); -var animationIDInvariant = function(funcName, anim) { +type MeasureOnSuccessCallback = ( + x: number, + y: number, + width: number, + height: number, + pageX: number, + pageY: number +) => void + +type MeasureLayoutOnSuccessCallback = ( + left: number, + top: number, + width: number, + height: number +) => void + +var animationIDInvariant = function( + funcName: string, + anim: number +) { invariant( anim, funcName + ' must be called with a valid animation ID returned from' + @@ -29,21 +48,25 @@ var animationIDInvariant = function(funcName, anim) { }; var NativeMethodsMixin = { - addAnimation: function(anim, callback) { + addAnimation: function(anim: number, callback?: (finished: bool) => void) { animationIDInvariant('addAnimation', anim); RCTPOPAnimationManager.addAnimation(this.getNodeHandle(), anim, callback); }, - removeAnimation: function(anim) { + removeAnimation: function(anim: number) { animationIDInvariant('removeAnimation', anim); RCTPOPAnimationManager.removeAnimation(this.getNodeHandle(), anim); }, - measure: function(callback) { + measure: function(callback: MeasureOnSuccessCallback) { RCTUIManager.measure(this.getNodeHandle(), callback); }, - measureLayout: function(relativeToNativeNode, onSuccess, onFail) { + measureLayout: function( + relativeToNativeNode: number, + onSuccess: MeasureLayoutOnSuccessCallback, + onFail: () => void /* currently unused */ + ) { RCTUIManager.measureLayout( this.getNodeHandle(), relativeToNativeNode, @@ -57,7 +80,7 @@ var NativeMethodsMixin = { * in future diff process, this means that if you do not include them in the * next render, they will remain active. */ - setNativeProps: function(nativeProps) { + setNativeProps: function(nativeProps: Object) { // nativeProps contains a style attribute that's going to be flattened // and all the attributes expanded in place. In order to make this // process do as few allocations and copies as possible, we return @@ -111,15 +134,19 @@ function throwOnStylesProp(component, props) { } } if (__DEV__) { + // hide this from Flow since we can't define these properties outside of + // __DEV__ without actually implementing them (setting them to undefined + // isn't allowed by ReactClass) + var NativeMethodsMixin_DEV = (NativeMethodsMixin: any); invariant( - !NativeMethodsMixin.componentWillMount && - !NativeMethodsMixin.componentWillReceiveProps, + !NativeMethodsMixin_DEV.componentWillMount && + !NativeMethodsMixin_DEV.componentWillReceiveProps, 'Do not override existing functions.' ); - NativeMethodsMixin.componentWillMount = function () { + NativeMethodsMixin_DEV.componentWillMount = function () { throwOnStylesProp(this, this.props); }; - NativeMethodsMixin.componentWillReceiveProps = function (newProps) { + NativeMethodsMixin_DEV.componentWillReceiveProps = function (newProps) { throwOnStylesProp(this, newProps); }; } diff --git a/Libraries/ReactIOS/React.js b/Libraries/ReactIOS/React.js index 34f190138..ce9de0852 100644 --- a/Libraries/ReactIOS/React.js +++ b/Libraries/ReactIOS/React.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule React + * @flow */ "use strict"; diff --git a/Libraries/ReactIOS/ReactIOS.js b/Libraries/ReactIOS/ReactIOS.js index e1c948065..028577429 100644 --- a/Libraries/ReactIOS/ReactIOS.js +++ b/Libraries/ReactIOS/ReactIOS.js @@ -7,8 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactIOS + * @flow */ - "use strict"; var ReactChildren = require('ReactChildren'); @@ -51,7 +51,7 @@ var resolveDefaultProps = function(element) { }; // Experimental optimized element creation -var augmentElement = function(element) { +var augmentElement = function(element: ReactElement) { if (__DEV__) { invariant( false, @@ -67,7 +67,7 @@ var augmentElement = function(element) { return element; }; -var render = function(component, mountInto) { +var render = function(component: ReactComponent, mountInto: number) { ReactIOSMount.renderComponent(component, mountInto); }; diff --git a/Libraries/ReactIOS/ReactIOSComponentEnvironment.js b/Libraries/ReactIOS/ReactIOSComponentEnvironment.js index 664ebb6ec..0dc0b1d1c 100644 --- a/Libraries/ReactIOS/ReactIOSComponentEnvironment.js +++ b/Libraries/ReactIOS/ReactIOSComponentEnvironment.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactIOSComponentEnvironment + * @flow */ 'use strict'; diff --git a/Libraries/ReactIOS/ReactIOSComponentMixin.js b/Libraries/ReactIOS/ReactIOSComponentMixin.js index d8044e5eb..94c708a43 100644 --- a/Libraries/ReactIOS/ReactIOSComponentMixin.js +++ b/Libraries/ReactIOS/ReactIOSComponentMixin.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactIOSComponentMixin + * @flow */ 'use strict'; diff --git a/Libraries/ReactIOS/ReactIOSDOMIDOperations.js b/Libraries/ReactIOS/ReactIOSDOMIDOperations.js index 7e266530a..7d421442f 100644 --- a/Libraries/ReactIOS/ReactIOSDOMIDOperations.js +++ b/Libraries/ReactIOS/ReactIOSDOMIDOperations.js @@ -7,7 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactIOSDOMIDOperations - * @typechecks static-only + * @flow */ "use strict"; diff --git a/Libraries/ReactIOS/ReactIOSDefaultInjection.js b/Libraries/ReactIOS/ReactIOSDefaultInjection.js index dd67987cc..d6b81da2c 100644 --- a/Libraries/ReactIOS/ReactIOSDefaultInjection.js +++ b/Libraries/ReactIOS/ReactIOSDefaultInjection.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactIOSDefaultInjection + * @flow */ "use strict"; diff --git a/Libraries/ReactIOS/createReactIOSNativeComponentClass.js b/Libraries/ReactIOS/createReactIOSNativeComponentClass.js index 19217cb72..5a5af04dc 100644 --- a/Libraries/ReactIOS/createReactIOSNativeComponentClass.js +++ b/Libraries/ReactIOS/createReactIOSNativeComponentClass.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule createReactIOSNativeComponentClass + * @flow */ "use strict"; @@ -14,11 +15,19 @@ var ReactElement = require('ReactElement'); var ReactIOSNativeComponent = require('ReactIOSNativeComponent'); +// See also ReactIOSNativeComponent +type ReactIOSNativeComponentViewConfig = { + validAttributes: Object; + uiViewClassName: string; +} + /** * @param {string} config iOS View configuration. * @private */ -var createReactIOSNativeComponentClass = function(viewConfig) { +var createReactIOSNativeComponentClass = function( + viewConfig: ReactIOSNativeComponentViewConfig +): Function { // returning Function is lossy :/ var Constructor = function(element) { this._currentElement = element; diff --git a/Libraries/ReactIOS/diffRawProperties.js b/Libraries/ReactIOS/diffRawProperties.js index 2be542aaa..3a5de284f 100644 --- a/Libraries/ReactIOS/diffRawProperties.js +++ b/Libraries/ReactIOS/diffRawProperties.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule diffRawProperties + * @flow */ 'use strict'; @@ -21,34 +22,41 @@ * previous. These properties are as supplied to component construction. * @return {?object} */ -function diffRawProperties(updatePayload, prevProps, nextProps, validAttributes) { +function diffRawProperties( + updatePayload: ?Object, + prevProps: ?Object, + nextProps: ?Object, + validAttributes: Object +): ?Object { var validAttributeConfig; var nextProp; var prevProp; var isScalar; var shouldUpdate; - for (var propKey in nextProps) { - validAttributeConfig = validAttributes[propKey]; - if (!validAttributeConfig) { - continue; // not a valid native prop - } - prevProp = prevProps && prevProps[propKey]; - nextProp = nextProps[propKey]; - if (prevProp !== nextProp) { - // If you want a property's diff to be detected, you must configure it - // to be so - *or* it must be a scalar property. For now, we'll allow - // creation with any attribute that is not scalar, but we should - // eventually even reject those unless they are properly configured. - isScalar = typeof nextProp !== 'object' || nextProp === null; - shouldUpdate = isScalar || - !prevProp || - validAttributeConfig.diff && - validAttributeConfig.diff(prevProp, nextProp); + if (nextProps) { + for (var propKey in nextProps) { + validAttributeConfig = validAttributes[propKey]; + if (!validAttributeConfig) { + continue; // not a valid native prop + } + prevProp = prevProps && prevProps[propKey]; + nextProp = nextProps[propKey]; + if (prevProp !== nextProp) { + // If you want a property's diff to be detected, you must configure it + // to be so - *or* it must be a scalar property. For now, we'll allow + // creation with any attribute that is not scalar, but we should + // eventually even reject those unless they are properly configured. + isScalar = typeof nextProp !== 'object' || nextProp === null; + shouldUpdate = isScalar || + !prevProp || + validAttributeConfig.diff && + validAttributeConfig.diff(prevProp, nextProp); - if (shouldUpdate) { - updatePayload = updatePayload || {}; - updatePayload[propKey] = nextProp; + if (shouldUpdate) { + updatePayload = updatePayload || {}; + updatePayload[propKey] = nextProp; + } } } } @@ -56,31 +64,33 @@ function diffRawProperties(updatePayload, prevProps, nextProps, validAttributes) // Also iterate through all the previous props to catch any that have been // removed and make sure native gets the signal so it can reset them to the // default. - for (var propKey in prevProps) { - validAttributeConfig = validAttributes[propKey]; - if (!validAttributeConfig) { - continue; // not a valid native prop - } - if (updatePayload && updatePayload[propKey] !== undefined) { - continue; // Prop already specified - } - prevProp = prevProps[propKey]; - nextProp = nextProps && nextProps[propKey]; - if (prevProp !== nextProp) { - if (nextProp === undefined) { - nextProp = null; // null is a sentinel we explicitly send to native + if (prevProps) { + for (var propKey in prevProps) { + validAttributeConfig = validAttributes[propKey]; + if (!validAttributeConfig) { + continue; // not a valid native prop } - // If you want a property's diff to be detected, you must configure it - // to be so - *or* it must be a scalar property. For now, we'll allow - // creation with any attribute that is not scalar, but we should - // eventually even reject those unless they are properly configured. - isScalar = typeof nextProp !== 'object' || nextProp === null; - shouldUpdate = isScalar && prevProp !== nextProp || - validAttributeConfig.diff && - validAttributeConfig.diff(prevProp, nextProp); - if (shouldUpdate) { - updatePayload = updatePayload || {}; - updatePayload[propKey] = nextProp; + if (updatePayload && updatePayload[propKey] !== undefined) { + continue; // Prop already specified + } + prevProp = prevProps[propKey]; + nextProp = nextProps && nextProps[propKey]; + if (prevProp !== nextProp) { + if (nextProp === undefined) { + nextProp = null; // null is a sentinel we explicitly send to native + } + // If you want a property's diff to be detected, you must configure it + // to be so - *or* it must be a scalar property. For now, we'll allow + // creation with any attribute that is not scalar, but we should + // eventually even reject those unless they are properly configured. + isScalar = typeof nextProp !== 'object' || nextProp === null; + shouldUpdate = isScalar && prevProp !== nextProp || + validAttributeConfig.diff && + validAttributeConfig.diff(prevProp, nextProp); + if (shouldUpdate) { + updatePayload = updatePayload || {}; + updatePayload[propKey] = nextProp; + } } } } diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h index 61c8f2e73..b14a623c8 100644 --- a/Libraries/Text/RCTShadowText.h +++ b/Libraries/Text/RCTShadowText.h @@ -21,6 +21,7 @@ extern NSString *const RCTReactTagAttributeName; @property (nonatomic, copy) NSString *fontFamily; @property (nonatomic, assign) CGFloat fontSize; @property (nonatomic, copy) NSString *fontWeight; +@property (nonatomic, copy) NSString *fontStyle; @property (nonatomic, assign) BOOL isHighlighted; @property (nonatomic, assign) CGFloat lineHeight; @property (nonatomic, assign) NSInteger maxNumberOfLines; diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 8899fea87..f00350205 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -50,12 +50,14 @@ static css_dim_t RCTMeasure(void *context, float width) { return [self _attributedStringWithFontFamily:nil fontSize:0 - fontWeight:nil]; + fontWeight:nil + fontStyle:nil]; } - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily fontSize:(CGFloat)fontSize fontWeight:(NSString *)fontWeight + fontStyle:(NSString *)fontStyle { if (![self isTextDirty] && _cachedAttributedString) { return _cachedAttributedString; @@ -67,6 +69,9 @@ static css_dim_t RCTMeasure(void *context, float width) if (_fontWeight) { fontWeight = _fontWeight; } + if (_fontStyle) { + fontStyle = _fontStyle; + } if (_fontFamily) { fontFamily = _fontFamily; } @@ -75,7 +80,7 @@ static css_dim_t RCTMeasure(void *context, float width) for (RCTShadowView *child in [self reactSubviews]) { if ([child isKindOfClass:[RCTShadowText class]]) { RCTShadowText *shadowText = (RCTShadowText *)child; - [attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight]]; + [attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight fontStyle:fontStyle]]; } else if ([child isKindOfClass:[RCTShadowRawText class]]) { RCTShadowRawText *shadowRawText = (RCTShadowRawText *)child; [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[shadowRawText text] ?: @""]]; @@ -96,7 +101,7 @@ static css_dim_t RCTMeasure(void *context, float width) [self _addAttribute:NSBackgroundColorAttributeName withValue:self.textBackgroundColor toAttributedString:attributedString]; } - _font = [RCTConvert UIFont:nil withFamily:fontFamily size:@(fontSize) weight:fontWeight]; + _font = [RCTConvert UIFont:nil withFamily:fontFamily size:@(fontSize) weight:fontWeight style:fontStyle]; [self _addAttribute:NSFontAttributeName withValue:_font toAttributedString:attributedString]; [self _addAttribute:RCTReactTagAttributeName withValue:self.reactTag toAttributedString:attributedString]; [self _setParagraphStyleOnAttributedString:attributedString]; @@ -110,7 +115,7 @@ static css_dim_t RCTMeasure(void *context, float width) - (UIFont *)font { - return _font ?: [RCTConvert UIFont:nil withFamily:_fontFamily size:@(_fontSize) weight:_fontWeight]; + return _font ?: [RCTConvert UIFont:nil withFamily:_fontFamily size:@(_fontSize) weight:_fontWeight style:_fontStyle]; } - (void)_addAttribute:(NSString *)attribute withValue:(id)attributeValue toAttributedString:(NSMutableAttributedString *)attributedString diff --git a/Libraries/react-native/react-native-interface.js b/Libraries/react-native/react-native-interface.js index 2ace79292..6408526b8 100644 --- a/Libraries/react-native/react-native-interface.js +++ b/Libraries/react-native/react-native-interface.js @@ -12,3 +12,7 @@ // see also react-native.js declare var __DEV__: boolean; + +declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{ + inject: ?((stuff: Object) => void) +};*/ diff --git a/ReactKit/Base/RCTConvert.h b/ReactKit/Base/RCTConvert.h index f26dea827..fb4f51883 100644 --- a/ReactKit/Base/RCTConvert.h +++ b/ReactKit/Base/RCTConvert.h @@ -12,6 +12,7 @@ #import "Layout.h" #import "RCTAnimationType.h" +#import "RCTLog.h" #import "RCTPointerEvents.h" /** @@ -69,8 +70,13 @@ + (UIFont *)UIFont:(UIFont *)font withSize:(id)json; + (UIFont *)UIFont:(UIFont *)font withWeight:(id)json; ++ (UIFont *)UIFont:(UIFont *)font withStyle:(id)json; + (UIFont *)UIFont:(UIFont *)font withFamily:(id)json; -+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json size:(id)json weight:(id)json; ++ (UIFont *)UIFont:(UIFont *)font + withFamily:(id)family + size:(id)size + weight:(id)weight + style:(id)style; + (NSArray *)NSStringArray:(id)json; + (NSArray *)NSURLArray:(id)json; diff --git a/ReactKit/Base/RCTConvert.m b/ReactKit/Base/RCTConvert.m index dd46fb4af..01300e306 100644 --- a/ReactKit/Base/RCTConvert.m +++ b/ReactKit/Base/RCTConvert.m @@ -11,8 +11,6 @@ #import -#import "RCTLog.h" - @implementation RCTConvert RCT_CONVERTER(BOOL, BOOL, boolValue) @@ -135,7 +133,9 @@ RCT_CGSTRUCT_CONVERTER(CATransform3D, (@[ @"m41", @"m42", @"m43", @"m44" ]), nil) -RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]), nil) +RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ + @"a", @"b", @"c", @"d", @"tx", @"ty" +]), nil) + (UIColor *)UIColor:(id)json { @@ -364,7 +364,8 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty } else if (json && ![json isKindOfClass:[NSNull class]]) { - RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, received %@: %@", [json class], json); + RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, \ + received %@: %@", [json class], json); } // Default color @@ -418,100 +419,163 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty return [self UIImage:json].CGImage; } +#ifndef __IPHONE_8_2 + +// These constants are defined in iPhone SDK 8.2 +// They'll work fine in earlier iOS versions, but the app cannot be built with +// an SDK version < 8.2 unless we redefine them here. This will be removed +// in a future version of ReactKit, once 8.2 is more widely adopted. + +static const CGFloat UIFontWeightUltraLight = -0.8; +static const CGFloat UIFontWeightThin = -0.6; +static const CGFloat UIFontWeightLight = -0.4; +static const CGFloat UIFontWeightRegular = 0; +static const CGFloat UIFontWeightMedium = 0.23; +static const CGFloat UIFontWeightSemibold = 0.3; +static const CGFloat UIFontWeightBold = 0.4; +static const CGFloat UIFontWeightHeavy = 0.56; +static const CGFloat UIFontWeightBlack = 0.62; + +#endif + +typedef CGFloat RCTFontWeight; +RCT_ENUM_CONVERTER(RCTFontWeight, (@{ + @"normal": @(UIFontWeightRegular), + @"bold": @(UIFontWeightBold), + @"100": @(UIFontWeightUltraLight), + @"200": @(UIFontWeightThin), + @"300": @(UIFontWeightLight), + @"400": @(UIFontWeightRegular), + @"500": @(UIFontWeightMedium), + @"600": @(UIFontWeightSemibold), + @"700": @(UIFontWeightBold), + @"800": @(UIFontWeightHeavy), + @"900": @(UIFontWeightBlack), +}), UIFontWeightRegular, doubleValue) + +typedef BOOL RCTFontStyle; +RCT_ENUM_CONVERTER(RCTFontStyle, (@{ + @"normal": @NO, + @"italic": @YES, + @"oblique": @YES, +}), NO, boolValue) + +static RCTFontWeight RCTWeightOfFont(UIFont *font) +{ + NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; + return [traits[UIFontWeightTrait] doubleValue]; +} + +static BOOL RCTFontIsItalic(UIFont *font) +{ + NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; + UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue]; + return (symbolicTraits & UIFontDescriptorTraitItalic) != 0; +} + +static BOOL RCTFontIsCondensed(UIFont *font) +{ + NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; + UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue]; + return (symbolicTraits & UIFontDescriptorTraitCondensed) != 0; +} + + (UIFont *)UIFont:(UIFont *)font withSize:(id)json { - return [self UIFont:font withFamily:nil size:json weight:nil]; + return [self UIFont:font withFamily:nil size:json weight:nil style:nil]; } + (UIFont *)UIFont:(UIFont *)font withWeight:(id)json { - return [self UIFont:font withFamily:nil size:nil weight:json]; + return [self UIFont:font withFamily:nil size:nil weight:json style:nil]; +} + ++ (UIFont *)UIFont:(UIFont *)font withStyle:(id)json +{ + return [self UIFont:font withFamily:nil size:nil weight:nil style:json]; } + (UIFont *)UIFont:(UIFont *)font withFamily:(id)json { - return [self UIFont:font withFamily:json size:nil weight:nil]; + return [self UIFont:font withFamily:json size:nil weight:nil style:nil]; } -+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family size:(id)size weight:(id)weight ++ (UIFont *)UIFont:(UIFont *)font + withFamily:(id)family + size:(id)size + weight:(id)weight + style:(id)style { - CGFloat const RCTDefaultFontSize = 14; - NSString *const RCTDefaultFontName = @"HelveticaNeue"; - NSString *const RCTDefaultFontWeight = @"normal"; - NSString *const RCTBoldFontWeight = @"bold"; + // Defaults + NSString *const RCTDefaultFontFamily = @"Helvetica Neue"; + const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular; + const CGFloat RCTDefaultFontSize = 14; - // Create descriptor - UIFontDescriptor *fontDescriptor = font.fontDescriptor ?: [UIFontDescriptor fontDescriptorWithName:RCTDefaultFontName size:RCTDefaultFontSize]; - - // Get font size - CGFloat fontSize = [self CGFloat:size]; - if (fontSize && !isnan(fontSize)) { - fontDescriptor = [fontDescriptor fontDescriptorWithSize:fontSize]; - } - - // Get font family - NSString *familyName = [self NSString:family]; - if (familyName) { - if ([UIFont fontNamesForFamilyName:familyName].count == 0) { - font = [UIFont fontWithName:familyName size:fontDescriptor.pointSize]; - if (font) { - // It's actually a font name, not a font family name, - // but we'll do what was meant, not what was said. - familyName = font.familyName; - fontDescriptor = font.fontDescriptor; - } else { - // Not a valid font or family - RCTLogError(@"Unrecognized font family '%@'", familyName); - familyName = [UIFont fontWithDescriptor:fontDescriptor size:0].familyName; - } - } else { - // Set font family - fontDescriptor = [fontDescriptor fontDescriptorWithFamily:familyName]; - } - } else { - familyName = [UIFont fontWithDescriptor:fontDescriptor size:0].familyName; + // Get existing properties + BOOL isItalic = NO; + BOOL isCondensed = NO; + RCTFontWeight fontWeight = RCTDefaultFontWeight; + if (font) { + family = font.familyName; + fontWeight = RCTWeightOfFont(font); + isItalic = RCTFontIsItalic(font); + isCondensed = RCTFontIsCondensed(font); } // Get font weight - NSString *fontWeight = [self NSString:weight]; - if (fontWeight) { + if (weight) { + fontWeight = [self RCTFontWeight:weight]; + } - static NSSet *values; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - values = [NSSet setWithObjects:RCTDefaultFontWeight, RCTBoldFontWeight, nil]; - }); + // Get font style + if (style) { + isItalic = [self RCTFontStyle:style]; + } - if (fontWeight && ![values containsObject:fontWeight]) { - RCTLogError(@"Unrecognized font weight '%@', must be one of %@", fontWeight, values); - fontWeight = RCTDefaultFontWeight; + // Get font size + CGFloat fontSize = [self CGFloat:size] ?: RCTDefaultFontSize; + + // Get font family + NSString *familyName = [self NSString:family] ?: RCTDefaultFontFamily; + if ([UIFont fontNamesForFamilyName:familyName].count == 0) { + font = [UIFont fontWithName:familyName size:fontSize]; + if (font) { + // It's actually a font name, not a font family name, + // but we'll do what was meant, not what was said. + familyName = font.familyName; + NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; + fontWeight = [traits[UIFontWeightTrait] doubleValue]; + } else { + // Not a valid font or family + RCTLogError(@"Unrecognized font family '%@'", familyName); + familyName = RCTDefaultFontFamily; } + } - // this is hacky. we are appending the string -Medium because most fonts we currently use - // just need to have -Medium appended to get the bold we want. we're going to revamp this - // to make it easier to know which options are available in JS. t4996115 - if ([fontWeight isEqualToString:RCTBoldFontWeight]) { - font = nil; - for (NSString *fontName in [UIFont fontNamesForFamilyName:familyName]) { - if ([fontName hasSuffix:@"-Medium"]) { - font = [UIFont fontWithName:fontName size:fontDescriptor.pointSize]; - break; - } - if ([fontName hasSuffix:@"-Bold"]) { - font = [UIFont fontWithName:fontName size:fontDescriptor.pointSize]; - // But keep searching in case there's a medium option - } - } - if (font) { - fontDescriptor = font.fontDescriptor; + // Get closest match + UIFont *bestMatch = font; + CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY; + for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) { + UIFont *match = [UIFont fontWithName:name size:fontSize]; + if (isItalic == RCTFontIsItalic(match) && + isCondensed == RCTFontIsCondensed(match)) { + CGFloat testWeight = RCTWeightOfFont(match); + if (ABS(testWeight - fontWeight) < ABS(closestWeight - fontWeight)) { + bestMatch = match; + closestWeight = testWeight; } } } - // TODO: font style + // Safety net + if (!bestMatch) { + RCTLogError(@"Could not find font with family: '%@', size: %@, \ + weight: %@, style: %@", family, size, weight, style); + bestMatch = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:familyName] firstObject] + size:fontSize]; + } - // Create font - return [UIFont fontWithDescriptor:fontDescriptor size:0]; + return bestMatch; } RCT_ARRAY_CONVERTER(NSString) diff --git a/ReactKit/Base/RCTDevelopmentViewController.h b/ReactKit/Base/RCTDevMenu.h similarity index 72% rename from ReactKit/Base/RCTDevelopmentViewController.h rename to ReactKit/Base/RCTDevMenu.h index 029f45f60..e7d3b8b30 100644 --- a/ReactKit/Base/RCTDevelopmentViewController.h +++ b/ReactKit/Base/RCTDevMenu.h @@ -9,6 +9,11 @@ #import -@interface RCTDevelopmentViewController : UIViewController +@class RCTRootView; + +@interface RCTDevMenu : NSObject + +- (instancetype)initWithRootView:(RCTRootView *)rootView; +- (void)show; @end diff --git a/ReactKit/Base/RCTDevMenu.m b/ReactKit/Base/RCTDevMenu.m new file mode 100644 index 000000000..77ce73935 --- /dev/null +++ b/ReactKit/Base/RCTDevMenu.m @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTDevMenu.h" + +#import "RCTRedBox.h" +#import "RCTRootView.h" + +@interface RCTDevMenu () { + BOOL _liveReload; +} + +@property (nonatomic, weak) RCTRootView *view; + +@end + +@implementation RCTDevMenu + +- (instancetype)initWithRootView:(RCTRootView *)rootView +{ + if (self = [super init]) { + self.view = rootView; + } + return self; +} + +- (void)show +{ + NSString *debugTitle = self.view.executorClass == nil ? @"Enable Debugging" : @"Disable Debugging"; + NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload"; + UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development" + delegate:self + cancelButtonTitle:@"Cancel" + destructiveButtonTitle:nil + otherButtonTitles:@"Reload", debugTitle, liveReloadTitle, nil]; + actionSheet.actionSheetStyle = UIBarStyleBlack; + [actionSheet showInView:self.view]; +} + +- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex +{ + if (buttonIndex == 0) { + [self.view reload]; + } else if (buttonIndex == 1) { + self.view.executorClass = self.view.executorClass == nil ? NSClassFromString(@"RCTWebSocketExecutor") : nil; + [self.view reload]; + } else if (buttonIndex == 2) { + _liveReload = !_liveReload; + [self _pollAndReload]; + } +} + +- (void)_pollAndReload +{ + if (_liveReload) { + NSURL *url = [self.view scriptURL]; + NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url]; + [self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL]; + } +} + +- (void)_checkForUpdates:(NSURL *)URL +{ + NSMutableURLRequest *longPollRequest = [NSMutableURLRequest requestWithURL:URL]; + longPollRequest.timeoutInterval = 30; + NSHTTPURLResponse *response; + [NSURLConnection sendSynchronousRequest:longPollRequest returningResponse:&response error:nil]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (_liveReload && response.statusCode == 205) { + [[RCTRedBox sharedInstance] dismiss]; + [self.view reload]; + } + [self _pollAndReload]; + }); +} + +@end diff --git a/ReactKit/Base/RCTDevelopmentViewController.m b/ReactKit/Base/RCTDevelopmentViewController.m deleted file mode 100644 index 9b414b14f..000000000 --- a/ReactKit/Base/RCTDevelopmentViewController.m +++ /dev/null @@ -1,89 +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. - */ - -#import "RCTDevelopmentViewController.h" - -#import "RCTRedBox.h" -#import "RCTRootView.h" - -@interface RCTDevelopmentViewController () { - BOOL _liveReload; -} - -@property (nonatomic, readonly) RCTRootView *RCTView; - -@end - -@implementation RCTDevelopmentViewController - -- (BOOL)canBecomeFirstResponder -{ - return YES; -} - -- (RCTRootView *)RCTView -{ - return (RCTRootView *)self.view; -} - -- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event -{ - if (motion == UIEventSubtypeMotionShake) - { - NSString *debugTitle = self.RCTView.executorClass == nil ? @"Enable Debugging" : @"Disable Debugging"; - NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload"; - UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development" - delegate:self - cancelButtonTitle:@"Cancel" - destructiveButtonTitle:nil - otherButtonTitles:@"Reload", debugTitle, liveReloadTitle, nil]; - actionSheet.actionSheetStyle = UIBarStyleBlack; - [actionSheet showInView:self.view]; - } -} - -- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex -{ - if (buttonIndex == 0) { - [self.RCTView reload]; - } else if (buttonIndex == 1) { - self.RCTView.executorClass = self.RCTView.executorClass == nil ? NSClassFromString(@"RCTWebSocketExecutor") : nil; - [self.RCTView reload]; - } else if (buttonIndex == 2) { - _liveReload = !_liveReload; - [self _pollAndReload]; - } -} - -- (void)_pollAndReload -{ - if (_liveReload) { - NSURL *url = [self.RCTView scriptURL]; - NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url]; - [self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL]; - } -} - -- (void)_checkForUpdates:(NSURL *)URL -{ - NSMutableURLRequest *longPollRequest = [NSMutableURLRequest requestWithURL:URL]; - longPollRequest.timeoutInterval = 30; - NSHTTPURLResponse *response; - [NSURLConnection sendSynchronousRequest:longPollRequest returningResponse:&response error:nil]; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (_liveReload && response.statusCode == 205) { - [[RCTRedBox sharedInstance] dismiss]; - [self.RCTView reload]; - } - [self _pollAndReload]; - }); -} - -@end diff --git a/ReactKit/Base/RCTRootView.h b/ReactKit/Base/RCTRootView.h index 15f99fdee..ef72d374b 100644 --- a/ReactKit/Base/RCTRootView.h +++ b/ReactKit/Base/RCTRootView.h @@ -48,6 +48,12 @@ */ @property (nonatomic, strong) Class executorClass; +/** + * If YES will watch for shake gestures and show development menu + * with options like "Reload", "Enable Debugging", etc. + */ +@property (nonatomic, assign) BOOL enableDevMenu; + /** * Reload this root view, or all root views, respectively. */ diff --git a/ReactKit/Base/RCTRootView.m b/ReactKit/Base/RCTRootView.m index 843d00dda..7375b77ba 100644 --- a/ReactKit/Base/RCTRootView.m +++ b/ReactKit/Base/RCTRootView.m @@ -11,6 +11,7 @@ #import "RCTBridge.h" #import "RCTContextExecutor.h" +#import "RCTDevMenu.h" #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" #import "RCTLog.h" @@ -26,6 +27,7 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification"; @implementation RCTRootView { + RCTDevMenu *_devMenu; RCTBridge *_bridge; RCTTouchHandler *_touchHandler; id _executor; @@ -84,6 +86,9 @@ static Class _globalExecutorClass; // Numbering of these tags goes from 1, 11, 21, 31, etc static NSInteger rootViewTag = 1; self.reactTag = @(rootViewTag); +#ifdef DEBUG + self.enableDevMenu = YES; +#endif rootViewTag += 10; // Add reload observer @@ -93,6 +98,21 @@ static Class _globalExecutorClass; object:nil]; } +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event +{ + if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) { + if (!_devMenu) { + _devMenu = [[RCTDevMenu alloc] initWithRootView:self]; + } + [_devMenu show]; + } +} + + (NSArray *)JSMethods { return @[ diff --git a/ReactKit/Base/RCTUtils.m b/ReactKit/Base/RCTUtils.m index 70ad4479a..300f6d721 100644 --- a/ReactKit/Base/RCTUtils.m +++ b/ReactKit/Base/RCTUtils.m @@ -50,11 +50,11 @@ NSString *RCTMD5Hash(NSString *string) CC_MD5(str, (CC_LONG)strlen(str), result); return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - result[0], result[1], result[2], result[3], - result[4], result[5], result[6], result[7], - result[8], result[9], result[10], result[11], - result[12], result[13], result[14], result[15] - ]; + result[0], result[1], result[2], result[3], + result[4], result[5], result[6], result[7], + result[8], result[9], result[10], result[11], + result[12], result[13], result[14], result[15] + ]; } CGFloat RCTScreenScale() diff --git a/ReactKit/ReactKit.xcodeproj/project.pbxproj b/ReactKit/ReactKit.xcodeproj/project.pbxproj index c742711d8..b7c68ccd5 100644 --- a/ReactKit/ReactKit.xcodeproj/project.pbxproj +++ b/ReactKit/ReactKit.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; }; - 00C1A2B31AC0B7E000E89A1C /* RCTDevelopmentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C1A2B21AC0B7E000E89A1C /* RCTDevelopmentViewController.m */; }; + 00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */; }; 134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; }; 134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; }; 134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */; }; @@ -77,8 +77,8 @@ /* Begin PBXFileReference section */ 000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSourceCode.h; sourceTree = ""; }; 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSourceCode.m; sourceTree = ""; }; - 00C1A2B11AC0B7E000E89A1C /* RCTDevelopmentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevelopmentViewController.h; sourceTree = ""; }; - 00C1A2B21AC0B7E000E89A1C /* RCTDevelopmentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevelopmentViewController.m; sourceTree = ""; }; + 00C1A2B11AC0B7E000E89A1C /* RCTDevMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevMenu.h; sourceTree = ""; }; + 00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevMenu.m; sourceTree = ""; }; 13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationType.h; sourceTree = ""; }; 13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = ""; }; 13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewControllerProtocol.h; sourceTree = ""; }; @@ -368,8 +368,8 @@ 83CBBA591A601E9000E9B192 /* RCTRedBox.m */, 830A229C1A66C68A008503DA /* RCTRootView.h */, 830A229D1A66C68A008503DA /* RCTRootView.m */, - 00C1A2B11AC0B7E000E89A1C /* RCTDevelopmentViewController.h */, - 00C1A2B21AC0B7E000E89A1C /* RCTDevelopmentViewController.m */, + 00C1A2B11AC0B7E000E89A1C /* RCTDevMenu.h */, + 00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */, 83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */, 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */, 83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */, @@ -499,7 +499,7 @@ 13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */, 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */, 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, - 00C1A2B31AC0B7E000E89A1C /* RCTDevelopmentViewController.m in Sources */, + 00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */, 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */, 134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */, 13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */, diff --git a/ReactKit/Views/RCTScrollView.h b/ReactKit/Views/RCTScrollView.h index 7e69affe6..db6aa7edd 100644 --- a/ReactKit/Views/RCTScrollView.h +++ b/ReactKit/Views/RCTScrollView.h @@ -22,19 +22,25 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; /** - * If the `contentSize` is not provided, then the `contentSize` will - * automatically be determined by the size of the `RKScrollView` subview. - * * The `RCTScrollView` may have at most one single subview. This will ensure * that the scroll view's `contentSize` will be efficiently set to the size of * the single subview's frame. That frame size will be determined somewhat * efficiently since it will have already been computed by the off-main-thread * layout system. */ -@property (nonatomic, readonly) UIScrollView *scrollView; @property (nonatomic, readonly) UIView *contentView; +/** + * If the `contentSize` is not specified (or is specified as {0, 0}, then the + * `contentSize` will automatically be determined by the size of the subview. + */ @property (nonatomic, assign) CGSize contentSize; + +/** + * The underlying scrollView (TODO: can we remove this?) + */ +@property (nonatomic, readonly) UIScrollView *scrollView; + @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; @property (nonatomic, assign) NSUInteger throttleScrollCallbackMS; diff --git a/ReactKit/Views/RCTScrollView.m b/ReactKit/Views/RCTScrollView.m index ff98252a9..8c3db22be 100644 --- a/ReactKit/Views/RCTScrollView.m +++ b/ReactKit/Views/RCTScrollView.m @@ -253,7 +253,6 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; @implementation RCTScrollView { RCTEventDispatcher *_eventDispatcher; - BOOL _contentSizeManuallySet; RCTCustomScrollView *_scrollView; UIView *_contentView; NSTimeInterval _lastScrollDispatchTime; @@ -273,6 +272,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; _scrollView.delaysContentTouches = NO; _automaticallyAdjustContentInsets = YES; _contentInset = UIEdgeInsetsZero; + _contentSize = CGSizeZero; _throttleScrollCallbackMS = 0; _lastScrollDispatchTime = CACurrentMediaTime(); @@ -319,16 +319,6 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; _scrollView.stickyHeaderIndices = headerIndices; } -/** - * Once you set the `contentSize`, it's assumed to be managed by you forever - * and we'll never automatically compute the size for you. - */ -- (void)setContentSize:(CGSize)contentSize -{ - _contentSize = contentSize; - _contentSizeManuallySet = YES; -} - - (void)dealloc { _scrollView.delegate = nil; @@ -556,31 +546,42 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) return newOffset; } -- (void)reactBridgeDidFinishTransaction +/** + * Once you set the `contentSize`, to a nonzero value, it is assumed to be + * managed by you, and we'll never automatically compute the size for you, + * unless you manually reset it back to {0, 0} + */ +- (CGSize)contentSize { - if (_contentSizeManuallySet) { - _scrollView.contentSize = _contentSize; + if (!CGSizeEqualToSize(_contentSize, CGSizeZero)) { + return _contentSize; } else if (!_contentView) { - _scrollView.contentSize = CGSizeZero; + return CGSizeZero; } else { CGSize singleSubviewSize = _contentView.frame.size; CGPoint singleSubviewPosition = _contentView.frame.origin; - CGSize fittedSize = { + return (CGSize){ singleSubviewSize.width + singleSubviewPosition.x, singleSubviewSize.height + singleSubviewPosition.y }; - if (!CGSizeEqualToSize(_scrollView.contentSize, fittedSize)) { - // When contentSize is set manually, ScrollView internals will reset contentOffset to 0,0. Since - // we potentially set contentSize whenever anything in the ScrollView updates, we workaround this - // issue by manually adjusting contentOffset whenever this happens - CGPoint newOffset = [self calculateOffsetForContentSize:fittedSize]; - _scrollView.contentSize = fittedSize; - _scrollView.contentOffset = newOffset; - } - [_scrollView dockClosestSectionHeader]; } } +- (void)reactBridgeDidFinishTransaction +{ + CGSize contentSize = self.contentSize; + if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) { + // When contentSize is set manually, ScrollView internals will reset + // contentOffset to {0, 0}. Since we potentially set contentSize whenever + // anything in the ScrollView updates, we workaround this issue by manually + // adjusting contentOffset whenever this happens + CGPoint newOffset = [self calculateOffsetForContentSize:contentSize]; + _scrollView.contentSize = contentSize; + _scrollView.contentOffset = newOffset; + } + [_scrollView dockClosestSectionHeader]; +} + // Note: setting several properties of UIScrollView has the effect of // resetting its contentOffset to {0, 0}. To prevent this, we generate // setters here that will record the contentOffset beforehand, and diff --git a/ReactKit/Views/RCTTextFieldManager.m b/ReactKit/Views/RCTTextFieldManager.m index 041886e55..8418c8858 100644 --- a/ReactKit/Views/RCTTextFieldManager.m +++ b/ReactKit/Views/RCTTextFieldManager.m @@ -41,7 +41,11 @@ RCT_CUSTOM_VIEW_PROPERTY(fontSize, RCTTextField) } RCT_CUSTOM_VIEW_PROPERTY(fontWeight, RCTTextField) { - view.font = [RCTConvert UIFont:view.font withWeight:json]; // TODO: default value + view.font = [RCTConvert UIFont:view.font withWeight:json]; // defaults to normal +} +RCT_CUSTOM_VIEW_PROPERTY(fontStyle, RCTTextField) +{ + view.font = [RCTConvert UIFont:view.font withStyle:json]; // defaults to normal } RCT_CUSTOM_VIEW_PROPERTY(fontFamily, RCTTextField) { diff --git a/packager/README.md b/packager/README.md new file mode 100644 index 000000000..8f9f649bf --- /dev/null +++ b/packager/README.md @@ -0,0 +1,138 @@ +React Native Packager +-------------------- + +React Native Packager is a project similar in scope to browserify or +webpack, it provides a CommonJS-like module system, JavaScript +compilation (ES6, Flow, JSX), bundling, and asset loading. + +The main difference is the Packager's focus on compilation and +bundling speed. We aim for a sub-second edit-reload +cycles. Additionally, we don't want users -- with large code bases -- +to wait more than a few seconds after starting the packager. + +The main deviation from the node module system is the support for our +proprietary module format known as `@providesModule`. However, we +discourage people to use this module format because going forward, we +want to completely separate our infrastructure from React Native and +provide an experience most JavaScript developers are familiar with, +namely the node module format. We want to even go further, and let you +choose your own packager and asset pipeline or even integrate into +your existing infrastructure. + +React Native users need not to understand how the packager work, +however, this documentation might be useful for advanced users and +people who want to fix bugs or add features to the packager (patches +welcome!). + +## HTTP interface + +The main way you'd interact with the packager is via the HTTP +interface. The following is the list of endpoints and their respective +functions. + +### /path/to/moduleName.bundle + +Does the following in order: + +* parse out `path/to/moduleName` +* add a `.js` suffix to the path +* looks in your project root(s) for the file +* recursively collects all the dependencies from an in memory graph +* runs the modules through the transformer (might just be cached) +* concatenate the modules' content into a bundle +* responds to the client with the bundle (and a SourceMap URL) + +### /path/to/moduleName.map + +* if the package has been previously generated via the `.bundle` + endpoint then the source map will be generated from that package +* if the package has not been previously asked for, this will go + through the same steps outlined in the `.bundle` endpoint then + generate the source map. + +Note that source map generation currently assumes that the code has +been compiled with jstransform, which preserves line and column +numbers which allows us to generate source maps super fast. + +### /path/to/moduleName.(map|bundle) query params + +You can pass options for the bundle creation through the query params, +if the option is boolean `1/0` or `true/false` is accepted. + +Here are the current options the packager accepts: + +* `dev` boolean, defaults to true: sets a global `__DEV__` variable + which will effect how the React Nativeg core libraries behave. +* `minify` boolean, defaults to false: whether to minify the bundle. +* `runModule` boolean, defaults to true: whether to require your entry + point module. So if you requested `moduleName`, this option will add + a `require('moduleName')` the end of your bundle. +* `inlineSourceMap` boolean, defaults to false: whether to inline + source maps. + +### /debug + +This is a page used for debugging, it has links to two pages: + +* Cached Packages: which shows you the packages that's been already + generated and cached +* Dependency Graph: is the in-memory graph of all the modules and + their dependencies + +## Programmatic API + +The packager is made of two things: + +* The core packager (which we're calling ReactPackager) +* The scripts, devtools launcher, server run etc. + +ReactPackager is how you mainly interact with the API. + +```js +var ReactPackager = require('./react-packager'); +``` + +### ReactPackager.middleware(options) + +Returns a function that can be used in a connect-like +middleware. Takes the following options: + +* `projectRoots` array (required): Is the roots where your JavaScript + file will exist +* `blacklistRE` regexp: Is a patter to ignore certain paths from the + packager +* `polyfillModuleName` array: Paths to polyfills you want to be + included at the start of the bundle +* `cacheVersion` string: used in creating the cache file +* `resetCache` boolean, defaults to false: whether to use the cache on + disk +* `transformModulePath` string: Path to the module used as a + JavaScript transformer +* `nonPersistent` boolean, defaults to false: Whether the server + should be used as a persistent deamon to watch files and update + itself +* `assetRoots` array: Where should the packager look for assets + +### ReactPackager.buildPackageFromUrl(options, url) + +Build a package from a url (see the `.bundle` endpoint). `options` is +the same options that is passed to `ReactPackager.middleware` + +### ReactPackager.getDependencies(options, main) + +Given an entry point module. Recursively collect all the dependent +modules and return it as an array. `options` is the same options that +is passed to `ReactPackager.middleware` + +## FAQ + +### Can I use this in my own non-React Native project? + +Yes. It's not really tied to React Native, however feature development +is informed by React Native needs. + +### Why didn't you use webpack? + +We love webpack, however, when we tried on our codebase it was slower +than our developers would like it to be. You find can more discussion about +the subject [here](https://github.com/facebook/react-native/issues/5)