Updates from Thu 28 May

This commit is contained in:
Tadeu Zagallo 2015-05-28 16:17:37 +01:00
commit 9d1960948c
74 changed files with 1546 additions and 732 deletions

View File

@ -1,8 +1,19 @@
{
"parser": "babel-eslint",
"ecmaFeatures": {
"jsx": true
},
"env": {
"es6": true,
"jasmine": true,
},
"plugins": [
"react"
],
// Map from global var to bool specifying if it can be redefined
"globals": {
"__DEV__": true,
@ -36,10 +47,10 @@
},
"rules": {
"comma-dangle": 0, // disallow trailing commas in object literals
"no-cond-assign": 1, // disallow assignment in conditional expressions
"no-console": 0, // disallow use of console (off by default in the node environment)
"no-constant-condition": 0, // disallow use of constant expressions in conditions
"no-comma-dangle": 0, // disallow trailing commas in object literals
"no-control-regex": 1, // disallow control characters in regular expressions
"no-debugger": 1, // disallow use of debugger
"no-dupe-keys": 1, // disallow duplicate keys when creating object literals
@ -107,6 +118,7 @@
"no-warning-comments": 0, // disallow usage of configurable warning terms in comments": 1, // e.g. TODO or FIXME (off by default)
"no-with": 1, // disallow use of the with statement
"radix": 1, // require use of the second argument for parseInt() (off by default)
"semi-spacing": 1, // require a space after a semi-colon
"vars-on-top": 0, // requires to declare all vars on top of their containing scope (off by default)
"wrap-iife": 0, // require immediate function invocation to be wrapped in parentheses (off by default)
"yoda": 1, // require or disallow Yoda conditions
@ -177,7 +189,7 @@
"space-in-parens": 0, // require or disallow spaces inside parentheses (off by default)
"space-infix-ops": 1, // require spaces around operators
"space-return-throw-case": 1, // require a space after return, throw, and case
"space-unary-word-ops": 1, // require a space around word operators such as typeof (off by default)
"space-unary-ops": [1, { "words": true, "nonwords": false }], // require or disallow spaces before/after unary operators (words on by default, nonwords off by default)
"max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default)
"one-var": 0, // allow just one var statement per function (off by default)
"wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default)
@ -190,6 +202,22 @@
"max-params": 0, // limits the number of parameters that can be used in the function declaration. (off by default)
"max-statements": 0, // specify the maximum number of statement allowed in a function (off by default)
"no-bitwise": 1, // disallow use of bitwise operators (off by default)
"no-plusplus": 0 // disallow use of unary operators, ++ and -- (off by default)
"no-plusplus": 0, // disallow use of unary operators, ++ and -- (off by default)
"react/display-name": 0,
"react/jsx-boolean-value": 0,
"react/jsx-quotes": [1, "double", "avoid-escape"],
"react/jsx-no-undef": 1,
"react/jsx-sort-props": 0,
"react/jsx-uses-react": 0,
"react/jsx-uses-vars": 1,
"react/no-did-mount-set-state": [1, "allow-in-func"],
"react/no-did-update-set-state": [1, "allow-in-func"],
"react/no-multi-comp": 0,
"react/no-unknown-property": 0,
"react/prop-types": 0,
"react/react-in-jsx-scope": 0,
"react/self-closing-comp": 1,
"react/wrap-multilines": 0
}
}

View File

@ -16,13 +16,13 @@
.*/node_modules/react-tools/src/event/EventPropagators.js
# Ignore commoner tests
.*/node_modules/react-tools/node_modules/commoner/test/.*
.*/node_modules/commoner/test/.*
# See https://github.com/facebook/flow/issues/442
.*/react-tools/node_modules/commoner/lib/reader.js
# Ignore jest
.*/react-native/node_modules/jest-cli/.*
.*/node_modules/jest-cli/.*
# Ignore Website
.*/website/.*

View File

@ -34,7 +34,10 @@ var MovieCell = React.createClass({
var criticsScore = this.props.movie.ratings.critics_score;
return (
<View>
<TouchableHighlight onPress={this.props.onSelect}>
<TouchableHighlight
onPress={this.props.onSelect}
onShowUnderlay={this.props.onHighlight}
onHideUnderlay={this.props.onUnhighlight}>
<View style={styles.row}>
<Image
source={getImageSource(this.props.movie, 'det')}
@ -54,7 +57,6 @@ var MovieCell = React.createClass({
</View>
</View>
</TouchableHighlight>
<View style={styles.cellBorder} />
</View>
);
}

View File

@ -16,6 +16,7 @@
140D9B661AC36C42004F25EE /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14312D241AC3654D00CDC950 /* libRCTLinking.a */; };
14A2D4421AC3E43800CC738A /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14A2D4411AC3E41A00CC738A /* libReact.a */; };
58C5726B1AA6239E00CDF9C8 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C5725B1AA6236500CDF9C8 /* libRCTText.a */; };
67C95F201B0E64A30040BCE2 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 67C95F1E1B0E647A0040BCE2 /* libRCTWebSocket.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -54,6 +55,13 @@
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
67C95F1D1B0E647A0040BCE2 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3C86DF461ADF2C930047B81A;
remoteInfo = RCTWebSocket;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@ -69,6 +77,7 @@
14312D1E1AC3654D00CDC950 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
14A2D43C1AC3E41A00CC738A /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../React/React.xcodeproj; sourceTree = "<group>"; };
587650F61A9EB120008B8F17 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = SOURCE_ROOT; };
67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -76,6 +85,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
67C95F201B0E64A30040BCE2 /* libRCTWebSocket.a in Frameworks */,
14A2D4421AC3E43800CC738A /* libReact.a in Frameworks */,
140D9B661AC36C42004F25EE /* libRCTLinking.a in Frameworks */,
1341801E1AA91750003F314A /* libRCTNetwork.a in Frameworks */,
@ -135,6 +145,7 @@
58C571FC1AA6124500CDF9C8 /* Libraries */ = {
isa = PBXGroup;
children = (
67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */,
14A2D43C1AC3E41A00CC738A /* React.xcodeproj */,
14312D1E1AC3654D00CDC950 /* RCTLinking.xcodeproj */,
134180151AA91740003F314A /* RCTNetwork.xcodeproj */,
@ -152,6 +163,14 @@
name = Products;
sourceTree = "<group>";
};
67C95F161B0E647A0040BCE2 /* Products */ = {
isa = PBXGroup;
children = (
67C95F1E1B0E647A0040BCE2 /* libRCTWebSocket.a */,
);
name = Products;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
@ -228,6 +247,10 @@
ProductGroup = 58C572571AA6236500CDF9C8 /* Products */;
ProjectRef = 587650F61A9EB120008B8F17 /* RCTText.xcodeproj */;
},
{
ProductGroup = 67C95F161B0E647A0040BCE2 /* Products */;
ProjectRef = 67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */;
},
{
ProductGroup = 14A2D43D1AC3E41A00CC738A /* Products */;
ProjectRef = 14A2D43C1AC3E41A00CC738A /* React.xcodeproj */;
@ -276,6 +299,13 @@
remoteRef = 58C5725A1AA6236500CDF9C8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
67C95F1E1B0E647A0040BCE2 /* libRCTWebSocket.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTWebSocket.a;
remoteRef = 67C95F1D1B0E647A0040BCE2 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */

View File

@ -237,10 +237,31 @@ var SearchScreen = React.createClass({
return <ActivityIndicatorIOS style={styles.scrollSpinner} />;
},
renderRow: function(movie: Object) {
renderSeparator: function(
sectionID: number | string,
rowID: number | string,
adjacentRowHighlighted: boolean
) {
var style = styles.rowSeparator;
if (adjacentRowHighlighted) {
style = [style, styles.rowSeparatorHide];
}
return (
<View key={"SEP_" + sectionID + "_" + rowID} style={style}/>
);
},
renderRow: function(
movie: Object,
sectionID: number | string,
rowID: number | string,
highlightRowFunc: (sectionID: ?number | string, rowID: ?number | string) => void,
) {
return (
<MovieCell
onSelect={() => this.selectMovie(movie)}
onHighlight={() => highlightRowFunc(sectionID, rowID)}
onUnhighlight={() => highlightRowFunc(null, null)}
movie={movie}
/>
);
@ -254,6 +275,7 @@ var SearchScreen = React.createClass({
/> :
<ListView
ref="listview"
renderSeparator={this.renderSeparator}
dataSource={this.state.dataSource}
renderFooter={this.renderFooter}
renderRow={this.renderRow}
@ -352,6 +374,14 @@ var styles = StyleSheet.create({
scrollSpinner: {
marginVertical: 20,
},
rowSeparator: {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
height: 1,
marginLeft: 4,
},
rowSeparatorHide: {
opacity: 0.0,
},
});
module.exports = SearchScreen;

View File

@ -50,11 +50,8 @@
redboxError = [[RCTRedBox sharedInstance] currentErrorMessage];
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
if ([view respondsToSelector:@selector(attributedText)]) {
NSString *text = [(id)view attributedText].string;
if ([text isEqualToString:TEXT_TO_LOOK_FOR]) {
return YES;
}
if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
return YES;
}
return NO;
}];

View File

@ -0,0 +1,65 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react-native');
var {
Text,
View,
} = React;
var AccessibilityIOSExample = React.createClass({
render() {
return (
<View>
<View
onAccessibilityTap={() => alert('onAccessibilityTap success')}
accessible={true}>
<Text>
Accessibility normal tap example
</Text>
</View>
<View onMagicTap={() => alert('onMagicTap success')}
accessible={true}>
<Text>
Accessibility magic tap example
</Text>
</View>
<View accessibilityLabel="Some announcement"
accessible={true}>
<Text>
Accessibility label example
</Text>
</View>
<View accessibilityTraits={["button", "selected"]}
accessible={true}>
<Text>
Accessibility traits example
</Text>
</View>
</View>
);
},
});
exports.title = 'AcccessibilityIOS';
exports.description = 'Interface to show iOS\' accessibility samples';
exports.examples = [
{
title: 'Accessibility elements',
render(): ReactElement { return <AccessibilityIOSExample />; }
},
];

View File

@ -28,13 +28,19 @@ var AppStateSubscription = React.createClass({
return {
appState: AppStateIOS.currentState,
previousAppStates: [],
memoryWarnings: 0,
};
},
componentDidMount: function() {
AppStateIOS.addEventListener('change', this._handleAppStateChange);
AppStateIOS.addEventListener('memoryWarning', this._handleMemoryWarning);
},
componentWillUnmount: function() {
AppStateIOS.removeEventListener('change', this._handleAppStateChange);
AppStateIOS.removeEventListener('memoryWarning', this._handleMemoryWarning);
},
_handleMemoryWarning: function() {
this.setState({memoryWarnings: this.state.memoryWarnings + 1})
},
_handleAppStateChange: function(appState) {
var previousAppStates = this.state.previousAppStates.slice();
@ -45,6 +51,13 @@ var AppStateSubscription = React.createClass({
});
},
render() {
if (this.props.showMemoryWarnings) {
return (
<View>
<Text>{this.state.memoryWarnings}</Text>
</View>
);
}
if (this.props.showCurrentOnly) {
return (
<View>
@ -77,4 +90,9 @@ exports.examples = [
title: 'Previous states:',
render(): ReactElement { return <AppStateSubscription showCurrentOnly={false} />; }
},
{
title: 'Memory Warnings',
description: "In the simulator, hit Shift+Command+M to simulate a memory warning.",
render(): ReactElement { return <AppStateSubscription showMemoryWarnings={true} />; }
},
];

View File

@ -80,6 +80,13 @@ var styles = StyleSheet.create({
borderTopLeftRadius: 100,
},
border7: {
borderRadius: 20,
},
border7_inner: {
backgroundColor: 'blue',
flex: 1,
},
});
exports.title = 'Border';
@ -134,4 +141,15 @@ exports.examples = [
return <View style={[styles.box, styles.border6]} />;
}
},
{
title: 'Custom Borders',
description: 'borderRadius & clipping',
render() {
return (
<View style={[styles.box, styles.border7]}>
<View style={styles.border7_inner} />
</View>
);
}
},
];

View File

@ -48,7 +48,9 @@ var TabBarExample = React.createClass({
render: function() {
return (
<TabBarIOS>
<TabBarIOS
tintColor="black"
barTintColor="#3abeff">
<TabBarIOS.Item
title="Blue Tab"
selected={this.state.selectedTab === 'blueTab'}

View File

@ -58,6 +58,7 @@ var COMPONENTS = [
];
var APIS = [
require('./AccessibilityIOSExample'),
require('./ActionSheetIOSExample'),
require('./AdSupportIOSExample'),
require('./AlertIOSExample'),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -74,11 +74,8 @@
redboxError = [[RCTRedBox sharedInstance] currentErrorMessage];
foundElement = [self findSubviewInView:vc.view matching:^(UIView *view) {
if ([view respondsToSelector:@selector(attributedText)]) {
NSString *text = [(id)view attributedText].string;
if ([text isEqualToString:@"<View>"]) {
return YES;
}
if ([view.accessibilityLabel isEqualToString:@"<View>"]) {
return YES;
}
return NO;
}];

View File

@ -11,15 +11,18 @@
*/
'use strict';
var Map = require('Map');
var NativeModules = require('NativeModules');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTAppState = NativeModules.AppState;
var logError = require('logError');
var invariant = require('invariant');
var DEVICE_APPSTATE_EVENT = 'appStateDidChange';
var _appStateHandlers = {};
var _eventHandlers = {
change: new Map(),
memoryWarning: new Map(),
};
/**
* `AppStateIOS` can tell you if the app is in the foreground or background,
@ -82,12 +85,23 @@ var AppStateIOS = {
type: string,
handler: Function
) {
_appStateHandlers[handler] = RCTDeviceEventEmitter.addListener(
DEVICE_APPSTATE_EVENT,
(appStateData) => {
handler(appStateData.app_state);
}
invariant(
['change', 'memoryWarning'].indexOf(type) !== -1,
'Trying to subscribe to unknown event: "%s"', type
);
if (type === 'change') {
_eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
'appStateDidChange',
(appStateData) => {
handler(appStateData.app_state);
}
));
} else if (type === 'memoryWarning') {
_eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
'memoryWarning',
handler
));
}
},
/**
@ -97,11 +111,15 @@ var AppStateIOS = {
type: string,
handler: Function
) {
if (!_appStateHandlers[handler]) {
invariant(
['change', 'memoryWarning'].indexOf(type) !== -1,
'Trying to remove listener for unknown event: "%s"', type
);
if (!_eventHandlers[type].has(handler)) {
return;
}
_appStateHandlers[handler].remove();
_appStateHandlers[handler] = null;
_eventHandlers[type].get(handler).remove();
_eventHandlers[type].delete(handler);
},
currentState: (null : ?String),
@ -109,7 +127,7 @@ var AppStateIOS = {
};
RCTDeviceEventEmitter.addListener(
DEVICE_APPSTATE_EVENT,
'appStateDidChange',
(appStateData) => {
AppStateIOS.currentState = appStateData.app_state;
}

View File

@ -25,11 +25,22 @@ var TabBarIOS = React.createClass({
propTypes: {
style: View.propTypes.style,
/**
* Color of the currently selected tab icon
*/
tintColor: React.PropTypes.string,
/**
* Background color of the tab bar
*/
barTintColor: React.PropTypes.string
},
render: function() {
return (
<RCTTabBar style={[styles.tabGroup, this.props.style]}>
<RCTTabBar
style={[styles.tabGroup, this.props.style]}
tintColor={this.props.tintColor}
barTintColor={this.props.barTintColor}>
{this.props.children}
</RCTTabBar>
);

View File

@ -70,6 +70,14 @@ var TouchableHighlight = React.createClass({
*/
underlayColor: React.PropTypes.string,
style: View.propTypes.style,
/**
* Called immediately after the underlay is shown
*/
onShowUnderlay: React.PropTypes.func,
/**
* Called immediately after the underlay is hidden
*/
onHideUnderlay: React.PropTypes.func,
},
mixins: [NativeMethodsMixin, TimerMixin, Touchable.Mixin],
@ -159,6 +167,7 @@ var TouchableHighlight = React.createClass({
_showUnderlay: function() {
this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps);
this.refs[CHILD_REF].setNativeProps(this.state.activeProps);
this.props.onShowUnderlay && this.props.onShowUnderlay();
},
_hideUnderlay: function() {
@ -170,6 +179,7 @@ var TouchableHighlight = React.createClass({
...INACTIVE_UNDERLAY_PROPS,
style: this.state.underlayStyle,
});
this.props.onHideUnderlay && this.props.onHideUnderlay();
}
},

View File

@ -108,7 +108,7 @@ var ListView = React.createClass({
* You must provide a renderRow function. If you omit any of the other render
* functions, ListView will simply skip rendering them.
*
* - renderRow(rowData, sectionID, rowID);
* - renderRow(rowData, sectionID, rowID, highlightRow);
* - renderSectionHeader(sectionData, sectionID);
*/
propTypes: {
@ -116,11 +116,22 @@ var ListView = React.createClass({
dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired,
/**
* (rowData, sectionID, rowID) => renderable
* (sectionID, rowID, adjacentRowHighlighted) => renderable
* If provided, a renderable component to be rendered as the separator
* below each row but not the last row if there is a section header below.
* Take a sectionID and rowID of the row above and whether its adjacent row
* is highlighted.
*/
renderSeparator: PropTypes.func,
/**
* (rowData, sectionID, rowID, highlightRow) => renderable
* Takes a data entry from the data source and its ids and should return
* a renderable component to be rendered as the row. By default the data
* is exactly what was put into the data source, but it's also possible to
* provide custom extractors.
* provide custom extractors. ListView can be notified when a row is
* being highlighted by calling highlightRow function. The separators above and
* below will be hidden when a row is highlighted. The highlighted state of
* a row can be reset by calling highlightRow(null).
*/
renderRow: PropTypes.func.isRequired,
/**
@ -227,6 +238,7 @@ var ListView = React.createClass({
return {
curRenderedRowsCount: this.props.initialListSize,
prevRenderedRowsCount: 0,
highlightedRow: {},
};
},
@ -256,6 +268,10 @@ var ListView = React.createClass({
}
},
onRowHighlighted: function(sectionID, rowID) {
this.setState({highlightedRow: {sectionID, rowID}});
},
render: function() {
var bodyComponents = [];
@ -305,11 +321,28 @@ var ListView = React.createClass({
null,
dataSource.getRowData(sectionIdx, rowIdx),
sectionID,
rowID
rowID,
this.onRowHighlighted
)}
/>;
bodyComponents.push(row);
totalIndex++;
if (this.props.renderSeparator &&
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length -1)) {
var adjacentRowHighlighted =
this.state.highlightedRow.sectionID === sectionID && (
this.state.highlightedRow.rowID === rowID ||
this.state.highlightedRow.rowID === rowIDs[rowIdx + 1]
);
var separator = this.props.renderSeparator(
sectionID,
rowID,
adjacentRowHighlighted
);
bodyComponents.push(separator);
totalIndex++;
}
if (++rowCount === this.state.curRenderedRowsCount) {
break;
}

View File

@ -332,7 +332,7 @@ var Navigator = React.createClass({
this._subRouteFocus = [];
this.navigatorContext = {
// Actions for child navigators or interceptors:
setHandlerForRoute: this.setHandlerForRoute,
setHandlerForIndex: this.setHandlerForIndex,
request: this.request,
// Contextual utilities
@ -340,14 +340,13 @@ var Navigator = React.createClass({
getCurrentRoutes: this.getCurrentRoutes,
// `route` is injected by NavigatorStaticContextContainer
// Contextual nav actions
// Contextual nav action
pop: this.requestPop,
popToRoute: this.requestPopTo,
// Legacy, imperitive nav actions. Will transition these to contextual actions
jumpBack: this.jumpBack,
jumpForward: this.jumpForward,
jumpTo: this.jumpTo,
popToRoute: this.popToRoute,
push: this.push,
replace: this.replace,
replaceAtIndex: this.replaceAtIndex,
@ -410,8 +409,6 @@ var Navigator = React.createClass({
switch (action) {
case 'pop':
return this._handlePop(arg1);
case 'popTo':
return this._handlePopTo(arg1);
case 'push':
return this._handlePush(arg1);
default:
@ -440,30 +437,13 @@ var Navigator = React.createClass({
return true;
},
_handlePopTo: function(destRoute) {
if (destRoute) {
var hasRoute = this.state.routeStack.indexOf(destRoute) !== -1;
if (hasRoute) {
this.popToRoute(destRoute);
return true;
} else {
return false;
}
}
if (this.state.presentedIndex === 0) {
return false;
}
this.pop();
return true;
},
_handlePush: function(route) {
this.push(route);
return true;
},
setHandlerForRoute: function(route, handler) {
this._handlers[this.state.routeStack.indexOf(route)] = handler;
setHandlerForIndex: function(index, handler) {
this._handlers[index] = handler;
},
componentDidMount: function() {
@ -1155,17 +1135,13 @@ var Navigator = React.createClass({
this.popToRoute(this.state.routeStack[0]);
},
_getNumToPopForRoute: function(route) {
popToRoute: function(route) {
var indexOfRoute = this.state.routeStack.indexOf(route);
invariant(
indexOfRoute !== -1,
'Calling pop to route for a route that doesn\'t exist!'
'Calling popToRoute for a route that doesn\'t exist!'
);
return this.state.presentedIndex - indexOfRoute;
},
popToRoute: function(route) {
var numToPop = this._getNumToPopForRoute(route);
var numToPop = this.state.presentedIndex - indexOfRoute;
this._popN(numToPop);
},
@ -1262,7 +1238,7 @@ var Navigator = React.createClass({
...this.navigatorContext,
route,
setHandler: (handler) => {
this.navigatorContext.setHandlerForRoute(route, handler);
this.navigatorContext.setHandlerForIndex(i, handler);
},
onWillFocus: (childRoute) => {
this._subRouteFocus[i] = childRoute;

View File

@ -85,7 +85,9 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /
- (void)dealloc
{
[_timeoutTimer invalidate];
if (_timeoutTimer.valid) {
[_timeoutTimer invalidate];
}
}
@end
@ -273,6 +275,7 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
// Fire all queued callbacks
for (RCTLocationRequest *request in _pendingRequests) {
request.successBlock(@[_lastLocationEvent]);
[request.timeoutTimer invalidate];
}
[_pendingRequests removeAllObjects];
@ -311,6 +314,7 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
// Fire all queued error callbacks
for (RCTLocationRequest *request in _pendingRequests) {
request.errorBlock(@[jsError]);
[request.timeoutTimer invalidate];
}
[_pendingRequests removeAllObjects];

62
Libraries/Inspector.js Normal file
View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Inspector
*/
'use strict';
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactNativeMount = require('ReactNativeMount');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
function traverseOwnerTreeUp(hierarchy, instance) {
if (instance) {
hierarchy.unshift(instance);
traverseOwnerTreeUp(hierarchy, instance._currentElement._owner);
}
}
function findInstance(component, targetID) {
if (targetID === findRootNodeID(component)) {
return component;
}
if (component._renderedComponent) {
return findInstance(component._renderedComponent, targetID);
} else {
for (var key in component._renderedChildren) {
var child = component._renderedChildren[key];
if (ReactInstanceHandles.isAncestorIDOf(findRootNodeID(child), targetID)) {
var instance = findInstance(child, targetID);
if (instance) {
return instance;
}
}
}
}
}
function findRootNodeID(component) {
var internalInstance = ReactInstanceMap.get(component);
return internalInstance ? internalInstance._rootNodeID : component._rootNodeID;
}
function findInstanceByNativeTag(rootTag, nativeTag) {
var containerID = ReactNativeTagHandles.tagToRootNodeID[rootTag];
var rootInstance = ReactNativeMount._instancesByContainerID[containerID];
var targetID = ReactNativeTagHandles.tagToRootNodeID[nativeTag];
return findInstance(rootInstance, targetID);
}
function getOwnerHierarchy(instance) {
var hierarchy = [];
traverseOwnerTreeUp(hierarchy, instance);
return hierarchy;
}
module.exports = {findInstanceByNativeTag, getOwnerHierarchy};

View File

@ -23,7 +23,7 @@ class XMLHttpRequestBase {
readyState: number;
responseHeaders: ?Object;
responseText: ?string;
status: ?string;
status: number;
_method: ?string;
_url: ?string;
@ -43,7 +43,7 @@ class XMLHttpRequestBase {
this.readyState = this.UNSENT;
this.responseHeaders = undefined;
this.responseText = undefined;
this.status = undefined;
this.status = 0;
this._method = null;
this._url = null;
@ -73,7 +73,7 @@ class XMLHttpRequestBase {
}
setRequestHeader(header: string, value: any): void {
this._headers[header] = value;
this._headers[header.toLowerCase()] = value;
}
open(method: string, url: string, async: ?boolean): void {
@ -127,7 +127,7 @@ class XMLHttpRequestBase {
this._aborted = true;
}
callback(status: string, responseHeaders: ?Object, responseText: string): void {
callback(status: number, responseHeaders: ?Object, responseText: string): void {
if (this._aborted) {
return;
}

View File

@ -0,0 +1,112 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule InspectorOverlay
*/
'use strict';
var Dimensions = require('Dimensions');
var Inspector = require('Inspector');
var React = require('React');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var UIManager = require('NativeModules').UIManager;
var View = require('View');
var InspectorOverlay = React.createClass({
getInitialState: function() {
return {
frame: null,
hierarchy: [],
};
},
findViewForTouchEvent: function(e) {
var {locationX, locationY} = e.nativeEvent.touches[0];
UIManager.findSubviewIn(
this.props.inspectedViewTag,
[locationX, locationY],
(nativeViewTag, left, top, width, height) => {
var instance = Inspector.findInstanceByNativeTag(this.props.rootTag, nativeViewTag);
var hierarchy = Inspector.getOwnerHierarchy(instance);
this.setState({
hierarchy,
frame: {left, top, width, height}
});
}
);
},
shouldSetResponser: function(e) {
this.findViewForTouchEvent(e);
return true;
},
render: function() {
var content = [];
if (this.state.frame) {
var distanceToTop = this.state.frame.top;
var distanceToBottom = Dimensions.get('window').height -
(this.state.frame.top + this.state.frame.height);
var justifyContent = distanceToTop > distanceToBottom
? 'flex-start'
: 'flex-end';
content.push(<View style={[styles.frame, this.state.frame]} />);
content.push(<ElementProperties hierarchy={this.state.hierarchy} />);
}
return (
<View
onStartShouldSetResponder={this.shouldSetResponser}
onResponderMove={this.findViewForTouchEvent}
style={[styles.inspector, {justifyContent}]}>
{content}
</View>
);
}
});
var ElementProperties = React.createClass({
render: function() {
var path = this.props.hierarchy.map((instance) => instance.getName()).join(' > ');
return (
<View style={styles.info}>
<Text style={styles.path}>
{path}
</Text>
</View>
);
}
});
var styles = StyleSheet.create({
inspector: {
backgroundColor: 'rgba(255,255,255,0.8)',
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
},
frame: {
position: 'absolute',
backgroundColor: 'rgba(155,155,255,0.3)',
},
info: {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
padding: 10,
},
path: {
color: 'white',
fontSize: 9,
}
});
module.exports = InspectorOverlay;

View File

@ -7,17 +7,59 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule renderApplication
* @flow
*/
'use strict';
var InspectorOverlay = require('InspectorOverlay');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var React = require('React');
var StyleSheet = require('StyleSheet');
var Subscribable = require('Subscribable');
var View = require('View');
var WarningBox = require('WarningBox');
var invariant = require('invariant');
var AppContainer = React.createClass({
mixins: [Subscribable.Mixin],
getInitialState: function() {
return { inspector: null };
},
toggleElementInspector: function() {
var inspector = this.state.inspector
? null
: <InspectorOverlay
rootTag={this.props.rootTag}
inspectedViewTag={React.findNodeHandle(this.refs.main)}
/>;
this.setState({inspector});
},
componentDidMount: function() {
this.addListenerOn(
RCTDeviceEventEmitter,
'toggleElementInspector',
this.toggleElementInspector
);
},
render: function() {
var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled;
var warningBox = shouldRenderWarningBox ? <WarningBox /> : null;
return (
<View style={styles.appContainer}>
<View style={styles.appContainer} ref="main">
{this.props.children}
</View>
{warningBox}
{this.state.inspector}
</View>
);
}
});
function renderApplication<D, P, S>(
RootComponent: ReactClass<D, P, S>,
initialProps: P,
@ -27,15 +69,12 @@ function renderApplication<D, P, S>(
rootTag,
'Expect to have a valid rootTag, instead got ', rootTag
);
var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled;
var warningBox = shouldRenderWarningBox ? <WarningBox /> : null;
React.render(
<View style={styles.appContainer}>
<AppContainer rootTag={rootTag}>
<RootComponent
{...initialProps}
/>
{warningBox}
</View>,
</AppContainer>,
rootTag
);
}

View File

@ -21,6 +21,7 @@ var pointsDiffer = require('pointsDiffer');
var matricesDiffer = require('matricesDiffer');
var sizesDiffer = require('sizesDiffer');
var verifyPropTypes = require('verifyPropTypes');
var warning = require('warning');
/**
* Used to create React components that directly wrap native component
@ -42,12 +43,13 @@ function requireNativeComponent(
wrapperComponent: ?Function
): Function {
var viewConfig = RCTUIManager[viewName];
if (!viewConfig || !viewConfig.nativeProps) {
if (!viewConfig || !viewConfig.NativeProps) {
warning(false, 'Native component for "%s" does not exist', viewName);
return UnimplementedView;
}
var nativeProps = {
...RCTUIManager.RCTView.nativeProps,
...viewConfig.nativeProps,
...RCTUIManager.RCTView.NativeProps,
...viewConfig.NativeProps,
};
viewConfig.uiViewClassName = viewName;
viewConfig.validAttributes = {};

View File

@ -29,7 +29,7 @@ function verifyPropTypes(
);
}
var nativeProps = viewConfig.nativeProps;
var nativeProps = viewConfig.NativeProps;
for (var prop in nativeProps) {
if (!component.propTypes[prop] &&
!View.propTypes[prop] &&

View File

@ -14,8 +14,6 @@ extern NSString *const RCTReactTagAttributeName;
@interface RCTShadowText : RCTShadowView
@property (nonatomic, assign) NSWritingDirection writingDirection;
@property (nonatomic, strong) UIColor *textBackgroundColor;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, copy) NSString *fontFamily;
@property (nonatomic, assign) CGFloat fontSize;
@ -24,17 +22,12 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, assign) BOOL isHighlighted;
@property (nonatomic, assign) CGFloat letterSpacing;
@property (nonatomic, assign) CGFloat lineHeight;
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
@property (nonatomic, assign) NSUInteger numberOfLines;
@property (nonatomic, assign) CGSize shadowOffset;
@property (nonatomic, assign) NSTextAlignment textAlign;
@property (nonatomic, strong) UIColor *textBackgroundColor;
@property (nonatomic, assign) NSWritingDirection writingDirection;
// Not exposed to JS
@property (nonatomic, strong) UIFont *font;
@property (nonatomic, assign) NSLineBreakMode truncationMode;
@property (nonatomic, assign) CGFloat effectiveLetterSpacing;
@property (nonatomic, copy, readonly) NSAttributedString *attributedString;
@property (nonatomic, strong, readonly) NSLayoutManager *layoutManager;
@property (nonatomic, strong, readonly) NSTextContainer *textContainer;
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width;
@end

View File

@ -17,62 +17,61 @@
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
@implementation RCTShadowText
{
NSAttributedString *_cachedAttributedString;
CGFloat _effectiveLetterSpacing;
}
static css_dim_t RCTMeasure(void *context, float width)
{
RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[shadowText attributedString]];
NSTextStorage *previousTextStorage = shadowText.layoutManager.textStorage;
if (previousTextStorage) {
[previousTextStorage removeLayoutManager:shadowText.layoutManager];
}
[textStorage addLayoutManager:shadowText.layoutManager];
shadowText.textContainer.size = CGSizeMake(isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX);
[shadowText.layoutManager ensureLayoutForTextContainer:shadowText.textContainer];
CGSize computedSize = [shadowText.layoutManager usedRectForTextContainer:shadowText.textContainer].size;
[textStorage removeLayoutManager:shadowText.layoutManager];
if (previousTextStorage) {
[previousTextStorage addLayoutManager:shadowText.layoutManager];
}
NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:width];
NSLayoutManager *layoutManager = [textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
CGSize computedSize = [layoutManager usedRectForTextContainer:textContainer].size;
css_dim_t result;
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
if (shadowText.effectiveLetterSpacing < 0) {
result.dimensions[CSS_WIDTH] -= shadowText.effectiveLetterSpacing;
if (shadowText->_effectiveLetterSpacing < 0) {
result.dimensions[CSS_WIDTH] -= shadowText->_effectiveLetterSpacing;
}
result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(computedSize.height);
return result;
}
@implementation RCTShadowText
{
NSLayoutManager *_layoutManager;
NSTextContainer *_textContainer;
NSAttributedString *_cachedAttributedString;
UIFont *_font;
}
- (instancetype)init
{
if ((self = [super init])) {
_fontSize = NAN;
_letterSpacing = NAN;
_isHighlighted = NO;
_textContainer = [[NSTextContainer alloc] init];
_textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
_textContainer.lineFragmentPadding = 0.0;
_layoutManager = [[NSLayoutManager alloc] init];
[_layoutManager addTextContainer:_textContainer];
}
return self;
}
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width
{
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedString];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] init];
textContainer.lineFragmentPadding = 0.0;
textContainer.lineBreakMode = _numberOfLines > 0 ? NSLineBreakByTruncatingTail : NSLineBreakByClipping;
textContainer.maximumNumberOfLines = _numberOfLines;
UIEdgeInsets padding = self.paddingAsInsets;
width -= (padding.left + padding.right);
textContainer.size = (CGSize){isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX};
[layoutManager addTextContainer:textContainer];
[layoutManager ensureLayoutForTextContainer:textContainer];
return textStorage;
}
- (NSAttributedString *)attributedString
{
return [self _attributedStringWithFontFamily:nil
@ -135,8 +134,8 @@ 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 style:fontStyle];
[self _addAttribute:NSFontAttributeName withValue:_font toAttributedString:attributedString];
UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle];
[self _addAttribute:NSFontAttributeName withValue:font toAttributedString:attributedString];
[self _addAttribute:NSKernAttributeName withValue:letterSpacing toAttributedString:attributedString];
[self _addAttribute:RCTReactTagAttributeName withValue:self.reactTag toAttributedString:attributedString];
[self _setParagraphStyleOnAttributedString:attributedString];
@ -148,11 +147,6 @@ static css_dim_t RCTMeasure(void *context, float width)
return _cachedAttributedString;
}
- (UIFont *)font
{
return _font ?: [RCTConvert UIFont:nil withFamily:_fontFamily size:@(_fontSize) weight:_fontWeight style:_fontStyle];
}
- (void)_addAttribute:(NSString *)attribute withValue:(id)attributeValue toAttributedString:(NSMutableAttributedString *)attributedString
{
[attributedString enumerateAttribute:attribute inRange:NSMakeRange(0, [attributedString length]) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
@ -231,38 +225,18 @@ static css_dim_t RCTMeasure(void *context, float width)
[self dirtyText]; \
}
RCT_TEXT_PROPERTY(TextBackgroundColor, _textBackgroundColor, UIColor *);
RCT_TEXT_PROPERTY(Color, _color, UIColor *);
RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *);
RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat);
RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *);
RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat);
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat);
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize);
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment);
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL);
RCT_TEXT_PROPERTY(Font, _font, UIFont *);
- (NSLineBreakMode)truncationMode
{
return _textContainer.lineBreakMode;
}
- (void)setTruncationMode:(NSLineBreakMode)truncationMode
{
_textContainer.lineBreakMode = truncationMode;
[self dirtyText];
}
- (NSUInteger)maximumNumberOfLines
{
return _textContainer.maximumNumberOfLines;
}
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
{
_textContainer.maximumNumberOfLines = maximumNumberOfLines;
[self dirtyText];
}
RCT_TEXT_PROPERTY(Color, _color, UIColor *)
RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *)
RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat)
RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *)
RCT_TEXT_PROPERTY(FontStyle, _fontStyle, NSString *)
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL)
RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat)
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat)
RCT_TEXT_PROPERTY(NumberOfLines, _numberOfLines, NSUInteger)
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize)
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment)
RCT_TEXT_PROPERTY(TextBackgroundColor, _textBackgroundColor, UIColor *)
RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection)
@end

View File

@ -11,9 +11,7 @@
@interface RCTText : UIView
@property (nonatomic, strong) NSLayoutManager *layoutManager;
@property (nonatomic, strong) NSTextContainer *textContainer;
@property (nonatomic, copy) NSAttributedString *attributedText;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, strong) NSTextStorage *textStorage;
@end

View File

@ -15,9 +15,7 @@
@implementation RCTText
{
NSLayoutManager *_layoutManager;
NSTextStorage *_textStorage;
NSTextContainer *_textContainer;
}
- (instancetype)initWithFrame:(CGRect)frame
@ -31,103 +29,42 @@
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
}
return self;
}
- (NSAttributedString *)attributedText
- (void)setTextStorage:(NSTextStorage *)textStorage
{
return [_textStorage copy];
}
- (void)setAttributedText:(NSAttributedString *)attributedText
{
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
[_textStorage removeLayoutManager:existingLayoutManager];
}
_textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText];
if (_layoutManager) {
[_textStorage addLayoutManager:_layoutManager];
}
_textStorage = textStorage;
[self setNeedsDisplay];
}
- (void)setTextContainer:(NSTextContainer *)textContainer
{
if ([_textContainer isEqual:textContainer]) {
return;
}
_textContainer = textContainer;
for (NSInteger i = _layoutManager.textContainers.count - 1; i >= 0; i--) {
[_layoutManager removeTextContainerAtIndex:i];
}
if (_textContainer) {
[_layoutManager addTextContainer:_textContainer];
}
[self setNeedsDisplay];
}
- (void)setLayoutManager:(NSLayoutManager *)layoutManager
{
if ([_layoutManager isEqual:layoutManager]) {
return;
}
_layoutManager = layoutManager;
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
[_textStorage removeLayoutManager:existingLayoutManager];
}
if (_layoutManager) {
[_textStorage addLayoutManager:_layoutManager];
}
[self setNeedsDisplay];
}
- (CGRect)textFrame
{
return UIEdgeInsetsInsetRect(self.bounds, _contentInset);
}
- (void)drawRect:(CGRect)rect
{
CGRect textFrame = [self textFrame];
// We reset the text container size every time because RCTShadowText's
// RCTMeasure overrides it. The header comment for `size` says that a height
// of 0.0 should be enough, but it isn't.
_textContainer.size = CGSizeMake(textFrame.size.width, CGFLOAT_MAX);
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
NSLayoutManager *layoutManager = [_textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
CGRect textFrame = UIEdgeInsetsInsetRect(self.bounds, _contentInset);
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
CGFloat fraction;
NSUInteger characterIndex = [_layoutManager characterIndexForPoint:point
inTextContainer:_textContainer
fractionOfDistanceBetweenInsertionPoints:&fraction];
NSNumber *reactTag = self.reactTag;
NSNumber *reactTag = nil;
CGFloat fraction;
NSLayoutManager *layoutManager = [_textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
NSUInteger characterIndex = [layoutManager characterIndexForPoint:point
inTextContainer:textContainer
fractionOfDistanceBetweenInsertionPoints:&fraction];
// If the point is not before (fraction == 0.0) the first character and not
// after (fraction == 1.0) the last character, then the attribute is valid.
if (_textStorage.length > 0 && (fraction > 0 || characterIndex > 0) && (fraction < 1 || characterIndex < _textStorage.length - 1)) {
reactTag = [_textStorage attribute:RCTReactTagAttributeName atIndex:characterIndex effectiveRange:NULL];
}
return reactTag ?: self.reactTag;
return reactTag;
}
#pragma mark - Accessibility

View File

@ -47,24 +47,11 @@ RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString)
RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger)
RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize)
RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textBackgroundColor, UIColor)
RCT_CUSTOM_SHADOW_PROPERTY(containerBackgroundColor, UIColor, RCTShadowText)
{
view.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
{
NSLineBreakMode truncationMode = NSLineBreakByClipping;
view.maximumNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maximumNumberOfLines;
if (view.maximumNumberOfLines > 0) {
truncationMode = NSLineBreakByTruncatingTail;
}
view.truncationMode = truncationMode;
}
RCT_REMAP_SHADOW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
@ -81,7 +68,7 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
continue;
}
RCTSparseArray *reactTaggedAttributedStrings = [[RCTSparseArray alloc] init];
RCTSparseArray *reactTaggedTextStorage = [[RCTSparseArray alloc] init];
NSMutableArray *queue = [NSMutableArray arrayWithObject:rootView];
for (NSInteger i = 0; i < [queue count]; i++) {
RCTShadowView *shadowView = queue[i];
@ -89,9 +76,11 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
if ([shadowView isKindOfClass:[RCTShadowText class]]) {
RCTShadowText *shadowText = (RCTShadowText *)shadowView;
reactTaggedAttributedStrings[shadowText.reactTag] = [shadowText attributedString];
NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:shadowView.frame.size.width];
reactTaggedTextStorage[shadowText.reactTag] = textStorage;
} else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) {
RCTLogError(@"Raw text cannot be used outside of a <Text> tag. Not rendering string: '%@'", [(RCTShadowRawText *)shadowView text]);
RCTLogError(@"Raw text cannot be used outside of a <Text> tag. Not rendering string: '%@'",
[(RCTShadowRawText *)shadowView text]);
} else {
for (RCTShadowView *child in [shadowView reactSubviews]) {
if ([child isTextDirty]) {
@ -104,9 +93,9 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
}
[uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
[reactTaggedAttributedStrings enumerateObjectsUsingBlock:^(NSAttributedString *attributedString, NSNumber *reactTag, BOOL *stop) {
[reactTaggedTextStorage enumerateObjectsUsingBlock:^(NSTextStorage *textStorage, NSNumber *reactTag, BOOL *stop) {
RCTText *text = viewRegistry[reactTag];
text.attributedText = attributedString;
text.textStorage = textStorage;
}];
}];
}
@ -126,8 +115,6 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
RCTText *text = viewRegistry[reactTag];
text.contentInset = padding;
text.layoutManager = shadowView.layoutManager;
text.textContainer = shadowView.textContainer;
};
}

View File

@ -1,5 +1,5 @@
/**
* @generated SignedSource<<9bce659a43d6f6115b20a18f6c995d8a>>
* @generated SignedSource<<77bdeb858138636c96c405d64b6be55c>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
@ -20,57 +20,73 @@
/* jslint bitwise: true */
/**
* Copyright (c) 2006 Andrea Ercolino
* http://www.opensource.org/licenses/mit-license.php
* Modified from the original for performance improvements.
*
* @see http://create.stephan-brumme.com/crc32/
* @see http://stackoverflow.com/questions/18638900/
* @copyright 2006 Andrea Ercolino
* @license MIT
*/
var table = '00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 ' +
'9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 ' +
'90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 ' +
'83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 ' +
'8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD ' +
'A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ' +
'ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 ' +
'B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB ' +
'B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 ' +
'E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 ' +
'E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 ' +
'F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 ' +
'FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D ' +
'D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F ' +
'DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 ' +
'CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B ' +
'C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 ' +
'73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 ' +
'7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 ' +
'6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 ' +
'60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD ' +
'48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF ' +
'4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 ' +
'5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B ' +
'5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 ' +
'05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 ' +
'0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 ' +
'18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 ' +
'166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D ' +
'3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F ' +
'30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 ' +
'23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B ' +
'2D02EF8D';
var table = [
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
];
if (global.Int32Array !== undefined) {
table = new Int32Array(table);
}
/**
* @returns Number
*/
function crc32(str) {
var crc = 0;
var n = 0;
var x = 0;
crc = crc ^ (-1);
for (var i = 0, iTop = str.length; i < iTop; i++) {
n = (crc ^ str.charCodeAt(i)) & 0xFF;
x = "0x" + table.substr(n * 9, 8);
crc = (crc >>> 8) ^ x;
var crc = -1;
for (var i = 0, len = str.length; i < len; i++) {
crc = (crc >>> 8) ^ table[(crc ^ str.charCodeAt(i)) & 0xFF];
}
return crc ^ (-1);
return ~crc;
}
module.exports = crc32;

View File

@ -62,3 +62,31 @@ RCT_EXTERN RCTAssertFunction RCTGetAssertFunction(void);
* assert info to an extra service without changing the default behavior.
*/
RCT_EXTERN void RCTAddAssertFunction(RCTAssertFunction assertFunction);
/**
* Get the current thread's name (or the current queue, if in debug mode)
*/
RCT_EXTERN NSString *RCTCurrentThreadName(void);
/**
* Convenience macro to assert which thread is currently running (DEBUG mode only)
*/
#if DEBUG
#define RCTAssertThread(thread, format...) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \
RCTAssert( \
[(id)thread isKindOfClass:[NSString class]] ? \
[RCTCurrentThreadName() isEqualToString:(NSString *)thread] : \
[(id)thread isKindOfClass:[NSThread class]] ? \
[NSThread currentThread] == (NSThread *)thread : \
dispatch_get_current_queue() == (dispatch_queue_t)thread, \
format); \
_Pragma("clang diagnostic pop")
#else
#define RCTAssertThread(thread, format...)
#endif

View File

@ -60,3 +60,20 @@ void RCTAddAssertFunction(RCTAssertFunction assertFunction)
RCTCurrentAssertFunction = assertFunction;
}
}
NSString *RCTCurrentThreadName(void)
{
NSThread *thread = [NSThread currentThread];
NSString *threadName = [thread isMainThread] ? @"main" : thread.name;
if (threadName.length == 0) {
#if DEBUG // This is DEBUG not RCT_DEBUG because it *really* must not ship in RC
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
#pragma clang diagnostic pop
#else
threadName = [NSString stringWithFormat:@"%p", thread];
#endif
}
return threadName;
}

View File

@ -95,6 +95,11 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
/**
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
* higher-level interface for sending UI events such as touches and text input.
*
* NOTE: RCTEventDispatcher is now a bridge module, this is implemented as a
* category but remains declared in the bridge to avoid breaking changes
*
* To be moved.
*/
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;

View File

@ -18,7 +18,6 @@
#import "RCTContextExecutor.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTJavaScriptLoader.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
@ -211,7 +210,6 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
@property (nonatomic, strong) RCTBatchedBridge *batchedBridge;
@property (nonatomic, strong) RCTBridgeModuleProviderBlock moduleProvider;
@property (nonatomic, strong, readwrite) RCTEventDispatcher *eventDispatcher;
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
@ -875,11 +873,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return _batchedBridge.modules;
}
- (RCTEventDispatcher *)eventDispatcher
{
return _eventDispatcher ?: _batchedBridge.eventDispatcher;
}
#define RCT_INNER_BRIDGE_ONLY(...) \
- (void)__VA_ARGS__ \
{ \
@ -943,11 +936,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
_javaScriptExecutor = RCTCreateExecutor(executorClass);
_latestJSExecutor = _javaScriptExecutor;
/**
* Setup event dispatcher before initializing modules to allow init calls
*/
self.eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
/**
* Initialize and register bridge modules *before* adding the display link
* so we don't have threading issues

View File

@ -11,6 +11,7 @@
#import "RCTBridge.h"
#import "RCTDefines.h"
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerfStats.h"
@ -241,6 +242,8 @@ RCT_EXPORT_METHOD(show)
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil];
[actionSheet addButtonWithTitle:@"Inspect Element"];
if (_liveReloadURL) {
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
@ -300,10 +303,14 @@ RCT_EXPORT_METHOD(reload)
break;
}
case 4: {
self.liveReloadEnabled = !_liveReloadEnabled;
[_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
break;
}
case 5: {
self.liveReloadEnabled = !_liveReloadEnabled;
break;
}
case 6: {
self.profilingEnabled = !_profilingEnabled;
break;
}

View File

@ -9,7 +9,7 @@
#import <UIKit/UIKit.h>
@class RCTBridge;
#import "RCTBridge.h"
typedef NS_ENUM(NSInteger, RCTTextEventType) {
RCTTextEventTypeFocus,
@ -28,14 +28,36 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
RCTScrollEventTypeEndAnimation,
};
@protocol RCTEvent <NSObject>
@required
@property (nonatomic, strong, readonly) NSNumber *viewTag;
@property (nonatomic, copy, readonly) NSString *eventName;
@property (nonatomic, copy, readonly) NSDictionary *body;
@property (nonatomic, assign, readonly) uint16_t coalescingKey;
- (BOOL)canCoalesce;
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent;
+ (NSString *)moduleDotMethod;
@end
@interface RCTBaseEvent : NSObject <RCTEvent>
- (instancetype)initWithViewTag:(NSNumber *)viewTag
eventName:(NSString *)eventName
body:(NSDictionary *)body NS_DESIGNATED_INITIALIZER;
@end
/**
* This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
* provides some convenience methods for generating event calls.
*/
@interface RCTEventDispatcher : NSObject
- (instancetype)initWithBridge:(RCTBridge *)bridge;
/**
* Send an application-specific event that does not relate to a specific
* view, e.g. a navigation or data update notification.
@ -61,13 +83,6 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
reactTag:(NSNumber *)reactTag
text:(NSString *)text;
/**
* Send a scroll event.
* (You can send a fake scroll event by passing nil for scrollView).
*/
- (void)sendScrollEventWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData;
- (void)sendEvent:(id<RCTEvent>)event;
@end

View File

@ -11,16 +11,76 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTSparseArray.h"
static uint64_t RCTGetEventID(id<RCTEvent> event)
{
return (
[event.viewTag intValue] |
(((uint64_t)event.eventName.hash & 0xFFFF) << 32) |
(((uint64_t)event.coalescingKey) << 48)
);
}
@implementation RCTBaseEvent
@synthesize viewTag = _viewTag;
@synthesize eventName = _eventName;
@synthesize body = _body;
- (instancetype)initWithViewTag:(NSNumber *)viewTag
eventName:(NSString *)eventName
body:(NSDictionary *)body
{
if ((self = [super init])) {
_viewTag = viewTag;
_eventName = eventName;
_body = body;
}
return self;
}
- (uint16_t)coalescingKey
{
return 0;
}
- (BOOL)canCoalesce
{
return YES;
}
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
{
return newEvent;
}
+ (NSString *)moduleDotMethod
{
return nil;
}
@end
@interface RCTEventDispatcher() <RCTBridgeModule, RCTFrameUpdateObserver>
@end
@implementation RCTEventDispatcher
{
RCTBridge __weak *_bridge;
RCTSparseArray *_eventQueue;
NSLock *_eventQueueLock;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
- (instancetype)init
{
if ((self = [super init])) {
_bridge = bridge;
_eventQueue = [[RCTSparseArray alloc] init];
_eventQueueLock = [[NSLock alloc] init];
}
return self;
}
@ -70,58 +130,71 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent);
}];
}
/**
* TODO: throttling
* NOTE: the old system used a per-scrollview throttling
* which would be fairly easy to re-implement if needed,
* but this is non-optimal as it leads to degradation in
* scroll responsiveness. A better solution would be to
* coalesce multiple scroll events into a single batch.
*/
- (void)sendScrollEventWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData
- (void)sendEvent:(id<RCTEvent>)event
{
static NSString *events[] = {
@"topScrollBeginDrag",
@"topScroll",
@"topScrollEndDrag",
@"topMomentumScrollBegin",
@"topMomentumScrollEnd",
@"topScrollAnimationEnd",
};
NSDictionary *body = @{
@"contentOffset": @{
@"x": @(scrollView.contentOffset.x),
@"y": @(scrollView.contentOffset.y)
},
@"contentInset": @{
@"top": @(scrollView.contentInset.top),
@"left": @(scrollView.contentInset.left),
@"bottom": @(scrollView.contentInset.bottom),
@"right": @(scrollView.contentInset.right)
},
@"contentSize": @{
@"width": @(scrollView.contentSize.width),
@"height": @(scrollView.contentSize.height)
},
@"layoutMeasurement": @{
@"width": @(scrollView.frame.size.width),
@"height": @(scrollView.frame.size.height)
},
@"zoomScale": @(scrollView.zoomScale ?: 1),
@"target": reactTag
};
if (userData) {
NSMutableDictionary *mutableBody = [body mutableCopy];
[mutableBody addEntriesFromDictionary:userData];
body = mutableBody;
if (!event.canCoalesce) {
[self dispatchEvent:event];
return;
}
[self sendInputEventWithName:events[type] body:body];
[_eventQueueLock lock];
uint64_t eventID = RCTGetEventID(event);
id<RCTEvent> previousEvent = _eventQueue[eventID];
if (previousEvent) {
event = [previousEvent coalesceWithEvent:event];
}
_eventQueue[eventID] = event;
[_eventQueueLock unlock];
}
- (void)dispatchEvent:(id<RCTEvent>)event
{
NSMutableArray *arguments = [[NSMutableArray alloc] init];
if (event.viewTag) {
[arguments addObject:event.viewTag];
}
[arguments addObject:event.eventName];
if (event.body) {
[arguments addObject:event.body];
}
[_bridge enqueueJSCall:[[event class] moduleDotMethod]
args:arguments];
}
- (dispatch_queue_t)methodQueue
{
return RCTJSThread;
}
- (void)didUpdateFrame:(RCTFrameUpdate *)update
{
RCTSparseArray *eventQueue;
[_eventQueueLock lock];
eventQueue = _eventQueue;
_eventQueue = [[RCTSparseArray alloc] init];
[_eventQueueLock unlock];
for (id<RCTEvent> event in eventQueue.allObjects) {
[self dispatchEvent:event];
}
}
@end
@implementation RCTBridge (RCTEventDispatcher)
- (RCTEventDispatcher *)eventDispatcher
{
return self.modules[RCTBridgeModuleNameForClass([RCTEventDispatcher class])];
}
@end

View File

@ -42,18 +42,12 @@ typedef void (^RCTLogFunction)(
NSString *message
);
/**
* Get a given thread's name (or the current queue, if in debug mode)
*/
RCT_EXTERN NSString *RCTThreadName(NSThread *);
/**
* A method to generate a string from a collection of log data. To omit any
* particular data from the log, just pass nil or zero for the argument.
*/
RCT_EXTERN NSString *RCTFormatLog(
NSDate *timestamp,
NSThread *thread,
RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,

View File

@ -53,7 +53,7 @@ RCTLogFunction RCTDefaultLogFunction = ^(
)
{
NSString *log = RCTFormatLog(
[NSDate date], [NSThread currentThread], level, fileName, lineNumber, message
[NSDate date], level, fileName, lineNumber, message
);
fprintf(stderr, "%s\n", log.UTF8String);
fflush(stderr);
@ -99,25 +99,8 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
[prefixStack removeLastObject];
}
NSString *RCTThreadName(NSThread *thread)
{
NSString *threadName = [thread isMainThread] ? @"main" : thread.name;
if (threadName.length == 0) {
#if DEBUG // This is DEBUG not RCT_DEBUG because it *really* must not ship in RC
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
#pragma clang diagnostic pop
#else
threadName = [NSString stringWithFormat:@"%p", thread];
#endif
}
return threadName;
}
NSString *RCTFormatLog(
NSDate *timestamp,
NSThread *thread,
RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,
@ -137,9 +120,9 @@ NSString *RCTFormatLog(
if (level) {
[log appendFormat:@"[%s]", RCTLogLevels[level - 1]];
}
if (thread) {
[log appendFormat:@"[tid:%@]", RCTThreadName(thread)];
}
[log appendFormat:@"[tid:%@]", RCTCurrentThreadName()];
if (fileName) {
fileName = [fileName lastPathComponent];
if (lineNumber) {

View File

@ -13,8 +13,8 @@
#import <UIKit/UIKit.h>
#import "RCTAssert.h"
#import "RCTDefines.h"
#import "RCTLog.h"
#import "RCTUtils.h"
#if RCT_DEV
@ -43,7 +43,7 @@ NSLock *_RCTProfileLock;
#define RCTProfileAddEvent(type, props...) \
[RCTProfileInfo[type] addObject:@{ \
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \
@"tid": RCTThreadName([NSThread currentThread]), \
@"tid": RCTCurrentThreadName(), \
props \
}];

View File

@ -51,7 +51,7 @@
RCTRootContentView *_contentView;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
{
RCTAssertMainThread();
@ -87,6 +87,12 @@
return [self initWithBridge:bridge moduleName:moduleName];
}
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
super.backgroundColor = backgroundColor;
_contentView.backgroundColor = backgroundColor;
}
- (UIViewController *)backingViewController
{
return _backingViewController ?: [super backingViewController];
@ -100,7 +106,6 @@
RCT_IMPORT_METHOD(AppRegistry, runApplication)
RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
- (void)javaScriptDidLoad:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
@ -124,6 +129,7 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
[_contentView removeFromSuperview];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge];
_contentView.backgroundColor = self.backgroundColor;
[self addSubview:_contentView];
NSString *moduleName = _moduleName ?: @"";
@ -172,6 +178,7 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
{
__weak RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
UIColor *_backgroundColor;
}
- (instancetype)initWithFrame:(CGRect)frame
@ -181,18 +188,32 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
_bridge = bridge;
[self setUp];
self.frame = frame;
self.layer.backgroundColor = NULL;
}
return self;
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
super.frame = frame;
if (self.reactTag && _bridge.isValid) {
[_bridge.uiManager setFrame:self.bounds forRootView:self];
[_bridge.uiManager setFrame:frame forRootView:self];
}
}
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
_backgroundColor = backgroundColor;
if (self.reactTag && _bridge.isValid) {
[_bridge.uiManager setBackgroundColor:backgroundColor forRootView:self];
}
}
- (UIColor *)backgroundColor
{
return _backgroundColor;
}
- (void)setUp
{
/**

View File

@ -66,6 +66,7 @@
{
RCTJavaScriptContext *_context;
NSThread *_javaScriptThread;
JSValueRef _undefined;
}
/**
@ -237,6 +238,9 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
JSContextGroupRelease(group);
}
// Constant value used for comparison
_undefined = JSValueMakeUndefined(ctx);
strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx];
[strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
[strongSelf _addNativeHook:RCTNoop withName:"noop"];
@ -291,21 +295,77 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
return;
}
NSError *error;
NSString *argsString = RCTJSONStringify(arguments, &error);
NSString *argsString = (arguments.count == 1) ? RCTJSONStringify(arguments[0], &error) : RCTJSONStringify(arguments, &error);
if (!argsString) {
RCTLogError(@"Cannot convert argument to string: %@", error);
onComplete(nil, error);
return;
}
NSString *execString = [NSString stringWithFormat:@"require('%@').%@.apply(null, %@);", name, method, argsString];
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)execString);
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, NULL, 0, &jsError);
JSStringRelease(execJSString);
JSValueRef errorJSRef = NULL;
JSValueRef resultJSRef = NULL;
JSGlobalContextRef contextJSRef = JSContextGetGlobalContext(strongSelf->_context.ctx);
JSObjectRef globalObjectJSRef = JSContextGetGlobalObject(strongSelf->_context.ctx);
if (!result) {
onComplete(nil, RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError));
// get require
JSStringRef requireNameJSStringRef = JSStringCreateWithUTF8CString("require");
JSValueRef requireJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, requireNameJSStringRef, &errorJSRef);
JSStringRelease(requireNameJSStringRef);
if (requireJSRef != NULL && requireJSRef != _undefined && errorJSRef == NULL) {
// get module
JSStringRef moduleNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)name);
JSValueRef moduleNameJSRef = JSValueMakeString(contextJSRef, moduleNameJSStringRef);
JSValueRef moduleJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)requireJSRef, NULL, 1, (const JSValueRef *)&moduleNameJSRef, &errorJSRef);
JSStringRelease(moduleNameJSStringRef);
if (moduleJSRef != NULL && errorJSRef == NULL) {
// get method
JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method);
JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef);
JSStringRelease(methodNameJSStringRef);
if (methodJSRef != NULL && errorJSRef == NULL) {
// direct method invoke with no arguments
if (arguments.count == 0) {
resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef);
}
// direct method invoke with 1 argument
else if(arguments.count == 1) {
JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString);
JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef);
resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 1, &argsJSRef, &errorJSRef);
JSStringRelease(argsJSStringRef);
} else {
// apply invoke with array of arguments
JSStringRef applyNameJSStringRef = JSStringCreateWithUTF8CString("apply");
JSValueRef applyJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)methodJSRef, applyNameJSStringRef, &errorJSRef);
JSStringRelease(applyNameJSStringRef);
if (applyJSRef != NULL && errorJSRef == NULL) {
// invoke apply
JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString);
JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef);
JSValueRef args[2];
args[0] = JSValueMakeNull(contextJSRef);
args[1] = argsJSRef;
resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)applyJSRef, (JSObjectRef)methodJSRef, 2, args, &errorJSRef);
JSStringRelease(argsJSStringRef);
}
}
}
}
}
if (!resultJSRef) {
onComplete(nil, RCTNSErrorFromJSError(contextJSRef, errorJSRef));
return;
}
@ -315,8 +375,8 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
id objcValue;
// We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds
// to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster
if (!JSValueIsNull(strongSelf->_context.ctx, result)) {
JSStringRef jsJSONString = JSValueCreateJSONString(strongSelf->_context.ctx, result, 0, nil);
if (!JSValueIsNull(contextJSRef, resultJSRef)) {
JSStringRef jsJSONString = JSValueCreateJSONString(contextJSRef, resultJSRef, 0, nil);
if (jsJSONString) {
NSString *objcJSONString = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsJSONString);
JSStringRelease(jsJSONString);

View File

@ -53,11 +53,21 @@ RCT_EXPORT_MODULE()
selector:@selector(handleAppStateDidChange)
name:name
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
}
return self;
}
- (void)handleMemoryWarning
{
[_bridge.eventDispatcher sendDeviceEventWithName:@"memoryWarning"
body:nil];
}
- (void)dealloc
{

View File

@ -36,10 +36,16 @@
/**
* Update the frame of a root view. This might be in response to a screen rotation
* or some other layout event outsde of the React-managed view hierarchy.
* or some other layout event outside of the React-managed view hierarchy.
*/
- (void)setFrame:(CGRect)frame forRootView:(UIView *)rootView;
/**
* Update the background color of a root view. This is usually triggered by
* manually setting the background color of the root view with native code.
*/
- (void)setBackgroundColor:(UIColor *)color forRootView:(UIView *)rootView;
/**
* Schedule a block to be executed on the UI thread. Useful if you need to execute
* view logic after all currently queued view updates have completed.

View File

@ -338,17 +338,14 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
_viewRegistry[reactTag] = rootView;
CGRect frame = rootView.frame;
// Register manager (TODO: should we do this, or leave it nil?)
_viewManagerRegistry[reactTag] = _viewManagers[@"RCTView"];
// Register shadow view
dispatch_async(_shadowQueue, ^{
RCTShadowView *shadowView = [[RCTShadowView alloc] init];
shadowView.reactTag = reactTag;
shadowView.frame = frame;
shadowView.backgroundColor = [UIColor whiteColor];
shadowView.backgroundColor = rootView.backgroundColor;
shadowView.viewName = NSStringFromClass([rootView class]);
_shadowViewRegistry[shadowView.reactTag] = shadowView;
[_rootViewTags addObject:reactTag];
});
}
@ -372,6 +369,22 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
});
}
- (void)setBackgroundColor:(UIColor *)color forRootView:(UIView *)rootView
{
RCTAssertMainThread();
NSNumber *reactTag = rootView.reactTag;
RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag);
dispatch_async(_shadowQueue, ^{
RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
rootShadowView.backgroundColor = color;
[self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootShadowView];
[self flushUIBlocks];
});
}
/**
* Unregisters views from registries
*/
@ -390,6 +403,14 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
- (void)addUIBlock:(RCTViewManagerUIBlock)block
{
RCTAssertThread(_shadowQueue,
@"-[RCTUIManager addUIBlock:] should only be called from the "
"UIManager's _shadowQueue (it may be accessed via `bridge.uiManager.methodQueue`)");
if (!block) {
return;
}
if (!self.isValid) {
return;
}
@ -795,6 +816,11 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
}
_shadowViewRegistry[reactTag] = shadowView;
// Shadow view is the source of truth for background color this is a little
// bit counter-intuitive if people try to set background color when setting up
// the view, but it's the only way that makes sense given our threading model
UIColor *backgroundColor = shadowView.backgroundColor;
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
RCTAssertMainThread();
@ -803,14 +829,15 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
// Generate default view, used for resetting default props
if (!uiManager->_defaultViews[viewName]) {
// Note the default is setup after the props are read for the first time ever
// for this className - this is ok because we only use the default for restoring
// defaults, which never happens on first creation.
// Note the default is setup after the props are read for the first time
// ever for this className - this is ok because we only use the default
// for restoring defaults, which never happens on first creation.
uiManager->_defaultViews[viewName] = [manager view];
}
// Set properties
view.reactTag = reactTag;
view.backgroundColor = backgroundColor;
if ([view isKindOfClass:[UIView class]]) {
view.multipleTouchEnabled = YES;
view.userInteractionEnabled = YES; // required for touch handling
@ -859,17 +886,33 @@ RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag)
}];
}
- (void)batchDidComplete
{
// Gather blocks to be executed now that all view hierarchy manipulations have
// been completed (note that these may still take place before layout has finished)
for (RCTViewManager *manager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
if (uiBlock) {
[self addUIBlock:uiBlock];
}
RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point callback:(RCTResponseSenderBlock)callback) {
if (!reactTag) {
callback(@[[NSNull null]]);
return;
}
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
UIView *view = viewRegistry[reactTag];
UIView *target = [view hitTest:point withEvent:nil];
CGRect frame = [target convertRect:target.bounds toView:view];
while (target.reactTag == nil && target.superview != nil) {
target = [target superview];
}
callback(@[
target.reactTag ?: [NSNull null],
@(frame.origin.x),
@(frame.origin.y),
@(frame.size.width),
@(frame.size.height),
]);
}];
}
- (void)batchDidComplete
{
// Set up next layout animation
if (_nextLayoutAnimation) {
RCTLayoutAnimation *layoutAnimation = _nextLayoutAnimation;
@ -893,6 +936,12 @@ RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag)
_nextLayoutAnimation = nil;
}
// Gather blocks to be executed now that layout is completed
for (RCTViewManager *manager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
[self addUIBlock:uiBlock];
}
[self flushUIBlocks];
}
@ -1433,7 +1482,7 @@ RCT_EXPORT_METHOD(clearJSResponder)
}
// Add native props
constantsNamespace[@"nativeProps"] = _viewConfigs[name];
constantsNamespace[@"NativeProps"] = _viewConfigs[name];
allJSConstants[name] = [constantsNamespace copy];
}];

View File

@ -15,6 +15,7 @@
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTNavItem.h"
#import "RCTScrollView.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTWrapperViewController.h"

View File

@ -10,13 +10,12 @@
#import <UIKit/UIScrollView.h>
#import "RCTAutoInsetsProtocol.h"
#import "RCTEventDispatcher.h"
#import "RCTScrollableProtocol.h"
#import "RCTView.h"
@protocol UIScrollViewDelegate;
@class RCTEventDispatcher;
@interface RCTScrollView : RCTView <UIScrollViewDelegate, RCTScrollableProtocol, RCTAutoInsetsProtocol>
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@ -48,3 +47,16 @@
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
@end
@interface RCTEventDispatcher (RCTScrollView)
/**
* Send a scroll event.
* (You can send a fake scroll event by passing nil for scrollView).
*/
- (void)sendScrollEventWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData;
@end

View File

@ -21,6 +21,107 @@
CGFloat const ZINDEX_DEFAULT = 0;
CGFloat const ZINDEX_STICKY_HEADER = 50;
@interface RCTScrollEvent : NSObject <RCTEvent>
- (instancetype)initWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData NS_DESIGNATED_INITIALIZER;
@end
@implementation RCTScrollEvent
{
RCTScrollEventType _type;
UIScrollView *_scrollView;
NSDictionary *_userData;
}
@synthesize viewTag = _viewTag;
- (instancetype)initWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData
{
if (self = [super init]) {
_type = type;
_viewTag = reactTag;
_scrollView = scrollView;
_userData = userData;
}
return self;
}
- (uint16_t)coalescingKey
{
return 0;
}
- (NSDictionary *)body
{
NSDictionary *body = @{
@"contentOffset": @{
@"x": @(_scrollView.contentOffset.x),
@"y": @(_scrollView.contentOffset.y)
},
@"contentInset": @{
@"top": @(_scrollView.contentInset.top),
@"left": @(_scrollView.contentInset.left),
@"bottom": @(_scrollView.contentInset.bottom),
@"right": @(_scrollView.contentInset.right)
},
@"contentSize": @{
@"width": @(_scrollView.contentSize.width),
@"height": @(_scrollView.contentSize.height)
},
@"layoutMeasurement": @{
@"width": @(_scrollView.frame.size.width),
@"height": @(_scrollView.frame.size.height)
},
@"zoomScale": @(_scrollView.zoomScale ?: 1),
};
if (_userData) {
NSMutableDictionary *mutableBody = [body mutableCopy];
[mutableBody addEntriesFromDictionary:_userData];
body = mutableBody;
}
return body;
}
- (NSString *)eventName
{
static NSString *events[] = {
@"topScrollBeginDrag",
@"topScroll",
@"topScrollEndDrag",
@"topMomentumScrollBegin",
@"topMomentumScrollEnd",
@"topScrollAnimationEnd",
};
return events[_type];
}
- (BOOL)canCoalesce
{
return YES;
}
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
{
return newEvent;
}
+ (NSString *)moduleDotMethod
{
return @"RCTEventEmitter.receiveEvent";
}
@end
/**
* Include a custom scroll view subclass because we want to limit certain
* default UIKit behaviors such as textFields automatically scrolling
@ -223,6 +324,24 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
}
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
__block UIView *stickyHeader;
[_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock:^(NSUInteger idx, BOOL *stop) {
stickyHeader = [self contentView].reactSubviews[idx];
CGPoint convertedPoint = [stickyHeader convertPoint:point fromView:self];
if ([stickyHeader hitTest:convertedPoint withEvent:event]) {
*stop = YES;
} else {
stickyHeader = nil;
}
}];
return stickyHeader ?: [super hitTest:point withEvent:event];
}
@end
@implementation RCTScrollView
@ -424,6 +543,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
reactTag:self.reactTag
scrollView:scrollView
userData:userData];
// Update dispatch time
_lastScrollDispatchTime = now;
_allowNextScrollNoMatterWhat = NO;
@ -612,3 +732,19 @@ RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, UIEdgeInsets);
}
@end
@implementation RCTEventDispatcher (RCTScrollView)
- (void)sendScrollEventWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData
{
RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithType:type
reactTag:reactTag
scrollView:scrollView
userData:userData];
[self sendEvent:scrollEvent];
}
@end

View File

@ -38,7 +38,6 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *);
@property (nonatomic, weak, readonly) RCTShadowView *superview;
@property (nonatomic, assign, readonly) css_node_t *cssNode;
@property (nonatomic, copy) NSString *viewName;
@property (nonatomic, assign) BOOL isBGColorExplicitlySet; // Used to propagate to children
@property (nonatomic, strong) UIColor *backgroundColor; // Used to propagate to children
@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle;
@property (nonatomic, assign) BOOL hasOnLayout;

View File

@ -38,6 +38,7 @@ typedef enum {
NSMutableArray *_reactSubviews;
BOOL _recomputePadding;
BOOL _recomputeMargin;
BOOL _isBGColorExplicitlySet;
float _paddingMetaProps[META_PROP_COUNT];
float _marginMetaProps[META_PROP_COUNT];
}
@ -180,7 +181,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
// Update parent properties for children
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:parentProperties];
CGFloat alpha = CGColorGetAlpha(_backgroundColor.CGColor);
if (alpha < 1.0 && alpha > 0.0) {
if (alpha < 1.0) {
// If we see partial transparency, start propagating full transparency
properties[RCTBackgroundColorProp] = [UIColor clearColor];
} else {
@ -515,6 +516,7 @@ RCT_STYLE_PROPERTY(FlexWrap, flexWrap, flex_wrap, css_wrap_type_t)
- (void)setBackgroundColor:(UIColor *)color
{
_backgroundColor = color;
_isBGColorExplicitlySet = YES;
[self dirtyPropagation];
}

View File

@ -13,6 +13,9 @@
@interface RCTTabBar : UIView
@property (nonatomic, strong) UIColor *tintColor;
@property (nonatomic, strong) UIColor *barTintColor;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end

View File

@ -114,6 +114,16 @@
}];
}
- (void)setBarTintColor:(UIColor *)barTintColor
{
_tabController.tabBar.barTintColor = barTintColor;
}
- (void)setTintColor:(UIColor *)tintColor
{
_tabController.tabBar.tintColor = tintColor;
}
#pragma mark - UITabBarControllerDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController

View File

@ -23,4 +23,7 @@ RCT_EXPORT_MODULE()
return [[RCTTabBar alloc] initWithEventDispatcher:_bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor)
@end

View File

@ -15,6 +15,8 @@
#import "RCTUtils.h"
#import "UIView+React.h"
static const CGFloat RCTViewBorderThreshold = 0.001;
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
{
for (UIView *subview in [view.subviews reverseObjectEnumerator]) {
@ -436,19 +438,22 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
if ([_backgroundColor isEqual:backgroundColor]) {
return;
}
_backgroundColor = backgroundColor;
[self.layer setNeedsDisplay];
}
- (UIImage *)generateBorderImage:(out CGRect *)contentsCenter
- (UIImage *)borderImage:(out CGRect *)contentsCenter
{
static const CGFloat threshold = 0.001;
const CGFloat maxRadius = ({
const CGRect bounds = self.bounds;
MIN(bounds.size.height, bounds.size.width);
});
const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width);
const CGFloat radius = MAX(0, _borderRadius);
const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius);
const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius);
const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius);
const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius);
const CGFloat bottomRightRadius = MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius);
const CGFloat borderWidth = MAX(0, _borderWidth);
@ -457,14 +462,19 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth;
const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth;
if (topLeftRadius < threshold &&
topRightRadius < threshold &&
bottomLeftRadius < threshold &&
bottomRightRadius < threshold &&
topWidth < threshold &&
rightWidth < threshold &&
bottomWidth < threshold &&
leftWidth < threshold) {
const BOOL hasCornerRadii =
topLeftRadius > RCTViewBorderThreshold ||
topRightRadius > RCTViewBorderThreshold ||
bottomLeftRadius > RCTViewBorderThreshold ||
bottomRightRadius > RCTViewBorderThreshold;
const BOOL hasBorders =
topWidth > RCTViewBorderThreshold ||
rightWidth > RCTViewBorderThreshold ||
bottomWidth > RCTViewBorderThreshold ||
leftWidth > RCTViewBorderThreshold;
if (!hasCornerRadii && !hasBorders) {
return nil;
}
@ -483,17 +493,26 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
const UIEdgeInsets edgeInsets = UIEdgeInsetsMake(topWidth + MAX(innerTopLeftRadiusY, innerTopRightRadiusY), leftWidth + MAX(innerTopLeftRadiusX, innerBottomLeftRadiusX), bottomWidth + MAX(innerBottomLeftRadiusY, innerBottomRightRadiusY), rightWidth + + MAX(innerBottomRightRadiusX, innerTopRightRadiusX));
const CGSize size = CGSizeMake(edgeInsets.left + 1 + edgeInsets.right, edgeInsets.top + 1 + edgeInsets.bottom);
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
const CGFloat alpha = CGColorGetAlpha(_backgroundColor.CGColor);
const BOOL opaque = (self.clipsToBounds || !hasCornerRadii) && alpha == 1.0;
UIGraphicsBeginImageContextWithOptions(size, opaque, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
const CGRect rect = {CGPointZero, size};
CGPathRef path = RCTPathCreateWithRoundedRect(rect, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
const CGRect rect = {.size = size};
CGPathRef path;
const BOOL hasClipping = self.clipsToBounds;
if (hasClipping) {
path = CGPathCreateWithRect(rect, NULL);
} else {
path = RCTPathCreateWithRoundedRect(rect, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
}
if (_backgroundColor) {
CGContextSaveGState(ctx);
CGContextAddPath(ctx, path);
CGContextSetFillColorWithColor(ctx, _backgroundColor.CGColor);
CGContextAddPath(ctx, path);
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
@ -502,29 +521,18 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
CGContextAddPath(ctx, path);
CGPathRelease(path);
BOOL hasRadius = topLeftRadius > 0 || topRightRadius > 0 || bottomLeftRadius > 0 || bottomRightRadius > 0;
if (hasRadius && topWidth > 0 && rightWidth > 0 && bottomWidth > 0 && leftWidth > 0) {
const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
const CGRect insetRect = UIEdgeInsetsInsetRect(rect, insetEdgeInsets);
CGPathRef insetPath = RCTPathCreateWithRoundedRect(insetRect, innerTopLeftRadiusX, innerTopLeftRadiusY, innerTopRightRadiusX, innerTopRightRadiusY, innerBottomLeftRadiusX, innerBottomLeftRadiusY, innerBottomRightRadiusX, innerBottomRightRadiusY, NULL);
CGContextAddPath(ctx, insetPath);
CGPathRelease(insetPath);
}
const BOOL hasRadius = topLeftRadius > 0 || topRightRadius > 0 || bottomLeftRadius > 0 || bottomRightRadius > 0;
const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
CGPathRef insetPath = RCTPathCreateWithRoundedRect(UIEdgeInsetsInsetRect(rect, insetEdgeInsets), innerTopLeftRadiusX, innerTopLeftRadiusY, innerTopRightRadiusX, innerTopRightRadiusY, innerBottomLeftRadiusX, innerBottomLeftRadiusY, innerBottomRightRadiusX, innerBottomRightRadiusY, NULL);
CGContextAddPath(ctx, insetPath);
CGContextEOClip(ctx);
BOOL hasEqualColor = !_borderTopColor && !_borderRightColor && !_borderBottomColor && !_borderLeftColor;
BOOL hasEqualBorder = _borderWidth >= 0 && _borderTopWidth < 0 && _borderRightWidth < 0 && _borderBottomWidth < 0 && _borderLeftWidth < 0;
if (!hasRadius && hasEqualBorder && hasEqualColor) {
CGContextSetStrokeColorWithColor(ctx, _borderColor);
CGContextSetLineWidth(ctx, 2 * _borderWidth);
CGContextClipToRect(ctx, rect);
CGContextStrokeRect(ctx, rect);
} else if (!hasRadius && hasEqualColor) {
if ((hasClipping || !hasRadius) && hasEqualColor) {
CGContextSetFillColorWithColor(ctx, _borderColor);
CGContextAddRect(ctx, rect);
const CGRect insetRect = UIEdgeInsetsInsetRect(rect, edgeInsets);
CGContextAddRect(ctx, insetRect);
CGContextAddPath(ctx, insetPath);
CGContextEOFillPath(ctx);
} else {
BOOL didSet = NO;
@ -660,6 +668,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
}
}
CGPathRelease(insetPath);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
@ -669,8 +679,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
- (void)displayLayer:(CALayer *)layer
{
CGRect contentsCenter = (CGRect){CGPointZero, {1, 1}};
UIImage *image = [self generateBorderImage:&contentsCenter];
CGRect contentsCenter = {.size = {1, 1}};
UIImage *image = [self borderImage:&contentsCenter];
if (image && RCTRunningInTestEnvironment()) {
const CGSize size = self.bounds.size;
@ -685,6 +695,46 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
layer.contentsCenter = contentsCenter;
layer.contentsScale = image.scale ?: 1.0;
layer.magnificationFilter = kCAFilterNearest;
layer.needsDisplayOnBoundsChange = image != nil;
[self updateClippingForLayer:layer];
}
- (void)updateClippingForLayer:(CALayer *)layer
{
CALayer *mask = nil;
CGFloat cornerRadius = 0;
if (self.clipsToBounds) {
if (_borderRadius > 0 && _borderTopLeftRadius < 0 && _borderTopRightRadius < 0 && _borderBottomLeftRadius < 0 && _borderBottomRightRadius < 0) {
cornerRadius = _borderRadius;
} else {
const CGRect bounds = layer.bounds;
const CGFloat maxRadius = MIN(bounds.size.height, bounds.size.width);
const CGFloat radius = MAX(0, _borderRadius);
const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius);
const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius);
const CGFloat bottomRightRadius = MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius);
if (ABS(topLeftRadius - topRightRadius) < RCTViewBorderThreshold &&
ABS(topLeftRadius - bottomLeftRadius) < RCTViewBorderThreshold &&
ABS(topLeftRadius - bottomRightRadius) < RCTViewBorderThreshold) {
cornerRadius = topLeftRadius;
} else {
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
CGPathRef path = RCTPathCreateWithRoundedRect(bounds, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
shapeLayer.path = path;
CGPathRelease(path);
mask = shapeLayer;
}
}
}
layer.cornerRadius = cornerRadius;
layer.mask = mask;
}
#pragma mark Border Color

View File

@ -228,36 +228,38 @@ RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomRight)
#pragma mark - ShadowView properties
RCT_EXPORT_SHADOW_PROPERTY(top, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(right, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(bottom, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(backgroundColor, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(top, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(right, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(bottom, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(left, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(width, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(height, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(width, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(height, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(borderTopWidth, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(borderRightWidth, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(borderBottomWidth, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(borderLeftWidth, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(borderTopWidth, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(borderRightWidth, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(borderBottomWidth, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(borderLeftWidth, CGFloat)
RCT_CUSTOM_SHADOW_PROPERTY(borderWidth, CGFloat, RCTShadowView) {
[view setBorderWidth:[RCTConvert CGFloat:json]];
}
RCT_EXPORT_SHADOW_PROPERTY(marginTop, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(marginRight, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(marginBottom, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(marginLeft, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(marginVertical, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(marginHorizontal, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(margin, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(marginTop, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(marginRight, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(marginBottom, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(marginLeft, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(marginVertical, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(marginHorizontal, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(margin, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(paddingTop, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(paddingRight, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(paddingBottom, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(paddingLeft, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(paddingVertical, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(paddingHorizontal, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(paddingTop, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(paddingRight, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(paddingBottom, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(paddingLeft, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(paddingVertical, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(paddingHorizontal, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(padding, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(flex, CGFloat)
@ -268,12 +270,6 @@ RCT_EXPORT_SHADOW_PROPERTY(alignItems, css_align_t)
RCT_EXPORT_SHADOW_PROPERTY(alignSelf, css_align_t)
RCT_REMAP_SHADOW_PROPERTY(position, positionType, css_position_type_t)
RCT_CUSTOM_SHADOW_PROPERTY(backgroundColor, UIColor, RCTShadowView)
{
view.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
RCT_REMAP_SHADOW_PROPERTY(onLayout, hasOnLayout, BOOL)
@end

View File

@ -1,84 +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.
*/
'use strict';
var eslint = require('eslint');
var ignoredStylisticRules = {
'key-spacing': false,
'comma-spacing': true,
'no-multi-spaces': true,
'brace-style': true,
'camelcase': true,
'consistent-this': true,
'eol-last': true,
'func-names': true,
'func-style': true,
'new-cap': true,
'new-parens': true,
'no-nested-ternary': true,
'no-array-constructor': true,
'no-lonely-if': true,
'no-new-object': true,
'no-spaced-func': true,
'no-space-before-semi': true,
'no-ternary': true,
'no-trailing-spaces': true,
'no-underscore-dangle': true,
'no-wrap-func': true,
'no-mixed-spaces-and-tabs': true,
'quotes': true,
'quote-props': true,
'semi': true,
'sort-vars': true,
'space-after-keywords': true,
'space-in-brackets': true,
'space-in-parens': true,
'space-infix-ops': true,
'space-return-throw-case': true,
'space-unary-word-ops': true,
'max-nested-callbacks': true,
'one-var': true,
'wrap-regex': true,
'curly': true,
'no-mixed-requires': true,
};
function setLinterTransform(transformSource) {
var originalVerify = eslint.linter.verify;
eslint.linter.verify = function(text, config, filename, saveState) {
var transformedText;
try {
transformedText = transformSource(text, filename);
} catch (e) {
return [{
severity: 2,
line: e.lineNumber,
message: e.message,
source: text
}];
}
var originalLines = text.split('\n');
var transformedLines = transformedText.split('\n');
var warnings = originalVerify.call(eslint.linter, transformedText, config, filename, saveState);
// JSX and ES6 transforms usually generate pretty ugly code. Let's skip lint warnings
// about code style for lines that have been changed by transform step.
// Note that more important issues, like use of undefined vars, will still be reported.
return warnings.filter(function(error) {
var lineHasBeenTransformed = originalLines[error.line - 1] !== transformedLines[error.line - 1];
var shouldIgnore = ignoredStylisticRules[error.ruleId] && lineHasBeenTransformed;
return !shouldIgnore;
});
};
}
module.exports = {
setLinterTransform: setLinterTransform,
};

View File

@ -1,17 +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.
*/
'use strict';
var transformSource = require('./jestSupport/scriptPreprocess.js').transformSource;
var linterTransform = require('./lint/linterTransform');
linterTransform.setLinterTransform(transformSource);
// Run the original CLI
require('eslint/bin/eslint');

View File

@ -44,6 +44,7 @@
},
"dependencies": {
"absolute-path": "0.0.0",
"babel": "5.4.3",
"bluebird": "^2.9.21",
"chalk": "^1.0.0",
"connect": "2.8.3",
@ -58,7 +59,7 @@
"react-timer-mixin": "^0.13.1",
"react-tools": "0.13.2",
"rebound": "^0.0.12",
"sane": "^1.1.2",
"sane": "git://github.com/tadeuzagallo/sane.git#a029f8b04a",
"source-map": "0.1.31",
"stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638",
"uglify-js": "~2.4.16",
@ -69,6 +70,8 @@
},
"devDependencies": {
"jest-cli": "0.4.5",
"eslint": "0.9.2"
"babel-eslint": "3.1.5",
"eslint": "0.21.2",
"eslint-plugin-react": "2.3.0"
}
}

View File

@ -36,14 +36,18 @@ var webSocketProxy = require('./webSocketProxy.js');
var options = parseCommandLine([{
command: 'port',
default: 8081,
type: 'string',
}, {
command: 'root',
type: 'string',
description: 'add another root(s) to be used by the packager in this project',
}, {
command: 'assetRoots',
type: 'string',
description: 'specify the root directories of app assets'
}, {
command: 'platform',
type: 'string',
default: 'ios',
description: 'Specify the platform-specific blacklist (ios, android, web).'
}, {
@ -65,13 +69,13 @@ if (options.projectRoots) {
}
if (options.root) {
if (typeof options.root === 'string') {
options.projectRoots.push(path.resolve(options.root));
} else {
options.root.forEach(function(root) {
options.projectRoots.push(path.resolve(root));
});
if (!Array.isArray(options.root)) {
options.root = options.root.split(',');
}
options.root.forEach(function(root) {
options.projectRoots.push(path.resolve(root));
});
}
if (options.assetRoots) {

View File

@ -18,14 +18,17 @@ exports.middleware = function(options) {
return server.processRequest.bind(server);
};
exports.buildPackageFromUrl = function(options, reqUrl) {
Activity.disable();
// Don't start the filewatcher or the cache.
if (options.nonPersistent == null) {
options.nonPersistent = true;
}
exports.buildPackage = function(options, packageOptions) {
var server = createServer(options);
return server.buildPackage(packageOptions)
.then(function(p) {
server.end();
return p;
});
};
var server = new Server(options);
exports.buildPackageFromUrl = function(options, reqUrl) {
var server = createServer(options);
return server.buildPackageFromUrl(reqUrl)
.then(function(p) {
server.end();
@ -34,13 +37,7 @@ exports.buildPackageFromUrl = function(options, reqUrl) {
};
exports.getDependencies = function(options, main) {
Activity.disable();
// Don't start the filewatcher or the cache.
if (options.nonPersistent == null) {
options.nonPersistent = true;
}
var server = new Server(options);
var server = createServer(options);
return server.getDependencies(main)
.then(function(r) {
server.end();
@ -60,3 +57,13 @@ function useGracefulFs() {
}
});
}
function createServer(options) {
Activity.disable();
// Don't start the filewatcher or the cache.
if (options.nonPersistent == null) {
options.nonPersistent = true;
}
return new Server(options);
}

View File

@ -67,6 +67,7 @@ function createWatcher(rootConfig) {
var watcher = new Watcher(rootConfig.dir, {
glob: rootConfig.globs,
dot: false,
ignore: '**/node_modules/**/*',
});
return new Promise(function(resolve, reject) {

View File

@ -57,25 +57,34 @@ describe('Transformer', function() {
});
pit('should add file info to parse errors', function() {
var message = 'message';
var snippet = 'snippet';
require('fs').readFile.mockImpl(function(file, callback) {
callback(null, 'var x;\nvar answer = 1 = x;');
});
workers.mockImpl(function(data, callback) {
var esprimaError = new Error('Error: Line 2: Invalid left-hand side in assignment');
esprimaError.description = 'Invalid left-hand side in assignment';
esprimaError.lineNumber = 2;
esprimaError.column = 15;
callback(null, {error: esprimaError});
var babelError = new SyntaxError(message);
babelError.type = 'SyntaxError';
babelError.description = message;
babelError.loc = {
line: 2,
column: 15,
};
babelError.codeFrame = snippet;
callback(babelError);
});
return new Transformer(OPTIONS).loadFileAndTransform('foo-file.js')
.catch(function(error) {
expect(error.type).toEqual('TransformError');
expect(error.snippet).toEqual([
'var answer = 1 = x;',
' ^',
].join('\n'));
expect(error.message).toBe('SyntaxError ' + message);
expect(error.lineNumber).toBe(2);
expect(error.column).toBe(15);
expect(error.filename).toBe('foo-file.js');
expect(error.description).toBe(message);
expect(error.snippet).toBe(snippet);
});
});
});

View File

@ -99,7 +99,12 @@ Transformer.prototype.loadFileAndTransform = function(filePath) {
}).then(
function(res) {
if (res.error) {
throw formatError(res.error, filePath, sourceCode);
console.warn(
'Error property on the result value form the transformer',
'module is deprecated and will be removed in future versions.',
'Please pass an error object as the first argument to the callback'
);
throw formatError(res.error, filePath);
}
return new ModuleTransport({
@ -110,6 +115,8 @@ Transformer.prototype.loadFileAndTransform = function(filePath) {
});
}
);
}).catch(function(err) {
throw formatError(err, filePath);
});
});
};
@ -118,8 +125,8 @@ function TransformError() {}
util.inherits(TransformError, SyntaxError);
function formatError(err, filename, source) {
if (err.lineNumber && err.column) {
return formatEsprimaError(err, filename, source);
if (err.loc) {
return formatBabelError(err, filename, source);
} else {
return formatGenericError(err, filename, source);
}
@ -136,26 +143,16 @@ function formatGenericError(err, filename) {
return error;
}
function formatEsprimaError(err, filename, source) {
var stack = err.stack.split('\n');
stack.shift();
var msg = 'TransformError: ' + err.description + ' ' + filename + ':' +
err.lineNumber + ':' + err.column;
var sourceLine = source.split('\n')[err.lineNumber - 1];
var snippet = sourceLine + '\n' + new Array(err.column - 1).join(' ') + '^';
stack.unshift(msg);
function formatBabelError(err, filename) {
var error = new TransformError();
error.message = msg;
error.type = 'TransformError';
error.stack = stack.join('\n');
error.snippet = snippet;
error.message = (err.type || error.type) + ' ' + err.message;
error.stack = err.stack;
error.snippet = err.codeFrame;
error.lineNumber = err.loc.line;
error.column = err.loc.column;
error.filename = filename;
error.lineNumber = err.lineNumber;
error.column = err.column;
error.description = err.description;
error.description = err.message;
return error;
}

View File

@ -15,6 +15,8 @@ var ModuleTransport = require('../lib/ModuleTransport');
module.exports = Package;
var SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL=';
function Package(sourceMapUrl) {
this._finalized = false;
this._modules = [];
@ -96,12 +98,11 @@ Package.prototype.getSource = function(options) {
}
var source = this._getSource();
source += '\n\/\/@ sourceMappingURL=';
if (options.inlineSourceMap) {
source += this._getInlineSourceMap();
} else {
source += this._sourceMapUrl;
source += SOURCEMAPPING_URL + this._getInlineSourceMap();
} else if (this._sourceMapUrl) {
source += SOURCEMAPPING_URL + this._sourceMapUrl;
}
return source;

View File

@ -47,6 +47,26 @@ describe('Package', function() {
].join('\n'));
});
it('should be ok to leave out the source map url', function() {
var p = new Package();
p.addModule(new ModuleTransport({
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
}));
p.addModule(new ModuleTransport({
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
}));
p.finalize({});
expect(p.getSource()).toBe([
'transformed foo;',
'transformed bar;',
].join('\n'));
});
it('should create a package and add run module code', function() {
ppackage.addModule(new ModuleTransport({
code: 'transformed foo;',

View File

@ -230,7 +230,7 @@ describe('processRequest', function() {
});
});
describe.only('/assets endpoint', function() {
describe('/assets endpoint', function() {
var AssetServer;
beforeEach(function() {
AssetServer = require('../../AssetServer');
@ -257,4 +257,30 @@ describe('processRequest', function() {
});
});
describe('buildPackage(options)', function() {
it('Calls the packager with the correct args', function() {
server.buildPackage({
entryFile: 'foo file'
});
expect(Packager.prototype.package).toBeCalledWith(
'foo file',
true,
undefined,
true
);
});
});
describe('buildPackageFromUrl(options)', function() {
it('Calls the packager with the correct args', function() {
server.buildPackageFromUrl('/path/to/foo.bundle?dev=false&runModule=false');
expect(Packager.prototype.package).toBeCalledWith(
'path/to/foo.js',
false,
'/path/to/foo.map',
false
);
});
});
});

View File

@ -129,7 +129,7 @@ Server.prototype._onFileChange = function(type, filepath, root) {
};
Server.prototype._rebuildPackages = function() {
var buildPackage = this._buildPackage.bind(this);
var buildPackage = this.buildPackage.bind(this);
var packages = this._packages;
Object.keys(packages).forEach(function(key) {
var options = getOptionsFromUrl(key);
@ -171,18 +171,47 @@ Server.prototype.end = function() {
]);
};
Server.prototype._buildPackage = function(options) {
var packageOpts = declareOpts({
sourceMapUrl: {
type: 'string',
required: false,
},
entryFile: {
type: 'string',
required: true,
},
dev: {
type: 'boolean',
default: true,
},
minify: {
type: 'boolean',
default: false,
},
runModule: {
type: 'boolean',
default: true,
},
inlineSourceMap: {
type: 'boolean',
default: false,
},
});
Server.prototype.buildPackage = function(options) {
var opts = packageOpts(options);
return this._packager.package(
options.main,
options.runModule,
options.sourceMapUrl,
options.dev
opts.entryFile,
opts.runModule,
opts.sourceMapUrl,
opts.dev
);
};
Server.prototype.buildPackageFromUrl = function(reqUrl) {
var options = getOptionsFromUrl(reqUrl);
return this._buildPackage(options);
return this.buildPackage(options);
};
Server.prototype.getDependencies = function(main) {
@ -321,7 +350,7 @@ Server.prototype.processRequest = function(req, res, next) {
var startReqEventId = Activity.startEvent('request:' + req.url);
var options = getOptionsFromUrl(req.url);
var building = this._packages[req.url] || this._buildPackage(options);
var building = this._packages[req.url] || this.buildPackage(options);
this._packages[req.url] = building;
building.then(
@ -363,7 +392,7 @@ function getOptionsFromUrl(reqUrl) {
return {
sourceMapUrl: pathname.replace(/\.bundle$/, '.map'),
main: entryFile,
entryFile: entryFile,
dev: getBoolOptionFromQuery(urlObj.query, 'dev', true),
minify: getBoolOptionFromQuery(urlObj.query, 'minify'),
runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true),

View File

@ -10,40 +10,37 @@
*/
'use strict';
var jstransform = require('jstransform').transform;
var babel = require('babel');
var reactVisitors =
require('react-tools/vendor/fbtransform/visitors').getAllVisitors();
var staticTypeSyntax =
require('jstransform/visitors/type-syntax').visitorList;
var trailingCommaVisitors =
require('jstransform/visitors/es7-trailing-comma-visitors.js').visitorList;
// Note that reactVisitors now handles ES6 classes, rest parameters, arrow
// functions, template strings, and object short notation.
var visitorList = reactVisitors.concat(trailingCommaVisitors);
function transform(srcTxt, filename) {
var options = {
es3: true,
sourceType: 'nonStrictModule',
function transform(srcTxt, filename, options) {
var result = babel.transform(srcTxt, {
retainLines: true,
compact: true,
comments: false,
filename: filename,
whitelist: [
'es6.arrowFunctions',
'es6.blockScoping',
'es6.classes',
'es6.destructuring',
'es6.parameters.rest',
'es6.properties.computed',
'es6.properties.shorthand',
'es6.spread',
'es6.templateLiterals',
'es7.trailingFunctionCommas',
'es7.objectRestSpread',
'flow',
'react',
],
sourceFileName: filename,
sourceMaps: false,
extra: options || {},
});
return {
code: result.code,
};
// These tranforms mostly just erase type annotations and static typing
// related statements, but they were conflicting with other tranforms.
// Running them first solves that problem
var staticTypeSyntaxResult = jstransform(
staticTypeSyntax,
srcTxt,
options
);
return jstransform(
visitorList,
staticTypeSyntaxResult.code,
options
);
}
module.exports = function(data, callback) {
@ -54,15 +51,8 @@ module.exports = function(data, callback) {
data.filename
);
} catch (e) {
return callback(null, {
error: {
lineNumber: e.lineNumber,
column: e.column,
message: e.message,
stack: e.stack,
description: e.description
}
});
callback(e);
return;
}
callback(null, result);