commit
62b90cfcc5
|
@ -15,6 +15,12 @@
|
|||
.*/node_modules/react-tools/src/core/ReactInstanceHandles.js
|
||||
.*/node_modules/react-tools/src/event/EventPropagators.js
|
||||
|
||||
# Ignore commoner tests
|
||||
.*/node_modules/react-tools/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/.*
|
||||
|
||||
|
|
|
@ -29,8 +29,6 @@ var TimerMixin = require('react-timer-mixin');
|
|||
var MovieCell = require('./MovieCell');
|
||||
var MovieScreen = require('./MovieScreen');
|
||||
|
||||
var fetch = require('fetch');
|
||||
|
||||
/**
|
||||
* This is for demo purposes only, and rate limited.
|
||||
* In case you want to use the Rotten Tomatoes' API on a real app you should
|
||||
|
|
|
@ -15,6 +15,12 @@
|
|||
.*/node_modules/react-tools/src/core/ReactInstanceHandles.js
|
||||
.*/node_modules/react-tools/src/event/EventPropagators.js
|
||||
|
||||
# Ignore commoner tests
|
||||
.*/node_modules/react-tools/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/.*
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6751" systemVersion="14C1510" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6736"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
|
|
@ -39,3 +39,8 @@ declare module 'image!uie_thumb_selected' {
|
|||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
declare module 'image!NavBarButtonPlus' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* 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 {
|
||||
Image,
|
||||
LayoutAnimation,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
type LayoutEvent = {
|
||||
nativeEvent: {
|
||||
layout: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
var LayoutEventExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
viewStyle: {
|
||||
margin: 20,
|
||||
},
|
||||
};
|
||||
},
|
||||
animateViewLayout: function() {
|
||||
LayoutAnimation.configureNext(
|
||||
LayoutAnimation.Presets.spring,
|
||||
() => {
|
||||
console.log('layout animation done.');
|
||||
this.addWrapText();
|
||||
},
|
||||
(error) => { throw new Error(JSON.stringify(error)); }
|
||||
);
|
||||
this.setState({
|
||||
viewStyle: {
|
||||
margin: this.state.viewStyle.margin > 20 ? 20 : 60,
|
||||
}
|
||||
});
|
||||
},
|
||||
addWrapText: function() {
|
||||
this.setState(
|
||||
{extraText: ' And a bunch more text to wrap around a few lines.'},
|
||||
this.changeContainer
|
||||
);
|
||||
},
|
||||
changeContainer: function() {
|
||||
this.setState({containerStyle: {width: 280}});
|
||||
},
|
||||
onViewLayout: function(e: LayoutEvent) {
|
||||
console.log('received view layout event\n', e.nativeEvent);
|
||||
this.setState({viewLayout: e.nativeEvent.layout});
|
||||
},
|
||||
onTextLayout: function(e: LayoutEvent) {
|
||||
console.log('received text layout event\n', e.nativeEvent);
|
||||
this.setState({textLayout: e.nativeEvent.layout});
|
||||
},
|
||||
onImageLayout: function(e: LayoutEvent) {
|
||||
console.log('received image layout event\n', e.nativeEvent);
|
||||
this.setState({imageLayout: e.nativeEvent.layout});
|
||||
},
|
||||
render: function() {
|
||||
var viewStyle = [styles.view, this.state.viewStyle];
|
||||
var textLayout = this.state.textLayout || {width: '?', height: '?'};
|
||||
var imageLayout = this.state.imageLayout || {x: '?', y: '?'};
|
||||
return (
|
||||
<View style={this.state.containerStyle}>
|
||||
<Text>
|
||||
onLayout events are called on mount and whenever layout is updated,
|
||||
including after layout animations complete.{' '}
|
||||
<Text style={styles.pressText} onPress={this.animateViewLayout}>
|
||||
Press here to change layout.
|
||||
</Text>
|
||||
</Text>
|
||||
<View ref="view" onLayout={this.onViewLayout} style={viewStyle}>
|
||||
<Image
|
||||
ref="img"
|
||||
onLayout={this.onImageLayout}
|
||||
style={styles.image}
|
||||
source={{uri: 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851561_767334496626293_1958532586_n.png'}}
|
||||
/>
|
||||
<Text>
|
||||
ViewLayout: {JSON.stringify(this.state.viewLayout, null, ' ') + '\n\n'}
|
||||
</Text>
|
||||
<Text ref="txt" onLayout={this.onTextLayout} style={styles.text}>
|
||||
A simple piece of text.{this.state.extraText}
|
||||
</Text>
|
||||
<Text>
|
||||
{'\n'}
|
||||
Text w/h: {textLayout.width}/{textLayout.height + '\n'}
|
||||
Image x/y: {imageLayout.x}/{imageLayout.y}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
view: {
|
||||
padding: 12,
|
||||
borderColor: 'black',
|
||||
borderWidth: 0.5,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
text: {
|
||||
alignSelf: 'flex-start',
|
||||
borderColor: 'rgba(0, 0, 255, 0.2)',
|
||||
borderWidth: 0.5,
|
||||
},
|
||||
image: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
marginBottom: 10,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
pressText: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = 'onLayout';
|
||||
exports.description = 'Layout events can be used to measure view size and position.';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'onLayout',
|
||||
render: function(): ReactElement {
|
||||
return <LayoutEventExample />;
|
||||
},
|
||||
}];
|
|
@ -19,6 +19,7 @@ var React = require('react-native');
|
|||
var ViewExample = require('./ViewExample');
|
||||
var createExamplePage = require('./createExamplePage');
|
||||
var {
|
||||
AlertIOS,
|
||||
PixelRatio,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
|
@ -92,6 +93,30 @@ var NavigatorIOSExample = React.createClass({
|
|||
}
|
||||
});
|
||||
})}
|
||||
{this._renderRow('Custom Left & Right Icons', () => {
|
||||
this.props.navigator.push({
|
||||
title: NavigatorIOSExample.title,
|
||||
component: EmptyPage,
|
||||
leftButtonTitle: 'Custom Left',
|
||||
onLeftButtonPress: () => this.props.navigator.pop(),
|
||||
rightButtonIcon: require('image!NavBarButtonPlus'),
|
||||
onRightButtonPress: () => {
|
||||
AlertIOS.alert(
|
||||
'Bar Button Action',
|
||||
'Recognized a tap on the bar button icon',
|
||||
[
|
||||
{
|
||||
text: 'OK',
|
||||
onPress: () => console.log('Tapped OK'),
|
||||
},
|
||||
]
|
||||
);
|
||||
},
|
||||
passProps: {
|
||||
text: 'This page has an icon for the right button in the nav bar',
|
||||
}
|
||||
});
|
||||
})}
|
||||
{this._renderRow('Pop', () => {
|
||||
this.props.navigator.pop();
|
||||
})}
|
||||
|
|
21
Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/Contents.json
vendored
Normal file
21
Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "NavBarButtonPlus@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/NavBarButtonPlus@3x.png
vendored
Normal file
BIN
Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/NavBarButtonPlus@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 166 B |
|
@ -64,6 +64,7 @@ var APIS = [
|
|||
require('./BorderExample'),
|
||||
require('./CameraRollExample.ios'),
|
||||
require('./GeolocationExample'),
|
||||
require('./LayoutEventsExample'),
|
||||
require('./LayoutExample'),
|
||||
require('./NetInfoExample'),
|
||||
require('./PanResponderExample'),
|
||||
|
|
|
@ -25,6 +25,7 @@ var TESTS = [
|
|||
require('./IntegrationTestHarnessTest'),
|
||||
require('./TimersTest'),
|
||||
require('./AsyncStorageTest'),
|
||||
require('./LayoutEventsTest'),
|
||||
require('./SimpleSnapshotTest'),
|
||||
];
|
||||
|
||||
|
|
|
@ -71,6 +71,11 @@
|
|||
[_runner runTest:_cmd module:@"AsyncStorageTest"];
|
||||
}
|
||||
|
||||
- (void)testLayoutEvents
|
||||
{
|
||||
[_runner runTest:_cmd module:@"LayoutEventsTest"];
|
||||
}
|
||||
|
||||
#pragma mark Snapshot Tests
|
||||
|
||||
- (void)testSimpleSnapshot
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/**
|
||||
* 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 LayoutEventsTest
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Image,
|
||||
LayoutAnimation,
|
||||
NativeModules,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} = React;
|
||||
var TestModule = NativeModules.TestModule || NativeModules.SnapshotTestManager;
|
||||
|
||||
var deepDiffer = require('deepDiffer');
|
||||
|
||||
function debug() {
|
||||
//console.log.apply(null, arguments);
|
||||
}
|
||||
|
||||
type LayoutEvent = {
|
||||
nativeEvent: {
|
||||
layout: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
var LayoutEventsTest = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
didAnimation: false,
|
||||
};
|
||||
},
|
||||
animateViewLayout: function() {
|
||||
LayoutAnimation.configureNext(
|
||||
LayoutAnimation.Presets.spring,
|
||||
() => {
|
||||
debug('layout animation done.');
|
||||
this.checkLayout(this.addWrapText);
|
||||
},
|
||||
(error) => { throw new Error(JSON.stringify(error)); }
|
||||
);
|
||||
this.setState({viewStyle: {margin: 60}});
|
||||
},
|
||||
addWrapText: function() {
|
||||
this.setState(
|
||||
{extraText: ' And a bunch more text to wrap around a few lines.'},
|
||||
() => this.checkLayout(this.changeContainer)
|
||||
);
|
||||
},
|
||||
changeContainer: function() {
|
||||
this.setState(
|
||||
{containerStyle: {width: 280}},
|
||||
() => this.checkLayout(TestModule.markTestCompleted)
|
||||
);
|
||||
},
|
||||
checkLayout: function(next?: ?Function) {
|
||||
if (!this.isMounted()) {
|
||||
return;
|
||||
}
|
||||
this.refs.view.measure((x, y, width, height) => {
|
||||
this.compare('view', {x, y, width, height}, this.state.viewLayout);
|
||||
if (typeof next === 'function') {
|
||||
next();
|
||||
} else if (!this.state.didAnimation) {
|
||||
// Trigger first state change after onLayout fires
|
||||
this.animateViewLayout();
|
||||
this.state.didAnimation = true;
|
||||
}
|
||||
});
|
||||
this.refs.txt.measure((x, y, width, height) => {
|
||||
this.compare('txt', {x, y, width, height}, this.state.textLayout);
|
||||
});
|
||||
this.refs.img.measure((x, y, width, height) => {
|
||||
this.compare('img', {x, y, width, height}, this.state.imageLayout);
|
||||
});
|
||||
},
|
||||
compare: function(node: string, measured: any, onLayout: any): void {
|
||||
if (deepDiffer(measured, onLayout)) {
|
||||
var data = {measured, onLayout};
|
||||
throw new Error(
|
||||
node + ' onLayout mismatch with measure ' +
|
||||
JSON.stringify(data, null, ' ')
|
||||
);
|
||||
}
|
||||
},
|
||||
onViewLayout: function(e: LayoutEvent) {
|
||||
debug('received view layout event\n', e.nativeEvent);
|
||||
this.setState({viewLayout: e.nativeEvent.layout}, this.checkLayout);
|
||||
},
|
||||
onTextLayout: function(e: LayoutEvent) {
|
||||
debug('received text layout event\n', e.nativeEvent);
|
||||
this.setState({textLayout: e.nativeEvent.layout}, this.checkLayout);
|
||||
},
|
||||
onImageLayout: function(e: LayoutEvent) {
|
||||
debug('received image layout event\n', e.nativeEvent);
|
||||
this.setState({imageLayout: e.nativeEvent.layout}, this.checkLayout);
|
||||
},
|
||||
render: function() {
|
||||
var viewStyle = [styles.view, this.state.viewStyle];
|
||||
var textLayout = this.state.textLayout || {width: '?', height: '?'};
|
||||
var imageLayout = this.state.imageLayout || {x: '?', y: '?'};
|
||||
return (
|
||||
<View style={[styles.container, this.state.containerStyle]}>
|
||||
<View ref="view" onLayout={this.onViewLayout} style={viewStyle}>
|
||||
<Image
|
||||
ref="img"
|
||||
onLayout={this.onImageLayout}
|
||||
style={styles.image}
|
||||
source={{uri: 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851561_767334496626293_1958532586_n.png'}}
|
||||
/>
|
||||
<Text>
|
||||
ViewLayout: {JSON.stringify(this.state.viewLayout, null, ' ') + '\n\n'}
|
||||
</Text>
|
||||
<Text ref="txt" onLayout={this.onTextLayout} style={styles.text}>
|
||||
A simple piece of text.{this.state.extraText}
|
||||
</Text>
|
||||
<Text>
|
||||
{'\n'}
|
||||
Text w/h: {textLayout.width}/{textLayout.height + '\n'}
|
||||
Image x/y: {imageLayout.x}/{imageLayout.y}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
margin: 40,
|
||||
},
|
||||
view: {
|
||||
margin: 20,
|
||||
padding: 12,
|
||||
borderColor: 'black',
|
||||
borderWidth: 0.5,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
text: {
|
||||
alignSelf: 'flex-start',
|
||||
borderColor: 'rgba(0, 0, 255, 0.2)',
|
||||
borderWidth: 0.5,
|
||||
},
|
||||
image: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
marginBottom: 10,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = LayoutEventsTest;
|
|
@ -12,6 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
var EventEmitter = require('EventEmitter');
|
||||
var Image = require('Image');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var RCTNavigatorManager = require('NativeModules').NavigatorManager;
|
||||
|
@ -47,11 +48,16 @@ var RCTNavigatorItem = createReactIOSNativeComponentClass({
|
|||
// NavigatorIOS does not use them all, because some are problematic
|
||||
title: true,
|
||||
barTintColor: true,
|
||||
leftButtonIcon: true,
|
||||
leftButtonTitle: true,
|
||||
onNavLeftButtonTap: true,
|
||||
rightButtonIcon: true,
|
||||
rightButtonTitle: true,
|
||||
onNavRightButtonTap: true,
|
||||
backButtonIcon: true,
|
||||
backButtonTitle: true,
|
||||
tintColor: true,
|
||||
navigationBarHidden: true,
|
||||
backButtonTitle: true,
|
||||
titleTextColor: true,
|
||||
style: true,
|
||||
},
|
||||
|
@ -79,7 +85,12 @@ type Route = {
|
|||
title: string;
|
||||
passProps: Object;
|
||||
backButtonTitle: string;
|
||||
backButtonIcon: Object;
|
||||
leftButtonTitle: string;
|
||||
leftButtonIcon: Object;
|
||||
onLeftButtonPress: Function;
|
||||
rightButtonTitle: string;
|
||||
rightButtonIcon: Object;
|
||||
onRightButtonPress: Function;
|
||||
wrapperStyle: any;
|
||||
};
|
||||
|
@ -212,6 +223,13 @@ var NavigatorIOS = React.createClass({
|
|||
*/
|
||||
passProps: PropTypes.object,
|
||||
|
||||
/**
|
||||
* If set, the left header button image will appear with this source. Note
|
||||
* that this doesn't apply for the header of the current view, but the
|
||||
* ones of the views that are pushed afterward.
|
||||
*/
|
||||
backButtonIcon: Image.propTypes.source,
|
||||
|
||||
/**
|
||||
* If set, the left header button will appear with this name. Note that
|
||||
* this doesn't apply for the header of the current view, but the ones
|
||||
|
@ -219,6 +237,26 @@ var NavigatorIOS = React.createClass({
|
|||
*/
|
||||
backButtonTitle: PropTypes.string,
|
||||
|
||||
/**
|
||||
* If set, the left header button image will appear with this source
|
||||
*/
|
||||
leftButtonIcon: Image.propTypes.source,
|
||||
|
||||
/**
|
||||
* If set, the left header button will appear with this name
|
||||
*/
|
||||
leftButtonTitle: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Called when the left header button is pressed
|
||||
*/
|
||||
onLeftButtonPress: PropTypes.func,
|
||||
|
||||
/**
|
||||
* If set, the right header button image will appear with this source
|
||||
*/
|
||||
rightButtonIcon: Image.propTypes.source,
|
||||
|
||||
/**
|
||||
* If set, the right header button will appear with this name
|
||||
*/
|
||||
|
@ -560,7 +598,12 @@ var NavigatorIOS = React.createClass({
|
|||
this.props.itemWrapperStyle,
|
||||
route.wrapperStyle
|
||||
]}
|
||||
backButtonIcon={this._imageNameFromSource(route.backButtonIcon)}
|
||||
backButtonTitle={route.backButtonTitle}
|
||||
leftButtonIcon={this._imageNameFromSource(route.leftButtonIcon)}
|
||||
leftButtonTitle={route.leftButtonTitle}
|
||||
onNavLeftButtonTap={route.onLeftButtonPress}
|
||||
rightButtonIcon={this._imageNameFromSource(route.rightButtonIcon)}
|
||||
rightButtonTitle={route.rightButtonTitle}
|
||||
onNavRightButtonTap={route.onRightButtonPress}
|
||||
navigationBarHidden={this.props.navigationBarHidden}
|
||||
|
@ -577,6 +620,10 @@ var NavigatorIOS = React.createClass({
|
|||
);
|
||||
},
|
||||
|
||||
_imageNameFromSource: function(source: ?Object) {
|
||||
return source ? source.uri : undefined;
|
||||
},
|
||||
|
||||
renderNavigationStackItems: function() {
|
||||
var shouldRecurseToNavigator =
|
||||
this.state.makingNavigatorRequest ||
|
||||
|
|
|
@ -20,6 +20,7 @@ var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
|||
|
||||
var cloneWithProps = require('cloneWithProps');
|
||||
var ensureComponentIsNative = require('ensureComponentIsNative');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var keyOf = require('keyOf');
|
||||
var onlyChild = require('onlyChild');
|
||||
|
||||
|
@ -105,12 +106,13 @@ var TouchableOpacity = React.createClass({
|
|||
},
|
||||
|
||||
touchableHandleActivePressOut: function() {
|
||||
this.setOpacityTo(1.0);
|
||||
var child = onlyChild(this.props.children);
|
||||
var childStyle = flattenStyle(child.props.style) || {};
|
||||
this.setOpacityTo(childStyle.opacity === undefined ? 1 : childStyle.opacity);
|
||||
this.props.onPressOut && this.props.onPressOut();
|
||||
},
|
||||
|
||||
touchableHandlePress: function() {
|
||||
this.setOpacityTo(1.0);
|
||||
this.props.onPress && this.props.onPress();
|
||||
},
|
||||
|
||||
|
|
|
@ -90,6 +90,11 @@ var View = React.createClass({
|
|||
onStartShouldSetResponder: PropTypes.func,
|
||||
onStartShouldSetResponderCapture: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked on mount and layout changes with {x, y, width, height}.
|
||||
*/
|
||||
onLayout: PropTypes.func,
|
||||
|
||||
/**
|
||||
* In the absence of `auto` property, `none` is much like `CSS`'s `none`
|
||||
* value. `box-none` is as if you had applied the `CSS` class:
|
||||
|
|
|
@ -47,6 +47,23 @@ var self = {};
|
|||
return
|
||||
}
|
||||
|
||||
function normalizeName(name) {
|
||||
if (typeof name !== 'string') {
|
||||
name = name.toString();
|
||||
}
|
||||
if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
|
||||
throw new TypeError('Invalid character in header field name')
|
||||
}
|
||||
return name.toLowerCase()
|
||||
}
|
||||
|
||||
function normalizeValue(value) {
|
||||
if (typeof value !== 'string') {
|
||||
value = value.toString();
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function Headers(headers) {
|
||||
this.map = {}
|
||||
|
||||
|
@ -66,7 +83,8 @@ var self = {};
|
|||
}
|
||||
|
||||
Headers.prototype.append = function(name, value) {
|
||||
name = name.toLowerCase()
|
||||
name = normalizeName(name)
|
||||
value = normalizeValue(value)
|
||||
var list = this.map[name]
|
||||
if (!list) {
|
||||
list = []
|
||||
|
@ -76,24 +94,24 @@ var self = {};
|
|||
}
|
||||
|
||||
Headers.prototype['delete'] = function(name) {
|
||||
delete this.map[name.toLowerCase()]
|
||||
delete this.map[normalizeName(name)]
|
||||
}
|
||||
|
||||
Headers.prototype.get = function(name) {
|
||||
var values = this.map[name.toLowerCase()]
|
||||
var values = this.map[normalizeName(name)]
|
||||
return values ? values[0] : null
|
||||
}
|
||||
|
||||
Headers.prototype.getAll = function(name) {
|
||||
return this.map[name.toLowerCase()] || []
|
||||
return this.map[normalizeName(name)] || []
|
||||
}
|
||||
|
||||
Headers.prototype.has = function(name) {
|
||||
return this.map.hasOwnProperty(name.toLowerCase())
|
||||
return this.map.hasOwnProperty(normalizeName(name))
|
||||
}
|
||||
|
||||
Headers.prototype.set = function(name, value) {
|
||||
this.map[name.toLowerCase()] = [value]
|
||||
this.map[normalizeName(name)] = [normalizeValue(value)]
|
||||
}
|
||||
|
||||
// Instead of iterable for now.
|
||||
|
@ -134,22 +152,51 @@ var self = {};
|
|||
return fileReaderReady(reader)
|
||||
}
|
||||
|
||||
var blobSupport = 'FileReader' in self && 'Blob' in self && (function() {
|
||||
try {
|
||||
new Blob();
|
||||
return true
|
||||
} catch(e) {
|
||||
return false
|
||||
}
|
||||
})();
|
||||
var support = {
|
||||
blob: 'FileReader' in self && 'Blob' in self && (function() {
|
||||
try {
|
||||
new Blob();
|
||||
return true
|
||||
} catch(e) {
|
||||
return false
|
||||
}
|
||||
})(),
|
||||
formData: 'FormData' in self
|
||||
}
|
||||
|
||||
function Body() {
|
||||
this.bodyUsed = false
|
||||
|
||||
if (blobSupport) {
|
||||
|
||||
this._initBody = function(body) {
|
||||
this._bodyInit = body
|
||||
if (typeof body === 'string') {
|
||||
this._bodyText = body
|
||||
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
|
||||
this._bodyBlob = body
|
||||
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
|
||||
this._bodyFormData = body
|
||||
} else if (!body) {
|
||||
this._bodyText = ''
|
||||
} else {
|
||||
throw new Error('unsupported BodyInit type')
|
||||
}
|
||||
}
|
||||
|
||||
if (support.blob) {
|
||||
this.blob = function() {
|
||||
var rejected = consumed(this)
|
||||
return rejected ? rejected : Promise.resolve(this._bodyBlob)
|
||||
if (rejected) {
|
||||
return rejected
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return Promise.resolve(this._bodyBlob)
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as blob')
|
||||
} else {
|
||||
return Promise.resolve(new Blob([this._bodyText]))
|
||||
}
|
||||
}
|
||||
|
||||
this.arrayBuffer = function() {
|
||||
|
@ -157,7 +204,18 @@ var self = {};
|
|||
}
|
||||
|
||||
this.text = function() {
|
||||
return this.blob().then(readBlobAsText)
|
||||
var rejected = consumed(this)
|
||||
if (rejected) {
|
||||
return rejected
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return readBlobAsText(this._bodyBlob)
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as text')
|
||||
} else {
|
||||
return Promise.resolve(this._bodyText)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.text = function() {
|
||||
|
@ -166,7 +224,7 @@ var self = {};
|
|||
}
|
||||
}
|
||||
|
||||
if ('FormData' in self) {
|
||||
if (support.formData) {
|
||||
this.formData = function() {
|
||||
return this.text().then(decode)
|
||||
}
|
||||
|
@ -190,12 +248,17 @@ var self = {};
|
|||
function Request(url, options) {
|
||||
options = options || {}
|
||||
this.url = url
|
||||
this._body = options.body
|
||||
|
||||
this.credentials = options.credentials || 'omit'
|
||||
this.headers = new Headers(options.headers)
|
||||
this.method = normalizeMethod(options.method || 'GET')
|
||||
this.mode = options.mode || null
|
||||
this.referrer = null
|
||||
|
||||
if ((this.method === 'GET' || this.method === 'HEAD') && options.body) {
|
||||
throw new TypeError('Body not allowed for GET or HEAD requests')
|
||||
}
|
||||
this._initBody(options.body)
|
||||
}
|
||||
|
||||
function decode(body) {
|
||||
|
@ -223,11 +286,43 @@ var self = {};
|
|||
return head
|
||||
}
|
||||
|
||||
Request.prototype.fetch = function() {
|
||||
var self = this
|
||||
Body.call(Request.prototype)
|
||||
|
||||
function Response(bodyInit, options) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
}
|
||||
|
||||
this._initBody(bodyInit)
|
||||
this.type = 'default'
|
||||
this.url = null
|
||||
this.status = options.status
|
||||
this.ok = this.status >= 200 && this.status < 300
|
||||
this.statusText = options.statusText
|
||||
this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
|
||||
this.url = options.url || ''
|
||||
}
|
||||
|
||||
Body.call(Response.prototype)
|
||||
|
||||
self.Headers = Headers;
|
||||
self.Request = Request;
|
||||
self.Response = Response;
|
||||
|
||||
self.fetch = function(input, init) {
|
||||
// TODO: Request constructor should accept input, init
|
||||
var request
|
||||
if (Request.prototype.isPrototypeOf(input) && !init) {
|
||||
request = input
|
||||
} else {
|
||||
request = new Request(input, init)
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
if (request.credentials === 'cors') {
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
|
||||
function responseURL() {
|
||||
if ('responseURL' in xhr) {
|
||||
|
@ -262,57 +357,24 @@ var self = {};
|
|||
reject(new TypeError('Network request failed'))
|
||||
}
|
||||
|
||||
xhr.open(self.method, self.url)
|
||||
if ('responseType' in xhr && blobSupport) {
|
||||
xhr.open(request.method, request.url, true)
|
||||
|
||||
if ('responseType' in xhr && support.blob) {
|
||||
xhr.responseType = 'blob'
|
||||
}
|
||||
|
||||
self.headers.forEach(function(name, values) {
|
||||
request.headers.forEach(function(name, values) {
|
||||
values.forEach(function(value) {
|
||||
xhr.setRequestHeader(name, value)
|
||||
})
|
||||
})
|
||||
|
||||
xhr.send((self._body === undefined) ? null : self._body)
|
||||
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
|
||||
})
|
||||
}
|
||||
|
||||
Body.call(Request.prototype)
|
||||
|
||||
function Response(bodyInit, options) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
}
|
||||
|
||||
if (blobSupport) {
|
||||
if (typeof bodyInit === 'string') {
|
||||
this._bodyBlob = new Blob([bodyInit])
|
||||
} else {
|
||||
this._bodyBlob = bodyInit
|
||||
}
|
||||
} else {
|
||||
this._bodyText = bodyInit
|
||||
}
|
||||
this.type = 'default'
|
||||
this.url = null
|
||||
this.status = options.status
|
||||
this.statusText = options.statusText
|
||||
this.headers = options.headers
|
||||
this.url = options.url || ''
|
||||
}
|
||||
|
||||
Body.call(Response.prototype)
|
||||
|
||||
self.Headers = Headers;
|
||||
self.Request = Request;
|
||||
self.Response = Response;
|
||||
|
||||
self.fetch = function (url, options) {
|
||||
return new Request(url, options).fetch()
|
||||
}
|
||||
self.fetch.polyfill = true
|
||||
})();
|
||||
|
||||
/** End of the third-party code */
|
||||
|
||||
module.exports = self.fetch;
|
||||
module.exports = self;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule AssetRegistry
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var assets = [];
|
||||
|
||||
function registerAsset(asset) {
|
||||
// `push` returns new array length, so the first asset will
|
||||
// get id 1 (not 0) to make the value truthy
|
||||
return assets.push(asset);
|
||||
}
|
||||
|
||||
function getAssetByID(assetId) {
|
||||
return assets[assetId - 1];
|
||||
}
|
||||
|
||||
module.exports = { registerAsset, getAssetByID };
|
|
@ -123,8 +123,7 @@ var Image = React.createClass({
|
|||
'not be set directly on Image.');
|
||||
}
|
||||
}
|
||||
var source = resolveAssetSource(this.props.source);
|
||||
invariant(source, 'source must be initialized');
|
||||
var source = resolveAssetSource(this.props.source) || {};
|
||||
|
||||
var {width, height} = source;
|
||||
var style = flattenStyle([{width, height}, styles.base, this.props.style]);
|
||||
|
|
|
@ -8,19 +8,24 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
jest.dontMock('../resolveAssetSource');
|
||||
jest
|
||||
.dontMock('AssetRegistry')
|
||||
.dontMock('../resolveAssetSource');
|
||||
|
||||
var resolveAssetSource;
|
||||
var SourceCode;
|
||||
var AssetRegistry;
|
||||
|
||||
function expectResolvesAsset(input, expectedSource) {
|
||||
expect(resolveAssetSource(input)).toEqual(expectedSource);
|
||||
var assetId = AssetRegistry.registerAsset(input);
|
||||
expect(resolveAssetSource(assetId)).toEqual(expectedSource);
|
||||
}
|
||||
|
||||
describe('resolveAssetSource', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModuleRegistry();
|
||||
SourceCode = require('NativeModules').SourceCode;
|
||||
AssetRegistry = require('AssetRegistry');
|
||||
resolveAssetSource = require('../resolveAssetSource');
|
||||
});
|
||||
|
||||
|
@ -32,6 +37,22 @@ describe('resolveAssetSource', () => {
|
|||
expect(resolveAssetSource(source2)).toBe(source2);
|
||||
});
|
||||
|
||||
it('does not change deprecated assets', () => {
|
||||
expect(resolveAssetSource({
|
||||
isStatic: true,
|
||||
deprecated: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'logo',
|
||||
})).toEqual({
|
||||
isStatic: true,
|
||||
deprecated: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'logo',
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores any weird data', () => {
|
||||
expect(resolveAssetSource(null)).toBe(null);
|
||||
expect(resolveAssetSource(42)).toBe(null);
|
||||
|
@ -81,25 +102,6 @@ describe('resolveAssetSource', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('does not change deprecated assets', () => {
|
||||
expectResolvesAsset({
|
||||
__packager_asset: true,
|
||||
deprecated: true,
|
||||
fileSystemLocation: '/root/app/module/a',
|
||||
httpServerLocation: '/assets/module/a',
|
||||
width: 100,
|
||||
height: 200,
|
||||
scales: [1],
|
||||
hash: '5b6f00f',
|
||||
name: 'logo',
|
||||
type: 'png',
|
||||
}, {
|
||||
isStatic: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'logo',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bundle was loaded from file', () => {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var AssetRegistry = require('AssetRegistry');
|
||||
var PixelRatio = require('PixelRatio');
|
||||
var SourceCode = require('NativeModules').SourceCode;
|
||||
|
||||
|
@ -44,58 +45,47 @@ function pickScale(scales, deviceScale) {
|
|||
}
|
||||
|
||||
function resolveAssetSource(source) {
|
||||
if (!source || typeof source !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!source.__packager_asset) {
|
||||
if (typeof source === 'object') {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Deprecated assets are managed by Xcode for now,
|
||||
// just returning image name as `uri`
|
||||
// Examples:
|
||||
// require('image!deprecatd_logo_example')
|
||||
// require('./new-hotness-logo-example.png')
|
||||
if (source.deprecated) {
|
||||
return {
|
||||
width: source.width,
|
||||
height: source.height,
|
||||
isStatic: true,
|
||||
uri: source.name || source.uri, // TODO(frantic): remove uri
|
||||
};
|
||||
var asset = AssetRegistry.getAssetByID(source);
|
||||
if (asset) {
|
||||
return assetToImageSource(asset);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function assetToImageSource(asset) {
|
||||
// TODO(frantic): currently httpServerLocation is used both as
|
||||
// path in http URL and path within IPA. Should we have zipArchiveLocation?
|
||||
var path = source.httpServerLocation;
|
||||
var path = asset.httpServerLocation;
|
||||
if (path[0] === '/') {
|
||||
path = path.substr(1);
|
||||
}
|
||||
|
||||
var scale = pickScale(source.scales, PixelRatio.get());
|
||||
var scale = pickScale(asset.scales, PixelRatio.get());
|
||||
var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
|
||||
|
||||
var fileName = source.name + scaleSuffix + '.' + source.type;
|
||||
var fileName = asset.name + scaleSuffix + '.' + asset.type;
|
||||
var serverURL = getServerURL();
|
||||
if (serverURL) {
|
||||
return {
|
||||
width: source.width,
|
||||
height: source.height,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
uri: serverURL + path + '/' + fileName +
|
||||
'?hash=' + source.hash,
|
||||
'?hash=' + asset.hash,
|
||||
isStatic: false,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
width: source.width,
|
||||
height: source.height,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
uri: path + '/' + fileName,
|
||||
isStatic: true,
|
||||
};
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
module.exports = resolveAssetSource;
|
||||
|
|
|
@ -135,7 +135,12 @@ function setupXHR() {
|
|||
// The native XMLHttpRequest in Chrome dev tools is CORS aware and won't
|
||||
// let you fetch anything from the internet
|
||||
GLOBAL.XMLHttpRequest = require('XMLHttpRequest');
|
||||
GLOBAL.fetch = require('fetch');
|
||||
|
||||
var fetchPolyfill = require('fetch');
|
||||
GLOBAL.fetch = fetchPolyfill.fetch;
|
||||
GLOBAL.Headers = fetchPolyfill.Headers;
|
||||
GLOBAL.Request = fetchPolyfill.Request;
|
||||
GLOBAL.Response = fetchPolyfill.Response;
|
||||
}
|
||||
|
||||
function setupGeolocation() {
|
||||
|
|
|
@ -17,8 +17,6 @@ var RCTSourceCode = require('NativeModules').SourceCode;
|
|||
var SourceMapConsumer = require('SourceMap').SourceMapConsumer;
|
||||
var SourceMapURL = require('./source-map-url');
|
||||
|
||||
var fetch = require('fetch');
|
||||
|
||||
function loadSourceMap(): Promise {
|
||||
return fetchSourceMap()
|
||||
.then(map => new SourceMapConsumer(map));
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
* @providesModule ReactIOSViewAttributes
|
||||
* @flow
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
var merge = require('merge');
|
||||
|
||||
|
@ -21,6 +20,7 @@ ReactIOSViewAttributes.UIView = {
|
|||
accessible: true,
|
||||
accessibilityLabel: true,
|
||||
testID: true,
|
||||
onLayout: true,
|
||||
};
|
||||
|
||||
ReactIOSViewAttributes.RCTView = merge(
|
||||
|
@ -31,7 +31,7 @@ ReactIOSViewAttributes.RCTView = merge(
|
|||
// For this property to be effective, it must be applied to a view that contains
|
||||
// many subviews that extend outside its bound. The subviews must also have
|
||||
// overflow: hidden, as should the containing view (or one of its superviews).
|
||||
removeClippedSubviews: true
|
||||
removeClippedSubviews: true,
|
||||
});
|
||||
|
||||
module.exports = ReactIOSViewAttributes;
|
||||
|
|
|
@ -42,6 +42,16 @@ function diffRawProperties(
|
|||
}
|
||||
prevProp = prevProps && prevProps[propKey];
|
||||
nextProp = nextProps[propKey];
|
||||
|
||||
// functions are converted to booleans as markers that the associated
|
||||
// events should be sent from native.
|
||||
if (typeof prevProp === 'function') {
|
||||
prevProp = true;
|
||||
}
|
||||
if (typeof nextProp === 'function') {
|
||||
nextProp = true;
|
||||
}
|
||||
|
||||
if (prevProp !== nextProp) {
|
||||
// If you want a property's diff to be detected, you must configure it
|
||||
// to be so - *or* it must be a scalar property. For now, we'll allow
|
||||
|
@ -75,6 +85,16 @@ function diffRawProperties(
|
|||
}
|
||||
prevProp = prevProps[propKey];
|
||||
nextProp = nextProps && nextProps[propKey];
|
||||
|
||||
// functions are converted to booleans as markers that the associated
|
||||
// events should be sent from native.
|
||||
if (typeof prevProp === 'function') {
|
||||
prevProp = true;
|
||||
}
|
||||
if (typeof nextProp === 'function') {
|
||||
nextProp = true;
|
||||
}
|
||||
|
||||
if (prevProp !== nextProp) {
|
||||
if (nextProp === undefined) {
|
||||
nextProp = null; // null is a sentinel we explicitly send to native
|
||||
|
|
|
@ -16,3 +16,8 @@ declare var __DEV__: boolean;
|
|||
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
|
||||
inject: ?((stuff: Object) => void)
|
||||
};*/
|
||||
|
||||
declare var fetch: any;
|
||||
declare var Headers: any;
|
||||
declare var Request: any;
|
||||
declare var Response: any;
|
||||
|
|
|
@ -28,6 +28,11 @@ extern NSString *const RCTReloadNotification;
|
|||
*/
|
||||
extern NSString *const RCTJavaScriptDidLoadNotification;
|
||||
|
||||
/**
|
||||
* This notification fires when the bridge failed to load.
|
||||
*/
|
||||
extern NSString *const RCTJavaScriptDidFailToLoadNotification;
|
||||
|
||||
/**
|
||||
* This block can be used to instantiate modules that require additional
|
||||
* init parameters, or additional configuration prior to being used.
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
NSString *const RCTReloadNotification = @"RCTReloadNotification";
|
||||
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
|
||||
NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification";
|
||||
|
||||
dispatch_queue_t const RCTJSThread = nil;
|
||||
|
||||
|
@ -867,6 +868,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
return _batchedBridge.modules;
|
||||
}
|
||||
|
||||
- (RCTEventDispatcher *)eventDispatcher
|
||||
{
|
||||
return _eventDispatcher ?: _batchedBridge.eventDispatcher;
|
||||
}
|
||||
|
||||
#define RCT_BRIDGE_WARN(...) \
|
||||
- (void)__VA_ARGS__ \
|
||||
{ \
|
||||
|
@ -1082,16 +1088,18 @@ RCT_BRIDGE_WARN(_invokeAndProcessModule:(NSString *)module method:(NSString *)me
|
|||
|
||||
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
|
||||
[loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
|
||||
|
||||
_loading = NO;
|
||||
if (!self.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = bundleURL;
|
||||
sourceCodeModule.scriptText = script;
|
||||
if (error != nil) {
|
||||
if (error) {
|
||||
|
||||
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
|
||||
NSArray *stack = [error userInfo][@"stack"];
|
||||
if (stack) {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
|
||||
withStack:stack];
|
||||
|
@ -1100,10 +1108,17 @@ RCT_BRIDGE_WARN(_invokeAndProcessModule:(NSString *)module method:(NSString *)me
|
|||
withDetails:[error localizedFailureReason]];
|
||||
}
|
||||
|
||||
NSDictionary *userInfo = @{@"error": error};
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
|
||||
object:self
|
||||
userInfo:userInfo];
|
||||
|
||||
} else {
|
||||
|
||||
[self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) {
|
||||
|
||||
if (!loadError) {
|
||||
|
||||
/**
|
||||
* Register the display link to start sending js calls after everything
|
||||
* is setup
|
||||
|
@ -1144,18 +1159,11 @@ RCT_BRIDGE_WARN(_invokeAndProcessModule:(NSString *)module method:(NSString *)me
|
|||
_latestJSExecutor = nil;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main Thread deallocations
|
||||
*/
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[_mainDisplayLink invalidate];
|
||||
void (^mainThreadInvalidate)(void) = ^{
|
||||
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
/**
|
||||
* JS Thread deallocations
|
||||
*/
|
||||
[_javaScriptExecutor invalidate];
|
||||
[_jsDisplayLink invalidate];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[_mainDisplayLink invalidate];
|
||||
_mainDisplayLink = nil;
|
||||
|
||||
// Invalidate modules
|
||||
for (id target in _modulesByID.allObjects) {
|
||||
|
@ -1165,11 +1173,35 @@ RCT_BRIDGE_WARN(_invokeAndProcessModule:(NSString *)module method:(NSString *)me
|
|||
}
|
||||
|
||||
// Release modules (breaks retain cycle if module has strong bridge reference)
|
||||
_javaScriptExecutor = nil;
|
||||
_frameUpdateObservers = nil;
|
||||
_modulesByID = nil;
|
||||
_queuesByID = nil;
|
||||
_modulesByName = nil;
|
||||
};
|
||||
|
||||
if (!_javaScriptExecutor) {
|
||||
|
||||
// No JS thread running
|
||||
mainThreadInvalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
|
||||
/**
|
||||
* JS Thread deallocations
|
||||
*/
|
||||
[_javaScriptExecutor invalidate];
|
||||
_javaScriptExecutor = nil;
|
||||
|
||||
[_jsDisplayLink invalidate];
|
||||
_jsDisplayLink = nil;
|
||||
|
||||
/**
|
||||
* Main Thread deallocations
|
||||
*/
|
||||
mainThreadInvalidate();
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -1300,48 +1332,6 @@ RCT_BRIDGE_WARN(_invokeAndProcessModule:(NSString *)module method:(NSString *)me
|
|||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event deduping
|
||||
*
|
||||
* Right now we make a lot of assumptions about the arguments structure
|
||||
* so just iterate if it's a `callFunctionReturnFlushedQueue()`
|
||||
*/
|
||||
if ([method isEqualToString:@"callFunctionReturnFlushedQueue"]) {
|
||||
NSString *moduleName = RCTLocalModuleNames[[args[0] integerValue]];
|
||||
/**
|
||||
* Keep going if it any event emmiter, e.g. RCT(Device|NativeApp)?EventEmitter
|
||||
*/
|
||||
if ([moduleName hasSuffix:@"EventEmitter"]) {
|
||||
for (NSDictionary *call in [strongSelf->_scheduledCalls copy]) {
|
||||
NSArray *callArgs = call[@"args"];
|
||||
/**
|
||||
* If it's the same module && method call on the bridge &&
|
||||
* the same EventEmitter module && method
|
||||
*/
|
||||
if (
|
||||
[call[@"module"] isEqualToString:module] &&
|
||||
[call[@"method"] isEqualToString:method] &&
|
||||
[callArgs[0] isEqual:args[0]] &&
|
||||
[callArgs[1] isEqual:args[1]]
|
||||
) {
|
||||
/**
|
||||
* args[2] contains the actual arguments for the event call, where
|
||||
* args[2][0] is the target for RCTEventEmitter or the eventName
|
||||
* for the other EventEmitters
|
||||
* if RCTEventEmitter we need to compare args[2][1] that will be
|
||||
* the eventName
|
||||
*/
|
||||
if (
|
||||
[args[2][0] isEqual:callArgs[2][0]] &&
|
||||
(![moduleName isEqualToString:@"RCTEventEmitter"] || [args[2][1] isEqual:callArgs[2][1]])
|
||||
) {
|
||||
[strongSelf->_scheduledCalls removeObject:call];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id call = @{
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
|
@ -1495,17 +1485,13 @@ RCT_BRIDGE_WARN(_invokeAndProcessModule:(NSString *)module method:(NSString *)me
|
|||
return;
|
||||
}
|
||||
|
||||
if (!RCT_DEBUG) {
|
||||
@try {
|
||||
[method invokeWithBridge:strongSelf module:module arguments:params context:context];
|
||||
} else {
|
||||
@try {
|
||||
[method invokeWithBridge:strongSelf module:module arguments:params context:context];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
|
||||
if ([exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
|
||||
@throw;
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
|
||||
if (!RCT_DEBUG && [exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
|
||||
@throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,9 +73,6 @@ RCT_EXPORT_MODULE()
|
|||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
[self updateSettings];
|
||||
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
|
||||
[notificationCenter addObserver:self
|
||||
|
@ -93,19 +90,27 @@ RCT_EXPORT_MODULE()
|
|||
name:RCTJavaScriptDidLoadNotification
|
||||
object:nil];
|
||||
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
_settings = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// Delay setup until after Bridge init
|
||||
__weak RCTDevMenu *weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateSettings];
|
||||
});
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
|
||||
__weak RCTDevMenu *weakSelf = self;
|
||||
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
|
||||
|
||||
// toggle debug menu
|
||||
// Toggle debug menu
|
||||
[commands registerKeyCommandWithInput:@"d"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
[weakSelf toggle];
|
||||
}];
|
||||
|
||||
// reload in normal mode
|
||||
// Reload in normal mode
|
||||
[commands registerKeyCommandWithInput:@"n"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
|
@ -117,22 +122,23 @@ RCT_EXPORT_MODULE()
|
|||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
- (void)updateSettings
|
||||
{
|
||||
__weak RCTDevMenu *weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
RCTDevMenu *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
NSDictionary *settings = [_defaults objectForKey:RCTDevMenuSettingsKey];
|
||||
if ([settings isEqualToDictionary:_settings]) {
|
||||
return;
|
||||
}
|
||||
|
||||
strongSelf->_settings = [NSMutableDictionary dictionaryWithDictionary:[strongSelf->_defaults objectForKey:RCTDevMenuSettingsKey]];
|
||||
|
||||
strongSelf.shakeToShow = [strongSelf->_settings[@"shakeToShow"] ?: @YES boolValue];
|
||||
strongSelf.profilingEnabled = [strongSelf->_settings[@"profilingEnabled"] ?: @NO boolValue];
|
||||
strongSelf.liveReloadEnabled = [strongSelf->_settings[@"liveReloadEnabled"] ?: @NO boolValue];
|
||||
strongSelf.executorClass = NSClassFromString(strongSelf->_settings[@"executorClass"]);
|
||||
});
|
||||
[_settings setDictionary:settings];
|
||||
self.shakeToShow = [_settings[@"shakeToShow"] ?: @YES boolValue];
|
||||
self.profilingEnabled = [_settings[@"profilingEnabled"] ?: @NO boolValue];
|
||||
self.liveReloadEnabled = [_settings[@"liveReloadEnabled"] ?: @NO boolValue];
|
||||
self.executorClass = NSClassFromString(_settings[@"executorClass"]);
|
||||
}
|
||||
|
||||
- (void)jsLoaded
|
||||
|
@ -161,6 +167,11 @@ RCT_EXPORT_MODULE()
|
|||
});
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_updateTask cancel];
|
||||
|
@ -170,6 +181,10 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (void)updateSetting:(NSString *)name value:(id)value
|
||||
{
|
||||
id currentValue = _settings[name];
|
||||
if (currentValue == value || [currentValue isEqual:value]) {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
_settings[name] = value;
|
||||
} else {
|
||||
|
@ -239,6 +254,9 @@ RCT_EXPORT_METHOD(reload)
|
|||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
_actionSheet = nil;
|
||||
if (buttonIndex == actionSheet.cancelButtonIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (buttonIndex) {
|
||||
case 0: {
|
||||
|
|
|
@ -76,10 +76,27 @@
|
|||
reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
|
||||
[_rootView addSubview:dismissButton];
|
||||
[_rootView addSubview:reloadButton];
|
||||
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(dismiss)
|
||||
name:RCTReloadNotification
|
||||
object:nil];
|
||||
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(dismiss)
|
||||
name:RCTJavaScriptDidLoadNotification
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)openStackFrameInEditor:(NSDictionary *)stackFrame
|
||||
{
|
||||
NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, nil) dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
@ -125,7 +142,6 @@
|
|||
- (void)reload
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil userInfo:nil];
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
#pragma mark - TableView
|
||||
|
|
|
@ -43,6 +43,7 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
|
|||
UIWebView *_webView;
|
||||
NSMutableDictionary *_objectsToInject;
|
||||
NSRegularExpression *_commentsRegex;
|
||||
NSRegularExpression *_scriptTagsRegex;
|
||||
}
|
||||
|
||||
@synthesize valid = _valid;
|
||||
|
@ -52,7 +53,8 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
|
|||
if ((self = [super init])) {
|
||||
_objectsToInject = [[NSMutableDictionary alloc] init];
|
||||
_webView = webView ?: [[UIWebView alloc] init];
|
||||
_commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]+?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL];
|
||||
_commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]*?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL],
|
||||
_scriptTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\/?script[^>]*?)>" options:0 error:NULL],
|
||||
_webView.delegate = self;
|
||||
}
|
||||
return self;
|
||||
|
@ -139,11 +141,6 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
|
|||
onComplete(error);
|
||||
};
|
||||
|
||||
script = [_commentsRegex stringByReplacingMatchesInString:script
|
||||
options:0
|
||||
range:NSMakeRange(0, script.length)
|
||||
withTemplate:@""];
|
||||
|
||||
if (_objectsToInject.count > 0) {
|
||||
NSMutableString *scriptWithInjections = [[NSMutableString alloc] initWithString:@"/* BEGIN NATIVELY INJECTED OBJECTS */\n"];
|
||||
[_objectsToInject enumerateKeysAndObjectsUsingBlock:^(NSString *objectName, NSString *blockScript, BOOL *stop) {
|
||||
|
@ -158,6 +155,15 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
|
|||
script = scriptWithInjections;
|
||||
}
|
||||
|
||||
script = [_commentsRegex stringByReplacingMatchesInString:script
|
||||
options:0
|
||||
range:NSMakeRange(0, script.length)
|
||||
withTemplate:@""];
|
||||
script = [_scriptTagsRegex stringByReplacingMatchesInString:script
|
||||
options:0
|
||||
range:NSMakeRange(0, script.length)
|
||||
withTemplate:@"\\\\<$1\\\\>"];
|
||||
|
||||
NSString *runScript =
|
||||
[NSString
|
||||
stringWithFormat:@"<html><head></head><body><script type='text/javascript'>%@</script></body></html>",
|
||||
|
|
|
@ -44,50 +44,45 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message
|
|||
return;
|
||||
}
|
||||
|
||||
#if RCT_DEBUG // Red box is only available in debug mode
|
||||
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
|
||||
|
||||
#else
|
||||
if (!RCT_DEBUG) {
|
||||
|
||||
static NSUInteger reloadRetries = 0;
|
||||
const NSUInteger maxMessageLength = 75;
|
||||
static NSUInteger reloadRetries = 0;
|
||||
const NSUInteger maxMessageLength = 75;
|
||||
|
||||
if (reloadRetries < _maxReloadAttempts) {
|
||||
if (reloadRetries < _maxReloadAttempts) {
|
||||
|
||||
reloadRetries++;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
|
||||
object:nil];
|
||||
reloadRetries++;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
|
||||
object:nil];
|
||||
|
||||
} else {
|
||||
} else {
|
||||
|
||||
if (message.length > maxMessageLength) {
|
||||
message = [[message substringToIndex:maxMessageLength] stringByAppendingString:@"..."];
|
||||
if (message.length > maxMessageLength) {
|
||||
message = [[message substringToIndex:maxMessageLength] stringByAppendingString:@"..."];
|
||||
}
|
||||
|
||||
NSMutableString *prettyStack = [NSMutableString stringWithString:@"\n"];
|
||||
for (NSDictionary *frame in stack) {
|
||||
[prettyStack appendFormat:@"%@@%@:%@\n", frame[@"methodName"], frame[@"lineNumber"], frame[@"column"]];
|
||||
}
|
||||
|
||||
NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:message];
|
||||
[NSException raise:name format:@"Message: %@, stack: %@", message, prettyStack];
|
||||
}
|
||||
|
||||
NSMutableString *prettyStack = [NSMutableString stringWithString:@"\n"];
|
||||
for (NSDictionary *frame in stack) {
|
||||
[prettyStack appendFormat:@"%@@%@:%@\n", frame[@"methodName"], frame[@"lineNumber"], frame[@"column"]];
|
||||
}
|
||||
|
||||
NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:message];
|
||||
[NSException raise:name format:@"Message: %@, stack: %@", message, prettyStack];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message
|
||||
stack:(NSArray *)stack)
|
||||
{
|
||||
if (_delegate) {
|
||||
[_delegate unhandledJSExceptionWithMessage:message stack:stack];
|
||||
return;
|
||||
}
|
||||
|
||||
#if RCT_DEBUG // Red box is only available in debug mode
|
||||
|
||||
[[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack];
|
||||
|
||||
#endif
|
||||
|
||||
[[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTProfile.h"
|
||||
#import "RCTRootView.h"
|
||||
|
@ -420,17 +421,31 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
|
|||
[rootShadowView collectRootUpdatedFrames:viewsWithNewFrames
|
||||
parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}];
|
||||
|
||||
// Parallel arrays
|
||||
// Parallel arrays are built and then handed off to main thread
|
||||
NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
||||
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
||||
NSMutableArray *areNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
||||
NSMutableArray *parentsAreNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
||||
NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
||||
|
||||
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
||||
[frameReactTags addObject:shadowView.reactTag];
|
||||
[frames addObject:[NSValue valueWithCGRect:shadowView.frame]];
|
||||
[areNew addObject:@(shadowView.isNewView)];
|
||||
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
|
||||
id event = [NSNull null];
|
||||
if (shadowView.hasOnLayout) {
|
||||
event = @{
|
||||
@"target": shadowView.reactTag,
|
||||
@"layout": @{
|
||||
@"x": @(shadowView.frame.origin.x),
|
||||
@"y": @(shadowView.frame.origin.y),
|
||||
@"width": @(shadowView.frame.size.width),
|
||||
@"height": @(shadowView.frame.size.height),
|
||||
},
|
||||
};
|
||||
}
|
||||
[onLayoutEvents addObject:event];
|
||||
}
|
||||
|
||||
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
||||
|
@ -448,20 +463,30 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
|
|||
// Perform layout (possibly animated)
|
||||
NSNumber *rootViewTag = rootShadowView.reactTag;
|
||||
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
RCTResponseSenderBlock callback = self->_layoutAnimation.callback;
|
||||
__block NSInteger completionsCalled = 0;
|
||||
for (NSUInteger ii = 0; ii < frames.count; ii++) {
|
||||
NSNumber *reactTag = frameReactTags[ii];
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
CGRect frame = [frames[ii] CGRectValue];
|
||||
id event = onLayoutEvents[ii];
|
||||
|
||||
BOOL isNew = [areNew[ii] boolValue];
|
||||
RCTAnimation *updateAnimation = isNew ? nil : _layoutAnimation.updateAnimation;
|
||||
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
|
||||
RCTAnimation *createAnimation = shouldAnimateCreation ? _layoutAnimation.createAnimation : nil;
|
||||
|
||||
void (^completion)(BOOL finished) = ^(BOOL finished) {
|
||||
if (self->_layoutAnimation.callback) {
|
||||
self->_layoutAnimation.callback(@[@(finished)]);
|
||||
completionsCalled++;
|
||||
if (event != [NSNull null]) {
|
||||
[self.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event];
|
||||
}
|
||||
if (callback && completionsCalled == frames.count - 1) {
|
||||
callback(@[@(finished)]);
|
||||
}
|
||||
};
|
||||
|
||||
// Animate view update
|
||||
BOOL isNew = [areNew[ii] boolValue];
|
||||
RCTAnimation *updateAnimation = isNew ? nil: _layoutAnimation.updateAnimation;
|
||||
if (updateAnimation) {
|
||||
[updateAnimation performAnimations:^{
|
||||
[view reactSetFrame:frame];
|
||||
|
@ -478,9 +503,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
|
|||
}
|
||||
|
||||
// Animate view creation
|
||||
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
|
||||
RCTAnimation *createAnimation = _layoutAnimation.createAnimation;
|
||||
if (shouldAnimateCreation && createAnimation) {
|
||||
if (createAnimation) {
|
||||
if ([createAnimation.property isEqualToString:@"scaleXY"]) {
|
||||
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
|
||||
} else if ([createAnimation.property isEqualToString:@"opacity"]) {
|
||||
|
@ -1159,6 +1182,12 @@ RCT_EXPORT_METHOD(clearJSResponder)
|
|||
@"captured": @"onNavigationCompleteCapture"
|
||||
}
|
||||
},
|
||||
@"topNavLeftButtonTap": @{
|
||||
@"phasedRegistrationNames": @{
|
||||
@"bubbled": @"onNavLeftButtonTap",
|
||||
@"captured": @"onNavLefttButtonTapCapture"
|
||||
}
|
||||
},
|
||||
@"topNavRightButtonTap": @{
|
||||
@"phasedRegistrationNames": @{
|
||||
@"bubbled": @"onNavRightButtonTap",
|
||||
|
@ -1256,6 +1285,9 @@ RCT_EXPORT_METHOD(clearJSResponder)
|
|||
@"topScrollAnimationEnd": @{
|
||||
@"registrationName": @"onScrollAnimationEnd"
|
||||
},
|
||||
@"topLayout": @{
|
||||
@"registrationName": @"onLayout"
|
||||
},
|
||||
@"topSelectionChange": @{
|
||||
@"registrationName": @"onSelectionChange"
|
||||
},
|
||||
|
|
|
@ -21,7 +21,7 @@ extern const CGFloat RCTMapZoomBoundBuffer;
|
|||
@interface RCTMap: MKMapView
|
||||
|
||||
@property (nonatomic, assign) BOOL followUserLocation;
|
||||
@property (nonatomic, assign) BOOL hasStartedLoading;
|
||||
@property (nonatomic, assign) BOOL hasStartedRendering;
|
||||
@property (nonatomic, assign) CGFloat minDelta;
|
||||
@property (nonatomic, assign) CGFloat maxDelta;
|
||||
@property (nonatomic, assign) UIEdgeInsets legalLabelInsets;
|
||||
|
|
|
@ -27,7 +27,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
|||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_hasStartedLoading = NO;
|
||||
_hasStartedRendering = NO;
|
||||
|
||||
// Find Apple link label
|
||||
for (UIView *subview in self.subviews) {
|
||||
|
|
|
@ -84,15 +84,15 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
|||
[self _regionChanged:mapView];
|
||||
|
||||
// Don't send region did change events until map has
|
||||
// started loading, as these won't represent the final location
|
||||
if (mapView.hasStartedLoading) {
|
||||
// started rendering, as these won't represent the final location
|
||||
if (mapView.hasStartedRendering) {
|
||||
[self _emitRegionChangeEvent:mapView continuous:NO];
|
||||
};
|
||||
}
|
||||
|
||||
- (void)mapViewWillStartLoadingMap:(RCTMap *)mapView
|
||||
- (void)mapViewWillStartRenderingMap:(RCTMap *)mapView
|
||||
{
|
||||
mapView.hasStartedLoading = YES;
|
||||
mapView.hasStartedRendering = YES;
|
||||
[self _emitRegionChangeEvent:mapView continuous:NO];
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,19 @@
|
|||
@interface RCTNavItem : UIView
|
||||
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, strong) UIImage *leftButtonIcon;
|
||||
@property (nonatomic, copy) NSString *leftButtonTitle;
|
||||
@property (nonatomic, strong) UIImage *rightButtonIcon;
|
||||
@property (nonatomic, copy) NSString *rightButtonTitle;
|
||||
@property (nonatomic, strong) UIImage *backButtonIcon;
|
||||
@property (nonatomic, copy) NSString *backButtonTitle;
|
||||
@property (nonatomic, assign) BOOL navigationBarHidden;
|
||||
@property (nonatomic, copy) UIColor *tintColor;
|
||||
@property (nonatomic, copy) UIColor *barTintColor;
|
||||
@property (nonatomic, copy) UIColor *titleTextColor;
|
||||
@property (nonatomic, strong) UIColor *tintColor;
|
||||
@property (nonatomic, strong) UIColor *barTintColor;
|
||||
@property (nonatomic, strong) UIColor *titleTextColor;
|
||||
|
||||
@property (nonatomic, readonly) UIBarButtonItem *backButtonItem;
|
||||
@property (nonatomic, readonly) UIBarButtonItem *leftButtonItem;
|
||||
@property (nonatomic, readonly) UIBarButtonItem *rightButtonItem;
|
||||
|
||||
@end
|
||||
|
|
|
@ -11,5 +11,104 @@
|
|||
|
||||
@implementation RCTNavItem
|
||||
|
||||
@end
|
||||
@synthesize backButtonItem = _backButtonItem;
|
||||
@synthesize leftButtonItem = _leftButtonItem;
|
||||
@synthesize rightButtonItem = _rightButtonItem;
|
||||
|
||||
- (void)setBackButtonTitle:(NSString *)backButtonTitle
|
||||
{
|
||||
_backButtonTitle = backButtonTitle;
|
||||
_backButtonItem = nil;
|
||||
}
|
||||
|
||||
- (void)setBackButtonIcon:(UIImage *)backButtonIcon
|
||||
{
|
||||
_backButtonIcon = backButtonIcon;
|
||||
_backButtonItem = nil;
|
||||
}
|
||||
|
||||
- (UIBarButtonItem *)backButtonItem
|
||||
{
|
||||
if (!_backButtonItem) {
|
||||
if (_backButtonIcon) {
|
||||
_backButtonItem = [[UIBarButtonItem alloc] initWithImage:_backButtonIcon
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
} else if (_backButtonTitle.length) {
|
||||
_backButtonItem = [[UIBarButtonItem alloc] initWithTitle:_backButtonTitle
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
} else {
|
||||
_backButtonItem = nil;
|
||||
}
|
||||
}
|
||||
return _backButtonItem;
|
||||
}
|
||||
|
||||
- (void)setLeftButtonTitle:(NSString *)leftButtonTitle
|
||||
{
|
||||
_leftButtonTitle = leftButtonTitle;
|
||||
_leftButtonItem = nil;
|
||||
}
|
||||
|
||||
- (void)setLeftButtonIcon:(UIImage *)leftButtonIcon
|
||||
{
|
||||
_leftButtonIcon = leftButtonIcon;
|
||||
_leftButtonIcon = nil;
|
||||
}
|
||||
|
||||
- (UIBarButtonItem *)leftButtonItem
|
||||
{
|
||||
if (!_leftButtonItem) {
|
||||
if (_leftButtonIcon) {
|
||||
_leftButtonItem = [[UIBarButtonItem alloc] initWithImage:_leftButtonIcon
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
} else if (_leftButtonTitle.length) {
|
||||
_leftButtonItem = [[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
} else {
|
||||
_leftButtonItem = nil;
|
||||
}
|
||||
}
|
||||
return _leftButtonItem;
|
||||
}
|
||||
|
||||
- (void)setRightButtonTitle:(NSString *)rightButtonTitle
|
||||
{
|
||||
_rightButtonTitle = rightButtonTitle;
|
||||
_rightButtonItem = nil;
|
||||
}
|
||||
|
||||
- (void)setRightButtonIcon:(UIImage *)rightButtonIcon
|
||||
{
|
||||
_rightButtonIcon = rightButtonIcon;
|
||||
_rightButtonItem = nil;
|
||||
}
|
||||
|
||||
- (UIBarButtonItem *)rightButtonItem
|
||||
{
|
||||
if (!_rightButtonItem) {
|
||||
if (_rightButtonIcon) {
|
||||
_rightButtonItem = [[UIBarButtonItem alloc] initWithImage:_rightButtonIcon
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
} else if (_rightButtonTitle.length) {
|
||||
_rightButtonItem = [[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
} else {
|
||||
_rightButtonItem = nil;
|
||||
}
|
||||
}
|
||||
return _rightButtonItem;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -21,12 +21,20 @@ RCT_EXPORT_MODULE()
|
|||
return [[RCTNavItem alloc] init];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString);
|
||||
RCT_EXPORT_VIEW_PROPERTY(backButtonTitle, NSString);
|
||||
RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor);
|
||||
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor);
|
||||
RCT_EXPORT_VIEW_PROPERTY(titleTextColor, UIColor);
|
||||
RCT_EXPORT_VIEW_PROPERTY(titleTextColor, UIColor)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(backButtonIcon, UIImage)
|
||||
RCT_EXPORT_VIEW_PROPERTY(backButtonTitle, NSString)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(leftButtonTitle, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(leftButtonIcon, UIImage)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(rightButtonIcon, UIImage)
|
||||
RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString)
|
||||
|
||||
@end
|
||||
|
|
|
@ -315,8 +315,14 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
|
|||
|
||||
- (void)setContentInset:(UIEdgeInsets)contentInset
|
||||
{
|
||||
CGPoint contentOffset = _scrollView.contentOffset;
|
||||
|
||||
_contentInset = contentInset;
|
||||
[self setNeedsLayout];
|
||||
[RCTView autoAdjustInsetsForView:self
|
||||
withScrollView:_scrollView
|
||||
updateOffset:NO];
|
||||
|
||||
_scrollView.contentOffset = contentOffset;
|
||||
}
|
||||
|
||||
- (void)scrollToOffset:(CGPoint)offset
|
||||
|
|
|
@ -41,6 +41,7 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *);
|
|||
@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;
|
||||
|
||||
/**
|
||||
* isNewView - Used to track the first time the view is introduced into the hierarchy. It is initialized YES, then is
|
||||
|
|
|
@ -198,4 +198,6 @@ RCT_CUSTOM_SHADOW_PROPERTY(backgroundColor, UIColor, RCTShadowView)
|
|||
view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
|
||||
}
|
||||
|
||||
RCT_REMAP_SHADOW_PROPERTY(onLayout, hasOnLayout, BOOL)
|
||||
|
||||
@end
|
||||
|
|
|
@ -64,7 +64,6 @@
|
|||
// TODO: find a way to make this less-tightly coupled to navigation controller
|
||||
if ([self.parentViewController isKindOfClass:[UINavigationController class]])
|
||||
{
|
||||
|
||||
[self.navigationController
|
||||
setNavigationBarHidden:_navItem.navigationBarHidden
|
||||
animated:animated];
|
||||
|
@ -73,33 +72,23 @@
|
|||
return;
|
||||
}
|
||||
|
||||
self.navigationItem.title = _navItem.title;
|
||||
|
||||
UINavigationBar *bar = self.navigationController.navigationBar;
|
||||
if (_navItem.barTintColor) {
|
||||
bar.barTintColor = _navItem.barTintColor;
|
||||
}
|
||||
if (_navItem.tintColor) {
|
||||
bar.tintColor = _navItem.tintColor;
|
||||
}
|
||||
bar.barTintColor = _navItem.barTintColor;
|
||||
bar.tintColor = _navItem.tintColor;
|
||||
if (_navItem.titleTextColor) {
|
||||
[bar setTitleTextAttributes:@{NSForegroundColorAttributeName : _navItem.titleTextColor}];
|
||||
}
|
||||
|
||||
if (_navItem.rightButtonTitle.length > 0) {
|
||||
self.navigationItem.rightBarButtonItem =
|
||||
[[UIBarButtonItem alloc] initWithTitle:_navItem.rightButtonTitle
|
||||
style:UIBarButtonItemStyleDone
|
||||
target:self
|
||||
action:@selector(handleNavRightButtonTapped)];
|
||||
UINavigationItem *item = self.navigationItem;
|
||||
item.title = _navItem.title;
|
||||
item.backBarButtonItem = _navItem.backButtonItem;
|
||||
if ((item.leftBarButtonItem = _navItem.leftButtonItem)) {
|
||||
item.leftBarButtonItem.target = self;
|
||||
item.leftBarButtonItem.action = @selector(handleNavLeftButtonTapped);
|
||||
}
|
||||
|
||||
if (_navItem.backButtonTitle.length > 0) {
|
||||
self.navigationItem.backBarButtonItem =
|
||||
[[UIBarButtonItem alloc] initWithTitle:_navItem.backButtonTitle
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
if ((item.rightBarButtonItem = _navItem.rightButtonItem)) {
|
||||
item.rightBarButtonItem.target = self;
|
||||
item.rightBarButtonItem.action = @selector(handleNavRightButtonTapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +103,12 @@
|
|||
self.view = _wrapperView;
|
||||
}
|
||||
|
||||
- (void)handleNavLeftButtonTapped
|
||||
{
|
||||
[_eventDispatcher sendInputEventWithName:@"topNavLeftButtonTap"
|
||||
body:@{@"target":_navItem.reactTag}];
|
||||
}
|
||||
|
||||
- (void)handleNavRightButtonTapped
|
||||
{
|
||||
[_eventDispatcher sendInputEventWithName:@"topNavRightButtonTap"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"graceful-fs": "^3.0.6",
|
||||
"image-size": "0.3.5",
|
||||
"joi": "~5.1.0",
|
||||
"jstransform": "10.1.0",
|
||||
"jstransform": "11.0.1",
|
||||
"module-deps": "3.5.6",
|
||||
"optimist": "0.6.1",
|
||||
"promise": "^7.0.0",
|
||||
|
|
|
@ -166,12 +166,12 @@ describe('Packager', function() {
|
|||
};
|
||||
|
||||
expect(p.addModule.mock.calls[3][0]).toEqual({
|
||||
code: 'lol module.exports = ' +
|
||||
code: 'lol module.exports = require("AssetRegistry").registerAsset(' +
|
||||
JSON.stringify(imgModule) +
|
||||
'; lol',
|
||||
sourceCode: 'module.exports = ' +
|
||||
'); lol',
|
||||
sourceCode: 'module.exports = require("AssetRegistry").registerAsset(' +
|
||||
JSON.stringify(imgModule) +
|
||||
';',
|
||||
');',
|
||||
sourcePath: '/root/img/new_image.png'
|
||||
});
|
||||
|
||||
|
|
|
@ -219,7 +219,8 @@ Packager.prototype.generateAssetModule = function(ppackage, module) {
|
|||
|
||||
ppackage.addAsset(img);
|
||||
|
||||
var code = 'module.exports = ' + JSON.stringify(img) + ';';
|
||||
var ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);';
|
||||
var code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img));
|
||||
|
||||
return new ModuleTransport({
|
||||
code: code,
|
||||
|
|
Loading…
Reference in New Issue