react-native-screens
Version:
Native navigation primitives for your React Native app.
194 lines (190 loc) • 7.83 kB
JavaScript
import React, { useEffect } from 'react';
import { Dimensions, Platform, findNodeHandle } from 'react-native';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import { useSharedValue, measure, startScreenTransition, finishScreenTransition, makeMutable, runOnUI } from 'react-native-reanimated';
import { getShadowNodeWrapperAndTagFromRef, isFabric } from './fabricUtils';
import { RNScreensTurboModule } from './RNScreensTurboModule';
import { DefaultEvent, DefaultScreenDimensions } from './defaults';
import { checkBoundaries, checkIfTransitionCancelled, getAnimationForTransition } from './constraints';
const EmptyGestureHandler = Gesture.Fling();
const ScreenGestureDetector = _ref => {
let {
children,
gestureDetectorBridge,
goBackGesture,
screenEdgeGesture,
transitionAnimation: customTransitionAnimation,
screensRefs,
currentRouteKey
} = _ref;
const sharedEvent = useSharedValue(DefaultEvent);
const startingGesturePosition = useSharedValue(DefaultEvent);
const canPerformUpdates = makeMutable(false);
const transitionAnimation = getAnimationForTransition(goBackGesture, customTransitionAnimation);
const screenTransitionConfig = makeMutable({
stackTag: -1,
belowTopScreenId: -1,
topScreenId: -1,
sharedEvent,
startingGesturePosition,
screenTransition: transitionAnimation,
isTransitionCanceled: false,
goBackGesture: goBackGesture ?? 'swipeRight',
screenDimensions: DefaultScreenDimensions,
onFinishAnimation: () => {
'worklet';
}
});
const stackTag = makeMutable(-1);
const screenTagToNodeWrapperUI = makeMutable({});
const IS_FABRIC = isFabric();
gestureDetectorBridge.current.stackUseEffectCallback = stackRef => {
if (!goBackGesture) {
return;
}
stackTag.value = findNodeHandle(stackRef.current);
if (Platform.OS === 'ios') {
runOnUI(() => {
RNScreensTurboModule.disableSwipeBackForTopScreen(stackTag.value);
})();
}
};
useEffect(() => {
if (!IS_FABRIC || !goBackGesture) {
return;
}
const screenTagToNodeWrapper = {};
for (const key in screensRefs.current) {
const screenRef = screensRefs.current[key];
const screenData = getShadowNodeWrapperAndTagFromRef(screenRef.current);
if (screenData.tag && screenData.shadowNodeWrapper) {
screenTagToNodeWrapper[screenData.tag] = screenData.shadowNodeWrapper;
} else {
console.warn('[RNScreens] Failed to find tag for screen.');
}
}
screenTagToNodeWrapperUI.value = screenTagToNodeWrapper;
}, [currentRouteKey]);
function computeProgress(event) {
'worklet';
let progress = 0;
const screenDimensions = screenTransitionConfig.value.screenDimensions;
const startingPosition = startingGesturePosition.value;
if (goBackGesture === 'swipeRight') {
progress = event.translationX / (screenDimensions.width - startingPosition.absoluteX);
} else if (goBackGesture === 'swipeLeft') {
progress = -1 * event.translationX / startingPosition.absoluteX;
} else if (goBackGesture === 'swipeDown') {
progress = -1 * event.translationY / (screenDimensions.height - startingPosition.absoluteY);
} else if (goBackGesture === 'swipeUp') {
progress = event.translationY / startingPosition.absoluteY;
} else if (goBackGesture === 'horizontalSwipe') {
progress = Math.abs(event.translationX / screenDimensions.width / 2);
} else if (goBackGesture === 'verticalSwipe') {
progress = Math.abs(event.translationY / screenDimensions.height / 2);
} else if (goBackGesture === 'twoDimensionalSwipe') {
const progressX = Math.abs(event.translationX / screenDimensions.width / 2);
const progressY = Math.abs(event.translationY / screenDimensions.height / 2);
progress = Math.max(progressX, progressY);
}
return progress;
}
function onStart(event) {
'worklet';
sharedEvent.value = event;
const transitionConfig = screenTransitionConfig.value;
const transitionData = RNScreensTurboModule.startTransition(stackTag.value);
if (transitionData.canStartTransition === false) {
canPerformUpdates.value = false;
return;
}
if (IS_FABRIC) {
transitionConfig.topScreenId = screenTagToNodeWrapperUI.value[transitionData.topScreenTag];
transitionConfig.belowTopScreenId = screenTagToNodeWrapperUI.value[transitionData.belowTopScreenTag];
} else {
transitionConfig.topScreenId = transitionData.topScreenTag;
transitionConfig.belowTopScreenId = transitionData.belowTopScreenTag;
}
transitionConfig.stackTag = stackTag.value;
startingGesturePosition.value = event;
const animatedRefMock = () => {
return screenTransitionConfig.value.topScreenId;
};
const screenSize = measure(animatedRefMock);
if (screenSize == null) {
throw new Error('[RNScreens] Failed to measure screen.');
}
if (screenSize == null) {
canPerformUpdates.value = false;
RNScreensTurboModule.finishTransition(stackTag.value, true);
return;
}
transitionConfig.screenDimensions = screenSize;
startScreenTransition(transitionConfig);
canPerformUpdates.value = true;
}
function onUpdate(event) {
'worklet';
if (!canPerformUpdates.value) {
return;
}
checkBoundaries(goBackGesture, event);
const progress = computeProgress(event);
sharedEvent.value = event;
const stackTag = screenTransitionConfig.value.stackTag;
RNScreensTurboModule.updateTransition(stackTag, progress);
}
function onEnd(event) {
'worklet';
if (!canPerformUpdates.value) {
return;
}
const velocityFactor = 0.3;
const screenSize = screenTransitionConfig.value.screenDimensions;
const distanceX = event.translationX + Math.min(event.velocityX * velocityFactor, 100);
const distanceY = event.translationY + Math.min(event.velocityY * velocityFactor, 100);
const requiredXDistance = screenSize.width / 2;
const requiredYDistance = screenSize.height / 2;
const isTransitionCanceled = checkIfTransitionCancelled(goBackGesture, distanceX, requiredXDistance, distanceY, requiredYDistance);
const stackTag = screenTransitionConfig.value.stackTag;
screenTransitionConfig.value.onFinishAnimation = () => {
RNScreensTurboModule.finishTransition(stackTag, isTransitionCanceled);
};
screenTransitionConfig.value.isTransitionCanceled = isTransitionCanceled;
finishScreenTransition(screenTransitionConfig.value);
}
let panGesture = Gesture.Pan().onStart(onStart).onUpdate(onUpdate).onEnd(onEnd);
if (screenEdgeGesture) {
const HIT_SLOP_SIZE = 50;
const ACTIVATION_DISTANCE = 30;
if (goBackGesture === 'swipeRight') {
panGesture = panGesture.activeOffsetX(ACTIVATION_DISTANCE).hitSlop({
left: 0,
top: 0,
width: HIT_SLOP_SIZE
});
} else if (goBackGesture === 'swipeLeft') {
panGesture = panGesture.activeOffsetX(-ACTIVATION_DISTANCE).hitSlop({
right: 0,
top: 0,
width: HIT_SLOP_SIZE
});
} else if (goBackGesture === 'swipeDown') {
panGesture = panGesture.activeOffsetY(ACTIVATION_DISTANCE).hitSlop({
top: 0,
height: Dimensions.get('window').height * 0.2
});
// workaround, because we don't have access to header height
} else if (goBackGesture === 'swipeUp') {
panGesture = panGesture.activeOffsetY(-ACTIVATION_DISTANCE).hitSlop({
bottom: 0,
height: HIT_SLOP_SIZE
});
}
}
return /*#__PURE__*/React.createElement(GestureDetector, {
gesture: goBackGesture ? panGesture : EmptyGestureHandler
}, children);
};
export default ScreenGestureDetector;
//# sourceMappingURL=ScreenGestureDetector.js.map