Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,53 @@ docs/.env.production.local
docs/npm-debug.log*
docs/yarn-debug.log*
docs/yarn-error.log*

# Dependencies
node_modules/

# Build outputs
dist/
target/

# Python
__pycache__/
*.pyc
*.pyo
*.pyd
*.egg-info/
.eggs/

# Logs
*.log

# Environment
.env
.env.local
.env.*

# Editors
.vscode/
.idea/
*.swp
*.swo

# OS
Thumbs.db

# Coverage
coverage/
htmlcov/
.coverage

# Testing
.pytest_cache/
.mypy_cache/

# Distribution / packaging
*.tar.gz
*.whl
*.egg

# Temporary files
*.tmp
*~
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ A performant interactive bottom sheet with fully configurable options 🚀
- Support [pull to refresh](https://gorhom.dev/react-native-bottom-sheet/pull-to-refresh) for scrollables.
- Support `FlatList`, `SectionList`, `ScrollView` & `View` scrolling interactions, [read more](https://gorhom.dev/react-native-bottom-sheet/scrollables).
- Support `React Navigation` Integration, [read more](https://gorhom.dev/react-native-bottom-sheet/react-navigation-integration).
- Compatible with `Reanimated` v1-3.
- Compatible with `Reanimated` v1-4.
- Compatible with `Expo`.
- Accessibility support.
- Written in `TypeScript`.
Expand All @@ -34,13 +34,23 @@ Check out [the documentation website](https://gorhom.dev/react-native-bottom-she

This library been written in 3 versions of `Reanimated`, and kept all implementation in separate branches:

- **`v6`** | [branch](https://github.com/gorhom/react-native-bottom-sheet/tree/master) | [changelog](https://github.com/gorhom/react-native-bottom-sheet/blob/master/CHANGELOG.md) : written with `Reanimated v4` & `Gesture Handler v2`.

- **`v5`** | [branch](https://github.com/gorhom/react-native-bottom-sheet/tree/master) | [changelog](https://github.com/gorhom/react-native-bottom-sheet/blob/master/CHANGELOG.md) : written with `Reanimated v3` & `Gesture Handler v2`.

- `v4` (not maintained) | [branch](https://github.com/gorhom/react-native-bottom-sheet/tree/v4) | [changelog](https://github.com/gorhom/react-native-bottom-sheet/blob/v4/CHANGELOG.md) : written with `Reanimated v2`.

- `v2` (not maintained) | [branch](https://github.com/gorhom/react-native-bottom-sheet/tree/v2) | [changelog](https://github.com/gorhom/react-native-bottom-sheet/blob/v2/CHANGELOG.md) : written with `Reanimated v1` & compatible with `Reanimated v2`.

> I highly recommend to use `v5` which provides more stability with all latest features.
> I highly recommend to use `v6` which provides more stability with all latest features.

### React Native Reanimated 4 Support

Starting from v5.1.8, this library supports `react-native-reanimated` v4. When using Reanimated 4:

- **New Architecture is required** - Reanimated 4 only supports the New Architecture
- **`react-native-worklets` dependency is required** - Install it alongside `react-native-reanimated`
- The babel plugin has been updated to use `react-native-worklets/plugin`

## Author

Expand Down
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['react-native-reanimated/plugin'],
plugins: ['react-native-worklets/plugin'],
};
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gorhom/bottom-sheet",
"version": "5.2.14",
"version": "6.0.0-alpha.0",
"description": "A performant interactive bottom sheet with fully configurable options 🚀",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down Expand Up @@ -61,6 +61,7 @@
"react-native-builder-bob": "^0.30.3",
"react-native-gesture-handler": "~2.20.2",
"react-native-reanimated": "~3.19.1",
"react-native-worklets": ">=0.5.1",
"release-it": "^19.0.4",
"typescript": "^5.8.3"
},
Expand All @@ -70,7 +71,8 @@
"react": "*",
"react-native": "*",
"react-native-gesture-handler": ">=2.16.1",
"react-native-reanimated": ">=3.16.0 || >=4.0.0-"
"react-native-reanimated": ">=4.0.0",
"react-native-worklets": ">=0.5.1"
},
"peerDependenciesMeta": {
"@types/react-native": {
Expand Down
56 changes: 35 additions & 21 deletions src/components/bottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ import React, {
useEffect,
useImperativeHandle,
useMemo,
useState,
} from 'react';
import { Dimensions, Platform, StyleSheet } from 'react-native';
import { State } from 'react-native-gesture-handler';
import Animated, {
import {
cancelAnimation,
Extrapolation,
interpolate,
ReduceMotion,
runOnJS,
runOnUI,
useAnimatedReaction,
useDerivedValue,
useReducedMotion,
useSharedValue,
type WithSpringConfig,
type WithTimingConfig,
} from 'react-native-reanimated';
import { scheduleOnRN, scheduleOnUI } from 'react-native-worklets';
import {
ANIMATION_SOURCE,
ANIMATION_STATUS,
Expand Down Expand Up @@ -84,9 +84,8 @@ import {
} from './constants';
import type { AnimateToPositionType, BottomSheetProps } from './types';

Animated.addWhitelistedUIProps({
decelerationRate: true,
});
// NOTE: addWhitelistedUIProps was removed in Reanimated 4
// and decelerationRate is now handled by default.

type BottomSheet = BottomSheetMethods;

Expand Down Expand Up @@ -279,6 +278,13 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
? userReduceMotionSetting
: _providedOverrideReduceMotion === ReduceMotion.Always;
}, [userReduceMotionSetting, _providedOverrideReduceMotion]);

const [, _forceUpdate] = useState(0);
const handleReduceMotionMountFlush = useCallback(() => {
if (Platform.OS === 'android' && reduceMotion) {
_forceUpdate((prev: number) => prev + 1);
}
}, [reduceMotion]);
//#endregion

//#region state/dynamic variables
Expand Down Expand Up @@ -537,7 +543,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
const { nextIndex, nextPosition } = animatedAnimationState.get();

if (__DEV__) {
runOnJS(print)({
scheduleOnRN(print, {
component: 'BottomSheet',
method: 'animateToPositionCompleted',
params: {
Expand All @@ -554,11 +560,11 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(

// callbacks
if (nextIndex !== animatedCurrentIndex.get()) {
runOnJS(handleOnChange)(nextIndex, nextPosition);
scheduleOnRN(handleOnChange, nextIndex, nextPosition);
}

if (nextIndex === -1) {
runOnJS(handleOnClose)();
scheduleOnRN(handleOnClose);
}

animatedCurrentIndex.set(nextIndex);
Expand Down Expand Up @@ -592,7 +598,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
) {
'worklet';
if (__DEV__) {
runOnJS(print)({
scheduleOnRN(print, {
component: 'BottomSheet',
method: 'animateToPosition',
params: {
Expand Down Expand Up @@ -683,7 +689,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
/**
* fire `onAnimate` callback
*/
runOnJS(handleOnAnimate)(index, position);
scheduleOnRN(handleOnAnimate, index, position);

/**
* start animation
Expand Down Expand Up @@ -739,7 +745,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
}

if (__DEV__) {
runOnJS(print)({
scheduleOnRN(print, {
component: 'BottomSheet',
method: 'setToPosition',
params: {
Expand Down Expand Up @@ -982,7 +988,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
* if animate on mount is set to true, then we animate to the propose position,
* else, we set the position with out animation.
*/
if (animateOnMount) {
if (animateOnMount && !(Platform.OS === 'android' && reduceMotion)) {
animateToPosition(
proposedPosition,
ANIMATION_SOURCE.MOUNT,
Expand All @@ -992,6 +998,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
} else {
setToPosition(proposedPosition);
didAnimateOnMount.value = true;
scheduleOnRN(handleReduceMotionMountFlush);
}
return;
}
Expand Down Expand Up @@ -1082,6 +1089,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
didAnimateOnMount,
isInTemporaryPosition,
isLayoutCalculated,
handleReduceMotionMountFlush,
]
);
//#endregion
Expand Down Expand Up @@ -1144,7 +1152,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
*/
isInTemporaryPosition.value = false;

runOnUI(animateToPosition)(
scheduleOnUI(
animateToPosition,
targetPosition,
ANIMATION_SOURCE.USER,
0,
Expand Down Expand Up @@ -1193,7 +1202,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
*/
isInTemporaryPosition.value = true;

runOnUI(animateToPosition)(
scheduleOnUI(
animateToPosition,
targetPosition,
ANIMATION_SOURCE.USER,
0,
Expand Down Expand Up @@ -1247,7 +1257,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
*/
isInTemporaryPosition.value = false;

runOnUI(animateToPosition)(
scheduleOnUI(
animateToPosition,
targetPosition,
ANIMATION_SOURCE.USER,
0,
Expand Down Expand Up @@ -1307,7 +1318,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
};
});

runOnUI(animateToPosition)(
scheduleOnUI(
animateToPosition,
targetPosition,
ANIMATION_SOURCE.USER,
0,
Expand Down Expand Up @@ -1362,7 +1374,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
*/
isInTemporaryPosition.value = false;

runOnUI(animateToPosition)(
scheduleOnUI(
animateToPosition,
targetPosition,
ANIMATION_SOURCE.USER,
0,
Expand Down Expand Up @@ -1417,7 +1430,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
*/
isInTemporaryPosition.value = false;

runOnUI(animateToPosition)(
scheduleOnUI(
animateToPosition,
targetPosition,
ANIMATION_SOURCE.USER,
0,
Expand Down Expand Up @@ -1605,7 +1619,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
}

if (__DEV__) {
runOnJS(print)({
scheduleOnRN(print, {
component: 'BottomSheet',
method: 'useAnimatedReaction::OnSnapPointChange',
category: 'effect',
Expand Down Expand Up @@ -1673,7 +1687,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
: Math.abs(height - containerOffset.bottom);

if (__DEV__) {
runOnJS(print)({
scheduleOnRN(print, {
component: 'BottomSheet',
method: 'useAnimatedReaction::OnKeyboardStateChange',
category: 'effect',
Expand Down
28 changes: 27 additions & 1 deletion src/components/bottomSheet/BottomSheetContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { memo, useMemo } from 'react';
import type { ViewProps, ViewStyle } from 'react-native';
import { State } from 'react-native-gesture-handler';
import Animated, {
type AnimatedStyle,
useAnimatedStyle,
Expand Down Expand Up @@ -51,6 +52,8 @@ function BottomSheetContentComponent({
animatedSheetHeight,
animatedKeyboardState,
isInTemporaryPosition,
animatedContentGestureState,
animatedHandleGestureState,
} = useBottomSheetInternal();
//#endregion

Expand Down Expand Up @@ -199,6 +202,28 @@ function BottomSheetContentComponent({
const paddingBottom = detached ? 0 : animatedPaddingBottom.get();
const height = animatedContentHeightMax.get() + paddingBottom;

/**
* if user is actively panning the sheet (handle or content gesture),
* bypass animate() to prevent mask geometry desync during the gesture.
* this fixes the Android Reanimated 4.x bug where the content mask
* temporarily collapses during a slow pan-down, causing the screen
* behind to flash through.
* @see https://github.com/gorhom/react-native-bottom-sheet/issues/2716
* @see https://github.com/gorhom/react-native-bottom-sheet/issues/2702
*/
const hasActiveGesture =
animatedContentGestureState.get() === State.ACTIVE ||
animatedContentGestureState.get() === State.BEGAN ||
animatedHandleGestureState.get() === State.ACTIVE ||
animatedHandleGestureState.get() === State.BEGAN;

if (hasActiveGesture) {
return {
paddingBottom,
height,
};
}

return {
paddingBottom: animate({
point: paddingBottom,
Expand All @@ -219,7 +244,8 @@ function BottomSheetContentComponent({
overrideReduceMotion,
animatedLayoutState,
animatedContentHeightMax,
animatedLayoutState,
animatedContentGestureState,
animatedHandleGestureState,
]);
const contentContainerStyle = useMemo(
() => [
Expand Down
Loading