diff --git a/example/app/package.json b/example/app/package.json
index 0894dc9f4..5d6cbf9bd 100644
--- a/example/app/package.json
+++ b/example/app/package.json
@@ -20,10 +20,10 @@
"nanoid": "^3.3.3",
"react": "17.0.2",
"react-native": "0.68.1",
- "react-native-gesture-handler": "^2.5.0",
+ "react-native-gesture-handler": "~2.8.0",
"react-native-maps": "^0.30.1",
"react-native-pager-view": "^5.4.24",
- "react-native-reanimated": "^2.9.1",
+ "react-native-reanimated": "~2.12.0",
"react-native-redash": "^16.0.11",
"react-native-safe-area-context": "4.2.4",
"react-native-screens": "^3.15.0",
diff --git a/example/app/src/components/contactList/styles.web.ts b/example/app/src/components/contactList/styles.web.ts
index b4f6f8ff0..44e5b1e37 100644
--- a/example/app/src/components/contactList/styles.web.ts
+++ b/example/app/src/components/contactList/styles.web.ts
@@ -9,10 +9,10 @@ export const styles = StyleSheet.create({
sectionHeaderTitle: {
fontSize: 16,
textTransform: 'uppercase',
+ color: 'black',
},
container: {
flex: 1,
- paddingHorizontal: 16,
},
contentContainer: {
paddingHorizontal: 16,
diff --git a/example/app/src/screens/modal/DetachedExample.tsx b/example/app/src/screens/modal/DetachedExample.tsx
index 2e06327fc..456b995a6 100644
--- a/example/app/src/screens/modal/DetachedExample.tsx
+++ b/example/app/src/screens/modal/DetachedExample.tsx
@@ -76,7 +76,7 @@ const DetachedExample = () => {
snapPoints={animatedSnapPoints}
handleHeight={animatedHandleHeight}
contentHeight={animatedContentHeight}
- bottomInset={safeBottomArea + 34}
+ bottomInset={safeBottomArea + 16}
enablePanDownToClose={true}
style={styles.sheetContainer}
backgroundComponent={null}
@@ -118,7 +118,7 @@ const styles = StyleSheet.create({
contentContainerStyle: {
paddingTop: 12,
paddingBottom: 12,
- paddingHorizontal: 16,
+ paddingHorizontal: 12,
},
footer: {
justifyContent: 'center',
diff --git a/example/bare/ios/Podfile.lock b/example/bare/ios/Podfile.lock
index 644322743..3d1109926 100644
--- a/example/bare/ios/Podfile.lock
+++ b/example/bare/ios/Podfile.lock
@@ -306,7 +306,7 @@ PODS:
- React
- react-native-maps (0.30.2):
- React-Core
- - react-native-pager-view (5.4.24):
+ - react-native-pager-view (5.4.25):
- React-Core
- react-native-safe-area-context (4.2.4):
- RCT-Folly
@@ -382,7 +382,7 @@ PODS:
- React-perflogger (= 0.69.4)
- RNCMaskedView (0.1.11):
- React
- - RNGestureHandler (2.6.2):
+ - RNGestureHandler (2.7.0):
- React-Core
- RNReanimated (2.10.0):
- DoubleConversion
@@ -411,7 +411,7 @@ PODS:
- React-RCTText
- ReactCommon/turbomodule/core
- Yoga
- - RNScreens (3.15.0):
+ - RNScreens (3.17.0):
- React-Core
- React-RCTImage
- SocketRocket (0.6.0)
@@ -629,7 +629,7 @@ SPEC CHECKSUMS:
React-logger: 1088859f145b8f6dd0d3ed051a647ef0e3e80fad
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-maps: df7b9fca1b1c8d356fadbf5b8a63a5f8cf32fc73
- react-native-pager-view: 95d0418c3c74279840abec6926653d32447bafb6
+ react-native-pager-view: da490aa1f902c9a5aeecf0909cc975ad0e92e53e
react-native-safe-area-context: f98b0b16d1546d208fc293b4661e3f81a895afd9
React-perflogger: cb386fd44c97ec7f8199c04c12b22066b0f2e1e0
React-RCTActionSheet: f803a85e46cf5b4066c2ac5e122447f918e9c6e5
@@ -644,9 +644,9 @@ SPEC CHECKSUMS:
React-runtimeexecutor: 61ee22a8cdf8b6bb2a7fb7b4ba2cc763e5285196
ReactCommon: 8f67bd7e0a6afade0f20718f859dc8c2275f2e83
RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489
- RNGestureHandler: 4defbd70b2faf3d6761b82fa7880285241762cb0
+ RNGestureHandler: 7673697e7c0e9391adefae4faa087442bc04af33
RNReanimated: 7faa787e8d4493fbc95fab2ad331fa7625828cfa
- RNScreens: 4a1af06327774490d97342c00aee0c2bafb497b7
+ RNScreens: 0df01424e9e0ed7827200d6ed1087ddd06c493f9
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
Yoga: ff994563b2fd98c982ca58e8cd9db2cdaf4dda74
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
diff --git a/example/expo/babel.config.js b/example/expo/babel.config.js
index 8c702fa88..dd6659691 100644
--- a/example/expo/babel.config.js
+++ b/example/expo/babel.config.js
@@ -16,6 +16,7 @@ module.exports = function (api) {
return {
presets: ['babel-preset-expo'],
plugins: [
+ '@babel/plugin-proposal-export-namespace-from',
'react-native-reanimated/plugin',
[
'module-resolver',
diff --git a/example/expo/index.ts b/example/expo/index.ts
index d70a55804..58a0f2985 100644
--- a/example/expo/index.ts
+++ b/example/expo/index.ts
@@ -1,5 +1,8 @@
import { registerRootComponent } from 'expo';
+import { enableExperimentalWebImplementation } from 'react-native-gesture-handler';
+enableExperimentalWebImplementation(true);
+
import { enableScreens } from 'react-native-screens';
enableScreens(true);
diff --git a/example/expo/package.json b/example/expo/package.json
index 521b9a090..e7adbb90d 100644
--- a/example/expo/package.json
+++ b/example/expo/package.json
@@ -5,13 +5,13 @@
"private": true,
"main": "./index.ts",
"scripts": {
- "start": "expo start",
- "android": "expo start --android",
- "ios": "expo start --ios",
- "web": "expo start --web",
- "eject": "expo eject"
+ "start": "npx expo start",
+ "android": "npx expo start --android",
+ "ios": "npx expo start --ios",
+ "web": "npx expo start --web"
},
"dependencies": {
+ "@expo/webpack-config": "^0.17.2",
"@gorhom/portal": "^1.0.13",
"@gorhom/showcase-template": "^2.1.0",
"@react-navigation/bottom-tabs": "^6.3.1",
@@ -20,28 +20,29 @@
"@react-navigation/native": "^6.0.10",
"@react-navigation/native-stack": "^6.6.2",
"@react-navigation/stack": "^6.2.1",
- "expo": "^46.0.0",
- "expo-status-bar": "~1.4.0",
+ "expo": "^47.0.0",
+ "expo-status-bar": "~1.4.2",
"faker": "^4.1.0",
"nanoid": "^3.3.3",
- "react": "18.0.0",
- "react-dom": "18.0.0",
- "react-native": "0.69.4",
- "react-native-gesture-handler": "~2.5.0",
- "react-native-pager-view": "5.4.24",
- "react-native-reanimated": "~2.9.1",
+ "react": "18.1.0",
+ "react-dom": "18.1.0",
+ "react-native": "0.70.5",
+ "react-native-gesture-handler": "~2.8.0",
+ "react-native-pager-view": "6.0.1",
+ "react-native-reanimated": "~2.12.0",
"react-native-redash": "^16.2.4",
- "react-native-safe-area-context": "4.3.1",
- "react-native-screens": "~3.15.0",
+ "react-native-safe-area-context": "4.4.1",
+ "react-native-screens": "~3.18.0",
"react-native-tab-view": "^3.1.1",
"react-native-web": "~0.18.7"
},
"devDependencies": {
- "@babel/core": "^7.18.6",
- "@types/react": "~18.0.0",
- "@types/react-native": "~0.69.1",
+ "@babel/core": "^7.19.3",
+ "@babel/plugin-proposal-export-namespace-from": "^7.18.9",
+ "@types/react": "~18.0.24",
+ "@types/react-native": "~0.70.6",
+ "babel-loader": "^8.2.3",
"babel-plugin-module-resolver": "^4.1.0",
- "expo-cli": "^6.0.2",
"typescript": "^4.6.3"
},
"resolutions": {
diff --git a/example/expo/web/index.html b/example/expo/web/index.html
new file mode 100644
index 000000000..b98004386
--- /dev/null
+++ b/example/expo/web/index.html
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+ %WEB_TITLE%
+
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index b450a0d19..5302630c4 100644
--- a/package.json
+++ b/package.json
@@ -69,8 +69,8 @@
"peerDependencies": {
"react": "*",
"react-native": "*",
- "react-native-gesture-handler": ">=2.5.0",
- "react-native-reanimated": ">=2.9.0"
+ "react-native-gesture-handler": ">=2.6.0",
+ "react-native-reanimated": ">=2.10.0"
},
"react-native-builder-bob": {
"source": "src",
diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx
index bd5ebddaf..8f6013255 100644
--- a/src/components/bottomSheet/BottomSheet.tsx
+++ b/src/components/bottomSheet/BottomSheet.tsx
@@ -1370,12 +1370,15 @@ const BottomSheetComponent = forwardRef(
/**
* Calculate the keyboard height in the container.
*/
- animatedKeyboardHeightInContainer.value = $modal
- ? Math.abs(
- _keyboardHeight -
- Math.abs(bottomInset - animatedContainerOffset.value.bottom)
- )
- : Math.abs(_keyboardHeight - animatedContainerOffset.value.bottom);
+ animatedKeyboardHeightInContainer.value =
+ _keyboardHeight === 0
+ ? 0
+ : $modal
+ ? Math.abs(
+ _keyboardHeight -
+ Math.abs(bottomInset - animatedContainerOffset.value.bottom)
+ )
+ : Math.abs(_keyboardHeight - animatedContainerOffset.value.bottom);
/**
* if keyboard state is equal to the previous state, then exit the method
diff --git a/src/components/bottomSheetDebugView/ReText.web.tsx b/src/components/bottomSheetDebugView/ReText.web.tsx
new file mode 100644
index 000000000..94fc67c38
--- /dev/null
+++ b/src/components/bottomSheetDebugView/ReText.web.tsx
@@ -0,0 +1,53 @@
+import React, { useRef } from 'react';
+import { TextProps as RNTextProps, TextInput } from 'react-native';
+import Animated, {
+ useAnimatedReaction,
+ useDerivedValue,
+} from 'react-native-reanimated';
+
+interface TextProps {
+ text: string;
+ value: Animated.SharedValue | number;
+ style?: Animated.AnimateProps['style'];
+}
+
+const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
+
+const ReText = (props: TextProps) => {
+ const { text, value: _providedValue, style } = { style: {}, ...props };
+ const textRef = useRef(null);
+
+ const providedValue = useDerivedValue(() => {
+ const value =
+ typeof _providedValue === 'number'
+ ? _providedValue
+ : typeof _providedValue.value === 'number'
+ ? _providedValue.value.toFixed(2)
+ : _providedValue.value;
+
+ return `${text}: ${value}`;
+ });
+
+ //region effects
+ useAnimatedReaction(
+ () => providedValue.value,
+ result => {
+ textRef.current?.setNativeProps({
+ text: result,
+ });
+ }
+ );
+ //endregion
+
+ return (
+
+ );
+};
+
+export default ReText;
diff --git a/src/components/bottomSheetDebugView/styles.web.ts b/src/components/bottomSheetDebugView/styles.web.ts
new file mode 100644
index 000000000..d77bfdc0b
--- /dev/null
+++ b/src/components/bottomSheetDebugView/styles.web.ts
@@ -0,0 +1,20 @@
+import { StyleSheet } from 'react-native';
+
+export const styles = StyleSheet.create({
+ container: {
+ position: 'absolute',
+ left: 4,
+ top: 80,
+ padding: 2,
+ width: 400,
+ backgroundColor: 'rgba(0, 0,0,0.5)',
+ },
+ text: {
+ fontSize: 14,
+ lineHeight: 16,
+ textAlignVertical: 'center',
+ height: 20,
+ padding: 0,
+ color: 'white',
+ },
+});
diff --git a/src/components/bottomSheetHandleContainer/BottomSheetHandleContainer.tsx b/src/components/bottomSheetHandleContainer/BottomSheetHandleContainer.tsx
index 87b5551e1..23724d557 100644
--- a/src/components/bottomSheetHandleContainer/BottomSheetHandleContainer.tsx
+++ b/src/components/bottomSheetHandleContainer/BottomSheetHandleContainer.tsx
@@ -8,6 +8,7 @@ import {
useBottomSheetInternal,
} from '../../hooks';
import { print } from '../../utilities';
+import { styles } from './styles';
import type { BottomSheetHandleContainerProps } from './types';
function BottomSheetHandleContainerComponent({
@@ -137,6 +138,7 @@ function BottomSheetHandleContainerComponent({
accessibilityRole="adjustable"
accessibilityLabel="Bottom Sheet handle"
accessibilityHint="Drag up or down to extend or minimize the Bottom Sheet"
+ style={styles.container}
onLayout={handleContainerLayout}
>
(
const nativeGesture = useMemo(
() =>
- Gesture.Simultaneous(
- Gesture.Native().shouldCancelWhenOutside(false),
- draggableGesture!
- ),
+ Gesture.Native()
+ // @ts-ignore
+ .simultaneousWithExternalGesture(draggableGesture!)
+ .shouldCancelWhenOutside(false),
[draggableGesture]
);
//#endregion
diff --git a/src/components/bottomSheetView/BottomSheetView.tsx b/src/components/bottomSheetView/BottomSheetView.tsx
index 12a2df6c7..16005ef62 100644
--- a/src/components/bottomSheetView/BottomSheetView.tsx
+++ b/src/components/bottomSheetView/BottomSheetView.tsx
@@ -19,27 +19,38 @@ function BottomSheetViewComponent({
animatedFooterHeight,
} = useBottomSheetInternal();
- // styles
+ //#region styles
+ const flattenContainerStyle = useMemo(
+ () => StyleSheet.flatten(style),
+ [style]
+ );
const containerStylePaddingBottom = useMemo(() => {
- const flattenStyle = StyleSheet.flatten(style);
const paddingBottom =
- flattenStyle && 'paddingBottom' in flattenStyle
- ? flattenStyle.paddingBottom
+ flattenContainerStyle && 'paddingBottom' in flattenContainerStyle
+ ? flattenContainerStyle.paddingBottom
: 0;
return typeof paddingBottom === 'number' ? paddingBottom : 0;
- }, [style]);
- const containerAnimatedStyle = useAnimatedStyle(
+ }, [flattenContainerStyle]);
+ const containerStyle = useMemo(() => {
+ return {
+ ...flattenContainerStyle,
+ paddingBottom: 0,
+ };
+ }, [flattenContainerStyle]);
+ const spaceStyle = useAnimatedStyle(
() => ({
- paddingBottom: enableFooterMarginAdjustment
+ opacity: 0,
+ height: enableFooterMarginAdjustment
? animatedFooterHeight.value + containerStylePaddingBottom
: containerStylePaddingBottom,
}),
- [containerStylePaddingBottom, enableFooterMarginAdjustment]
- );
- const containerStyle = useMemo(
- () => [style, containerAnimatedStyle],
- [style, containerAnimatedStyle]
+ [
+ enableFooterMarginAdjustment,
+ containerStylePaddingBottom,
+ animatedFooterHeight,
+ ]
);
+ //#endregion
// callback
const handleSettingScrollable = useCallback(() => {
@@ -54,6 +65,7 @@ function BottomSheetViewComponent({
return (
{children}
+
);
}
diff --git a/src/hooks/useBottomSheetDynamicSnapPoints.ts b/src/hooks/useBottomSheetDynamicSnapPoints.ts
index a1c25735d..cb3f471cd 100644
--- a/src/hooks/useBottomSheetDynamicSnapPoints.ts
+++ b/src/hooks/useBottomSheetDynamicSnapPoints.ts
@@ -1,4 +1,5 @@
import { useCallback } from 'react';
+import { LayoutChangeEvent } from 'react-native';
import { useDerivedValue, useSharedValue } from 'react-native-reanimated';
import {
INITIAL_HANDLE_HEIGHT,
@@ -45,7 +46,7 @@ export const useBottomSheetDynamicSnapPoints = (
nativeEvent: {
layout: { height },
},
- }) => {
+ }: LayoutChangeEvent) => {
animatedContentHeight.value = height;
},
[animatedContentHeight]
diff --git a/src/hooks/useGestureEventsHandlersDefault.tsx b/src/hooks/useGestureEventsHandlersDefault.tsx
index efd28dc1a..0ded828d9 100644
--- a/src/hooks/useGestureEventsHandlersDefault.tsx
+++ b/src/hooks/useGestureEventsHandlersDefault.tsx
@@ -33,7 +33,6 @@ const INITIAL_CONTEXT: GestureEventContextType = {
const resetContext = (context: any) => {
'worklet';
-
Object.keys(context).map(key => {
context[key] = undefined;
});
diff --git a/src/hooks/useGestureEventsHandlersDefault.web.tsx b/src/hooks/useGestureEventsHandlersDefault.web.tsx
new file mode 100644
index 000000000..fc4204ff6
--- /dev/null
+++ b/src/hooks/useGestureEventsHandlersDefault.web.tsx
@@ -0,0 +1,396 @@
+import { Keyboard, Platform } from 'react-native';
+import {
+ runOnJS,
+ useSharedValue,
+ useWorkletCallback,
+} from 'react-native-reanimated';
+import { useBottomSheetInternal } from './useBottomSheetInternal';
+import {
+ ANIMATION_SOURCE,
+ GESTURE_SOURCE,
+ KEYBOARD_STATE,
+ SCROLLABLE_TYPE,
+ WINDOW_HEIGHT,
+} from '../constants';
+import type { GestureEventHandlerCallbackType } from '../types';
+import { clamp } from '../utilities/clamp';
+import { snapPoint } from '../utilities/snapPoint';
+
+type GestureEventContextType = {
+ initialPosition: number;
+ initialKeyboardState: KEYBOARD_STATE;
+ initialTranslationY: number;
+ isScrollablePositionLocked: boolean;
+};
+
+const INITIAL_CONTEXT: GestureEventContextType = {
+ initialPosition: 0,
+ initialTranslationY: 0,
+ initialKeyboardState: KEYBOARD_STATE.UNDETERMINED,
+ isScrollablePositionLocked: false,
+};
+
+const resetContext = (context: any) => {
+ 'worklet';
+ Object.keys(context).map(key => {
+ context[key] = undefined;
+ });
+};
+
+export const useGestureEventsHandlersDefault = () => {
+ //#region variables
+ const {
+ animatedPosition,
+ animatedSnapPoints,
+ animatedKeyboardState,
+ animatedKeyboardHeight,
+ animatedContainerHeight,
+ animatedScrollableType,
+ animatedHighestSnapPoint,
+ animatedClosedPosition,
+ animatedScrollableContentOffsetY,
+ enableOverDrag,
+ enablePanDownToClose,
+ overDragResistanceFactor,
+ isInTemporaryPosition,
+ isScrollableRefreshable,
+ animateToPosition,
+ stopAnimation,
+ } = useBottomSheetInternal();
+
+ const context = useSharedValue({
+ ...INITIAL_CONTEXT,
+ });
+ //#endregion
+
+ //#region gesture methods
+ const handleOnStart: GestureEventHandlerCallbackType = useWorkletCallback(
+ function handleOnStart(__, { translationY }) {
+ // cancel current animation
+ stopAnimation();
+
+ // store current animated position
+ context.value = {
+ ...context.value,
+ initialPosition: animatedPosition.value,
+ initialKeyboardState: animatedKeyboardState.value,
+ initialTranslationY: translationY,
+ };
+
+ /**
+ * if the scrollable content is scrolled, then
+ * we lock the position.
+ */
+ if (animatedScrollableContentOffsetY.value > 0) {
+ context.value.isScrollablePositionLocked = true;
+ }
+ },
+ [
+ stopAnimation,
+ animatedPosition,
+ animatedKeyboardState,
+ animatedScrollableContentOffsetY,
+ ]
+ );
+ const handleOnChange: GestureEventHandlerCallbackType = useWorkletCallback(
+ function handleOnChange(source, { translationY }) {
+ let highestSnapPoint = animatedHighestSnapPoint.value;
+
+ translationY = translationY - context.value.initialTranslationY;
+ /**
+ * if keyboard is shown, then we set the highest point to the current
+ * position which includes the keyboard height.
+ */
+ if (
+ isInTemporaryPosition.value &&
+ context.value.initialKeyboardState === KEYBOARD_STATE.SHOWN
+ ) {
+ highestSnapPoint = context.value.initialPosition;
+ }
+
+ /**
+ * if current position is out of provided `snapPoints` and smaller then
+ * highest snap pont, then we set the highest point to the current position.
+ */
+ if (
+ isInTemporaryPosition.value &&
+ context.value.initialPosition < highestSnapPoint
+ ) {
+ highestSnapPoint = context.value.initialPosition;
+ }
+
+ const lowestSnapPoint = enablePanDownToClose
+ ? animatedContainerHeight.value
+ : animatedSnapPoints.value[0];
+
+ /**
+ * if scrollable is refreshable and sheet position at the highest
+ * point, then do not interact with current gesture.
+ */
+ if (
+ source === GESTURE_SOURCE.CONTENT &&
+ isScrollableRefreshable.value &&
+ animatedPosition.value === highestSnapPoint
+ ) {
+ return;
+ }
+
+ /**
+ * a negative scrollable content offset to be subtracted from accumulated
+ * current position and gesture translation Y to allow user to drag the sheet,
+ * when scrollable position at the top.
+ * a negative scrollable content offset when the scrollable is not locked.
+ */
+ const negativeScrollableContentOffset =
+ (context.value.initialPosition === highestSnapPoint &&
+ source === GESTURE_SOURCE.CONTENT) ||
+ !context.value.isScrollablePositionLocked
+ ? animatedScrollableContentOffsetY.value * -1
+ : 0;
+
+ /**
+ * an accumulated value of starting position with gesture translation y.
+ */
+ const draggedPosition = context.value.initialPosition + translationY;
+
+ /**
+ * an accumulated value of dragged position and negative scrollable content offset,
+ * this will insure locking sheet position when user is scrolling the scrollable until,
+ * they reach to the top of the scrollable.
+ */
+ const accumulatedDraggedPosition =
+ draggedPosition + negativeScrollableContentOffset;
+
+ /**
+ * a clamped value of the accumulated dragged position, to insure keeping the dragged
+ * position between the highest and lowest snap points.
+ */
+ const clampedPosition = clamp(
+ accumulatedDraggedPosition,
+ highestSnapPoint,
+ lowestSnapPoint
+ );
+
+ /**
+ * if scrollable position is locked and the animated position
+ * reaches the highest point, then we unlock the scrollable position.
+ */
+ if (
+ context.value.isScrollablePositionLocked &&
+ source === GESTURE_SOURCE.CONTENT &&
+ animatedPosition.value === highestSnapPoint
+ ) {
+ context.value.isScrollablePositionLocked = false;
+ }
+
+ /**
+ * over-drag implementation.
+ */
+ if (enableOverDrag) {
+ if (
+ (source === GESTURE_SOURCE.HANDLE ||
+ animatedScrollableType.value === SCROLLABLE_TYPE.VIEW) &&
+ draggedPosition < highestSnapPoint
+ ) {
+ const resistedPosition =
+ highestSnapPoint -
+ Math.sqrt(1 + (highestSnapPoint - draggedPosition)) *
+ overDragResistanceFactor;
+ animatedPosition.value = resistedPosition;
+ return;
+ }
+
+ if (
+ source === GESTURE_SOURCE.HANDLE &&
+ draggedPosition > lowestSnapPoint
+ ) {
+ const resistedPosition =
+ lowestSnapPoint +
+ Math.sqrt(1 + (draggedPosition - lowestSnapPoint)) *
+ overDragResistanceFactor;
+ animatedPosition.value = resistedPosition;
+ return;
+ }
+
+ if (
+ source === GESTURE_SOURCE.CONTENT &&
+ draggedPosition + negativeScrollableContentOffset > lowestSnapPoint
+ ) {
+ const resistedPosition =
+ lowestSnapPoint +
+ Math.sqrt(
+ 1 +
+ (draggedPosition +
+ negativeScrollableContentOffset -
+ lowestSnapPoint)
+ ) *
+ overDragResistanceFactor;
+ animatedPosition.value = resistedPosition;
+ return;
+ }
+ }
+
+ animatedPosition.value = clampedPosition;
+ },
+ [
+ enableOverDrag,
+ enablePanDownToClose,
+ overDragResistanceFactor,
+ isInTemporaryPosition,
+ isScrollableRefreshable,
+ animatedHighestSnapPoint,
+ animatedContainerHeight,
+ animatedSnapPoints,
+ animatedPosition,
+ animatedScrollableType,
+ animatedScrollableContentOffsetY,
+ ]
+ );
+ const handleOnEnd: GestureEventHandlerCallbackType = useWorkletCallback(
+ function handleOnEnd(source, { translationY, absoluteY, velocityY }) {
+ const highestSnapPoint = animatedHighestSnapPoint.value;
+ const isSheetAtHighestSnapPoint =
+ animatedPosition.value === highestSnapPoint;
+
+ /**
+ * if scrollable is refreshable and sheet position at the highest
+ * point, then do not interact with current gesture.
+ */
+ if (
+ source === GESTURE_SOURCE.CONTENT &&
+ isScrollableRefreshable.value &&
+ isSheetAtHighestSnapPoint
+ ) {
+ return;
+ }
+
+ /**
+ * if the sheet is in a temporary position and the gesture ended above
+ * the current position, then we snap back to the temporary position.
+ */
+ if (
+ isInTemporaryPosition.value &&
+ context.value.initialPosition >= animatedPosition.value
+ ) {
+ if (context.value.initialPosition > animatedPosition.value) {
+ animateToPosition(
+ context.value.initialPosition,
+ ANIMATION_SOURCE.GESTURE,
+ velocityY / 2
+ );
+ }
+ return;
+ }
+
+ /**
+ * close keyboard if current position is below the recorded
+ * start position and keyboard still shown.
+ */
+ const isScrollable =
+ animatedScrollableType.value !== SCROLLABLE_TYPE.UNDETERMINED &&
+ animatedScrollableType.value !== SCROLLABLE_TYPE.VIEW;
+
+ /**
+ * if keyboard is shown and the sheet is dragged down,
+ * then we dismiss the keyboard.
+ */
+ if (
+ context.value.initialKeyboardState === KEYBOARD_STATE.SHOWN &&
+ animatedPosition.value > context.value.initialPosition
+ ) {
+ /**
+ * if the platform is ios, current content is scrollable and
+ * the end touch point is below the keyboard position then
+ * we exit the method.
+ *
+ * because the the keyboard dismiss is interactive in iOS.
+ */
+ if (
+ !(
+ Platform.OS === 'ios' &&
+ isScrollable &&
+ absoluteY > WINDOW_HEIGHT - animatedKeyboardHeight.value
+ )
+ ) {
+ runOnJS(Keyboard.dismiss)();
+ }
+ }
+
+ /**
+ * reset isInTemporaryPosition value
+ */
+ if (isInTemporaryPosition.value) {
+ isInTemporaryPosition.value = false;
+ }
+
+ /**
+ * clone snap points array, and insert the container height
+ * if pan down to close is enabled.
+ */
+ const snapPoints = animatedSnapPoints.value.slice();
+ if (enablePanDownToClose) {
+ snapPoints.unshift(animatedClosedPosition.value);
+ }
+
+ /**
+ * calculate the destination point, using redash.
+ */
+ const destinationPoint = snapPoint(
+ translationY + context.value.initialPosition,
+ velocityY,
+ snapPoints
+ );
+
+ /**
+ * if destination point is the same as the current position,
+ * then no need to perform animation.
+ */
+ if (destinationPoint === animatedPosition.value) {
+ return;
+ }
+
+ const wasGestureHandledByScrollView =
+ source === GESTURE_SOURCE.CONTENT &&
+ animatedScrollableContentOffsetY.value > 0;
+ /**
+ * prevents snapping from top to middle / bottom with repeated interrupted scrolls
+ */
+ if (wasGestureHandledByScrollView && isSheetAtHighestSnapPoint) {
+ return;
+ }
+
+ animateToPosition(
+ destinationPoint,
+ ANIMATION_SOURCE.GESTURE,
+ velocityY / 2
+ );
+ },
+ [
+ enablePanDownToClose,
+ isInTemporaryPosition,
+ isScrollableRefreshable,
+ animatedClosedPosition,
+ animatedHighestSnapPoint,
+ animatedKeyboardHeight,
+ animatedPosition,
+ animatedScrollableType,
+ animatedSnapPoints,
+ animatedScrollableContentOffsetY,
+ animateToPosition,
+ ]
+ );
+ const handleOnFinalize: GestureEventHandlerCallbackType = useWorkletCallback(
+ function handleOnFinalize() {
+ resetContext(context);
+ },
+ [context]
+ );
+ //#endregion
+
+ return {
+ handleOnStart,
+ handleOnChange,
+ handleOnEnd,
+ handleOnFinalize,
+ };
+};
diff --git a/src/hooks/useScrollHandler.web.ts b/src/hooks/useScrollHandler.web.ts
new file mode 100644
index 000000000..6b11a17e7
--- /dev/null
+++ b/src/hooks/useScrollHandler.web.ts
@@ -0,0 +1,171 @@
+import { useEffect, useRef, TouchEvent } from 'react';
+import { useSharedValue } from 'react-native-reanimated';
+import { useBottomSheetInternal } from './useBottomSheetInternal';
+import { ANIMATION_STATE, SCROLLABLE_STATE } from '../constants';
+import { getRefNativeTag } from '../utilities/getRefNativeTag';
+import type { Scrollable } from '../types';
+
+export type ScrollEventContextType = {
+ initialContentOffsetY: number;
+ shouldLockInitialPosition: boolean;
+};
+
+export const useScrollHandler = () => {
+ //#region refs
+ const scrollableRef = useRef();
+ //#endregion
+
+ //#region variables
+ const scrollableContentOffsetY = useSharedValue(0);
+ //#endregion
+
+ //#region hooks
+ const {
+ animatedScrollableState,
+ animatedAnimationState,
+ animatedScrollableContentOffsetY,
+ } = useBottomSheetInternal();
+ //#endregion
+
+ //#region effects
+ useEffect(() => {
+ const element = getRefNativeTag(scrollableRef) as any;
+
+ var scrollOffset = 0;
+ var supportsPassive = false;
+ var maybePrevent = false;
+ var lastTouchY = 0;
+
+ var initialContentOffsetY = 0;
+ var shouldLockInitialPosition = false;
+
+ function handleOnTouchStart(event: TouchEvent) {
+ if (event.touches.length !== 1) return;
+
+ initialContentOffsetY = element.scrollTop;
+ lastTouchY = event.touches[0].clientY;
+ maybePrevent = scrollOffset <= 0;
+ }
+
+ function handleOnTouchMove(event: TouchEvent) {
+ if (animatedScrollableState.value === SCROLLABLE_STATE.LOCKED) {
+ return event.preventDefault();
+ }
+
+ if (maybePrevent) {
+ maybePrevent = false;
+
+ const touchY = event.touches[0].clientY;
+ const touchYDelta = touchY - lastTouchY;
+
+ if (touchYDelta > 0) {
+ return event.preventDefault();
+ }
+ }
+
+ return true;
+ }
+
+ function handleOnTouchEnd() {
+ if (animatedScrollableState.value === SCROLLABLE_STATE.LOCKED) {
+ const lockPosition = shouldLockInitialPosition
+ ? initialContentOffsetY ?? 0
+ : 0;
+ element.scroll({
+ top: 0,
+ left: 0,
+ behavior: 'instant',
+ });
+ scrollableContentOffsetY.value = lockPosition;
+ return;
+ }
+ }
+
+ function handleOnScroll(event: TouchEvent) {
+ scrollOffset = element.scrollTop;
+
+ if (animatedAnimationState.value !== ANIMATION_STATE.RUNNING) {
+ scrollableContentOffsetY.value = Math.max(0, scrollOffset);
+ animatedScrollableContentOffsetY.value = Math.max(0, scrollOffset);
+ }
+
+ if (scrollOffset <= 0) {
+ event.preventDefault();
+ event.stopPropagation();
+ return false;
+ }
+ return true;
+ }
+
+ try {
+ // @ts-ignore
+ window.addEventListener('test', null, {
+ // @ts-ignore
+ get passive() {
+ supportsPassive = true;
+ },
+ });
+ } catch (e) {}
+
+ element.addEventListener(
+ 'touchstart',
+ handleOnTouchStart,
+ supportsPassive
+ ? {
+ passive: true,
+ }
+ : false
+ );
+
+ element.addEventListener(
+ 'touchmove',
+ handleOnTouchMove,
+ supportsPassive
+ ? {
+ passive: false,
+ }
+ : false
+ );
+
+ element.addEventListener(
+ 'touchend',
+ handleOnTouchEnd,
+ supportsPassive
+ ? {
+ passive: false,
+ }
+ : false
+ );
+
+ element.addEventListener(
+ 'scroll',
+ handleOnScroll,
+ supportsPassive
+ ? {
+ passive: false,
+ }
+ : false
+ );
+
+ return () => {
+ // @ts-ignore
+ window.removeEventListener('test', null);
+ element.removeEventListener('touchstart', handleOnTouchStart);
+ element.removeEventListener('touchmove', handleOnTouchMove);
+ element.removeEventListener('touchend', handleOnTouchEnd);
+ element.removeEventListener('scroll', handleOnScroll);
+ };
+ }, [
+ animatedAnimationState,
+ animatedScrollableContentOffsetY,
+ animatedScrollableState,
+ scrollableContentOffsetY,
+ ]);
+ //#endregion
+
+ return {
+ scrollHandler: undefined,
+ scrollableRef,
+ scrollableContentOffsetY,
+ };
+};
diff --git a/src/types.d.ts b/src/types.d.ts
index a0a310405..7357f0506 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -137,7 +137,7 @@ export type GestureEventContextType = {
export type GestureEventHandlerCallbackType = (
source: GESTURE_SOURCE,
- event: GestureStateChangeEvent
+ payload: GestureEventPayloadType
) => void;
export type GestureEventsHandlersHookType = () => {
diff --git a/src/utilities/getRefNativeTag.web.ts b/src/utilities/getRefNativeTag.web.ts
new file mode 100644
index 000000000..08c3ca2ff
--- /dev/null
+++ b/src/utilities/getRefNativeTag.web.ts
@@ -0,0 +1,6 @@
+import type { RefObject } from 'react';
+import { findNodeHandle } from 'react-native';
+
+export function getRefNativeTag(ref: RefObject) {
+ return findNodeHandle(ref?.current) || null;
+}
diff --git a/yarn.lock b/yarn.lock
index 56a06cecb..eae10e6f2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1901,13 +1901,13 @@
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^5.30.5":
- version "5.38.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz#9f05d42fa8fb9f62304cc2f5c2805e03c01c2620"
- integrity sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ==
+ version "5.39.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.39.0.tgz#778b2d9e7f293502c7feeea6c74dca8eb3e67511"
+ integrity sha512-xVfKOkBm5iWMNGKQ2fwX5GVgBuHmZBO1tCRwXmY5oAIsPscfwm2UADDuNB8ZVYCtpQvJK4xpjrK7jEhcJ0zY9A==
dependencies:
- "@typescript-eslint/scope-manager" "5.38.1"
- "@typescript-eslint/type-utils" "5.38.1"
- "@typescript-eslint/utils" "5.38.1"
+ "@typescript-eslint/scope-manager" "5.39.0"
+ "@typescript-eslint/type-utils" "5.39.0"
+ "@typescript-eslint/utils" "5.39.0"
debug "^4.3.4"
ignore "^5.2.0"
regexpp "^3.2.0"
@@ -1915,69 +1915,69 @@
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.30.5":
- version "5.38.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.38.1.tgz#c577f429f2c32071b92dff4af4f5fbbbd2414bd0"
- integrity sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw==
+ version "5.39.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.39.0.tgz#93fa0bc980a3a501e081824f6097f7ca30aaa22b"
+ integrity sha512-PhxLjrZnHShe431sBAGHaNe6BDdxAASDySgsBCGxcBecVCi8NQWxQZMcizNA4g0pN51bBAn/FUfkWG3SDVcGlA==
dependencies:
- "@typescript-eslint/scope-manager" "5.38.1"
- "@typescript-eslint/types" "5.38.1"
- "@typescript-eslint/typescript-estree" "5.38.1"
+ "@typescript-eslint/scope-manager" "5.39.0"
+ "@typescript-eslint/types" "5.39.0"
+ "@typescript-eslint/typescript-estree" "5.39.0"
debug "^4.3.4"
-"@typescript-eslint/scope-manager@5.38.1":
- version "5.38.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz#f87b289ef8819b47189351814ad183e8801d5764"
- integrity sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ==
+"@typescript-eslint/scope-manager@5.39.0":
+ version "5.39.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.39.0.tgz#873e1465afa3d6c78d8ed2da68aed266a08008d0"
+ integrity sha512-/I13vAqmG3dyqMVSZPjsbuNQlYS082Y7OMkwhCfLXYsmlI0ca4nkL7wJ/4gjX70LD4P8Hnw1JywUVVAwepURBw==
dependencies:
- "@typescript-eslint/types" "5.38.1"
- "@typescript-eslint/visitor-keys" "5.38.1"
+ "@typescript-eslint/types" "5.39.0"
+ "@typescript-eslint/visitor-keys" "5.39.0"
-"@typescript-eslint/type-utils@5.38.1":
- version "5.38.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz#7f038fcfcc4ade4ea76c7c69b2aa25e6b261f4c1"
- integrity sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw==
+"@typescript-eslint/type-utils@5.39.0":
+ version "5.39.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.39.0.tgz#0a8c00f95dce4335832ad2dc6bc431c14e32a0a6"
+ integrity sha512-KJHJkOothljQWzR3t/GunL0TPKY+fGJtnpl+pX+sJ0YiKTz3q2Zr87SGTmFqsCMFrLt5E0+o+S6eQY0FAXj9uA==
dependencies:
- "@typescript-eslint/typescript-estree" "5.38.1"
- "@typescript-eslint/utils" "5.38.1"
+ "@typescript-eslint/typescript-estree" "5.39.0"
+ "@typescript-eslint/utils" "5.39.0"
debug "^4.3.4"
tsutils "^3.21.0"
-"@typescript-eslint/types@5.38.1":
- version "5.38.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.38.1.tgz#74f9d6dcb8dc7c58c51e9fbc6653ded39e2e225c"
- integrity sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg==
+"@typescript-eslint/types@5.39.0":
+ version "5.39.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.39.0.tgz#f4e9f207ebb4579fd854b25c0bf64433bb5ed78d"
+ integrity sha512-gQMZrnfEBFXK38hYqt8Lkwt8f4U6yq+2H5VDSgP/qiTzC8Nw8JO3OuSUOQ2qW37S/dlwdkHDntkZM6SQhKyPhw==
-"@typescript-eslint/typescript-estree@5.38.1":
- version "5.38.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz#657d858d5d6087f96b638ee383ee1cff52605a1e"
- integrity sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g==
+"@typescript-eslint/typescript-estree@5.39.0":
+ version "5.39.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.39.0.tgz#c0316aa04a1a1f4f7f9498e3c13ef1d3dc4cf88b"
+ integrity sha512-qLFQP0f398sdnogJoLtd43pUgB18Q50QSA+BTE5h3sUxySzbWDpTSdgt4UyxNSozY/oDK2ta6HVAzvGgq8JYnA==
dependencies:
- "@typescript-eslint/types" "5.38.1"
- "@typescript-eslint/visitor-keys" "5.38.1"
+ "@typescript-eslint/types" "5.39.0"
+ "@typescript-eslint/visitor-keys" "5.39.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
-"@typescript-eslint/utils@5.38.1", "@typescript-eslint/utils@^5.10.0":
- version "5.38.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.38.1.tgz#e3ac37d7b33d1362bb5adf4acdbe00372fb813ef"
- integrity sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA==
+"@typescript-eslint/utils@5.39.0", "@typescript-eslint/utils@^5.10.0":
+ version "5.39.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.39.0.tgz#b7063cca1dcf08d1d21b0d91db491161ad0be110"
+ integrity sha512-+DnY5jkpOpgj+EBtYPyHRjXampJfC0yUZZzfzLuUWVZvCuKqSdJVC8UhdWipIw7VKNTfwfAPiOWzYkAwuIhiAg==
dependencies:
"@types/json-schema" "^7.0.9"
- "@typescript-eslint/scope-manager" "5.38.1"
- "@typescript-eslint/types" "5.38.1"
- "@typescript-eslint/typescript-estree" "5.38.1"
+ "@typescript-eslint/scope-manager" "5.39.0"
+ "@typescript-eslint/types" "5.39.0"
+ "@typescript-eslint/typescript-estree" "5.39.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
-"@typescript-eslint/visitor-keys@5.38.1":
- version "5.38.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz#508071bfc6b96d194c0afe6a65ad47029059edbc"
- integrity sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA==
+"@typescript-eslint/visitor-keys@5.39.0":
+ version "5.39.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.39.0.tgz#8f41f7d241b47257b081ddba5d3ce80deaae61e2"
+ integrity sha512-yyE3RPwOG+XJBLrhvsxAidUgybJVQ/hG8BhiJo0k8JSAYfk/CshVcxf0HwP4Jt7WZZ6vLmxdo1p6EyN3tzFTkg==
dependencies:
- "@typescript-eslint/types" "5.38.1"
+ "@typescript-eslint/types" "5.39.0"
eslint-visitor-keys "^3.3.0"
JSONStream@^1.0.4:
@@ -3228,9 +3228,9 @@ copyfiles@^2.4.1:
yargs "^16.1.0"
core-js-compat@^3.25.1:
- version "3.25.4"
- resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.4.tgz#730a255d4a47a937513abf1672bf278dc24dcebf"
- integrity sha512-gCEcIEEqCR6230WroNunK/653CWKhqyCKJ9b+uESqOt/WFJA8B4lTnnQFdpYY5vmBcwJAA90Bo5vXs+CVsf6iA==
+ version "3.25.5"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.5.tgz#0016e8158c904f7b059486639e6e82116eafa7d9"
+ integrity sha512-ovcyhs2DEBUIE0MGEKHP4olCUW/XYte3Vroyxuh38rD1wAO4dHohsovUC4eAOuzFxE6b+RXvBU3UZ9o0YhUTkA==
dependencies:
browserslist "^4.21.4"
@@ -4303,9 +4303,9 @@ for-in@^1.0.2:
integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==
form-data-encoder@^2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.2.tgz#5996b7c236e8c418d08316055a2235226c5e4061"
- integrity sha512-FCaIOVTRA9E0siY6FeXid7D5yrCqpsErplUkE2a1BEiKj1BE9z6FbKB4ntDTwC4NVLie9p+4E9nX4mWwEOT05A==
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.3.tgz#682cd821a8423605093992ff895e6b2ed5a9d429"
+ integrity sha512-KqU0nnPMgIJcCOFTNJFEA8epcseEaoox4XZffTgy8jlI6pL/5EFyR54NRG7CnCJN0biY7q52DO3MH6/sJ/TKlQ==
form-data@4.0.0:
version "4.0.0"
@@ -7421,9 +7421,9 @@ react-native-builder-bob@^0.18.3:
jetifier "^2.0.0"
react-native-gesture-handler@^2.5.0:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.6.2.tgz#f3b68d374f5dda603ff29f7df2edb39472eb97ce"
- integrity sha512-Ff/WKlR8KiM1wq7UJZvIyCB+OsweewaeZk+4RDIYNGM9tvNIAXEm/MtYnLHiBXiSJjZItF/8B83gE6pVq40vIw==
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.7.0.tgz#53ad828add926c8e025f68ea581758c0f8893054"
+ integrity sha512-0jr3FNm2R3gv/v6XTtENgjv0fewD6LEct8EWmXw/oHw36M3YiIIpxnW57thL+0YiKwyLBXN0QHL4JZbs/heW2Q==
dependencies:
"@egjs/hammerjs" "^2.0.17"
hoist-non-react-statics "^3.3.0"