mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-11 22:32:38 +08:00
NavigationExperimental
Summary: A new API to unify internal navigation. Also addresses a highly-rated community 'pain': https://productpains.com/post/react-native/better-navigator-api-and-docs/ Offers the following improvements: - Redux-style navigation logic is easy to reason about - Navigation state can be easily saved and restored through refreshes - Declarative navigation views can be implemented in native or JS - Animations and gestures are isolated and now use the Animated library public Reviewed By: hedgerwang Differential Revision: D2798048 fb-gh-sync-id: 88027ef9ead8a80afa38354252bc377455cc6dbb
This commit is contained in:
committed by
facebook-github-bot-9
parent
2f73ad0241
commit
a3085464f6
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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-native');
|
||||
var {
|
||||
NavigationExperimental,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
} = React;
|
||||
var NavigationExampleRow = require('./NavigationExampleRow');
|
||||
var {
|
||||
AnimatedView: NavigationAnimatedView,
|
||||
Card: NavigationCard,
|
||||
RootContainer: NavigationRootContainer,
|
||||
Reducer: NavigationReducer,
|
||||
Header: NavigationHeader,
|
||||
} = NavigationExperimental;
|
||||
|
||||
const NavigationBasicReducer = NavigationReducer.StackReducer({
|
||||
initialStates: [
|
||||
{key: 'First Route'}
|
||||
],
|
||||
matchAction: action => true,
|
||||
actionStateMap: actionString => ({key: actionString}),
|
||||
});
|
||||
|
||||
class NavigationAnimatedExample extends React.Component {
|
||||
componentWillMount() {
|
||||
this._renderNavigated = this._renderNavigated.bind(this);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={NavigationBasicReducer}
|
||||
persistenceKey="NavigationAnimatedExampleState"
|
||||
renderNavigation={this._renderNavigated}
|
||||
/>
|
||||
);
|
||||
}
|
||||
_renderNavigated(navigationState, onNavigate) {
|
||||
if (!navigationState) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<NavigationAnimatedView
|
||||
navigationState={navigationState}
|
||||
style={styles.animatedView}
|
||||
renderOverlay={(position, layout) => (
|
||||
<NavigationHeader
|
||||
navigationState={navigationState}
|
||||
position={position}
|
||||
getTitle={state => state.key}
|
||||
/>
|
||||
)}
|
||||
renderScene={(state, index, position, layout) => (
|
||||
<NavigationCard
|
||||
key={state.key}
|
||||
index={index}
|
||||
navigationState={navigationState}
|
||||
position={position}
|
||||
layout={layout}>
|
||||
<ScrollView style={styles.scrollView}>
|
||||
<NavigationExampleRow
|
||||
text={navigationState.children[navigationState.index].key}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="Push!"
|
||||
onPress={() => {
|
||||
onNavigate('Route #' + navigationState.children.length);
|
||||
}}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="Exit Animated Nav Example"
|
||||
onPress={this.props.onExampleExit}
|
||||
/>
|
||||
</ScrollView>
|
||||
</NavigationCard>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
animatedView: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
marginTop: 64
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationAnimatedExample;
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const React = require('react-native');
|
||||
const {
|
||||
NavigationExperimental,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
} = React;
|
||||
const NavigationExampleRow = require('./NavigationExampleRow');
|
||||
const {
|
||||
RootContainer: NavigationRootContainer,
|
||||
Reducer: NavigationReducer,
|
||||
} = NavigationExperimental;
|
||||
const StackReducer = NavigationReducer.StackReducer;
|
||||
|
||||
const NavigationBasicReducer = StackReducer({
|
||||
initialStates: [
|
||||
{key: 'first_page'}
|
||||
],
|
||||
matchAction: action => true,
|
||||
actionStateMap: action => ({key: action}),
|
||||
});
|
||||
|
||||
const NavigationBasicExample = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={NavigationBasicReducer}
|
||||
persistenceKey="NavigationBasicExampleState"
|
||||
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('page #' + navState.children.length);
|
||||
}}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="pop"
|
||||
onPress={() => {
|
||||
onNavigate(StackReducer.PopAction());
|
||||
}}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="Exit Basic Nav Example"
|
||||
onPress={this.props.onExampleExit}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topView: {
|
||||
backgroundColor: '#E9E9EF',
|
||||
flex: 1,
|
||||
paddingTop: 30,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationBasicExample;
|
||||
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const React = require('react-native');
|
||||
const {
|
||||
NavigationExperimental,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} = React;
|
||||
const {
|
||||
AnimatedView: NavigationAnimatedView,
|
||||
Card: NavigationCard,
|
||||
Container: NavigationContainer,
|
||||
RootContainer: NavigationRootContainer,
|
||||
Header: NavigationHeader,
|
||||
Reducer: NavigationReducer,
|
||||
View: NavigationView,
|
||||
} = NavigationExperimental;
|
||||
const NavigationExampleRow = require('./NavigationExampleRow');
|
||||
const NavigationExampleTabBar = require('./NavigationExampleTabBar');
|
||||
|
||||
const ExampleExitAction = () => ({
|
||||
isExitAction: true,
|
||||
});
|
||||
ExampleExitAction.match = (action) => (
|
||||
action && action.isExitAction === true
|
||||
);
|
||||
|
||||
const ExamplePageAction = (type) => ({
|
||||
type,
|
||||
isPageAction: true,
|
||||
});
|
||||
ExamplePageAction.match = (action) => (
|
||||
action && action.isPageAction === true
|
||||
);
|
||||
|
||||
const ExampleSettingsPageAction = (type) => ({
|
||||
...ExamplePageAction(type),
|
||||
isSettingsPageAction: true,
|
||||
});
|
||||
ExampleSettingsPageAction.match = (action) => (
|
||||
action && action.isSettingsPageAction === true
|
||||
);
|
||||
|
||||
const ExampleInfoAction = () => ExamplePageAction('InfoPage');
|
||||
|
||||
const ExampleNotifSettingsAction = () => ExampleSettingsPageAction('NotifSettingsPage');
|
||||
|
||||
const _jsInstanceUniqueId = '' + Date.now();
|
||||
let _uniqueIdCount = 0;
|
||||
function pageStateActionMap(action) {
|
||||
return {
|
||||
key: 'page-' + _jsInstanceUniqueId + '-' + (_uniqueIdCount++),
|
||||
type: action.type,
|
||||
};
|
||||
}
|
||||
|
||||
function getTabActionMatcher(key) {
|
||||
return function (action) {
|
||||
if (!ExamplePageAction.match(action)) {
|
||||
return false;
|
||||
}
|
||||
if (ExampleSettingsPageAction.match(action)) {
|
||||
return key === 'settings';
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
var ExampleTabs = [
|
||||
{
|
||||
label: 'Account',
|
||||
reducer: NavigationReducer.StackReducer({
|
||||
initialStates: [
|
||||
{type: 'AccountPage', key: 'base'}
|
||||
],
|
||||
key: 'account',
|
||||
matchAction: getTabActionMatcher('account'),
|
||||
actionStateMap: pageStateActionMap,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Notifications',
|
||||
reducer: NavigationReducer.StackReducer({
|
||||
initialStates: [
|
||||
{type: 'NotifsPage', key: 'base'}
|
||||
],
|
||||
key: 'notifs',
|
||||
matchAction: getTabActionMatcher('notifs'),
|
||||
actionStateMap: pageStateActionMap,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
reducer: NavigationReducer.StackReducer({
|
||||
initialStates: [
|
||||
{type: 'SettingsPage', key: 'base'}
|
||||
],
|
||||
key: 'settings',
|
||||
matchAction: getTabActionMatcher('settings'),
|
||||
actionStateMap: pageStateActionMap,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const ExampleAppReducer = NavigationReducer.TabsReducer({
|
||||
tabReducers: ExampleTabs.map(tab => tab.reducer),
|
||||
});
|
||||
|
||||
function stateTypeTitleMap(pageState) {
|
||||
switch (pageState.type) {
|
||||
case 'AccountPage':
|
||||
return 'Account Page';
|
||||
case 'NotifsPage':
|
||||
return 'Notifications';
|
||||
case 'SettingsPage':
|
||||
return 'Settings';
|
||||
case 'InfoPage':
|
||||
return 'Info Page';
|
||||
case 'NotifSettingsPage':
|
||||
return 'Notification Settings';
|
||||
}
|
||||
}
|
||||
|
||||
let ExampleTabScreen = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<NavigationAnimatedView
|
||||
style={styles.tabContent}
|
||||
navigationState={this.props.navigationState}
|
||||
renderOverlay={this._renderHeader}
|
||||
renderScene={this._renderScene}
|
||||
/>
|
||||
);
|
||||
},
|
||||
_renderHeader: function(position, layout) {
|
||||
return (
|
||||
<NavigationHeader
|
||||
navigationState={this.props.navigationState}
|
||||
position={position}
|
||||
layout={layout}
|
||||
getTitle={state => stateTypeTitleMap(state)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
_renderScene: function(child, index, position, layout) {
|
||||
return (
|
||||
<NavigationCard
|
||||
key={child.key}
|
||||
index={index}
|
||||
childState={child}
|
||||
navigationState={this.props.navigationState}
|
||||
position={position}
|
||||
layout={layout}>
|
||||
<ScrollView style={styles.scrollView}>
|
||||
<NavigationExampleRow
|
||||
text="Open page"
|
||||
onPress={() => {
|
||||
this.props.onNavigate(ExampleInfoAction());
|
||||
}}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="Open notifs settings in settings tab"
|
||||
onPress={() => {
|
||||
this.props.onNavigate(ExampleNotifSettingsAction());
|
||||
}}
|
||||
/>
|
||||
<NavigationExampleRow
|
||||
text="Exit Composition Example"
|
||||
onPress={() => {
|
||||
this.props.onNavigate(ExampleExitAction());
|
||||
}}
|
||||
/>
|
||||
</ScrollView>
|
||||
</NavigationCard>
|
||||
);
|
||||
},
|
||||
});
|
||||
ExampleTabScreen = NavigationContainer.create(ExampleTabScreen);
|
||||
|
||||
const NavigationCompositionExample = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={ExampleAppReducer}
|
||||
persistenceKey="NavigationCompositionExampleState"
|
||||
renderNavigation={this.renderApp}
|
||||
/>
|
||||
);
|
||||
},
|
||||
renderApp: function(navigationState, onNavigate) {
|
||||
if (!navigationState) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View style={styles.topView}>
|
||||
<ExampleMainView
|
||||
navigationState={navigationState}
|
||||
onExampleExit={this.props.onExampleExit}
|
||||
/>
|
||||
<NavigationExampleTabBar
|
||||
tabs={navigationState.children}
|
||||
index={navigationState.index}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
let ExampleMainView = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<NavigationView
|
||||
navigationState={this.props.navigationState}
|
||||
style={styles.tabsContent}
|
||||
renderScene={(tabState, index) => (
|
||||
<ExampleTabScreen
|
||||
key={tabState.key}
|
||||
navigationState={tabState}
|
||||
onNavigate={this._handleNavigation.bind(null, tabState.key)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
_handleNavigation: function(tabKey, action) {
|
||||
if (ExampleExitAction.match(action)) {
|
||||
this.props.onExampleExit();
|
||||
return;
|
||||
}
|
||||
this.props.onNavigate(action);
|
||||
},
|
||||
});
|
||||
ExampleMainView = NavigationContainer.create(ExampleMainView);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topView: {
|
||||
flex: 1,
|
||||
},
|
||||
tabsContent: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
marginTop: 64
|
||||
},
|
||||
tabContent: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationCompositionExample;
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Text,
|
||||
PixelRatio,
|
||||
StyleSheet,
|
||||
View,
|
||||
TouchableHighlight,
|
||||
} = React;
|
||||
|
||||
var NavigationExampleRow = React.createClass({
|
||||
render: function() {
|
||||
if (this.props.onPress) {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.row}
|
||||
underlayColor="#D0D0D0"
|
||||
onPress={this.props.onPress}>
|
||||
<Text style={styles.buttonText}>
|
||||
{this.props.text}
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.rowText}>
|
||||
{this.props.text}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
padding: 15,
|
||||
backgroundColor: 'white',
|
||||
borderBottomWidth: 1 / PixelRatio.get(),
|
||||
borderBottomColor: '#CDCDCD',
|
||||
},
|
||||
rowText: {
|
||||
fontSize: 17,
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationExampleRow;
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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-native');
|
||||
var {
|
||||
NavigationExperimental,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = React;
|
||||
const {
|
||||
Container: NavigationContainer,
|
||||
Reducer: NavigationReducer,
|
||||
} = NavigationExperimental;
|
||||
const {
|
||||
JumpToAction,
|
||||
} = NavigationReducer.TabsReducer;
|
||||
|
||||
var NavigationExampleTabBar = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.tabBar}>
|
||||
{this.props.tabs.map(this._renderTab)}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
_renderTab: function(tab, index) {
|
||||
var textStyle = [styles.tabButtonText];
|
||||
if (this.props.index === index) {
|
||||
textStyle.push(styles.selectedTabButtonText);
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.tabButton}
|
||||
key={tab.key}
|
||||
onPress={() => {
|
||||
this.props.onNavigate(JumpToAction(index));
|
||||
}}>
|
||||
<Text style={textStyle}>
|
||||
{tab.key}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
NavigationExampleTabBar = NavigationContainer.create(NavigationExampleTabBar);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabBar: {
|
||||
height: 50,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tabButton: {
|
||||
flex: 1,
|
||||
},
|
||||
tabButtonText: {
|
||||
paddingTop: 20,
|
||||
textAlign: 'center',
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
},
|
||||
selectedTabButtonText: {
|
||||
color: 'blue',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationExampleTabBar;
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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-native');
|
||||
var {
|
||||
AsyncStorage,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} = React;
|
||||
var NavigationExampleRow = require('./NavigationExampleRow');
|
||||
|
||||
/*
|
||||
* 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 exmample files:
|
||||
*/
|
||||
var EXAMPLES = {
|
||||
'Tabs': require('./NavigationTabsExample'),
|
||||
'Basic': require('./NavigationBasicExample'),
|
||||
'Animated Card Stack': require('./NavigationAnimatedExample'),
|
||||
'Composition': require('./NavigationCompositionExample'),
|
||||
};
|
||||
|
||||
var EXAMPLE_STORAGE_KEY = 'NavigationExampleExample';
|
||||
|
||||
var NavigationExperimentalExample = React.createClass({
|
||||
statics: {
|
||||
title: 'Navigation (Experimental)',
|
||||
description: 'Upcoming navigation APIs and animated navigation views',
|
||||
external: true,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
exampe: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
AsyncStorage.getItem(EXAMPLE_STORAGE_KEY, (err, example) => {
|
||||
if (err || !example) {
|
||||
this.setState({
|
||||
example: 'menu',
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
example,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
setExample: function(example) {
|
||||
this.setState({
|
||||
example,
|
||||
});
|
||||
AsyncStorage.setItem(EXAMPLE_STORAGE_KEY, example);
|
||||
},
|
||||
|
||||
_renderMenu: function() {
|
||||
var exitRow = null;
|
||||
if (this.props.onExampleExit) {
|
||||
exitRow = (
|
||||
<NavigationExampleRow
|
||||
text="Exit Navigation Examples"
|
||||
onPress={this.props.onExampleExit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={styles.menu}>
|
||||
<ScrollView>
|
||||
{this._renderExampleList()}
|
||||
{exitRow}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_renderExampleList: function() {
|
||||
return Object.keys(EXAMPLES).map(exampleName => (
|
||||
<NavigationExampleRow
|
||||
key={exampleName}
|
||||
text={exampleName}
|
||||
onPress={() => {
|
||||
this.setExample(exampleName);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
},
|
||||
|
||||
_exitInnerExample: function() {
|
||||
this.setExample('menu');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.example === 'menu') {
|
||||
return this._renderMenu();
|
||||
}
|
||||
if (EXAMPLES[this.state.example]) {
|
||||
var Component = EXAMPLES[this.state.example];
|
||||
return <Component onExampleExit={this._exitInnerExample} />;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
menu: {
|
||||
backgroundColor: '#E9E9EF',
|
||||
flex: 1,
|
||||
marginTop: 20,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationExperimentalExample;
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const React = require('react-native');
|
||||
const {
|
||||
NavigationExperimental,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} = React;
|
||||
const {
|
||||
Container: NavigationContainer,
|
||||
RootContainer: NavigationRootContainer,
|
||||
Reducer: NavigationReducer,
|
||||
} = NavigationExperimental;
|
||||
|
||||
const NavigationExampleRow = require('./NavigationExampleRow');
|
||||
const NavigationExampleTabBar = require('./NavigationExampleTabBar');
|
||||
|
||||
class ExmpleTabPage extends React.Component {
|
||||
render() {
|
||||
const currentTabRoute = this.props.tabs[this.props.index];
|
||||
return (
|
||||
<ScrollView style={styles.tabPage}>
|
||||
<NavigationExampleRow
|
||||
text={`Current Tab is: ${currentTabRoute.key}`}
|
||||
/>
|
||||
{this.props.tabs.map((tab, index) => (
|
||||
<NavigationExampleRow
|
||||
key={tab.key}
|
||||
text={`Go to ${tab.key}`}
|
||||
onPress={() => {
|
||||
this.props.onNavigate(NavigationReducer.TabsReducer.JumpToAction(index));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<NavigationExampleRow
|
||||
text="Exit Tabs Example"
|
||||
onPress={this.props.onExampleExit}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
ExmpleTabPage = NavigationContainer.create(ExmpleTabPage);
|
||||
|
||||
const ExampleTabsReducer = NavigationReducer.TabsReducer({
|
||||
tabReducers: [
|
||||
(lastRoute) => lastRoute || {key: 'one'},
|
||||
(lastRoute) => lastRoute || {key: 'two'},
|
||||
(lastRoute) => lastRoute || {key: 'three'},
|
||||
],
|
||||
});
|
||||
|
||||
const NavigationTabsExample = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={ExampleTabsReducer}
|
||||
persistenceKey="NAV_EXAMPLE_STATE_TABS"
|
||||
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>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topView: {
|
||||
flex: 1,
|
||||
paddingTop: 30,
|
||||
},
|
||||
tabPage: {
|
||||
backgroundColor: '#E9E9EF',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationTabsExample;
|
||||
@@ -74,6 +74,7 @@ var APIS = [
|
||||
require('./GeolocationExample'),
|
||||
require('./ImageEditingExample'),
|
||||
require('./LayoutExample'),
|
||||
require('./NavigationExperimental/NavigationExperimentalExample'),
|
||||
require('./NetInfoExample'),
|
||||
require('./PanResponderExample'),
|
||||
require('./PointerEventsExample'),
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* 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 NavigationCard
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animated = require('Animated');
|
||||
const NavigationReducer = require('NavigationReducer');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const PanResponder = require('PanResponder');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
import type {
|
||||
NavigationParentState
|
||||
} from 'NavigationState';
|
||||
|
||||
type Layout = {
|
||||
initWidth: number,
|
||||
initHeight: number,
|
||||
width: Animated.Value;
|
||||
height: Animated.Value;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
navigationState: NavigationParentState;
|
||||
index: number;
|
||||
position: Animated.Value;
|
||||
layout: Layout;
|
||||
onNavigate: Function;
|
||||
children: Object;
|
||||
};
|
||||
|
||||
class NavigationCard extends React.Component {
|
||||
_responder: ?Object;
|
||||
_lastHeight: number;
|
||||
_lastWidth: number;
|
||||
_widthListener: string;
|
||||
_heightListener: string;
|
||||
props: Props;
|
||||
componentWillMount(props) {
|
||||
this._responder = PanResponder.create({
|
||||
onMoveShouldSetPanResponder: (e, {dx, dy, moveX, moveY, x0, y0}) => {
|
||||
if (this.props.navigationState.index === 0) {
|
||||
return false;
|
||||
}
|
||||
if (moveX > 30) {
|
||||
return false;
|
||||
}
|
||||
if (dx > 5 && Math.abs(dy) < 4) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
onPanResponderGrant: (e, {dx, dy, moveX, moveY, x0, y0}) => {
|
||||
},
|
||||
onPanResponderMove: (e, {dx}) => {
|
||||
const a = (-dx / this._lastWidth) + this.props.navigationState.index;
|
||||
this.props.position.setValue(a);
|
||||
},
|
||||
onPanResponderRelease: (e, {vx, dx}) => {
|
||||
const xRatio = dx / this._lastWidth;
|
||||
const doesPop = (xRatio + vx) > 0.45;
|
||||
if (doesPop) {
|
||||
// todo: add an action which accepts velocity of the pop action/gesture, which is caught and used by NavigationAnimatedView
|
||||
this.props.onNavigate(NavigationReducer.StackReducer.PopAction());
|
||||
return;
|
||||
}
|
||||
Animated.spring(this.props.position, {
|
||||
toValue: this.props.navigationState.index,
|
||||
}).start();
|
||||
},
|
||||
onPanResponderTerminate: (e, {vx, dx}) => {
|
||||
Animated.spring(this.props.position, {
|
||||
toValue: this.props.navigationState.index,
|
||||
}).start();
|
||||
},
|
||||
});
|
||||
}
|
||||
componentDidMount() {
|
||||
this._lastHeight = this.props.layout.initHeight;
|
||||
this._lastWidth = this.props.layout.initWidth;
|
||||
this._widthListener = this.props.layout.width.addListener(({value}) => {
|
||||
this._lastWidth = value;
|
||||
});
|
||||
this._heightListener = this.props.layout.height.addListener(({value}) => {
|
||||
this._lastHeight = value;
|
||||
});
|
||||
// todo: fix listener and last layout dimentsions when props change. potential bugs here
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.props.layout.width.removeListener(this._widthListener);
|
||||
this.props.layout.height.removeListener(this._heightListener);
|
||||
}
|
||||
render() {
|
||||
const cardPosition = Animated.add(this.props.position, new Animated.Value(-this.props.index));
|
||||
const gestureValue = Animated.multiply(cardPosition, this.props.layout.width);
|
||||
return (
|
||||
<Animated.View
|
||||
{...this._responder.panHandlers}
|
||||
style={[
|
||||
styles.card,
|
||||
{
|
||||
right: gestureValue,
|
||||
left: gestureValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, -1],
|
||||
}),
|
||||
opacity: cardPosition.interpolate({
|
||||
inputRange: [-1,0,1],
|
||||
outputRange: [0,1,1],
|
||||
}),
|
||||
}
|
||||
]}>
|
||||
{this.props.children}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NavigationCard = NavigationContainer.create(NavigationCard);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: '#E9E9EF',
|
||||
shadowColor: 'black',
|
||||
shadowOpacity: 0.4,
|
||||
shadowOffset: {width: 0, height: 0},
|
||||
shadowRadius: 10,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
position: 'absolute',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationCard;
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* 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 NavigationHeader
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animated = require('Animated');
|
||||
const Image = require('Image');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const NavigationReducer = require('NavigationReducer');
|
||||
const React = require('react-native');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const Text = require('Text');
|
||||
const TouchableOpacity = require('TouchableOpacity');
|
||||
const View = require('View');
|
||||
|
||||
import type {
|
||||
NavigationState,
|
||||
NavigationParentState
|
||||
} from 'NavigationState';
|
||||
|
||||
type Props = {
|
||||
navigationState: NavigationParentState,
|
||||
onNavigate: Function,
|
||||
position: Animated.Value,
|
||||
getTitle: (navState: NavigationState) => string,
|
||||
};
|
||||
|
||||
class NavigationHeader extends React.Component {
|
||||
_handleBackPress: Function;
|
||||
props: Props;
|
||||
componentWillMount() {
|
||||
this._handleBackPress = this._handleBackPress.bind(this);
|
||||
}
|
||||
render() {
|
||||
var state = this.props.navigationState;
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.header,
|
||||
]}>
|
||||
{state.children.map(this._renderTitle, this)}
|
||||
{this._renderBackButton()}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
_renderBackButton() {
|
||||
if (this.props.navigationState.index === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity style={styles.backButton} onPress={this._handleBackPress}>
|
||||
<Image source={require('./back_chevron.png')} style={styles.backButtonImage} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
_renderTitle(childState, index) {
|
||||
return (
|
||||
<Animated.Text
|
||||
key={childState.key}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
opacity: this.props.position.interpolate({
|
||||
inputRange: [index - 1, index, index + 1],
|
||||
outputRange: [0, 1, 0],
|
||||
}),
|
||||
left: this.props.position.interpolate({
|
||||
inputRange: [index - 1, index + 1],
|
||||
outputRange: [200, -200],
|
||||
}),
|
||||
right: this.props.position.interpolate({
|
||||
inputRange: [index - 1, index + 1],
|
||||
outputRange: [-200, 200],
|
||||
}),
|
||||
},
|
||||
]}>
|
||||
{this.props.getTitle(childState)}
|
||||
</Animated.Text>
|
||||
);
|
||||
}
|
||||
_handleBackPress() {
|
||||
this.props.onNavigate(NavigationReducer.StackReducer.PopAction());
|
||||
}
|
||||
}
|
||||
|
||||
NavigationHeader = NavigationContainer.create(NavigationHeader);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
marginTop: 10,
|
||||
fontSize: 18,
|
||||
fontWeight: '500',
|
||||
color: '#0A0A0A',
|
||||
position: 'absolute',
|
||||
top: 20,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
header: {
|
||||
backgroundColor: '#EFEFF2',
|
||||
paddingTop: 20,
|
||||
top: 0,
|
||||
height: 64,
|
||||
right: 0,
|
||||
left: 0,
|
||||
borderBottomWidth: 0.5,
|
||||
borderBottomColor: '#828287',
|
||||
position: 'absolute',
|
||||
},
|
||||
backButton: {
|
||||
width: 29,
|
||||
height: 37,
|
||||
position: 'absolute',
|
||||
bottom: 4,
|
||||
left: 2,
|
||||
padding: 8,
|
||||
},
|
||||
backButtonImage: {
|
||||
width: 13,
|
||||
height: 21,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationHeader;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 575 B |
225
Libraries/NavigationExperimental/NavigationAnimatedView.js
Normal file
225
Libraries/NavigationExperimental/NavigationAnimatedView.js
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* 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 NavigationAnimatedView
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Animated = require('Animated');
|
||||
var Map = require('Map');
|
||||
var NavigationStateUtils = require('NavigationState');
|
||||
var NavigationContainer = require('NavigationContainer');
|
||||
var React = require('react-native');
|
||||
var View = require('View');
|
||||
|
||||
import type {
|
||||
NavigationState,
|
||||
NavigationParentState,
|
||||
} from 'NavigationState';
|
||||
|
||||
type NavigationScene = {
|
||||
index: number,
|
||||
state: NavigationState,
|
||||
isStale: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to compare route keys (e.g. "9", "11").
|
||||
*/
|
||||
function compareKey(one: string, two: string): number {
|
||||
var delta = one.length - two.length;
|
||||
if (delta > 0) {
|
||||
return 1;
|
||||
}
|
||||
if (delta < 0) {
|
||||
return -1;
|
||||
}
|
||||
return one > two ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to sort scenes based on their index and view key.
|
||||
*/
|
||||
function compareScenes(
|
||||
one: NavigationScene,
|
||||
two: NavigationScene
|
||||
): number {
|
||||
if (one.index > two.index) {
|
||||
return 1;
|
||||
}
|
||||
if (one.index < two.index) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return compareKey(
|
||||
one.state.key,
|
||||
two.state.key
|
||||
);
|
||||
}
|
||||
|
||||
type Layout = {
|
||||
initWidth: number,
|
||||
initHeight: number,
|
||||
width: Animated.Value;
|
||||
height: Animated.Value;
|
||||
};
|
||||
|
||||
type OverlayRenderer = (
|
||||
position: Animated.Value,
|
||||
layout: Layout
|
||||
) => ReactElement;
|
||||
|
||||
type SceneRenderer = (
|
||||
state: NavigationState,
|
||||
index: number,
|
||||
position: Animated.Value,
|
||||
layout: Layout
|
||||
) => ReactElement
|
||||
|
||||
type Props = {
|
||||
navigationState: NavigationParentState;
|
||||
renderScene: SceneRenderer;
|
||||
renderOverlay: ?OverlayRenderer;
|
||||
style: any;
|
||||
};
|
||||
|
||||
class NavigationAnimatedView extends React.Component {
|
||||
_animatedHeight: Animated.Value;
|
||||
_animatedWidth: Animated.Value;
|
||||
_lastHeight: number;
|
||||
_lastWidth: number;
|
||||
props: Props;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._animatedHeight = new Animated.Value(0);
|
||||
this._animatedWidth = new Animated.Value(0);
|
||||
this.state = {
|
||||
position: new Animated.Value(this.props.navigationState.index),
|
||||
scenes: new Map(),
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
scenes: this._reduceScenes(this.state.scenes, this.props.navigationState),
|
||||
});
|
||||
}
|
||||
componentDidMount() {
|
||||
this.postionListener = this.state.position.addListener(this._onProgressChange.bind(this));
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.navigationState !== this.props.navigationState) {
|
||||
this.setState({
|
||||
scenes: this._reduceScenes(this.state.scenes, nextProps.navigationState, this.props.navigationState),
|
||||
});
|
||||
}
|
||||
}
|
||||
componentDidUpdate(lastProps) {
|
||||
if (lastProps.navigationState.index !== this.props.navigationState.index) {
|
||||
Animated.spring(
|
||||
this.state.position,
|
||||
{toValue: this.props.navigationState.index}
|
||||
).start();
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this.postionListener) {
|
||||
this.state.position.removeListener(this.postionListener);
|
||||
this.postionListener = null;
|
||||
}
|
||||
}
|
||||
_onProgressChange(data: Object): void {
|
||||
if (Math.abs(data.value - this.props.navigationState.index) > Number.EPSILON) {
|
||||
return;
|
||||
}
|
||||
this.state.scenes.forEach((scene, index) => {
|
||||
if (scene.isStale) {
|
||||
const scenes = this.state.scenes.slice();
|
||||
scenes.splice(index, 1);
|
||||
this.setState({ scenes, });
|
||||
}
|
||||
});
|
||||
}
|
||||
_reduceScenes(
|
||||
scenes: Array<NavigationScene>,
|
||||
nextState: NavigationParentState,
|
||||
lastState: ?NavigationParentState
|
||||
): Array<NavigationScene> {
|
||||
let nextScenes = nextState.children.map((child, index) => {
|
||||
return {
|
||||
index,
|
||||
state: child,
|
||||
isStale: false,
|
||||
};
|
||||
});
|
||||
|
||||
if (lastState) {
|
||||
lastState.children.forEach((child, index) => {
|
||||
if (!NavigationStateUtils.get(nextState, child.key)) {
|
||||
nextScenes.push({
|
||||
index,
|
||||
state: child,
|
||||
isStale: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextScenes = nextScenes.sort(compareScenes);
|
||||
|
||||
return nextScenes;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<View
|
||||
onLayout={(e) => {
|
||||
const {height, width} = e.nativeEvent.layout;
|
||||
this._animatedHeight &&
|
||||
this._animatedHeight.setValue(height);
|
||||
this._animatedWidth &&
|
||||
this._animatedWidth.setValue(width);
|
||||
this._lastHeight = height;
|
||||
this._lastWidth = width;
|
||||
}}
|
||||
style={this.props.style}>
|
||||
{this.state.scenes.map(this._renderScene, this)}
|
||||
{this._renderOverlay(this._renderOverlay, this)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
_getLayout() {
|
||||
return {
|
||||
height: this._animatedHeight,
|
||||
width: this._animatedWidth,
|
||||
initWidth: this._lastWidth,
|
||||
initHeight: this._lastHeight,
|
||||
};
|
||||
}
|
||||
_renderScene(scene: NavigationScene) {
|
||||
return this.props.renderScene(
|
||||
scene.state,
|
||||
scene.index,
|
||||
this.state.position,
|
||||
this._getLayout()
|
||||
);
|
||||
}
|
||||
_renderOverlay() {
|
||||
const {renderOverlay} = this.props;
|
||||
if (renderOverlay) {
|
||||
return renderOverlay(
|
||||
this.state.position,
|
||||
this._getLayout()
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
NavigationAnimatedView = NavigationContainer.create(NavigationAnimatedView);
|
||||
|
||||
module.exports = NavigationAnimatedView;
|
||||
51
Libraries/NavigationExperimental/NavigationContainer.js
Normal file
51
Libraries/NavigationExperimental/NavigationContainer.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 NavigationContainer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var NavigationRootContainer = require('NavigationRootContainer');
|
||||
|
||||
function createNavigationContainer(Component: React.Component): React.Component {
|
||||
class NavigationComponent extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Component
|
||||
onNavigate={this.getNavigationHandler()}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
getNavigationHandler() {
|
||||
return this.props.onNavigate || this.context.onNavigate;
|
||||
}
|
||||
getChildContext() {
|
||||
return {
|
||||
onNavigate: this.getNavigationHandler(),
|
||||
};
|
||||
}
|
||||
}
|
||||
NavigationComponent.contextTypes = {
|
||||
onNavigate: React.PropTypes.func,
|
||||
};
|
||||
NavigationComponent.childContextTypes = {
|
||||
onNavigate: React.PropTypes.func,
|
||||
};
|
||||
return NavigationComponent;
|
||||
}
|
||||
|
||||
var NavigationContainer = {
|
||||
create: createNavigationContainer,
|
||||
RootContainer: NavigationRootContainer,
|
||||
};
|
||||
|
||||
|
||||
module.exports = NavigationContainer;
|
||||
41
Libraries/NavigationExperimental/NavigationExperimental.js
Normal file
41
Libraries/NavigationExperimental/NavigationExperimental.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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 NavigationExperimental
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NavigationAnimatedView = require('NavigationAnimatedView');
|
||||
const NavigationCard = require('NavigationCard');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const NavigationHeader = require('NavigationHeader');
|
||||
const NavigationRootContainer = require('NavigationRootContainer');
|
||||
const NavigationReducer = require('NavigationReducer');
|
||||
const NavigationState = require('NavigationState');
|
||||
const NavigationView = require('NavigationView');
|
||||
|
||||
const NavigationExperimental = {
|
||||
// Core
|
||||
State: NavigationState,
|
||||
Reducer: NavigationReducer,
|
||||
|
||||
// Containers
|
||||
Container: NavigationContainer,
|
||||
RootContainer: NavigationRootContainer,
|
||||
|
||||
// Views
|
||||
View: NavigationView,
|
||||
AnimatedView: NavigationAnimatedView,
|
||||
|
||||
// CustomComponents:
|
||||
Header: NavigationHeader,
|
||||
Card: NavigationCard,
|
||||
};
|
||||
|
||||
module.exports = NavigationExperimental;
|
||||
86
Libraries/NavigationExperimental/NavigationRootContainer.js
Normal file
86
Libraries/NavigationExperimental/NavigationRootContainer.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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 NavigationRootContainer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AsyncStorage = require('AsyncStorage');
|
||||
var React = require('React');
|
||||
|
||||
import type {
|
||||
NavigationState,
|
||||
NavigationReducer
|
||||
} from 'NavigationState';
|
||||
|
||||
type NavigationRenderer = (
|
||||
navigationState: NavigationState,
|
||||
onNavigate: Function
|
||||
) => ReactElement;
|
||||
|
||||
type Props = {
|
||||
renderNavigation: NavigationRenderer;
|
||||
reducer: NavigationReducer;
|
||||
persistenceKey: ?string;
|
||||
};
|
||||
|
||||
class NavigationRootContainer extends React.Component {
|
||||
props: Props;
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.handleNavigation = this.handleNavigation.bind(this);
|
||||
let navState = null;
|
||||
if (!this.props.persistenceKey) {
|
||||
navState = this.props.reducer(null, null);
|
||||
}
|
||||
this.state = { navState };
|
||||
}
|
||||
componentDidMount() {
|
||||
if (this.props.persistenceKey) {
|
||||
AsyncStorage.getItem(this.props.persistenceKey, (err, storedString) => {
|
||||
if (err || !storedString) {
|
||||
this.setState({
|
||||
navState: this.props.reducer(null, null),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
navState: JSON.parse(storedString),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
getChildContext(): Object {
|
||||
return {
|
||||
onNavigate: this.handleNavigation,
|
||||
};
|
||||
}
|
||||
handleNavigation(action: Object) {
|
||||
const navState = this.props.reducer(this.state.navState, action);
|
||||
this.setState({
|
||||
navState,
|
||||
});
|
||||
if (this.props.persistenceKey) {
|
||||
AsyncStorage.setItem(this.props.persistenceKey, JSON.stringify(navState));
|
||||
}
|
||||
}
|
||||
render(): ReactElement {
|
||||
var navigation = this.props.renderNavigation(
|
||||
this.state.navState,
|
||||
this.handleNavigation
|
||||
);
|
||||
return navigation;
|
||||
}
|
||||
}
|
||||
|
||||
NavigationRootContainer.childContextTypes = {
|
||||
onNavigate: React.PropTypes.func,
|
||||
};
|
||||
|
||||
module.exports = NavigationRootContainer;
|
||||
196
Libraries/NavigationExperimental/NavigationState.js
Normal file
196
Libraries/NavigationExperimental/NavigationState.js
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* 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 NavigationState
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
export type NavigationState = {
|
||||
key: string;
|
||||
};
|
||||
|
||||
export type NavigationParentState = {
|
||||
key: string;
|
||||
index: number;
|
||||
children: Array<NavigationState>;
|
||||
};
|
||||
|
||||
export type NavigationReducer = (
|
||||
state: ?NavigationState,
|
||||
action: ?any
|
||||
) => ?NavigationState;
|
||||
|
||||
export type NavigationReducerWithDefault = (
|
||||
state: ?NavigationState,
|
||||
action: ?any
|
||||
) => NavigationState;
|
||||
|
||||
export function getParent(state: NavigationState): ?NavigationParentState {
|
||||
if (
|
||||
(state instanceof Object) &&
|
||||
(state.children instanceof Array) &&
|
||||
(state.children[0] !== undefined) &&
|
||||
(typeof state.index === 'number') &&
|
||||
(state.children[state.index] !== undefined)
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function get(state: NavigationState, key: string): ?NavigationState {
|
||||
const parentState = getParent(state);
|
||||
if (!parentState) {
|
||||
return null;
|
||||
}
|
||||
const childState = parentState.children.find(child => child.key === key);
|
||||
return childState || null;
|
||||
}
|
||||
|
||||
export function indexOf(state: NavigationState, key: string): ?number {
|
||||
const parentState = getParent(state);
|
||||
if (!parentState) {
|
||||
return null;
|
||||
}
|
||||
const index = parentState.children.map(child => child.key).indexOf(key);
|
||||
if (index === -1) {
|
||||
return null;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
export function push(state: NavigationState, newChildState: NavigationState): NavigationState {
|
||||
const parentState = getParent(state);
|
||||
if (!parentState) {
|
||||
return state;
|
||||
}
|
||||
var lastChildren: Array<NavigationState> = parentState.children;
|
||||
return {
|
||||
...parentState,
|
||||
children: [
|
||||
...lastChildren,
|
||||
newChildState,
|
||||
],
|
||||
index: lastChildren.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function pop(state: NavigationState): NavigationState {
|
||||
const parentState = getParent(state);
|
||||
if (!parentState) {
|
||||
return state;
|
||||
}
|
||||
const lastChildren = parentState.children;
|
||||
return {
|
||||
...parentState,
|
||||
children: lastChildren.slice(0, lastChildren.length - 1),
|
||||
index: lastChildren.length - 2,
|
||||
};
|
||||
}
|
||||
|
||||
export function reset(state: NavigationState, nextChildren: ?Array<NavigationState>, nextIndex: ?number): NavigationState {
|
||||
const parentState = getParent(state);
|
||||
if (!parentState) {
|
||||
return state;
|
||||
}
|
||||
const children = nextChildren || parentState.children;
|
||||
const index = nextIndex == null ? parentState.index : nextIndex;
|
||||
if (children === parentState.children && index === parentState.index) {
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...parentState,
|
||||
children,
|
||||
index,
|
||||
};
|
||||
}
|
||||
|
||||
export function set(state: ?NavigationState, key: string, nextChildren: Array<NavigationState>, nextIndex: number): NavigationState {
|
||||
if (!state) {
|
||||
return {
|
||||
children: nextChildren,
|
||||
index: nextIndex,
|
||||
key,
|
||||
};
|
||||
}
|
||||
const parentState = getParent(state);
|
||||
if (!parentState) {
|
||||
return {
|
||||
children: nextChildren,
|
||||
index: nextIndex,
|
||||
key,
|
||||
};
|
||||
}
|
||||
if (nextChildren === parentState.children && nextIndex === parentState.index && key === parentState.key) {
|
||||
return parentState;
|
||||
}
|
||||
return {
|
||||
...parentState,
|
||||
children: nextChildren,
|
||||
index: nextIndex,
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
||||
export function jumpToIndex(state: NavigationState, index: number): NavigationState {
|
||||
const parentState = getParent(state);
|
||||
return {
|
||||
...parentState,
|
||||
index,
|
||||
};
|
||||
}
|
||||
|
||||
export function jumpTo(state: NavigationState, key: string): NavigationState {
|
||||
const parentState = getParent(state);
|
||||
if (!parentState) {
|
||||
return state;
|
||||
}
|
||||
const index = parentState.children.indexOf(parentState.children.find(child => child.key === key));
|
||||
invariant(
|
||||
index !== -1,
|
||||
'Cannot find child with matching key in this NavigationState'
|
||||
);
|
||||
return {
|
||||
...parentState,
|
||||
index,
|
||||
};
|
||||
}
|
||||
|
||||
export function replaceAt(state: NavigationState, key: string, newState: NavigationState): NavigationState {
|
||||
const parentState = getParent(state);
|
||||
if (!parentState) {
|
||||
return state;
|
||||
}
|
||||
const children = [...parentState.children];
|
||||
const index = parentState.children.indexOf(parentState.children.find(child => child.key === key));
|
||||
invariant(
|
||||
index !== -1,
|
||||
'Cannot find child with matching key in this NavigationState'
|
||||
);
|
||||
children[index] = newState;
|
||||
return {
|
||||
...parentState,
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
export function replaceAtIndex(state: NavigationState, index: number, newState: NavigationState): NavigationState {
|
||||
const parentState = getParent(state);
|
||||
if (!parentState) {
|
||||
return state;
|
||||
}
|
||||
const children = [...parentState.children];
|
||||
children[index] = newState;
|
||||
return {
|
||||
...parentState,
|
||||
children,
|
||||
};
|
||||
}
|
||||
63
Libraries/NavigationExperimental/NavigationView.js
Normal file
63
Libraries/NavigationExperimental/NavigationView.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 NavigationView
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var NavigationContainer = require('NavigationContainer');
|
||||
var {
|
||||
StyleSheet,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var NavigationView = React.createClass({
|
||||
propTypes: {
|
||||
// todo, figure out a propType for getK
|
||||
navigationState: React.PropTypes.object.isRequired,
|
||||
renderScene: React.PropTypes.func.isRequired,
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View
|
||||
style={this.props.style}>
|
||||
{this.props.navigationState.children.map(this._renderScene)}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
_renderScene: function(route, index) {
|
||||
var isSelected = index === this.props.navigationState.index;
|
||||
return (
|
||||
<View
|
||||
key={route.key}
|
||||
pointerEvents={isSelected ? 'auto' : 'none'}
|
||||
style={[
|
||||
styles.navView,
|
||||
{opacity: isSelected ? 1 : 0},
|
||||
]}>
|
||||
{this.props.renderScene(route, index)}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
NavigationView = NavigationContainer.create(NavigationView);
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
navView: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationView;
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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 NavigationFindReducer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* NavigationFindReducer takes an array of reducers, and returns a reducer that
|
||||
* iterates through all of the reducers and the result of the first reducer
|
||||
* that modifies the input
|
||||
*/
|
||||
|
||||
import type {
|
||||
NavigationState,
|
||||
NavigationReducer
|
||||
} from 'NavigationState';
|
||||
|
||||
function NavigationFindReducer(reducers: Array<NavigationReducer>): ?NavigationReducer {
|
||||
return function(lastState: ?NavigationState, action: ?any): ?NavigationState {
|
||||
for (let i = 0; i < reducers.length; i++) {
|
||||
let reducer = reducers[i];
|
||||
let newState = reducer(lastState, action);
|
||||
if (newState !== lastState) {
|
||||
return newState;
|
||||
}
|
||||
}
|
||||
return lastState;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = NavigationFindReducer;
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 NavigationReducer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NavigationFindReducer = require('NavigationFindReducer');
|
||||
var NavigationStackReducer = require('NavigationStackReducer');
|
||||
var NavigationTabsReducer = require('NavigationTabsReducer');
|
||||
|
||||
const NavigationReducer = {
|
||||
FindReducer: NavigationFindReducer,
|
||||
StackReducer: NavigationStackReducer,
|
||||
TabsReducer: NavigationTabsReducer,
|
||||
};
|
||||
|
||||
module.exports = NavigationReducer;
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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 NavigationStackReducer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NavigationStateUtils = require('NavigationState');
|
||||
|
||||
import type {
|
||||
NavigationState,
|
||||
NavigationReducer,
|
||||
} from 'NavigationState';
|
||||
|
||||
export type NavigationStackReducerAction = {
|
||||
type: string,
|
||||
};
|
||||
|
||||
const ActionTypes = {
|
||||
PUSH: 'react-native/NavigationExperimental/stack-push',
|
||||
POP: 'react-native/NavigationExperimental/stack-pop',
|
||||
JUMP_TO: 'react-native/NavigationExperimental/stack-jumpTo',
|
||||
JUMP_TO_INDEX: 'react-native/NavigationExperimental/stack-jumpToIndex',
|
||||
RESET: 'react-native/NavigationExperimental/stack-reset',
|
||||
};
|
||||
|
||||
const DEFAULT_KEY = 'NAV_STACK_DEFAULT_KEY';
|
||||
|
||||
function NavigationStackPushAction(state: NavigationState): NavigationStackReducerAction {
|
||||
return {
|
||||
type: ActionTypes.PUSH,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
function NavigationStackPopAction(): NavigationStackReducerAction {
|
||||
return {
|
||||
type: ActionTypes.POP,
|
||||
};
|
||||
}
|
||||
|
||||
function NavigationStackJumpToAction(key: string): NavigationStackReducerAction {
|
||||
return {
|
||||
type: ActionTypes.JUMP_TO,
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
||||
function NavigationStackJumpToIndexAction(index: number): NavigationStackReducerAction {
|
||||
return {
|
||||
type: ActionTypes.JUMP_TO_INDEX,
|
||||
index,
|
||||
};
|
||||
}
|
||||
|
||||
function NavigationStackResetAction(children: Array<NavigationState>, index: number): NavigationStackReducerAction {
|
||||
return {
|
||||
type: ActionTypes.RESET,
|
||||
index,
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
type StackReducerConfig = {
|
||||
initialStates: Array<NavigationState>;
|
||||
initialIndex: ?number;
|
||||
key: ?string;
|
||||
matchAction: (action: any) => boolean;
|
||||
actionStateMap: (action: any) => NavigationState;
|
||||
};
|
||||
|
||||
function NavigationStackReducer({initialStates, initialIndex, key, matchAction, actionStateMap}: StackReducerConfig): NavigationReducer {
|
||||
return function (lastState: ?NavigationState, action: any): NavigationState {
|
||||
if (key == null) {
|
||||
key = DEFAULT_KEY;
|
||||
}
|
||||
if (initialIndex == null) {
|
||||
initialIndex = initialStates.length - 1;
|
||||
}
|
||||
if (!lastState) {
|
||||
lastState = {
|
||||
index: initialIndex,
|
||||
children: initialStates,
|
||||
key,
|
||||
};
|
||||
}
|
||||
const lastParentState = NavigationStateUtils.getParent(lastState);
|
||||
if (!action || !lastParentState) {
|
||||
return lastState;
|
||||
}
|
||||
switch (action.type) {
|
||||
case ActionTypes.PUSH:
|
||||
return NavigationStateUtils.push(
|
||||
lastParentState,
|
||||
action.state
|
||||
);
|
||||
case ActionTypes.POP:
|
||||
if (lastParentState.index === 0 || lastParentState.children.length === 1) {
|
||||
return lastParentState;
|
||||
}
|
||||
return NavigationStateUtils.pop(lastParentState);
|
||||
case ActionTypes.JUMP_TO:
|
||||
return NavigationStateUtils.jumpTo(
|
||||
lastParentState,
|
||||
action.key
|
||||
);
|
||||
case ActionTypes.JUMP_TO_INDEX:
|
||||
return NavigationStateUtils.jumpToIndex(
|
||||
lastParentState,
|
||||
action.index
|
||||
);
|
||||
case ActionTypes.RESET:
|
||||
return {
|
||||
...lastParentState,
|
||||
index: action.index,
|
||||
children: action.children,
|
||||
};
|
||||
}
|
||||
if (matchAction(action)) {
|
||||
return NavigationStateUtils.push(
|
||||
lastParentState,
|
||||
actionStateMap(action)
|
||||
);
|
||||
}
|
||||
return lastParentState;
|
||||
};
|
||||
}
|
||||
|
||||
NavigationStackReducer.PushAction = NavigationStackPushAction;
|
||||
NavigationStackReducer.PopAction = NavigationStackPopAction;
|
||||
NavigationStackReducer.JumpToAction = NavigationStackJumpToAction;
|
||||
NavigationStackReducer.JumpToIndexAction = NavigationStackJumpToIndexAction;
|
||||
NavigationStackReducer.ResetAction = NavigationStackResetAction;
|
||||
|
||||
module.exports = NavigationStackReducer;
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 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 NavigationTabsReducer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NavigationFindReducer = require('NavigationFindReducer');
|
||||
const NavigationStateUtils = require('NavigationState');
|
||||
|
||||
import type {
|
||||
NavigationReducer,
|
||||
NavigationReducerWithDefault,
|
||||
NavigationState
|
||||
} from 'NavigationState';
|
||||
|
||||
const ActionTypes = {
|
||||
JUMP_TO: 'react-native/NavigationExperimental/tabs-jumpTo',
|
||||
ON_TAB_ACTION: 'react-native/NavigationExperimental/tabs-onTabAction',
|
||||
};
|
||||
|
||||
const DEFAULT_KEY = 'TABS_STATE_DEFAULT_KEY';
|
||||
|
||||
export type JumpToAction = {
|
||||
type: typeof ActionTypes.JUMP_TO,
|
||||
index: number,
|
||||
};
|
||||
function NavigationTabsJumpToAction(index: number): JumpToAction {
|
||||
return {
|
||||
type: ActionTypes.JUMP_TO,
|
||||
index,
|
||||
};
|
||||
}
|
||||
|
||||
export type OnTabAction = {
|
||||
type: string,
|
||||
index: number,
|
||||
action: any,
|
||||
};
|
||||
function NavigationTabsOnTabAction(index: number, action: any): OnTabAction {
|
||||
return {
|
||||
type: ActionTypes.ON_TAB_ACTION,
|
||||
index,
|
||||
action,
|
||||
};
|
||||
}
|
||||
|
||||
type TabsReducerConfig = {
|
||||
key: string;
|
||||
initialIndex: ?number;
|
||||
tabReducers: Array<NavigationReducerWithDefault>;
|
||||
};
|
||||
|
||||
function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConfig): NavigationReducer {
|
||||
if (initialIndex == null) {
|
||||
initialIndex = 0;
|
||||
}
|
||||
if (key == null) {
|
||||
key = DEFAULT_KEY;
|
||||
}
|
||||
return function(lastNavState: ?NavigationState, action: ?any): ?NavigationState {
|
||||
if (!lastNavState) {
|
||||
lastNavState = {
|
||||
children: tabReducers.map(reducer => reducer(null, null)),
|
||||
index: initialIndex,
|
||||
key,
|
||||
};
|
||||
}
|
||||
const lastParentNavState = NavigationStateUtils.getParent(lastNavState);
|
||||
if (!action || !lastParentNavState) {
|
||||
return lastNavState;
|
||||
}
|
||||
if (
|
||||
action.type === ActionTypes.JUMP_TO &&
|
||||
action.index !== lastParentNavState.index
|
||||
) {
|
||||
return NavigationStateUtils.jumpToIndex(
|
||||
lastParentNavState,
|
||||
action.index,
|
||||
);
|
||||
}
|
||||
if (action.type === ActionTypes.ON_TAB_ACTION) {
|
||||
const onTabAction: OnTabAction = action;
|
||||
const lastTabRoute = lastParentNavState.children[onTabAction.index];
|
||||
const tabReducer = tabReducers[onTabAction.index];
|
||||
if (tabReducer) {
|
||||
const newTabRoute = tabReducer(lastTabRoute, action.action);
|
||||
if (newTabRoute && newTabRoute !== lastTabRoute) {
|
||||
let navState = NavigationStateUtils.replaceAtIndex(
|
||||
lastParentNavState,
|
||||
onTabAction.index,
|
||||
newTabRoute
|
||||
);
|
||||
navState = NavigationStateUtils.jumpToIndex(
|
||||
navState,
|
||||
onTabAction.index
|
||||
);
|
||||
return navState;
|
||||
}
|
||||
}
|
||||
}
|
||||
const subReducers = tabReducers.map((tabReducer, tabIndex) => {
|
||||
return function reduceTab(lastTabState: ?NavigationState, tabAction: ?any): ?NavigationState {
|
||||
if (!lastTabState) {
|
||||
return tabReducer(lastTabState, tabAction);
|
||||
}
|
||||
if (!lastParentNavState) {
|
||||
return lastTabState;
|
||||
}
|
||||
const lastSubTabState = lastParentNavState.children[tabIndex];
|
||||
const nextSubTabState = tabReducer(lastSubTabState, tabAction);
|
||||
if (nextSubTabState && lastSubTabState !== nextSubTabState) {
|
||||
const tabs = lastParentNavState.children;
|
||||
tabs[tabIndex] = nextSubTabState;
|
||||
return {
|
||||
...lastParentNavState,
|
||||
tabs,
|
||||
index: tabIndex,
|
||||
};
|
||||
}
|
||||
return lastParentNavState;
|
||||
};
|
||||
});
|
||||
let selectedTabReducer = subReducers.splice(lastParentNavState.index, 1)[0];
|
||||
subReducers.unshift(selectedTabReducer);
|
||||
const findReducer = NavigationFindReducer(subReducers);
|
||||
if (findReducer) {
|
||||
return findReducer(lastParentNavState, action);
|
||||
}
|
||||
return lastParentNavState;
|
||||
};
|
||||
}
|
||||
|
||||
NavigationTabsReducer.JumpToAction = NavigationTabsJumpToAction;
|
||||
NavigationTabsReducer.OnTabAction = NavigationTabsOnTabAction;
|
||||
|
||||
module.exports = NavigationTabsReducer;
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow-broken
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.autoMockOff()
|
||||
.mock('ErrorUtils');
|
||||
|
||||
const NavigationFindReducer = require('NavigationFindReducer');
|
||||
|
||||
describe('NavigationFindReducer', () => {
|
||||
|
||||
it('handles basic find reducing with strings', () => {
|
||||
let reducer = NavigationFindReducer([
|
||||
s => s,
|
||||
s => s + '_yes',
|
||||
s => 'nope',
|
||||
]);
|
||||
let route = reducer('input');
|
||||
expect(route).toBe('input_yes');
|
||||
|
||||
reducer = NavigationFindReducer([
|
||||
(s, action) => s,
|
||||
(s, action) => 'origRoute',
|
||||
(s, action) => 'firstChangedState',
|
||||
]);
|
||||
route = reducer('origRoute', 'action1');
|
||||
expect(route).toBe('firstChangedState');
|
||||
|
||||
reducer = NavigationFindReducer([
|
||||
(s, action) => s,
|
||||
(s, action) => action,
|
||||
]);
|
||||
route = reducer('inputState', 'action2');
|
||||
expect(route).toBe('action2');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow-broken
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.autoMockOff()
|
||||
.mock('ErrorUtils');
|
||||
|
||||
const NavigationStackReducer = require('NavigationStackReducer');
|
||||
|
||||
const {
|
||||
JumpToAction,
|
||||
JumpToIndexAction,
|
||||
PopAction,
|
||||
PushAction,
|
||||
ResetAction,
|
||||
} = NavigationStackReducer;
|
||||
|
||||
describe('NavigationStackReducer', () => {
|
||||
|
||||
it('handles PushAction', () => {
|
||||
const initialStates = [
|
||||
{key: 'route0'},
|
||||
{key: 'route1'},
|
||||
];
|
||||
let reducer = NavigationStackReducer({
|
||||
initialStates,
|
||||
matchAction: () => true,
|
||||
actionStateMap: (action) => action,
|
||||
});
|
||||
|
||||
let state = reducer();
|
||||
expect(state.children).toBe(initialStates);
|
||||
expect(state.index).toBe(1);
|
||||
expect(state.key).toBe('NAV_STACK_DEFAULT_KEY');
|
||||
|
||||
state = reducer(state, PushAction({key: 'route2'}));
|
||||
expect(state.children[0].key).toBe('route0');
|
||||
expect(state.children[1].key).toBe('route1');
|
||||
expect(state.children[2].key).toBe('route2');
|
||||
expect(state.index).toBe(2);
|
||||
});
|
||||
|
||||
it('handles PopAction', () => {
|
||||
let reducer = NavigationStackReducer({
|
||||
initialStates: [
|
||||
{key: 'a'},
|
||||
{key: 'b'},
|
||||
],
|
||||
initialIndex: 1,
|
||||
key: 'myStack',
|
||||
matchAction: () => true,
|
||||
actionStateMap: (action) => action,
|
||||
});
|
||||
|
||||
let state = reducer();
|
||||
expect(state.children[0].key).toBe('a');
|
||||
expect(state.children[1].key).toBe('b');
|
||||
expect(state.children.length).toBe(2);
|
||||
expect(state.index).toBe(1);
|
||||
expect(state.key).toBe('myStack');
|
||||
|
||||
state = reducer(state, PopAction());
|
||||
expect(state.children[0].key).toBe('a');
|
||||
expect(state.children.length).toBe(1);
|
||||
expect(state.index).toBe(0);
|
||||
expect(state.key).toBe('myStack');
|
||||
|
||||
// make sure Pop on an single-route state is a no-op
|
||||
state = reducer(state, PopAction());
|
||||
expect(state.children[0].key).toBe('a');
|
||||
expect(state.children.length).toBe(1);
|
||||
expect(state.index).toBe(0);
|
||||
expect(state.key).toBe('myStack');
|
||||
});
|
||||
|
||||
it('handles JumpToAction', () => {
|
||||
let reducer = NavigationStackReducer({
|
||||
initialStates: [
|
||||
{key: 'a'},
|
||||
{key: 'b'},
|
||||
{key: 'c'},
|
||||
],
|
||||
initialIndex: 0,
|
||||
key: 'myStack',
|
||||
matchAction: () => true,
|
||||
actionStateMap: (action) => action,
|
||||
});
|
||||
|
||||
let state = reducer();
|
||||
expect(state.children[0].key).toBe('a');
|
||||
expect(state.children[1].key).toBe('b');
|
||||
expect(state.children[2].key).toBe('c');
|
||||
expect(state.children.length).toBe(3);
|
||||
expect(state.index).toBe(0);
|
||||
|
||||
state = reducer(state, JumpToAction('b'));
|
||||
expect(state.children[0].key).toBe('a');
|
||||
expect(state.children[1].key).toBe('b');
|
||||
expect(state.children[2].key).toBe('c');
|
||||
expect(state.children.length).toBe(3);
|
||||
expect(state.index).toBe(1);
|
||||
|
||||
state = reducer(state, JumpToAction('c'));
|
||||
expect(state.children[0].key).toBe('a');
|
||||
expect(state.children[1].key).toBe('b');
|
||||
expect(state.children[2].key).toBe('c');
|
||||
expect(state.children.length).toBe(3);
|
||||
expect(state.index).toBe(2);
|
||||
|
||||
state = reducer(state, JumpToAction('c'));
|
||||
expect(state.children[0].key).toBe('a');
|
||||
expect(state.children[1].key).toBe('b');
|
||||
expect(state.children[2].key).toBe('c');
|
||||
expect(state.children.length).toBe(3);
|
||||
expect(state.index).toBe(2);
|
||||
expect(state.key).toBe('myStack');
|
||||
});
|
||||
|
||||
it('handles JumpToIndexAction', () => {
|
||||
let reducer = NavigationStackReducer({
|
||||
initialStates: [
|
||||
{key: 'a'},
|
||||
{key: 'b'},
|
||||
{key: 'c'},
|
||||
],
|
||||
initialIndex: 2,
|
||||
key: 'myStack',
|
||||
matchAction: () => true,
|
||||
actionStateMap: (action) => action,
|
||||
});
|
||||
|
||||
let state = reducer();
|
||||
expect(state.children.length).toBe(3);
|
||||
expect(state.index).toBe(2);
|
||||
|
||||
state = reducer(state, JumpToIndexAction(0));
|
||||
expect(state.children.length).toBe(3);
|
||||
expect(state.index).toBe(0);
|
||||
|
||||
state = reducer(state, JumpToIndexAction(1));
|
||||
expect(state.children[0].key).toBe('a');
|
||||
expect(state.children[1].key).toBe('b');
|
||||
expect(state.children[2].key).toBe('c');
|
||||
expect(state.children.length).toBe(3);
|
||||
expect(state.index).toBe(1);
|
||||
expect(state.key).toBe('myStack');
|
||||
});
|
||||
|
||||
it('handles ResetAction', () => {
|
||||
let reducer = NavigationStackReducer({
|
||||
initialStates: [
|
||||
{key: 'a'},
|
||||
{key: 'b'},
|
||||
],
|
||||
initialIndex: 1,
|
||||
key: 'myStack',
|
||||
matchAction: () => true,
|
||||
actionStateMap: (action) => action,
|
||||
});
|
||||
|
||||
let state = reducer();
|
||||
expect(state.children[0].key).toBe('a');
|
||||
expect(state.children[1].key).toBe('b');
|
||||
expect(state.children.length).toBe(2);
|
||||
expect(state.index).toBe(1);
|
||||
|
||||
state = reducer(state, ResetAction([{key: 'c'}, {key: 'd'}], 0));
|
||||
expect(state.children[0].key).toBe('c');
|
||||
expect(state.children[1].key).toBe('d');
|
||||
expect(state.children.length).toBe(2);
|
||||
expect(state.index).toBe(0);
|
||||
|
||||
const newStates = [
|
||||
{key: 'e'},
|
||||
{key: 'f'},
|
||||
{key: 'g'},
|
||||
];
|
||||
|
||||
state = reducer(state, ResetAction(newStates, 1));
|
||||
expect(state.children[0].key).toBe('e');
|
||||
expect(state.children[1].key).toBe('f');
|
||||
expect(state.children[2].key).toBe('g');
|
||||
expect(state.children.length).toBe(3);
|
||||
expect(state.index).toBe(1);
|
||||
expect(state.key).toBe('myStack');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow-broken
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.autoMockOff()
|
||||
.mock('ErrorUtils');
|
||||
|
||||
const NavigationTabsReducer = require('NavigationTabsReducer');
|
||||
|
||||
const {
|
||||
JumpToAction,
|
||||
} = NavigationTabsReducer;
|
||||
|
||||
describe('NavigationTabsReducer', () => {
|
||||
|
||||
it('handles JumpTo with index', () => {
|
||||
let reducer = NavigationTabsReducer({
|
||||
tabReducers: [
|
||||
(tabState, action) => tabState || 'a',
|
||||
(tabState, action) => tabState || 'b',
|
||||
(tabState, action) => tabState || 'c',
|
||||
],
|
||||
initialIndex: 1,
|
||||
});
|
||||
|
||||
let navState = reducer();
|
||||
|
||||
expect(navState.children[0]).toBe('a');
|
||||
expect(navState.children[1]).toBe('b');
|
||||
expect(navState.children[2]).toBe('c');
|
||||
expect(navState.children.length).toBe(3);
|
||||
expect(navState.index).toBe(1);
|
||||
|
||||
navState = reducer(
|
||||
navState,
|
||||
JumpToAction(2)
|
||||
);
|
||||
|
||||
expect(navState.children[0]).toEqual('a');
|
||||
expect(navState.children[1]).toEqual('b');
|
||||
expect(navState.children[2]).toEqual('c');
|
||||
expect(navState.children.length).toBe(3);
|
||||
expect(navState.index).toBe(2);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow-broken
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.autoMockOff()
|
||||
.mock('ErrorUtils');
|
||||
|
||||
var NavigationState = require('NavigationState');
|
||||
|
||||
var VALID_PARENT_STATES = [
|
||||
{children: ['a','b'], index: 0},
|
||||
{children: [{key: 'a'},{key: 'b', foo: 123}], index: 1},
|
||||
{children: [{key: 'a'},{key: 'b'}], index: 0},
|
||||
{children: [{key: 'a'},{key: 'b'}], index: 2},
|
||||
];
|
||||
var INVALID_PARENT_STATES = [
|
||||
'foo',
|
||||
{},
|
||||
{children: [{key: 'a'}], index: 4},
|
||||
{children: [{key: 'a'}], index: -1},
|
||||
{children: [{key: 'a'}]},
|
||||
{children: {key: 'foo'}},
|
||||
12,
|
||||
null,
|
||||
undefined,
|
||||
[],
|
||||
];
|
||||
|
||||
describe('NavigationState', () => {
|
||||
|
||||
it('identifies parents correctly with getParent', () => {
|
||||
for (var i = 0; i <= VALID_PARENT_STATES.length; i++) {
|
||||
var navState = VALID_PARENT_STATES[0];
|
||||
expect(NavigationState.getParent(navState)).toBe(navState);
|
||||
}
|
||||
for (var i = 0; i <= INVALID_PARENT_STATES.length; i++) {
|
||||
var navState = INVALID_PARENT_STATES[0];
|
||||
expect(NavigationState.getParent(navState)).toBe(null);
|
||||
}
|
||||
});
|
||||
|
||||
it('can get children', () => {
|
||||
var fooState = {key: 'foo'};
|
||||
var navState = {children: [{key: 'foobar'}, fooState], index: 0};
|
||||
expect(NavigationState.get(navState, 'foo')).toBe(fooState);
|
||||
expect(NavigationState.get(navState, 'missing')).toBe(null);
|
||||
});
|
||||
});
|
||||
1
Libraries/react-native/react-native.js
vendored
1
Libraries/react-native/react-native.js
vendored
@@ -76,6 +76,7 @@ var ReactNative = {
|
||||
get LayoutAnimation() { return require('LayoutAnimation'); },
|
||||
get Linking() { return require('Linking'); },
|
||||
get LinkingIOS() { return require('LinkingIOS'); },
|
||||
get NavigationExperimental() { return require('NavigationExperimental'); },
|
||||
get NetInfo() { return require('NetInfo'); },
|
||||
get PanResponder() { return require('PanResponder'); },
|
||||
get PixelRatio() { return require('PixelRatio'); },
|
||||
|
||||
Reference in New Issue
Block a user