UNPKG

react-native-screens

Version:
214 lines (212 loc) 7.85 kB
'use client'; function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } import React from 'react'; import { Freeze } from 'react-freeze'; import { Image, StyleSheet, findNodeHandle, processColor } from 'react-native'; import { freezeEnabled } from '../../core'; import BottomTabsScreenNativeComponent from '../../fabric/bottom-tabs/BottomTabsScreenNativeComponent'; import { featureFlags } from '../../flags'; import { bottomTabsDebugLog } from '../../private/logging'; /** * EXPERIMENTAL API, MIGHT CHANGE W/O ANY NOTICE */ function BottomTabsScreen(props) { const componentNodeRef = React.useRef(null); const componentNodeHandle = React.useRef(-1); React.useEffect(() => { if (componentNodeRef.current != null) { componentNodeHandle.current = findNodeHandle(componentNodeRef.current) ?? -1; } else { componentNodeHandle.current = -1; } }, []); const [nativeViewIsVisible, setNativeViewIsVisible] = React.useState(false); const { onWillAppear, onDidAppear, onWillDisappear, onDidDisappear, isFocused = false, freezeContents, icon, iconResource, selectedIcon, standardAppearance, scrollEdgeAppearance, ...rest } = props; const shouldFreeze = shouldFreezeScreen(nativeViewIsVisible, isFocused, freezeContents); const onWillAppearCallback = React.useCallback(event => { bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current}] onWillAppear received`); setNativeViewIsVisible(true); onWillAppear?.(event); }, [onWillAppear]); const onDidAppearCallback = React.useCallback(event => { bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current}] onDidAppear received`); onDidAppear?.(event); }, [onDidAppear]); const onWillDisappearCallback = React.useCallback(event => { bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current}] onWillDisappear received`); onWillDisappear?.(event); }, [onWillDisappear]); const onDidDisappearCallback = React.useCallback(event => { bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current}] onDidDisappear received`); setNativeViewIsVisible(false); onDidDisappear?.(event); }, [onDidDisappear]); bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current ?? -1}] render; tabKey: ${rest.tabKey} shouldFreeze: ${shouldFreeze}, isFocused: ${isFocused} nativeViewIsVisible: ${nativeViewIsVisible}`); const iconProps = parseIconsToNativeProps(icon, selectedIcon); let parsedIconResource; if (iconResource) { parsedIconResource = Image.resolveAssetSource(iconResource); if (!parsedIconResource) { console.error('[RNScreens] failed to resolve an asset for bottom tab icon'); } } return /*#__PURE__*/React.createElement(BottomTabsScreenNativeComponent, _extends({ collapsable: false, style: styles.fillParent, onWillAppear: onWillAppearCallback, onDidAppear: onDidAppearCallback, onWillDisappear: onWillDisappearCallback, onDidDisappear: onDidDisappearCallback, isFocused: isFocused // I'm keeping undefined as a fallback if `Image.resolveAssetSource` has failed for some reason. // It won't render any icon, but it will prevent from crashing on the native side which is expecting // ReadableMap. Passing `iconResource` directly will result in crash, because `require` API is returning // double as a value. , iconResource: parsedIconResource || undefined }, iconProps, { standardAppearance: mapAppearanceToNativeProp(standardAppearance), scrollEdgeAppearance: mapAppearanceToNativeProp(scrollEdgeAppearance) // @ts-ignore - This is debug only anyway , ref: componentNodeRef }, rest), /*#__PURE__*/React.createElement(Freeze, { freeze: shouldFreeze, placeholder: rest.placeholder }, rest.children)); } function mapAppearanceToNativeProp(appearance) { if (!appearance) return undefined; const { stacked, inline, compactInline, tabBarBackgroundColor } = appearance; return { ...appearance, stacked: mapItemAppearanceToNativeProp(stacked), inline: mapItemAppearanceToNativeProp(inline), compactInline: mapItemAppearanceToNativeProp(compactInline), tabBarBackgroundColor: processColor(tabBarBackgroundColor) }; } function mapItemAppearanceToNativeProp(itemAppearance) { if (!itemAppearance) return undefined; const { normal, selected, focused, disabled } = itemAppearance; return { ...itemAppearance, normal: mapItemStateAppearanceToNativeProp(normal), selected: mapItemStateAppearanceToNativeProp(selected), focused: mapItemStateAppearanceToNativeProp(focused), disabled: mapItemStateAppearanceToNativeProp(disabled) }; } function mapItemStateAppearanceToNativeProp(itemStateAppearance) { if (!itemStateAppearance) return undefined; const { tabBarItemTitleFontColor, tabBarItemIconColor, tabBarItemBadgeBackgroundColor, tabBarItemTitleFontWeight } = itemStateAppearance; return { ...itemStateAppearance, tabBarItemTitleFontColor: processColor(tabBarItemTitleFontColor), tabBarItemIconColor: processColor(tabBarItemIconColor), tabBarItemBadgeBackgroundColor: processColor(tabBarItemBadgeBackgroundColor), tabBarItemTitleFontWeight: tabBarItemTitleFontWeight !== undefined ? String(tabBarItemTitleFontWeight) : undefined }; } function shouldFreezeScreen(nativeViewVisible, screenFocused, freezeOverride) { if (!freezeEnabled()) { return false; } if (freezeOverride !== undefined) { return freezeOverride; } if (featureFlags.experiment.controlledBottomTabs) { // If the tabs are JS controlled, we want to freeze only when given view is not focused && it is not currently visible return !nativeViewVisible && !screenFocused; } return !nativeViewVisible; } function parseIconToNativeProps(icon) { if (!icon) { return {}; } if ('sfSymbolName' in icon) { // iOS-specific: SFSymbol usage return { iconType: 'sfSymbol', iconSfSymbolName: icon.sfSymbolName }; } else if ('imageSource' in icon) { return { iconType: 'image', iconImageSource: icon.imageSource }; } else if ('templateSource' in icon) { // iOS-specifig: image as a template usage return { iconType: 'template', iconImageSource: icon.templateSource }; } else { // iOS-specific: SFSymbol, image as a template usage throw new Error('[RNScreens] Incorrect icon format. You must provide sfSymbolName, imageSource or templateSource.'); } } function parseIconsToNativeProps(icon, selectedIcon) { const { iconImageSource, iconSfSymbolName, iconType } = parseIconToNativeProps(icon); const { iconImageSource: selectedIconImageSource, iconSfSymbolName: selectedIconSfSymbolName, iconType: selectedIconType } = parseIconToNativeProps(selectedIcon); if (iconType !== undefined && selectedIconType !== undefined && iconType !== selectedIconType) { throw new Error('[RNScreens] icon and selectedIcon must be same type.'); } else if (iconType === undefined && selectedIconType !== undefined) { // iOS-specific: UIKit requirement throw new Error('[RNScreens] To use selectedIcon prop, the icon prop must also be provided.'); } return { iconType, iconImageSource, iconSfSymbolName, selectedIconImageSource, selectedIconSfSymbolName }; } export default BottomTabsScreen; const styles = StyleSheet.create({ fillParent: { position: 'absolute', flex: 1, width: '100%', height: '100%' } }); //# sourceMappingURL=BottomTabsScreen.js.map