expo-router
Version:
Expo Router is a file-based router for React Native and web applications.
311 lines (309 loc) • 13.3 kB
JavaScript
'use client';
;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "BottomTabView", {
enumerable: true,
get: function() {
return BottomTabView;
}
});
function _jsxruntime() {
const data = require("react/jsx-runtime");
_jsxruntime = function() {
return data;
};
return data;
}
function _react() {
const data = /*#__PURE__*/ _interop_require_wildcard(require("react"));
_react = function() {
return data;
};
return data;
}
function _reactnative() {
const data = require("react-native");
_reactnative = function() {
return data;
};
return data;
}
function _reactnativesafeareacontext() {
const data = require("react-native-safe-area-context");
_reactnativesafeareacontext = function() {
return data;
};
return data;
}
const _elements = require("../../elements");
const _TransitionPresets = require("../TransitionConfigs/TransitionPresets");
const _BottomTabBarHeightCallbackContext = require("../utils/BottomTabBarHeightCallbackContext");
const _BottomTabBarHeightContext = require("../utils/BottomTabBarHeightContext");
const _useAnimatedHashMap = require("../utils/useAnimatedHashMap");
const _BottomTabBar = require("./BottomTabBar");
const _ScreenFallback = require("./ScreenFallback");
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function(nodeInterop) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
return {
default: obj
};
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {
__proto__: null
};
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
for(var key in obj){
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
const STATE_INACTIVE = 0;
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
const STATE_ON_TOP = 2;
const NAMED_TRANSITIONS_PRESETS = {
fade: _TransitionPresets.FadeTransition,
shift: _TransitionPresets.ShiftTransition,
none: {
sceneStyleInterpolator: undefined,
transitionSpec: {
animation: 'timing',
config: {
duration: 0
}
}
}
};
const useNativeDriver = _reactnative().Platform.OS !== 'web';
const hasAnimation = (options)=>{
const { animation, transitionSpec } = options;
if (animation) {
return animation !== 'none';
}
return Boolean(transitionSpec);
};
const renderTabBarDefault = (props)=>/*#__PURE__*/ (0, _jsxruntime().jsx)(_BottomTabBar.BottomTabBar, {
...props
});
function BottomTabView(props) {
const { tabBar = renderTabBarDefault, state, descriptors, emitter, navigateToTab, popNestedStackToTop, safeAreaInsets, detachInactiveScreens = _reactnative().Platform.OS === 'web' || _reactnative().Platform.OS === 'android' || _reactnative().Platform.OS === 'ios' } = props;
const focusedRouteKey = state.routes[state.index].key;
const previousRouteKeyRef = _react().useRef(focusedRouteKey);
const tabAnims = (0, _useAnimatedHashMap.useAnimatedHashMap)(state);
const [lastUpdate, setLastUpdate] = _react().useState({
current: focusedRouteKey,
animating: false
});
if (lastUpdate.current !== focusedRouteKey) {
setLastUpdate({
current: focusedRouteKey,
previous: lastUpdate.current,
animating: true
});
}
_react().useEffect(()=>{
const previousRouteKey = previousRouteKeyRef.current;
const shouldPopPreviousToTop = previousRouteKey !== focusedRouteKey && !!descriptors[previousRouteKey]?.options.popToTopOnBlur;
let timer;
const animateToIndex = ()=>{
if (previousRouteKey !== focusedRouteKey) {
emitter.emit({
type: 'transitionStart',
target: focusedRouteKey
});
}
_reactnative().Animated.parallel(state.routes.map((route, index)=>{
const { options } = descriptors[route.key];
const { animation = 'none', transitionSpec = NAMED_TRANSITIONS_PRESETS[animation].transitionSpec } = options;
let spec = transitionSpec;
if (route.key !== previousRouteKey && route.key !== focusedRouteKey) {
// Don't animate if the screen is not previous one or new one
// This will avoid flicker for screens not involved in the transition
spec = NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
}
spec = spec ?? NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
const toValue = index === state.index ? 0 : index >= state.index ? 1 : -1;
return _reactnative().Animated[spec.animation](tabAnims[route.name], {
...spec.config,
toValue,
useNativeDriver
});
}).filter(Boolean)).start(({ finished })=>{
if (finished && shouldPopPreviousToTop) {
popNestedStackToTop(previousRouteKey);
}
if (previousRouteKey !== focusedRouteKey) {
emitter.emit({
type: 'transitionEnd',
target: focusedRouteKey
});
}
if (finished) {
// Delay clearing so the previous screen stays attached
// This will give time for any native logic to run
timer = setTimeout(()=>{
setLastUpdate((update)=>update.animating ? {
...update,
animating: false
} : update);
}, 32);
}
});
};
animateToIndex();
previousRouteKeyRef.current = focusedRouteKey;
return ()=>{
if (timer !== undefined) {
clearTimeout(timer);
}
};
}, [
descriptors,
focusedRouteKey,
emitter,
popNestedStackToTop,
state.index,
state.routes,
tabAnims
]);
const dimensions = _elements.SafeAreaProviderCompat.initialMetrics.frame;
const [tabBarHeight, setTabBarHeight] = _react().useState(()=>(0, _BottomTabBar.getTabBarHeight)({
state,
descriptors,
dimensions,
insets: {
..._elements.SafeAreaProviderCompat.initialMetrics.insets,
...props.safeAreaInsets
},
style: descriptors[state.routes[state.index].key].options.tabBarStyle
}));
const renderTabBar = ()=>{
return /*#__PURE__*/ (0, _jsxruntime().jsx)(_reactnativesafeareacontext().SafeAreaInsetsContext.Consumer, {
children: (insets)=>tabBar({
state,
descriptors,
emitter,
navigateToTab,
insets: {
top: safeAreaInsets?.top ?? insets?.top ?? 0,
right: safeAreaInsets?.right ?? insets?.right ?? 0,
bottom: safeAreaInsets?.bottom ?? insets?.bottom ?? 0,
left: safeAreaInsets?.left ?? insets?.left ?? 0
}
})
});
};
const { routes } = state;
// If there is no animation, we only have 2 states: visible and invisible
const hasTwoStates = !routes.some((route)=>hasAnimation(descriptors[route.key].options));
const { tabBarPosition = 'bottom' } = descriptors[focusedRouteKey].options;
const tabBarElement = /*#__PURE__*/ (0, _jsxruntime().jsx)(_BottomTabBarHeightCallbackContext.BottomTabBarHeightCallbackContext.Provider, {
value: setTabBarHeight,
children: renderTabBar()
}, "tabbar");
return /*#__PURE__*/ (0, _jsxruntime().jsxs)(_elements.SafeAreaProviderCompat, {
style: {
flexDirection: tabBarPosition === 'left' || tabBarPosition === 'right' ? 'row' : 'column'
},
children: [
tabBarPosition === 'top' || tabBarPosition === 'left' ? tabBarElement : null,
/*#__PURE__*/ (0, _jsxruntime().jsx)(_ScreenFallback.MaybeScreenContainer, {
enabled: detachInactiveScreens,
hasTwoStates: hasTwoStates,
style: styles.screens,
children: routes.map((route, index)=>{
const descriptor = descriptors[route.key];
const { animation = 'none', sceneStyleInterpolator = NAMED_TRANSITIONS_PRESETS[animation].sceneStyleInterpolator } = descriptor.options;
const isFocused = state.index === index;
if (descriptor.route.key === undefined) {
// Don't render placeholder screens.
return null;
}
const { header = ({ layout, options })=>/*#__PURE__*/ (0, _jsxruntime().jsx)(_elements.Header, {
...options,
layout: layout,
title: (0, _elements.getHeaderTitle)(options, route.name)
}), headerShown, headerStatusBarHeight, headerTransparent, sceneStyle: customSceneStyle } = descriptor.options;
const { sceneStyle } = sceneStyleInterpolator?.({
current: {
progress: tabAnims[route.name]
}
}) ?? {};
const animationEnabled = hasAnimation(descriptor.options);
const isAnimatingRoute = lastUpdate.animating && (lastUpdate.previous === route.key || lastUpdate.current === route.key);
const activityState = isFocused ? STATE_ON_TOP // the screen is on top after the transition
: animationEnabled && isAnimatingRoute // if animation is not enabled, immediately move to inactive state
? STATE_TRANSITIONING_OR_BELOW_TOP // screen visible during transition
: STATE_INACTIVE;
return /*#__PURE__*/ (0, _jsxruntime().jsx)(_ScreenFallback.MaybeScreen, {
style: [
_reactnative().StyleSheet.absoluteFill,
{
zIndex: isFocused ? 0 : -1
}
],
active: activityState,
enabled: detachInactiveScreens,
children: /*#__PURE__*/ (0, _jsxruntime().jsx)(_BottomTabBarHeightContext.BottomTabBarHeightContext.Provider, {
value: tabBarPosition === 'bottom' ? tabBarHeight : 0,
children: /*#__PURE__*/ (0, _jsxruntime().jsx)(_elements.Screen, {
focused: isFocused,
route: route,
navigation: descriptor.navigation,
headerShown: headerShown,
headerStatusBarHeight: headerStatusBarHeight,
headerTransparent: headerTransparent,
header: header({
layout: dimensions,
route,
navigation: descriptor.navigation,
options: descriptor.options
}),
style: [
customSceneStyle,
animationEnabled && sceneStyle
],
children: descriptor.render()
})
})
}, route.key);
})
}, "screens"),
tabBarPosition === 'bottom' || tabBarPosition === 'right' ? tabBarElement : null
]
});
}
const styles = _reactnative().StyleSheet.create({
screens: {
flex: 1,
overflow: 'hidden'
}
});
//# sourceMappingURL=BottomTabView.js.map