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
This commit is contained in:
Christopher Chedeau 2015-03-25 18:19:28 -07:00
parent bd64b14fbe
commit c7efc4dd11
51 changed files with 802 additions and 375 deletions

View File

@ -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

View File

@ -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',

View File

@ -62,7 +62,7 @@ var styles = StyleSheet.create({
movieTitle: {
flex: 1,
fontSize: 16,
fontWeight: 'bold',
fontWeight: '500',
marginBottom: 2,
},
movieYear: {

View File

@ -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: {

View File

@ -101,7 +101,7 @@ var ShareActionSheetExample = React.createClass({
var style = StyleSheet.create({
button: {
marginBottom: 10,
fontWeight: 'bold',
fontWeight: '500',
}
});

View File

@ -94,6 +94,6 @@ var AdSupportIOSExample = React.createClass({
var styles = StyleSheet.create({
title: {
fontWeight: 'bold',
fontWeight: '500',
},
});

View File

@ -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,
},
});

View File

@ -74,6 +74,6 @@ var GeolocationExample = React.createClass({
var styles = StyleSheet.create({
title: {
fontWeight: 'bold',
fontWeight: '500',
},
});

View File

@ -214,7 +214,7 @@ var styles = StyleSheet.create({
},
rowText: {
fontSize: 17,
fontWeight: 'bold',
fontWeight: '500',
},
});

View File

@ -254,7 +254,7 @@ var styles = StyleSheet.create({
fontSize: 18,
color: '#666666',
textAlign: 'center',
fontWeight: 'bold',
fontWeight: '500',
lineHeight: 32,
},
filterText: {

View File

@ -107,7 +107,7 @@ var styles = StyleSheet.create({
},
navBarTitleText: {
color: cssVar('fbui-bluegray-60'),
fontWeight: 'bold',
fontWeight: '500',
marginVertical: 9,
},
navBarButtonText: {

View File

@ -176,7 +176,7 @@ var styles = StyleSheet.create({
fontSize: 18,
color: '#666666',
textAlign: 'center',
fontWeight: 'bold',
fontWeight: '500',
lineHeight: 32,
},
filterText: {

View File

@ -47,7 +47,7 @@ var styles = StyleSheet.create({
text: {
fontSize: 14,
textAlign: 'center',
fontWeight: 'bold',
fontWeight: '500',
margin: 10,
},
});

View File

@ -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 (
<Text style={{fontWeight: 'bold'}}>
Move fast and be bold
</Text>
<View>
<Text style={{fontWeight: '100'}}>
Move fast and be ultralight
</Text>
<Text style={{fontWeight: '200'}}>
Move fast and be light
</Text>
<Text style={{fontWeight: 'normal'}}>
Move fast and be normal
</Text>
<Text style={{fontWeight: 'bold'}}>
Move fast and be bold
</Text>
<Text style={{fontWeight: '900'}}>
Move fast and be ultrabold
</Text>
</View>
);
},
}, {
title: 'Font Style',
render: function() {
return (
<View>
<Text style={{fontStyle: 'normal'}}>
Normal text
</Text>
<Text style={{fontStyle: 'italic'}}>
Italic text
</Text>
</View>
);
},
}, {
@ -279,7 +307,7 @@ var styles = StyleSheet.create({
backgroundColor: 'rgba(100, 100, 100, 0.3)'
},
entity: {
fontWeight: 'bold',
fontWeight: '500',
color: '#527fe4',
},
});

View File

@ -187,7 +187,7 @@ var styles = StyleSheet.create({
backgroundColor: '#f9f9f9',
},
textBlock: {
fontWeight: 'bold',
fontWeight: '500',
color: 'blue',
},
});

View File

@ -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];

View File

@ -76,7 +76,7 @@ var styles = StyleSheet.create({
},
titleText: {
fontSize: 14,
fontWeight: 'bold',
fontWeight: '500',
},
descriptionText: {
fontSize: 14,

View File

@ -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,

View File

@ -42,7 +42,7 @@ var styles = StyleSheet.create({
},
text: {
fontSize: 19,
fontWeight: 'bold',
fontWeight: '500',
},
});

View File

@ -239,7 +239,7 @@ var styles = StyleSheet.create({
},
errorTextTitle: {
fontSize: 15,
fontWeight: 'bold',
fontWeight: '500',
marginBottom: 10,
},
errorText: {

View File

@ -81,7 +81,7 @@ var styles = StyleSheet.create({
padding: 10,
},
testName: {
fontWeight: 'bold',
fontWeight: '500',
},
separator: {
height: 1,

View File

@ -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 (
* <Text>Current state is: {this.state.currentAppState}</Text>
* );
* },
* ```
*
* 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,

View File

@ -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);
* </View>
* ```
*
* 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,
/**

View File

@ -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)
);
},
};

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule IOSDefaultEventPluginOrder
* @flow
*/
'use strict';

View File

@ -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(

View File

@ -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);
};
}

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule React
* @flow
*/
"use strict";

View File

@ -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);
};

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactIOSComponentEnvironment
* @flow
*/
'use strict';

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactIOSComponentMixin
* @flow
*/
'use strict';

View File

@ -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";

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactIOSDefaultInjection
* @flow
*/
"use strict";

View File

@ -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;

View File

@ -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;
}
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -12,3 +12,7 @@
// see also react-native.js
declare var __DEV__: boolean;
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
inject: ?((stuff: Object) => void)
};*/

View File

@ -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;

View File

@ -11,8 +11,6 @@
#import <objc/message.h>
#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)

View File

@ -9,6 +9,11 @@
#import <UIKit/UIKit.h>
@interface RCTDevelopmentViewController : UIViewController
@class RCTRootView;
@interface RCTDevMenu : NSObject
- (instancetype)initWithRootView:(RCTRootView *)rootView;
- (void)show;
@end

View File

@ -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 () <UIActionSheetDelegate> {
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

View File

@ -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 () <UIActionSheetDelegate> {
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

View File

@ -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.
*/

View File

@ -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<RCTJavaScriptExecutor> _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 @[

View File

@ -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()

View File

@ -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 = "<group>"; };
000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSourceCode.m; sourceTree = "<group>"; };
00C1A2B11AC0B7E000E89A1C /* RCTDevelopmentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevelopmentViewController.h; sourceTree = "<group>"; };
00C1A2B21AC0B7E000E89A1C /* RCTDevelopmentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevelopmentViewController.m; sourceTree = "<group>"; };
00C1A2B11AC0B7E000E89A1C /* RCTDevMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevMenu.h; sourceTree = "<group>"; };
00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevMenu.m; sourceTree = "<group>"; };
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationType.h; sourceTree = "<group>"; };
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; };
13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewControllerProtocol.h; sourceTree = "<group>"; };
@ -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 */,

View File

@ -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;

View File

@ -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

View File

@ -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)
{

138
packager/README.md Normal file
View File

@ -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)