UNPKG

@exponent/ex-navigation

Version:

Route-centric navigation library for React Native.

334 lines (273 loc) 9.53 kB
import _ from 'lodash'; import { NavigationExperimental, } from 'react-native'; import invariant from 'invariant'; import ActionTypes from './ExNavigationActionTypes'; const { StateUtils: NavigationStateUtils, } = NavigationExperimental; const INITIAL_STATE = { navigators: {}, alerts: {}, currentNavigatorUID: null, }; let navigatorsToRestore = []; class ExNavigationReducer { static reduce(state = null, action) { if (!ExNavigationReducer[action.type]) { return state; } const newState = ExNavigationReducer[action.type](state, action); return newState; } static [ActionTypes.INITIALIZE](state, action) { return INITIAL_STATE; } static [ActionTypes.SET_CURRENT_NAVIGATOR](state, { navigatorUID, parentNavigatorUID, navigatorType, defaultRouteConfig, routes, index }) { if (!state.navigators[navigatorUID] && !routes) { return state; } let newState = { currentNavigatorUID: navigatorUID, }; if (routes) { const navigatorState = state.navigators[navigatorUID]; routes = routes.map(child => { if (child.clone) { const newChild = child.clone(); newChild.config = _.merge({}, defaultRouteConfig, child.config); return newChild; } else { return child; } }); newState = { ...newState, navigators: { ...state.navigators, [navigatorUID]: { routes: routes, index: index, ...(parentNavigatorUID ? { parentNavigatorUID } : null), defaultRouteConfig, type: navigatorType, }, }, }; } return { ...state, ...newState, }; } static [ActionTypes.REMOVE_NAVIGATOR](state, { navigatorUID }) { const currentNavigatorUID = (navigatorsToRestore.length && navigatorsToRestore[navigatorsToRestore.length - 1]) || state.navigators[navigatorUID].parentNavigatorUID; navigatorsToRestore.pop(); return { ...state, currentNavigatorUID, navigators: _.omit(state.navigators, navigatorUID), }; } static [ActionTypes.PUSH](state, { navigatorUID, child }) { const navigatorState = state.navigators[navigatorUID] || { routes: [], key: navigatorUID, index: 0, defaultRouteConfig: {}, type: 'stack', }; if (navigatorUID !== state.currentNavigatorUID) { navigatorsToRestore.push(state.currentNavigatorUID); } const defaultRouteConfig = navigatorState.defaultRouteConfig; const newChild = child.clone(); newChild.config = _.merge({}, defaultRouteConfig, child.config); return { ..._updateNavigator(state, navigatorUID, NavigationStateUtils.push(navigatorState, newChild)), currentNavigatorUID: navigatorUID, }; } static [ActionTypes.REPLACE](state, { navigatorUID, child }) { invariant(state.navigators[navigatorUID], 'Navigator does not exist.'); const navigatorState = state.navigators[navigatorUID]; const index = navigatorState.index; const defaultRouteConfig = navigatorState.defaultRouteConfig; const newChild = child.clone(); newChild.config = _.merge({}, defaultRouteConfig, child.config); return _updateNavigator( state, navigatorUID, NavigationStateUtils.replaceAtIndex(navigatorState, index, newChild) ); } static [ActionTypes.POP](state, { navigatorUID }) { invariant(state.navigators[navigatorUID], 'Navigator does not exist.'); const navigatorState = state.navigators[navigatorUID]; if (navigatorState.index === 0) { return state; } if (navigatorState.type === 'slidingTab') { return _updateNavigator( state, navigatorUID, {...navigatorState, index: 0 }, ); } return _updateNavigator(state, navigatorUID, NavigationStateUtils.pop(navigatorState)); } static [ActionTypes.POP_N](state, { navigatorUID, n }) { invariant(state.navigators[navigatorUID], 'Navigator does not exist.'); const navigatorState = state.navigators[navigatorUID]; if (navigatorState.index === 0) { return state; } if (navigatorState.type === 'slidingTab') { return _updateNavigator( state, navigatorUID, {...navigatorState, index: 0 }, ); } let sliceTo; if (n > navigatorState.routes.length) { if (__DEV__) { console.warn('Tried to pop ' + n + ' routes, but only ' + navigatorState.routes.length + ' routes are available on the stack.'); } sliceTo = 1; } else { sliceTo = -n; } const routes = navigatorState.routes.slice(0, sliceTo); const newNavigatorState = { ...navigatorState, index: routes.length - 1, routes }; return _updateNavigator(state, navigatorUID, newNavigatorState); } static [ActionTypes.POP_TO_TOP](state, { navigatorUID }) { invariant(state.navigators[navigatorUID], 'Navigator does not exist.'); const navigatorState = state.navigators[navigatorUID]; if (navigatorState.index === 0) { return state; } if (navigatorState.type === 'slidingTab') { return _updateNavigator( state, navigatorUID, {...navigatorState, index: 0 }, ); } const routes = navigatorState.routes.slice(0, 1); const newNavigatorState = { ...navigatorState, index: 0, routes }; return _updateNavigator(state, navigatorUID, newNavigatorState); } static [ActionTypes.SHOW_LOCAL_ALERT_BAR](state, { navigatorUID, message, options }) { let alertState = { message, options, }; return { ..._updateAlert(state, navigatorUID, alertState), }; } static [ActionTypes.HIDE_LOCAL_ALERT_BAR](state, { navigatorUID }) { let alertState = null; return { ..._updateAlert(state, navigatorUID, alertState), }; } static [ActionTypes.TOGGLE_DRAWER](state, { navigatorUID }) { invariant(state.navigators[navigatorUID], 'Navigator does not exist.'); const navigatorState = state.navigators[navigatorUID]; if (navigatorState.index === 0) { return state; } return _updateNavigator(state, navigatorUID, NavigationStateUtils.pop(navigatorState)); } static [ActionTypes.IMMEDIATELY_RESET_STACK](state, { navigatorUID, routes, index }) { const navigatorState = state.navigators[navigatorUID] || { routes: [], index: 0, key: navigatorUID, defaultRouteConfig: {}, type: 'stack', }; const defaultRouteConfig = navigatorState.defaultRouteConfig; const newChildren = routes.map(child => { const newChild = child.clone(); newChild.config = _.merge({}, defaultRouteConfig, child.config); return newChild; }); return { ..._updateNavigator(state, navigatorUID, NavigationStateUtils.reset(navigatorState, newChildren, index)), currentNavigatorUID: navigatorUID, }; } static [ActionTypes.UPDATE_ROUTE_AT_INDEX](state, { navigatorUID, index, newRoute }) { invariant(state.navigators[navigatorUID], 'Navigator does not exist.'); const navigatorState = state.navigators[navigatorUID]; return _updateNavigator(state, navigatorUID, NavigationStateUtils.replaceAtIndex(navigatorState, index, newRoute)); } static [ActionTypes.JUMP_TO_ITEM](state, { navigatorUID, item }) { invariant(state.navigators[navigatorUID], 'Navigator does not exist.'); invariant(state.navigators[navigatorUID].type === 'drawer', 'Navigator is not drawer navigator.'); return _updateSelectedKey(item, state, navigatorUID); } static [ActionTypes.JUMP_TO_TAB](state, { navigatorUID, tab }) { let navigator = state.navigators[navigatorUID]; invariant(navigator, 'Navigator does not exist.'); let { type } = navigator; invariant(['slidingTab', 'tab'].indexOf(type) !== -1, 'Navigator is not tab navigator.'); if (type === 'tab') { return _updateSelectedKey(tab, state, navigatorUID); } else if (type === 'slidingTab') { let route = navigator.routes.find(r => r.key === tab.key); let index = navigator.routes.indexOf(route); // With slidingTab we only need to change the index return _updateNavigator( state, navigatorUID, {...navigator, index }, ); } } } export default ExNavigationReducer.reduce; function _updateSelectedKey(target, state, navigatorUID) { const newNavigatorState = { ...state.navigators[navigatorUID] }; const selected = newNavigatorState.routes[newNavigatorState.index]; if (target.key === selected.key) { // haven't changed sections return state; } let targetIndex = NavigationStateUtils.indexOf(newNavigatorState, target.key); if (targetIndex !== -1) { const old = newNavigatorState.routes[targetIndex]; newNavigatorState.routes.splice(targetIndex, 1); newNavigatorState.routes.push(old); } else { newNavigatorState.routes.push(target); } newNavigatorState.index = newNavigatorState.routes.length - 1; return { ..._updateNavigator(state, navigatorUID, newNavigatorState), currentNavigatorUID: navigatorUID, }; } function _updateAlert(state, navigatorUID, newState) { return { ...state, alerts: { ...state.alerts, [navigatorUID]: newState, }, }; } function _updateNavigator(state, navigatorUID, newState) { return { ...state, navigators: { ...state.navigators, [navigatorUID]: newState, }, }; }