react-native-screens
Version:
Native navigation primitives for your React Native app.
214 lines (212 loc) • 7.85 kB
JavaScript
'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