@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.
Files changed (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/mock.js +231 -0
  4. package/package.json +107 -0
  5. package/src/components/bottomSheet/BottomSheet.tsx +1885 -0
  6. package/src/components/bottomSheet/BottomSheetBody.tsx +44 -0
  7. package/src/components/bottomSheet/BottomSheetContent.tsx +261 -0
  8. package/src/components/bottomSheet/constants.ts +58 -0
  9. package/src/components/bottomSheet/index.ts +2 -0
  10. package/src/components/bottomSheet/styles.ts +11 -0
  11. package/src/components/bottomSheet/types.d.ts +358 -0
  12. package/src/components/bottomSheetBackdrop/BottomSheetBackdrop.tsx +165 -0
  13. package/src/components/bottomSheetBackdrop/constants.ts +22 -0
  14. package/src/components/bottomSheetBackdrop/index.ts +2 -0
  15. package/src/components/bottomSheetBackdrop/styles.ts +8 -0
  16. package/src/components/bottomSheetBackdrop/types.d.ts +58 -0
  17. package/src/components/bottomSheetBackground/BottomSheetBackground.tsx +20 -0
  18. package/src/components/bottomSheetBackground/BottomSheetBackgroundContainer.tsx +35 -0
  19. package/src/components/bottomSheetBackground/index.ts +2 -0
  20. package/src/components/bottomSheetBackground/styles.ts +9 -0
  21. package/src/components/bottomSheetBackground/types.d.ts +12 -0
  22. package/src/components/bottomSheetDebugView/BottomSheetDebugView.tsx +26 -0
  23. package/src/components/bottomSheetDebugView/ReText.tsx +72 -0
  24. package/src/components/bottomSheetDebugView/ReText.webx.tsx +55 -0
  25. package/src/components/bottomSheetDebugView/index.ts +1 -0
  26. package/src/components/bottomSheetDebugView/styles.ts +19 -0
  27. package/src/components/bottomSheetDebugView/styles.web.ts +20 -0
  28. package/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx +123 -0
  29. package/src/components/bottomSheetDraggableView/index.ts +1 -0
  30. package/src/components/bottomSheetDraggableView/types.d.ts +9 -0
  31. package/src/components/bottomSheetFooter/BottomSheetFooter.tsx +119 -0
  32. package/src/components/bottomSheetFooter/BottomSheetFooterContainer.tsx +43 -0
  33. package/src/components/bottomSheetFooter/index.ts +3 -0
  34. package/src/components/bottomSheetFooter/styles.ts +12 -0
  35. package/src/components/bottomSheetFooter/types.d.ts +41 -0
  36. package/src/components/bottomSheetGestureHandlersProvider/BottomSheetGestureHandlersProvider.tsx +69 -0
  37. package/src/components/bottomSheetGestureHandlersProvider/index.ts +1 -0
  38. package/src/components/bottomSheetGestureHandlersProvider/types.d.ts +8 -0
  39. package/src/components/bottomSheetHandle/BottomSheetHandle.tsx +51 -0
  40. package/src/components/bottomSheetHandle/BottomSheetHandleContainer.tsx +187 -0
  41. package/src/components/bottomSheetHandle/constants.ts +12 -0
  42. package/src/components/bottomSheetHandle/index.ts +6 -0
  43. package/src/components/bottomSheetHandle/styles.ts +23 -0
  44. package/src/components/bottomSheetHandle/types.d.ts +52 -0
  45. package/src/components/bottomSheetHostingContainer/BottomSheetHostingContainer.tsx +130 -0
  46. package/src/components/bottomSheetHostingContainer/index.ts +2 -0
  47. package/src/components/bottomSheetHostingContainer/styles.ts +5 -0
  48. package/src/components/bottomSheetHostingContainer/styles.web.ts +11 -0
  49. package/src/components/bottomSheetHostingContainer/types.d.ts +17 -0
  50. package/src/components/bottomSheetModal/BottomSheetModal.tsx +482 -0
  51. package/src/components/bottomSheetModal/constants.ts +4 -0
  52. package/src/components/bottomSheetModal/index.ts +6 -0
  53. package/src/components/bottomSheetModal/types.d.ts +67 -0
  54. package/src/components/bottomSheetModalProvider/BottomSheetModalProvider.tsx +211 -0
  55. package/src/components/bottomSheetModalProvider/index.ts +1 -0
  56. package/src/components/bottomSheetModalProvider/types.d.ts +12 -0
  57. package/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.android.tsx +84 -0
  58. package/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.tsx +1 -0
  59. package/src/components/bottomSheetRefreshControl/index.ts +20 -0
  60. package/src/components/bottomSheetScrollable/BottomSheetDraggableScrollable.tsx +23 -0
  61. package/src/components/bottomSheetScrollable/BottomSheetFlashList.tsx +88 -0
  62. package/src/components/bottomSheetScrollable/BottomSheetFlashList.web.tsx +1 -0
  63. package/src/components/bottomSheetScrollable/BottomSheetFlatList.tsx +26 -0
  64. package/src/components/bottomSheetScrollable/BottomSheetScrollView.tsx +27 -0
  65. package/src/components/bottomSheetScrollable/BottomSheetSectionList.tsx +29 -0
  66. package/src/components/bottomSheetScrollable/BottomSheetVirtualizedList.tsx +27 -0
  67. package/src/components/bottomSheetScrollable/ScrollableContainer.android.tsx +55 -0
  68. package/src/components/bottomSheetScrollable/ScrollableContainer.tsx +22 -0
  69. package/src/components/bottomSheetScrollable/ScrollableContainer.web.tsx +102 -0
  70. package/src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsx +153 -0
  71. package/src/components/bottomSheetScrollable/index.ts +15 -0
  72. package/src/components/bottomSheetScrollable/styles.ts +8 -0
  73. package/src/components/bottomSheetScrollable/types.d.ts +280 -0
  74. package/src/components/bottomSheetScrollable/useBottomSheetContentSizeSetter.ts +32 -0
  75. package/src/components/bottomSheetTextInput/BottomSheetTextInput.tsx +127 -0
  76. package/src/components/bottomSheetTextInput/index.ts +2 -0
  77. package/src/components/bottomSheetTextInput/types.ts +3 -0
  78. package/src/components/bottomSheetView/BottomSheetView.tsx +93 -0
  79. package/src/components/bottomSheetView/index.ts +1 -0
  80. package/src/components/bottomSheetView/styles.ts +10 -0
  81. package/src/components/bottomSheetView/types.d.ts +24 -0
  82. package/src/components/touchables/Touchables.ios.tsx +5 -0
  83. package/src/components/touchables/Touchables.tsx +5 -0
  84. package/src/components/touchables/index.ts +20 -0
  85. package/src/constants.ts +159 -0
  86. package/src/contexts/external.ts +8 -0
  87. package/src/contexts/gesture.ts +13 -0
  88. package/src/contexts/index.ts +15 -0
  89. package/src/contexts/internal.ts +65 -0
  90. package/src/contexts/modal/external.ts +11 -0
  91. package/src/contexts/modal/internal.ts +25 -0
  92. package/src/hooks/index.ts +29 -0
  93. package/src/hooks/useAnimatedDetents.ts +119 -0
  94. package/src/hooks/useAnimatedKeyboard.ts +174 -0
  95. package/src/hooks/useAnimatedLayout.ts +109 -0
  96. package/src/hooks/useBottomSheet.ts +12 -0
  97. package/src/hooks/useBottomSheetContentContainerStyle.ts +88 -0
  98. package/src/hooks/useBottomSheetGestureHandlers.ts +12 -0
  99. package/src/hooks/useBottomSheetInternal.ts +25 -0
  100. package/src/hooks/useBottomSheetModal.ts +12 -0
  101. package/src/hooks/useBottomSheetModalInternal.ts +25 -0
  102. package/src/hooks/useBottomSheetScrollableCreator.tsx +60 -0
  103. package/src/hooks/useBottomSheetSpringConfigs.ts +11 -0
  104. package/src/hooks/useBottomSheetTimingConfigs.ts +36 -0
  105. package/src/hooks/useBoundingClientRect.ts +77 -0
  106. package/src/hooks/useGestureEventsHandlersDefault.tsx +436 -0
  107. package/src/hooks/useGestureEventsHandlersDefault.web.tsx +418 -0
  108. package/src/hooks/useGestureHandler.ts +90 -0
  109. package/src/hooks/usePropsValidator.ts +108 -0
  110. package/src/hooks/useReactiveSharedValue.ts +45 -0
  111. package/src/hooks/useScrollEventsHandlersDefault.ts +167 -0
  112. package/src/hooks/useScrollHandler.ts +72 -0
  113. package/src/hooks/useScrollHandler.web.ts +181 -0
  114. package/src/hooks/useScrollable.ts +131 -0
  115. package/src/hooks/useScrollableSetter.ts +56 -0
  116. package/src/hooks/useStableCallback.ts +26 -0
  117. package/src/index.ts +79 -0
  118. package/src/types.d.ts +336 -0
  119. package/src/utilities/animate.ts +56 -0
  120. package/src/utilities/clamp.ts +8 -0
  121. package/src/utilities/easingExp.ts +10 -0
  122. package/src/utilities/findNodeHandle.ts +1 -0
  123. package/src/utilities/findNodeHandle.web.ts +33 -0
  124. package/src/utilities/getKeyboardAnimationConfigs.ts +44 -0
  125. package/src/utilities/getRefNativeTag.web.ts +6 -0
  126. package/src/utilities/id.ts +6 -0
  127. package/src/utilities/index.ts +7 -0
  128. package/src/utilities/isFabricInstalled.ts +9 -0
  129. package/src/utilities/logger.ts +55 -0
  130. package/src/utilities/noop.ts +7 -0
  131. package/src/utilities/normalizeSnapPoint.ts +17 -0
  132. package/src/utilities/snapPoint.ts +11 -0
  133. 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
+ };