UNPKG

react-native-tab-view

Version:
200 lines (199 loc) 7 kB
"use strict"; import * as React from 'react'; import { Animated, Keyboard, PanResponder, StyleSheet, View } from 'react-native'; import useLatestCallback from 'use-latest-callback'; import { useAnimatedValue } from "./useAnimatedValue.js"; import { jsx as _jsx } from "react/jsx-runtime"; const DEAD_ZONE = 12; const DefaultTransitionSpec = { timing: Animated.spring, stiffness: 1000, damping: 500, mass: 3, overshootClamping: true }; export function PanResponderAdapter({ layout, keyboardDismissMode = 'auto', swipeEnabled = true, navigationState, onIndexChange, onSwipeStart, onSwipeEnd, children, style, animationEnabled = false, layoutDirection = 'ltr' }) { const { routes, index } = navigationState; const panX = useAnimatedValue(0); const listenersRef = React.useRef([]); const navigationStateRef = React.useRef(navigationState); const layoutRef = React.useRef(layout); const onIndexChangeRef = React.useRef(onIndexChange); const currentIndexRef = React.useRef(index); const pendingIndexRef = React.useRef(undefined); const swipeVelocityThreshold = 0.15; const swipeDistanceThreshold = layout.width / 1.75; const jumpToIndex = useLatestCallback((index, animate = animationEnabled) => { const offset = -index * layoutRef.current.width; const { timing, ...transitionConfig } = DefaultTransitionSpec; if (animate) { Animated.parallel([timing(panX, { ...transitionConfig, toValue: offset, useNativeDriver: false })]).start(({ finished }) => { if (finished) { onIndexChangeRef.current(index); pendingIndexRef.current = undefined; } }); pendingIndexRef.current = index; } else { panX.setValue(offset); onIndexChangeRef.current(index); pendingIndexRef.current = undefined; } }); React.useEffect(() => { navigationStateRef.current = navigationState; layoutRef.current = layout; onIndexChangeRef.current = onIndexChange; }); React.useEffect(() => { const offset = -navigationStateRef.current.index * layout.width; panX.setValue(offset); }, [layout.width, panX]); React.useEffect(() => { if (keyboardDismissMode === 'auto') { Keyboard.dismiss(); } if (layout.width && currentIndexRef.current !== index) { currentIndexRef.current = index; jumpToIndex(index); } }, [jumpToIndex, keyboardDismissMode, layout.width, index]); const isMovingHorizontally = (_, gestureState) => { return Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 2) && Math.abs(gestureState.vx) > Math.abs(gestureState.vy * 2); }; const canMoveScreen = (event, gestureState) => { if (swipeEnabled === false) { return false; } const diffX = layoutDirection === 'rtl' ? -gestureState.dx : gestureState.dx; return isMovingHorizontally(event, gestureState) && (diffX >= DEAD_ZONE && currentIndexRef.current > 0 || diffX <= -DEAD_ZONE && currentIndexRef.current < routes.length - 1); }; const startGesture = () => { onSwipeStart?.(); if (keyboardDismissMode === 'on-drag') { Keyboard.dismiss(); } panX.stopAnimation(); // @ts-expect-error: _value is private, but docs use it as well panX.setOffset(panX._value); }; const respondToGesture = (_, gestureState) => { const diffX = layoutDirection === 'rtl' ? -gestureState.dx : gestureState.dx; if ( // swiping left diffX > 0 && index <= 0 || // swiping right diffX < 0 && index >= routes.length - 1) { return; } if (layout.width) { // @ts-expect-error: _offset is private, but docs use it as well const position = (panX._offset + diffX) / -layout.width; const next = position > index ? Math.ceil(position) : Math.floor(position); if (next !== index) { listenersRef.current.forEach(listener => listener(next)); } } panX.setValue(diffX); }; const finishGesture = (_, gestureState) => { panX.flattenOffset(); onSwipeEnd?.(); const currentIndex = typeof pendingIndexRef.current === 'number' ? pendingIndexRef.current : currentIndexRef.current; let nextIndex = currentIndex; if (Math.abs(gestureState.dx) > Math.abs(gestureState.dy) && Math.abs(gestureState.vx) > Math.abs(gestureState.vy) && (Math.abs(gestureState.dx) > swipeDistanceThreshold || Math.abs(gestureState.vx) > swipeVelocityThreshold)) { nextIndex = Math.round(Math.min(Math.max(0, layoutDirection === 'rtl' ? currentIndex + gestureState.dx / Math.abs(gestureState.dx) : currentIndex - gestureState.dx / Math.abs(gestureState.dx)), routes.length - 1)); currentIndexRef.current = nextIndex; } if (!isFinite(nextIndex)) { nextIndex = currentIndex; } jumpToIndex(nextIndex, true); }; const addEnterListener = useLatestCallback(listener => { listenersRef.current.push(listener); return () => { const index = listenersRef.current.indexOf(listener); if (index > -1) { listenersRef.current.splice(index, 1); } }; }); const jumpTo = useLatestCallback(key => { const index = navigationStateRef.current.routes.findIndex(route => route.key === key); jumpToIndex(index); onIndexChange(index); }); const panResponder = PanResponder.create({ onMoveShouldSetPanResponder: canMoveScreen, onMoveShouldSetPanResponderCapture: canMoveScreen, onPanResponderGrant: startGesture, onPanResponderMove: respondToGesture, onPanResponderTerminate: finishGesture, onPanResponderRelease: finishGesture, onPanResponderTerminationRequest: () => true }); const maxTranslate = layout.width * (routes.length - 1); const translateX = Animated.multiply(panX.interpolate({ inputRange: [-maxTranslate, 0], outputRange: [-maxTranslate, 0], extrapolate: 'clamp' }), layoutDirection === 'rtl' ? -1 : 1); const position = React.useMemo(() => layout.width ? Animated.divide(panX, -layout.width) : null, [layout.width, panX]); return children({ position: position ?? new Animated.Value(index), addEnterListener, jumpTo, render: children => /*#__PURE__*/_jsx(Animated.View, { style: [styles.sheet, layout.width ? { width: routes.length * layout.width, transform: [{ translateX }] } : null, style], ...panResponder.panHandlers, children: React.Children.map(children, (child, i) => { const route = routes[i]; const focused = i === index; return /*#__PURE__*/_jsx(View, { style: layout.width ? { width: layout.width } : focused ? StyleSheet.absoluteFill : null, children: focused || layout.width ? child : null }, route.key); }) }) }); } const styles = StyleSheet.create({ sheet: { flex: 1, flexDirection: 'row', alignItems: 'stretch' } }); //# sourceMappingURL=PanResponderAdapter.js.map