|
|
@@ -36,6 +36,7 @@ import {
|
|
|
import Modal from "../modal";
|
|
|
|
|
|
const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> = ({
|
|
|
+ renderHeader: RenderHeaderComponent,
|
|
|
renderBottom: RenderBottomComponent,
|
|
|
isForceFullScreenOnSwipe = false,
|
|
|
isCanFullScreenOnSwipe = false,
|
|
|
@@ -44,12 +45,16 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
isWrapSafeareaContext = true,
|
|
|
backgroundColor = "default",
|
|
|
isWorkAsFullScreen = false,
|
|
|
+ isWorkWithPortal = true,
|
|
|
isCloseOnOverlay = true,
|
|
|
handleContainerSpacing,
|
|
|
isActive: isActiveProp,
|
|
|
handleBackgroundColor,
|
|
|
isAutoHeight = false,
|
|
|
isShowHandle = true,
|
|
|
+ isSwipeClose = true,
|
|
|
+ isCanSwipe = true,
|
|
|
+ onOverlayPressed,
|
|
|
snapPoint,
|
|
|
children,
|
|
|
onClosed,
|
|
|
@@ -57,6 +62,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
onClose,
|
|
|
onOpen,
|
|
|
style,
|
|
|
+ key,
|
|
|
...props
|
|
|
}, ref) => {
|
|
|
const {
|
|
|
@@ -82,27 +88,29 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
let bottomSafeArea = isWrapSafeareaContext ? bottom : 0;
|
|
|
let topSafeArea = isWrapSafeareaContext ? top : 0;
|
|
|
|
|
|
- if(isWorkAsFullScreen) {
|
|
|
- bottomSafeArea = 0;
|
|
|
+ if(isForceFullScreenOnSwipe) {
|
|
|
topSafeArea = 0;
|
|
|
}
|
|
|
|
|
|
- if(isForceFullScreenOnSwipe) {
|
|
|
+ if(!isWorkWithPortal) {
|
|
|
+ bottomSafeArea = 0;
|
|
|
topSafeArea = 0;
|
|
|
}
|
|
|
|
|
|
const scrollViewRef = useRef<ComponentRef<ScrollView>>(null);
|
|
|
const modalRef = useRef<IModalRef>(null);
|
|
|
|
|
|
- const animatedTranslateY = useRef(new Animated.Value(snapPoint && !isWorkAsFullScreen ? snapPoint : windowHeight)).current;
|
|
|
+ const containerHeightRef = useRef(windowHeight);
|
|
|
+
|
|
|
+ const animatedTranslateY = useRef(new Animated.Value(snapPoint && !isWorkAsFullScreen ? snapPoint : containerHeightRef.current)).current;
|
|
|
const animatedHeight = useRef(new Animated.Value(
|
|
|
isAutoHeight ? 0 : snapPoint ?? 0
|
|
|
)).current;
|
|
|
|
|
|
const TOP_GRAB_AREA = 140;
|
|
|
|
|
|
- const maxHeight = useRef(isWorkAsFullScreen ? windowHeight : windowHeight - (isForceFullScreenOnSwipe ? 0 : isWrapSafeareaContext ? topSafeArea : 0));
|
|
|
- const heightValue = useRef(isWorkAsFullScreen ? windowHeight : snapPoint ?? 0);
|
|
|
+ const maxHeight = useRef(isWorkAsFullScreen ? containerHeightRef.current - (isWrapSafeareaContext ? topSafeArea : 0) : containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeareaContext ? topSafeArea : 0));
|
|
|
+ const heightValue = useRef(isWorkAsFullScreen ? containerHeightRef.current : snapPoint ?? 0);
|
|
|
const initialTranslateY = useRef(0);
|
|
|
const translateYValue = useRef(0);
|
|
|
const contentHeight = useRef(-1);
|
|
|
@@ -115,6 +123,11 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
|
|
|
const gestureStartY = useRef(0);
|
|
|
|
|
|
+ const isCanFullScreenOnSwipeRef = useRef(isCanFullScreenOnSwipe);
|
|
|
+ const isWorkAsFullScreenRef = useRef(isWorkAsFullScreen);
|
|
|
+ const isSwipeCloseRef = useRef(isSwipeClose);
|
|
|
+ const isCanSwipeRef = useRef(isCanSwipe);
|
|
|
+
|
|
|
if(!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
|
|
|
maxHeight.current = snapPoint;
|
|
|
}
|
|
|
@@ -132,10 +145,26 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
[]
|
|
|
);
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ isCanSwipeRef.current = isCanSwipe;
|
|
|
+ }, [isCanSwipe]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ isSwipeCloseRef.current = isSwipeClose;
|
|
|
+ }, [isSwipeClose]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ isCanFullScreenOnSwipeRef.current = isCanFullScreenOnSwipe;
|
|
|
+ }, [isCanFullScreenOnSwipe]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ isWorkAsFullScreenRef.current = isWorkAsFullScreen;
|
|
|
+ }, [isWorkAsFullScreen]);
|
|
|
+
|
|
|
useEffect(() => {
|
|
|
if(isMeasured && contentHeight.current !== -1) {
|
|
|
if(isCanFullScreenOnSwipe && !isWorkAsFullScreen) {
|
|
|
- maxHeight.current = windowHeight - (isForceFullScreenOnSwipe ? 0 : topSafeArea);
|
|
|
+ maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : topSafeArea);
|
|
|
} else if(isAutoHeight || !snapPoint) {
|
|
|
maxHeight.current = contentHeight.current;
|
|
|
}
|
|
|
@@ -151,6 +180,23 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
isMeasured
|
|
|
]);
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
|
|
|
+ maxHeight.current = snapPoint;
|
|
|
+ } else if (!isWorkAsFullScreen) {
|
|
|
+ maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeareaContext ? topSafeArea : 0);
|
|
|
+ } else {
|
|
|
+ maxHeight.current = containerHeightRef.current - (isWrapSafeareaContext ? topSafeArea : 0);
|
|
|
+ }
|
|
|
+ }, [
|
|
|
+ isForceFullScreenOnSwipe,
|
|
|
+ isCanFullScreenOnSwipe,
|
|
|
+ isWrapSafeareaContext,
|
|
|
+ isWorkAsFullScreen,
|
|
|
+ topSafeArea,
|
|
|
+ snapPoint
|
|
|
+ ]);
|
|
|
+
|
|
|
useEffect(() => {
|
|
|
const listenerAHeightId = animatedHeight.addListener(({
|
|
|
value
|
|
|
@@ -237,8 +283,10 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
const panResponder = useRef(
|
|
|
PanResponder.create({
|
|
|
onStartShouldSetPanResponder: () => false,
|
|
|
- onMoveShouldSetPanResponderCapture: () => true,
|
|
|
+ onMoveShouldSetPanResponderCapture: () => isCanSwipeRef.current ? true : false,
|
|
|
onPanResponderGrant: (evt) => {
|
|
|
+ if(!isCanSwipeRef.current) return;
|
|
|
+
|
|
|
gestureStartY.current = evt.nativeEvent.pageY;
|
|
|
|
|
|
animatedTranslateY.stopAnimation((currentY) => {
|
|
|
@@ -263,6 +311,8 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
animatedTranslateY.setValue(0);
|
|
|
},
|
|
|
onPanResponderMove: (_, gestureState) => {
|
|
|
+ if(!isCanSwipeRef.current) return;
|
|
|
+
|
|
|
const {
|
|
|
dy
|
|
|
} = gestureState;
|
|
|
@@ -303,13 +353,11 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
if (dy < 0) {
|
|
|
let currentDelta = delta;
|
|
|
|
|
|
- if (initialT > 0) {
|
|
|
- const usedForT = Math.min(currentDelta, initialT);
|
|
|
-
|
|
|
- animatedTranslateY.setValue(-usedForT);
|
|
|
+ const effectiveInitialT = Math.max(0, initialT);
|
|
|
+ const usedForT = Math.min(currentDelta, effectiveInitialT);
|
|
|
|
|
|
- currentDelta -= usedForT;
|
|
|
- }
|
|
|
+ animatedTranslateY.setValue(-usedForT);
|
|
|
+ currentDelta -= usedForT;
|
|
|
|
|
|
if (currentDelta > 0) {
|
|
|
if (initialH < pivot) {
|
|
|
@@ -321,7 +369,10 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
currentDelta -= usedForH;
|
|
|
}
|
|
|
|
|
|
- if (currentDelta > 0) {
|
|
|
+ const isRestoringHeight = initialH < pivot;
|
|
|
+ const canScroll = isCanFullScreenOnSwipeRef.current || isWorkAsFullScreenRef.current || !isRestoringHeight;
|
|
|
+
|
|
|
+ if (currentDelta > 0 && canScroll) {
|
|
|
const remainingScroll = maxS - initialS;
|
|
|
if (remainingScroll > 0) {
|
|
|
const usedForS = Math.min(currentDelta, remainingScroll);
|
|
|
@@ -339,7 +390,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
}
|
|
|
|
|
|
if (currentDelta > 0) {
|
|
|
- if (isCanFullScreenOnSwipe) {
|
|
|
+ if (isCanFullScreenOnSwipeRef.current) {
|
|
|
const totalUsedBefore = (initialH < pivot ? (pivot - initialH) : 0);
|
|
|
|
|
|
animatedHeight.setValue(totalUsedBefore + currentDelta);
|
|
|
@@ -390,6 +441,8 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
}
|
|
|
},
|
|
|
onPanResponderEnd: (_, gestureState) => {
|
|
|
+ if(!isCanSwipeRef.current) return;
|
|
|
+
|
|
|
const isAtTop = scrollOffset.current <= 1;
|
|
|
const isAtTavan = heightValue.current >= maxHeight.current - 1;
|
|
|
const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;
|
|
|
@@ -423,12 +476,12 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
if (currentT <= 0.5) {
|
|
|
let toValue = pivot;
|
|
|
|
|
|
- if (isFastSwipeUp && isCanFullScreenOnSwipe) {
|
|
|
+ if (isFastSwipeUp && isCanFullScreenOnSwipeRef.current) {
|
|
|
toValue = tavan;
|
|
|
} else if (isFastSwipeDown) {
|
|
|
toValue = pivot;
|
|
|
} else {
|
|
|
- if (!isCanFullScreenOnSwipe) {
|
|
|
+ if (!isCanFullScreenOnSwipeRef.current) {
|
|
|
toValue = pivot;
|
|
|
} else {
|
|
|
const totalRange = tavan - pivot;
|
|
|
@@ -454,40 +507,55 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
let toValueT = 0;
|
|
|
let isClosing = false;
|
|
|
|
|
|
- if (isFastSwipeDown) {
|
|
|
- toValueT = pivot + 100;
|
|
|
-
|
|
|
- isClosing = true;
|
|
|
+ if (!isSwipeCloseRef.current) {
|
|
|
+ Animated.spring(animatedHeight, {
|
|
|
+ useNativeDriver: false,
|
|
|
+ toValue: pivot,
|
|
|
+ friction: 10,
|
|
|
+ tension: 40
|
|
|
+ }).start();
|
|
|
+
|
|
|
+ Animated.timing(animatedTranslateY, {
|
|
|
+ useNativeDriver: false,
|
|
|
+ duration: 300,
|
|
|
+ toValue: 0
|
|
|
+ }).start();
|
|
|
} else {
|
|
|
- if (currentT > pivot * 0.5) {
|
|
|
+ if (isFastSwipeDown) {
|
|
|
toValueT = pivot + 100;
|
|
|
|
|
|
isClosing = true;
|
|
|
} else {
|
|
|
- toValueT = 0;
|
|
|
+ if (currentT > pivot * 0.5) {
|
|
|
+ toValueT = pivot + 100;
|
|
|
|
|
|
- isClosing = false;
|
|
|
- }
|
|
|
- }
|
|
|
+ isClosing = true;
|
|
|
+ } else {
|
|
|
+ toValueT = 0;
|
|
|
|
|
|
- Animated.timing(animatedTranslateY, {
|
|
|
- useNativeDriver: false,
|
|
|
- toValue: toValueT,
|
|
|
- duration: 300
|
|
|
- }).start(({
|
|
|
- finished
|
|
|
- }) => {
|
|
|
- if (finished && isClosing) {
|
|
|
- setIsActive(false);
|
|
|
-
|
|
|
- if (onClosed) onClosed();
|
|
|
+ isClosing = false;
|
|
|
+ }
|
|
|
}
|
|
|
- });
|
|
|
+
|
|
|
+ Animated.timing(animatedTranslateY, {
|
|
|
+ useNativeDriver: false,
|
|
|
+ toValue: toValueT,
|
|
|
+ duration: 300
|
|
|
+ }).start(({
|
|
|
+ finished
|
|
|
+ }) => {
|
|
|
+ if (finished && isClosing) {
|
|
|
+ setIsActive(false);
|
|
|
+
|
|
|
+ if (onClosed) onClosed();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
onPanResponderTerminationRequest: () => false,
|
|
|
onShouldBlockNativeResponder: () => true
|
|
|
- }),
|
|
|
+ })
|
|
|
).current;
|
|
|
|
|
|
const renderView = () => {
|
|
|
@@ -513,6 +581,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
style
|
|
|
]}
|
|
|
>
|
|
|
+ {renderHeader()}
|
|
|
<ScrollView
|
|
|
onContentSizeChange={(w, h) => {
|
|
|
scrollViewContentHeight.current = h;
|
|
|
@@ -524,9 +593,9 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
onMoveShouldSetResponderCapture={() => false}
|
|
|
showsHorizontalScrollIndicator={false}
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
+ scrollEnabled={!isCanSwipe}
|
|
|
scrollEventThrottle={1}
|
|
|
overScrollMode="never"
|
|
|
- scrollEnabled={false}
|
|
|
ref={scrollViewRef}
|
|
|
bounces={false}
|
|
|
>
|
|
|
@@ -537,6 +606,14 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
</Animated.View>;
|
|
|
};
|
|
|
|
|
|
+ const renderHeader = () => {
|
|
|
+ if(!RenderHeaderComponent) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return <RenderHeaderComponent/>;
|
|
|
+ };
|
|
|
+
|
|
|
const renderBottom = () => {
|
|
|
if(!RenderBottomComponent) {
|
|
|
return null;
|
|
|
@@ -546,7 +623,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
};
|
|
|
|
|
|
const renderHandle = () => {
|
|
|
- if(!isShowHandle) {
|
|
|
+ if(!isShowHandle || isWorkAsFullScreen) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
@@ -587,15 +664,35 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
|
|
|
};
|
|
|
|
|
|
return <Modal
|
|
|
+ isWorkWithPortal={isWorkWithPortal}
|
|
|
isContentRequired={false}
|
|
|
+ key={`${key}-modal`}
|
|
|
isActive={isActive}
|
|
|
+ alignContent="free"
|
|
|
isAnimated={false}
|
|
|
ref={modalRef}
|
|
|
+ onContainerLayout={(e) => {
|
|
|
+ const containerLayoutHeight = e.nativeEvent.layout.height;
|
|
|
+
|
|
|
+ if (containerLayoutHeight && containerLayoutHeight !== containerHeightRef.current) {
|
|
|
+ containerHeightRef.current = containerLayoutHeight;
|
|
|
+
|
|
|
+ if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
|
|
|
+ maxHeight.current = snapPoint;
|
|
|
+ } else if (!isWorkAsFullScreen) {
|
|
|
+ maxHeight.current = containerLayoutHeight - (isForceFullScreenOnSwipe ? 0 : isWrapSafeareaContext ? topSafeArea : 0);
|
|
|
+ } else {
|
|
|
+ maxHeight.current = containerLayoutHeight;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }}
|
|
|
overlayProps={{
|
|
|
onStartShouldSetResponderCapture: () => false,
|
|
|
onMoveShouldSetResponderCapture: () => false
|
|
|
}}
|
|
|
onOverlayPress={() => {
|
|
|
+ if(onOverlayPressed) onOverlayPressed();
|
|
|
+
|
|
|
if(isCloseOnOverlay) {
|
|
|
closeAnimation();
|
|
|
}
|