diff --git a/example/src/Dev.tsx b/example/src/Dev.tsx
index a9f95dd..2a97a6e 100644
--- a/example/src/Dev.tsx
+++ b/example/src/Dev.tsx
@@ -13,10 +13,9 @@ import Animated, {
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';
-import { TextInput } from 'react-native-gesture-handler';
import { useSafeArea } from 'react-native-safe-area-context';
import BottomSheet from '@gorhom/bottom-sheet';
-import BottomSheetKeyboardView from '../../src/components/bottomSheetKeyboardView';
+import SearchHandle from './components/searchHandle'
import Button from './components/button';
import ContactList from './components/contactList';
@@ -106,7 +105,7 @@ const BasicExample = () => {
// renders
return (
- */}
-
+ {/*
-
+ */}
);
};
@@ -174,8 +175,11 @@ const styles = StyleSheet.create({
marginBottom: 6,
},
textInput: {
- backgroundColor: 'blue',
+ backgroundColor: 'red',
+ opacity: 1,
padding: 6,
+ margin: 6,
+ borderRadius: 24,
},
line: {
position: 'absolute',
diff --git a/example/src/components/contactList/ContactList.tsx b/example/src/components/contactList/ContactList.tsx
index fa781ca..f9fb0e8 100644
--- a/example/src/components/contactList/ContactList.tsx
+++ b/example/src/components/contactList/ContactList.tsx
@@ -169,6 +169,7 @@ const styles = StyleSheet.create({
},
container: {
overflow: 'visible',
+ flex: 1,
},
contentContainer: {
paddingHorizontal: 24,
diff --git a/example/src/components/searchHandle/SearchHandle.tsx b/example/src/components/searchHandle/SearchHandle.tsx
index ee43db9..708ad71 100644
--- a/example/src/components/searchHandle/SearchHandle.tsx
+++ b/example/src/components/searchHandle/SearchHandle.tsx
@@ -6,7 +6,6 @@ import {
NativeSyntheticEvent,
TextInputChangeEventData,
} from 'react-native';
-import { useBottomSheet } from '@gorhom/bottom-sheet';
import { TextInput } from 'react-native-gesture-handler';
import isEqual from 'lodash.isequal';
import { useAppearance } from '../../hooks';
@@ -19,7 +18,6 @@ const BottomSheetHandleComponent = () => {
const [value, setValue] = useState('');
// hooks
- const { snapTo } = useBottomSheet();
const { appearance } = useAppearance();
// styles
@@ -45,9 +43,6 @@ const BottomSheetHandleComponent = () => {
},
[]
);
- const handleInputFocus = useCallback(() => {
- snapTo(2);
- }, [snapTo]);
// render
return (
@@ -59,7 +54,6 @@ const BottomSheetHandleComponent = () => {
textContentType="location"
placeholder="Search for a place or address"
onChange={handleInputChange}
- onFocus={handleInputFocus}
/>
);
diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx
index 3f51e90..a10ce6b 100644
--- a/src/components/bottomSheet/BottomSheet.tsx
+++ b/src/components/bottomSheet/BottomSheet.tsx
@@ -49,6 +49,9 @@ import {
WINDOW_HEIGHT,
KEYBOARD_STATE,
KEYBOARD_EASING_MAPPER,
+ KEYBOARD_BEHAVIOR,
+ SHEET_STATE,
+ SCROLLABLE_STATE,
} from '../../constants';
import {
DEFAULT_ANIMATION_EASING,
@@ -61,6 +64,7 @@ import {
DEFAULT_ENABLE_FLASH_SCROLLABLE_INDICATOR_ON_EXPAND,
DEFAULT_ANIMATE_ON_MOUNT,
DECELERATION_RATE,
+ DEFAULT_KEYBOARD_BEHAVIOR,
} from './constants';
import type { ScrollableRef, BottomSheetMethods } from '../../types';
import type { BottomSheetProps } from './types';
@@ -93,6 +97,7 @@ const BottomSheetComponent = forwardRef(
enableHandlePanningGesture = DEFAULT_ENABLE_HANDLE_PANNING_GESTURE,
enableOverDrag = DEFAULT_ENABLE_OVER_DRAG,
enableFlashScrollableIndicatorOnExpand = DEFAULT_ENABLE_FLASH_SCROLLABLE_INDICATOR_ON_EXPAND,
+ keyboardBehavior = DEFAULT_KEYBOARD_BEHAVIOR,
style: _providedStyle,
// layout
@@ -178,13 +183,15 @@ const BottomSheetComponent = forwardRef(
// scrollable variables
const {
scrollableContentOffsetY,
- scrollableDecelerationRate,
+ // scrollableDecelerationRate,
setScrollableRef,
removeScrollableRef,
flashScrollableIndicators,
} = useScrollable();
// keyboard
+ const isExtendedByKeyboard = useSharedValue(false);
+ const animatedKeyboardOffset = useSharedValue(0);
const {
state: keyboardState,
height: keyboardHeight,
@@ -273,24 +280,32 @@ const BottomSheetComponent = forwardRef(
//#region gesture interaction / animation
// variables
- const animatedKeyboardOffset = useDerivedValue(
- () =>
- withTiming(
- keyboardState.value === KEYBOARD_STATE.SHOWN
- ? keyboardHeight.value * -1
- : 0,
- {
- duration: keyboardAnimationDuration.value,
- easing: KEYBOARD_EASING_MAPPER[keyboardAnimationEasing.value],
- }
- ),
- [
- keyboardState,
- keyboardHeight,
- keyboardAnimationDuration,
- keyboardAnimationEasing,
- ]
- );
+ const animatedSheetHeight = useDerivedValue(() => {
+ if (!isExtendedByKeyboard.value) {
+ return sheetHeight;
+ }
+
+ if (
+ keyboardBehavior === KEYBOARD_BEHAVIOR.none ||
+ keyboardBehavior === KEYBOARD_BEHAVIOR.extend
+ ) {
+ return sheetHeight;
+ }
+
+ if (keyboardBehavior === KEYBOARD_BEHAVIOR.fullScreen) {
+ return safeContainerHeight - topInset - safeHandleHeight;
+ }
+
+ return (
+ sheetHeight + (isExtendedByKeyboard.value ? keyboardHeight.value : 0)
+ );
+ }, [
+ sheetHeight,
+ keyboardBehavior,
+ safeContainerHeight,
+ topInset,
+ safeHandleHeight,
+ ]);
const animationState = useSharedValue(ANIMATION_STATE.UNDETERMINED);
const animatedSnapPoints = useReactiveSharedValue(snapPoints);
const animatedPosition = useSharedValue(initialPosition);
@@ -316,6 +331,22 @@ const BottomSheetComponent = forwardRef(
)
: -1;
}, [snapPoints, safeContainerHeight, isLayoutCalculated]);
+ const animatedSheetState = useDerivedValue(() => {
+ const extendedSheetPosition =
+ safeContainerHeight - safeHandleHeight - sheetHeight;
+
+ if (animatedPosition.value >= safeContainerHeight) {
+ return SHEET_STATE.CLOSED;
+ } else if (animatedPosition.value === extendedSheetPosition) {
+ return SHEET_STATE.EXTENDED;
+ } else if (animatedPosition.value === topInset) {
+ return SHEET_STATE.FULL_SCREEN;
+ } else if (animatedPosition.value < extendedSheetPosition) {
+ return SHEET_STATE.OVER_EXTENDED;
+ }
+
+ return SHEET_STATE.OPENED;
+ });
// callbacks
const animateToPointCompleted = useWorkletCallback(() => {
@@ -358,31 +389,46 @@ const BottomSheetComponent = forwardRef(
]
);
+ const scrollableState = useDerivedValue(() => {
+ return animatedSheetState.value === SHEET_STATE.FULL_SCREEN ||
+ animatedSheetState.value === SHEET_STATE.EXTENDED
+ ? SCROLLABLE_STATE.UNLOCKED
+ : SCROLLABLE_STATE.LOCKED;
+ });
+
// hooks
const [
contentPanGestureHandler,
contentPanGestureState,
- ] = useInteractivePanGestureHandler(
- GESTURE.CONTENT,
- animatedPosition,
- animatedSnapPoints,
- animateToPoint,
+ ] = useInteractivePanGestureHandler({
+ type: GESTURE.CONTENT,
enableOverDrag,
overDragResistanceFactor,
- scrollableContentOffsetY
- );
+ keyboardState,
+ keyboardHeight,
+ keyboardBehavior: keyboardBehavior,
+ animatedPosition,
+ animatedSnapPoints,
+ isExtendedByKeyboard,
+ scrollableContentOffsetY,
+ animateToPoint,
+ });
const [
handlePanGestureHandler,
handlePanGestureState,
- ] = useInteractivePanGestureHandler(
- GESTURE.HANDLE,
+ ] = useInteractivePanGestureHandler({
+ type: GESTURE.HANDLE,
+ enableOverDrag,
+ overDragResistanceFactor,
+ keyboardState,
+ keyboardHeight,
+ keyboardBehavior,
animatedPosition,
animatedSnapPoints,
+ isExtendedByKeyboard,
animateToPoint,
- enableOverDrag,
- overDragResistanceFactor
- );
+ });
//#endregion
//#region layout callbacks
@@ -457,9 +503,10 @@ const BottomSheetComponent = forwardRef(
animatedIndex,
animatedPosition,
animationState,
+ animatedSheetState,
contentPanGestureHandler,
+ scrollableState,
scrollableContentOffsetY,
- scrollableDecelerationRate,
simultaneousHandlers: _providedSimultaneousHandlers,
waitFor: _providedWaitFor,
activeOffsetX: _providedActiveOffsetX,
@@ -474,11 +521,12 @@ const BottomSheetComponent = forwardRef(
animatedIndex,
animatedPosition,
animationState,
+ animatedSheetState,
contentPanGestureHandler,
handleSettingScrollableRef,
removeScrollableRef,
+ scrollableState,
scrollableContentOffsetY,
- scrollableDecelerationRate,
enableContentPanningGesture,
_providedSimultaneousHandlers,
_providedWaitFor,
@@ -531,13 +579,16 @@ const BottomSheetComponent = forwardRef(
() => [_providedStyle, styles.container, containerAnimatedStyle],
[_providedStyle, containerAnimatedStyle]
);
- const contentContainerStyle = useMemo(
+ const contentContainerAnimatedStyle = useAnimatedStyle(
() => ({
- ...styles.contentContainer,
- height: sheetHeight,
+ height: animatedSheetHeight.value,
}),
[sheetHeight]
);
+ const contentContainerStyle = useMemo(
+ () => [styles.contentContainer, contentContainerAnimatedStyle],
+ [contentContainerAnimatedStyle]
+ );
/**
* added safe area to prevent the sheet from floating above
@@ -593,22 +644,74 @@ const BottomSheetComponent = forwardRef(
}
}, [isLayoutCalculated, snapPoints, animateToPoint]);
+ /**
+ * handle keyboard appearance behavior
+ */
+ useAnimatedReaction(
+ () => keyboardState.value,
+ (state, previousState) => {
+ if (state === previousState) {
+ return;
+ }
+
+ /**
+ * Handle extend behavior
+ */
+ if (
+ keyboardBehavior === KEYBOARD_BEHAVIOR.extend &&
+ state === KEYBOARD_STATE.SHOWN
+ ) {
+ const newSnapPoint = snapPoints[snapPoints.length - 1];
+ animateToPoint(newSnapPoint);
+ return;
+ }
+
+ /**
+ * Handle full screen behavior
+ */
+ if (
+ keyboardBehavior === KEYBOARD_BEHAVIOR.fullScreen &&
+ state === KEYBOARD_STATE.SHOWN
+ ) {
+ isExtendedByKeyboard.value = true;
+ animateToPoint(topInset);
+ return;
+ }
+
+ // if (
+ // state === KEYBOARD_STATE.SHOWN &&
+ // keyboardBehavior === KEYBOARD_BEHAVIOR.fullScreen
+ // ) {
+ // animateToPoint(topInset);
+ // } else if (keyboardBehavior === KEYBOARD_BEHAVIOR.interactive) {
+ // animatedKeyboardOffset.value = withTiming(
+ // state === KEYBOARD_STATE.SHOWN ? keyboardHeight.value * -1 : 0,
+ // {
+ // duration: keyboardAnimationDuration.value,
+ // easing: KEYBOARD_EASING_MAPPER[keyboardAnimationEasing.value],
+ // }
+ // );
+ // }
+ },
+ [snapPoints, keyboardBehavior, topInset, animateToPoint]
+ );
+
/**
* set scrollable deceleration rate based on sheet
* position.
*/
- useAnimatedReaction(
- () => animatedIndex.value === snapPoints.length - 1,
- (shouldNormalizeDecelerationRate: boolean) => {
- const newDecelerationRate = shouldNormalizeDecelerationRate
- ? DECELERATION_RATE
- : 0;
- if (scrollableDecelerationRate.value !== newDecelerationRate) {
- scrollableDecelerationRate.value = newDecelerationRate;
- }
- },
- [snapPoints.length]
- );
+ // useAnimatedReaction(
+ // () => [animatedSheetState, animatedPosition],
+ // ([_state, _position]) => {
+ // // const newDecelerationRate = shouldNormalizeDecelerationRate
+ // // ? DECELERATION_RATE
+ // // : 0;
+ // // if (scrollableDecelerationRate.value !== newDecelerationRate) {
+ // // scrollableDecelerationRate.value = newDecelerationRate;
+ // // }
+ // },
+ // [snapPoints.length]
+ // );
/**
* sets provided animated position
@@ -668,7 +771,14 @@ const BottomSheetComponent = forwardRef(
//#endregion
// render
- // console.log('BottomSheet', 'render', snapPoints, sheetHeight);
+ console.log(
+ 'BottomSheet',
+ 'render',
+ snapPoints,
+ safeContainerHeight,
+ safeHandleHeight,
+ sheetHeight
+ );
return (
(
values={{
animatedIndex,
animatedPosition,
- contentPanGestureState,
keyboardState,
keyboardHeight,
keyboardAnimationDuration,
+ animatedSheetHeight,
+ animatedSheetState,
+ scrollableState,
}}
/>
diff --git a/src/components/bottomSheet/constants.ts b/src/components/bottomSheet/constants.ts
index db85178..8575be4 100644
--- a/src/components/bottomSheet/constants.ts
+++ b/src/components/bottomSheet/constants.ts
@@ -1,5 +1,6 @@
import { Platform } from 'react-native';
import Animated, { Easing } from 'react-native-reanimated';
+import { KEYBOARD_BEHAVIOR } from '../../constants';
import { exp } from '../../utilities/easingExp';
const DEFAULT_ANIMATION_EASING: Animated.EasingFunction = Easing.out(exp);
@@ -13,6 +14,9 @@ const DEFAULT_ENABLE_OVER_DRAG = true;
const DEFAULT_ENABLE_FLASH_SCROLLABLE_INDICATOR_ON_EXPAND = true;
const DEFAULT_ANIMATE_ON_MOUNT = false;
+const DEFAULT_KEYBOARD_BEHAVIOR =
+ KEYBOARD_BEHAVIOR.none;
+
const DECELERATION_RATE = Platform.select({
ios: 0.998,
android: 0.985,
@@ -29,5 +33,6 @@ export {
DEFAULT_ENABLE_OVER_DRAG,
DEFAULT_ENABLE_FLASH_SCROLLABLE_INDICATOR_ON_EXPAND,
DEFAULT_ANIMATE_ON_MOUNT,
+ DEFAULT_KEYBOARD_BEHAVIOR,
DECELERATION_RATE,
};
diff --git a/src/components/bottomSheet/types.d.ts b/src/components/bottomSheet/types.d.ts
index 2d2439c..a57d269 100644
--- a/src/components/bottomSheet/types.d.ts
+++ b/src/components/bottomSheet/types.d.ts
@@ -5,6 +5,7 @@ import type { PanGestureHandlerProps } from 'react-native-gesture-handler';
import type { BottomSheetHandleProps } from '../bottomSheetHandle';
import type { BottomSheetBackdropProps } from '../bottomSheetBackdrop';
import type { BottomSheetBackgroundProps } from '../bottomSheetBackground';
+import type { KEYBOARD_BEHAVIOR } from '../../constants';
export interface BottomSheetProps
extends BottomSheetAnimationConfigs,
@@ -92,6 +93,16 @@ export interface BottomSheetProps
* @default false
*/
animateOnMount?: boolean;
+ /**
+ * Defines the keyboard appearance behavior.
+ * - `none`: do nothing.
+ * - `extend`: extend the sheet to its maximum snap point.
+ * - `fullScreen`: extend the sheet to full screen.
+ * - `interactive`: offset the sheet by the size of the keyboard.
+ * @type `none` | `extend` | `interactive`
+ * @default none
+ */
+ keyboardBehavior?: keyof typeof KEYBOARD_BEHAVIOR;
/**
* View style to be applied at the sheet container,
diff --git a/src/components/bottomSheetDebugView/styles.ts b/src/components/bottomSheetDebugView/styles.ts
index 7a0c18d..282bcd2 100644
--- a/src/components/bottomSheetDebugView/styles.ts
+++ b/src/components/bottomSheetDebugView/styles.ts
@@ -3,8 +3,8 @@ import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
container: {
position: 'absolute',
- left: 4,
- top: 4,
+ right: 4,
+ top: 200,
padding: 2,
backgroundColor: 'rgba(0, 0,0,0.75)',
},
diff --git a/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx b/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx
index 42f2b30..576ffa2 100644
--- a/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx
+++ b/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx
@@ -5,6 +5,7 @@ import { PanGestureHandler } from 'react-native-gesture-handler';
import { useBottomSheetInternal } from '../../hooks';
import type { BottomSheetDraggableViewProps } from './types';
import { styles } from './styles';
+import { StyleSheet } from 'react-native';
const BottomSheetDraggableViewComponent = ({
nativeGestureRef,
@@ -47,10 +48,17 @@ const BottomSheetDraggableViewComponent = ({
}, [_providedSimultaneousHandlers, nativeGestureRef]);
// styles
- const containerStyle = useMemo(
- () => (style ? [styles.container, style] : styles.container),
- [style]
- );
+ const containerStyle = useMemo(() => {
+ if (!style) {
+ return styles.container;
+ }
+
+ if (Array.isArray(style)) {
+ return [styles.container, ...style];
+ }
+
+ return [styles.container, style];
+ }, [style]);
return (
;
animatedIndex: Animated.SharedValue;
animationState: Animated.SharedValue;
+ animatedSheetState: Animated.SharedValue;
contentPanGestureHandler: any;
+ scrollableState: Animated.SharedValue;
scrollableContentOffsetY: Animated.SharedValue;
scrollableDecelerationRate: Animated.SharedValue;
setScrollableRef: (ref: ScrollableRef) => void;
diff --git a/src/hooks/useInteractivePanGestureHandler.ts b/src/hooks/useInteractivePanGestureHandler.ts
index 7ab2796..96ac66b 100644
--- a/src/hooks/useInteractivePanGestureHandler.ts
+++ b/src/hooks/useInteractivePanGestureHandler.ts
@@ -10,21 +10,40 @@ import {
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import { clamp, snapPoint } from 'react-native-redash';
-import { GESTURE } from '../constants';
+import { GESTURE, KEYBOARD_BEHAVIOR, KEYBOARD_STATE } from '../constants';
+
+interface useInteractivePanGestureHandlerConfigs {
+ type: GESTURE;
+ enableOverDrag: boolean;
+ overDragResistanceFactor: number;
+ isExtendedByKeyboard: Animated.SharedValue;
+ keyboardState: Animated.SharedValue;
+ keyboardHeight: Animated.SharedValue;
+ keyboardBehavior: keyof typeof KEYBOARD_BEHAVIOR;
+ animatedSnapPoints: Animated.SharedValue;
+ animatedPosition: Animated.SharedValue;
+ scrollableContentOffsetY?: Animated.SharedValue;
+ animateToPoint: (point: number, velocity: number) => void;
+}
type InteractivePanGestureHandlerContextType = {
currentPosition: number;
+ keyboardState: KEYBOARD_STATE;
};
-export const useInteractivePanGestureHandler = (
- type: GESTURE,
- animatedPosition: Animated.SharedValue,
- snapPoints: Animated.SharedValue,
- animateToPoint: (point: number, velocity: number) => void,
- enableOverDrag: boolean,
- overDragResistanceFactor: number,
- scrollableContentOffsetY?: Animated.SharedValue
-): [
+export const useInteractivePanGestureHandler = ({
+ type,
+ enableOverDrag,
+ overDragResistanceFactor,
+ keyboardState,
+ keyboardHeight,
+ keyboardBehavior,
+ isExtendedByKeyboard,
+ animatedPosition,
+ animatedSnapPoints,
+ scrollableContentOffsetY,
+ animateToPoint,
+}: useInteractivePanGestureHandlerConfigs): [
(event: PanGestureHandlerGestureEvent) => void,
Animated.SharedValue,
Animated.SharedValue,
@@ -37,108 +56,127 @@ export const useInteractivePanGestureHandler = (
const gestureHandler = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
InteractivePanGestureHandlerContextType
- >(
- {
- onStart: ({ state, translationY, velocityY }, context) => {
- // cancel current animation
- cancelAnimation(animatedPosition);
+ >({
+ onStart: ({ state, translationY, velocityY }, context) => {
+ // cancel current animation
+ cancelAnimation(animatedPosition);
- // store current animated position
- context.currentPosition = animatedPosition.value;
+ // store current animated position
+ context.currentPosition = animatedPosition.value;
+ context.keyboardState = keyboardState.value;
- // set variables
- gestureState.value = state;
- gestureTranslationY.value = translationY;
- gestureVelocityY.value = velocityY;
- },
- onActive: ({ state, translationY, velocityY }, context) => {
- gestureState.value = state;
- gestureTranslationY.value = translationY;
- gestureVelocityY.value = velocityY;
+ if (
+ keyboardState.value === KEYBOARD_STATE.SHOWN &&
+ (keyboardBehavior === KEYBOARD_BEHAVIOR.interactive ||
+ keyboardBehavior === KEYBOARD_BEHAVIOR.fullScreen)
+ ) {
+ isExtendedByKeyboard.value = true;
+ }
- runOnJS(Keyboard.dismiss)();
-
- const position = context.currentPosition + translationY;
- const negativeScrollableContentOffset =
- context.currentPosition ===
- snapPoints.value[snapPoints.value.length - 1] &&
- scrollableContentOffsetY
- ? scrollableContentOffsetY.value * -1
- : 0;
- const clampedPosition = clamp(
- position + negativeScrollableContentOffset,
- snapPoints.value[snapPoints.value.length - 1],
- snapPoints.value[0]
- );
-
- if (enableOverDrag) {
- if (
- type === GESTURE.HANDLE &&
- position <= snapPoints.value[snapPoints.value.length - 1]
- ) {
- const resistedPosition =
- snapPoints.value[snapPoints.value.length - 1] -
- Math.sqrt(
- 1 + (snapPoints.value[snapPoints.value.length - 1] - position)
- ) *
- overDragResistanceFactor;
- animatedPosition.value = resistedPosition;
- return;
- }
-
- if (position >= snapPoints.value[0]) {
- const resistedPosition =
- snapPoints.value[0] +
- Math.sqrt(1 + (position - snapPoints.value[0])) *
- overDragResistanceFactor;
- animatedPosition.value = resistedPosition;
- return;
- }
- }
-
- animatedPosition.value = clampedPosition;
- },
- onEnd: ({ state }, context) => {
- gestureState.value = state;
-
- const destinationPoint = snapPoint(
- gestureTranslationY.value + context.currentPosition,
- gestureVelocityY.value,
- snapPoints.value
- );
-
- /**
- * if destination point is the same as the current position,
- * then no need to perform animation.
- */
- if (destinationPoint === animatedPosition.value) {
- return;
- }
-
- if (
- (scrollableContentOffsetY ? scrollableContentOffsetY.value : 0) > 0 &&
- context.currentPosition ===
- snapPoints.value[snapPoints.value.length - 1] &&
- animatedPosition.value ===
- snapPoints.value[snapPoints.value.length - 1]
- ) {
- return;
- }
-
- animateToPoint(destinationPoint, gestureVelocityY.value / 2);
- },
- onCancel: ({ state }) => {
- gestureState.value = state;
- },
- onFail: ({ state }) => {
- gestureState.value = state;
- },
- onFinish: ({ state }) => {
- gestureState.value = state;
- },
+ // set variables
+ gestureState.value = state;
+ gestureTranslationY.value = translationY;
+ gestureVelocityY.value = velocityY;
},
- [snapPoints, enableOverDrag, overDragResistanceFactor]
- );
+ onActive: ({ state, translationY, velocityY }, context) => {
+ gestureState.value = state;
+ gestureTranslationY.value = translationY;
+ gestureVelocityY.value = velocityY;
+
+ runOnJS(Keyboard.dismiss)();
+
+ const position = context.currentPosition + translationY;
+ const maxSnapPoint = isExtendedByKeyboard.value
+ ? context.currentPosition
+ : animatedSnapPoints.value[animatedSnapPoints.value.length - 1];
+
+ const negativeScrollableContentOffset =
+ context.currentPosition === maxSnapPoint && scrollableContentOffsetY
+ ? scrollableContentOffsetY.value * -1
+ : 0;
+ const clampedPosition = clamp(
+ position + negativeScrollableContentOffset,
+ maxSnapPoint,
+ animatedSnapPoints.value[0]
+ );
+
+ if (enableOverDrag) {
+ if (type === GESTURE.HANDLE && position <= maxSnapPoint) {
+ const resistedPosition =
+ maxSnapPoint -
+ Math.sqrt(1 + (maxSnapPoint - position)) * overDragResistanceFactor;
+ animatedPosition.value = resistedPosition;
+ return;
+ }
+
+ if (position >= animatedSnapPoints.value[0]) {
+ const resistedPosition =
+ animatedSnapPoints.value[0] +
+ Math.sqrt(1 + (position - animatedSnapPoints.value[0])) *
+ overDragResistanceFactor;
+ animatedPosition.value = resistedPosition;
+ return;
+ }
+ }
+
+ animatedPosition.value = clampedPosition;
+ },
+ onEnd: ({ state }, context) => {
+ gestureState.value = state;
+
+ /**
+ *
+ */
+ console.log(
+ 'currentPosition',
+ context.currentPosition,
+ 'animatedPosition',
+ animatedPosition.value
+ );
+ if (
+ isExtendedByKeyboard.value &&
+ context.currentPosition >= animatedPosition.value
+ ) {
+ return;
+ }
+ isExtendedByKeyboard.value = false;
+
+ const destinationPoint = snapPoint(
+ gestureTranslationY.value + context.currentPosition,
+ gestureVelocityY.value,
+ animatedSnapPoints.value
+ );
+
+ /**
+ * if destination point is the same as the current position,
+ * then no need to perform animation.
+ */
+ if (destinationPoint === animatedPosition.value) {
+ return;
+ }
+
+ if (
+ (scrollableContentOffsetY ? scrollableContentOffsetY.value : 0) > 0 &&
+ context.currentPosition ===
+ animatedSnapPoints.value[animatedSnapPoints.value.length - 1] &&
+ animatedPosition.value ===
+ animatedSnapPoints.value[animatedSnapPoints.value.length - 1]
+ ) {
+ return;
+ }
+
+ animateToPoint(destinationPoint, gestureVelocityY.value / 2);
+ },
+ onCancel: ({ state }) => {
+ gestureState.value = state;
+ },
+ onFail: ({ state }) => {
+ gestureState.value = state;
+ },
+ onFinish: ({ state }) => {
+ gestureState.value = state;
+ },
+ });
return [gestureHandler, gestureState, gestureTranslationY, gestureVelocityY];
};
diff --git a/src/hooks/useScrollable.ts b/src/hooks/useScrollable.ts
index 318e646..a70bcf9 100644
--- a/src/hooks/useScrollable.ts
+++ b/src/hooks/useScrollable.ts
@@ -1,6 +1,7 @@
import { useCallback, RefObject, useRef } from 'react';
import { findNodeHandle, Platform } from 'react-native';
import { useSharedValue } from 'react-native-reanimated';
+import { SCROLLABLE_STATE } from '../constants';
import type { ScrollableRef, Scrollable } from '../types';
export const useScrollable = () => {
@@ -9,6 +10,9 @@ export const useScrollable = () => {
const previousScrollableRef = useRef(null);
// variables
+ const scrollableState = useSharedValue(
+ SCROLLABLE_STATE.LOCKED
+ );
const scrollableContentOffsetY = useSharedValue(0);
const scrollableDecelerationRate = useSharedValue(0);
@@ -77,6 +81,7 @@ export const useScrollable = () => {
return {
scrollableRef,
+ scrollableState,
scrollableContentOffsetY,
scrollableDecelerationRate,
setScrollableRef,
diff --git a/src/hooks/useScrollableInternal.ts b/src/hooks/useScrollableInternal.ts
index 1994c9e..2091ce5 100644
--- a/src/hooks/useScrollableInternal.ts
+++ b/src/hooks/useScrollableInternal.ts
@@ -10,7 +10,11 @@ import {
} from 'react-native-reanimated';
import { useBottomSheetInternal } from './useBottomSheetInternal';
import type { Scrollable, ScrollableType } from '../types';
-import { ANIMATION_STATE } from '../constants';
+import {
+ ANIMATION_STATE,
+ SCROLLABLE_DECELERATION_RATE_MAPPER,
+ SCROLLABLE_STATE,
+} from '../constants';
export const useScrollableInternal = (type: ScrollableType) => {
// refs
@@ -21,10 +25,8 @@ export const useScrollableInternal = (type: ScrollableType) => {
// hooks
const {
- snapPointsCount,
+ scrollableState,
animationState,
- animatedIndex,
- scrollableDecelerationRate,
scrollableContentOffsetY: _rootScrollableContentOffsetY,
setScrollableRef,
removeScrollableRef,
@@ -32,13 +34,14 @@ export const useScrollableInternal = (type: ScrollableType) => {
// variables
const scrollableAnimatedProps = useAnimatedProps(() => ({
- decelerationRate: scrollableDecelerationRate.value,
+ decelerationRate:
+ SCROLLABLE_DECELERATION_RATE_MAPPER[scrollableState.value],
}));
// callbacks
const handleScrollEvent = useAnimatedScrollHandler({
onBeginDrag: ({ contentOffset: { y } }: NativeScrollEvent) => {
- if (animatedIndex.value !== snapPointsCount - 1) {
+ if (scrollableState.value === SCROLLABLE_STATE.LOCKED) {
initialScrollingPosition.value = y;
justStartedScrolling.value = 1;
scrollableContentOffsetY.value = 0;
@@ -55,7 +58,7 @@ export const useScrollableInternal = (type: ScrollableType) => {
scrollTo(scrollableRef, 0, initialScrollingPosition.value, false);
return;
}
- if (animatedIndex.value !== snapPointsCount - 1) {
+ if (scrollableState.value === SCROLLABLE_STATE.LOCKED) {
// @ts-ignore
scrollTo(scrollableRef, 0, 0, false);
scrollableContentOffsetY.value = 0;
@@ -63,7 +66,7 @@ export const useScrollableInternal = (type: ScrollableType) => {
}
},
onEndDrag: ({ contentOffset: { y } }: NativeScrollEvent) => {
- if (animatedIndex.value !== snapPointsCount - 1) {
+ if (scrollableState.value === SCROLLABLE_STATE.LOCKED) {
// @ts-ignore
scrollTo(scrollableRef, 0, 0, false);
scrollableContentOffsetY.value = 0;
@@ -75,7 +78,7 @@ export const useScrollableInternal = (type: ScrollableType) => {
}
},
onMomentumEnd: ({ contentOffset: { y } }: NativeScrollEvent) => {
- if (animatedIndex.value !== snapPointsCount - 1) {
+ if (scrollableState.value === SCROLLABLE_STATE.LOCKED) {
// @ts-ignore
scrollTo(scrollableRef, 0, 0, false);
scrollableContentOffsetY.value = 0;