@mustmove/bottom-sheet 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/mock.js +231 -0
- package/package.json +107 -0
- package/src/components/bottomSheet/BottomSheet.tsx +1885 -0
- package/src/components/bottomSheet/BottomSheetBody.tsx +44 -0
- package/src/components/bottomSheet/BottomSheetContent.tsx +261 -0
- package/src/components/bottomSheet/constants.ts +58 -0
- package/src/components/bottomSheet/index.ts +2 -0
- package/src/components/bottomSheet/styles.ts +11 -0
- package/src/components/bottomSheet/types.d.ts +358 -0
- package/src/components/bottomSheetBackdrop/BottomSheetBackdrop.tsx +165 -0
- package/src/components/bottomSheetBackdrop/constants.ts +22 -0
- package/src/components/bottomSheetBackdrop/index.ts +2 -0
- package/src/components/bottomSheetBackdrop/styles.ts +8 -0
- package/src/components/bottomSheetBackdrop/types.d.ts +58 -0
- package/src/components/bottomSheetBackground/BottomSheetBackground.tsx +20 -0
- package/src/components/bottomSheetBackground/BottomSheetBackgroundContainer.tsx +35 -0
- package/src/components/bottomSheetBackground/index.ts +2 -0
- package/src/components/bottomSheetBackground/styles.ts +9 -0
- package/src/components/bottomSheetBackground/types.d.ts +12 -0
- package/src/components/bottomSheetDebugView/BottomSheetDebugView.tsx +26 -0
- package/src/components/bottomSheetDebugView/ReText.tsx +72 -0
- package/src/components/bottomSheetDebugView/ReText.webx.tsx +55 -0
- package/src/components/bottomSheetDebugView/index.ts +1 -0
- package/src/components/bottomSheetDebugView/styles.ts +19 -0
- package/src/components/bottomSheetDebugView/styles.web.ts +20 -0
- package/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx +123 -0
- package/src/components/bottomSheetDraggableView/index.ts +1 -0
- package/src/components/bottomSheetDraggableView/types.d.ts +9 -0
- package/src/components/bottomSheetFooter/BottomSheetFooter.tsx +119 -0
- package/src/components/bottomSheetFooter/BottomSheetFooterContainer.tsx +43 -0
- package/src/components/bottomSheetFooter/index.ts +3 -0
- package/src/components/bottomSheetFooter/styles.ts +12 -0
- package/src/components/bottomSheetFooter/types.d.ts +41 -0
- package/src/components/bottomSheetGestureHandlersProvider/BottomSheetGestureHandlersProvider.tsx +69 -0
- package/src/components/bottomSheetGestureHandlersProvider/index.ts +1 -0
- package/src/components/bottomSheetGestureHandlersProvider/types.d.ts +8 -0
- package/src/components/bottomSheetHandle/BottomSheetHandle.tsx +51 -0
- package/src/components/bottomSheetHandle/BottomSheetHandleContainer.tsx +187 -0
- package/src/components/bottomSheetHandle/constants.ts +12 -0
- package/src/components/bottomSheetHandle/index.ts +6 -0
- package/src/components/bottomSheetHandle/styles.ts +23 -0
- package/src/components/bottomSheetHandle/types.d.ts +52 -0
- package/src/components/bottomSheetHostingContainer/BottomSheetHostingContainer.tsx +130 -0
- package/src/components/bottomSheetHostingContainer/index.ts +2 -0
- package/src/components/bottomSheetHostingContainer/styles.ts +5 -0
- package/src/components/bottomSheetHostingContainer/styles.web.ts +11 -0
- package/src/components/bottomSheetHostingContainer/types.d.ts +17 -0
- package/src/components/bottomSheetModal/BottomSheetModal.tsx +482 -0
- package/src/components/bottomSheetModal/constants.ts +4 -0
- package/src/components/bottomSheetModal/index.ts +6 -0
- package/src/components/bottomSheetModal/types.d.ts +67 -0
- package/src/components/bottomSheetModalProvider/BottomSheetModalProvider.tsx +211 -0
- package/src/components/bottomSheetModalProvider/index.ts +1 -0
- package/src/components/bottomSheetModalProvider/types.d.ts +12 -0
- package/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.android.tsx +84 -0
- package/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.tsx +1 -0
- package/src/components/bottomSheetRefreshControl/index.ts +20 -0
- package/src/components/bottomSheetScrollable/BottomSheetDraggableScrollable.tsx +23 -0
- package/src/components/bottomSheetScrollable/BottomSheetFlashList.tsx +88 -0
- package/src/components/bottomSheetScrollable/BottomSheetFlashList.web.tsx +1 -0
- package/src/components/bottomSheetScrollable/BottomSheetFlatList.tsx +26 -0
- package/src/components/bottomSheetScrollable/BottomSheetScrollView.tsx +27 -0
- package/src/components/bottomSheetScrollable/BottomSheetSectionList.tsx +29 -0
- package/src/components/bottomSheetScrollable/BottomSheetVirtualizedList.tsx +27 -0
- package/src/components/bottomSheetScrollable/ScrollableContainer.android.tsx +55 -0
- package/src/components/bottomSheetScrollable/ScrollableContainer.tsx +22 -0
- package/src/components/bottomSheetScrollable/ScrollableContainer.web.tsx +102 -0
- package/src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsx +153 -0
- package/src/components/bottomSheetScrollable/index.ts +15 -0
- package/src/components/bottomSheetScrollable/styles.ts +8 -0
- package/src/components/bottomSheetScrollable/types.d.ts +280 -0
- package/src/components/bottomSheetScrollable/useBottomSheetContentSizeSetter.ts +32 -0
- package/src/components/bottomSheetTextInput/BottomSheetTextInput.tsx +127 -0
- package/src/components/bottomSheetTextInput/index.ts +2 -0
- package/src/components/bottomSheetTextInput/types.ts +3 -0
- package/src/components/bottomSheetView/BottomSheetView.tsx +93 -0
- package/src/components/bottomSheetView/index.ts +1 -0
- package/src/components/bottomSheetView/styles.ts +10 -0
- package/src/components/bottomSheetView/types.d.ts +24 -0
- package/src/components/touchables/Touchables.ios.tsx +5 -0
- package/src/components/touchables/Touchables.tsx +5 -0
- package/src/components/touchables/index.ts +20 -0
- package/src/constants.ts +159 -0
- package/src/contexts/external.ts +8 -0
- package/src/contexts/gesture.ts +13 -0
- package/src/contexts/index.ts +15 -0
- package/src/contexts/internal.ts +65 -0
- package/src/contexts/modal/external.ts +11 -0
- package/src/contexts/modal/internal.ts +25 -0
- package/src/hooks/index.ts +29 -0
- package/src/hooks/useAnimatedDetents.ts +119 -0
- package/src/hooks/useAnimatedKeyboard.ts +174 -0
- package/src/hooks/useAnimatedLayout.ts +109 -0
- package/src/hooks/useBottomSheet.ts +12 -0
- package/src/hooks/useBottomSheetContentContainerStyle.ts +88 -0
- package/src/hooks/useBottomSheetGestureHandlers.ts +12 -0
- package/src/hooks/useBottomSheetInternal.ts +25 -0
- package/src/hooks/useBottomSheetModal.ts +12 -0
- package/src/hooks/useBottomSheetModalInternal.ts +25 -0
- package/src/hooks/useBottomSheetScrollableCreator.tsx +60 -0
- package/src/hooks/useBottomSheetSpringConfigs.ts +11 -0
- package/src/hooks/useBottomSheetTimingConfigs.ts +36 -0
- package/src/hooks/useBoundingClientRect.ts +77 -0
- package/src/hooks/useGestureEventsHandlersDefault.tsx +436 -0
- package/src/hooks/useGestureEventsHandlersDefault.web.tsx +418 -0
- package/src/hooks/useGestureHandler.ts +90 -0
- package/src/hooks/usePropsValidator.ts +108 -0
- package/src/hooks/useReactiveSharedValue.ts +45 -0
- package/src/hooks/useScrollEventsHandlersDefault.ts +167 -0
- package/src/hooks/useScrollHandler.ts +72 -0
- package/src/hooks/useScrollHandler.web.ts +181 -0
- package/src/hooks/useScrollable.ts +131 -0
- package/src/hooks/useScrollableSetter.ts +56 -0
- package/src/hooks/useStableCallback.ts +26 -0
- package/src/index.ts +79 -0
- package/src/types.d.ts +336 -0
- package/src/utilities/animate.ts +56 -0
- package/src/utilities/clamp.ts +8 -0
- package/src/utilities/easingExp.ts +10 -0
- package/src/utilities/findNodeHandle.ts +1 -0
- package/src/utilities/findNodeHandle.web.ts +33 -0
- package/src/utilities/getKeyboardAnimationConfigs.ts +44 -0
- package/src/utilities/getRefNativeTag.web.ts +6 -0
- package/src/utilities/id.ts +6 -0
- package/src/utilities/index.ts +7 -0
- package/src/utilities/isFabricInstalled.ts +9 -0
- package/src/utilities/logger.ts +55 -0
- package/src/utilities/noop.ts +7 -0
- package/src/utilities/normalizeSnapPoint.ts +17 -0
- package/src/utilities/snapPoint.ts +11 -0
- package/src/utilities/validateSnapPoint.ts +20 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type RefObject, useLayoutEffect } from 'react';
|
|
2
|
+
import type { View } from 'react-native';
|
|
3
|
+
import { isFabricInstalled } from '../utilities/isFabricInstalled';
|
|
4
|
+
|
|
5
|
+
export type BoundingClientRect = {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
left: number;
|
|
11
|
+
right: number;
|
|
12
|
+
top: number;
|
|
13
|
+
bottom: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A custom hook that retrieves the bounding client rectangle of a given `ref` element
|
|
18
|
+
* and invokes a handler function with the layout information.
|
|
19
|
+
*
|
|
20
|
+
* This hook is designed to work with React Native's Fabric architecture and provides
|
|
21
|
+
* support for both `unstable_getBoundingClientRect` and `getBoundingClientRect` methods.
|
|
22
|
+
*
|
|
23
|
+
* @param ref - A `RefObject` pointing to a `View` or `null`. The bounding client rectangle
|
|
24
|
+
* will be retrieved from this reference.
|
|
25
|
+
* @param handler - A callback function that will be invoked with the layout information
|
|
26
|
+
* of the referenced element.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* - The hook uses `useLayoutEffect` to ensure the layout information is retrieved
|
|
30
|
+
* after the DOM updates.
|
|
31
|
+
* - The `isFabricInstalled` function is used to determine if the Fabric architecture
|
|
32
|
+
* is available.
|
|
33
|
+
* - The `unstable_getBoundingClientRect` method is used if available, falling back
|
|
34
|
+
* to `getBoundingClientRect` otherwise.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* const ref = useRef<View | null>(null);
|
|
39
|
+
* useBoundingClientRect(ref, (layout) => {
|
|
40
|
+
* console.log('Bounding client rect:', layout);
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function useBoundingClientRect(
|
|
45
|
+
ref: RefObject<View | null>,
|
|
46
|
+
handler: (layout: BoundingClientRect) => void
|
|
47
|
+
) {
|
|
48
|
+
if (!isFabricInstalled()) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: `isFabricInstalled` is a constant that will not change during the runtime
|
|
53
|
+
useLayoutEffect(() => {
|
|
54
|
+
if (!ref || !ref.current) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
// @ts-expect-error 👉 https://github.com/facebook/react/commit/53b1f69ba
|
|
60
|
+
ref.current.unstable_getBoundingClientRect !== null &&
|
|
61
|
+
// @ts-expect-error 👉 https://github.com/facebook/react/commit/53b1f69ba
|
|
62
|
+
typeof ref.current.unstable_getBoundingClientRect === 'function'
|
|
63
|
+
) {
|
|
64
|
+
// @ts-expect-error https://github.com/facebook/react/commit/53b1f69ba
|
|
65
|
+
const layout = ref.current.unstable_getBoundingClientRect();
|
|
66
|
+
handler(layout);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// @ts-expect-error once it `unstable_getBoundingClientRect` gets stable 🤞.
|
|
71
|
+
if (ref.current.getBoundingClientRect !== null) {
|
|
72
|
+
// @ts-expect-error once it `unstable_getBoundingClientRect` gets stable.
|
|
73
|
+
const layout = ref.current.getBoundingClientRect();
|
|
74
|
+
handler(layout);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { Keyboard, Platform } from 'react-native';
|
|
3
|
+
import { runOnJS, useSharedValue } from 'react-native-reanimated';
|
|
4
|
+
import {
|
|
5
|
+
ANIMATION_SOURCE,
|
|
6
|
+
GESTURE_SOURCE,
|
|
7
|
+
KEYBOARD_STATUS,
|
|
8
|
+
SCROLLABLE_TYPE,
|
|
9
|
+
WINDOW_HEIGHT,
|
|
10
|
+
} from '../constants';
|
|
11
|
+
import type {
|
|
12
|
+
GestureEventHandlerCallbackType,
|
|
13
|
+
GestureEventsHandlersHookType,
|
|
14
|
+
} from '../types';
|
|
15
|
+
import { clamp } from '../utilities/clamp';
|
|
16
|
+
import { snapPoint } from '../utilities/snapPoint';
|
|
17
|
+
import { useBottomSheetInternal } from './useBottomSheetInternal';
|
|
18
|
+
|
|
19
|
+
type GestureEventContextType = {
|
|
20
|
+
initialPosition: number;
|
|
21
|
+
initialKeyboardStatus: KEYBOARD_STATUS;
|
|
22
|
+
isScrollablePositionLocked: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const INITIAL_CONTEXT: GestureEventContextType = {
|
|
26
|
+
initialPosition: 0,
|
|
27
|
+
initialKeyboardStatus: KEYBOARD_STATUS.UNDETERMINED,
|
|
28
|
+
isScrollablePositionLocked: false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const dismissKeyboard = Keyboard.dismiss;
|
|
32
|
+
|
|
33
|
+
// biome-ignore lint: to be addressed!
|
|
34
|
+
const resetContext = (context: any) => {
|
|
35
|
+
'worklet';
|
|
36
|
+
Object.keys(context).map(key => {
|
|
37
|
+
context[key] = undefined;
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const useGestureEventsHandlersDefault: GestureEventsHandlersHookType =
|
|
42
|
+
() => {
|
|
43
|
+
//#region variables
|
|
44
|
+
const {
|
|
45
|
+
animatedPosition,
|
|
46
|
+
animatedDetentsState,
|
|
47
|
+
animatedKeyboardState,
|
|
48
|
+
animatedScrollableState,
|
|
49
|
+
animatedLayoutState,
|
|
50
|
+
enableOverDrag,
|
|
51
|
+
enablePanDownToClose,
|
|
52
|
+
overDragResistanceFactor,
|
|
53
|
+
isInTemporaryPosition,
|
|
54
|
+
enableBlurKeyboardOnGesture,
|
|
55
|
+
animateToPosition,
|
|
56
|
+
stopAnimation,
|
|
57
|
+
} = useBottomSheetInternal();
|
|
58
|
+
|
|
59
|
+
const context = useSharedValue<GestureEventContextType>({
|
|
60
|
+
...INITIAL_CONTEXT,
|
|
61
|
+
});
|
|
62
|
+
//#endregion
|
|
63
|
+
|
|
64
|
+
//#region gesture methods
|
|
65
|
+
const handleOnStart: GestureEventHandlerCallbackType = useCallback(
|
|
66
|
+
function handleOnStart(__, _) {
|
|
67
|
+
'worklet';
|
|
68
|
+
// cancel current animation
|
|
69
|
+
stopAnimation();
|
|
70
|
+
|
|
71
|
+
let initialKeyboardStatus = animatedKeyboardState.get().status;
|
|
72
|
+
// blur the keyboard when user start dragging the bottom sheet
|
|
73
|
+
if (
|
|
74
|
+
enableBlurKeyboardOnGesture &&
|
|
75
|
+
initialKeyboardStatus === KEYBOARD_STATUS.SHOWN
|
|
76
|
+
) {
|
|
77
|
+
initialKeyboardStatus = KEYBOARD_STATUS.HIDDEN;
|
|
78
|
+
runOnJS(dismissKeyboard)();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// store current animated position
|
|
82
|
+
context.value = {
|
|
83
|
+
...context.value,
|
|
84
|
+
initialPosition: animatedPosition.value,
|
|
85
|
+
initialKeyboardStatus,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* if the scrollable content is scrolled, then
|
|
90
|
+
* we lock the position.
|
|
91
|
+
*/
|
|
92
|
+
if (animatedScrollableState.get().contentOffsetY > 0) {
|
|
93
|
+
context.value = {
|
|
94
|
+
...context.value,
|
|
95
|
+
isScrollablePositionLocked: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
[
|
|
100
|
+
stopAnimation,
|
|
101
|
+
context,
|
|
102
|
+
enableBlurKeyboardOnGesture,
|
|
103
|
+
animatedPosition,
|
|
104
|
+
animatedKeyboardState,
|
|
105
|
+
animatedScrollableState,
|
|
106
|
+
]
|
|
107
|
+
);
|
|
108
|
+
const handleOnChange: GestureEventHandlerCallbackType = useCallback(
|
|
109
|
+
function handleOnChange(source, { translationY }) {
|
|
110
|
+
'worklet';
|
|
111
|
+
const { highestDetentPosition, detents } = animatedDetentsState.get();
|
|
112
|
+
if (
|
|
113
|
+
highestDetentPosition === undefined ||
|
|
114
|
+
detents === undefined ||
|
|
115
|
+
detents.length === 0
|
|
116
|
+
) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let highestSnapPoint = highestDetentPosition;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* if keyboard is shown, then we set the highest point to the current
|
|
124
|
+
* position which includes the keyboard height.
|
|
125
|
+
*/
|
|
126
|
+
if (
|
|
127
|
+
isInTemporaryPosition.value &&
|
|
128
|
+
context.value.initialKeyboardStatus === KEYBOARD_STATUS.SHOWN
|
|
129
|
+
) {
|
|
130
|
+
highestSnapPoint = context.value.initialPosition;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* if current position is out of provided `snapPoints` and smaller then
|
|
135
|
+
* highest snap pont, then we set the highest point to the current position.
|
|
136
|
+
*/
|
|
137
|
+
if (
|
|
138
|
+
isInTemporaryPosition.value &&
|
|
139
|
+
context.value.initialPosition < highestSnapPoint
|
|
140
|
+
) {
|
|
141
|
+
highestSnapPoint = context.value.initialPosition;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { containerHeight } = animatedLayoutState.get();
|
|
145
|
+
const lowestSnapPoint = enablePanDownToClose
|
|
146
|
+
? containerHeight
|
|
147
|
+
: detents[0];
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* if scrollable is refreshable and sheet position at the highest
|
|
151
|
+
* point, then do not interact with current gesture.
|
|
152
|
+
*/
|
|
153
|
+
if (
|
|
154
|
+
source === GESTURE_SOURCE.CONTENT &&
|
|
155
|
+
animatedScrollableState.get().refreshable &&
|
|
156
|
+
animatedPosition.value === highestSnapPoint
|
|
157
|
+
) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* a negative scrollable content offset to be subtracted from accumulated
|
|
163
|
+
* current position and gesture translation Y to allow user to drag the sheet,
|
|
164
|
+
* when scrollable position at the top.
|
|
165
|
+
* a negative scrollable content offset when the scrollable is not locked.
|
|
166
|
+
*/
|
|
167
|
+
const negativeScrollableContentOffset =
|
|
168
|
+
(context.value.initialPosition === highestSnapPoint &&
|
|
169
|
+
source === GESTURE_SOURCE.CONTENT) ||
|
|
170
|
+
!context.value.isScrollablePositionLocked
|
|
171
|
+
? animatedScrollableState.get().contentOffsetY * -1
|
|
172
|
+
: 0;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* an accumulated value of starting position with gesture translation y.
|
|
176
|
+
*/
|
|
177
|
+
const draggedPosition = context.value.initialPosition + translationY;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* an accumulated value of dragged position and negative scrollable content offset,
|
|
181
|
+
* this will insure locking sheet position when user is scrolling the scrollable until,
|
|
182
|
+
* they reach to the top of the scrollable.
|
|
183
|
+
*/
|
|
184
|
+
const accumulatedDraggedPosition =
|
|
185
|
+
draggedPosition + negativeScrollableContentOffset;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* a clamped value of the accumulated dragged position, to insure keeping the dragged
|
|
189
|
+
* position between the highest and lowest snap points.
|
|
190
|
+
*/
|
|
191
|
+
const clampedPosition = clamp(
|
|
192
|
+
accumulatedDraggedPosition,
|
|
193
|
+
highestSnapPoint,
|
|
194
|
+
lowestSnapPoint
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* if scrollable position is locked and the animated position
|
|
199
|
+
* reaches the highest point, then we unlock the scrollable position.
|
|
200
|
+
*/
|
|
201
|
+
if (
|
|
202
|
+
context.value.isScrollablePositionLocked &&
|
|
203
|
+
source === GESTURE_SOURCE.CONTENT &&
|
|
204
|
+
animatedPosition.value === highestSnapPoint
|
|
205
|
+
) {
|
|
206
|
+
context.value = {
|
|
207
|
+
...context.value,
|
|
208
|
+
isScrollablePositionLocked: false,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* over-drag implementation.
|
|
214
|
+
*/
|
|
215
|
+
if (enableOverDrag) {
|
|
216
|
+
if (
|
|
217
|
+
(source === GESTURE_SOURCE.HANDLE ||
|
|
218
|
+
animatedScrollableState.get().type === SCROLLABLE_TYPE.VIEW) &&
|
|
219
|
+
draggedPosition < highestSnapPoint
|
|
220
|
+
) {
|
|
221
|
+
const resistedPosition =
|
|
222
|
+
highestSnapPoint -
|
|
223
|
+
Math.sqrt(1 + (highestSnapPoint - draggedPosition)) *
|
|
224
|
+
overDragResistanceFactor;
|
|
225
|
+
animatedPosition.value = resistedPosition;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (
|
|
230
|
+
source === GESTURE_SOURCE.HANDLE &&
|
|
231
|
+
draggedPosition > lowestSnapPoint
|
|
232
|
+
) {
|
|
233
|
+
const resistedPosition =
|
|
234
|
+
lowestSnapPoint +
|
|
235
|
+
Math.sqrt(1 + (draggedPosition - lowestSnapPoint)) *
|
|
236
|
+
overDragResistanceFactor;
|
|
237
|
+
animatedPosition.value = resistedPosition;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
source === GESTURE_SOURCE.CONTENT &&
|
|
243
|
+
draggedPosition + negativeScrollableContentOffset > lowestSnapPoint
|
|
244
|
+
) {
|
|
245
|
+
const resistedPosition =
|
|
246
|
+
lowestSnapPoint +
|
|
247
|
+
Math.sqrt(
|
|
248
|
+
1 +
|
|
249
|
+
(draggedPosition +
|
|
250
|
+
negativeScrollableContentOffset -
|
|
251
|
+
lowestSnapPoint)
|
|
252
|
+
) *
|
|
253
|
+
overDragResistanceFactor;
|
|
254
|
+
animatedPosition.value = resistedPosition;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
animatedPosition.value = clampedPosition;
|
|
260
|
+
},
|
|
261
|
+
[
|
|
262
|
+
enableOverDrag,
|
|
263
|
+
enablePanDownToClose,
|
|
264
|
+
overDragResistanceFactor,
|
|
265
|
+
isInTemporaryPosition,
|
|
266
|
+
animatedScrollableState,
|
|
267
|
+
animatedDetentsState,
|
|
268
|
+
animatedLayoutState,
|
|
269
|
+
animatedPosition,
|
|
270
|
+
context,
|
|
271
|
+
]
|
|
272
|
+
);
|
|
273
|
+
const handleOnEnd: GestureEventHandlerCallbackType = useCallback(
|
|
274
|
+
function handleOnEnd(source, { translationY, absoluteY, velocityY }) {
|
|
275
|
+
'worklet';
|
|
276
|
+
const { highestDetentPosition, detents, closedDetentPosition } =
|
|
277
|
+
animatedDetentsState.get();
|
|
278
|
+
if (
|
|
279
|
+
highestDetentPosition === undefined ||
|
|
280
|
+
detents === undefined ||
|
|
281
|
+
detents.length === 0 ||
|
|
282
|
+
closedDetentPosition === undefined
|
|
283
|
+
) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const highestSnapPoint = highestDetentPosition;
|
|
288
|
+
const isSheetAtHighestSnapPoint =
|
|
289
|
+
animatedPosition.value === highestSnapPoint;
|
|
290
|
+
const {
|
|
291
|
+
refreshable: scrollableIsRefreshable,
|
|
292
|
+
contentOffsetY: scrollableContentOffsetY,
|
|
293
|
+
type: scrollableType,
|
|
294
|
+
} = animatedScrollableState.get();
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* if scrollable is refreshable and sheet position at the highest
|
|
298
|
+
* point, then do not interact with current gesture.
|
|
299
|
+
*/
|
|
300
|
+
if (
|
|
301
|
+
source === GESTURE_SOURCE.CONTENT &&
|
|
302
|
+
scrollableIsRefreshable &&
|
|
303
|
+
isSheetAtHighestSnapPoint
|
|
304
|
+
) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* if the sheet is in a temporary position and the gesture ended above
|
|
310
|
+
* the current position, then we snap back to the temporary position.
|
|
311
|
+
*/
|
|
312
|
+
if (
|
|
313
|
+
isInTemporaryPosition.value &&
|
|
314
|
+
context.value.initialPosition >= animatedPosition.value
|
|
315
|
+
) {
|
|
316
|
+
if (context.value.initialPosition > animatedPosition.value) {
|
|
317
|
+
animateToPosition(
|
|
318
|
+
context.value.initialPosition,
|
|
319
|
+
ANIMATION_SOURCE.GESTURE,
|
|
320
|
+
velocityY / 2
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* close keyboard if current position is below the recorded
|
|
328
|
+
* start position and keyboard still shown.
|
|
329
|
+
*/
|
|
330
|
+
const isScrollable =
|
|
331
|
+
scrollableType !== SCROLLABLE_TYPE.UNDETERMINED &&
|
|
332
|
+
scrollableType !== SCROLLABLE_TYPE.VIEW;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* if keyboard is shown and the sheet is dragged down,
|
|
336
|
+
* then we dismiss the keyboard.
|
|
337
|
+
*/
|
|
338
|
+
if (
|
|
339
|
+
context.value.initialKeyboardStatus === KEYBOARD_STATUS.SHOWN &&
|
|
340
|
+
animatedPosition.value > context.value.initialPosition
|
|
341
|
+
) {
|
|
342
|
+
/**
|
|
343
|
+
* if the platform is ios, current content is scrollable and
|
|
344
|
+
* the end touch point is below the keyboard position then
|
|
345
|
+
* we exit the method.
|
|
346
|
+
*
|
|
347
|
+
* because the the keyboard dismiss is interactive in iOS.
|
|
348
|
+
*/
|
|
349
|
+
if (
|
|
350
|
+
!(
|
|
351
|
+
Platform.OS === 'ios' &&
|
|
352
|
+
isScrollable &&
|
|
353
|
+
absoluteY >
|
|
354
|
+
WINDOW_HEIGHT -
|
|
355
|
+
animatedKeyboardState.get().heightWithinContainer
|
|
356
|
+
)
|
|
357
|
+
) {
|
|
358
|
+
runOnJS(dismissKeyboard)();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* reset isInTemporaryPosition value
|
|
364
|
+
*/
|
|
365
|
+
if (isInTemporaryPosition.value) {
|
|
366
|
+
isInTemporaryPosition.value = false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* clone snap points array, and insert the container height
|
|
371
|
+
* if pan down to close is enabled.
|
|
372
|
+
*/
|
|
373
|
+
const snapPoints = detents.slice();
|
|
374
|
+
if (enablePanDownToClose) {
|
|
375
|
+
snapPoints.unshift(closedDetentPosition);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* calculate the destination point, using redash.
|
|
380
|
+
*/
|
|
381
|
+
const destinationPoint = snapPoint(
|
|
382
|
+
translationY + context.value.initialPosition,
|
|
383
|
+
velocityY,
|
|
384
|
+
snapPoints
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* if destination point is the same as the current position,
|
|
389
|
+
* then no need to perform animation.
|
|
390
|
+
*/
|
|
391
|
+
if (destinationPoint === animatedPosition.value) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const wasGestureHandledByScrollView =
|
|
396
|
+
source === GESTURE_SOURCE.CONTENT && scrollableContentOffsetY > 0;
|
|
397
|
+
/**
|
|
398
|
+
* prevents snapping from top to middle / bottom with repeated interrupted scrolls
|
|
399
|
+
*/
|
|
400
|
+
if (wasGestureHandledByScrollView && isSheetAtHighestSnapPoint) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
animateToPosition(
|
|
405
|
+
destinationPoint,
|
|
406
|
+
ANIMATION_SOURCE.GESTURE,
|
|
407
|
+
velocityY / 2
|
|
408
|
+
);
|
|
409
|
+
},
|
|
410
|
+
[
|
|
411
|
+
enablePanDownToClose,
|
|
412
|
+
isInTemporaryPosition,
|
|
413
|
+
animatedScrollableState,
|
|
414
|
+
animatedDetentsState,
|
|
415
|
+
animatedKeyboardState,
|
|
416
|
+
animatedPosition,
|
|
417
|
+
animateToPosition,
|
|
418
|
+
context,
|
|
419
|
+
]
|
|
420
|
+
);
|
|
421
|
+
const handleOnFinalize: GestureEventHandlerCallbackType = useCallback(
|
|
422
|
+
function handleOnFinalize() {
|
|
423
|
+
'worklet';
|
|
424
|
+
resetContext(context);
|
|
425
|
+
},
|
|
426
|
+
[context]
|
|
427
|
+
);
|
|
428
|
+
//#endregion
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
handleOnStart,
|
|
432
|
+
handleOnChange,
|
|
433
|
+
handleOnEnd,
|
|
434
|
+
handleOnFinalize,
|
|
435
|
+
};
|
|
436
|
+
};
|