mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
227
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3268f7dd1 | ||
|
|
8abca24268 | ||
|
|
8074b09e7d | ||
|
|
a6b8110528 | ||
|
|
877df8cb1a | ||
|
|
fe189c18aa | ||
|
|
37cc5cfb0c | ||
|
|
41fa6e7f45 | ||
|
|
8b734173d3 | ||
|
|
252102588d | ||
|
|
76003f3720 | ||
|
|
3cabf4f98f | ||
|
|
bbccdd5b84 | ||
|
|
cacf6b1267 | ||
|
|
934acd809d | ||
|
|
24a806a9f1 | ||
|
|
7d1b4d681a | ||
|
|
0f9e31cce8 | ||
|
|
bc634ea350 | ||
|
|
0aea74ebea | ||
|
|
d6c2f5f3f1 | ||
|
|
ef21d99ded | ||
|
|
149d0b91c2 | ||
|
|
00c77800c9 | ||
|
|
3ccfb58701 | ||
|
|
62e588bece | ||
|
|
830197847a | ||
|
|
5e91a2a312 | ||
|
|
942b9332b2 | ||
|
|
6a34c9c3da | ||
|
|
8a201b842a | ||
|
|
838d8d4094 | ||
|
|
514677525a | ||
|
|
dc6a44713d | ||
|
|
7db7f78dc7 | ||
|
|
0e997c6eab | ||
|
|
6796f136f1 | ||
|
|
779314a413 | ||
|
|
7f1346bfdd | ||
|
|
858643dbdf | ||
|
|
18e1b1f197 | ||
|
|
42e517b7a3 | ||
|
|
e3bbd300f6 | ||
|
|
921d0de2e8 | ||
|
|
061151cdae | ||
|
|
f203c5d78c | ||
|
|
17be4c754e | ||
|
|
8199893574 | ||
|
|
0694a2991c | ||
|
|
2a67f9b249 | ||
|
|
6bbaff2944 | ||
|
|
5047f6f54c | ||
|
|
d62f9612f2 | ||
|
|
fdc6240018 | ||
|
|
7446d42d33 | ||
|
|
5bb5ea7135 | ||
|
|
fe5c0d2d06 | ||
|
|
908041b80b | ||
|
|
9092198b1b | ||
|
|
f71329946c | ||
|
|
89b68419ff | ||
|
|
12fcacb58f | ||
|
|
a52e684121 | ||
|
|
ee77e50c4a | ||
|
|
c779e233b6 | ||
|
|
34907c3810 | ||
|
|
0c40f96c32 | ||
|
|
2bb4e69382 | ||
|
|
67d9a7bae9 | ||
|
|
aac7f5b362 | ||
|
|
df40f482b0 | ||
|
|
6457effb7b | ||
|
|
3fe2e6581c | ||
|
|
10429b5822 | ||
|
|
d2934e58b3 | ||
|
|
1673e00e2b | ||
|
|
f13322c820 | ||
|
|
610cfdc819 | ||
|
|
6a3fc86778 | ||
|
|
2f41c56f59 | ||
|
|
516bf7bd94 | ||
|
|
9a899bed65 | ||
|
|
ccef1b2941 | ||
|
|
fa5d1fefa1 | ||
|
|
433c0e8021 | ||
|
|
e34c9c4e5f | ||
|
|
c2a8e52d72 | ||
|
|
3a2d024514 | ||
|
|
9e9dfd2ac9 | ||
|
|
f433ed716c | ||
|
|
267264f1ec | ||
|
|
86f2eb18e5 | ||
|
|
b7fe8e68be | ||
|
|
86e18ace94 | ||
|
|
418e16f711 | ||
|
|
05832c3469 | ||
|
|
448b48ca85 | ||
|
|
7762297f77 | ||
|
|
03edc75b56 | ||
|
|
cac5ce3b93 | ||
|
|
8975bb8e70 | ||
|
|
2849bafbc3 | ||
|
|
b67d4a20d7 | ||
|
|
fd376660df | ||
|
|
cc683bc088 | ||
|
|
e491f7d5c6 | ||
|
|
1322e06cb2 | ||
|
|
b66f098193 | ||
|
|
99b106658f | ||
|
|
588183b166 | ||
|
|
b4e970f6c3 | ||
|
|
3f1cd5aa4e | ||
|
|
90663bcb12 | ||
|
|
e4bb9f52e5 | ||
|
|
cafd63440a | ||
|
|
a53d0f99dd | ||
|
|
853074d1ee | ||
|
|
2c3ca4c058 | ||
|
|
31c8b3bd61 | ||
|
|
f2c1868b56 | ||
|
|
2525feb37f | ||
|
|
cd691e2cc2 | ||
|
|
44bf85a041 | ||
|
|
428c563c75 | ||
|
|
1b5b5d9c5b | ||
|
|
9b56ff6bab | ||
|
|
36b085422d | ||
|
|
85d2086321 | ||
|
|
55c308615a | ||
|
|
763e9cce27 | ||
|
|
8fbce3099d | ||
|
|
20c8899b31 | ||
|
|
31250ad150 | ||
|
|
c3714d7ed7 | ||
|
|
0934470676 | ||
|
|
4a7f2192f9 | ||
|
|
9ee1f37bad | ||
|
|
2d43663ac8 | ||
|
|
4b4c040af7 | ||
|
|
892e509a0c | ||
|
|
052cd7eb8a | ||
|
|
a0384dad3c | ||
|
|
2a150cc30a | ||
|
|
3abccd9640 | ||
|
|
574e81e8cb | ||
|
|
a053f53860 | ||
|
|
df9a2b20a1 | ||
|
|
a95405f419 | ||
|
|
68b2f49a31 | ||
|
|
c1f0ce283c | ||
|
|
1b00534793 | ||
|
|
ce0594dece | ||
|
|
d5ceb4175b | ||
|
|
f1d12f6b38 | ||
|
|
1c31aea83f | ||
|
|
65af925501 | ||
|
|
4c3286f2a2 | ||
|
|
ff7f260ce9 | ||
|
|
6ec5b953cb | ||
|
|
ca1caf2713 | ||
|
|
cd9271567f | ||
|
|
d3e5a96b15 | ||
|
|
6d1790a625 | ||
|
|
128d93b8aa | ||
|
|
d4f6f61a96 | ||
|
|
963a53b1a7 | ||
|
|
e737891242 | ||
|
|
146df61775 | ||
|
|
b24d471fb2 | ||
|
|
57ceeafd4f | ||
|
|
ed1ee9bc0f | ||
|
|
191d278fda | ||
|
|
abca466037 | ||
|
|
28cc8c9776 | ||
|
|
d7de68241f | ||
|
|
54f7ae1c02 | ||
|
|
f7ce0c1c2f | ||
|
|
2953a1a02e | ||
|
|
9aa37e8fb2 | ||
|
|
b88d9d2380 | ||
|
|
17f08070e8 | ||
|
|
0cd2904b23 | ||
|
|
0fab691e6f | ||
|
|
1d101f4236 | ||
|
|
c6d3ca858e | ||
|
|
ff431734a4 | ||
|
|
d03d455cc4 | ||
|
|
0c90ab493f | ||
|
|
097f59112f | ||
|
|
deef8aade2 | ||
|
|
4d2c72b977 | ||
|
|
2132ad1441 | ||
|
|
156d3ed7a2 | ||
|
|
359b381796 | ||
|
|
0ea020d823 | ||
|
|
b1892e6099 | ||
|
|
14eb427a80 | ||
|
|
a412fd1504 | ||
|
|
07767e86a3 | ||
|
|
1958e93f3a | ||
|
|
472f815e45 | ||
|
|
2a7f6aeda7 | ||
|
|
b1cd1cba32 | ||
|
|
b824722501 | ||
|
|
ef44781f3d | ||
|
|
dd8caf4cf2 | ||
|
|
02af7b57d0 | ||
|
|
75c71cfbff | ||
|
|
457e348081 | ||
|
|
a1ba0918ab | ||
|
|
a69b883537 | ||
|
|
a3146e41a2 | ||
|
|
a26afd2d73 | ||
|
|
d037840158 | ||
|
|
9b1a3c79e9 | ||
|
|
e76998531c | ||
|
|
c4e79a4f54 | ||
|
|
3105dc8d6e | ||
|
|
7518c2f827 | ||
|
|
110d1587ae | ||
|
|
2760df761d | ||
|
|
2310494f32 | ||
|
|
041185edfd | ||
|
|
5071907b27 | ||
|
|
00167e4956 | ||
|
|
e72163f0f2 | ||
|
|
a9a90aa2f0 |
@@ -221,17 +221,19 @@
|
||||
|
||||
"react/display-name": 0,
|
||||
"react/jsx-boolean-value": 0,
|
||||
"react/jsx-no-duplicate-props": 2,
|
||||
"react/jsx-no-undef": 1,
|
||||
"react/jsx-sort-props": 0,
|
||||
"react/jsx-uses-react": 0,
|
||||
"react/jsx-uses-react": 1,
|
||||
"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-string-refs": 1,
|
||||
"react/no-unknown-property": 0,
|
||||
"react/prop-types": 0,
|
||||
"react/react-in-jsx-scope": 0,
|
||||
"react/react-in-jsx-scope": 1,
|
||||
"react/self-closing-comp": 1,
|
||||
"react/wrap-multilines": 0
|
||||
"react/wrap-multilines": 0,
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -46,6 +46,9 @@
|
||||
# Ignore BUCK generated folders
|
||||
.*\.buckd/
|
||||
|
||||
# Ignore RNPM
|
||||
.*/local-cli/rnpm/.*
|
||||
|
||||
.*/node_modules/is-my-json-valid/test/.*\.json
|
||||
.*/node_modules/iconv-lite/encodings/tables/.*\.json
|
||||
.*/node_modules/y18n/test/.*\.json
|
||||
@@ -85,9 +88,9 @@ suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
|
||||
[version]
|
||||
0.24.0
|
||||
^0.25.0
|
||||
|
||||
@@ -49,3 +49,6 @@ node_modules
|
||||
|
||||
# OS X
|
||||
.DS_Store
|
||||
|
||||
# Test generated files
|
||||
/ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# rnpm
|
||||
/local-cli/rnpm
|
||||
@@ -5,6 +5,7 @@ osx_image: xcode7.3
|
||||
install:
|
||||
- mkdir -p /Users/travis/build/facebook/.nvm
|
||||
- export NVM_DIR="/Users/travis/build/facebook/.nvm"
|
||||
- brew install nvm
|
||||
- source $(brew --prefix nvm)/nvm.sh
|
||||
# testing with npm@2 for diversity, Circle tests with npm@3
|
||||
- nvm install 4.4.3
|
||||
|
||||
@@ -58,17 +58,20 @@ var ListViewSimpleExample = React.createClass({
|
||||
dataSource={this.state.dataSource}
|
||||
renderRow={this._renderRow}
|
||||
renderScrollComponent={props => <RecyclerViewBackedScrollView {...props} />}
|
||||
renderSeparator={(sectionID, rowID) => <View key={`${sectionID}-${rowID}`} style={styles.separator} />}
|
||||
renderSeparator={this._renderSeperator}
|
||||
/>
|
||||
</UIExplorerPage>
|
||||
);
|
||||
},
|
||||
|
||||
_renderRow: function(rowData: string, sectionID: number, rowID: number) {
|
||||
_renderRow: function(rowData: string, sectionID: number, rowID: number, highlightRow: (sectionID: number, rowID: number) => void) {
|
||||
var rowHash = Math.abs(hashCode(rowData));
|
||||
var imgSource = THUMB_URLS[rowHash % THUMB_URLS.length];
|
||||
return (
|
||||
<TouchableHighlight onPress={() => this._pressRow(rowID)}>
|
||||
<TouchableHighlight onPress={() => {
|
||||
this._pressRow(rowID);
|
||||
highlightRow(sectionID, rowID);
|
||||
}}>
|
||||
<View>
|
||||
<View style={styles.row}>
|
||||
<Image style={styles.thumb} source={imgSource} />
|
||||
@@ -96,6 +99,18 @@ var ListViewSimpleExample = React.createClass({
|
||||
this._genRows(this._pressData)
|
||||
)});
|
||||
},
|
||||
|
||||
_renderSeperator: function(sectionID: number, rowID: number, adjacentRowHighlighted: bool) {
|
||||
return (
|
||||
<View
|
||||
key={`${sectionID}-${rowID}`}
|
||||
style={{
|
||||
height: adjacentRowHighlighted ? 4 : 1,
|
||||
backgroundColor: adjacentRowHighlighted ? '#3B5998' : '#CCCCCC',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var THUMB_URLS = [
|
||||
@@ -130,10 +145,6 @@ var styles = StyleSheet.create({
|
||||
padding: 10,
|
||||
backgroundColor: '#F6F6F6',
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: '#CCCCCC',
|
||||
},
|
||||
thumb: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
NavigationExperimental,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity
|
||||
} = ReactNative;
|
||||
|
||||
var _getRandomRoute = function() {
|
||||
return {
|
||||
title: '#' + Math.ceil(Math.random() * 1000),
|
||||
};
|
||||
};
|
||||
|
||||
var Navigator = NavigationExperimental.LegacyNavigator;
|
||||
|
||||
class NavButton extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.button}
|
||||
underlayColor="#B5B5B5"
|
||||
onPress={this.props.onPress}>
|
||||
<Text style={styles.buttonText}>{this.props.text}</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var BreadcrumbNavSample = React.createClass({
|
||||
|
||||
componentWillMount: function() {
|
||||
this._navBarRouteMapper = {
|
||||
rightContentForRoute: function(route, navigator) {
|
||||
return null;
|
||||
},
|
||||
titleContentForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.push(_getRandomRoute())}>
|
||||
<Text style={styles.titleText}>{route.title}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
iconForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => { navigator.popToRoute(route); }}
|
||||
style={styles.crumbIconPlaceholder}
|
||||
/>
|
||||
);
|
||||
},
|
||||
separatorForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={navigator.pop}
|
||||
style={styles.crumbSeparatorPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
_renderScene: function(route, navigator) {
|
||||
return (
|
||||
<ScrollView style={styles.scene}>
|
||||
<NavButton
|
||||
onPress={() => { navigator.push(_getRandomRoute()); }}
|
||||
text="Push"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => { navigator.immediatelyResetRouteStack([_getRandomRoute(), _getRandomRoute()]); }}
|
||||
text="Reset w/ 2 scenes"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => { navigator.popToTop(); }}
|
||||
text="Pop to top"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => { navigator.replace(_getRandomRoute()); }}
|
||||
text="Replace"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => { this.props.navigator.pop(); }}
|
||||
text="Close breadcrumb example"
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<Navigator
|
||||
style={styles.container}
|
||||
initialRoute={_getRandomRoute()}
|
||||
renderScene={this._renderScene}
|
||||
navigationBar={
|
||||
<Navigator.BreadcrumbNavigationBar
|
||||
routeMapper={this._navBarRouteMapper}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
scene: {
|
||||
paddingTop: 50,
|
||||
flex: 1,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: 'white',
|
||||
padding: 15,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#CDCDCD',
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
},
|
||||
container: {
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#dddddd',
|
||||
flex: 1,
|
||||
},
|
||||
titleText: {
|
||||
fontSize: 18,
|
||||
color: '#666666',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
lineHeight: 32,
|
||||
},
|
||||
crumbIconPlaceholder: {
|
||||
flex: 1,
|
||||
backgroundColor: '#666666',
|
||||
},
|
||||
crumbSeparatorPlaceholder: {
|
||||
flex: 1,
|
||||
backgroundColor: '#aaaaaa',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = BreadcrumbNavSample;
|
||||
@@ -1,219 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
NavigationExperimental,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
TabBarIOS,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = ReactNative;
|
||||
|
||||
var _getRandomRoute = function() {
|
||||
return {
|
||||
randNumber: Math.ceil(Math.random() * 1000),
|
||||
};
|
||||
};
|
||||
|
||||
var Navigator = NavigationExperimental.LegacyNavigator;
|
||||
|
||||
class NavButton extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.button}
|
||||
underlayColor="#B5B5B5"
|
||||
onPress={this.props.onPress}>
|
||||
<Text style={styles.buttonText}>{this.props.text}</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var ROUTE_STACK = [
|
||||
_getRandomRoute(),
|
||||
_getRandomRoute(),
|
||||
_getRandomRoute(),
|
||||
];
|
||||
var INIT_ROUTE_INDEX = 1;
|
||||
|
||||
class JumpingNavBar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tabIndex: props.initTabIndex,
|
||||
};
|
||||
}
|
||||
handleWillFocus(route) {
|
||||
var tabIndex = ROUTE_STACK.indexOf(route);
|
||||
this.setState({ tabIndex, });
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.tabs}>
|
||||
<TabBarIOS>
|
||||
<TabBarIOS.Item
|
||||
icon={require('image!tabnav_notification')}
|
||||
selected={this.state.tabIndex === 0}
|
||||
onPress={() => {
|
||||
this.props.onTabIndex(0);
|
||||
this.setState({ tabIndex: 0, });
|
||||
}}>
|
||||
<View />
|
||||
</TabBarIOS.Item>
|
||||
<TabBarIOS.Item
|
||||
icon={require('image!tabnav_list')}
|
||||
selected={this.state.tabIndex === 1}
|
||||
onPress={() => {
|
||||
this.props.onTabIndex(1);
|
||||
this.setState({ tabIndex: 1, });
|
||||
}}>
|
||||
<View />
|
||||
</TabBarIOS.Item>
|
||||
<TabBarIOS.Item
|
||||
icon={require('image!tabnav_settings')}
|
||||
selected={this.state.tabIndex === 2}
|
||||
onPress={() => {
|
||||
this.props.onTabIndex(2);
|
||||
this.setState({ tabIndex: 2, });
|
||||
}}>
|
||||
<View />
|
||||
</TabBarIOS.Item>
|
||||
</TabBarIOS>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var JumpingNavSample = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<Navigator
|
||||
debugOverlay={false}
|
||||
style={styles.appContainer}
|
||||
ref={(navigator) => {
|
||||
this._navigator = navigator;
|
||||
}}
|
||||
initialRoute={ROUTE_STACK[INIT_ROUTE_INDEX]}
|
||||
initialRouteStack={ROUTE_STACK}
|
||||
renderScene={this.renderScene}
|
||||
configureScene={() => ({
|
||||
...Navigator.SceneConfigs.HorizontalSwipeJump,
|
||||
})}
|
||||
navigationBar={
|
||||
<JumpingNavBar
|
||||
ref={(navBar) => { this.navBar = navBar; }}
|
||||
initTabIndex={INIT_ROUTE_INDEX}
|
||||
routeStack={ROUTE_STACK}
|
||||
onTabIndex={(index) => {
|
||||
this._navigator.jumpTo(ROUTE_STACK[index]);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
renderScene: function(route, navigator) {
|
||||
var backBtn;
|
||||
var forwardBtn;
|
||||
if (ROUTE_STACK.indexOf(route) !== 0) {
|
||||
backBtn = (
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
navigator.jumpBack();
|
||||
}}
|
||||
text="jumpBack"
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (ROUTE_STACK.indexOf(route) !== ROUTE_STACK.length - 1) {
|
||||
forwardBtn = (
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
navigator.jumpForward();
|
||||
}}
|
||||
text="jumpForward"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ScrollView style={styles.scene}>
|
||||
<Text style={styles.messageText}>#{route.randNumber}</Text>
|
||||
{backBtn}
|
||||
{forwardBtn}
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
navigator.jumpTo(ROUTE_STACK[1]);
|
||||
}}
|
||||
text="jumpTo middle route"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.pop();
|
||||
}}
|
||||
text="Exit Navigation Example"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.push({
|
||||
message: 'Came from jumping example',
|
||||
});
|
||||
}}
|
||||
text="Nav Menu"
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
button: {
|
||||
backgroundColor: 'white',
|
||||
padding: 15,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#CDCDCD',
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
},
|
||||
appContainer: {
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#dddddd',
|
||||
flex: 1,
|
||||
},
|
||||
messageText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
padding: 15,
|
||||
marginTop: 50,
|
||||
marginLeft: 15,
|
||||
},
|
||||
scene: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
backgroundColor: '#EAEAEA',
|
||||
},
|
||||
tabs: {
|
||||
height: 50,
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = JumpingNavSample;
|
||||
@@ -1,220 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
NavigationExperimental,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
} = ReactNative;
|
||||
|
||||
var BreadcrumbNavSample = require('./BreadcrumbNavSample');
|
||||
var NavigationBarSample = require('./NavigationBarSample');
|
||||
var JumpingNavSample = require('./JumpingNavSample');
|
||||
|
||||
var Navigator = NavigationExperimental.LegacyNavigator;
|
||||
|
||||
class NavButton extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.button}
|
||||
underlayColor="#B5B5B5"
|
||||
onPress={this.props.onPress}>
|
||||
<Text style={styles.buttonText}>{this.props.text}</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NavMenu extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ScrollView style={styles.scene}>
|
||||
<Text style={styles.messageText}>{this.props.message}</Text>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.push({
|
||||
message: 'Swipe right to dismiss',
|
||||
sceneConfig: Navigator.SceneConfigs.FloatFromRight,
|
||||
});
|
||||
}}
|
||||
text="Float in from right"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.push({
|
||||
message: 'Swipe down to dismiss',
|
||||
sceneConfig: Navigator.SceneConfigs.FloatFromBottom,
|
||||
});
|
||||
}}
|
||||
text="Float in from bottom"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.pop();
|
||||
}}
|
||||
text="Pop"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.popToTop();
|
||||
}}
|
||||
text="Pop to top"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.push({ id: 'navbar' });
|
||||
}}
|
||||
text="Navbar Example"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.push({ id: 'jumping' });
|
||||
}}
|
||||
text="Jumping Example"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.push({ id: 'breadcrumbs' });
|
||||
}}
|
||||
text="Breadcrumbs Example"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.onExampleExit();
|
||||
}}
|
||||
text="Exit <LegacyNavigator> Example"
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var TabBarExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
title: '<LegacyNavigator> (Experimental)',
|
||||
description: 'Experimental navigator that ports the API of the current ' +
|
||||
'Navigator component',
|
||||
},
|
||||
|
||||
renderScene: function(route, nav) {
|
||||
switch (route.id) {
|
||||
case 'navbar':
|
||||
return <NavigationBarSample navigator={nav} />;
|
||||
case 'breadcrumbs':
|
||||
return <BreadcrumbNavSample navigator={nav} />;
|
||||
case 'jumping':
|
||||
return <JumpingNavSample navigator={nav} />;
|
||||
default:
|
||||
return (
|
||||
<NavMenu
|
||||
message={route.message}
|
||||
navigator={nav}
|
||||
onExampleExit={this.props.onExampleExit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<Navigator
|
||||
ref={this._setNavigatorRef}
|
||||
style={styles.container}
|
||||
initialRoute={{ message: 'First Scene', }}
|
||||
renderScene={this.renderScene}
|
||||
configureScene={(route) => {
|
||||
if (route.sceneConfig) {
|
||||
return route.sceneConfig;
|
||||
}
|
||||
return Navigator.SceneConfigs.FloatFromBottom;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._listeners && this._listeners.forEach(listener => listener.remove());
|
||||
},
|
||||
|
||||
_setNavigatorRef: function(navigator) {
|
||||
if (navigator !== this._navigator) {
|
||||
this._navigator = navigator;
|
||||
|
||||
if (navigator) {
|
||||
var callback = (event) => {
|
||||
console.log(
|
||||
`LegacyNavigator: event ${event.type}`,
|
||||
{
|
||||
route: JSON.stringify(event.data.route),
|
||||
target: event.target,
|
||||
type: event.type,
|
||||
}
|
||||
);
|
||||
};
|
||||
// Observe focus change events from the owner.
|
||||
this._listeners = [
|
||||
navigator.navigationContext.addListener('willfocus', callback),
|
||||
navigator.navigationContext.addListener('didfocus', callback),
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
messageText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
padding: 15,
|
||||
marginTop: 50,
|
||||
marginLeft: 15,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: 'white',
|
||||
padding: 15,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#CDCDCD',
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
},
|
||||
scene: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
backgroundColor: '#ccc',
|
||||
}
|
||||
});
|
||||
|
||||
TabBarExample.external = true;
|
||||
|
||||
module.exports = TabBarExample;
|
||||
@@ -1,202 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
NavigationExperimental,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
} = ReactNative;
|
||||
|
||||
var Navigator = NavigationExperimental.LegacyNavigator;
|
||||
|
||||
class NavButton extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.button}
|
||||
underlayColor="#B5B5B5"
|
||||
onPress={this.props.onPress}>
|
||||
<Text style={styles.buttonText}>{this.props.text}</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var NavigationBarRouteMapper = {
|
||||
|
||||
LeftButton: function(route, navigator, index, navState) {
|
||||
if (index === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var previousRoute = navState.routeStack[index - 1];
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.pop()}
|
||||
style={styles.navBarLeftButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
{previousRoute.title}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
|
||||
RightButton: function(route, navigator, index, navState) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.push(newRandomRoute())}
|
||||
style={styles.navBarRightButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
Next
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
|
||||
Title: function(route, navigator, index, navState) {
|
||||
return (
|
||||
<Text style={[styles.navBarText, styles.navBarTitleText]}>
|
||||
{route.title} [{index}]
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
function newRandomRoute() {
|
||||
return {
|
||||
title: '#' + Math.ceil(Math.random() * 1000),
|
||||
};
|
||||
}
|
||||
|
||||
var NavigationBarSample = React.createClass({
|
||||
|
||||
componentWillMount: function() {
|
||||
var navigator = this.props.navigator;
|
||||
|
||||
var callback = (event) => {
|
||||
console.log(
|
||||
`NavigationBarSample : event ${event.type}`,
|
||||
{
|
||||
route: JSON.stringify(event.data.route),
|
||||
target: event.target,
|
||||
type: event.type,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Observe focus change events from this component.
|
||||
this._listeners = [
|
||||
navigator.navigationContext.addListener('willfocus', callback),
|
||||
navigator.navigationContext.addListener('didfocus', callback),
|
||||
];
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._listeners && this._listeners.forEach(listener => listener.remove());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<Navigator
|
||||
debugOverlay={false}
|
||||
style={styles.appContainer}
|
||||
initialRoute={newRandomRoute()}
|
||||
renderScene={(route, navigator) => (
|
||||
<ScrollView style={styles.scene}>
|
||||
<Text style={styles.messageText}>{route.content}</Text>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
navigator.immediatelyResetRouteStack([
|
||||
newRandomRoute(),
|
||||
newRandomRoute(),
|
||||
newRandomRoute(),
|
||||
]);
|
||||
}}
|
||||
text="Reset w/ 3 scenes"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => {
|
||||
this.props.navigator.pop();
|
||||
}}
|
||||
text="Exit NavigationBar Example"
|
||||
/>
|
||||
</ScrollView>
|
||||
)}
|
||||
navigationBar={
|
||||
<Navigator.NavigationBar
|
||||
routeMapper={NavigationBarRouteMapper}
|
||||
style={styles.navBar}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
messageText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
padding: 15,
|
||||
marginTop: 50,
|
||||
marginLeft: 15,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: 'white',
|
||||
padding: 15,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#CDCDCD',
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
},
|
||||
navBar: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
navBarText: {
|
||||
fontSize: 16,
|
||||
marginVertical: 10,
|
||||
},
|
||||
navBarTitleText: {
|
||||
color: '#373E4D',
|
||||
fontWeight: '500',
|
||||
marginVertical: 9,
|
||||
},
|
||||
navBarLeftButton: {
|
||||
paddingLeft: 10,
|
||||
},
|
||||
navBarRightButton: {
|
||||
paddingRight: 10,
|
||||
},
|
||||
navBarButtonText: {
|
||||
color: '#5890FF',
|
||||
},
|
||||
scene: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
backgroundColor: '#EAEAEA',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationBarSample;
|
||||
@@ -37,10 +37,9 @@ const {
|
||||
Card: NavigationCard,
|
||||
Header: NavigationHeader,
|
||||
Reducer: NavigationReducer,
|
||||
RootContainer: NavigationRootContainer,
|
||||
} = NavigationExperimental;
|
||||
|
||||
const NavigationBasicReducer = NavigationReducer.StackReducer({
|
||||
const ExampleReducer = NavigationReducer.StackReducer({
|
||||
getPushedReducerForAction: (action) => {
|
||||
if (action.type === 'push') {
|
||||
return (state) => state || {key: action.key};
|
||||
@@ -58,37 +57,41 @@ const NavigationBasicReducer = NavigationReducer.StackReducer({
|
||||
});
|
||||
|
||||
class NavigationAnimatedExample extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = ExampleReducer();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._renderCard = this._renderCard.bind(this);
|
||||
this._renderHeader = this._renderHeader.bind(this);
|
||||
this._renderNavigation = this._renderNavigation.bind(this);
|
||||
this._renderScene = this._renderScene.bind(this);
|
||||
this._renderTitleComponent = this._renderTitleComponent.bind(this);
|
||||
this._handleAction = this._handleAction.bind(this);
|
||||
}
|
||||
|
||||
_handleAction(action): boolean {
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
const newState = ExampleReducer(this.state, action);
|
||||
if (newState === this.state) {
|
||||
return false;
|
||||
}
|
||||
this.setState(newState);
|
||||
return true;
|
||||
}
|
||||
|
||||
handleBackAction(): boolean {
|
||||
return this._handleAction({ type: 'BackAction', });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={NavigationBasicReducer}
|
||||
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
|
||||
persistenceKey="NavigationAnimExampleState"
|
||||
renderNavigation={this._renderNavigation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
handleBackAction() {
|
||||
return (
|
||||
this.navRootContainer &&
|
||||
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
|
||||
);
|
||||
}
|
||||
_renderNavigation(navigationState, onNavigate) {
|
||||
if (!navigationState) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<NavigationAnimatedView
|
||||
navigationState={navigationState}
|
||||
navigationState={this.state}
|
||||
style={styles.animatedView}
|
||||
onNavigate={this._handleAction}
|
||||
renderOverlay={this._renderHeader}
|
||||
applyAnimation={(pos, navState) => {
|
||||
Animated.timing(pos, {toValue: navState.index, duration: 500}).start();
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -22,12 +29,10 @@ const {
|
||||
} = ReactNative;
|
||||
const NavigationExampleRow = require('./NavigationExampleRow');
|
||||
const {
|
||||
RootContainer: NavigationRootContainer,
|
||||
Reducer: NavigationReducer,
|
||||
} = NavigationExperimental;
|
||||
const StackReducer = NavigationReducer.StackReducer;
|
||||
|
||||
const NavigationBasicReducer = NavigationReducer.StackReducer({
|
||||
const ExampleReducer = NavigationReducer.StackReducer({
|
||||
getPushedReducerForAction: (action) => {
|
||||
if (action.type === 'push') {
|
||||
return (state) => state || {key: action.key};
|
||||
@@ -45,47 +50,51 @@ const NavigationBasicReducer = NavigationReducer.StackReducer({
|
||||
});
|
||||
|
||||
const NavigationBasicExample = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return ExampleReducer();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={NavigationBasicReducer}
|
||||
persistenceKey="NavigationBasicExampleState"
|
||||
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
|
||||
renderNavigation={(navState, onNavigate) => {
|
||||
if (!navState) { return null; }
|
||||
return (
|
||||
<ScrollView style={styles.topView}>
|
||||
<NavigationExampleRow
|
||||
text={`Current page: ${navState.children[navState.index].key}`}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text={`Push page #${navState.children.length}`}
|
||||
onPress={() => {
|
||||
onNavigate({ type: 'push', key: 'page #' + navState.children.length });
|
||||
}}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="pop"
|
||||
onPress={() => {
|
||||
onNavigate(NavigationRootContainer.getBackAction());
|
||||
}}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="Exit Basic Nav Example"
|
||||
onPress={this.props.onExampleExit}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<ScrollView style={styles.topView}>
|
||||
<NavigationExampleRow
|
||||
text={`Current page: ${this.state.children[this.state.index].key}`}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text={`Push page #${this.state.children.length}`}
|
||||
onPress={() => {
|
||||
this._handleAction({ type: 'push', key: 'page #' + this.state.children.length });
|
||||
}}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="pop"
|
||||
onPress={() => {
|
||||
this._handleAction({ type: 'BackAction' });
|
||||
}}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="Exit Basic Nav Example"
|
||||
onPress={this.props.onExampleExit}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
},
|
||||
|
||||
handleBackAction: function() {
|
||||
return (
|
||||
this.navRootContainer &&
|
||||
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
|
||||
);
|
||||
_handleAction(action) {
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
const newState = ExampleReducer(this.state, action);
|
||||
if (newState === this.state) {
|
||||
return false;
|
||||
}
|
||||
this.setState(newState);
|
||||
return true;
|
||||
},
|
||||
|
||||
handleBackAction() {
|
||||
return this._handleAction({ type: 'BackAction' });
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -33,18 +33,15 @@ const {
|
||||
const {
|
||||
CardStack: NavigationCardStack,
|
||||
StateUtils: NavigationStateUtils,
|
||||
RootContainer: NavigationRootContainer,
|
||||
} = NavigationExperimental;
|
||||
|
||||
function createReducer(initialState) {
|
||||
return (currentState, action) => {
|
||||
return (currentState = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'RootContainerInitialAction':
|
||||
return initialState;
|
||||
|
||||
case 'push':
|
||||
return NavigationStateUtils.push(currentState, {key: action.key});
|
||||
|
||||
case 'BackAction':
|
||||
case 'back':
|
||||
case 'pop':
|
||||
return currentState.index > 0 ?
|
||||
@@ -67,35 +64,46 @@ class NavigationCardStackExample extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {isHorizontal: true};
|
||||
this.state = {
|
||||
isHorizontal: true,
|
||||
navState: ExampleReducer(undefined, {}),
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._renderNavigation = this._renderNavigation.bind(this);
|
||||
this._renderScene = this._renderScene.bind(this);
|
||||
this._toggleDirection = this._toggleDirection.bind(this);
|
||||
this._handleAction = this._handleAction.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={ExampleReducer}
|
||||
renderNavigation={this._renderNavigation}
|
||||
<NavigationCardStack
|
||||
direction={this.state.isHorizontal ? 'horizontal' : 'vertical'}
|
||||
navigationState={this.state.navState}
|
||||
onNavigate={this._handleAction}
|
||||
renderScene={this._renderScene}
|
||||
style={styles.main}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_renderNavigation(navigationState, onNavigate) {
|
||||
return (
|
||||
<NavigationCardStack
|
||||
direction={this.state.isHorizontal ? 'horizontal' : 'vertical'}
|
||||
navigationState={navigationState}
|
||||
onNavigate={onNavigate}
|
||||
renderScene={this._renderScene}
|
||||
style={styles.main}
|
||||
/>
|
||||
);
|
||||
_handleAction(action): boolean {
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
const newState = ExampleReducer(this.state.navState, action);
|
||||
if (newState === this.state.navState) {
|
||||
return false;
|
||||
}
|
||||
this.setState({
|
||||
navState: newState,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
handleBackAction(): boolean {
|
||||
return this._handleAction({ type: 'BackAction', });
|
||||
}
|
||||
|
||||
_renderScene(/*NavigationSceneRendererProps*/ props) {
|
||||
|
||||
@@ -36,11 +36,8 @@ const {
|
||||
|
||||
const {
|
||||
CardStack: NavigationCardStack,
|
||||
Container: NavigationContainer,
|
||||
Header: NavigationHeader,
|
||||
Reducer: NavigationReducer,
|
||||
RootContainer: NavigationRootContainer,
|
||||
View: NavigationView,
|
||||
} = NavigationExperimental;
|
||||
|
||||
|
||||
@@ -177,6 +174,7 @@ class ExampleTabScreen extends React.Component {
|
||||
<NavigationCardStack
|
||||
style={styles.tabContent}
|
||||
navigationState={this.props.navigationState}
|
||||
onNavigate={this.props.onNavigate}
|
||||
renderOverlay={this._renderHeader}
|
||||
renderScene={this._renderScene}
|
||||
/>
|
||||
@@ -225,40 +223,42 @@ class ExampleTabScreen extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
ExampleTabScreen = NavigationContainer.create(ExampleTabScreen);
|
||||
|
||||
class NavigationCompositionExample extends React.Component {
|
||||
navRootContainer: NavigationRootContainer;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={ExampleAppReducer}
|
||||
persistenceKey="NavigationCompositionState"
|
||||
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
|
||||
renderNavigation={this.renderApp.bind(this)}
|
||||
/>
|
||||
);
|
||||
state: NavigationParentState;
|
||||
constructor() {
|
||||
super();
|
||||
this.state = ExampleAppReducer(undefined, {});
|
||||
}
|
||||
handleAction(action: Object): boolean {
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
const newState = ExampleAppReducer(this.state, action);
|
||||
if (newState === this.state) {
|
||||
return false;
|
||||
}
|
||||
this.setState(newState);
|
||||
return true;
|
||||
}
|
||||
handleBackAction(): boolean {
|
||||
return (
|
||||
this.navRootContainer &&
|
||||
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
|
||||
);
|
||||
return this.handleAction({ type: 'BackAction' });
|
||||
}
|
||||
renderApp(navigationState: NavigationParentState, onNavigate: Function) {
|
||||
if (!navigationState) {
|
||||
render() {
|
||||
if (!this.state) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View style={styles.topView}>
|
||||
<ExampleMainView
|
||||
navigationState={navigationState}
|
||||
navigationState={this.state}
|
||||
onExampleExit={this.props.onExampleExit}
|
||||
onNavigate={this.handleAction.bind(this)}
|
||||
/>
|
||||
<NavigationExampleTabBar
|
||||
tabs={navigationState.children}
|
||||
index={navigationState.index}
|
||||
tabs={this.state.children}
|
||||
index={this.state.index}
|
||||
onNavigate={this.handleAction.bind(this)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -266,34 +266,35 @@ class NavigationCompositionExample extends React.Component {
|
||||
}
|
||||
|
||||
class ExampleMainView extends React.Component {
|
||||
_renderScene: NavigationSceneRenderer;
|
||||
_renderScene: Function;
|
||||
_handleNavigation: Function;
|
||||
|
||||
componentWillMount() {
|
||||
this._renderScene = this._renderScene.bind(this);
|
||||
this._handleNavigation = this._handleNavigation.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NavigationView
|
||||
navigationState={this.props.navigationState}
|
||||
style={styles.tabsContent}
|
||||
renderScene={this._renderScene}
|
||||
/>
|
||||
<View style={styles.tabsContent}>
|
||||
{this._renderScene()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderScene(props: NavigationSceneRendererProps): ReactElement {
|
||||
const {scene} = props;
|
||||
_renderScene(): ReactElement {
|
||||
const {navigationState} = this.props;
|
||||
const childState = navigationState.children[navigationState.index];
|
||||
return (
|
||||
<ExampleTabScreen
|
||||
key={'tab_screen' + scene.key}
|
||||
navigationState={scene.navigationState}
|
||||
onNavigate={this._handleNavigation.bind(this, scene.key)}
|
||||
key={'tab_screen' + childState.key}
|
||||
navigationState={childState}
|
||||
onNavigate={this._handleNavigation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_handleNavigation(tabKey, action) {
|
||||
_handleNavigation(action: Object) {
|
||||
if (ExampleExitAction.match(action)) {
|
||||
this.props.onExampleExit();
|
||||
return;
|
||||
@@ -302,8 +303,6 @@ class ExampleMainView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
ExampleMainView = NavigationContainer.create(ExampleMainView);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topView: {
|
||||
flex: 1,
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -13,24 +20,20 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
NavigationExperimental,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = ReactNative;
|
||||
const NavigationExperimental = require('NavigationExperimental');
|
||||
const React = require('react');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const Text = require('Text');
|
||||
const TouchableOpacity = require('TouchableOpacity');
|
||||
const View = require('View');
|
||||
const {
|
||||
Container: NavigationContainer,
|
||||
Reducer: NavigationReducer,
|
||||
} = NavigationExperimental;
|
||||
const {
|
||||
JumpToAction,
|
||||
} = NavigationReducer.TabsReducer;
|
||||
|
||||
var NavigationExampleTabBar = React.createClass({
|
||||
const NavigationExampleTabBar = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.tabBar}>
|
||||
@@ -39,7 +42,7 @@ var NavigationExampleTabBar = React.createClass({
|
||||
);
|
||||
},
|
||||
_renderTab: function(tab, index) {
|
||||
var textStyle = [styles.tabButtonText];
|
||||
const textStyle = [styles.tabButtonText];
|
||||
if (this.props.index === index) {
|
||||
textStyle.push(styles.selectedTabButtonText);
|
||||
}
|
||||
@@ -58,8 +61,6 @@ var NavigationExampleTabBar = React.createClass({
|
||||
},
|
||||
});
|
||||
|
||||
NavigationExampleTabBar = NavigationContainer.create(NavigationExampleTabBar);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabBar: {
|
||||
height: 50,
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -13,22 +20,19 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
AsyncStorage,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} = ReactNative;
|
||||
var NavigationExampleRow = require('./NavigationExampleRow');
|
||||
const AsyncStorage = require('AsyncStorage');
|
||||
const NavigationExampleRow = require('./NavigationExampleRow');
|
||||
const React = require('react');
|
||||
const ScrollView = require('ScrollView');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
/*
|
||||
* Heads up! This file is not the real navigation example- only a utility to switch between them.
|
||||
*
|
||||
* To learn how to use the Navigation API, take a look at the following example files:
|
||||
*/
|
||||
var EXAMPLES = {
|
||||
const EXAMPLES = {
|
||||
'Tabs': require('./NavigationTabsExample'),
|
||||
'Basic': require('./NavigationBasicExample'),
|
||||
'Animated Example': require('./NavigationAnimatedExample'),
|
||||
@@ -37,9 +41,9 @@ var EXAMPLES = {
|
||||
'Tic Tac Toe': require('./NavigationTicTacToeExample'),
|
||||
};
|
||||
|
||||
var EXAMPLE_STORAGE_KEY = 'NavigationExperimentalExample';
|
||||
const EXAMPLE_STORAGE_KEY = 'NavigationExperimentalExample';
|
||||
|
||||
var NavigationExperimentalExample = React.createClass({
|
||||
const NavigationExperimentalExample = React.createClass({
|
||||
statics: {
|
||||
title: 'Navigation (Experimental)',
|
||||
description: 'Upcoming navigation APIs and animated navigation views',
|
||||
@@ -74,7 +78,7 @@ var NavigationExperimentalExample = React.createClass({
|
||||
},
|
||||
|
||||
_renderMenu: function() {
|
||||
var exitRow = null;
|
||||
let exitRow = null;
|
||||
if (this.props.onExampleExit) {
|
||||
exitRow = (
|
||||
<NavigationExampleRow
|
||||
@@ -130,7 +134,7 @@ var NavigationExperimentalExample = React.createClass({
|
||||
return this._renderMenu();
|
||||
}
|
||||
if (EXAMPLES[this.state.example]) {
|
||||
var Component = EXAMPLES[this.state.example];
|
||||
const Component = EXAMPLES[this.state.example];
|
||||
return (
|
||||
<Component
|
||||
onExampleExit={this._exitInnerExample}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -22,8 +29,6 @@ const {
|
||||
View,
|
||||
} = ReactNative;
|
||||
const {
|
||||
Container: NavigationContainer,
|
||||
RootContainer: NavigationRootContainer,
|
||||
Reducer: NavigationReducer,
|
||||
} = NavigationExperimental;
|
||||
|
||||
@@ -55,7 +60,6 @@ class ExmpleTabPage extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
ExmpleTabPage = NavigationContainer.create(ExmpleTabPage);
|
||||
|
||||
const ExampleTabsReducer = NavigationReducer.TabsReducer({
|
||||
tabReducers: [
|
||||
@@ -66,36 +70,40 @@ const ExampleTabsReducer = NavigationReducer.TabsReducer({
|
||||
});
|
||||
|
||||
class NavigationTabsExample extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = ExampleTabsReducer(undefined, {});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={ExampleTabsReducer}
|
||||
persistenceKey="NAV_EXAMPLE_STATE_TABS"
|
||||
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
|
||||
renderNavigation={(navigationState) => {
|
||||
if (!navigationState) { return null; }
|
||||
return (
|
||||
<View style={styles.topView}>
|
||||
<ExmpleTabPage
|
||||
tabs={navigationState.children}
|
||||
index={navigationState.index}
|
||||
onExampleExit={this.props.onExampleExit}
|
||||
/>
|
||||
<NavigationExampleTabBar
|
||||
tabs={navigationState.children}
|
||||
index={navigationState.index}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<View style={styles.topView}>
|
||||
<ExmpleTabPage
|
||||
tabs={this.state.children}
|
||||
index={this.state.index}
|
||||
onExampleExit={this.props.onExampleExit}
|
||||
onNavigate={this.handleAction.bind(this)}
|
||||
/>
|
||||
<NavigationExampleTabBar
|
||||
tabs={this.state.children}
|
||||
index={this.state.index}
|
||||
onNavigate={this.handleAction.bind(this)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
handleAction(action) {
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
const newState = ExampleTabsReducer(this.state, action);
|
||||
if (newState === this.state) {
|
||||
return false;
|
||||
}
|
||||
this.setState(newState);
|
||||
return true;
|
||||
}
|
||||
handleBackAction() {
|
||||
return (
|
||||
this.navRootContainer &&
|
||||
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
|
||||
);
|
||||
return this.handleAction({ type: 'BackAction' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -16,19 +23,11 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
NavigationExperimental,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = ReactNative;
|
||||
const {
|
||||
Container: NavigationContainer,
|
||||
RootContainer: NavigationRootContainer,
|
||||
} = NavigationExperimental;
|
||||
const React = require('react');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const Text = require('Text');
|
||||
const TouchableHighlight = require('TouchableHighlight');
|
||||
const View = require('View');
|
||||
|
||||
type GameGrid = Array<Array<?string>>;
|
||||
|
||||
@@ -41,7 +40,7 @@ function parseGame(game: string): GameGrid {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const row = Array(3);
|
||||
for (let j = 0; j < 3; j++) {
|
||||
const turnIndex = gameTurns.indexOf(rowLetterMap[i]+j);
|
||||
const turnIndex = gameTurns.indexOf(rowLetterMap[i] + j);
|
||||
if (turnIndex === -1) {
|
||||
row[j] = null;
|
||||
} else {
|
||||
@@ -60,13 +59,13 @@ function playTurn(game: string, row: number, col: number): string {
|
||||
|
||||
function getWinner(gameString: string): ?string {
|
||||
const game = parseGame(gameString);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (game[i][0] !== null && game[i][0] === game[i][1] &&
|
||||
game[i][0] === game[i][2]) {
|
||||
return game[i][0];
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < 3; i++) {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (game[0][i] !== null && game[0][i] === game[1][i] &&
|
||||
game[0][i] === game[2][i]) {
|
||||
return game[0][i];
|
||||
@@ -88,8 +87,8 @@ function isGameOver(gameString: string): boolean {
|
||||
return true;
|
||||
}
|
||||
const game = parseGame(gameString);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
for (var j = 0; j < 3; j++) {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (let j = 0; j < 3; j++) {
|
||||
if (game[i][j] === null) {
|
||||
return false;
|
||||
}
|
||||
@@ -157,10 +156,9 @@ function GameEndOverlay(props) {
|
||||
</View>
|
||||
);
|
||||
}
|
||||
GameEndOverlay = NavigationContainer.create(GameEndOverlay);
|
||||
|
||||
function TicTacToeGame(props) {
|
||||
var rows = parseGame(props.game).map((cells, row) =>
|
||||
const rows = parseGame(props.game).map((cells, row) =>
|
||||
<View key={'row' + row} style={styles.row}>
|
||||
{cells.map((player, col) =>
|
||||
<Cell
|
||||
@@ -184,11 +182,11 @@ function TicTacToeGame(props) {
|
||||
</View>
|
||||
<GameEndOverlay
|
||||
game={props.game}
|
||||
onNavigate={props.onNavigate}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
TicTacToeGame = NavigationContainer.create(TicTacToeGame);
|
||||
|
||||
const GameActions = {
|
||||
Turn: (row, col) => ({type: 'TicTacToeTurnAction', row, col }),
|
||||
@@ -208,21 +206,34 @@ function GameReducer(lastGame: ?string, action: Object): string {
|
||||
return lastGame;
|
||||
}
|
||||
|
||||
type AppState = {
|
||||
game: string,
|
||||
};
|
||||
|
||||
class NavigationTicTacToeExample extends React.Component {
|
||||
static GameView = TicTacToeGame;
|
||||
static GameReducer = GameReducer;
|
||||
static GameActions = GameActions;
|
||||
_handleAction: Function;
|
||||
state: AppState;
|
||||
constructor() {
|
||||
super();
|
||||
this._handleAction = this._handleAction.bind(this);
|
||||
this.state = {
|
||||
game: ''
|
||||
};
|
||||
}
|
||||
_handleAction(action: Object) {
|
||||
const newState = GameReducer(this.state.game, action);
|
||||
if (newState !== this.state.game) {
|
||||
this.setState({
|
||||
game: newState,
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={GameReducer}
|
||||
persistenceKey="TicTacToeGameState"
|
||||
renderNavigation={(game) => (
|
||||
<TicTacToeGame
|
||||
game={game}
|
||||
onExampleExit={this.props.onExampleExit}
|
||||
/>
|
||||
)}
|
||||
<TicTacToeGame
|
||||
game={this.state && this.state.game}
|
||||
onExampleExit={this.props.onExampleExit}
|
||||
onNavigate={this._handleAction}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -415,12 +422,13 @@ exports.examples = [
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Inline images',
|
||||
title: 'Inline views',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
This text contains an inline image <Image source={require('./flux.png')} style={{width: 30, height: 11, resizeMode: 'cover'}}/>. Neat, huh?
|
||||
This text contains an inline blue view <View style={{width: 25, height: 25, backgroundColor: 'steelblue'}} /> and
|
||||
an inline image <Image source={require('./flux.png')} style={{width: 30, height: 11, resizeMode: 'cover'}}/>. Neat, huh?
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -531,4 +531,43 @@ exports.examples = [
|
||||
return <TokenizedTextExample />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Return key',
|
||||
render: function() {
|
||||
var returnKeyTypes = [
|
||||
'none',
|
||||
'go',
|
||||
'search',
|
||||
'send',
|
||||
'done',
|
||||
'previous',
|
||||
'next',
|
||||
];
|
||||
var returnKeyLabels = [
|
||||
'Compile',
|
||||
'React Native',
|
||||
];
|
||||
var examples = returnKeyTypes.map((type) => {
|
||||
return (
|
||||
<TextInput
|
||||
key={type}
|
||||
returnKeyType={type}
|
||||
placeholder={'returnKeyType: ' + type}
|
||||
style={styles.singleLine}
|
||||
/>
|
||||
);
|
||||
});
|
||||
var types = returnKeyLabels.map((type) => {
|
||||
return (
|
||||
<TextInput
|
||||
key={type}
|
||||
returnKeyLabel={type}
|
||||
placeholder={'returnKeyLabel: ' + type}
|
||||
style={styles.singleLine}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return <View>{examples}{types}</View>;
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -25,9 +32,7 @@ export type UIExplorerExampleAction = {
|
||||
openExample: string;
|
||||
};
|
||||
|
||||
import type {BackAction} from 'NavigationRootContainer';
|
||||
|
||||
export type UIExplorerAction = BackAction | UIExplorerListWithFilterAction | UIExplorerExampleAction;
|
||||
export type UIExplorerAction = UIExplorerListWithFilterAction | UIExplorerExampleAction;
|
||||
|
||||
function ExampleListWithFilter(filter: ?string): UIExplorerListWithFilterAction {
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -16,29 +23,24 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AppRegistry = require('AppRegistry');
|
||||
const AsyncStorage = require('AsyncStorage');
|
||||
const BackAndroid = require('BackAndroid');
|
||||
const Dimensions = require('Dimensions');
|
||||
const DrawerLayoutAndroid = require('DrawerLayoutAndroid');
|
||||
const Linking = require('Linking');
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const {
|
||||
AppRegistry,
|
||||
BackAndroid,
|
||||
Dimensions,
|
||||
DrawerLayoutAndroid,
|
||||
NavigationExperimental,
|
||||
StyleSheet,
|
||||
ToolbarAndroid,
|
||||
View,
|
||||
StatusBar,
|
||||
} = ReactNative;
|
||||
const {
|
||||
RootContainer: NavigationRootContainer,
|
||||
} = NavigationExperimental;
|
||||
const StatusBar = require('StatusBar');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const ToolbarAndroid = require('ToolbarAndroid');
|
||||
const UIExplorerExampleList = require('./UIExplorerExampleList');
|
||||
const UIExplorerList = require('./UIExplorerList');
|
||||
const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer');
|
||||
const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap');
|
||||
const URIActionMap = require('./URIActionMap');
|
||||
const View = require('View');
|
||||
|
||||
var DRAWER_WIDTH_LEFT = 56;
|
||||
const DRAWER_WIDTH_LEFT = 56;
|
||||
|
||||
type Props = {
|
||||
exampleFromAppetizeParams: string,
|
||||
@@ -49,14 +51,13 @@ type State = {
|
||||
};
|
||||
|
||||
class UIExplorerApp extends React.Component {
|
||||
_handleOpenInitialExample: Function;
|
||||
_handleAction: Function;
|
||||
_renderDrawerContent: Function;
|
||||
state: State;
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this._handleOpenInitialExample = this._handleOpenInitialExample.bind(this);
|
||||
this.state = {
|
||||
initialExampleUri: props.exampleFromAppetizeParams,
|
||||
};
|
||||
this._handleAction = this._handleAction.bind(this);
|
||||
this._renderDrawerContent = this._renderDrawerContent.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
@@ -64,38 +65,28 @@ class UIExplorerApp extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// There's a race condition if we try to navigate to the specified example
|
||||
// from the initial props at the same time the navigation logic is setting
|
||||
// up the initial navigation state. This hack adds a delay to avoid this
|
||||
// scenario. So after the initial example list is shown, we then transition
|
||||
// to the initial example.
|
||||
setTimeout(this._handleOpenInitialExample, 500);
|
||||
Linking.getInitialURL().then((url) => {
|
||||
AsyncStorage.getItem('UIExplorerAppState', (err, storedString) => {
|
||||
const exampleAction = URIActionMap(this.props.exampleFromAppetizeParams);
|
||||
const urlAction = URIActionMap(url);
|
||||
const launchAction = exampleAction || urlAction;
|
||||
if (err || !storedString) {
|
||||
const initialAction = launchAction || {type: 'InitialAction'};
|
||||
this.setState(UIExplorerNavigationReducer(null, initialAction));
|
||||
return;
|
||||
}
|
||||
const storedState = JSON.parse(storedString);
|
||||
if (launchAction) {
|
||||
this.setState(UIExplorerNavigationReducer(storedState, launchAction));
|
||||
return;
|
||||
}
|
||||
this.setState(storedState);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
persistenceKey="UIExplorerStateNavState"
|
||||
ref={navRootRef => { this._navigationRootRef = navRootRef; }}
|
||||
reducer={UIExplorerNavigationReducer}
|
||||
renderNavigation={this._renderApp.bind(this)}
|
||||
linkingActionMap={URIActionMap}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_handleOpenInitialExample() {
|
||||
if (this.state.initialExampleUri) {
|
||||
const exampleAction = URIActionMap(this.state.initialExampleUri);
|
||||
if (exampleAction && this._navigationRootRef) {
|
||||
this._navigationRootRef.handleNavigation(exampleAction);
|
||||
}
|
||||
}
|
||||
this.setState({initialExampleUri: null});
|
||||
}
|
||||
|
||||
_renderApp(navigationState, onNavigate) {
|
||||
if (!navigationState) {
|
||||
if (!this.state) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
@@ -110,42 +101,42 @@ class UIExplorerApp extends React.Component {
|
||||
this._overrideBackPressForDrawerLayout = false;
|
||||
}}
|
||||
ref={(drawer) => { this.drawer = drawer; }}
|
||||
renderNavigationView={this._renderDrawerContent.bind(this, onNavigate)}
|
||||
renderNavigationView={this._renderDrawerContent}
|
||||
statusBarBackgroundColor="#589c90">
|
||||
{this._renderNavigation(navigationState, onNavigate)}
|
||||
{this._renderApp()}
|
||||
</DrawerLayoutAndroid>
|
||||
);
|
||||
}
|
||||
|
||||
_renderDrawerContent(onNavigate) {
|
||||
_renderDrawerContent() {
|
||||
return (
|
||||
<View style={styles.drawerContentWrapper}>
|
||||
<UIExplorerExampleList
|
||||
list={UIExplorerList}
|
||||
displayTitleRow={true}
|
||||
disableSearch={true}
|
||||
onNavigate={(action) => {
|
||||
this.drawer && this.drawer.closeDrawer();
|
||||
onNavigate(action);
|
||||
}}
|
||||
onNavigate={this._handleAction}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderNavigation(navigationState, onNavigate) {
|
||||
if (navigationState.externalExample) {
|
||||
var Component = UIExplorerList.Modules[navigationState.externalExample];
|
||||
_renderApp() {
|
||||
const {
|
||||
externalExample,
|
||||
stack,
|
||||
} = this.state;
|
||||
if (externalExample) {
|
||||
const Component = UIExplorerList.Modules[externalExample];
|
||||
return (
|
||||
<Component
|
||||
onExampleExit={() => {
|
||||
onNavigate(NavigationRootContainer.getBackAction());
|
||||
this._handleAction({ type: 'BackAction' });
|
||||
}}
|
||||
ref={(example) => { this._exampleRef = example; }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const {stack} = navigationState;
|
||||
const title = UIExplorerStateTitleMap(stack.children[stack.index]);
|
||||
const index = stack.children.length <= 1 ? 1 : stack.index;
|
||||
|
||||
@@ -178,6 +169,7 @@ class UIExplorerApp extends React.Component {
|
||||
title={title}
|
||||
/>
|
||||
<UIExplorerExampleList
|
||||
onNavigate={this._handleAction}
|
||||
list={UIExplorerList}
|
||||
{...stack.children[0]}
|
||||
/>
|
||||
@@ -185,6 +177,17 @@ class UIExplorerApp extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
_handleAction(action: Object): boolean {
|
||||
this.drawer && this.drawer.closeDrawer();
|
||||
const newState = UIExplorerNavigationReducer(this.state, action);
|
||||
if (this.state !== newState) {
|
||||
this.setState(newState);
|
||||
AsyncStorage.setItem('UIExplorerAppState', JSON.stringify(this.state));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_handleBackButtonPress() {
|
||||
if (this._overrideBackPressForDrawerLayout) {
|
||||
// This hack is necessary because drawer layout provides an imperative API
|
||||
@@ -200,12 +203,7 @@ class UIExplorerApp extends React.Component {
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this._navigationRootRef) {
|
||||
return this._navigationRootRef.handleNavigation(
|
||||
NavigationRootContainer.getBackAction()
|
||||
);
|
||||
}
|
||||
return false;
|
||||
return this._handleAction({ type: 'BackAction' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AsyncStorage = require('AsyncStorage');
|
||||
const Linking = require('Linking');
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const UIExplorerList = require('./UIExplorerList.ios');
|
||||
@@ -42,7 +44,6 @@ const {
|
||||
const {
|
||||
CardStack: NavigationCardStack,
|
||||
Header: NavigationHeader,
|
||||
RootContainer: NavigationRootContainer,
|
||||
} = NavigationExperimental;
|
||||
|
||||
import type { NavigationSceneRendererProps } from 'NavigationTypeDefinition';
|
||||
@@ -55,81 +56,86 @@ type Props = {
|
||||
exampleFromAppetizeParams: string,
|
||||
};
|
||||
|
||||
type State = {
|
||||
initialExampleUri: ?string,
|
||||
type State = UIExplorerNavigationState & {
|
||||
externalExample?: string,
|
||||
};
|
||||
|
||||
class UIExplorerApp extends React.Component {
|
||||
_navigationRootRef: ?NavigationRootContainer;
|
||||
_renderNavigation: Function;
|
||||
_renderOverlay: Function;
|
||||
_renderScene: Function;
|
||||
_renderCard: Function;
|
||||
_renderTitleComponent: Function;
|
||||
_handleOpenInitialExample: Function;
|
||||
_handleAction: Function;
|
||||
state: State;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this._handleOpenInitialExample = this._handleOpenInitialExample.bind(this);
|
||||
this.state = {
|
||||
initialExampleUri: props.exampleFromAppetizeParams,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._renderNavigation = this._renderNavigation.bind(this);
|
||||
this._handleAction = this._handleAction.bind(this);
|
||||
this._renderOverlay = this._renderOverlay.bind(this);
|
||||
this._renderScene = this._renderScene.bind(this);
|
||||
this._renderTitleComponent = this._renderTitleComponent.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// There's a race condition if we try to navigate to the specified example
|
||||
// from the initial props at the same time the navigation logic is setting
|
||||
// up the initial navigation state. This hack adds a delay to avoid this
|
||||
// scenario. So after the initial example list is shown, we then transition
|
||||
// to the initial example.
|
||||
setTimeout(this._handleOpenInitialExample, 500);
|
||||
Linking.getInitialURL().then((url) => {
|
||||
AsyncStorage.getItem('UIExplorerAppState', (err, storedString) => {
|
||||
const exampleAction = URIActionMap(this.props.exampleFromAppetizeParams);
|
||||
const urlAction = URIActionMap(url);
|
||||
const launchAction = exampleAction || urlAction;
|
||||
if (err || !storedString) {
|
||||
const initialAction = launchAction || {type: 'InitialAction'};
|
||||
this.setState(UIExplorerNavigationReducer(null, initialAction));
|
||||
return;
|
||||
}
|
||||
const storedState = JSON.parse(storedString);
|
||||
if (launchAction) {
|
||||
this.setState(UIExplorerNavigationReducer(storedState, launchAction));
|
||||
return;
|
||||
}
|
||||
this.setState(storedState);
|
||||
});
|
||||
});
|
||||
|
||||
Linking.addEventListener('url', (url) => {
|
||||
this._handleAction(URIActionMap(url));
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
persistenceKey="UIExplorerState"
|
||||
reducer={UIExplorerNavigationReducer}
|
||||
ref={navRootRef => { this._navigationRootRef = navRootRef; }}
|
||||
renderNavigation={this._renderNavigation}
|
||||
linkingActionMap={URIActionMap}
|
||||
/>
|
||||
);
|
||||
}
|
||||
_handleOpenInitialExample() {
|
||||
if (this.state.initialExampleUri) {
|
||||
const exampleAction = URIActionMap(this.state.initialExampleUri);
|
||||
if (exampleAction && this._navigationRootRef) {
|
||||
this._navigationRootRef.handleNavigation(exampleAction);
|
||||
}
|
||||
|
||||
_handleAction(action: Object) {
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
const newState = UIExplorerNavigationReducer(this.state, action);
|
||||
if (this.state !== newState) {
|
||||
this.setState(newState);
|
||||
AsyncStorage.setItem('UIExplorerAppState', JSON.stringify(this.state));
|
||||
}
|
||||
this.setState({initialExampleUri: null});
|
||||
}
|
||||
_renderNavigation(navigationState: UIExplorerNavigationState, onNavigate: Function) {
|
||||
if (!navigationState) {
|
||||
|
||||
render() {
|
||||
if (!this.state) {
|
||||
return null;
|
||||
}
|
||||
if (navigationState.externalExample) {
|
||||
var Component = UIExplorerList.Modules[navigationState.externalExample];
|
||||
if (this.state.externalExample) {
|
||||
const Component = UIExplorerList.Modules[this.state.externalExample];
|
||||
return (
|
||||
<Component
|
||||
onExampleExit={() => {
|
||||
onNavigate(NavigationRootContainer.getBackAction());
|
||||
this._handleAction({ type: 'BackAction' });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const {stack} = navigationState;
|
||||
return (
|
||||
<NavigationCardStack
|
||||
navigationState={stack}
|
||||
navigationState={this.state.stack}
|
||||
style={styles.container}
|
||||
renderOverlay={this._renderOverlay}
|
||||
renderScene={this._renderScene}
|
||||
onNavigate={this._handleAction}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -156,6 +162,7 @@ class UIExplorerApp extends React.Component {
|
||||
if (state.key === 'AppList') {
|
||||
return (
|
||||
<UIExplorerExampleList
|
||||
onNavigate={this._handleAction}
|
||||
list={UIExplorerList}
|
||||
style={styles.exampleContainer}
|
||||
{...state}
|
||||
@@ -194,9 +201,9 @@ AppRegistry.registerComponent('UIExplorerApp', () => UIExplorerApp);
|
||||
UIExplorerList.ComponentExamples.concat(UIExplorerList.APIExamples).forEach((Example: UIExplorerExample) => {
|
||||
const ExampleModule = Example.module;
|
||||
if (ExampleModule.displayName) {
|
||||
var Snapshotter = React.createClass({
|
||||
const Snapshotter = React.createClass({
|
||||
render: function() {
|
||||
var Renderable = UIExplorerExampleList.makeRenderable(ExampleModule);
|
||||
const Renderable = UIExplorerExampleList.makeRenderable(ExampleModule);
|
||||
return (
|
||||
<SnapshotViewIOS>
|
||||
<Renderable />
|
||||
|
||||
@@ -27,7 +27,6 @@ const React = require('react');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const Text = require('Text');
|
||||
const TextInput = require('TextInput');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const TouchableHighlight = require('TouchableHighlight');
|
||||
const View = require('View');
|
||||
const UIExplorerActions = require('./UIExplorerActions');
|
||||
@@ -56,6 +55,13 @@ class UIExplorerExampleList extends React.Component {
|
||||
}) {
|
||||
|
||||
}
|
||||
|
||||
static makeRenderable(example: any): ReactClass<any> {
|
||||
return example.examples ?
|
||||
createExamplePage(null, example) :
|
||||
example;
|
||||
}
|
||||
|
||||
render(): ?ReactElement {
|
||||
const filterText = this.props.filter || '';
|
||||
const filterRegex = new RegExp(String(filterText), 'i');
|
||||
@@ -160,15 +166,6 @@ class UIExplorerExampleList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function makeRenderable(example: any): ReactClass<any> {
|
||||
return example.examples ?
|
||||
createExamplePage(null, example) :
|
||||
example;
|
||||
}
|
||||
|
||||
UIExplorerExampleList = NavigationContainer.create(UIExplorerExampleList);
|
||||
UIExplorerExampleList.makeRenderable = makeRenderable;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listContainer: {
|
||||
flex: 1,
|
||||
|
||||
@@ -138,12 +138,6 @@ static ControlBlock propertiesUpdateBlock()
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
#if __LP64__
|
||||
RCTAssert(NO, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)");
|
||||
#endif
|
||||
|
||||
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
|
||||
RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
_runner = RCTInitRunnerForApp(@"IntegrationTests/RCTRootViewIntegrationTestApp", nil);
|
||||
}
|
||||
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 153 KiB |
@@ -31,12 +31,6 @@
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
#if __LP64__
|
||||
RCTAssert(NO, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)");
|
||||
#endif
|
||||
|
||||
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
|
||||
RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
_runner = RCTInitRunnerForApp(@"IntegrationTests/IntegrationTestsApp", nil);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,6 @@
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
#if __LP64__
|
||||
RCTAssert(NO, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)");
|
||||
#endif
|
||||
|
||||
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
|
||||
RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp.ios", nil);
|
||||
_runner.recordMode = NO;
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ var ComponentExamples: Array<UIExplorerExample> = [
|
||||
module: require('./ModalExample'),
|
||||
},
|
||||
{
|
||||
key: 'PickerAndroidExample',
|
||||
module: require('./PickerAndroidExample'),
|
||||
key: 'PickerExample',
|
||||
module: require('./PickerExample'),
|
||||
},
|
||||
{
|
||||
key: 'ProgressBarAndroidExample',
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -20,7 +27,7 @@ export type UIExplorerExample = {
|
||||
module: Object;
|
||||
};
|
||||
|
||||
var ComponentExamples: Array<UIExplorerExample> = [
|
||||
const ComponentExamples: Array<UIExplorerExample> = [
|
||||
{
|
||||
key: 'ActivityIndicatorIOSExample',
|
||||
module: require('./ActivityIndicatorIOSExample'),
|
||||
@@ -69,6 +76,10 @@ var ComponentExamples: Array<UIExplorerExample> = [
|
||||
key: 'NavigatorIOSExample',
|
||||
module: require('./NavigatorIOSExample'),
|
||||
},
|
||||
{
|
||||
key: 'PickerExample',
|
||||
module: require('./PickerExample'),
|
||||
},
|
||||
{
|
||||
key: 'PickerIOSExample',
|
||||
module: require('./PickerIOSExample'),
|
||||
@@ -131,7 +142,7 @@ var ComponentExamples: Array<UIExplorerExample> = [
|
||||
},
|
||||
];
|
||||
|
||||
var APIExamples: Array<UIExplorerExample> = [
|
||||
const APIExamples: Array<UIExplorerExample> = [
|
||||
{
|
||||
key: 'AccessibilityIOSExample',
|
||||
module: require('./AccessibilityIOSExample'),
|
||||
@@ -144,6 +155,10 @@ var APIExamples: Array<UIExplorerExample> = [
|
||||
key: 'AdSupportIOSExample',
|
||||
module: require('./AdSupportIOSExample'),
|
||||
},
|
||||
{
|
||||
key: 'AlertExample',
|
||||
module: require('./AlertExample').AlertExample,
|
||||
},
|
||||
{
|
||||
key: 'AlertIOSExample',
|
||||
module: require('./AlertIOSExample'),
|
||||
@@ -208,10 +223,6 @@ var APIExamples: Array<UIExplorerExample> = [
|
||||
key: 'NavigationExperimentalExample',
|
||||
module: require('./NavigationExperimental/NavigationExperimentalExample'),
|
||||
},
|
||||
{
|
||||
key: 'NavigationExperimentalLegacyNavigatorExample',
|
||||
module: require('./NavigationExperimental/LegacyNavigator/LegacyNavigatorExample'),
|
||||
},
|
||||
{
|
||||
key: 'NetInfoExample',
|
||||
module: require('./NetInfoExample'),
|
||||
|
||||
@@ -46,7 +46,6 @@ var ViewBorderStyleExample = React.createClass({
|
||||
<View>
|
||||
<View style={{
|
||||
borderWidth: 1,
|
||||
borderRadius: 5,
|
||||
borderStyle: this.state.showBorder ? 'dashed' : null,
|
||||
padding: 5
|
||||
}}>
|
||||
|
||||
@@ -109,6 +109,7 @@ var ViewPagerAndroidExample = React.createClass({
|
||||
return {
|
||||
page: 0,
|
||||
animationsAreEnabled: true,
|
||||
scrollEnabled: true,
|
||||
progress: {
|
||||
position: 0,
|
||||
offset: 0,
|
||||
@@ -167,6 +168,7 @@ var ViewPagerAndroidExample = React.createClass({
|
||||
<ViewPagerAndroid
|
||||
style={styles.viewPager}
|
||||
initialPage={0}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
onPageScroll={this.onPageScroll}
|
||||
onPageSelected={this.onPageSelected}
|
||||
onPageScrollStateChanged={this.onPageScrollStateChanged}
|
||||
@@ -174,6 +176,13 @@ var ViewPagerAndroidExample = React.createClass({
|
||||
ref={viewPager => { this.viewPager = viewPager; }}>
|
||||
{pages}
|
||||
</ViewPagerAndroid>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
enabled={true}
|
||||
text={this.state.scrollEnabled ? 'Scroll Enabled' : 'Scroll Disabled'}
|
||||
onPress={() => this.setState({scrollEnabled: !this.state.scrollEnabled})}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.buttons}>
|
||||
{ animationsAreEnabled ?
|
||||
<Button
|
||||
|
||||
@@ -69,6 +69,7 @@ class Animated {
|
||||
__getNativeConfig(): Object {
|
||||
throw new Error('This JS animated node type cannot be used as native animated node');
|
||||
}
|
||||
toJSON(): any { return this.__getValue(); }
|
||||
}
|
||||
|
||||
type AnimationConfig = {
|
||||
|
||||
@@ -233,7 +233,7 @@ function createInterpolationFromStringOutputRange(
|
||||
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
|
||||
return outputRange[0].replace(stringShapeRegex, () => {
|
||||
const val = +interpolations[i++](input);
|
||||
const rounded = shouldRound && i < 4 ? Math.round(val) : val;
|
||||
const rounded = shouldRound && i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000;
|
||||
return String(rounded);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -142,6 +142,9 @@ describe('Animated', () => {
|
||||
expect(anim.__getValue()).toBe(15);
|
||||
});
|
||||
|
||||
it('convert to JSON', () => {
|
||||
expect(JSON.stringify(new Animated.Value(10))).toBe('10');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -294,4 +294,14 @@ describe('Interpolation', () => {
|
||||
outputRange: ['20deg', '30rad'],
|
||||
})).toThrow();
|
||||
});
|
||||
|
||||
it('should round the alpha channel of a color to the nearest thousandth', () => {
|
||||
var interpolation = Interpolation.create({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 1)'],
|
||||
});
|
||||
|
||||
expect(interpolation(1e-12)).toBe('rgba(0, 0, 0, 0)');
|
||||
expect(interpolation(2 / 3)).toBe('rgba(0, 0, 0, 0.667)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ var ReactNative = require('ReactNative');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var renderApplication = require('renderApplication');
|
||||
const infoLog = require('infoLog');
|
||||
|
||||
if (__DEV__) {
|
||||
// In order to use Cmd+P to record/dump perf data, we need to make sure
|
||||
@@ -87,8 +88,7 @@ var AppRegistry = {
|
||||
'__DEV__ === ' + String(__DEV__) +
|
||||
', development-level warning are ' + (__DEV__ ? 'ON' : 'OFF') +
|
||||
', performance optimizations are ' + (__DEV__ ? 'OFF' : 'ON');
|
||||
console.log(msg);
|
||||
BugReporting.init();
|
||||
infoLog(msg);
|
||||
BugReporting.addSource('AppRegistry.runApplication' + runCount++, () => msg);
|
||||
invariant(
|
||||
runnables[appKey] && runnables[appKey].run,
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
const MessageQueue = require('MessageQueue');
|
||||
|
||||
const BatchedBridge = new MessageQueue(
|
||||
__fbBatchedBridgeConfig.remoteModuleConfig,
|
||||
__fbBatchedBridgeConfig.localModulesConfig,
|
||||
() => global.__fbBatchedBridgeConfig
|
||||
);
|
||||
|
||||
// TODO: Move these around to solve the cycle in a cleaner way.
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
const BatchedBridge = require('BatchedBridge');
|
||||
const RemoteModules = BatchedBridge.RemoteModules;
|
||||
const Platform = require('Platform');
|
||||
|
||||
function normalizePrefix(moduleName: string): string {
|
||||
return moduleName.replace(/^(RCT|RK)/, '');
|
||||
@@ -52,67 +51,14 @@ Object.keys(RemoteModules).forEach((moduleName) => {
|
||||
module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
|
||||
RemoteModules[moduleName] = module;
|
||||
}
|
||||
Object.defineProperty(NativeModules, moduleName, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: module,
|
||||
});
|
||||
return module;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Copies the ViewManager constants and commands into UIManager. This is
|
||||
* only needed for iOS, which puts the constants in the ViewManager
|
||||
* namespace instead of UIManager, unlike Android.
|
||||
*
|
||||
* We'll eventually move this logic to UIManager.js, once all
|
||||
* the call sites accessing NativeModules.UIManager directly have
|
||||
* been removed #9344445
|
||||
*/
|
||||
if (Platform.OS === 'ios') {
|
||||
const UIManager = NativeModules.UIManager;
|
||||
UIManager && Object.keys(UIManager).forEach(viewName => {
|
||||
const viewConfig = UIManager[viewName];
|
||||
if (viewConfig.Manager) {
|
||||
let constants;
|
||||
/* $FlowFixMe - nice try. Flow doesn't like getters */
|
||||
Object.defineProperty(viewConfig, 'Constants', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
if (constants) {
|
||||
return constants;
|
||||
}
|
||||
constants = {};
|
||||
const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)];
|
||||
viewManager && Object.keys(viewManager).forEach(key => {
|
||||
const value = viewManager[key];
|
||||
if (typeof value !== 'function') {
|
||||
constants[key] = value;
|
||||
}
|
||||
});
|
||||
return constants;
|
||||
},
|
||||
});
|
||||
let commands;
|
||||
/* $FlowFixMe - nice try. Flow doesn't like getters */
|
||||
Object.defineProperty(viewConfig, 'Commands', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
if (commands) {
|
||||
return commands;
|
||||
}
|
||||
commands = {};
|
||||
const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)];
|
||||
viewManager && Object.keys(viewManager).forEach((key, index) => {
|
||||
const value = viewManager[key];
|
||||
if (typeof value === 'function') {
|
||||
commands[key] = index;
|
||||
}
|
||||
});
|
||||
return commands;
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = NativeModules;
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
|
||||
const BatchedBridge = require('BatchedBridge');
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTEventEmitter',
|
||||
ReactNativeEventEmitter
|
||||
);
|
||||
const RCTEventEmitter = {
|
||||
register(eventEmitter: any) {
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTEventEmitter',
|
||||
eventEmitter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Completely locally implemented - no native hooks.
|
||||
module.exports = ReactNativeEventEmitter;
|
||||
module.exports = RCTEventEmitter;
|
||||
|
||||
@@ -11,13 +11,19 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const BugReportingNativeModule = require('NativeModules').BugReporting;
|
||||
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
|
||||
const Map = require('Map');
|
||||
const infoLog = require('infoLog');
|
||||
|
||||
import type {EmitterSubscription} from 'EventEmitter';
|
||||
import type EmitterSubscription from 'EmitterSubscription';
|
||||
|
||||
type ExtraData = { [key: string]: string };
|
||||
type SourceCallback = () => string;
|
||||
type DebugData = { extras: ExtraData, files: ExtraData };
|
||||
|
||||
function defaultExtras() {
|
||||
BugReporting.addFileSource('react_hierarchy.txt', () => require('dumpReactTree')());
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class for collecting bug report data. Components can add sources that will be queried when a bug report
|
||||
@@ -26,17 +32,15 @@ type SourceCallback = () => string;
|
||||
* returned by `addSource` when they are unmounted.
|
||||
*/
|
||||
class BugReporting {
|
||||
static _extraSources: Map<string, SourceCallback> = new Map();
|
||||
static _fileSources: Map<string, SourceCallback> = new Map();
|
||||
static _subscription: ?EmitterSubscription = null;
|
||||
|
||||
static _sources: Map<string, SourceCallback> = new Map();
|
||||
static _subscription: EmitterSubscription = null;
|
||||
|
||||
/**
|
||||
* `init` is called in `AppRegistry.runApplication`, so you shouldn't have to worry about it.
|
||||
*/
|
||||
static init() {
|
||||
static _maybeInit() {
|
||||
if (!BugReporting._subscription) {
|
||||
BugReporting._subscription = RCTDeviceEventEmitter
|
||||
.addListener('collectBugExtraData', BugReporting.collectExtraData, null);
|
||||
defaultExtras();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,11 +53,28 @@ class BugReporting {
|
||||
* Conflicts trample with a warning.
|
||||
*/
|
||||
static addSource(key: string, callback: SourceCallback): {remove: () => void} {
|
||||
if (BugReporting._sources.has(key)) {
|
||||
console.warn(`BugReporting.addSource called multiple times for same key '${key}'`);
|
||||
return this._addSource(key, callback, BugReporting._extraSources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a string key to a simple callback that should return a string payload to be attached
|
||||
* to a bug report. Source callbacks are called when `collectExtraData` is called.
|
||||
*
|
||||
* Returns an object to remove the source when the component unmounts.
|
||||
*
|
||||
* Conflicts trample with a warning.
|
||||
*/
|
||||
static addFileSource(key: string, callback: SourceCallback): {remove: () => void} {
|
||||
return this._addSource(key, callback, BugReporting._fileSources);
|
||||
}
|
||||
|
||||
static _addSource(key: string, callback: SourceCallback, source: Map<string, SourceCallback>): {remove: () => void} {
|
||||
BugReporting._maybeInit();
|
||||
if (source.has(key)) {
|
||||
console.warn(`BugReporting.add* called multiple times for same key '${key}'`);
|
||||
}
|
||||
BugReporting._sources.set(key, callback);
|
||||
return {remove: () => { BugReporting._sources.delete(key); }};
|
||||
source.set(key, callback);
|
||||
return {remove: () => { source.delete(key); }};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,16 +83,22 @@ class BugReporting {
|
||||
* If available, this will call `NativeModules.BugReporting.setExtraData(extraData)`
|
||||
* after collecting `extraData`.
|
||||
*/
|
||||
static collectExtraData(): ExtraData {
|
||||
static collectExtraData(): DebugData {
|
||||
const extraData: ExtraData = {};
|
||||
for (const [key, callback] of BugReporting._sources) {
|
||||
for (const [key, callback] of BugReporting._extraSources) {
|
||||
extraData[key] = callback();
|
||||
}
|
||||
console.log('BugReporting extraData:', extraData);
|
||||
const fileData: ExtraData = {};
|
||||
for (const [key, callback] of BugReporting._fileSources) {
|
||||
fileData[key] = callback();
|
||||
}
|
||||
infoLog('BugReporting extraData:', extraData);
|
||||
const BugReportingNativeModule = require('NativeModules').BugReporting;
|
||||
BugReportingNativeModule &&
|
||||
BugReportingNativeModule.setExtraData &&
|
||||
BugReportingNativeModule.setExtraData(extraData);
|
||||
return extraData;
|
||||
BugReportingNativeModule.setExtraData(extraData, fileData);
|
||||
|
||||
return { extras: extraData, files: fileData };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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 dumpReactTree
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const ReactNativeMount = require('ReactNativeMount');
|
||||
const getReactData = require('getReactData');
|
||||
|
||||
const INDENTATION_SIZE = 2;
|
||||
const MAX_DEPTH = 2;
|
||||
const MAX_STRING_LENGTH = 50;
|
||||
|
||||
/**
|
||||
* Dump all React Native root views and their content. This function tries
|
||||
* it best to get the content but ultimately relies on implementation details
|
||||
* of React and will fail in future versions.
|
||||
*/
|
||||
function dumpReactTree() {
|
||||
try {
|
||||
return getReactTree();
|
||||
} catch (e) {
|
||||
return 'Failed to dump react tree: ' + e;
|
||||
}
|
||||
}
|
||||
|
||||
function getReactTree() {
|
||||
let output = '';
|
||||
const rootIds = Object.getOwnPropertyNames(ReactNativeMount._instancesByContainerID);
|
||||
for (const rootId of rootIds) {
|
||||
const instance = ReactNativeMount._instancesByContainerID[rootId];
|
||||
output += `============ Root ID: ${rootId} ============\n`;
|
||||
output += dumpNode(instance, 0);
|
||||
output += `============ End root ID: ${rootId} ============\n`;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function dumpNode(node: Object, identation: number) {
|
||||
const data = getReactData(node);
|
||||
if (data.nodeType === 'Text') {
|
||||
return indent(identation) + data.text + '\n';
|
||||
} else if (data.nodeType === 'Empty') {
|
||||
return '';
|
||||
}
|
||||
let output = indent(identation) + `<${data.name}`;
|
||||
if (data.nodeType === 'Composite') {
|
||||
for (const propName of Object.getOwnPropertyNames(data.props || {})) {
|
||||
if (isNormalProp(propName)) {
|
||||
try {
|
||||
const value = convertValue(data.props[propName]);
|
||||
if (value) {
|
||||
output += ` ${propName}=${value}`;
|
||||
}
|
||||
} catch (e) {
|
||||
const message = `[Failed to get property: ${e}]`;
|
||||
output += ` ${propName}=${message}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let childOutput = '';
|
||||
for (const child of data.children || []) {
|
||||
childOutput += dumpNode(child, identation + 1);
|
||||
}
|
||||
|
||||
if (childOutput) {
|
||||
output += '>\n' + childOutput + indent(identation) + `</${data.name}>\n`;
|
||||
} else {
|
||||
output += ' />\n';
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function isNormalProp(name: string): boolean {
|
||||
switch (name) {
|
||||
case 'children':
|
||||
case 'key':
|
||||
case 'ref':
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function convertObject(object: Object, depth: number) {
|
||||
if (depth >= MAX_DEPTH) {
|
||||
return '[...omitted]';
|
||||
}
|
||||
let output = '{';
|
||||
let first = true;
|
||||
for (const key of Object.getOwnPropertyNames(object)) {
|
||||
if (!first) {
|
||||
output += ', ';
|
||||
}
|
||||
output += `${key}: ${convertValue(object[key], depth + 1)}`;
|
||||
first = false;
|
||||
}
|
||||
return output + '}';
|
||||
}
|
||||
|
||||
function convertValue(value, depth = 0): ?string {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return JSON.stringify(possiblyEllipsis(value).replace('\n', '\\n'));
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
return JSON.stringify(value);
|
||||
case 'function':
|
||||
return '[function]';
|
||||
case 'object':
|
||||
return convertObject(value, depth);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function possiblyEllipsis(value: string) {
|
||||
if (value.length > MAX_STRING_LENGTH) {
|
||||
return value.slice(0, MAX_STRING_LENGTH) + '...';
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function indent(size: number) {
|
||||
return ' '.repeat(size * INDENTATION_SIZE);
|
||||
}
|
||||
|
||||
module.exports = dumpReactTree;
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 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 getReactData
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Convert a react internal instance to a sanitized data object.
|
||||
*
|
||||
* This is shamelessly stolen from react-devtools:
|
||||
* https://github.com/facebook/react-devtools/blob/master/backend/getData.js
|
||||
*/
|
||||
function getData(element: Object): Object {
|
||||
var children = null;
|
||||
var props = null;
|
||||
var state = null;
|
||||
var context = null;
|
||||
var updater = null;
|
||||
var name = null;
|
||||
var type = null;
|
||||
var text = null;
|
||||
var publicInstance = null;
|
||||
var nodeType = 'Native';
|
||||
// If the parent is a native node without rendered children, but with
|
||||
// multiple string children, then the `element` that gets passed in here is
|
||||
// a plain value -- a string or number.
|
||||
if (typeof element !== 'object') {
|
||||
nodeType = 'Text';
|
||||
text = element + '';
|
||||
} else if (element._currentElement === null || element._currentElement === false) {
|
||||
nodeType = 'Empty';
|
||||
} else if (element._renderedComponent) {
|
||||
nodeType = 'NativeWrapper';
|
||||
children = [element._renderedComponent];
|
||||
props = element._instance.props;
|
||||
state = element._instance.state;
|
||||
context = element._instance.context;
|
||||
if (context && Object.keys(context).length === 0) {
|
||||
context = null;
|
||||
}
|
||||
} else if (element._renderedChildren) {
|
||||
children = childrenList(element._renderedChildren);
|
||||
} else if (element._currentElement && element._currentElement.props) {
|
||||
// This is a native node without rendered children -- meaning the children
|
||||
// prop is just a string or (in the case of the <option>) a list of
|
||||
// strings & numbers.
|
||||
children = element._currentElement.props.children;
|
||||
}
|
||||
|
||||
if (!props && element._currentElement && element._currentElement.props) {
|
||||
props = element._currentElement.props;
|
||||
}
|
||||
|
||||
// != used deliberately here to catch undefined and null
|
||||
if (element._currentElement != null) {
|
||||
type = element._currentElement.type;
|
||||
if (typeof type === 'string') {
|
||||
name = type;
|
||||
} else if (element.getName) {
|
||||
nodeType = 'Composite';
|
||||
name = element.getName();
|
||||
// 0.14 top-level wrapper
|
||||
// TODO(jared): The backend should just act as if these don't exist.
|
||||
if (element._renderedComponent && element._currentElement.props === element._renderedComponent._currentElement) {
|
||||
nodeType = 'Wrapper';
|
||||
}
|
||||
if (name === null) {
|
||||
name = 'No display name';
|
||||
}
|
||||
} else if (element._stringText) {
|
||||
nodeType = 'Text';
|
||||
text = element._stringText;
|
||||
} else {
|
||||
name = type.displayName || type.name || 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
if (element._instance) {
|
||||
var inst = element._instance;
|
||||
updater = {
|
||||
setState: inst.setState && inst.setState.bind(inst),
|
||||
forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst),
|
||||
setInProps: inst.forceUpdate && setInProps.bind(null, element),
|
||||
setInState: inst.forceUpdate && setInState.bind(null, inst),
|
||||
setInContext: inst.forceUpdate && setInContext.bind(null, inst),
|
||||
};
|
||||
publicInstance = inst;
|
||||
|
||||
// TODO: React ART currently falls in this bucket, but this doesn't
|
||||
// actually make sense and we should clean this up after stabilizing our
|
||||
// API for backends
|
||||
if (inst._renderedChildren) {
|
||||
children = childrenList(inst._renderedChildren);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nodeType,
|
||||
type,
|
||||
name,
|
||||
props,
|
||||
state,
|
||||
context,
|
||||
children,
|
||||
text,
|
||||
updater,
|
||||
publicInstance,
|
||||
};
|
||||
}
|
||||
|
||||
function setInProps(internalInst, path: Array<string | number>, value: any) {
|
||||
var element = internalInst._currentElement;
|
||||
internalInst._currentElement = {
|
||||
...element,
|
||||
props: copyWithSet(element.props, path, value),
|
||||
};
|
||||
internalInst._instance.forceUpdate();
|
||||
}
|
||||
|
||||
function setInState(inst, path: Array<string | number>, value: any) {
|
||||
setIn(inst.state, path, value);
|
||||
inst.forceUpdate();
|
||||
}
|
||||
|
||||
function setInContext(inst, path: Array<string | number>, value: any) {
|
||||
setIn(inst.context, path, value);
|
||||
inst.forceUpdate();
|
||||
}
|
||||
|
||||
function setIn(obj: Object, path: Array<string | number>, value: any) {
|
||||
var last = path.pop();
|
||||
var parent = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj);
|
||||
if (parent) {
|
||||
parent[last] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function childrenList(children) {
|
||||
var res = [];
|
||||
for (var name in children) {
|
||||
res.push(children[name]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function copyWithSetImpl(obj, path, idx, value) {
|
||||
if (idx >= path.length) {
|
||||
return value;
|
||||
}
|
||||
var key = path[idx];
|
||||
var updated = Array.isArray(obj) ? obj.slice() : {...obj};
|
||||
// $FlowFixMe number or string is fine here
|
||||
updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value);
|
||||
return updated;
|
||||
}
|
||||
|
||||
function copyWithSet(obj: Object | Array<any>, path: Array<string | number>, value: any): Object | Array<any> {
|
||||
return copyWithSetImpl(obj, path, 0, value);
|
||||
}
|
||||
|
||||
module.exports = getData;
|
||||
@@ -108,8 +108,15 @@ didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
|
||||
BOOL isMovie = [mediaType isEqualToString:(NSString *)kUTTypeMovie];
|
||||
NSString *key = isMovie ? UIImagePickerControllerMediaURL : UIImagePickerControllerReferenceURL;
|
||||
NSURL *imageURL = info[key];
|
||||
UIImage *image = info[UIImagePickerControllerOriginalImage];
|
||||
NSNumber *width = 0;
|
||||
NSNumber *height = 0;
|
||||
if (image) {
|
||||
height = @(image.size.height);
|
||||
width = @(image.size.width);
|
||||
}
|
||||
if (imageURL) {
|
||||
[self _dismissPicker:picker args:@[imageURL.absoluteString]];
|
||||
[self _dismissPicker:picker args:@[imageURL.absoluteString, RCTNullIfNil(height), RCTNullIfNil(width)]];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,7 +127,7 @@ didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
|
||||
// WARNING: Using ImageStoreManager may cause a memory leak because the
|
||||
// image isn't automatically removed from store once we're done using it.
|
||||
[_bridge.imageStoreManager storeImage:originalImage withBlock:^(NSString *tempImageTag) {
|
||||
[self _dismissPicker:picker args:tempImageTag ? @[tempImageTag] : nil];
|
||||
[self _dismissPicker:picker args:tempImageTag ? @[tempImageTag, height, width] : nil];
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ var DrawerLayoutAndroid = React.createClass({
|
||||
* - idle, meaning there is no interaction with the navigation view happening at the time
|
||||
* - dragging, meaning there is currently an interaction with the navigation view
|
||||
* - settling, meaning that there was an interaction with the navigation view, and the
|
||||
* navigation view is now finishing it's closing or opening animation
|
||||
* navigation view is now finishing its closing or opening animation
|
||||
*/
|
||||
onDrawerStateChanged: ReactPropTypes.func,
|
||||
/**
|
||||
|
||||
+4
-11
@@ -6,19 +6,12 @@
|
||||
* 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 RCTDeviceEventEmitter
|
||||
* @providesModule Keyboard
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('EventEmitter');
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
const NativeEventEmitter = require('NativeEventEmitter');
|
||||
const KeyboardObserver = require('NativeModules').KeyboardObserver;
|
||||
|
||||
var RCTDeviceEventEmitter = new EventEmitter();
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTDeviceEventEmitter',
|
||||
RCTDeviceEventEmitter
|
||||
);
|
||||
|
||||
module.exports = RCTDeviceEventEmitter;
|
||||
module.exports = new NativeEventEmitter(KeyboardObserver);
|
||||
@@ -308,6 +308,15 @@ var NavigatorIOS = React.createClass({
|
||||
*/
|
||||
translucent: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* A Boolean value that indicates whether the interactive pop gesture is enabled. Useful
|
||||
* for enabling/disabling the back swipe navigation gesture. If this prop is not provided,
|
||||
* the default behavior is for the back swipe gesture to be enabled when the navigation bar
|
||||
* is shown and disabled when the navigation bar is hidden. Once you've provided
|
||||
* the interactivePopGestureEnabled prop, you can never restore the default behavior.
|
||||
*/
|
||||
interactivePopGestureEnabled: PropTypes.bool,
|
||||
|
||||
},
|
||||
|
||||
navigator: (undefined: ?Object),
|
||||
@@ -686,7 +695,8 @@ var NavigatorIOS = React.createClass({
|
||||
style={styles.transitioner}
|
||||
vertical={this.props.vertical}
|
||||
requestedTopOfStack={this.state.requestedTopOfStack}
|
||||
onNavigationComplete={this._handleNavigationComplete}>
|
||||
onNavigationComplete={this._handleNavigationComplete}
|
||||
interactivePopGestureEnabled={this.props.interactivePopGestureEnabled}>
|
||||
{items}
|
||||
</NavigatorTransitionerIOS>
|
||||
</StaticContainer>
|
||||
|
||||
@@ -52,7 +52,11 @@ var PickerAndroid = React.createClass({
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this._stateFromProps(this.props);
|
||||
var state = this._stateFromProps(this.props);
|
||||
return {
|
||||
...state,
|
||||
initialSelectedIndex: state.selectedIndex,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
@@ -87,7 +91,7 @@ var PickerAndroid = React.createClass({
|
||||
mode: this.props.mode,
|
||||
onSelect: this._onChange,
|
||||
prompt: this.props.prompt,
|
||||
selected: this.state.selectedIndex,
|
||||
selected: this.state.initialSelectedIndex,
|
||||
testID: this.props.testID,
|
||||
style: [styles.pickerAndroid, this.props.style],
|
||||
};
|
||||
@@ -105,15 +109,24 @@ var PickerAndroid = React.createClass({
|
||||
this.props.onValueChange(null, position);
|
||||
}
|
||||
}
|
||||
this._lastNativePosition = event.nativeEvent.position;
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._lastNativePosition = this.state.initialSelectedIndex;
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
// The picker is a controlled component. This means we expect the
|
||||
// on*Change handlers to be in charge of updating our
|
||||
// `selectedValue` prop. That way they can also
|
||||
// disallow/undo/mutate the selection of certain values. In other
|
||||
// words, the embedder of this component should be the source of
|
||||
// truth, not the native component.
|
||||
if (this.refs[REF_PICKER] && this.state.selectedIndex !== event.nativeEvent.position) {
|
||||
if (this.refs[REF_PICKER] && this.state.selectedIndex !== this._lastNativePosition) {
|
||||
this.refs[REF_PICKER].setNativeProps({selected: this.state.selectedIndex});
|
||||
this._lastNativePosition = this.state.selectedIndex;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ const View = require('View');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
var RefreshLayoutConsts = require('NativeModules').UIManager.AndroidSwipeRefreshLayout.Constants;
|
||||
var RefreshLayoutConsts = require('UIManager').AndroidSwipeRefreshLayout.Constants;
|
||||
} else {
|
||||
var RefreshLayoutConsts = {SIZE: {}};
|
||||
}
|
||||
@@ -86,7 +86,7 @@ const RefreshControl = React.createClass({
|
||||
/**
|
||||
* Whether the view should be indicating an active refresh.
|
||||
*/
|
||||
refreshing: React.PropTypes.bool,
|
||||
refreshing: React.PropTypes.bool.isRequired,
|
||||
/**
|
||||
* The color of the refresh indicator.
|
||||
* @platform ios
|
||||
@@ -121,10 +121,32 @@ const RefreshControl = React.createClass({
|
||||
* Size of the refresh indicator, see RefreshControl.SIZE.
|
||||
* @platform android
|
||||
*/
|
||||
size: React.PropTypes.oneOf(RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE),
|
||||
size: React.PropTypes.oneOf([RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE]),
|
||||
/**
|
||||
* Progress view top offset
|
||||
* @platform android
|
||||
*/
|
||||
progressViewOffset: React.PropTypes.number,
|
||||
},
|
||||
|
||||
_nativeRef: {},
|
||||
_nativeRef: (null: any),
|
||||
_lastNativeRefreshing: false,
|
||||
|
||||
componentDidMount() {
|
||||
this._lastNativeRefreshing = this.props.refreshing;
|
||||
},
|
||||
|
||||
componentDidUpdate(prevProps: {refreshing: boolean}) {
|
||||
// RefreshControl is a controlled component so if the native refreshing
|
||||
// value doesn't match the current js refreshing prop update it to
|
||||
// the js value.
|
||||
if (this.props.refreshing !== prevProps.refreshing) {
|
||||
this._lastNativeRefreshing = this.props.refreshing;
|
||||
} else if (this.props.refreshing !== this._lastNativeRefreshing) {
|
||||
this._nativeRef.setNativeProps({refreshing: this.props.refreshing});
|
||||
this._lastNativeRefreshing = this.props.refreshing;
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
@@ -137,11 +159,13 @@ const RefreshControl = React.createClass({
|
||||
},
|
||||
|
||||
_onRefresh() {
|
||||
this._lastNativeRefreshing = true;
|
||||
|
||||
this.props.onRefresh && this.props.onRefresh();
|
||||
|
||||
if (this._nativeRef) {
|
||||
this._nativeRef.setNativeProps({refreshing: this.props.refreshing});
|
||||
}
|
||||
// The native component will start refreshing so force an update to
|
||||
// make sure it stays in sync with the js component.
|
||||
this.forceUpdate();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
var Dimensions = require('Dimensions');
|
||||
var Platform = require('Platform');
|
||||
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
|
||||
var Keyboard = require('Keyboard');
|
||||
var ReactNative = require('ReactNative');
|
||||
var Subscribable = require('Subscribable');
|
||||
var TextInputState = require('TextInputState');
|
||||
@@ -476,10 +476,10 @@ var ScrollResponderMixin = {
|
||||
componentWillMount: function() {
|
||||
this.keyboardWillOpenTo = null;
|
||||
this.additionalScrollOffset = 0;
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardWillShow', this.scrollResponderKeyboardWillShow);
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardWillHide', this.scrollResponderKeyboardWillHide);
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardDidShow', this.scrollResponderKeyboardDidShow);
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardDidHide', this.scrollResponderKeyboardDidHide);
|
||||
this.addListenerOn(Keyboard, 'keyboardWillShow', this.scrollResponderKeyboardWillShow);
|
||||
this.addListenerOn(Keyboard, 'keyboardWillHide', this.scrollResponderKeyboardWillHide);
|
||||
this.addListenerOn(Keyboard, 'keyboardDidShow', this.scrollResponderKeyboardDidShow);
|
||||
this.addListenerOn(Keyboard, 'keyboardDidHide', this.scrollResponderKeyboardDidHide);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,30 +11,26 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ColorPropType = require('ColorPropType');
|
||||
var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
var Platform = require('Platform');
|
||||
var PointPropType = require('PointPropType');
|
||||
var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
|
||||
var RCTScrollViewManager = require('NativeModules').ScrollViewManager;
|
||||
var React = require('React');
|
||||
var ReactNative = require('ReactNative');
|
||||
var ScrollResponder = require('ScrollResponder');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var StyleSheetPropType = require('StyleSheetPropType');
|
||||
var View = require('View');
|
||||
var ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
const ColorPropType = require('ColorPropType');
|
||||
const EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
const Platform = require('Platform');
|
||||
const PointPropType = require('PointPropType');
|
||||
const RCTScrollViewManager = require('NativeModules').ScrollViewManager;
|
||||
const React = require('React');
|
||||
const ReactNative = require('ReactNative');
|
||||
const ScrollResponder = require('ScrollResponder');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const StyleSheetPropType = require('StyleSheetPropType');
|
||||
const View = require('View');
|
||||
const ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
|
||||
var deprecatedPropType = require('deprecatedPropType');
|
||||
var dismissKeyboard = require('dismissKeyboard');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var processDecelerationRate = require('processDecelerationRate');
|
||||
var PropTypes = React.PropTypes;
|
||||
|
||||
var SCROLLVIEW = 'ScrollView';
|
||||
var INNERVIEW = 'InnerScrollView';
|
||||
const deprecatedPropType = require('deprecatedPropType');
|
||||
const dismissKeyboard = require('dismissKeyboard');
|
||||
const flattenStyle = require('flattenStyle');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const processDecelerationRate = require('processDecelerationRate');
|
||||
const PropTypes = React.PropTypes;
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
/**
|
||||
* Component that wraps platform ScrollView while providing
|
||||
@@ -51,7 +47,7 @@ var INNERVIEW = 'InnerScrollView';
|
||||
* Doesn't yet support other contained responders from blocking this scroll
|
||||
* view from becoming the responder.
|
||||
*/
|
||||
var ScrollView = React.createClass({
|
||||
const ScrollView = React.createClass({
|
||||
propTypes: {
|
||||
...View.propTypes,
|
||||
/**
|
||||
@@ -119,7 +115,7 @@ var ScrollView = React.createClass({
|
||||
* </ScrollView>
|
||||
* );
|
||||
* ...
|
||||
* var styles = StyleSheet.create({
|
||||
* const styles = StyleSheet.create({
|
||||
* contentContainer: {
|
||||
* paddingVertical: 20
|
||||
* }
|
||||
@@ -222,7 +218,6 @@ var ScrollView = React.createClass({
|
||||
* When true, the scroll view stops on multiples of the scroll view's size
|
||||
* when scrolling. This can be used for horizontal pagination. The default
|
||||
* value is false.
|
||||
* @platform ios
|
||||
*/
|
||||
pagingEnabled: PropTypes.bool,
|
||||
/**
|
||||
@@ -332,6 +327,15 @@ var ScrollView = React.createClass({
|
||||
* @platform android
|
||||
*/
|
||||
endFillColor: ColorPropType,
|
||||
|
||||
/**
|
||||
* Tag used to log scroll performance on this scroll view. Will force
|
||||
* momentum events to be turned on (see sendMomentumEvents). This doesn't do
|
||||
* anything out of the box and you need to implement a custom native
|
||||
* FpsListener for it to be useful.
|
||||
* @platform android
|
||||
*/
|
||||
scrollPerfTag: PropTypes.string,
|
||||
},
|
||||
|
||||
mixins: [ScrollResponder.Mixin],
|
||||
@@ -341,7 +345,7 @@ var ScrollView = React.createClass({
|
||||
},
|
||||
|
||||
setNativeProps: function(props: Object) {
|
||||
this.refs[SCROLLVIEW].setNativeProps(props);
|
||||
this._scrollViewRef && this._scrollViewRef.setNativeProps(props);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -359,16 +363,16 @@ var ScrollView = React.createClass({
|
||||
* implement this method so that they can be composed while providing access
|
||||
* to the underlying scroll responder's methods.
|
||||
*/
|
||||
getScrollResponder: function(): ReactComponent {
|
||||
getScrollResponder: function(): ScrollView {
|
||||
return this;
|
||||
},
|
||||
|
||||
getScrollableNode: function(): any {
|
||||
return ReactNative.findNodeHandle(this.refs[SCROLLVIEW]);
|
||||
return ReactNative.findNodeHandle(this._scrollViewRef);
|
||||
},
|
||||
|
||||
getInnerViewNode: function(): any {
|
||||
return ReactNative.findNodeHandle(this.refs[INNERVIEW]);
|
||||
return ReactNative.findNodeHandle(this._innerViewRef);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -392,7 +396,6 @@ var ScrollView = React.createClass({
|
||||
} else {
|
||||
({x, y, animated} = y || {});
|
||||
}
|
||||
// $FlowFixMe - Don't know how to pass Mixin correctly. Postpone for now
|
||||
this.getScrollResponder().scrollResponderScrollTo({x: x || 0, y: y || 0, animated: animated !== false});
|
||||
},
|
||||
|
||||
@@ -407,7 +410,7 @@ var ScrollView = React.createClass({
|
||||
_handleScroll: function(e: Object) {
|
||||
if (__DEV__) {
|
||||
if (this.props.onScroll && !this.props.scrollEventThrottle && Platform.OS === 'ios') {
|
||||
console.log(
|
||||
console.log( // eslint-disable-line no-console-disallow
|
||||
'You specified `onScroll` on a <ScrollView> but not ' +
|
||||
'`scrollEventThrottle`. You will only receive one event. ' +
|
||||
'Using `16` you get all the events but be aware that it may ' +
|
||||
@@ -425,18 +428,29 @@ var ScrollView = React.createClass({
|
||||
},
|
||||
|
||||
_handleContentOnLayout: function(e: Object) {
|
||||
var {width, height} = e.nativeEvent.layout;
|
||||
const {width, height} = e.nativeEvent.layout;
|
||||
this.props.onContentSizeChange && this.props.onContentSizeChange(width, height);
|
||||
},
|
||||
|
||||
_scrollViewRef: (null: ?ScrollView),
|
||||
_setScrollViewRef: function(ref: ?ScrollView) {
|
||||
this._scrollViewRef = ref;
|
||||
},
|
||||
|
||||
_innerViewRef: (null: ?View),
|
||||
_setInnerViewRef: function(ref: ?View) {
|
||||
this._innerViewRef = ref;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var contentContainerStyle = [
|
||||
const contentContainerStyle = [
|
||||
this.props.horizontal && styles.contentContainerHorizontal,
|
||||
this.props.contentContainerStyle,
|
||||
];
|
||||
let style, childLayoutProps;
|
||||
if (__DEV__ && this.props.style) {
|
||||
var style = flattenStyle(this.props.style);
|
||||
var childLayoutProps = ['alignItems', 'justifyContent']
|
||||
style = flattenStyle(this.props.style);
|
||||
childLayoutProps = ['alignItems', 'justifyContent']
|
||||
.filter((prop) => style && style[prop] !== undefined);
|
||||
invariant(
|
||||
childLayoutProps.length === 0,
|
||||
@@ -445,34 +459,34 @@ var ScrollView = React.createClass({
|
||||
);
|
||||
}
|
||||
|
||||
var contentSizeChangeProps = {};
|
||||
let contentSizeChangeProps = {};
|
||||
if (this.props.onContentSizeChange) {
|
||||
contentSizeChangeProps = {
|
||||
onLayout: this._handleContentOnLayout,
|
||||
};
|
||||
}
|
||||
|
||||
var contentContainer =
|
||||
const contentContainer =
|
||||
<View
|
||||
{...contentSizeChangeProps}
|
||||
ref={INNERVIEW}
|
||||
ref={this._setInnerViewRef}
|
||||
style={contentContainerStyle}
|
||||
removeClippedSubviews={this.props.removeClippedSubviews}
|
||||
collapsable={false}>
|
||||
{this.props.children}
|
||||
</View>;
|
||||
|
||||
var alwaysBounceHorizontal =
|
||||
const alwaysBounceHorizontal =
|
||||
this.props.alwaysBounceHorizontal !== undefined ?
|
||||
this.props.alwaysBounceHorizontal :
|
||||
this.props.horizontal;
|
||||
|
||||
var alwaysBounceVertical =
|
||||
const alwaysBounceVertical =
|
||||
this.props.alwaysBounceVertical !== undefined ?
|
||||
this.props.alwaysBounceVertical :
|
||||
!this.props.horizontal;
|
||||
|
||||
var props = {
|
||||
const props = {
|
||||
...this.props,
|
||||
alwaysBounceHorizontal,
|
||||
alwaysBounceVertical,
|
||||
@@ -496,7 +510,7 @@ var ScrollView = React.createClass({
|
||||
sendMomentumEvents: (this.props.onMomentumScrollBegin || this.props.onMomentumScrollEnd) ? true : false,
|
||||
};
|
||||
|
||||
var onRefreshStart = this.props.onRefreshStart;
|
||||
const onRefreshStart = this.props.onRefreshStart;
|
||||
if (onRefreshStart) {
|
||||
// this is necessary because if we set it on props, even when empty,
|
||||
// it'll trigger the default pull-to-refresh behavior on native.
|
||||
@@ -504,12 +518,12 @@ var ScrollView = React.createClass({
|
||||
function() { onRefreshStart && onRefreshStart(this.endRefreshing); }.bind(this);
|
||||
}
|
||||
|
||||
var { decelerationRate } = this.props;
|
||||
const { decelerationRate } = this.props;
|
||||
if (decelerationRate) {
|
||||
props.decelerationRate = processDecelerationRate(decelerationRate);
|
||||
}
|
||||
|
||||
var ScrollViewClass;
|
||||
let ScrollViewClass;
|
||||
if (Platform.OS === 'ios') {
|
||||
ScrollViewClass = RCTScrollView;
|
||||
} else if (Platform.OS === 'android') {
|
||||
@@ -524,12 +538,12 @@ var ScrollView = React.createClass({
|
||||
'ScrollViewClass must not be undefined'
|
||||
);
|
||||
|
||||
var refreshControl = this.props.refreshControl;
|
||||
const refreshControl = this.props.refreshControl;
|
||||
if (refreshControl) {
|
||||
if (Platform.OS === 'ios') {
|
||||
// On iOS the RefreshControl is a child of the ScrollView.
|
||||
return (
|
||||
<ScrollViewClass {...props} ref={SCROLLVIEW}>
|
||||
<ScrollViewClass {...props} ref={this._setScrollViewRef}>
|
||||
{refreshControl}
|
||||
{contentContainer}
|
||||
</ScrollViewClass>
|
||||
@@ -541,21 +555,21 @@ var ScrollView = React.createClass({
|
||||
return React.cloneElement(
|
||||
refreshControl,
|
||||
{style: props.style},
|
||||
<ScrollViewClass {...props} style={styles.base} ref={SCROLLVIEW}>
|
||||
<ScrollViewClass {...props} style={styles.base} ref={this._setScrollViewRef}>
|
||||
{contentContainer}
|
||||
</ScrollViewClass>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ScrollViewClass {...props} ref={SCROLLVIEW}>
|
||||
<ScrollViewClass {...props} ref={this._setScrollViewRef}>
|
||||
{contentContainer}
|
||||
</ScrollViewClass>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
flex: 1,
|
||||
},
|
||||
@@ -565,20 +579,21 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
let nativeOnlyProps, AndroidScrollView, AndroidHorizontalScrollView, RCTScrollView;
|
||||
if (Platform.OS === 'android') {
|
||||
var nativeOnlyProps = {
|
||||
nativeOnlyProps = {
|
||||
nativeOnly: {
|
||||
sendMomentumEvents: true,
|
||||
}
|
||||
};
|
||||
var AndroidScrollView = requireNativeComponent('RCTScrollView', ScrollView, nativeOnlyProps);
|
||||
var AndroidHorizontalScrollView = requireNativeComponent(
|
||||
AndroidScrollView = requireNativeComponent('RCTScrollView', ScrollView, nativeOnlyProps);
|
||||
AndroidHorizontalScrollView = requireNativeComponent(
|
||||
'AndroidHorizontalScrollView',
|
||||
ScrollView,
|
||||
nativeOnlyProps
|
||||
);
|
||||
} else if (Platform.OS === 'ios') {
|
||||
var nativeOnlyProps = {
|
||||
nativeOnlyProps = {
|
||||
nativeOnly: {
|
||||
onMomentumScrollBegin: true,
|
||||
onMomentumScrollEnd : true,
|
||||
@@ -586,7 +601,7 @@ if (Platform.OS === 'android') {
|
||||
onScrollEndDrag: true,
|
||||
}
|
||||
};
|
||||
var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView, nativeOnlyProps);
|
||||
RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView, nativeOnlyProps);
|
||||
}
|
||||
|
||||
module.exports = ScrollView;
|
||||
|
||||
@@ -11,4 +11,6 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = null;
|
||||
const NativeEventEmitter = require('NativeEventEmitter');
|
||||
|
||||
module.exports = new NativeEventEmitter('StatusBarManager');
|
||||
|
||||
@@ -11,24 +11,26 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NativeEventEmitter = require('NativeEventEmitter');
|
||||
const StatusBar = require('StatusBar');
|
||||
const StatusBarManager = require('NativeModules').StatusBarManager;
|
||||
|
||||
import type {StatusBarStyle, StatusBarAnimation} from 'StatusBar';
|
||||
|
||||
/**
|
||||
* Deprecated. Use `StatusBar` instead.
|
||||
*/
|
||||
const StatusBarIOS = {
|
||||
class StatusBarIOS extends NativeEventEmitter {
|
||||
|
||||
setStyle(style: StatusBarStyle, animated?: boolean) {
|
||||
console.warn('`StatusBarIOS.setStyle` is deprecated. Use `StatusBar.setBarStyle` instead.');
|
||||
StatusBar.setBarStyle(style, animated);
|
||||
},
|
||||
}
|
||||
|
||||
setHidden(hidden: boolean, animation?: StatusBarAnimation) {
|
||||
console.warn('`StatusBarIOS.setHidden` is deprecated. Use `StatusBar.setHidden` instead.');
|
||||
StatusBar.setHidden(hidden, animation);
|
||||
},
|
||||
}
|
||||
|
||||
setNetworkActivityIndicatorVisible(visible: boolean) {
|
||||
console.warn(
|
||||
@@ -36,7 +38,7 @@ const StatusBarIOS = {
|
||||
'Use `StatusBar.setNetworkActivityIndicatorVisible` instead.'
|
||||
);
|
||||
StatusBar.setNetworkActivityIndicatorVisible(visible);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StatusBarIOS;
|
||||
module.exports = new StatusBarIOS(StatusBarManager);
|
||||
|
||||
@@ -120,6 +120,7 @@ var TextInput = React.createClass({
|
||||
* Determines which keyboard to open, e.g.`numeric`.
|
||||
*
|
||||
* The following values work across platforms:
|
||||
*
|
||||
* - default
|
||||
* - numeric
|
||||
* - email-address
|
||||
@@ -150,22 +151,54 @@ var TextInput = React.createClass({
|
||||
'dark',
|
||||
]),
|
||||
/**
|
||||
* Determines how the return key should look.
|
||||
* @platform ios
|
||||
* Determines how the return key should look. On Android you can also use
|
||||
* `returnKeyLabel`.
|
||||
*
|
||||
* The following values work across platforms:
|
||||
*
|
||||
* - done
|
||||
* - go
|
||||
* - next
|
||||
* - search
|
||||
* - send
|
||||
*
|
||||
* The following values work on Android only:
|
||||
*
|
||||
* - none
|
||||
* - previous
|
||||
*
|
||||
* The following values work on iOS only:
|
||||
*
|
||||
* - default
|
||||
* - emergency-call
|
||||
* - google
|
||||
* - join
|
||||
* - route
|
||||
* - yahoo
|
||||
*/
|
||||
returnKeyType: PropTypes.oneOf([
|
||||
'default',
|
||||
// Cross-platform
|
||||
'done',
|
||||
'go',
|
||||
'google',
|
||||
'join',
|
||||
'next',
|
||||
'route',
|
||||
'search',
|
||||
'send',
|
||||
'yahoo',
|
||||
'done',
|
||||
// Android-only
|
||||
'none',
|
||||
'previous',
|
||||
// iOS-only
|
||||
'default',
|
||||
'emergency-call',
|
||||
'google',
|
||||
'join',
|
||||
'route',
|
||||
'yahoo',
|
||||
]),
|
||||
/**
|
||||
* Sets the return key to the label. Use it instead of `returnKeyType`.
|
||||
* @platform android
|
||||
*/
|
||||
returnKeyLabel: PropTypes.string,
|
||||
/**
|
||||
* Limits the maximum number of characters that can be entered. Use this
|
||||
* instead of implementing the logic in JS to avoid flicker.
|
||||
@@ -531,6 +564,8 @@ var TextInput = React.createClass({
|
||||
children={children}
|
||||
editable={this.props.editable}
|
||||
selectTextOnFocus={this.props.selectTextOnFocus}
|
||||
returnKeyType={this.props.returnKeyType}
|
||||
returnKeyLabel={this.props.returnKeyLabel}
|
||||
/>;
|
||||
|
||||
return (
|
||||
|
||||
@@ -128,7 +128,7 @@ var ViewPagerAndroid = React.createClass({
|
||||
* When false, the content does not scroll.
|
||||
* The default value is true.
|
||||
*/
|
||||
scrollEnabled: React.PropTypes.bool,
|
||||
scrollEnabled: ReactPropTypes.bool,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
@@ -207,7 +207,7 @@ var ViewPagerAndroid = React.createClass({
|
||||
|
||||
/**
|
||||
* A helper function to scroll to a specific page in the ViewPager.
|
||||
* The transition between pages will be *not* be animated.
|
||||
* The transition between pages will *not* be animated.
|
||||
*/
|
||||
setPageWithoutAnimation: function(selectedPage: number) {
|
||||
UIManager.dispatchViewManagerCommand(
|
||||
|
||||
@@ -137,9 +137,10 @@ var ListView = React.createClass({
|
||||
* 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. 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).
|
||||
* being highlighted by calling `highlightRow(sectionID, rowID)`. This
|
||||
* sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you
|
||||
* to control the separators above and below the highlighted row. The highlighted
|
||||
* state of a row can be reset by calling highlightRow(null).
|
||||
*/
|
||||
renderRow: PropTypes.func.isRequired,
|
||||
/**
|
||||
@@ -595,12 +596,19 @@ var ListView = React.createClass({
|
||||
var rowID = rowIDs[rowIdx];
|
||||
var frame = this._childFrames[totalIndex];
|
||||
totalIndex++;
|
||||
if(this.props.renderSeparator &&
|
||||
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)){
|
||||
totalIndex++;
|
||||
}
|
||||
if (!frame) {
|
||||
break;
|
||||
}
|
||||
var rowVisible = visibleSection[rowID];
|
||||
var min = isVertical ? frame.y : frame.x;
|
||||
var max = min + (isVertical ? frame.height : frame.width);
|
||||
if ((!min && !max) || (min === max)) {
|
||||
break;
|
||||
}
|
||||
if (min > visibleMax || max < visibleMin) {
|
||||
if (rowVisible) {
|
||||
visibilityChanged = true;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
const Animated = require('Animated');
|
||||
const NavigationCardStackPanResponder = require('NavigationCardStackPanResponder');
|
||||
const NavigationCardStackStyleInterpolator = require('NavigationCardStackStyleInterpolator');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const NavigationPagerPanResponder = require('NavigationPagerPanResponder');
|
||||
const NavigationPagerStyleInterpolator = require('NavigationPagerStyleInterpolator');
|
||||
const NavigationPointerEventsContainer = require('NavigationPointerEventsContainer');
|
||||
@@ -70,7 +69,8 @@ class SceneView extends React.Component<any, SceneViewProps, any> {
|
||||
|
||||
static propTypes = {
|
||||
sceneRenderer: PropTypes.func.isRequired,
|
||||
sceneRendererProps: NavigationPropTypes.SceneRenderer,
|
||||
sceneRendererProps:
|
||||
PropTypes.shape(NavigationPropTypes.SceneRenderer).isRequired,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps: SceneViewProps, nextState: any): boolean {
|
||||
@@ -92,7 +92,7 @@ class NavigationCard extends React.Component<any, Props, any> {
|
||||
props: Props;
|
||||
|
||||
static propTypes = {
|
||||
...NavigationPropTypes.SceneRenderer,
|
||||
...NavigationPropTypes.SceneRendererProps,
|
||||
onComponentRef: PropTypes.func.isRequired,
|
||||
panHandlers: NavigationPropTypes.panHandlers,
|
||||
pointerEvents: PropTypes.string.isRequired,
|
||||
@@ -138,6 +138,11 @@ class NavigationCard extends React.Component<any, Props, any> {
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
static CardStackPanResponder = NavigationCardStackPanResponder;
|
||||
static CardStackStyleInterpolator = NavigationCardStackStyleInterpolator;
|
||||
static PagerPanResponder = NavigationPagerPanResponder;
|
||||
static PagerStyleInterpolator = NavigationPagerStyleInterpolator;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -156,12 +161,5 @@ const styles = StyleSheet.create({
|
||||
});
|
||||
|
||||
NavigationCard = NavigationPointerEventsContainer.create(NavigationCard);
|
||||
NavigationCard = NavigationContainer.create(NavigationCard);
|
||||
|
||||
// Export these buil-in interaction modules.
|
||||
NavigationCard.CardStackPanResponder = NavigationCardStackPanResponder;
|
||||
NavigationCard.CardStackStyleInterpolator = NavigationCardStackStyleInterpolator;
|
||||
NavigationCard.PagerPanResponder = NavigationPagerPanResponder;
|
||||
NavigationCard.PagerStyleInterpolator = NavigationPagerStyleInterpolator;
|
||||
|
||||
module.exports = NavigationCard;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
const NavigationAnimatedView = require('NavigationAnimatedView');
|
||||
const NavigationCard = require('NavigationCard');
|
||||
const NavigationCardStackStyleInterpolator = require('NavigationCardStackStyleInterpolator');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const NavigationCardStackPanResponder = require('NavigationCardStackPanResponder');
|
||||
const NavigationPropTypes = require('NavigationPropTypes');
|
||||
const React = require('React');
|
||||
@@ -48,6 +47,7 @@ const {PropTypes} = React;
|
||||
const {Directions} = NavigationCardStackPanResponder;
|
||||
|
||||
import type {
|
||||
NavigationActionCaller,
|
||||
NavigationParentState,
|
||||
NavigationSceneRenderer,
|
||||
NavigationSceneRendererProps,
|
||||
@@ -60,6 +60,7 @@ import type {
|
||||
type Props = {
|
||||
direction: NavigationGestureDirection,
|
||||
navigationState: NavigationParentState,
|
||||
onNavigate: NavigationActionCaller,
|
||||
renderOverlay: ?NavigationSceneRenderer,
|
||||
renderScene: NavigationSceneRenderer,
|
||||
};
|
||||
@@ -89,6 +90,7 @@ class NavigationCardStack extends React.Component<DefaultProps, Props, void> {
|
||||
static propTypes = {
|
||||
direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
|
||||
navigationState: NavigationPropTypes.navigationParentState.isRequired,
|
||||
onNavigate: NavigationPropTypes.SceneRendererProps.onNavigate,
|
||||
renderOverlay: PropTypes.func,
|
||||
renderScene: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -120,6 +122,7 @@ class NavigationCardStack extends React.Component<DefaultProps, Props, void> {
|
||||
navigationState={this.props.navigationState}
|
||||
renderOverlay={this.props.renderOverlay}
|
||||
renderScene={this._renderScene}
|
||||
onNavigate={this.props.onNavigate}
|
||||
// $FlowFixMe - style should be declared
|
||||
style={[styles.animatedView, this.props.style]}
|
||||
/>
|
||||
@@ -155,4 +158,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationContainer.create(NavigationCardStack);
|
||||
module.exports = NavigationCardStack;
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
|
||||
const React = require('React');
|
||||
const ReactNative = require('react-native');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const NavigationHeaderTitle = require('NavigationHeaderTitle');
|
||||
const NavigationHeaderBackButton = require('NavigationHeaderBackButton');
|
||||
const NavigationPropTypes = require('NavigationPropTypes');
|
||||
@@ -49,6 +48,7 @@ const {
|
||||
} = ReactNative;
|
||||
|
||||
import type {
|
||||
NavigationActionCaller,
|
||||
NavigationSceneRenderer,
|
||||
NavigationSceneRendererProps,
|
||||
NavigationStyleInterpolator,
|
||||
@@ -64,8 +64,9 @@ type Props = NavigationSceneRendererProps & {
|
||||
renderLeftComponent: NavigationSceneRenderer,
|
||||
renderRightComponent: NavigationSceneRenderer,
|
||||
renderTitleComponent: NavigationSceneRenderer,
|
||||
style?: any;
|
||||
viewProps?: any;
|
||||
onNavigate: NavigationActionCaller,
|
||||
style?: any,
|
||||
viewProps?: any,
|
||||
};
|
||||
|
||||
type SubViewName = 'left' | 'title' | 'right';
|
||||
@@ -86,7 +87,14 @@ class NavigationHeader extends React.Component<DefaultProps, Props, any> {
|
||||
},
|
||||
|
||||
renderLeftComponent: (props: NavigationSceneRendererProps) => {
|
||||
return props.scene.index > 0 ? <NavigationHeaderBackButton /> : null;
|
||||
if (props.scene.index === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<NavigationHeaderBackButton
|
||||
onNavigate={props.onNavigate}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
renderRightComponent: (props: NavigationSceneRendererProps) => {
|
||||
@@ -95,7 +103,7 @@ class NavigationHeader extends React.Component<DefaultProps, Props, any> {
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
...NavigationPropTypes.SceneRenderer,
|
||||
...NavigationPropTypes.SceneRendererProps,
|
||||
renderLeftComponent: PropTypes.func,
|
||||
renderRightComponent: PropTypes.func,
|
||||
renderTitleComponent: PropTypes.func,
|
||||
@@ -199,6 +207,11 @@ class NavigationHeader extends React.Component<DefaultProps, Props, any> {
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
static HEIGHT = APPBAR_HEIGHT + STATUSBAR_HEIGHT;
|
||||
static Title = NavigationHeaderTitle;
|
||||
static BackButton = NavigationHeaderBackButton;
|
||||
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -244,10 +257,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
const NavigationHeaderContainer = NavigationContainer.create(NavigationHeader);
|
||||
|
||||
NavigationHeaderContainer.HEIGHT = APPBAR_HEIGHT + STATUSBAR_HEIGHT;
|
||||
NavigationHeaderContainer.Title = NavigationHeaderTitle;
|
||||
NavigationHeaderContainer.BackButton = NavigationHeaderBackButton;
|
||||
|
||||
module.exports = NavigationHeaderContainer;
|
||||
module.exports = NavigationHeader;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
@@ -18,8 +25,6 @@
|
||||
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const NavigationRootContainer = require('NavigationRootContainer');
|
||||
|
||||
const {
|
||||
Image,
|
||||
@@ -33,7 +38,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const NavigationHeaderBackButton = (props: Props) => (
|
||||
<TouchableOpacity style={styles.buttonContainer} onPress={() => props.onNavigate(NavigationRootContainer.getBackAction())}>
|
||||
<TouchableOpacity style={styles.buttonContainer} onPress={() => props.onNavigate({type: 'BackAction'})}>
|
||||
<Image style={styles.button} source={require('./assets/back-icon.png')} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
@@ -57,4 +62,4 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NavigationContainer.create(NavigationHeaderBackButton);
|
||||
module.exports = NavigationHeaderBackButton;
|
||||
|
||||
@@ -1,446 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @providesModule NavigationLegacyNavigator
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animated = require('Animated');
|
||||
const NavigationAnimatedValueSubscription = require('NavigationAnimatedValueSubscription');
|
||||
const NavigationAnimatedView = require('NavigationAnimatedView');
|
||||
const NavigationCard = require('NavigationCard');
|
||||
const NavigationCardStackStyleInterpolator = require('NavigationCardStackStyleInterpolator');
|
||||
const NavigationContext = require('NavigationContext');
|
||||
const NavigationLegacyNavigatorRouteStack = require('NavigationLegacyNavigatorRouteStack');
|
||||
const NavigationCardStackPanResponder = require('NavigationCardStackPanResponder');
|
||||
const NavigationPagerPanResponder = require('NavigationPagerPanResponder');
|
||||
const NavigationPagerStyleInterpolator = require('NavigationPagerStyleInterpolator');
|
||||
const NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
|
||||
const NavigatorNavigationBar = require('NavigatorNavigationBar');
|
||||
const NavigatorSceneConfigs = require('NavigatorSceneConfigs');
|
||||
const React = require('React');
|
||||
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
|
||||
|
||||
import type {
|
||||
NavigationActionCaller,
|
||||
NavigationAnimatedValue,
|
||||
NavigationAnimationSetter,
|
||||
NavigationParentState,
|
||||
NavigationSceneRenderer,
|
||||
NavigationSceneRendererProps,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
type Props = {
|
||||
configureScene: any,
|
||||
initialRoute: any,
|
||||
initialRouteStack: any,
|
||||
navigationBar: any,
|
||||
navigationBarNavigator: any,
|
||||
navigator: any,
|
||||
onDidFocus: any,
|
||||
onWillFocus: any,
|
||||
renderScene: any,
|
||||
renderScene: any,
|
||||
style: any,
|
||||
};
|
||||
|
||||
type State = {
|
||||
presentedIndex: number,
|
||||
routeStack: Array<any>,
|
||||
};
|
||||
|
||||
const RouteStack = NavigationLegacyNavigatorRouteStack;
|
||||
|
||||
/**
|
||||
* NavigationLegacyNavigator is meant to replace Navigator seemlessly with
|
||||
* minimum API changes.
|
||||
*
|
||||
* While the APIs remain compatible with Navigator, it is built with good
|
||||
* intention by using the new Navigation API such as
|
||||
* `NavigationAnimatedView`...etc.
|
||||
*/
|
||||
class NavigationLegacyNavigator extends React.Component<any, Props, State> {
|
||||
static BreadcrumbNavigationBar: any;
|
||||
static NavigationBar: any;
|
||||
static SceneConfigs: any;
|
||||
|
||||
_applyAnimation: NavigationAnimationSetter;
|
||||
_navigationBarRef: any;
|
||||
_onNavigationBarRef: (ref: any) => void;
|
||||
_onPositionChange: (data: {value: number}) => void;
|
||||
_positionListener: ?NavigationAnimatedValueSubscription;
|
||||
_previousStack: NavigationLegacyNavigatorRouteStack;
|
||||
_renderCard: NavigationSceneRenderer;
|
||||
_renderHeader: NavigationSceneRenderer;
|
||||
_renderScene: NavigationSceneRenderer;
|
||||
_routeFocused: any;
|
||||
_routeToFocus: any;
|
||||
_onNavigate: NavigationActionCaller;
|
||||
_stack: NavigationLegacyNavigatorRouteStack;
|
||||
_useAnimation: boolean;
|
||||
|
||||
navigationContext: NavigationContext;
|
||||
parentNavigator: any;
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
constructor(props: Props, context: any) {
|
||||
super(props, context);
|
||||
|
||||
const stack = this._getInitialRouteStack();
|
||||
|
||||
// Unfortunately, due to historical reasons, the `state` has been exposed
|
||||
// as public members of the navigator, therefore we'd keep private state
|
||||
// as private members.
|
||||
this._previousStack = stack;
|
||||
this._stack = stack;
|
||||
this._useAnimation = false;
|
||||
|
||||
// Legacy members portred from `Navigator`.
|
||||
this.parentNavigator = props.navigator;
|
||||
this.navigationContext = new NavigationContext();
|
||||
|
||||
this.state = {
|
||||
routeStack: stack.toArray(),
|
||||
presentedIndex: stack.index,
|
||||
};
|
||||
}
|
||||
|
||||
jumpTo(route: any): void {
|
||||
this._applyStack(this._stack.jumpTo(route));
|
||||
}
|
||||
|
||||
jumpForward(): void {
|
||||
this._applyStack(this._stack.jumpForward());
|
||||
}
|
||||
|
||||
jumpBack(): void {
|
||||
this._applyStack(this._stack.jumpBack());
|
||||
}
|
||||
|
||||
push(route: any): void {
|
||||
this._applyStack(this._stack.push(route));
|
||||
}
|
||||
|
||||
pop(): void {
|
||||
this._applyStack(this._stack.pop());
|
||||
}
|
||||
|
||||
replaceAtIndex(route: any, index: number): void {
|
||||
this._applyStack(this._stack.replaceAtIndex(index, route));
|
||||
}
|
||||
|
||||
replace(route: any): void {
|
||||
this.replaceAtIndex(route, this._stack.index);
|
||||
}
|
||||
|
||||
replacePrevious(route: any): void {
|
||||
this.replaceAtIndex(route, this._stack.index - 1);
|
||||
}
|
||||
|
||||
popToTop(): void {
|
||||
this._applyStack(this._stack.slice(0, 1));
|
||||
}
|
||||
|
||||
popToRoute(route: any): void {
|
||||
this._applyStack(this._stack.popToRoute(route));
|
||||
}
|
||||
|
||||
replacePreviousAndPop(route: any): void {
|
||||
this._applyStack(this._stack.replacePreviousAndPop(route));
|
||||
}
|
||||
|
||||
resetTo(route: any): void {
|
||||
this._applyStack(this._stack.resetTo(route));
|
||||
}
|
||||
|
||||
immediatelyResetRouteStack(routes: Array<any>): void {
|
||||
this._applyStack(this._stack.resetRoutes(routes), true);
|
||||
}
|
||||
|
||||
getCurrentRoutes(): Array<any> {
|
||||
return this._stack.toArray();
|
||||
}
|
||||
|
||||
// Life cycle and private methods below.
|
||||
|
||||
shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
|
||||
return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
|
||||
this,
|
||||
nextProps,
|
||||
nextState
|
||||
);
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
this._applyAnimation = this._applyAnimation.bind(this);
|
||||
this._onNavigate = this._onNavigate.bind(this);
|
||||
this._onNavigationBarRef = this._onNavigationBarRef.bind(this);
|
||||
this._onPositionChange = this._onPositionChange.bind(this);
|
||||
this._renderCard = this._renderCard.bind(this);
|
||||
this._renderHeader = this._renderHeader.bind(this);
|
||||
this._renderScene = this._renderScene.bind(this);
|
||||
|
||||
this._willFocus();
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this._didFocus();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this._positionListener && this._positionListener.remove();
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps: Props, nextState: State): void {
|
||||
this._willFocus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State): void {
|
||||
if (this._useAnimation) {
|
||||
// will play animation.
|
||||
return;
|
||||
}
|
||||
this._didFocus();
|
||||
}
|
||||
|
||||
render(): ReactElement {
|
||||
return (
|
||||
<NavigationAnimatedView
|
||||
applyAnimation={this._applyAnimation}
|
||||
navigationState={this._stack.toNavigationState()}
|
||||
onNavigate={this._onNavigate}
|
||||
renderOverlay={this._renderHeader}
|
||||
renderScene={this._renderCard}
|
||||
style={this.props.style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_getInitialRouteStack(): RouteStack {
|
||||
const {initialRouteStack, initialRoute} = this.props;
|
||||
const routes = initialRouteStack || [initialRoute];
|
||||
const index = initialRoute ?
|
||||
routes.indexOf(initialRoute) :
|
||||
routes.length - 1;
|
||||
return new RouteStack(index, routes);
|
||||
}
|
||||
|
||||
_renderHeader(props: NavigationSceneRendererProps): ?ReactElement {
|
||||
// `_renderHeader` is always called before `_renderCard`. We should
|
||||
// subscribe to the position here.
|
||||
this._positionListener && this._positionListener.remove();
|
||||
this._positionListener = new NavigationAnimatedValueSubscription(
|
||||
props.position,
|
||||
this._onPositionChange,
|
||||
);
|
||||
|
||||
const {navigationBar, navigationBarNavigator} = this.props;
|
||||
if (!navigationBar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.cloneElement(
|
||||
navigationBar,
|
||||
{
|
||||
key: 'header_' + props.scene.key,
|
||||
ref: this._onNavigationBarRef,
|
||||
navigator: navigationBarNavigator || this,
|
||||
navState: {...this.state},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_renderCard(props: NavigationSceneRendererProps): ReactElement {
|
||||
const {scene} = props;
|
||||
const {configureScene} = this.props;
|
||||
|
||||
// Default getters for style and pan responders.
|
||||
let styleGetter = NavigationCardStackStyleInterpolator.forHorizontal;
|
||||
let panResponderGetter = NavigationCardStackPanResponder.forHorizontal;
|
||||
|
||||
if (configureScene) {
|
||||
const route = RouteStack.getRouteByNavigationState(scene.navigationState);
|
||||
const config = configureScene(route, this.state.routeStack);
|
||||
|
||||
if (config) {
|
||||
const gestures = config.gestures || {};
|
||||
if (gestures.pop && gestures.pop.direction === 'left-to-right') {
|
||||
// pass, will use default getters.
|
||||
} else if (gestures.pop && gestures.pop.direction === 'top-to-bottom') {
|
||||
styleGetter = NavigationCardStackStyleInterpolator.forVertical;
|
||||
panResponderGetter = NavigationCardStackPanResponder.forVertical;
|
||||
} else if (
|
||||
gestures.jumpBack &&
|
||||
gestures.jumpForward &&
|
||||
gestures.jumpBack.direction === 'left-to-right' &&
|
||||
gestures.jumpForward.direction === 'right-to-left'
|
||||
) {
|
||||
styleGetter = NavigationPagerStyleInterpolator.forHorizontal;
|
||||
panResponderGetter = NavigationPagerPanResponder.forHorizontal;
|
||||
} else if (__DEV__) {
|
||||
console.warn('unsupported scene configuration', config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const style = styleGetter(props);
|
||||
const panHandlers = panResponderGetter(props);
|
||||
|
||||
return (
|
||||
<NavigationCard
|
||||
{...props}
|
||||
key={'card_' + props.scene.key}
|
||||
panHandlers={panHandlers}
|
||||
renderScene={this._renderScene}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_renderScene(props: NavigationSceneRendererProps): ReactElement {
|
||||
const {navigationState} = props.scene;
|
||||
const route = RouteStack.getRouteByNavigationState(navigationState);
|
||||
return this.props.renderScene(route, this);
|
||||
}
|
||||
|
||||
_applyStack(
|
||||
stack: NavigationLegacyNavigatorRouteStack,
|
||||
noAnimation: ?boolean,
|
||||
): void {
|
||||
if (stack !== this._stack) {
|
||||
this._previousStack = this._stack;
|
||||
this._stack = stack;
|
||||
|
||||
this._useAnimation = noAnimation ||
|
||||
this._previousStack.index !== stack.index;
|
||||
|
||||
this.setState({
|
||||
presentedIndex: stack.index,
|
||||
routeStack: stack.toArray(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onNavigationBarRef(navigationBarRef: any): void {
|
||||
this._navigationBarRef = navigationBarRef;
|
||||
const {navigationBar} = this.props;
|
||||
if (navigationBar && typeof navigationBar.ref === 'function') {
|
||||
navigationBar.ref(navigationBarRef);
|
||||
}
|
||||
}
|
||||
|
||||
_onPositionChange(data: {value: number}): void {
|
||||
const fromIndex = this._previousStack.index;
|
||||
const toIndex = this._stack.index;
|
||||
|
||||
if (
|
||||
fromIndex !== toIndex &&
|
||||
this._navigationBarRef &&
|
||||
typeof this._navigationBarRef.updateProgress === 'function'
|
||||
) {
|
||||
const progress = (data.value - fromIndex) / (toIndex - fromIndex);
|
||||
this._navigationBarRef.updateProgress(progress, fromIndex, toIndex);
|
||||
}
|
||||
|
||||
const diff = this._stack.index - data.value;
|
||||
// When animation stops, the `diff` can still be very a small non-zero value
|
||||
// (e.g. 0.00000002). Call `willFocus` when `diff` is small enough.
|
||||
if (diff < 0.05) {
|
||||
this._didFocus();
|
||||
}
|
||||
}
|
||||
|
||||
_applyAnimation(
|
||||
position: NavigationAnimatedValue,
|
||||
nextState: NavigationParentState,
|
||||
prevState: NavigationParentState,
|
||||
): void {
|
||||
const {index} = nextState;
|
||||
|
||||
if (!this._useAnimation) {
|
||||
position.setValue(index);
|
||||
return;
|
||||
}
|
||||
|
||||
Animated.timing(
|
||||
position,
|
||||
{
|
||||
duration: 500,
|
||||
toValue: index,
|
||||
}
|
||||
).start();
|
||||
}
|
||||
|
||||
_willFocus(): void {
|
||||
const route = this._stack.get(this._stack.index);
|
||||
if (this._routeToFocus === route) {
|
||||
return;
|
||||
}
|
||||
this._routeToFocus = route;
|
||||
this.navigationContext.emit('willfocus', {route: route});
|
||||
this.props.onWillFocus && this.props.onWillFocus(route);
|
||||
}
|
||||
|
||||
_didFocus(): void {
|
||||
const route = this._stack.get(this._stack.index);
|
||||
if (this._routeFocused === route) {
|
||||
return;
|
||||
}
|
||||
this._routeFocused = route;
|
||||
this.navigationContext.emit('didfocus', {route: route});
|
||||
this.props.onDidFocus && this.props.onDidFocus(route);
|
||||
}
|
||||
|
||||
_onNavigate(action: any): void {
|
||||
switch (action) {
|
||||
case NavigationCardStackPanResponder.Actions.BACK:
|
||||
this.pop();
|
||||
break;
|
||||
case NavigationPagerPanResponder.Actions.JUMP_BACK:
|
||||
this.jumpBack();
|
||||
break;
|
||||
case NavigationPagerPanResponder.Actions.JUMP_FORWARD:
|
||||
this.jumpForward();
|
||||
break;
|
||||
default:
|
||||
if (__DEV__) {
|
||||
console.warn('unsupported gesture action', action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy static members.
|
||||
NavigationLegacyNavigator.BreadcrumbNavigationBar = NavigatorBreadcrumbNavigationBar;
|
||||
NavigationLegacyNavigator.NavigationBar = NavigatorNavigationBar;
|
||||
NavigationLegacyNavigator.SceneConfigs = NavigatorSceneConfigs;
|
||||
|
||||
module.exports = NavigationLegacyNavigator;
|
||||
-381
@@ -1,381 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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 NavigationLegacyNavigatorRouteStack
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {
|
||||
NavigationState,
|
||||
NavigationParentState,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
type IterationCallback = (route: any, index: number, key: string) => void;
|
||||
|
||||
function isRouteEmpty(route: any): boolean {
|
||||
return (route === undefined || route === null || route === '') || false;
|
||||
}
|
||||
|
||||
function areRouteNodesEqual(
|
||||
one: Array<RouteNode>,
|
||||
two: Array<RouteNode>,
|
||||
): boolean {
|
||||
if (one === two) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (one.length !== two.length) {
|
||||
return false;
|
||||
}
|
||||
for (let ii = 0, jj = one.length; ii < jj; ii++) {
|
||||
if (one[ii] !== two[ii]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let _nextRouteNodeID = 0;
|
||||
|
||||
/**
|
||||
* Private struct class that holds the key for a route.
|
||||
*/
|
||||
class RouteNode {
|
||||
key: string;
|
||||
route: any;
|
||||
|
||||
/**
|
||||
* Cast `navigationState` as `RouteNode`.
|
||||
* Also see `RouteNode#toNavigationState`.
|
||||
*/
|
||||
static fromNavigationState(navigationState: NavigationState): RouteNode {
|
||||
invariant(
|
||||
navigationState instanceof RouteNode,
|
||||
'navigationState should be an instacne of RouteNode'
|
||||
);
|
||||
return navigationState;
|
||||
}
|
||||
|
||||
constructor(route: any) {
|
||||
// Key value gets bigger incrementally. Developer can compare the
|
||||
// keys of two routes then know which route is added to the stack
|
||||
// earlier.
|
||||
const key = String(_nextRouteNodeID++);
|
||||
if (__DEV__ ) {
|
||||
// Ensure the immutability of the node.
|
||||
Object.defineProperty(this, 'key' , {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: key,
|
||||
});
|
||||
Object.defineProperty(this, 'route' , {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: route,
|
||||
});
|
||||
} else {
|
||||
this.key = key;
|
||||
this.route = route;
|
||||
}
|
||||
}
|
||||
|
||||
toNavigationState(): NavigationState {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
let _nextRouteStackID = 0;
|
||||
|
||||
/**
|
||||
* The data structure that holds a list of routes and the focused index
|
||||
* of the routes. This data structure is implemented as immutable data
|
||||
* and mutation (e.g. push, pop...etc) will yields a new instance.
|
||||
*/
|
||||
class RouteStack {
|
||||
_index: number;
|
||||
_key: string;
|
||||
_routeNodes: Array<RouteNode>;
|
||||
|
||||
static getRouteByNavigationState(navigationState: NavigationState): any {
|
||||
return RouteNode.fromNavigationState(navigationState).route;
|
||||
}
|
||||
|
||||
constructor(index: number, routes: Array<any>) {
|
||||
invariant(
|
||||
routes.length > 0,
|
||||
'routes must not be an empty array'
|
||||
);
|
||||
|
||||
invariant(
|
||||
index > -1 && index <= routes.length - 1,
|
||||
'RouteStack: index out of bound'
|
||||
);
|
||||
|
||||
|
||||
let routeNodes;
|
||||
if (routes[0] instanceof RouteNode) {
|
||||
// The array is already an array of <RouteNode>.
|
||||
routeNodes = routes;
|
||||
} else {
|
||||
// Wrap the route with <RouteNode>.
|
||||
routeNodes = routes.map((route) => {
|
||||
invariant(!isRouteEmpty(route), 'route must not be mepty');
|
||||
return new RouteNode(route);
|
||||
});
|
||||
}
|
||||
|
||||
this._routeNodes = routeNodes;
|
||||
this._index = index;
|
||||
this._key = String(_nextRouteStackID++);
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get size(): number {
|
||||
return this._routeNodes.length;
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get index(): number {
|
||||
return this._index;
|
||||
}
|
||||
|
||||
// Export as...
|
||||
toArray(): Array<any> {
|
||||
return this._routeNodes.map(node => node.route);
|
||||
}
|
||||
|
||||
toNavigationState(): NavigationParentState {
|
||||
return {
|
||||
index: this._index,
|
||||
key: this._key,
|
||||
children: this._routeNodes.map(node => node.toNavigationState()),
|
||||
};
|
||||
}
|
||||
|
||||
get(index: number): any {
|
||||
if (index < 0 || index > this._routeNodes.length - 1) {
|
||||
return null;
|
||||
}
|
||||
return this._routeNodes[index].route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key associated with the route.
|
||||
* When a route is added to a stack, the stack creates a key for this route.
|
||||
* The key will persist until the initial stack and its derived stack
|
||||
* no longer contains this route.
|
||||
*/
|
||||
keyOf(route: any): ?string {
|
||||
if (isRouteEmpty(route)) {
|
||||
return null;
|
||||
}
|
||||
const index = this.indexOf(route);
|
||||
return index > -1 ?
|
||||
this._routeNodes[index].key :
|
||||
null;
|
||||
}
|
||||
|
||||
indexOf(route: any): number {
|
||||
if (isRouteEmpty(route)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (let ii = 0, jj = this._routeNodes.length; ii < jj; ii++) {
|
||||
const node = this._routeNodes[ii];
|
||||
if (node.route === route) {
|
||||
return ii;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
slice(begin: ?number, end: ?number): RouteStack {
|
||||
// check `begin` and `end` first to keep @flow happy.
|
||||
const routeNodes = (end === undefined || end === null) ?
|
||||
this._routeNodes.slice(begin || 0) :
|
||||
this._routeNodes.slice(begin || 0, end || 0);
|
||||
|
||||
const index = Math.min(this._index, routeNodes.length - 1);
|
||||
return this._update(index, routeNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new stack with the provided route appended,
|
||||
* starting at this stack size.
|
||||
*/
|
||||
push(route: any): RouteStack {
|
||||
|
||||
invariant(
|
||||
!isRouteEmpty(route),
|
||||
'Must supply route to push'
|
||||
);
|
||||
|
||||
invariant(this.indexOf(route) === -1, 'route must be unique');
|
||||
|
||||
// When pushing, removes the rest of the routes past the current index.
|
||||
const routeNodes = this._routeNodes.slice(0, this._index + 1);
|
||||
routeNodes.push(new RouteNode(route));
|
||||
return this._update(routeNodes.length - 1, routeNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new stack a size ones less than this stack,
|
||||
* excluding the last index in this stack.
|
||||
*/
|
||||
pop(): RouteStack {
|
||||
if (this._routeNodes.length <= 1) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// When popping, removes the rest of the routes past the current index.
|
||||
const routeNodes = this._routeNodes.slice(0, this._index);
|
||||
return this._update(routeNodes.length - 1, routeNodes);
|
||||
}
|
||||
|
||||
popToRoute(route: any): RouteStack {
|
||||
const index = this.indexOf(route);
|
||||
invariant(
|
||||
index > -1,
|
||||
'Calling popToRoute for a route that doesn\'t exist!'
|
||||
);
|
||||
return this.slice(0, index + 1);
|
||||
}
|
||||
|
||||
jumpTo(route: any): RouteStack {
|
||||
const index = this.indexOf(route);
|
||||
return this.jumpToIndex(index);
|
||||
}
|
||||
|
||||
jumpToIndex(index: number): RouteStack {
|
||||
invariant(
|
||||
index > -1 && index < this._routeNodes.length,
|
||||
'jumpToIndex: index out of bound'
|
||||
);
|
||||
|
||||
return this._update(index, this._routeNodes);
|
||||
}
|
||||
|
||||
jumpForward(): RouteStack {
|
||||
const index = this._index + 1;
|
||||
if (index >= this._routeNodes.length) {
|
||||
return this;
|
||||
}
|
||||
return this._update(index, this._routeNodes);
|
||||
}
|
||||
|
||||
jumpBack(): RouteStack {
|
||||
const index = this._index - 1;
|
||||
if (index < 0) {
|
||||
return this;
|
||||
}
|
||||
return this._update(index, this._routeNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a route in the navigation stack.
|
||||
*
|
||||
* `index` specifies the route in the stack that should be replaced.
|
||||
* If it's negative, it counts from the back.
|
||||
*/
|
||||
replaceAtIndex(index: number, route: any): RouteStack {
|
||||
invariant(
|
||||
!isRouteEmpty(route),
|
||||
'Must supply route to replace'
|
||||
);
|
||||
|
||||
if (this.get(index) === route) {
|
||||
return this;
|
||||
}
|
||||
|
||||
invariant(this.indexOf(route) === -1, 'route must be unique');
|
||||
|
||||
const size = this._routeNodes.length;
|
||||
if (index < 0) {
|
||||
index += size;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= size) {
|
||||
return this;
|
||||
}
|
||||
|
||||
const routeNodes = this._routeNodes.slice(0);
|
||||
routeNodes[index] = new RouteNode(route);
|
||||
return this._update(index, routeNodes);
|
||||
}
|
||||
|
||||
replacePreviousAndPop(route: any): RouteStack {
|
||||
if (this._index < 1) {
|
||||
// stack is too small.
|
||||
return this;
|
||||
}
|
||||
|
||||
const index = this.indexOf(route);
|
||||
invariant(
|
||||
index === -1 || index === this._index - 1,
|
||||
'route already exists in the stack'
|
||||
);
|
||||
|
||||
return this.replaceAtIndex(this._index - 1, route).popToRoute(route);
|
||||
}
|
||||
|
||||
// Reset
|
||||
|
||||
/**
|
||||
* Replace the current active route with a new route, and pops out
|
||||
* the rest routes after it.
|
||||
*/
|
||||
resetTo(route: any): RouteStack {
|
||||
invariant(!isRouteEmpty(route), 'Must supply route');
|
||||
const index = this.indexOf(route);
|
||||
if (index === this._index) {
|
||||
// Already has this active route.
|
||||
return this;
|
||||
}
|
||||
invariant(index === -1, 'route already exists in the stack');
|
||||
const routeNodes = this._routeNodes.slice(0, this._index);
|
||||
routeNodes.push(new RouteNode(route));
|
||||
return this._update(routeNodes.length - 1, routeNodes);
|
||||
}
|
||||
|
||||
resetRoutes(routes: Array<any>): RouteStack {
|
||||
const index = routes.length - 1;
|
||||
return new RouteStack(index, routes);
|
||||
}
|
||||
|
||||
// Iterations
|
||||
forEach(callback: IterationCallback, context: ?Object): void {
|
||||
this._routeNodes.forEach((node, index) => {
|
||||
callback.call(context, node.route, index, node.key);
|
||||
});
|
||||
}
|
||||
|
||||
mapToArray(callback: IterationCallback, context: ?Object): Array<any> {
|
||||
return this._routeNodes.map((node, index) => {
|
||||
return callback.call(context, node.route, index, node.key);
|
||||
});
|
||||
}
|
||||
|
||||
_update(index: number, routeNodes: Array<RouteNode>): RouteStack {
|
||||
if (
|
||||
this._index === index &&
|
||||
areRouteNodesEqual(this._routeNodes, routeNodes)
|
||||
) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return new RouteStack(index, routeNodes);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RouteStack;
|
||||
-454
@@ -1,454 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('NavigationLegacyNavigatorRouteStack');
|
||||
|
||||
const NavigationLegacyNavigatorRouteStack = require('NavigationLegacyNavigatorRouteStack');
|
||||
|
||||
function assetStringNotEmpty(str) {
|
||||
expect(!!str && typeof str === 'string').toBe(true);
|
||||
}
|
||||
|
||||
describe('NavigationLegacyNavigatorRouteStack:', () => {
|
||||
// Different types of routes.
|
||||
const ROUTES = [
|
||||
'foo',
|
||||
1,
|
||||
true,
|
||||
{foo: 'bar'},
|
||||
['foo'],
|
||||
];
|
||||
|
||||
// Basic
|
||||
it('gets index', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
|
||||
expect(stack.index).toBe(1);
|
||||
});
|
||||
|
||||
it('gets size', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
|
||||
expect(stack.size).toBe(3);
|
||||
});
|
||||
|
||||
it('gets route', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b', 'c']);
|
||||
expect(stack.get(2)).toBe('c');
|
||||
});
|
||||
|
||||
it('converts to an array', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
|
||||
expect(stack.toArray()).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('creates a new stack after mutation', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
|
||||
const stack2 = stack1.push('c');
|
||||
expect(stack1).not.toBe(stack2);
|
||||
});
|
||||
|
||||
it('throws at index out of bound', () => {
|
||||
expect(
|
||||
() => new NavigationLegacyNavigatorRouteStack(-1, ['a', 'b'])
|
||||
).toThrow();
|
||||
|
||||
expect(
|
||||
() => new NavigationLegacyNavigatorRouteStack(100, ['a', 'b'])
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('finds index', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
|
||||
expect(stack.indexOf('b')).toBe(1);
|
||||
expect(stack.indexOf('c')).toBe(-1);
|
||||
});
|
||||
|
||||
// Key
|
||||
it('gets key for route', () => {
|
||||
const test = (route) => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
|
||||
const key = stack.push(route).keyOf(route);
|
||||
expect(typeof key).toBe('string');
|
||||
expect(!!key).toBe(true);
|
||||
};
|
||||
|
||||
ROUTES.forEach(test);
|
||||
});
|
||||
|
||||
it('gets a key of larger value for route', () => {
|
||||
let lastKey = '';
|
||||
const test = (route) => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
|
||||
const key = stack.push(route).keyOf(route);
|
||||
expect(key > lastKey).toBe(true);
|
||||
lastKey = key;
|
||||
};
|
||||
|
||||
ROUTES.forEach(test);
|
||||
});
|
||||
|
||||
it('gets an unique key for a different route', () => {
|
||||
let stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
|
||||
const keys = {};
|
||||
|
||||
const test = (route) => {
|
||||
stack = stack.push(route);
|
||||
const key = stack.keyOf(route);
|
||||
expect(keys[key]).toBe(undefined);
|
||||
keys[key] = true;
|
||||
};
|
||||
|
||||
ROUTES.forEach(test);
|
||||
});
|
||||
|
||||
it('gets the same unique key for the same route', () => {
|
||||
const test = (route) => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, [route]);
|
||||
expect(stack.keyOf(route)).toBe(stack.keyOf(route));
|
||||
};
|
||||
|
||||
ROUTES.forEach(test);
|
||||
});
|
||||
|
||||
|
||||
it('gets the same unique key form the derived stack', () => {
|
||||
const test = (route) => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, [route]);
|
||||
const derivedStack = stack.push('wow').pop().slice(0, 10).push('blah');
|
||||
expect(derivedStack.keyOf(route)).toBe(stack.keyOf(route));
|
||||
};
|
||||
|
||||
ROUTES.forEach(test);
|
||||
});
|
||||
|
||||
it('gets a different key from a different stack', () => {
|
||||
const test = (route) => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(0, [route]);
|
||||
const stack2 = new NavigationLegacyNavigatorRouteStack(0, [route]);
|
||||
expect(stack1.keyOf(route)).not.toBe(stack2.keyOf(route));
|
||||
};
|
||||
|
||||
ROUTES.forEach(test);
|
||||
});
|
||||
|
||||
it('gets no key for a route that does not contains this route', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
|
||||
expect(stack.keyOf('b')).toBe(null);
|
||||
});
|
||||
|
||||
it('gets a new key for a route that was removed and added again', () => {
|
||||
const test = (route) => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
|
||||
|
||||
const key1 = stack.push(route).keyOf(route);
|
||||
const key2 = stack.push(route).pop().push(route).keyOf(route);
|
||||
expect(key1).not.toBe(key2);
|
||||
};
|
||||
|
||||
ROUTES.forEach(test);
|
||||
});
|
||||
|
||||
// Slice
|
||||
it('slices', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c', 'd']);
|
||||
const stack2 = stack1.slice(1, 3);
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['b', 'c']);
|
||||
});
|
||||
|
||||
it('may update index after slicing', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(2, ['a', 'b', 'c']);
|
||||
expect(stack.slice().index).toBe(2);
|
||||
expect(stack.slice(0, 1).index).toBe(0);
|
||||
expect(stack.slice(0, 2).index).toBe(1);
|
||||
expect(stack.slice(0, 3).index).toBe(2);
|
||||
expect(stack.slice(0, 100).index).toBe(2);
|
||||
expect(stack.slice(-2).index).toBe(1);
|
||||
});
|
||||
|
||||
it('slices without specifying params', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
|
||||
const stack2 = stack1.slice();
|
||||
expect(stack2).toBe(stack1);
|
||||
});
|
||||
|
||||
it('slices to from the end', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c', 'd']);
|
||||
const stack2 = stack1.slice(-2);
|
||||
expect(stack2.toArray()).toEqual(['c', 'd']);
|
||||
});
|
||||
|
||||
it('throws when slicing to empty', () => {
|
||||
expect(() => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
stack.slice(100);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
// Push
|
||||
it('pushes route', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
const stack2 = stack1.push('c');
|
||||
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a', 'b', 'c']);
|
||||
expect(stack2.index).toBe(2);
|
||||
expect(stack2.size).toBe(3);
|
||||
});
|
||||
|
||||
it('throws when pushing empty route', () => {
|
||||
expect(() => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
stack.push(null);
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
stack.push('');
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
stack.push(undefined);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('replaces routes on push', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
|
||||
const stack2 = stack1.push('d');
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a', 'b', 'd']);
|
||||
expect(stack2.index).toBe(2);
|
||||
});
|
||||
|
||||
// Pop
|
||||
it('pops route', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(2, ['a', 'b', 'c']);
|
||||
const stack2 = stack1.pop();
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a', 'b']);
|
||||
expect(stack2.index).toBe(1);
|
||||
expect(stack2.size).toBe(2);
|
||||
});
|
||||
|
||||
it('replaces routes on pop', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
|
||||
const stack2 = stack1.pop();
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a']);
|
||||
expect(stack2.index).toBe(0);
|
||||
});
|
||||
|
||||
it('does nothing while popping to empty', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
|
||||
expect(stack.pop()).toBe(stack);
|
||||
expect(stack.pop().pop()).toBe(stack);
|
||||
});
|
||||
|
||||
it('pops to route', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
|
||||
expect(stack.popToRoute('b').toArray()).toEqual(['a', 'b']);
|
||||
expect(stack.popToRoute('b').index).toBe(1);
|
||||
expect(stack.popToRoute('a').toArray()).toEqual(['a']);
|
||||
expect(stack.popToRoute('a').index).toBe(0);
|
||||
|
||||
expect(() => {stack.popToRoute('x');}).toThrow();
|
||||
});
|
||||
|
||||
// Jump
|
||||
it('jumps to index', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b', 'c']);
|
||||
const stack2 = stack1.jumpToIndex(2);
|
||||
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.index).toBe(2);
|
||||
});
|
||||
|
||||
it('throws then jumping to index out of bound', () => {
|
||||
expect(() => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
stack.jumpToIndex(2);
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
stack.jumpToIndex(-1);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('jumps to route', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
|
||||
expect(stack.jumpTo('b').index).toBe(1);
|
||||
});
|
||||
|
||||
it('jumps backward', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
expect(stack.jumpBack().index).toBe(0);
|
||||
expect(stack.jumpBack().jumpBack().jumpBack().index).toBe(0);
|
||||
});
|
||||
|
||||
it('jumps forward', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
|
||||
expect(stack.jumpForward().index).toBe(1);
|
||||
expect(stack.jumpForward().jumpForward().jumpForward().index).toBe(1);
|
||||
});
|
||||
|
||||
|
||||
// Replace
|
||||
it('replaces route at index', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
const stack2 = stack1.replaceAtIndex(0, 'x');
|
||||
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['x', 'b']);
|
||||
expect(stack2.index).toBe(0);
|
||||
});
|
||||
|
||||
it('replaces route at negative index', () => {
|
||||
const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
const stack2 = stack1.replaceAtIndex(-1, 'x');
|
||||
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a', 'x']);
|
||||
expect(stack2.index).toBe(1);
|
||||
});
|
||||
|
||||
it('throws when replacing empty route', () => {
|
||||
expect(() => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
stack.replaceAtIndex(1, null);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('does nothing when replacing at index out of bound', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
|
||||
expect(stack.replaceAtIndex(100, 'x')).toBe(stack);
|
||||
expect(stack.replaceAtIndex(-100, 'x')).toBe(stack);
|
||||
});
|
||||
|
||||
it('replaces previous and pop route', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
|
||||
expect(stack.replacePreviousAndPop('x').toArray()).toEqual(['x']);
|
||||
expect(stack.replacePreviousAndPop('x').index).toBe(0);
|
||||
});
|
||||
|
||||
it('does nothing when there is nothing to replace', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b', 'c']);
|
||||
expect(stack.replacePreviousAndPop('x')).toBe(stack);
|
||||
});
|
||||
|
||||
// Reset
|
||||
|
||||
it('resets route', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
|
||||
expect(stack.resetTo('b')).toBe(stack);
|
||||
|
||||
expect(stack.resetTo('x').toArray()).toEqual(['a', 'x']);
|
||||
expect(stack.resetTo('x').index).toBe(1);
|
||||
|
||||
expect(() => {stack.resetTo(null);}).toThrow();
|
||||
expect(() => {stack.resetTo('a');}).toThrow();
|
||||
});
|
||||
|
||||
it('resets routes', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
|
||||
expect(stack.resetRoutes(['x', 'y']).toArray()).toEqual(['x', 'y']);
|
||||
expect(stack.resetRoutes(['x', 'y']).index).toBe(1);
|
||||
});
|
||||
|
||||
// Iteration
|
||||
it('iterates each item', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
|
||||
const logs = [];
|
||||
const keys = {};
|
||||
const context = {name: 'yo'};
|
||||
|
||||
stack.forEach(function (route, index, key) {
|
||||
assetStringNotEmpty(key);
|
||||
if (!keys.hasOwnProperty(key)) {
|
||||
keys[key] = true;
|
||||
logs.push([
|
||||
route,
|
||||
index,
|
||||
this.name,
|
||||
]);
|
||||
}
|
||||
}, context);
|
||||
|
||||
expect(logs).toEqual([
|
||||
['a', 0, 'yo'],
|
||||
['b', 1, 'yo'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Maps to an array', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
|
||||
const keys = {};
|
||||
const context = {name: 'yo'};
|
||||
|
||||
const logs = stack.mapToArray(function(route, index, key) {
|
||||
assetStringNotEmpty(key);
|
||||
if (!keys.hasOwnProperty(key)) {
|
||||
keys[key] = true;
|
||||
return [
|
||||
route,
|
||||
index,
|
||||
this.name,
|
||||
];
|
||||
}
|
||||
}, context);
|
||||
|
||||
expect(logs).toEqual([
|
||||
['a', 0, 'yo'],
|
||||
['b', 1, 'yo'],
|
||||
]);
|
||||
});
|
||||
|
||||
// Navigation State
|
||||
it('coverts to navigation state', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
|
||||
const state = stack.toNavigationState();
|
||||
expect(state).toEqual({
|
||||
index: 0,
|
||||
key: '0',
|
||||
children:[
|
||||
{key: '0', route: 'a'},
|
||||
{key: '1', route: 'b'},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('coverts from navigation state', () => {
|
||||
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
|
||||
const state = stack.toNavigationState().children[0];
|
||||
const route = NavigationLegacyNavigatorRouteStack.getRouteByNavigationState(state);
|
||||
expect(route).toBe('a');
|
||||
});
|
||||
});
|
||||
@@ -117,7 +117,7 @@ class RouteStack {
|
||||
return this._routeNodes.findIndex(finder, this);
|
||||
}
|
||||
|
||||
slice(begin: ?number, end: ?number): RouteStack {
|
||||
slice(begin?: number, end?: number): RouteStack {
|
||||
var routeNodes = this._routeNodes.slice(begin, end);
|
||||
var index = Math.min(this._index, routeNodes.size - 1);
|
||||
return this._update(index, routeNodes);
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
@@ -446,7 +451,7 @@ var Navigator = React.createClass({
|
||||
this._onAnimationEnd();
|
||||
var presentedIndex = this.state.presentedIndex;
|
||||
var didFocusRoute = this._subRouteFocus[presentedIndex] || this.state.routeStack[presentedIndex];
|
||||
this._emitDidFocus(didFocusRoute);
|
||||
|
||||
if (AnimationsDebugModule) {
|
||||
AnimationsDebugModule.stopRecordingFps(Date.now());
|
||||
}
|
||||
@@ -457,6 +462,9 @@ var Navigator = React.createClass({
|
||||
this.state.transitionCb();
|
||||
this.state.transitionCb = null;
|
||||
}
|
||||
|
||||
this._emitDidFocus(didFocusRoute);
|
||||
|
||||
if (this._interactionHandle) {
|
||||
this.clearInteractionHandle(this._interactionHandle);
|
||||
this._interactionHandle = null;
|
||||
@@ -917,16 +925,15 @@ var Navigator = React.createClass({
|
||||
var activeAnimationConfigStack = this.state.sceneConfigStack.slice(0, activeLength);
|
||||
var nextStack = activeStack.concat([route]);
|
||||
var destIndex = nextStack.length - 1;
|
||||
var nextAnimationConfigStack = activeAnimationConfigStack.concat([
|
||||
this.props.configureScene(route, nextStack),
|
||||
]);
|
||||
var nextSceneConfig = this.props.configureScene(route, nextStack);
|
||||
var nextAnimationConfigStack = activeAnimationConfigStack.concat([nextSceneConfig]);
|
||||
this._emitWillFocus(nextStack[destIndex]);
|
||||
this.setState({
|
||||
routeStack: nextStack,
|
||||
sceneConfigStack: nextAnimationConfigStack,
|
||||
}, () => {
|
||||
this._enableScene(destIndex);
|
||||
this._transitionTo(destIndex);
|
||||
this._transitionTo(destIndex, nextSceneConfig.defaultTransitionVelocity);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -939,11 +946,13 @@ var Navigator = React.createClass({
|
||||
'Cannot pop below zero'
|
||||
);
|
||||
var popIndex = this.state.presentedIndex - n;
|
||||
var presentedRoute = this.state.routeStack[this.state.presentedIndex];
|
||||
var popSceneConfig = this.props.configureScene(presentedRoute); // using the scene config of the currently presented view
|
||||
this._enableScene(popIndex);
|
||||
this._emitWillFocus(this.state.routeStack[popIndex]);
|
||||
this._transitionTo(
|
||||
popIndex,
|
||||
null, // default velocity
|
||||
popSceneConfig.defaultTransitionVelocity,
|
||||
null, // no spring jumping
|
||||
() => {
|
||||
this._cleanScenesPastIndex(popIndex);
|
||||
|
||||
@@ -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 EmitterSubscription
|
||||
* @noflow
|
||||
* @typechecks
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const EventSubscription = require('EventSubscription');
|
||||
|
||||
import type EventEmitter from 'EventEmitter';
|
||||
import type EventSubscriptionVendor from 'EventSubscriptionVendor';
|
||||
|
||||
/**
|
||||
* EmitterSubscription represents a subscription with listener and context data.
|
||||
*/
|
||||
class EmitterSubscription extends EventSubscription {
|
||||
|
||||
emitter: EventEmitter;
|
||||
listener: Function;
|
||||
context: ?Object;
|
||||
|
||||
/**
|
||||
* @param {EventEmitter} emitter - The event emitter that registered this
|
||||
* subscription
|
||||
* @param {EventSubscriptionVendor} subscriber - The subscriber that controls
|
||||
* this subscription
|
||||
* @param {function} listener - Function to invoke when the specified event is
|
||||
* emitted
|
||||
* @param {*} context - Optional context object to use when invoking the
|
||||
* listener
|
||||
*/
|
||||
constructor(
|
||||
emitter: EventEmitter,
|
||||
subscriber: EventSubscriptionVendor,
|
||||
listener: Function,
|
||||
context: ?Object
|
||||
) {
|
||||
super(subscriber);
|
||||
this.emitter = emitter;
|
||||
this.listener = listener;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this subscription from the emitter that registered it.
|
||||
* Note: we're overriding the `remove()` method of EventSubscription here
|
||||
* but deliberately not calling `super.remove()` as the responsibility
|
||||
* for removing the subscription lies with the EventEmitter.
|
||||
*/
|
||||
remove() {
|
||||
this.emitter.removeSubscription(this);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EmitterSubscription;
|
||||
@@ -8,14 +8,13 @@
|
||||
*
|
||||
* @providesModule EventEmitter
|
||||
* @noflow
|
||||
* @typechecks
|
||||
* @typecheck
|
||||
*/
|
||||
|
||||
var EmitterSubscription = require('EmitterSubscription');
|
||||
var ErrorUtils = require('ErrorUtils');
|
||||
var EventSubscriptionVendor = require('EventSubscriptionVendor');
|
||||
var emptyFunction = require('fbjs/lib/emptyFunction');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
const EmitterSubscription = require('EmitterSubscription');
|
||||
const EventSubscriptionVendor = require('EventSubscriptionVendor');
|
||||
const emptyFunction = require('fbjs/lib/emptyFunction');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
/**
|
||||
* @class EventEmitter
|
||||
@@ -31,11 +30,18 @@ var invariant = require('fbjs/lib/invariant');
|
||||
* more advanced emitter may use an EventHolder and EventFactory.
|
||||
*/
|
||||
class EventEmitter {
|
||||
|
||||
_subscriber: EventSubscriptionVendor;
|
||||
_currentSubscription: ?EmitterSubscription;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {EventSubscriptionVendor} subscriber - Optional subscriber instance
|
||||
* to use. If omitted, a new subscriber will be created for the emitter.
|
||||
*/
|
||||
constructor() {
|
||||
this._subscriber = new EventSubscriptionVendor();
|
||||
constructor(subscriber: ?EventSubscriptionVendor) {
|
||||
this._subscriber = subscriber || new EventSubscriptionVendor();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,10 +59,12 @@ class EventEmitter {
|
||||
* listener
|
||||
*/
|
||||
addListener(
|
||||
eventType: String, listener, context: ?Object): EmitterSubscription {
|
||||
return this._subscriber.addSubscription(
|
||||
eventType: string, listener: Function, context: ?Object): EmitterSubscription {
|
||||
|
||||
return (this._subscriber.addSubscription(
|
||||
eventType,
|
||||
new EmitterSubscription(this._subscriber, listener, context));
|
||||
new EmitterSubscription(this, this._subscriber, listener, context)
|
||||
) : any);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,10 +77,9 @@ class EventEmitter {
|
||||
* @param {*} context - Optional context object to use when invoking the
|
||||
* listener
|
||||
*/
|
||||
once(eventType: String, listener, context: ?Object): EmitterSubscription {
|
||||
var emitter = this;
|
||||
return this.addListener(eventType, function() {
|
||||
emitter.removeCurrentListener();
|
||||
once(eventType: string, listener: Function, context: ?Object): EmitterSubscription {
|
||||
return this.addListener(eventType, () => {
|
||||
this.removeCurrentListener();
|
||||
listener.apply(context, arguments);
|
||||
});
|
||||
}
|
||||
@@ -84,7 +91,7 @@ class EventEmitter {
|
||||
* @param {?string} eventType - Optional name of the event whose registered
|
||||
* listeners to remove
|
||||
*/
|
||||
removeAllListeners(eventType: ?String) {
|
||||
removeAllListeners(eventType: ?string) {
|
||||
this._subscriber.removeAllSubscriptions(eventType);
|
||||
}
|
||||
|
||||
@@ -114,7 +121,19 @@ class EventEmitter {
|
||||
!!this._currentSubscription,
|
||||
'Not in an emitting cycle; there is no current subscription'
|
||||
);
|
||||
this._subscriber.removeSubscription(this._currentSubscription);
|
||||
this.removeSubscription(this._currentSubscription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific subscription. Called by the `remove()` method of the
|
||||
* subscription itself to ensure any necessary cleanup is performed.
|
||||
*/
|
||||
removeSubscription(subscription: EmitterSubscription) {
|
||||
invariant(
|
||||
subscription.emitter === this,
|
||||
'Subscription does not belong to this emitter.'
|
||||
);
|
||||
this._subscriber.removeSubscription(subscription);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,8 +143,8 @@ class EventEmitter {
|
||||
* @param {string} eventType - Name of the event to query
|
||||
* @returns {array}
|
||||
*/
|
||||
listeners(eventType: String): Array /* TODO: Array<EventSubscription> */ {
|
||||
var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
|
||||
listeners(eventType: string): [EmitterSubscription] {
|
||||
const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any);
|
||||
return subscriptions
|
||||
? subscriptions.filter(emptyFunction.thatReturnsTrue).map(
|
||||
function(subscription) {
|
||||
@@ -148,13 +167,11 @@ class EventEmitter {
|
||||
*
|
||||
* emitter.emit('someEvent', 'abc'); // logs 'abc'
|
||||
*/
|
||||
emit(eventType: String) {
|
||||
var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
|
||||
emit(eventType: string) {
|
||||
const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any);
|
||||
if (subscriptions) {
|
||||
var keys = Object.keys(subscriptions);
|
||||
for (var ii = 0; ii < keys.length; ii++) {
|
||||
var key = keys[ii];
|
||||
var subscription = subscriptions[key];
|
||||
for (let i = 0, l = subscriptions.length; i < l; i++) {
|
||||
const subscription = subscriptions[i];
|
||||
|
||||
// The subscription may have been removed during this event loop.
|
||||
if (subscription) {
|
||||
+30
-29
@@ -1,22 +1,20 @@
|
||||
/**
|
||||
* @generated SignedSource<<fb2bb5c1c402a097a7e1da7413526629>>
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* 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 EventEmitterWithHolding
|
||||
* @typechecks
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import type EmitterSubscription from 'EmitterSubscription';
|
||||
import type EventEmitter from 'EventEmitter';
|
||||
import type EventHolder from 'EventHolder';
|
||||
|
||||
/**
|
||||
* @class EventEmitterWithHolding
|
||||
* @description
|
||||
@@ -30,6 +28,12 @@
|
||||
* that uses an emitter.
|
||||
*/
|
||||
class EventEmitterWithHolding {
|
||||
|
||||
_emitter: EventEmitter;
|
||||
_eventHolder: EventHolder;
|
||||
_currentEventToken: ?Object;
|
||||
_emittingHeldEvents: boolean;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {object} emitter - The object responsible for emitting the actual
|
||||
@@ -37,7 +41,7 @@ class EventEmitterWithHolding {
|
||||
* @param {object} holder - The event holder that is responsible for holding
|
||||
* and then emitting held events.
|
||||
*/
|
||||
constructor(emitter, holder) {
|
||||
constructor(emitter: EventEmitter, holder: EventHolder) {
|
||||
this._emitter = emitter;
|
||||
this._eventHolder = holder;
|
||||
this._currentEventToken = null;
|
||||
@@ -47,14 +51,14 @@ class EventEmitterWithHolding {
|
||||
/**
|
||||
* @see EventEmitter#addListener
|
||||
*/
|
||||
addListener(eventType: String, listener, context: ?Object) {
|
||||
addListener(eventType: string, listener: Function, context: ?Object) {
|
||||
return this._emitter.addListener(eventType, listener, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EventEmitter#once
|
||||
*/
|
||||
once(eventType: String, listener, context: ?Object) {
|
||||
once(eventType: string, listener: Function, context: ?Object) {
|
||||
return this._emitter.once(eventType, listener, context);
|
||||
}
|
||||
|
||||
@@ -79,8 +83,8 @@ class EventEmitterWithHolding {
|
||||
* }); // logs 'abc'
|
||||
*/
|
||||
addRetroactiveListener(
|
||||
eventType: String, listener, context: ?Object): EmitterSubscription {
|
||||
var subscription = this._emitter.addListener(eventType, listener, context);
|
||||
eventType: string, listener: Function, context: ?Object): EmitterSubscription {
|
||||
const subscription = this._emitter.addListener(eventType, listener, context);
|
||||
|
||||
this._emittingHeldEvents = true;
|
||||
this._eventHolder.emitToListener(eventType, listener, context);
|
||||
@@ -92,7 +96,7 @@ class EventEmitterWithHolding {
|
||||
/**
|
||||
* @see EventEmitter#removeAllListeners
|
||||
*/
|
||||
removeAllListeners(eventType: String) {
|
||||
removeAllListeners(eventType: string) {
|
||||
this._emitter.removeAllListeners(eventType);
|
||||
}
|
||||
|
||||
@@ -106,15 +110,15 @@ class EventEmitterWithHolding {
|
||||
/**
|
||||
* @see EventEmitter#listeners
|
||||
*/
|
||||
listeners(eventType: String) /* TODO: Annotate return type here */ {
|
||||
listeners(eventType: string) /* TODO: Annotate return type here */ {
|
||||
return this._emitter.listeners(eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EventEmitter#emit
|
||||
*/
|
||||
emit(eventType: String, a, b, c, d, e, _) {
|
||||
this._emitter.emit(eventType, a, b, c, d, e, _);
|
||||
emit(eventType: string, ...args: any) {
|
||||
this._emitter.emit(eventType, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,12 +136,9 @@ class EventEmitterWithHolding {
|
||||
* console.log(message);
|
||||
* }); // logs 'abc'
|
||||
*/
|
||||
emitAndHold(eventType: String, a, b, c, d, e, _) {
|
||||
this._currentEventToken = this._eventHolder.holdEvent(
|
||||
eventType,
|
||||
a, b, c, d, e, _
|
||||
);
|
||||
this._emitter.emit(eventType, a, b, c, d, e, _);
|
||||
emitAndHold(eventType: string, ...args: any) {
|
||||
this._currentEventToken = this._eventHolder.holdEvent(eventType, ...args);
|
||||
this._emitter.emit(eventType, ...args);
|
||||
this._currentEventToken = null;
|
||||
}
|
||||
|
||||
@@ -145,7 +146,7 @@ class EventEmitterWithHolding {
|
||||
* @see EventHolder#releaseCurrentEvent
|
||||
*/
|
||||
releaseCurrentEvent() {
|
||||
if (this._currentEventToken !== null) {
|
||||
if (this._currentEventToken) {
|
||||
this._eventHolder.releaseEvent(this._currentEventToken);
|
||||
} else if (this._emittingHeldEvents) {
|
||||
this._eventHolder.releaseCurrentEvent();
|
||||
@@ -156,7 +157,7 @@ class EventEmitterWithHolding {
|
||||
* @see EventHolder#releaseEventType
|
||||
* @param {string} eventType
|
||||
*/
|
||||
releaseHeldEventType(eventType: String) {
|
||||
releaseHeldEventType(eventType: string) {
|
||||
this._eventHolder.releaseEventType(eventType);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,23 @@
|
||||
/**
|
||||
* @generated SignedSource<<0591836c443c735d24e61782320d3d16>>
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* 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 EventHolder
|
||||
* @typechecks
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
class EventHolder {
|
||||
|
||||
_heldEvents: Object;
|
||||
_currentEventKey: ?Object;
|
||||
|
||||
constructor() {
|
||||
this._heldEvents = {};
|
||||
this._currentEventKey = null;
|
||||
@@ -46,14 +44,14 @@ class EventHolder {
|
||||
* }); //logs 'abc'
|
||||
*
|
||||
*/
|
||||
holdEvent(eventType: String, a, b, c, d, e, _) {
|
||||
holdEvent(eventType: string, ...args: any) {
|
||||
this._heldEvents[eventType] = this._heldEvents[eventType] || [];
|
||||
var eventsOfType = this._heldEvents[eventType];
|
||||
var key = {
|
||||
const eventsOfType = this._heldEvents[eventType];
|
||||
const key = {
|
||||
eventType: eventType,
|
||||
index: eventsOfType.length
|
||||
};
|
||||
eventsOfType.push([a, b, c, d, e, _]);
|
||||
eventsOfType.push(args);
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -65,12 +63,12 @@ class EventHolder {
|
||||
* @param {?object} context - Optional context object to use when invoking
|
||||
* the listener
|
||||
*/
|
||||
emitToListener(eventType: ?String , listener, context: ?Object) {
|
||||
var eventsOfType = this._heldEvents[eventType];
|
||||
emitToListener(eventType: ?string , listener: Function, context: ?Object) {
|
||||
const eventsOfType = this._heldEvents[eventType];
|
||||
if (!eventsOfType) {
|
||||
return;
|
||||
}
|
||||
var origEventKey = this._currentEventKey;
|
||||
const origEventKey = this._currentEventKey;
|
||||
eventsOfType.forEach((/*?array*/ eventHeld, /*number*/ index) => {
|
||||
if (!eventHeld) {
|
||||
return;
|
||||
@@ -97,7 +95,7 @@ class EventHolder {
|
||||
this._currentEventKey !== null,
|
||||
'Not in an emitting cycle; there is no current event'
|
||||
);
|
||||
this.releaseEvent(this._currentEventKey);
|
||||
this._currentEventKey && this.releaseEvent(this._currentEventKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +113,7 @@ class EventHolder {
|
||||
*
|
||||
* @param {string} type
|
||||
*/
|
||||
releaseEventType(type: String) {
|
||||
releaseEventType(type: string) {
|
||||
this._heldEvents[type] = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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 EventSubscription
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import type EventSubscriptionVendor from 'EventSubscriptionVendor';
|
||||
|
||||
/**
|
||||
* EventSubscription represents a subscription to a particular event. It can
|
||||
* remove its own subscription.
|
||||
*/
|
||||
class EventSubscription {
|
||||
|
||||
eventType: string;
|
||||
key: number;
|
||||
subscriber: EventSubscriptionVendor;
|
||||
|
||||
/**
|
||||
* @param {EventSubscriptionVendor} subscriber the subscriber that controls
|
||||
* this subscription.
|
||||
*/
|
||||
constructor(subscriber: EventSubscriptionVendor) {
|
||||
this.subscriber = subscriber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this subscription from the subscriber that controls it.
|
||||
*/
|
||||
remove() {
|
||||
this.subscriber.removeSubscription(this);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EventSubscription;
|
||||
+19
-20
@@ -1,23 +1,19 @@
|
||||
/**
|
||||
* @generated SignedSource<<24d5cc1cdd24704296686faf89dd36cf>>
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* 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 EventSubscriptionVendor
|
||||
* @typechecks
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type EventSubscription from 'EventSubscription';
|
||||
|
||||
/**
|
||||
* EventSubscriptionVendor stores a set of EventSubscriptions that are
|
||||
@@ -25,6 +21,9 @@ var invariant = require('fbjs/lib/invariant');
|
||||
*/
|
||||
class EventSubscriptionVendor {
|
||||
|
||||
_subscriptionsForType: Object;
|
||||
_currentSubscription: ?EventSubscription;
|
||||
|
||||
constructor() {
|
||||
this._subscriptionsForType = {};
|
||||
this._currentSubscription = null;
|
||||
@@ -37,14 +36,14 @@ class EventSubscriptionVendor {
|
||||
* @param {EventSubscription} subscription
|
||||
*/
|
||||
addSubscription(
|
||||
eventType: String, subscription: EventSubscription): EventSubscription {
|
||||
eventType: string, subscription: EventSubscription): EventSubscription {
|
||||
invariant(
|
||||
subscription.subscriber === this,
|
||||
'The subscriber of the subscription is incorrectly set.');
|
||||
if (!this._subscriptionsForType[eventType]) {
|
||||
this._subscriptionsForType[eventType] = [];
|
||||
}
|
||||
var key = this._subscriptionsForType[eventType].length;
|
||||
const key = this._subscriptionsForType[eventType].length;
|
||||
this._subscriptionsForType[eventType].push(subscription);
|
||||
subscription.eventType = eventType;
|
||||
subscription.key = key;
|
||||
@@ -57,7 +56,7 @@ class EventSubscriptionVendor {
|
||||
* @param {?string} eventType - Optional name of the event type whose
|
||||
* registered supscriptions to remove, if null remove all subscriptions.
|
||||
*/
|
||||
removeAllSubscriptions(eventType: ?String) {
|
||||
removeAllSubscriptions(eventType: ?string) {
|
||||
if (eventType === undefined) {
|
||||
this._subscriptionsForType = {};
|
||||
} else {
|
||||
@@ -72,10 +71,10 @@ class EventSubscriptionVendor {
|
||||
* @param {object} subscription
|
||||
*/
|
||||
removeSubscription(subscription: Object) {
|
||||
var eventType = subscription.eventType;
|
||||
var key = subscription.key;
|
||||
const eventType = subscription.eventType;
|
||||
const key = subscription.key;
|
||||
|
||||
var subscriptionsForType = this._subscriptionsForType[eventType];
|
||||
const subscriptionsForType = this._subscriptionsForType[eventType];
|
||||
if (subscriptionsForType) {
|
||||
delete subscriptionsForType[key];
|
||||
}
|
||||
@@ -93,7 +92,7 @@ class EventSubscriptionVendor {
|
||||
* @param {string} eventType
|
||||
* @returns {?array}
|
||||
*/
|
||||
getSubscriptionsForType(eventType: String): ?Array {
|
||||
getSubscriptionsForType(eventType: string): ?[EventSubscription] {
|
||||
return this._subscriptionsForType[eventType];
|
||||
}
|
||||
}
|
||||
+20
-25
@@ -1,22 +1,17 @@
|
||||
/**
|
||||
* @generated SignedSource<<7149bdac6fb48595f245ad6e76938e44>>
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* 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 EventValidator
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var copyProperties = require('copyProperties');
|
||||
const copyProperties = require('copyProperties');
|
||||
|
||||
/**
|
||||
* EventValidator is designed to validate event types to make it easier to catch
|
||||
@@ -27,7 +22,7 @@ var copyProperties = require('copyProperties');
|
||||
* mistyped the event name it will suggest what you might have meant to type in
|
||||
* the error message.
|
||||
*/
|
||||
var EventValidator = {
|
||||
const EventValidator = {
|
||||
/**
|
||||
* @param {Object} emitter - The object responsible for emitting the actual
|
||||
* events
|
||||
@@ -35,12 +30,12 @@ var EventValidator = {
|
||||
* check for errors
|
||||
* @return {Object} A new emitter with event type validation
|
||||
* @example
|
||||
* var types = {someEvent: true, anotherEvent: true};
|
||||
* var emitter = EventValidator.addValidation(emitter, types);
|
||||
* const types = {someEvent: true, anotherEvent: true};
|
||||
* const emitter = EventValidator.addValidation(emitter, types);
|
||||
*/
|
||||
addValidation: function(emitter: Object, types: Object) {
|
||||
var eventTypes = Object.keys(types);
|
||||
var emitterWithValidation = Object.create(emitter);
|
||||
const eventTypes = Object.keys(types);
|
||||
const emitterWithValidation = Object.create(emitter);
|
||||
|
||||
copyProperties(emitterWithValidation, {
|
||||
emit: function emit(type, a, b, c, d, e, _) {
|
||||
@@ -60,7 +55,7 @@ function assertAllowsEventType(type, allowedTypes) {
|
||||
}
|
||||
|
||||
function errorMessageFor(type, allowedTypes) {
|
||||
var message = 'Unknown event type "' + type + '". ';
|
||||
let message = 'Unknown event type "' + type + '". ';
|
||||
if (__DEV__) {
|
||||
message += recommendationFor(type, allowedTypes);
|
||||
}
|
||||
@@ -71,7 +66,7 @@ function errorMessageFor(type, allowedTypes) {
|
||||
// Allow for good error messages
|
||||
if (__DEV__) {
|
||||
var recommendationFor = function (type, allowedTypes) {
|
||||
var closestTypeRecommendation = closestTypeFor(type, allowedTypes);
|
||||
const closestTypeRecommendation = closestTypeFor(type, allowedTypes);
|
||||
if (isCloseEnough(closestTypeRecommendation, type)) {
|
||||
return 'Did you mean "' + closestTypeRecommendation.type + '"? ';
|
||||
} else {
|
||||
@@ -80,7 +75,7 @@ if (__DEV__) {
|
||||
};
|
||||
|
||||
var closestTypeFor = function (type, allowedTypes) {
|
||||
var typeRecommendations = allowedTypes.map(
|
||||
const typeRecommendations = allowedTypes.map(
|
||||
typeRecommendationFor.bind(this, type)
|
||||
);
|
||||
return typeRecommendations.sort(recommendationSort)[0];
|
||||
@@ -108,8 +103,8 @@ if (__DEV__) {
|
||||
};
|
||||
|
||||
var damerauLevenshteinDistance = function (a, b) {
|
||||
var i, j;
|
||||
var d = [];
|
||||
let i, j;
|
||||
const d = [];
|
||||
|
||||
for (i = 0; i <= a.length; i++) {
|
||||
d[i] = [i];
|
||||
@@ -121,7 +116,7 @@ if (__DEV__) {
|
||||
|
||||
for (i = 1; i <= a.length; i++) {
|
||||
for (j = 1; j <= b.length; j++) {
|
||||
var cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
|
||||
const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
|
||||
|
||||
d[i][j] = Math.min(
|
||||
d[i - 1][j] + 1,
|
||||
@@ -130,8 +125,8 @@ if (__DEV__) {
|
||||
);
|
||||
|
||||
if (i > 1 && j > 1 &&
|
||||
a.charAt(i - 1) == b.charAt(j - 2) &&
|
||||
a.charAt(i - 2) == b.charAt(j - 1)) {
|
||||
a.charAt(i - 1) === b.charAt(j - 2) &&
|
||||
a.charAt(i - 2) === b.charAt(j - 1)) {
|
||||
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 NativeEventEmitter
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('EventEmitter');
|
||||
const Platform = require('Platform');
|
||||
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type EmitterSubscription from 'EmitterSubscription';
|
||||
|
||||
/**
|
||||
* Abstract base class for implementing event-emitting modules. This implements
|
||||
* a subset of the standard EventEmitter node module API.
|
||||
*/
|
||||
class NativeEventEmitter extends EventEmitter {
|
||||
|
||||
_nativeModule: Object;
|
||||
|
||||
constructor(nativeModule: Object) {
|
||||
super(RCTDeviceEventEmitter.sharedSubscriber);
|
||||
if (Platform.OS === 'ios') {
|
||||
invariant(nativeModule, 'Native module cannot be null.');
|
||||
this._nativeModule = nativeModule;
|
||||
}
|
||||
}
|
||||
|
||||
addListener(eventType: string, listener: Function, context: ?Object): EmitterSubscription {
|
||||
if (Platform.OS === 'ios') {
|
||||
this._nativeModule.addListener(eventType);
|
||||
}
|
||||
return super.addListener(eventType, listener, context);
|
||||
}
|
||||
|
||||
removeAllListeners(eventType: string) {
|
||||
invariant(eventType, 'eventType argument is required.');
|
||||
if (Platform.OS === 'ios') {
|
||||
const count = this.listeners(eventType).length;
|
||||
this._nativeModule.removeListeners(count);
|
||||
}
|
||||
super.removeAllListeners(eventType);
|
||||
}
|
||||
|
||||
removeSubscription(subscription: EmitterSubscription) {
|
||||
if (Platform.OS === 'ios') {
|
||||
this._nativeModule.removeListeners(1);
|
||||
}
|
||||
super.removeSubscription(subscription);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NativeEventEmitter;
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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 RCTDeviceEventEmitter
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('EventEmitter');
|
||||
const EventSubscriptionVendor = require('EventSubscriptionVendor');
|
||||
const BatchedBridge = require('BatchedBridge');
|
||||
|
||||
import type EmitterSubscription from 'EmitterSubscription';
|
||||
|
||||
/**
|
||||
* Deprecated - subclass NativeEventEmitter to create granular event modules instead of
|
||||
* adding all event listeners directly to RCTDeviceEventEmitter.
|
||||
*/
|
||||
class RCTDeviceEventEmitter extends EventEmitter {
|
||||
|
||||
sharedSubscriber: EventSubscriptionVendor;
|
||||
|
||||
constructor() {
|
||||
const sharedSubscriber = new EventSubscriptionVendor();
|
||||
super(sharedSubscriber);
|
||||
this.sharedSubscriber = sharedSubscriber;
|
||||
}
|
||||
|
||||
addListener(eventType: string, listener: Function, context: ?Object): EmitterSubscription {
|
||||
if (eventType.lastIndexOf('statusBar', 0) === 0) {
|
||||
console.warn('`%s` event should be registered via the StatusBarIOS module', eventType);
|
||||
return require('StatusBarIOS').addListener(eventType, listener, context);
|
||||
}
|
||||
if (eventType.lastIndexOf('keyboard', 0) === 0) {
|
||||
console.warn('`%s` event should be registered via the Keyboard module', eventType);
|
||||
return require('Keyboard').addListener(eventType, listener, context);
|
||||
}
|
||||
return super.addListener(eventType, listener, context);
|
||||
}
|
||||
|
||||
removeAllListeners(eventType: ?string) {
|
||||
if (eventType) {
|
||||
if (eventType.lastIndexOf('statusBar', 0) === 0) {
|
||||
console.warn('statusBar events should be unregistered via the StatusBarIOS module');
|
||||
return require('StatusBarIOS').removeAllListeners(eventType);
|
||||
}
|
||||
if (eventType.lastIndexOf('keyboard', 0) === 0) {
|
||||
console.warn('keyboard events should be unregistered via the Keyboard module');
|
||||
return require('Keyboard').removeAllListeners(eventType);
|
||||
}
|
||||
}
|
||||
super.removeAllListeners(eventType);
|
||||
}
|
||||
|
||||
removeSubscription(subscription: EmitterSubscription) {
|
||||
if (subscription.emitter !== this) {
|
||||
subscription.emitter.removeSubscription(subscription);
|
||||
} else {
|
||||
super.removeSubscription(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RCTDeviceEventEmitter = new RCTDeviceEventEmitter();
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTDeviceEventEmitter',
|
||||
RCTDeviceEventEmitter
|
||||
);
|
||||
|
||||
module.exports = RCTDeviceEventEmitter;
|
||||
+7
-3
@@ -11,10 +11,14 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
var EventEmitter = require('EventEmitter');
|
||||
const BatchedBridge = require('BatchedBridge');
|
||||
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
|
||||
|
||||
var RCTNativeAppEventEmitter = new EventEmitter();
|
||||
/**
|
||||
* Deprecated - subclass NativeEventEmitter to create granular event modules instead of
|
||||
* adding all event listeners directly to RCTNativeAppEventEmitter.
|
||||
*/
|
||||
const RCTNativeAppEventEmitter = RCTDeviceEventEmitter;
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTNativeAppEventEmitter',
|
||||
+20
-25
@@ -1,30 +1,25 @@
|
||||
/**
|
||||
* @generated SignedSource<<c735038726af2daf584b3e7fb3950e8b>>
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* 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 mixInEventEmitter
|
||||
* @flow
|
||||
*/
|
||||
|
||||
var EventEmitter = require('EventEmitter');
|
||||
var EventEmitterWithHolding = require('EventEmitterWithHolding');
|
||||
var EventHolder = require('EventHolder');
|
||||
var EventValidator = require('EventValidator');
|
||||
const EventEmitter = require('EventEmitter');
|
||||
const EventEmitterWithHolding = require('EventEmitterWithHolding');
|
||||
const EventHolder = require('EventHolder');
|
||||
const EventValidator = require('EventValidator');
|
||||
|
||||
var copyProperties = require('copyProperties');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var keyOf = require('fbjs/lib/keyOf');
|
||||
const copyProperties = require('copyProperties');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const keyOf = require('fbjs/lib/keyOf');
|
||||
|
||||
var TYPES_KEY = keyOf({__types: true});
|
||||
const TYPES_KEY = keyOf({__types: true});
|
||||
|
||||
/**
|
||||
* API to setup an object or constructor to be able to emit data events.
|
||||
@@ -47,16 +42,16 @@ var TYPES_KEY = keyOf({__types: true});
|
||||
* mixInEventEmitter(Singleton, {lonely: true});
|
||||
* Singleton.emit('lonely', true);
|
||||
*/
|
||||
function mixInEventEmitter(klass, types) {
|
||||
function mixInEventEmitter(cls: Function | Object, types: Object) {
|
||||
invariant(types, 'Must supply set of valid event types');
|
||||
|
||||
// If this is a constructor, write to the prototype, otherwise write to the
|
||||
// singleton object.
|
||||
var target = klass.prototype || klass;
|
||||
const target = cls.prototype || cls;
|
||||
|
||||
invariant(!target.__eventEmitter, 'An active emitter is already mixed in');
|
||||
|
||||
var ctor = klass.constructor;
|
||||
const ctor = cls.constructor;
|
||||
if (ctor) {
|
||||
invariant(
|
||||
ctor === Object || ctor === Function,
|
||||
@@ -76,7 +71,7 @@ function mixInEventEmitter(klass, types) {
|
||||
copyProperties(target, EventEmitterMixin);
|
||||
}
|
||||
|
||||
var EventEmitterMixin = {
|
||||
const EventEmitterMixin = {
|
||||
emit: function(eventType, a, b, c, d, e, _) {
|
||||
return this.__getEventEmitter().emit(eventType, a, b, c, d, e, _);
|
||||
},
|
||||
@@ -123,10 +118,10 @@ var EventEmitterMixin = {
|
||||
|
||||
__getEventEmitter: function() {
|
||||
if (!this.__eventEmitter) {
|
||||
var emitter = new EventEmitter();
|
||||
let emitter = new EventEmitter();
|
||||
emitter = EventValidator.addValidation(emitter, this.__types);
|
||||
|
||||
var holder = new EventHolder();
|
||||
const holder = new EventHolder();
|
||||
this.__eventEmitter = new EventEmitterWithHolding(emitter, holder);
|
||||
}
|
||||
return this.__eventEmitter;
|
||||
@@ -14,6 +14,8 @@
|
||||
const InteractionManager = require('InteractionManager');
|
||||
const React = require('React');
|
||||
|
||||
const infoLog = require('infoLog');
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
/**
|
||||
@@ -121,12 +123,12 @@ class Incremental extends React.Component<DefaultProps, Props, State> {
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
var ctx = this.context.incrementalGroup || {};
|
||||
const ctx = this.context.incrementalGroup || {};
|
||||
return ctx.groupId + ':' + this._incrementId + '-' + this.props.name;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
var ctx = this.context.incrementalGroup;
|
||||
const ctx = this.context.incrementalGroup;
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
@@ -134,15 +136,15 @@ class Incremental extends React.Component<DefaultProps, Props, State> {
|
||||
InteractionManager.runAfterInteractions({
|
||||
name: 'Incremental:' + this.getName(),
|
||||
gen: () => new Promise(resolve => {
|
||||
if (!this._mounted) {
|
||||
if (!this._mounted || this._rendered) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
DEBUG && console.log('set doIncrementalRender for ' + this.getName());
|
||||
DEBUG && infoLog('set doIncrementalRender for ' + this.getName());
|
||||
this.setState({doIncrementalRender: true}, resolve);
|
||||
}),
|
||||
}).then(() => {
|
||||
DEBUG && console.log('call onDone for ' + this.getName());
|
||||
DEBUG && infoLog('call onDone for ' + this.getName());
|
||||
this._mounted && this.props.onDone && this.props.onDone();
|
||||
}).catch((ex) => {
|
||||
ex.message = `Incremental render failed for ${this.getName()}: ${ex.message}`;
|
||||
@@ -154,7 +156,7 @@ class Incremental extends React.Component<DefaultProps, Props, State> {
|
||||
if (this._rendered || // Make sure that once we render once, we stay rendered even if incrementalGroupEnabled gets flipped.
|
||||
!this.context.incrementalGroupEnabled ||
|
||||
this.state.doIncrementalRender) {
|
||||
DEBUG && console.log('render ' + this.getName());
|
||||
DEBUG && infoLog('render ' + this.getName());
|
||||
this._rendered = true;
|
||||
return this.props.children;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
const Incremental = require('Incremental');
|
||||
const React = require('React');
|
||||
|
||||
const infoLog = require('infoLog');
|
||||
|
||||
let _groupCounter = -1;
|
||||
const DEBUG = false;
|
||||
|
||||
@@ -31,12 +33,12 @@ import type {Props, Context} from 'Incremental';
|
||||
* See Incremental.js for more info.
|
||||
*/
|
||||
class IncrementalGroup extends React.Component {
|
||||
props: Props;
|
||||
props: Props & {disabled?: boolean};
|
||||
context: Context;
|
||||
_groupInc: string;
|
||||
componentWillMount() {
|
||||
this._groupInc = `g${++_groupCounter}-`;
|
||||
DEBUG && console.log(
|
||||
DEBUG && infoLog(
|
||||
'create IncrementalGroup with id ' + this.getGroupId()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
*
|
||||
* @providesModule SwipeableListView
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const ListView = require('ListView');
|
||||
const React = require('React');
|
||||
const SwipeableListViewDataSource = require('SwipeableListViewDataSource');
|
||||
const SwipeableRow = require('SwipeableRow');
|
||||
|
||||
const {PropTypes} = React;
|
||||
|
||||
/**
|
||||
* A container component that renders multiple SwipeableRow's in a ListView
|
||||
* implementation.
|
||||
*/
|
||||
const SwipeableListView = React.createClass({
|
||||
statics: {
|
||||
getNewDataSource(): Object {
|
||||
return new SwipeableListViewDataSource({
|
||||
getRowData: (data, sectionID, rowID) => data[rowID],
|
||||
getSectionHeaderData: (data, sectionID) => data[sectionID],
|
||||
sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
|
||||
rowHasChanged: (row1, row2) => row1 !== row2,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
_listViewRef: (null: ?string),
|
||||
|
||||
propTypes: {
|
||||
dataSource: PropTypes.instanceOf(SwipeableListViewDataSource).isRequired,
|
||||
maxSwipeDistance: PropTypes.number,
|
||||
// Callback method to render the swipeable view
|
||||
renderRow: PropTypes.func.isRequired,
|
||||
// Callback method to render the view that will be unveiled on swipe
|
||||
renderQuickActions: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getDefaultProps(): Object {
|
||||
return {
|
||||
renderQuickActions: () => null,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState(): Object {
|
||||
return {
|
||||
dataSource: this.props.dataSource,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps(nextProps: Object): void {
|
||||
if (
|
||||
this.state.dataSource.getDataSource() !== nextProps.dataSource.getDataSource()
|
||||
) {
|
||||
this.setState({
|
||||
dataSource: nextProps.dataSource,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render(): ReactElement {
|
||||
return (
|
||||
<ListView
|
||||
{...this.props}
|
||||
ref={(ref) => {
|
||||
this._listViewRef = ref;
|
||||
}}
|
||||
dataSource={this.state.dataSource.getDataSource()}
|
||||
renderRow={this._renderRow}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is a work-around to lock vertical `ListView` scrolling on iOS and
|
||||
* mimic Android behaviour. Locking vertical scrolling when horizontal
|
||||
* scrolling is active allows us to significantly improve framerates
|
||||
* (from high 20s to almost consistently 60 fps)
|
||||
*/
|
||||
_setListViewScrollable(value: boolean): void {
|
||||
if (this._listViewRef && this._listViewRef.setNativeProps) {
|
||||
this._listViewRef.setNativeProps({
|
||||
scrollEnabled: value,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Passing through ListView's getScrollResponder() function
|
||||
getScrollResponder(): ?Object {
|
||||
if (this._listViewRef && this._listViewRef.getScrollResponder) {
|
||||
return this._listViewRef.getScrollResponder();
|
||||
}
|
||||
},
|
||||
|
||||
_renderRow(rowData: Object, sectionID: string, rowID: string): ReactElement {
|
||||
const slideoutView = this.props.renderQuickActions(rowData, sectionID, rowID);
|
||||
|
||||
// If renderRowSlideout is unspecified or returns falsey, don't allow swipe
|
||||
if (!slideoutView) {
|
||||
return this.props.renderRow(rowData, sectionID, rowID);
|
||||
}
|
||||
|
||||
return (
|
||||
<SwipeableRow
|
||||
slideoutView={slideoutView}
|
||||
isOpen={rowData.id === this.props.dataSource.getOpenRowID()}
|
||||
maxSwipeDistance={this.props.maxSwipeDistance}
|
||||
key={rowID}
|
||||
onOpen={() => this._onOpen(rowData.id)}
|
||||
onSwipeEnd={() => this._setListViewScrollable(true)}
|
||||
onSwipeStart={() => this._setListViewScrollable(false)}>
|
||||
{this.props.renderRow(rowData, sectionID, rowID)}
|
||||
</SwipeableRow>
|
||||
);
|
||||
},
|
||||
|
||||
_onOpen(rowID: string): void {
|
||||
this.setState({
|
||||
dataSource: this.state.dataSource.setOpenRowID(rowID),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = SwipeableListView;
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
*
|
||||
* @providesModule SwipeableListViewDataSource
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const ListViewDataSource = require('ListViewDataSource');
|
||||
|
||||
/**
|
||||
* Data source wrapper around ListViewDataSource to allow for tracking of
|
||||
* which row is swiped open and close opened row(s) when another row is swiped
|
||||
* open.
|
||||
*
|
||||
* See https://github.com/facebook/react-native/pull/5602 for why
|
||||
* ListViewDataSource is not subclassed.
|
||||
*/
|
||||
class SwipeableListViewDataSource {
|
||||
_previousOpenRowID: string;
|
||||
_openRowID: string;
|
||||
|
||||
_dataBlob: any;
|
||||
_dataSource: ListViewDataSource;
|
||||
|
||||
rowIdentities: Array<Array<string>>;
|
||||
sectionIdentities: Array<string>;
|
||||
|
||||
constructor(params: Object) {
|
||||
this._dataSource = new ListViewDataSource({
|
||||
getRowData: params.getRowData,
|
||||
getSectionHeaderData: params.getSectionHeaderData,
|
||||
rowHasChanged: (row1, row2) => {
|
||||
/**
|
||||
* Row needs to be re-rendered if its swiped open/close status is
|
||||
* changed, or its data blob changed.
|
||||
*/
|
||||
return (
|
||||
(row1.id !== this._previousOpenRowID && row2.id === this._openRowID) ||
|
||||
(row1.id === this._previousOpenRowID && row2.id !== this._openRowID) ||
|
||||
params.rowHasChanged(row1, row2)
|
||||
);
|
||||
},
|
||||
sectionHeaderHasChanged: params.sectionHeaderHasChanged,
|
||||
});
|
||||
}
|
||||
|
||||
cloneWithRowsAndSections(
|
||||
dataBlob: any,
|
||||
sectionIdentities: ?Array<string>,
|
||||
rowIdentities: ?Array<Array<string>>
|
||||
): SwipeableListViewDataSource {
|
||||
this._dataSource = this._dataSource.cloneWithRowsAndSections(
|
||||
dataBlob,
|
||||
sectionIdentities,
|
||||
rowIdentities
|
||||
);
|
||||
|
||||
this._dataBlob = dataBlob;
|
||||
this.rowIdentities = this._dataSource.rowIdentities;
|
||||
this.sectionIdentities = this._dataSource.sectionIdentities;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// For the actual ListView to use
|
||||
getDataSource(): ListViewDataSource {
|
||||
return this._dataSource;
|
||||
}
|
||||
|
||||
getOpenRowID(): ?string {
|
||||
return this._openRowID;
|
||||
}
|
||||
|
||||
setOpenRowID(rowID: string): SwipeableListViewDataSource {
|
||||
this._previousOpenRowID = this._openRowID;
|
||||
this._openRowID = rowID;
|
||||
|
||||
this._dataSource = this._dataSource.cloneWithRowsAndSections(
|
||||
this._dataBlob,
|
||||
this.sectionIdentities,
|
||||
this.rowIdentities
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SwipeableListViewDataSource;
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
*
|
||||
* @providesModule SwipeableQuickActionButton
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Image = require('Image');
|
||||
const React = require('React');
|
||||
const Text = require('Text');
|
||||
const TouchableHighlight = require('TouchableHighlight');
|
||||
const View = require('View');
|
||||
|
||||
const {PropTypes} = React;
|
||||
|
||||
const SwipeableQuickActionButton = React.createClass({
|
||||
propTypes: {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
imageSource: Image.propTypes.source.isRequired,
|
||||
imageStyle: Image.propTypes.style,
|
||||
onPress: PropTypes.func,
|
||||
style: View.propTypes.style,
|
||||
testID: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
textStyle: Text.propTypes.style,
|
||||
},
|
||||
|
||||
render(): ?ReactElement {
|
||||
if (!this.props.imageSource && !this.props.text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
onPress={this.props.onPress}
|
||||
testID={this.props.testID}
|
||||
underlayColor="transparent">
|
||||
<View style={this.props.style}>
|
||||
<Image
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
source={this.props.imageSource}
|
||||
style={this.props.imageStyle}
|
||||
/>
|
||||
<Text style={this.props.textStyle}>
|
||||
{this.props.text}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = SwipeableQuickActionButton;
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
*
|
||||
* @providesModule SwipeableQuickActions
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
const MAX_QUICK_ACTIONS = 2;
|
||||
|
||||
const SwipeableQuickActions = React.createClass({
|
||||
propTypes: {
|
||||
style: View.propTypes.style,
|
||||
},
|
||||
|
||||
render(): ReactElement {
|
||||
const children = this.props.children;
|
||||
let buttons = [];
|
||||
|
||||
// Multiple children
|
||||
if (children instanceof Array) {
|
||||
for (let i = 0; i < children.length && i < MAX_QUICK_ACTIONS; i++) {
|
||||
buttons.push(children[i]);
|
||||
|
||||
if (i < this.props.children.length - 1) { // Not last button
|
||||
buttons.push(<View key={i} style={styles.divider} />);
|
||||
}
|
||||
}
|
||||
} else { // 1 child
|
||||
buttons = children;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.background, this.props.style]}>
|
||||
{buttons}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
background: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
divider: {
|
||||
width: 4,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = SwipeableQuickActions;
|
||||
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
*
|
||||
* @providesModule SwipeableRow
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animated = require('Animated');
|
||||
const PanResponder = require('PanResponder');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
const {PropTypes} = React;
|
||||
|
||||
const emptyFunction = require('emptyFunction');
|
||||
|
||||
// Position of the left of the swipable item when closed
|
||||
const CLOSED_LEFT_POSITION = 0;
|
||||
// Minimum swipe distance before we recognize it as such
|
||||
const HORIZONTAL_SWIPE_DISTANCE_THRESHOLD = 15;
|
||||
|
||||
/**
|
||||
* Creates a swipable row that allows taps on the main item and a custom View
|
||||
* on the item hidden behind the row
|
||||
*/
|
||||
const SwipeableRow = React.createClass({
|
||||
_panResponder: {},
|
||||
_previousLeft: CLOSED_LEFT_POSITION,
|
||||
|
||||
propTypes: {
|
||||
isOpen: PropTypes.bool,
|
||||
maxSwipeDistance: PropTypes.number.isRequired,
|
||||
onOpen: PropTypes.func,
|
||||
onSwipeEnd: PropTypes.func.isRequired,
|
||||
onSwipeStart: PropTypes.func.isRequired,
|
||||
/**
|
||||
* A ReactElement that is unveiled when the user swipes
|
||||
*/
|
||||
slideoutView: PropTypes.node.isRequired,
|
||||
/**
|
||||
* The minimum swipe distance required before fully animating the swipe. If
|
||||
* the user swipes less than this distance, the item will return to its
|
||||
* previous (open/close) position.
|
||||
*/
|
||||
swipeThreshold: PropTypes.number.isRequired,
|
||||
},
|
||||
|
||||
getInitialState(): Object {
|
||||
return {
|
||||
currentLeft: new Animated.Value(this._previousLeft),
|
||||
/**
|
||||
* In order to render component A beneath component B, A must be rendered
|
||||
* before B. However, this will cause "flickering", aka we see A briefly
|
||||
* then B. To counter this, _isSwipeableViewRendered flag is used to set
|
||||
* component A to be transparent until component B is loaded.
|
||||
*/
|
||||
isSwipeableViewRendered: false,
|
||||
rowHeight: (null: ?number),
|
||||
};
|
||||
},
|
||||
|
||||
getDefaultProps(): Object {
|
||||
return {
|
||||
isOpen: false,
|
||||
maxSwipeDistance: 0,
|
||||
onSwipeEnd: emptyFunction,
|
||||
onSwipeStart: emptyFunction,
|
||||
swipeThreshold: 30,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount(): void {
|
||||
this._panResponder = PanResponder.create({
|
||||
onStartShouldSetPanResponder: (event, gestureState) => true,
|
||||
// Don't capture child's start events
|
||||
onStartShouldSetPanResponderCapture: (event, gestureState) => false,
|
||||
onMoveShouldSetPanResponder: (event, gestureState) => false,
|
||||
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture,
|
||||
onPanResponderGrant: this._handlePanResponderGrant,
|
||||
onPanResponderMove: this._handlePanResponderMove,
|
||||
onPanResponderRelease: this._handlePanResponderEnd,
|
||||
onPanResponderTerminationRequest: this._onPanResponderTerminationRequest,
|
||||
onPanResponderTerminate: this._handlePanResponderEnd,
|
||||
});
|
||||
},
|
||||
|
||||
componentWillReceiveProps(nextProps: Object): void {
|
||||
/**
|
||||
* We do not need an "animateOpen(noCallback)" because this animation is
|
||||
* handled internally by this component.
|
||||
*/
|
||||
if (this.props.isOpen && !nextProps.isOpen) {
|
||||
this._animateToClosedPosition();
|
||||
}
|
||||
},
|
||||
|
||||
render(): ReactElement {
|
||||
// The view hidden behind the main view
|
||||
let slideOutView;
|
||||
if (this.state.isSwipeableViewRendered) {
|
||||
slideOutView = (
|
||||
<View style={[
|
||||
styles.slideOutContainer,
|
||||
{height: this.state.rowHeight},
|
||||
]}>
|
||||
{this.props.slideoutView}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// The swipeable item
|
||||
const swipeableView = (
|
||||
<Animated.View
|
||||
onLayout={this._onSwipeableViewLayout}
|
||||
style={[
|
||||
styles.swipeableContainer,
|
||||
{
|
||||
transform: [{translateX: this.state.currentLeft}],
|
||||
},
|
||||
]}>
|
||||
{this.props.children}
|
||||
</Animated.View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
{...this._panResponder.panHandlers}>
|
||||
{slideOutView}
|
||||
{swipeableView}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_onSwipeableViewLayout(event: Object): void {
|
||||
if (!this.state.isSwipeableViewRendered) {
|
||||
this.setState({
|
||||
isSwipeableViewRendered: true,
|
||||
rowHeight: event.nativeEvent.layout.height,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_handleMoveShouldSetPanResponderCapture(
|
||||
event: Object,
|
||||
gestureState: Object,
|
||||
): boolean {
|
||||
// Decides whether a swipe is responded to by this component or its child
|
||||
return gestureState.dy < 10 && this._isValidSwipe(gestureState);
|
||||
},
|
||||
|
||||
_handlePanResponderGrant(event: Object, gestureState: Object): void {
|
||||
|
||||
},
|
||||
|
||||
_handlePanResponderMove(event: Object, gestureState: Object): void {
|
||||
this.props.onSwipeStart();
|
||||
this.state.currentLeft.setValue(this._previousLeft + gestureState.dx);
|
||||
},
|
||||
|
||||
_onPanResponderTerminationRequest(event: Object, gestureState: Object): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
_animateTo(toValue: number): void {
|
||||
Animated.timing(
|
||||
this.state.currentLeft,
|
||||
{
|
||||
toValue: toValue,
|
||||
},
|
||||
).start(() => {
|
||||
this._previousLeft = toValue;
|
||||
});
|
||||
},
|
||||
|
||||
_animateToOpenPosition(): void {
|
||||
this._animateTo(-this.props.maxSwipeDistance);
|
||||
},
|
||||
|
||||
_animateToClosedPosition(): void {
|
||||
this._animateTo(CLOSED_LEFT_POSITION);
|
||||
},
|
||||
|
||||
// Ignore swipes due to user's finger moving slightly when tapping
|
||||
_isValidSwipe(gestureState: Object): boolean {
|
||||
return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD;
|
||||
},
|
||||
|
||||
_handlePanResponderEnd(event: Object, gestureState: Object): void {
|
||||
const horizontalDistance = gestureState.dx;
|
||||
|
||||
if (Math.abs(horizontalDistance) > this.props.swipeThreshold) {
|
||||
if (horizontalDistance < 0) {
|
||||
// Swiped left
|
||||
this.props.onOpen && this.props.onOpen();
|
||||
this._animateToOpenPosition();
|
||||
} else {
|
||||
// Swiped right
|
||||
this._animateToClosedPosition();
|
||||
}
|
||||
} else {
|
||||
if (this._previousLeft === CLOSED_LEFT_POSITION) {
|
||||
this._animateToClosedPosition();
|
||||
} else {
|
||||
this._animateToOpenPosition();
|
||||
}
|
||||
}
|
||||
|
||||
this.props.onSwipeEnd();
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
slideOutContainer: {
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
},
|
||||
swipeableContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = SwipeableRow;
|
||||
@@ -20,19 +20,20 @@
|
||||
const ViewabilityHelper = {
|
||||
computeViewableRows(
|
||||
viewablePercentThreshold: number,
|
||||
rowFrames: Array<Object>,
|
||||
rowFrames: {[key: string]: Object},
|
||||
data: Array<{rowKey: string, rowData: any}>,
|
||||
scrollOffsetY: number,
|
||||
viewportHeight: number
|
||||
): Array<number> {
|
||||
var viewableRows = [];
|
||||
var firstVisible = -1;
|
||||
for (var idx = 0; idx < rowFrames.length; idx++) {
|
||||
var frame = rowFrames[idx];
|
||||
const viewableRows = [];
|
||||
let firstVisible = -1;
|
||||
for (let idx = 0; idx < data.length; idx++) {
|
||||
const frame = rowFrames[data[idx].rowKey];
|
||||
if (!frame) {
|
||||
continue;
|
||||
}
|
||||
var top = frame.y - scrollOffsetY;
|
||||
var bottom = top + frame.height;
|
||||
const top = frame.y - scrollOffsetY;
|
||||
const bottom = top + frame.height;
|
||||
if ((top < viewportHeight) && (bottom > 0)) {
|
||||
firstVisible = idx;
|
||||
if (_isViewable(
|
||||
@@ -67,7 +68,7 @@ function _getPercentOccupied(
|
||||
bottom: number,
|
||||
viewportHeight: number
|
||||
): number {
|
||||
var visibleHeight = Math.min(bottom, viewportHeight) - Math.max(top, 0);
|
||||
let visibleHeight = Math.min(bottom, viewportHeight) - Math.max(top, 0);
|
||||
visibleHeight = Math.max(0, visibleHeight);
|
||||
return Math.max(0, visibleHeight * 100 / viewportHeight);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ const ViewabilityHelper = require('ViewabilityHelper');
|
||||
|
||||
const clamp = require('clamp');
|
||||
const deepDiffer = require('deepDiffer');
|
||||
const infoLog = require('infoLog');
|
||||
const invariant = require('invariant');
|
||||
const nullthrows = require('nullthrows');
|
||||
|
||||
@@ -83,13 +84,13 @@ type Props = {
|
||||
* A simple array of data blobs that are passed to the renderRow function in
|
||||
* order. Note there is no dataSource like in the standard `ListView`.
|
||||
*/
|
||||
data: Array<any>;
|
||||
data: Array<{rowKey: string, rowData: any}>;
|
||||
/**
|
||||
* Takes a data blob from the `data` array prop plus some meta info and should
|
||||
* return a row.
|
||||
*/
|
||||
renderRow: (
|
||||
data: any, sectionIdx: number, rowIdx: number, key?: string
|
||||
rowData: any, sectionIdx: number, rowIdx: number, rowKey: string
|
||||
) => ?ReactElement;
|
||||
/**
|
||||
* Rendered when the list is scrolled faster than rows can be rendered.
|
||||
@@ -130,13 +131,6 @@ type Props = {
|
||||
* determine how many rows to render above the viewport.
|
||||
*/
|
||||
numToRenderAhead: number;
|
||||
/**
|
||||
* Super dangerous and experimental - rows and all their decendents must be
|
||||
* fully stateless otherwise recycling their instances may introduce nasty
|
||||
* bugs. Some apps may see an improvement in perf, but sometimes perf and
|
||||
* memory usage can actually get worse with this.
|
||||
*/
|
||||
enableDangerousRecycling: boolean;
|
||||
/**
|
||||
* Used to log perf events for async row rendering.
|
||||
*/
|
||||
@@ -177,26 +171,27 @@ class WindowedListView extends React.Component {
|
||||
_firstVisible: number = -1;
|
||||
_lastVisible: number = -1;
|
||||
_scrollOffsetY: number = 0;
|
||||
_isScrolling: boolean = false;
|
||||
_frameHeight: number = 0;
|
||||
_rowFrames: Array<Object> = [];
|
||||
_rowFrames: {[key: string]: Object} = {};
|
||||
_rowRenderMode: {[key: string]: null | 'async' | 'sync'} = {};
|
||||
_rowFramesDirty: boolean = false;
|
||||
_hasCalledOnEndReached: bool = false;
|
||||
_willComputeRowsToRender: bool = false;
|
||||
_hasCalledOnEndReached: boolean = false;
|
||||
_willComputeRowsToRender: boolean = false;
|
||||
_timeoutHandle: number = 0;
|
||||
_incrementPending: bool = false;
|
||||
_incrementPending: boolean = false;
|
||||
_viewableRows: Array<number> = [];
|
||||
_cellsInProgress: Set<number> = new Set();
|
||||
_cellsInProgress: Set<string> = new Set();
|
||||
_scrollRef: ?Object;
|
||||
|
||||
static defaultProps = {
|
||||
enableDangerousRecycling: false,
|
||||
initialNumToRender: 10,
|
||||
maxNumToRender: 30,
|
||||
numToRenderAhead: 10,
|
||||
viewablePercentThreshold: 50,
|
||||
renderScrollComponent: (props) => <ScrollView {...props} />,
|
||||
disableIncrementalRendering: false,
|
||||
recomputeRowsBatchingPeriod: 100,
|
||||
recomputeRowsBatchingPeriod: 10, // This should capture most events that will happen in one frame
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
@@ -239,23 +234,26 @@ class WindowedListView extends React.Component {
|
||||
componentWillReceiveProps(newProps: Object) {
|
||||
// This has to happen immediately otherwise we could crash, e.g. if the data
|
||||
// array has gotten shorter.
|
||||
if (newProps.data.length < this._rowFrames.length) {
|
||||
this._rowFrames = this._rowFrames.splice(0, newProps.data.length);
|
||||
}
|
||||
this._computeRowsToRender(newProps);
|
||||
}
|
||||
_onMomentumScrollEnd = (e: Object) => {
|
||||
this._onScroll(e);
|
||||
};
|
||||
_onScroll = (e: Object) => {
|
||||
this._scrollOffsetY = e.nativeEvent.contentOffset.y;
|
||||
const newScrollY = e.nativeEvent.contentOffset.y;
|
||||
this._isScrolling = this._scrollOffsetY !== newScrollY;
|
||||
this._scrollOffsetY = newScrollY;
|
||||
this._frameHeight = e.nativeEvent.layoutMeasurement.height;
|
||||
// We don't want to enqueue any updates if any cells are in the middle of an incremental render,
|
||||
// because it would just be wasted work.
|
||||
if (this._cellsInProgress.size === 0) {
|
||||
this._enqueueComputeRowsToRender();
|
||||
}
|
||||
if (this.props.onViewableRowsChanged && this._rowFrames.length) {
|
||||
if (this.props.onViewableRowsChanged && Object.keys(this._rowFrames).length) {
|
||||
const viewableRows = ViewabilityHelper.computeViewableRows(
|
||||
this.props.viewablePercentThreshold,
|
||||
this._rowFrames,
|
||||
this.props.data,
|
||||
e.nativeEvent.contentOffset.y,
|
||||
e.nativeEvent.layoutMeasurement.height
|
||||
);
|
||||
@@ -267,35 +265,44 @@ class WindowedListView extends React.Component {
|
||||
this.props.onScroll && this.props.onScroll(e);
|
||||
};
|
||||
// Caller does the diffing so we don't have to.
|
||||
_onNewLayout = (params: {rowIndex: number, layout: Object}) => {
|
||||
const {rowIndex, layout} = params;
|
||||
_onNewLayout = (params: {rowKey: string, layout: Object}) => {
|
||||
const {rowKey, layout} = params;
|
||||
if (DEBUG) {
|
||||
const layoutPrev = this._rowFrames[rowIndex] || {};
|
||||
console.log(
|
||||
const layoutPrev = this._rowFrames[rowKey] || {};
|
||||
infoLog(
|
||||
'record layout for row: ',
|
||||
{i: rowIndex, h: layout.height, y: layout.y, x: layout.x, hp: layoutPrev.height, yp: layoutPrev.y}
|
||||
{k: rowKey, h: layout.height, y: layout.y, x: layout.x, hp: layoutPrev.height, yp: layoutPrev.y}
|
||||
);
|
||||
}
|
||||
this._rowFrames[rowIndex] = {...layout, offscreenLayoutDone: true};
|
||||
if (this._rowFrames[rowKey]) {
|
||||
const deltaY = Math.abs(this._rowFrames[rowKey].y - layout.y);
|
||||
const deltaH = Math.abs(this._rowFrames[rowKey].height - layout.height);
|
||||
if (deltaY > 2 || deltaH > 2) {
|
||||
const dataEntry = this.props.data.find((datum) => datum.rowKey === rowKey);
|
||||
console.warn('layout jump: ', {dataEntry, prevLayout: this._rowFrames[rowKey], newLayout: layout});
|
||||
}
|
||||
}
|
||||
this._rowFrames[rowKey] = {...layout, offscreenLayoutDone: true};
|
||||
this._rowFramesDirty = true;
|
||||
if (this._cellsInProgress.size === 0) {
|
||||
this._enqueueComputeRowsToRender();
|
||||
}
|
||||
};
|
||||
_onWillUnmountCell = (rowIndex: number) => {
|
||||
if (this._rowFrames[rowIndex]) {
|
||||
this._rowFrames[rowIndex].offscreenLayoutDone = false;
|
||||
_onWillUnmountCell = (rowKey: string) => {
|
||||
if (this._rowFrames[rowKey]) {
|
||||
this._rowFrames[rowKey].offscreenLayoutDone = false;
|
||||
this._rowRenderMode[rowKey] = null;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* This is used to keep track of cells that are in the process of rendering. If any cells are in progress, then
|
||||
* other updates are skipped because they will just be wasted work.
|
||||
*/
|
||||
_onProgressChange = ({rowIndex, inProgress}: {rowIndex: number, inProgress: boolean}) => {
|
||||
_onProgressChange = ({rowKey, inProgress}: {rowKey: string, inProgress: boolean}) => {
|
||||
if (inProgress) {
|
||||
this._cellsInProgress.add(rowIndex);
|
||||
this._cellsInProgress.add(rowKey);
|
||||
} else {
|
||||
this._cellsInProgress.delete(rowIndex);
|
||||
this._cellsInProgress.delete(rowKey);
|
||||
}
|
||||
};
|
||||
/**
|
||||
@@ -332,10 +339,11 @@ class WindowedListView extends React.Component {
|
||||
const rowFrames = this._rowFrames;
|
||||
let firstVisible = -1;
|
||||
let lastVisible = 0;
|
||||
let lastRow = this.state.lastRow;
|
||||
const top = this._scrollOffsetY;
|
||||
const bottom = top + this._frameHeight;
|
||||
for (let idx = 0; idx < rowFrames.length; idx++) {
|
||||
const frame = rowFrames[idx];
|
||||
for (let idx = 0; idx < lastRow; idx++) {
|
||||
const frame = rowFrames[props.data[idx].rowKey];
|
||||
if (!frame) {
|
||||
// No frame - sometimes happens when they come out of order, so just wait for the rest.
|
||||
return;
|
||||
@@ -357,14 +365,13 @@ class WindowedListView extends React.Component {
|
||||
// Unfortuantely, we can't use <Incremental> to simplify our increment logic in this function because we need to
|
||||
// make sure that cells are rendered in the right order one at a time when scrolling back up.
|
||||
|
||||
const numRendered = this.state.lastRow - this.state.firstRow + 1;
|
||||
const numRendered = lastRow - this.state.firstRow + 1;
|
||||
// Our last row target that we will approach incrementally
|
||||
const targetLastRow = clamp(
|
||||
numRendered - 1, // Don't reduce numRendered when scrolling back up
|
||||
lastVisible + props.numToRenderAhead, // Primary goal
|
||||
totalRows - 1, // Don't render past the end
|
||||
);
|
||||
let lastRow = this.state.lastRow;
|
||||
// Increment the last row one at a time per JS event loop
|
||||
if (!this._incrementPending) {
|
||||
if (targetLastRow > this.state.lastRow) {
|
||||
@@ -403,8 +410,11 @@ class WindowedListView extends React.Component {
|
||||
const rowsShouldChange = firstRow !== this.state.firstRow || lastRow !== this.state.lastRow;
|
||||
if (this._rowFramesDirty || rowsShouldChange) {
|
||||
if (rowsShouldChange) {
|
||||
this.props.onMountedRowsWillChange && this.props.onMountedRowsWillChange(firstRow, lastRow - firstRow + 1);
|
||||
console.log('WLV: row render range will change:', {firstRow, lastRow});
|
||||
props.onMountedRowsWillChange && props.onMountedRowsWillChange(firstRow, lastRow - firstRow + 1);
|
||||
infoLog(
|
||||
'WLV: row render range will change:',
|
||||
{firstRow, firstVis: this._firstVisible, lastVis: this._lastVisible, lastRow},
|
||||
);
|
||||
}
|
||||
this._rowFramesDirty = false;
|
||||
this.setState({firstRow, lastRow});
|
||||
@@ -425,15 +435,40 @@ class WindowedListView extends React.Component {
|
||||
const rowFrames = this._rowFrames;
|
||||
const rows = [];
|
||||
let spacerHeight = 0;
|
||||
// Incremental rendering is a tradeoff between throughput and responsiveness. When we have plenty of buffer (say 50%
|
||||
// of the target), we render incrementally to keep the app responsive. If we are dangerously low on buffer (say
|
||||
// below 25%) we always disable incremental to try to catch up as fast as possible. In the middle, we only disable
|
||||
// incremental while scrolling since it's unlikely the user will try to press a button while scrolling. We also
|
||||
// ignore the "buffer" size when we are bumped up against the edge of the available data.
|
||||
const firstBuffer = firstRow === 0 ? Infinity : this._firstVisible - firstRow;
|
||||
const lastBuffer = lastRow === this.props.data.length - 1 ? Infinity : lastRow - this._lastVisible;
|
||||
const minBuffer = Math.min(firstBuffer, lastBuffer);
|
||||
const disableIncrementalRendering = this.props.disableIncrementalRendering ||
|
||||
(this._isScrolling && minBuffer < this.props.numToRenderAhead * 0.5) ||
|
||||
(minBuffer < this.props.numToRenderAhead * 0.25);
|
||||
// Render mode is sticky while the component is mounted.
|
||||
for (let ii = firstRow; ii <= lastRow; ii++) {
|
||||
if (!rowFrames[ii]) {
|
||||
const rowKey = this.props.data[ii].rowKey;
|
||||
if (this._rowRenderMode[rowKey] === 'sync' || (disableIncrementalRendering && this._rowRenderMode[rowKey] !== 'async')) {
|
||||
this._rowRenderMode[rowKey] = 'sync';
|
||||
} else {
|
||||
this._rowRenderMode[rowKey] = 'async';
|
||||
}
|
||||
}
|
||||
for (let ii = firstRow; ii <= lastRow; ii++) {
|
||||
const rowKey = this.props.data[ii].rowKey;
|
||||
if (!rowFrames[rowKey]) {
|
||||
break; // if rowFrame missing, no following ones will exist so quit early
|
||||
}
|
||||
// Look for the first row where offscreen layout is done (only true for mounted rows) and set the spacer height
|
||||
// such that it will offset all the unmounted rows before that one using the saved frame data.
|
||||
if (rowFrames[ii].offscreenLayoutDone) {
|
||||
const frame = rowFrames[ii - 1];
|
||||
spacerHeight = frame ? frame.y + frame.height : 0;
|
||||
// Look for the first row where offscreen layout is done (only true for mounted rows) or it will be rendered
|
||||
// synchronously and set the spacer height such that it will offset all the unmounted rows before that one using
|
||||
// the saved frame data.
|
||||
if (rowFrames[rowKey].offscreenLayoutDone || this._rowRenderMode[rowKey] === 'sync') {
|
||||
if (ii > 0) {
|
||||
const prevRowKey = this.props.data[ii - 1].rowKey;
|
||||
const frame = rowFrames[prevRowKey];
|
||||
spacerHeight = frame ? frame.y + frame.height : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -442,7 +477,7 @@ class WindowedListView extends React.Component {
|
||||
showIndicator = true;
|
||||
spacerHeight -= this.state.boundaryIndicatorHeight || 0;
|
||||
}
|
||||
DEBUG && console.log('render top spacer with height ', spacerHeight);
|
||||
DEBUG && infoLog('render top spacer with height ', spacerHeight);
|
||||
rows.push(<View key="sp-top" style={{height: spacerHeight}} />);
|
||||
if (this.props.renderWindowBoundaryIndicator) {
|
||||
// Always render it, even if removed, so that we can get the height right away and don't waste time creating/
|
||||
@@ -462,25 +497,27 @@ class WindowedListView extends React.Component {
|
||||
);
|
||||
}
|
||||
for (let idx = firstRow; idx <= lastRow; idx++) {
|
||||
const key = '' + (this.props.enableDangerousRecycling ? (idx % this.props.maxNumToRender) : idx);
|
||||
const rowKey = this.props.data[idx].rowKey;
|
||||
const includeInLayout = this._rowRenderMode[rowKey] === 'sync' ||
|
||||
(this._rowFrames[rowKey] && this._rowFrames[rowKey].offscreenLayoutDone);
|
||||
rows.push(
|
||||
<CellRenderer
|
||||
key={key}
|
||||
recyclingKey={key}
|
||||
key={rowKey}
|
||||
rowKey={rowKey}
|
||||
rowIndex={idx}
|
||||
onNewLayout={this._onNewLayout}
|
||||
onWillUnmount={this._onWillUnmountCell}
|
||||
includeInLayout={this.props.disableIncrementalRendering ||
|
||||
(this._rowFrames[idx] && this._rowFrames[idx].offscreenLayoutDone)}
|
||||
includeInLayout={includeInLayout}
|
||||
onProgressChange={this._onProgressChange}
|
||||
asyncRowPerfEventName={this.props.asyncRowPerfEventName}
|
||||
data={this.props.data[idx]}
|
||||
rowData={this.props.data[idx].rowData}
|
||||
renderRow={this.props.renderRow}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const showFooter = this._rowFrames[lastRow] &&
|
||||
this._rowFrames[lastRow].offscreenLayoutDone &&
|
||||
const lastRowKey = this.props.data[lastRow].rowKey;
|
||||
const showFooter = this._rowFrames[lastRowKey] &&
|
||||
this._rowFrames[lastRowKey].offscreenLayoutDone &&
|
||||
lastRow === this.props.data.length - 1;
|
||||
if (this.props.renderFooter) {
|
||||
rows.push(
|
||||
@@ -509,17 +546,16 @@ class WindowedListView extends React.Component {
|
||||
// Prevent user from scrolling into empty space of unmounted rows.
|
||||
const contentInset = {top: firstRow === 0 ? 0 : -spacerHeight};
|
||||
return (
|
||||
<IncrementalGroup name="WLV" disabled={this.props.disableIncrementalRendering}>
|
||||
{this.props.renderScrollComponent({
|
||||
scrollEventThrottle: 50,
|
||||
removeClippedSubviews: true,
|
||||
...this.props,
|
||||
contentInset,
|
||||
ref: (ref) => { this._scrollRef = ref; },
|
||||
onScroll: this._onScroll,
|
||||
children: rows,
|
||||
})}
|
||||
</IncrementalGroup>
|
||||
this.props.renderScrollComponent({
|
||||
scrollEventThrottle: 50,
|
||||
removeClippedSubviews: true,
|
||||
...this.props,
|
||||
contentInset,
|
||||
ref: (ref) => { this._scrollRef = ref; },
|
||||
onScroll: this._onScroll,
|
||||
onMomentumScrollEnd: this._onMomentumScrollEnd,
|
||||
children: rows,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -531,11 +567,14 @@ type CellProps = {
|
||||
/**
|
||||
* Row-specific data passed to renderRow and used in shouldComponentUpdate with ===
|
||||
*/
|
||||
data: mixed;
|
||||
rowData: mixed;
|
||||
rowKey: string;
|
||||
/**
|
||||
* Renders the actual row contents.
|
||||
*/
|
||||
renderRow: (data: mixed, sectionIdx: number, rowIdx: number) => ?ReactElement;
|
||||
renderRow: (
|
||||
rowData: mixed, sectionIdx: number, rowIdx: number, rowKey: string
|
||||
) => ?ReactElement;
|
||||
/**
|
||||
* Index of the row, passed through to other callbacks.
|
||||
*/
|
||||
@@ -557,21 +596,22 @@ type CellProps = {
|
||||
* Updates the parent with the latest layout. Only called when incremental rendering is done and triggers the parent
|
||||
* to re-render this row with includeInLayout true.
|
||||
*/
|
||||
onNewLayout: (params: {rowIndex: number, layout: Object}) => void;
|
||||
onNewLayout: (params: {rowKey: string, layout: Object}) => void;
|
||||
/**
|
||||
* Used to track when rendering is in progress so the parent can avoid wastedful re-renders that are just going to be
|
||||
* invalidated once the cell finishes.
|
||||
*/
|
||||
onProgressChange: (progress: {rowIndex: number; inProgress: boolean}) => void;
|
||||
onProgressChange: (progress: {rowKey: string; inProgress: boolean}) => void;
|
||||
/**
|
||||
* Used to invalidate the layout so the parent knows it needs to compensate for the height in the placeholder size.
|
||||
*/
|
||||
onWillUnmount: (rowIndex: number) => void;
|
||||
onWillUnmount: (rowKey: string) => void;
|
||||
};
|
||||
class CellRenderer extends React.Component {
|
||||
props: CellProps;
|
||||
_containerRef: View;
|
||||
_offscreenRenderDone = false;
|
||||
_timer = 0;
|
||||
_timeout = 0;
|
||||
_lastLayout: ?Object = null;
|
||||
_perfUpdateID: number = 0;
|
||||
_asyncCookie: any;
|
||||
@@ -580,12 +620,12 @@ class CellRenderer extends React.Component {
|
||||
if (this.props.asyncRowPerfEventName) {
|
||||
this._perfUpdateID = g_perf_update_id++;
|
||||
this._asyncCookie = Systrace.beginAsyncEvent(this.props.asyncRowPerfEventName + this._perfUpdateID);
|
||||
console.log(`perf_asynctest_${this.props.asyncRowPerfEventName}_start ${this._perfUpdateID} ${Date.now()}`);
|
||||
infoLog(`perf_asynctest_${this.props.asyncRowPerfEventName}_start ${this._perfUpdateID} ${Date.now()}`);
|
||||
}
|
||||
if (this.props.includeInLayout) {
|
||||
this._includeInLayoutLatch = true;
|
||||
}
|
||||
this.props.onProgressChange({rowIndex: this.props.rowIndex, inProgress: true});
|
||||
this.props.onProgressChange({rowKey: this.props.rowKey, inProgress: true});
|
||||
}
|
||||
_onLayout = (e) => {
|
||||
const layout = e.nativeEvent.layout;
|
||||
@@ -595,64 +635,77 @@ class CellRenderer extends React.Component {
|
||||
return; // Don't send premature or duplicate updates
|
||||
}
|
||||
this.props.onNewLayout({
|
||||
rowIndex: this.props.rowIndex,
|
||||
rowKey: this.props.rowKey,
|
||||
layout,
|
||||
});
|
||||
};
|
||||
_updateParent() {
|
||||
invariant(!this._offscreenRenderDone, 'should only finish rendering once');
|
||||
this._offscreenRenderDone = true;
|
||||
|
||||
// If this is not called before calling onNewLayout, the number of inProgress cells will remain non-zero,
|
||||
// and thus the onNewLayout call will not fire the needed state change update.
|
||||
this.props.onProgressChange({rowKey: this.props.rowKey, inProgress: false});
|
||||
|
||||
// If an onLayout event hasn't come in yet, then we skip here and assume it will come in later. This happens
|
||||
// when Incremental is disabled and _onOffscreenRenderDone is called faster than layout can happen.
|
||||
this._lastLayout && this.props.onNewLayout({rowKey: this.props.rowKey, layout: this._lastLayout});
|
||||
|
||||
DEBUG && infoLog('\n >>>>> display row ' + this.props.rowIndex + '\n\n\n');
|
||||
if (this.props.asyncRowPerfEventName) {
|
||||
// Note this doesn't include the native render time but is more accurate than also including the JS render
|
||||
// time of anything that has been queued up.
|
||||
Systrace.endAsyncEvent(this.props.asyncRowPerfEventName + this._perfUpdateID, this._asyncCookie);
|
||||
infoLog(`perf_asynctest_${this.props.asyncRowPerfEventName}_end ${this._perfUpdateID} ${Date.now()}`);
|
||||
}
|
||||
}
|
||||
_onOffscreenRenderDone = () => {
|
||||
DEBUG && console.log('_onOffscreenRenderDone for row ' + this.props.rowIndex);
|
||||
this._timer = setTimeout(() => { // Flush any pending layout events.
|
||||
invariant(!this._offscreenRenderDone, 'should only finish rendering once');
|
||||
this._offscreenRenderDone = true;
|
||||
|
||||
// If this is not called before calling onNewLayout, the number of inProgress cells will remain non-zero,
|
||||
// and thus the onNewLayout call will not fire the needed state change update.
|
||||
this.props.onProgressChange({rowIndex: this.props.rowIndex, inProgress: false});
|
||||
|
||||
// If an onLayout event hasn't come in yet, then we skip here and assume it will come in later. This happens
|
||||
// when Incremental is disabled and _onOffscreenRenderDone is called faster than layout can happen.
|
||||
this._lastLayout && this.props.onNewLayout({rowIndex: this.props.rowIndex, layout: this._lastLayout});
|
||||
|
||||
DEBUG && console.log('\n >>>>> display row ' + this.props.rowIndex + '\n\n\n');
|
||||
if (this.props.asyncRowPerfEventName) {
|
||||
Systrace.endAsyncEvent(this.props.asyncRowPerfEventName + this._perfUpdateID, this._asyncCookie);
|
||||
console.log(`perf_asynctest_${this.props.asyncRowPerfEventName}_end ${this._perfUpdateID} ${Date.now()}`);
|
||||
}
|
||||
}, 1);
|
||||
DEBUG && infoLog('_onOffscreenRenderDone for row ' + this.props.rowIndex);
|
||||
if (this._includeInLayoutLatch) {
|
||||
this._updateParent(); // rendered straight into layout, so no need to flush
|
||||
} else {
|
||||
this._timeout = setTimeout(() => this._updateParent(), 1); // Flush any pending layout events.
|
||||
}
|
||||
};
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this._timer);
|
||||
this.props.onProgressChange({rowIndex: this.props.rowIndex, inProgress: false});
|
||||
this.props.onWillUnmount(this.props.rowIndex);
|
||||
clearTimeout(this._timeout);
|
||||
this.props.onProgressChange({rowKey: this.props.rowKey, inProgress: false});
|
||||
this.props.onWillUnmount(this.props.rowKey);
|
||||
}
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.includeInLayout && !this.props.includeInLayout) {
|
||||
invariant(this._offscreenRenderDone, 'Should never try to add to layout before render done');
|
||||
this._includeInLayoutLatch = true; // Once we render in layout, make sure it sticks.
|
||||
this.refs.container.setNativeProps({style: styles.include});
|
||||
this._containerRef.setNativeProps({style: styles.include});
|
||||
}
|
||||
}
|
||||
shouldComponentUpdate(newProps) {
|
||||
return newProps.data !== this.props.data;
|
||||
shouldComponentUpdate(newProps: CellProps) {
|
||||
return newProps.rowData !== this.props.rowData;
|
||||
}
|
||||
_setRef = (ref) => {
|
||||
this._containerRef = ref;
|
||||
};
|
||||
render() {
|
||||
let debug;
|
||||
if (DEBUG) {
|
||||
console.log('render cell ' + this.props.rowIndex);
|
||||
infoLog('render cell ' + this.props.rowIndex);
|
||||
const Text = require('Text');
|
||||
debug = <Text style={{backgroundColor: 'lightblue'}}>
|
||||
Row: {this.props.rowIndex}
|
||||
</Text>;
|
||||
}
|
||||
const style = (this._includeInLayoutLatch || this.props.includeInLayout) ? styles.include : styles.remove;
|
||||
const style = this._includeInLayoutLatch ? styles.include : styles.remove;
|
||||
return (
|
||||
<IncrementalGroup onDone={this._onOffscreenRenderDone} name={`CellRenderer_${this.props.rowIndex}`}>
|
||||
<IncrementalGroup
|
||||
disabled={this._includeInLayoutLatch}
|
||||
onDone={this._onOffscreenRenderDone}
|
||||
name={`WLVCell_${this.props.rowIndex}`}>
|
||||
<View
|
||||
ref="container"
|
||||
ref={this._setRef}
|
||||
style={style}
|
||||
onLayout={this._onLayout}>
|
||||
{debug}
|
||||
{this.props.renderRow(this.props.data, 0, this.props.rowIndex)}
|
||||
{this.props.renderRow(this.props.rowData, 0, this.props.rowIndex, this.props.rowKey)}
|
||||
{debug}
|
||||
</View>
|
||||
</IncrementalGroup>
|
||||
|
||||
@@ -130,11 +130,11 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
#pragma mark - Private API
|
||||
|
||||
- (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy
|
||||
|
||||
- (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy distanceFilter:(CLLocationDistance)distanceFilter
|
||||
{
|
||||
if (!_locationManager) {
|
||||
_locationManager = [CLLocationManager new];
|
||||
_locationManager.distanceFilter = _observerOptions.distanceFilter;
|
||||
_locationManager.delegate = self;
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ RCT_EXPORT_MODULE()
|
||||
[_locationManager requestWhenInUseAuthorization];
|
||||
}
|
||||
|
||||
_locationManager.distanceFilter = distanceFilter;
|
||||
_locationManager.desiredAccuracy = desiredAccuracy;
|
||||
// Start observing location
|
||||
[_locationManager startUpdatingLocation];
|
||||
@@ -179,7 +180,7 @@ RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options)
|
||||
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
|
||||
}
|
||||
|
||||
[self beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy];
|
||||
[self beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy distanceFilter:_observerOptions.distanceFilter];
|
||||
_observingLocation = YES;
|
||||
}
|
||||
|
||||
@@ -253,7 +254,7 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
|
||||
if (_locationManager) {
|
||||
accuracy = MIN(_locationManager.desiredAccuracy, accuracy);
|
||||
}
|
||||
[self beginLocationUpdatesWithDesiredAccuracy:accuracy];
|
||||
[self beginLocationUpdatesWithDesiredAccuracy:accuracy distanceFilter:options.distanceFilter];
|
||||
}
|
||||
|
||||
#pragma mark - CLLocationManagerDelegate
|
||||
|
||||
@@ -201,10 +201,6 @@ var Image = React.createClass({
|
||||
validAttributes: ReactNativeViewAttributes.UIView
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
isInAParentText: React.PropTypes.bool
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var source = resolveAssetSource(this.props.source) || {};
|
||||
var {width, height, uri} = source;
|
||||
@@ -225,13 +221,6 @@ var Image = React.createClass({
|
||||
console.warn('The <Image> component requires a `source` property rather than `src`.');
|
||||
}
|
||||
|
||||
if (this.context.isInAParentText) {
|
||||
RawImage = RCTVirtualImage;
|
||||
if (!width || !height) {
|
||||
console.warn('You must specify a width and height for the image %s', uri);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<RawImage
|
||||
{...this.props}
|
||||
@@ -252,7 +241,6 @@ var styles = StyleSheet.create({
|
||||
|
||||
var RCTImageView = requireNativeComponent('RCTImageView', Image);
|
||||
var RCTNetworkImageView = NetworkImageViewManager ? requireNativeComponent('RCTNetworkImageView', Image) : RCTImageView;
|
||||
var RCTVirtualImage = requireNativeComponent('RCTVirtualImage', Image);
|
||||
|
||||
|
||||
module.exports = Image;
|
||||
|
||||
@@ -31,9 +31,7 @@ var ImageStylePropTypes = {
|
||||
overflow: ReactPropTypes.oneOf(['visible', 'hidden']),
|
||||
|
||||
/**
|
||||
* iOS-Specific style to "tint" an image.
|
||||
* Changes the color of all the non-transparent pixels to the tintColor.
|
||||
* @platform ios
|
||||
*/
|
||||
tintColor: ColorPropType,
|
||||
opacity: ReactPropTypes.number,
|
||||
|
||||
@@ -44,10 +44,6 @@
|
||||
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; };
|
||||
139A38821C4D57AD00862840 /* RCTResizeMode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTResizeMode.h; sourceTree = "<group>"; };
|
||||
139A38831C4D587C00862840 /* RCTResizeMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTResizeMode.m; sourceTree = "<group>"; };
|
||||
13EF7F071BC42D4E003F47DD /* RCTShadowVirtualImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowVirtualImage.h; sourceTree = "<group>"; };
|
||||
13EF7F081BC42D4E003F47DD /* RCTShadowVirtualImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowVirtualImage.m; sourceTree = "<group>"; };
|
||||
13EF7F091BC42D4E003F47DD /* RCTVirtualImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVirtualImageManager.h; sourceTree = "<group>"; };
|
||||
13EF7F0A1BC42D4E003F47DD /* RCTVirtualImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVirtualImageManager.m; sourceTree = "<group>"; };
|
||||
13EF7F7D1BC825B1003F47DD /* RCTXCAssetImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTXCAssetImageLoader.h; sourceTree = "<group>"; };
|
||||
13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTXCAssetImageLoader.m; sourceTree = "<group>"; };
|
||||
143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; };
|
||||
@@ -95,10 +91,6 @@
|
||||
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */,
|
||||
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
|
||||
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
|
||||
13EF7F071BC42D4E003F47DD /* RCTShadowVirtualImage.h */,
|
||||
13EF7F081BC42D4E003F47DD /* RCTShadowVirtualImage.m */,
|
||||
13EF7F091BC42D4E003F47DD /* RCTVirtualImageManager.h */,
|
||||
13EF7F0A1BC42D4E003F47DD /* RCTVirtualImageManager.m */,
|
||||
58B5115E1A9E6B3D00147676 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
@@ -169,7 +161,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */,
|
||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
|
||||
@@ -178,7 +169,6 @@
|
||||
139A38841C4D587C00862840 /* RCTResizeMode.m in Sources */,
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
|
||||
EEF314721C9B0DD30049118E /* RCTImageBlurUtils.m in Sources */,
|
||||
13EF7F0B1BC42D4E003F47DD /* RCTShadowVirtualImage.m in Sources */,
|
||||
13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */,
|
||||
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
|
||||
);
|
||||
|
||||
@@ -425,10 +425,12 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
if (!_pendingTasks) {
|
||||
_pendingTasks = [NSMutableArray new];
|
||||
}
|
||||
[_pendingTasks addObject:task];
|
||||
if (MAX(_activeTasks, _scheduledDecodes) < _maxConcurrentLoadingTasks) {
|
||||
[task start];
|
||||
_activeTasks++;
|
||||
if (task) {
|
||||
[_pendingTasks addObject:task];
|
||||
if (MAX(_activeTasks, _scheduledDecodes) < _maxConcurrentLoadingTasks) {
|
||||
[task start];
|
||||
_activeTasks++;
|
||||
}
|
||||
}
|
||||
|
||||
cancelLoad = ^{
|
||||
|
||||
@@ -8,13 +8,12 @@
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "RCTImageComponent.h"
|
||||
#import "RCTResizeMode.h"
|
||||
|
||||
@class RCTBridge;
|
||||
@class RCTImageSource;
|
||||
|
||||
@interface RCTImageView : UIImageView <RCTImageComponent>
|
||||
@interface RCTImageView : UIImageView
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
|
||||
@@ -207,13 +207,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
scale:imageScale
|
||||
resizeMode:(RCTResizeMode)self.contentMode
|
||||
progressBlock:progressHandler
|
||||
completionBlock:^(NSError *error, UIImage *image) {
|
||||
completionBlock:^(NSError *error, UIImage *loadedImage) {
|
||||
RCTImageView *strongSelf = weakSelf;
|
||||
if (blurRadius > __FLT_EPSILON__) {
|
||||
// Do this on the background thread to avoid blocking interaction
|
||||
image = RCTBlurredImageWithRadius(image, blurRadius);
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
void (^setImageBlock)(UIImage *) = ^(UIImage *image) {
|
||||
if (![source isEqual:strongSelf.source]) {
|
||||
// Bail out if source has changed since we started loading
|
||||
return;
|
||||
@@ -234,9 +230,31 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
}
|
||||
}
|
||||
if (strongSelf->_onLoadEnd) {
|
||||
strongSelf->_onLoadEnd(nil);
|
||||
strongSelf->_onLoadEnd(nil);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (blurRadius > __FLT_EPSILON__) {
|
||||
// Blur on a background thread to avoid blocking interaction
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
UIImage *image = RCTBlurredImageWithRadius(loadedImage, blurRadius);
|
||||
RCTExecuteOnMainThread(^{
|
||||
setImageBlock(image);
|
||||
}, NO);
|
||||
});
|
||||
} else {
|
||||
// No blur, so try to set the image on the main thread synchronously to minimize image
|
||||
// flashing. (For instance, if this view gets attached to a window, then -didMoveToWindow
|
||||
// calls -reloadImage, and we want to set the image synchronously if possible so that the
|
||||
// image property is set in the same CATransaction that attaches this view to the window.)
|
||||
if ([NSThread isMainThread]) {
|
||||
setImageBlock(loadedImage);
|
||||
} else {
|
||||
RCTExecuteOnMainThread(^{
|
||||
setImageBlock(loadedImage);
|
||||
}, NO);
|
||||
}
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[self clearImage];
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTShadowView.h"
|
||||
#import "RCTImageComponent.h"
|
||||
#import "RCTImageSource.h"
|
||||
#import "RCTResizeMode.h"
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
/**
|
||||
* Shadow image component, used for embedding images in non-view contexts such
|
||||
* as text. This is NOT used for ordinary <Image> views.
|
||||
*/
|
||||
@interface RCTShadowVirtualImage : RCTShadowView <RCTImageComponent>
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
|
||||
@property (nonatomic, strong) RCTImageSource *source;
|
||||
@property (nonatomic, assign) RCTResizeMode resizeMode;
|
||||
|
||||
@end
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTShadowVirtualImage.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTShadowVirtualImage
|
||||
{
|
||||
RCTBridge *_bridge;
|
||||
RCTImageLoaderCancellationBlock _cancellationBlock;
|
||||
}
|
||||
|
||||
@synthesize image = _image;
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_bridge = bridge;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
||||
|
||||
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
||||
{
|
||||
[super didSetProps:changedProps];
|
||||
|
||||
if (changedProps.count == 0) {
|
||||
// No need to reload image
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel previous request
|
||||
if (_cancellationBlock) {
|
||||
_cancellationBlock();
|
||||
}
|
||||
|
||||
CGSize imageSize = {
|
||||
RCTZeroIfNaN(self.width),
|
||||
RCTZeroIfNaN(self.height),
|
||||
};
|
||||
|
||||
__weak RCTShadowVirtualImage *weakSelf = self;
|
||||
_cancellationBlock = [_bridge.imageLoader loadImageWithTag:_source.imageURL.absoluteString
|
||||
size:imageSize
|
||||
scale:RCTScreenScale()
|
||||
resizeMode:_resizeMode
|
||||
progressBlock:nil
|
||||
completionBlock:^(NSError *error, UIImage *image) {
|
||||
|
||||
dispatch_async(_bridge.uiManager.methodQueue, ^{
|
||||
RCTShadowVirtualImage *strongSelf = weakSelf;
|
||||
if (![_source isEqual:strongSelf.source]) {
|
||||
// Bail out if source has changed since we started loading
|
||||
return;
|
||||
}
|
||||
strongSelf->_image = image;
|
||||
[strongSelf dirtyText];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_cancellationBlock) {
|
||||
_cancellationBlock();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTVirtualImageManager.h"
|
||||
#import "RCTShadowVirtualImage.h"
|
||||
|
||||
@implementation RCTVirtualImageManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return [[RCTShadowVirtualImage alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
|
||||
RCT_EXPORT_SHADOW_PROPERTY(source, RCTImageSource)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(resizeMode, UIViewContentMode)
|
||||
|
||||
@end
|
||||
@@ -20,6 +20,8 @@ var Text = require('Text');
|
||||
var TouchableHighlight = require('TouchableHighlight');
|
||||
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
var View = require('View');
|
||||
var {SourceCode} = require('NativeModules');
|
||||
var {fetch} = require('fetch');
|
||||
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var mapWithSeparator = require('mapWithSeparator');
|
||||
@@ -32,11 +34,31 @@ var ElementProperties = React.createClass({
|
||||
PropTypes.array,
|
||||
PropTypes.number,
|
||||
]),
|
||||
source: PropTypes.shape({
|
||||
fileName: PropTypes.string,
|
||||
lineNumber: PropTypes.number,
|
||||
}),
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var style = flattenStyle(this.props.style);
|
||||
var selection = this.props.selection;
|
||||
var openFileButton;
|
||||
var source = this.props.source;
|
||||
var {fileName, lineNumber} = source || {};
|
||||
if (fileName && lineNumber) {
|
||||
var parts = fileName.split('/');
|
||||
var fileNameShort = parts[parts.length - 1];
|
||||
openFileButton = (
|
||||
<TouchableHighlight
|
||||
style={styles.openButton}
|
||||
onPress={this._openFile.bind(null, fileName, lineNumber)}>
|
||||
<Text style={styles.openButtonTitle} numberOfLines={1}>
|
||||
{fileNameShort}:{lineNumber}
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
// Without the `TouchableWithoutFeedback`, taps on this inspector pane
|
||||
// would change the inspected element to whatever is under the inspector
|
||||
return (
|
||||
@@ -63,13 +85,26 @@ var ElementProperties = React.createClass({
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<StyleInspector style={style} />
|
||||
<View style={styles.col}>
|
||||
<StyleInspector style={style} />
|
||||
{openFileButton}
|
||||
</View>
|
||||
<BoxInspector style={style} frame={this.props.frame} />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_openFile: function(fileName: string, lineNumber: number) {
|
||||
var match = SourceCode.scriptURL && SourceCode.scriptURL.match(/^https?:\/\/.*?\//);
|
||||
var baseURL = match ? match[0] : 'http://localhost:8081/';
|
||||
|
||||
fetch(baseURL + 'open-stack-frame', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({file: fileName, lineNumber}),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
@@ -101,6 +136,9 @@ var styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
col: {
|
||||
flex: 1,
|
||||
},
|
||||
info: {
|
||||
padding: 10,
|
||||
},
|
||||
@@ -108,6 +146,17 @@ var styles = StyleSheet.create({
|
||||
color: 'white',
|
||||
fontSize: 9,
|
||||
},
|
||||
openButton: {
|
||||
padding: 10,
|
||||
backgroundColor: '#000',
|
||||
marginVertical: 5,
|
||||
marginRight: 5,
|
||||
borderRadius: 2,
|
||||
},
|
||||
openButtonTitle: {
|
||||
color: 'white',
|
||||
fontSize: 8,
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ElementProperties;
|
||||
|
||||
@@ -21,7 +21,7 @@ var InspectorUtils = require('InspectorUtils');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var Touchable = require('Touchable');
|
||||
var UIManager = require('NativeModules').UIManager;
|
||||
var UIManager = require('UIManager');
|
||||
var View = require('View');
|
||||
|
||||
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
|
||||
@@ -129,11 +129,13 @@ class Inspector extends React.Component {
|
||||
// if we inspect a stateless component we can't use the getPublicInstance method
|
||||
// therefore we use the internal _instance property directly.
|
||||
var publicInstance = instance['_instance'] || {};
|
||||
var source = instance._currentElement && instance._currentElement._source;
|
||||
UIManager.measure(instance.getNativeNode(), (x, y, width, height, left, top) => {
|
||||
this.setState({
|
||||
inspected: {
|
||||
frame: {left, top, width, height},
|
||||
style: publicInstance.props ? publicInstance.props.style : {},
|
||||
source,
|
||||
},
|
||||
selection: i,
|
||||
});
|
||||
@@ -149,6 +151,7 @@ class Inspector extends React.Component {
|
||||
// therefore we use the internal _instance property directly.
|
||||
var publicInstance = instance._instance || {};
|
||||
var props = publicInstance.props || {};
|
||||
var source = instance._currentElement && instance._currentElement._source;
|
||||
this.setState({
|
||||
panelPos: pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom',
|
||||
selection: hierarchy.length - 1,
|
||||
@@ -156,6 +159,7 @@ class Inspector extends React.Component {
|
||||
inspected: {
|
||||
style: props.style || {},
|
||||
frame,
|
||||
source,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ var Dimensions = require('Dimensions');
|
||||
var InspectorUtils = require('InspectorUtils');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var UIManager = require('NativeModules').UIManager;
|
||||
var UIManager = require('UIManager');
|
||||
var View = require('View');
|
||||
var ElementBox = require('ElementBox');
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class InspectorPanel extends React.Component {
|
||||
<ElementProperties
|
||||
style={this.props.inspected.style}
|
||||
frame={this.props.inspected.frame}
|
||||
source={this.props.inspected.source}
|
||||
hierarchy={this.props.hierarchy}
|
||||
selection={this.props.selection}
|
||||
setSelection={this.props.setSelection}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user