UNPKG

react-native-collapsible-tab-view

Version:
358 lines (349 loc) 13.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Container = void 0; var _react = _interopRequireDefault(require("react")); var _reactNative = require("react-native"); var _reactNativePagerView = _interopRequireDefault(require("react-native-pager-view")); var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated")); var _Context = require("./Context"); var _Lazy = require("./Lazy"); var _MaterialTabBar = require("./MaterialTabBar"); var _Tab = require("./Tab"); var _helpers = require("./helpers"); var _hooks = require("./hooks"); var _jsxRuntime = require("react/jsx-runtime"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const AnimatedPagerView = _reactNativeReanimated.default.createAnimatedComponent(_reactNativePagerView.default); /** * Basic usage looks like this: * * ```tsx * import { Tabs } from 'react-native-collapsible-tab-view' * * const Example = () => { * return ( * <Tabs.Container renderHeader={MyHeader}> * <Tabs.Tab name="A"> * <ScreenA /> * </Tabs.Tab> * <Tabs.Tab name="B"> * <ScreenB /> * </Tabs.Tab> * </Tabs.Container> * ) * } * ``` */ const Container = exports.Container = /*#__PURE__*/_react.default.memo(/*#__PURE__*/_react.default.forwardRef(({ initialTabName, headerHeight: initialHeaderHeight, minHeaderHeight = 0, tabBarHeight: initialTabBarHeight = _MaterialTabBar.TABBAR_HEIGHT, revealHeaderOnScroll = false, snapThreshold, children, renderHeader, renderTabBar = props => /*#__PURE__*/(0, _jsxRuntime.jsx)(_MaterialTabBar.MaterialTabBar, { ...props }), headerContainerStyle, cancelTranslation, containerStyle, lazy, cancelLazyFadeIn, pagerProps, onIndexChange, onTabChange, width: customWidth, allowHeaderOverscroll }, ref) => { const containerRef = (0, _hooks.useContainerRef)(); const [tabProps, tabNamesArray] = (0, _hooks.useTabProps)(children, _Tab.Tab); const [refMap, setRef] = (0, _hooks.useAnimatedDynamicRefs)(); const windowWidth = (0, _reactNative.useWindowDimensions)().width; const width = customWidth ?? windowWidth; const [containerHeight, getContainerLayoutHeight] = (0, _hooks.useLayoutHeight)(); const [tabBarHeight, getTabBarHeight] = (0, _hooks.useLayoutHeight)(initialTabBarHeight); const [headerHeight, getHeaderHeight] = (0, _hooks.useLayoutHeight)(!renderHeader ? 0 : initialHeaderHeight); const initialIndex = _react.default.useMemo(() => initialTabName ? tabNamesArray.findIndex(n => n === initialTabName) : 0, [initialTabName, tabNamesArray]); const contentInset = _react.default.useMemo(() => { if (allowHeaderOverscroll) return 0; // necessary for the refresh control on iOS to be positioned underneath the header // this also adjusts the scroll bars to clamp underneath the header area return _helpers.IS_IOS ? (headerHeight || 0) + (tabBarHeight || 0) : 0; }, [headerHeight, tabBarHeight, allowHeaderOverscroll]); const snappingTo = (0, _reactNativeReanimated.useSharedValue)(0); const offset = (0, _reactNativeReanimated.useSharedValue)(0); const accScrollY = (0, _reactNativeReanimated.useSharedValue)(0); const oldAccScrollY = (0, _reactNativeReanimated.useSharedValue)(0); const accDiffClamp = (0, _reactNativeReanimated.useSharedValue)(0); const scrollYCurrent = (0, _reactNativeReanimated.useSharedValue)(0); const scrollY = (0, _reactNativeReanimated.useSharedValue)(Object.fromEntries(tabNamesArray.map(n => [n, 0]))); const contentHeights = (0, _reactNativeReanimated.useSharedValue)(tabNamesArray.map(() => 0)); const tabNames = (0, _reactNativeReanimated.useDerivedValue)(() => tabNamesArray, [tabNamesArray]); const index = (0, _reactNativeReanimated.useSharedValue)(initialIndex); const focusedTab = (0, _reactNativeReanimated.useDerivedValue)(() => { return tabNames.value[index.value]; }, [tabNames]); const calculateNextOffset = (0, _reactNativeReanimated.useSharedValue)(initialIndex); const headerScrollDistance = (0, _reactNativeReanimated.useDerivedValue)(() => { return headerHeight !== undefined ? headerHeight - minHeaderHeight : 0; }, [headerHeight, minHeaderHeight]); const indexDecimal = (0, _reactNativeReanimated.useSharedValue)(index.value); const afterRender = (0, _reactNativeReanimated.useSharedValue)(0); _react.default.useEffect(() => { afterRender.value = (0, _reactNativeReanimated.withDelay)(_helpers.ONE_FRAME_MS * 5, (0, _reactNativeReanimated.withTiming)(1, { duration: 0 })); }, [afterRender, tabNamesArray]); const resyncTabScroll = () => { 'worklet'; for (const name of tabNamesArray) { (0, _helpers.scrollToImpl)(refMap[name], 0, scrollYCurrent.value - contentInset, false); } }; // the purpose of this is to scroll to the proper position if dynamic tabs are changing (0, _reactNativeReanimated.useAnimatedReaction)(() => { return afterRender.value === 1; }, trigger => { if (trigger) { afterRender.value = 0; resyncTabScroll(); } }, [tabNamesArray, refMap, afterRender, contentInset]); // derived from scrollX // calculate the next offset and index if swiping // if scrollX changes from tab press, // the same logic must be done, but knowing // the next index in advance (0, _reactNativeReanimated.useAnimatedReaction)(() => { const nextIndex = Math.round(indexDecimal.value); return nextIndex; }, nextIndex => { if (nextIndex !== null && nextIndex !== index.value) { calculateNextOffset.value = nextIndex; } }, []); const propagateTabChange = _react.default.useCallback(change => { onTabChange?.(change); onIndexChange?.(change.index); }, [onIndexChange, onTabChange]); const syncCurrentTabScrollPosition = () => { 'worklet'; const name = tabNamesArray[index.value]; (0, _helpers.scrollToImpl)(refMap[name], 0, scrollYCurrent.value - contentInset, false); }; /* * We run syncCurrentTabScrollPosition in every frame after the index * changes for about 1500ms because the Lists can be late to accept the * scrollTo event we send. This fixes the issue of the scroll position * jumping when the user changes tab. * */ const toggleSyncScrollFrame = toggle => syncScrollFrame.setActive(toggle); const syncScrollFrame = (0, _reactNativeReanimated.useFrameCallback)(({ timeSinceFirstFrame }) => { syncCurrentTabScrollPosition(); if (timeSinceFirstFrame > 1500) { (0, _reactNativeReanimated.runOnJS)(toggleSyncScrollFrame)(false); } }, false); (0, _reactNativeReanimated.useAnimatedReaction)(() => { return calculateNextOffset.value; }, i => { if (i !== index.value) { offset.value = scrollY.value[tabNames.value[index.value]] - scrollY.value[tabNames.value[i]] + offset.value; (0, _reactNativeReanimated.runOnJS)(propagateTabChange)({ prevIndex: index.value, index: i, prevTabName: tabNames.value[index.value], tabName: tabNames.value[i] }); index.value = i; if (typeof scrollY.value[tabNames.value[index.value]] === 'number') { scrollYCurrent.value = scrollY.value[tabNames.value[index.value]] || 0; } (0, _reactNativeReanimated.runOnJS)(toggleSyncScrollFrame)(true); } }, []); (0, _reactNativeReanimated.useAnimatedReaction)(() => headerHeight, (_current, prev) => { if (prev === undefined) { // sync scroll if we started with undefined header height resyncTabScroll(); } }); const headerTranslateY = (0, _reactNativeReanimated.useDerivedValue)(() => { return revealHeaderOnScroll ? -accDiffClamp.value : -Math.min(scrollYCurrent.value, headerScrollDistance.value); }, [revealHeaderOnScroll]); const stylez = (0, _reactNativeReanimated.useAnimatedStyle)(() => { return { transform: [{ translateY: headerTranslateY.value }] }; }, [revealHeaderOnScroll]); const onTabPress = _react.default.useCallback(name => { const i = tabNames.value.findIndex(n => n === name); if (name === focusedTab.value) { const ref = refMap[name]; (0, _reactNativeReanimated.runOnUI)(_helpers.scrollToImpl)(ref, 0, headerScrollDistance.value - contentInset, true); } else { containerRef.current?.setPage(i); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [containerRef, refMap, contentInset]); (0, _reactNativeReanimated.useAnimatedReaction)(() => tabNamesArray.length, tabLength => { if (index.value >= tabLength) { (0, _reactNativeReanimated.runOnJS)(onTabPress)(tabNamesArray[tabLength - 1]); } }); const pageScrollHandler = (0, _hooks.usePageScrollHandler)({ onPageScroll: e => { 'worklet'; indexDecimal.value = e.position + e.offset; } }); _react.default.useImperativeHandle(ref, () => ({ setIndex: index => { const name = tabNames.value[index]; onTabPress(name); return true; }, jumpToTab: name => { onTabPress(name); return true; }, getFocusedTab: () => { return tabNames.value[index.value]; }, getCurrentIndex: () => { return index.value; } }), // eslint-disable-next-line react-hooks/exhaustive-deps [onTabPress]); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Context.Context.Provider, { value: { contentInset, tabBarHeight, headerHeight, refMap, tabNames, index, snapThreshold, revealHeaderOnScroll, focusedTab, accDiffClamp, indexDecimal, containerHeight, minHeaderHeight, scrollYCurrent, scrollY, setRef, headerScrollDistance, accScrollY, oldAccScrollY, offset, snappingTo, contentHeights, headerTranslateY, width, allowHeaderOverscroll }, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, { style: [styles.container, { width }, containerStyle], onLayout: getContainerLayoutHeight, pointerEvents: "box-none", children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, { pointerEvents: "box-none", style: [styles.topContainer, headerContainerStyle, !cancelTranslation && stylez], children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: [styles.container, styles.headerContainer], onLayout: getHeaderHeight, pointerEvents: "box-none", children: renderHeader && renderHeader({ containerRef, index, tabNames: tabNamesArray, focusedTab, indexDecimal, onTabPress, tabProps }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: [styles.container, styles.tabBarContainer], onLayout: getTabBarHeight, pointerEvents: "box-none", children: renderTabBar && renderTabBar({ containerRef, index, tabNames: tabNamesArray, focusedTab, indexDecimal, width, onTabPress, tabProps }) })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(AnimatedPagerView, { ref: containerRef, onPageScroll: pageScrollHandler, initialPage: initialIndex, ...pagerProps, style: [pagerProps?.style, _reactNative.StyleSheet.absoluteFill], children: tabNamesArray.map((tabName, i) => { return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.pageContainer, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Context.TabNameContext.Provider, { value: tabName, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Lazy.Lazy, { startMounted: lazy ? undefined : true, cancelLazyFadeIn: !lazy ? true : !!cancelLazyFadeIn // ensure that we remount the tab if its name changes but the index doesn't , children: _react.default.Children.toArray(children)[i] }, tabName) }) }, i); }) })] }) }); })); const styles = _reactNative.StyleSheet.create({ container: { flex: 1 }, pageContainer: { height: '100%', width: '100%' }, topContainer: { position: 'absolute', zIndex: 100, width: '100%', backgroundColor: 'white', shadowColor: '#000000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.23, shadowRadius: 2.62, elevation: 4 }, tabBarContainer: { zIndex: 1 }, headerContainer: { zIndex: 2 } }); //# sourceMappingURL=Container.js.map