UNPKG

expo-router

Version:

Expo Router is a file-based router for React Native and web applications.

311 lines (309 loc) • 13.3 kB
'use client'; "use strict"; 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