@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,418 @@
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 { GestureEventHandlerCallbackType } from '../types';
12
+ import { clamp } from '../utilities/clamp';
13
+ import { snapPoint } from '../utilities/snapPoint';
14
+ import { useBottomSheetInternal } from './useBottomSheetInternal';
15
+
16
+ type GestureEventContextType = {
17
+ initialPosition: number;
18
+ initialKeyboardStatus: KEYBOARD_STATUS;
19
+ initialTranslationY: number;
20
+ isScrollablePositionLocked: boolean;
21
+ };
22
+
23
+ const INITIAL_CONTEXT: GestureEventContextType = {
24
+ initialPosition: 0,
25
+ initialTranslationY: 0,
26
+ initialKeyboardStatus: KEYBOARD_STATUS.UNDETERMINED,
27
+ isScrollablePositionLocked: false,
28
+ };
29
+
30
+ const dismissKeyboardOnJs = runOnJS(Keyboard.dismiss);
31
+
32
+ // biome-ignore lint: to be addressed!
33
+ const resetContext = (context: any) => {
34
+ 'worklet';
35
+ Object.keys(context).map(key => {
36
+ context[key] = undefined;
37
+ });
38
+ };
39
+
40
+ export const useGestureEventsHandlersDefault = () => {
41
+ //#region variables
42
+ const {
43
+ animatedPosition,
44
+ animatedDetentsState,
45
+ animatedKeyboardState,
46
+ animatedLayoutState,
47
+ animatedScrollableState,
48
+ enableOverDrag,
49
+ enablePanDownToClose,
50
+ overDragResistanceFactor,
51
+ isInTemporaryPosition,
52
+ animateToPosition,
53
+ stopAnimation,
54
+ } = useBottomSheetInternal();
55
+
56
+ const context = useSharedValue<GestureEventContextType>({
57
+ ...INITIAL_CONTEXT,
58
+ });
59
+ //#endregion
60
+
61
+ //#region gesture methods
62
+ const handleOnStart: GestureEventHandlerCallbackType = useCallback(
63
+ function handleOnStart(__, { translationY }) {
64
+ 'worklet';
65
+ // cancel current animation
66
+ stopAnimation();
67
+
68
+ // store current animated position
69
+ context.value = {
70
+ ...context.value,
71
+ initialPosition: animatedPosition.value,
72
+ initialKeyboardStatus: animatedKeyboardState.get().status,
73
+ initialTranslationY: translationY,
74
+ };
75
+
76
+ /**
77
+ * if the scrollable content is scrolled, then
78
+ * we lock the position.
79
+ */
80
+ if (animatedScrollableState.get().contentOffsetY > 0) {
81
+ context.value.isScrollablePositionLocked = true;
82
+ }
83
+ },
84
+ [
85
+ stopAnimation,
86
+ context,
87
+ animatedPosition,
88
+ animatedKeyboardState,
89
+ animatedScrollableState,
90
+ ]
91
+ );
92
+ const handleOnChange: GestureEventHandlerCallbackType = useCallback(
93
+ function handleOnChange(source, { translationY }) {
94
+ 'worklet';
95
+ const { highestDetentPosition, detents } = animatedDetentsState.get();
96
+ if (highestDetentPosition === undefined || detents === undefined) {
97
+ return;
98
+ }
99
+
100
+ let highestSnapPoint = highestDetentPosition;
101
+ translationY = translationY - context.value.initialTranslationY;
102
+ /**
103
+ * if keyboard is shown, then we set the highest point to the current
104
+ * position which includes the keyboard height.
105
+ */
106
+ if (
107
+ isInTemporaryPosition.value &&
108
+ context.value.initialKeyboardStatus === KEYBOARD_STATUS.SHOWN
109
+ ) {
110
+ highestSnapPoint = context.value.initialPosition;
111
+ }
112
+
113
+ /**
114
+ * if current position is out of provided `snapPoints` and smaller then
115
+ * highest snap pont, then we set the highest point to the current position.
116
+ */
117
+ if (
118
+ isInTemporaryPosition.value &&
119
+ context.value.initialPosition < highestSnapPoint
120
+ ) {
121
+ highestSnapPoint = context.value.initialPosition;
122
+ }
123
+
124
+ const { containerHeight } = animatedLayoutState.get();
125
+ const lowestSnapPoint = enablePanDownToClose
126
+ ? containerHeight
127
+ : detents[0];
128
+
129
+ const {
130
+ refreshable: scrollableIsRefreshable,
131
+ contentOffsetY: scrollableContentOffsetY,
132
+ type: scrollableType,
133
+ } = animatedScrollableState.get();
134
+
135
+ /**
136
+ * if scrollable is refreshable and sheet position at the highest
137
+ * point, then do not interact with current gesture.
138
+ */
139
+ if (
140
+ source === GESTURE_SOURCE.CONTENT &&
141
+ scrollableIsRefreshable &&
142
+ animatedPosition.value === highestSnapPoint
143
+ ) {
144
+ return;
145
+ }
146
+
147
+ /**
148
+ * a negative scrollable content offset to be subtracted from accumulated
149
+ * current position and gesture translation Y to allow user to drag the sheet,
150
+ * when scrollable position at the top.
151
+ * a negative scrollable content offset when the scrollable is not locked.
152
+ */
153
+ const negativeScrollableContentOffset =
154
+ (context.value.initialPosition === highestSnapPoint &&
155
+ source === GESTURE_SOURCE.CONTENT) ||
156
+ !context.value.isScrollablePositionLocked
157
+ ? scrollableContentOffsetY * -1
158
+ : 0;
159
+
160
+ /**
161
+ * an accumulated value of starting position with gesture translation y.
162
+ */
163
+ const draggedPosition = context.value.initialPosition + translationY;
164
+
165
+ /**
166
+ * an accumulated value of dragged position and negative scrollable content offset,
167
+ * this will insure locking sheet position when user is scrolling the scrollable until,
168
+ * they reach to the top of the scrollable.
169
+ */
170
+ const accumulatedDraggedPosition =
171
+ draggedPosition + negativeScrollableContentOffset;
172
+
173
+ /**
174
+ * a clamped value of the accumulated dragged position, to insure keeping the dragged
175
+ * position between the highest and lowest snap points.
176
+ */
177
+ const clampedPosition = clamp(
178
+ accumulatedDraggedPosition,
179
+ highestSnapPoint,
180
+ lowestSnapPoint
181
+ );
182
+
183
+ /**
184
+ * if scrollable position is locked and the animated position
185
+ * reaches the highest point, then we unlock the scrollable position.
186
+ */
187
+ if (
188
+ context.value.isScrollablePositionLocked &&
189
+ source === GESTURE_SOURCE.CONTENT &&
190
+ animatedPosition.value === highestSnapPoint
191
+ ) {
192
+ context.value.isScrollablePositionLocked = false;
193
+ }
194
+
195
+ /**
196
+ * over-drag implementation.
197
+ */
198
+ if (enableOverDrag) {
199
+ if (
200
+ (source === GESTURE_SOURCE.HANDLE ||
201
+ scrollableType === SCROLLABLE_TYPE.VIEW) &&
202
+ draggedPosition < highestSnapPoint
203
+ ) {
204
+ const resistedPosition =
205
+ highestSnapPoint -
206
+ Math.sqrt(1 + (highestSnapPoint - draggedPosition)) *
207
+ overDragResistanceFactor;
208
+ animatedPosition.value = resistedPosition;
209
+ return;
210
+ }
211
+
212
+ if (
213
+ source === GESTURE_SOURCE.HANDLE &&
214
+ draggedPosition > lowestSnapPoint
215
+ ) {
216
+ const resistedPosition =
217
+ lowestSnapPoint +
218
+ Math.sqrt(1 + (draggedPosition - lowestSnapPoint)) *
219
+ overDragResistanceFactor;
220
+ animatedPosition.value = resistedPosition;
221
+ return;
222
+ }
223
+
224
+ if (
225
+ source === GESTURE_SOURCE.CONTENT &&
226
+ draggedPosition + negativeScrollableContentOffset > lowestSnapPoint
227
+ ) {
228
+ const resistedPosition =
229
+ lowestSnapPoint +
230
+ Math.sqrt(
231
+ 1 +
232
+ (draggedPosition +
233
+ negativeScrollableContentOffset -
234
+ lowestSnapPoint)
235
+ ) *
236
+ overDragResistanceFactor;
237
+ animatedPosition.value = resistedPosition;
238
+ return;
239
+ }
240
+ }
241
+
242
+ animatedPosition.value = clampedPosition;
243
+ },
244
+ [
245
+ context,
246
+ enableOverDrag,
247
+ enablePanDownToClose,
248
+ overDragResistanceFactor,
249
+ isInTemporaryPosition,
250
+ animatedDetentsState,
251
+ animatedLayoutState,
252
+ animatedPosition,
253
+ animatedScrollableState,
254
+ ]
255
+ );
256
+ const handleOnEnd: GestureEventHandlerCallbackType = useCallback(
257
+ function handleOnEnd(source, { translationY, absoluteY, velocityY }) {
258
+ 'worklet';
259
+ const { highestDetentPosition, detents, closedDetentPosition } =
260
+ animatedDetentsState.get();
261
+ if (
262
+ highestDetentPosition === undefined ||
263
+ detents === undefined ||
264
+ closedDetentPosition === undefined
265
+ ) {
266
+ return;
267
+ }
268
+
269
+ const highestSnapPoint = highestDetentPosition;
270
+ const isSheetAtHighestSnapPoint =
271
+ animatedPosition.value === highestSnapPoint;
272
+
273
+ const {
274
+ refreshable: scrollableIsRefreshable,
275
+ contentOffsetY: scrollableContentOffsetY,
276
+ type: scrollableType,
277
+ } = animatedScrollableState.get();
278
+
279
+ /**
280
+ * if scrollable is refreshable and sheet position at the highest
281
+ * point, then do not interact with current gesture.
282
+ */
283
+ if (
284
+ source === GESTURE_SOURCE.CONTENT &&
285
+ scrollableIsRefreshable &&
286
+ isSheetAtHighestSnapPoint
287
+ ) {
288
+ return;
289
+ }
290
+
291
+ /**
292
+ * if the sheet is in a temporary position and the gesture ended above
293
+ * the current position, then we snap back to the temporary position.
294
+ */
295
+ if (
296
+ isInTemporaryPosition.value &&
297
+ context.value.initialPosition >= animatedPosition.value
298
+ ) {
299
+ if (context.value.initialPosition > animatedPosition.value) {
300
+ animateToPosition(
301
+ context.value.initialPosition,
302
+ ANIMATION_SOURCE.GESTURE,
303
+ velocityY / 2
304
+ );
305
+ }
306
+ return;
307
+ }
308
+
309
+ /**
310
+ * close keyboard if current position is below the recorded
311
+ * start position and keyboard still shown.
312
+ */
313
+ const isScrollable =
314
+ scrollableType !== SCROLLABLE_TYPE.UNDETERMINED &&
315
+ scrollableType !== SCROLLABLE_TYPE.VIEW;
316
+
317
+ /**
318
+ * if keyboard is shown and the sheet is dragged down,
319
+ * then we dismiss the keyboard.
320
+ */
321
+ if (
322
+ context.value.initialKeyboardStatus === KEYBOARD_STATUS.SHOWN &&
323
+ animatedPosition.value > context.value.initialPosition
324
+ ) {
325
+ /**
326
+ * if the platform is ios, current content is scrollable and
327
+ * the end touch point is below the keyboard position then
328
+ * we exit the method.
329
+ *
330
+ * because the the keyboard dismiss is interactive in iOS.
331
+ */
332
+ if (
333
+ !(
334
+ Platform.OS === 'ios' &&
335
+ isScrollable &&
336
+ absoluteY >
337
+ WINDOW_HEIGHT - animatedKeyboardState.get().heightWithinContainer
338
+ )
339
+ ) {
340
+ dismissKeyboardOnJs();
341
+ }
342
+ }
343
+
344
+ /**
345
+ * reset isInTemporaryPosition value
346
+ */
347
+ if (isInTemporaryPosition.value) {
348
+ isInTemporaryPosition.value = false;
349
+ }
350
+
351
+ /**
352
+ * clone snap points array, and insert the container height
353
+ * if pan down to close is enabled.
354
+ */
355
+ const snapPoints = detents.slice();
356
+ if (enablePanDownToClose) {
357
+ snapPoints.unshift(closedDetentPosition);
358
+ }
359
+
360
+ /**
361
+ * calculate the destination point, using redash.
362
+ */
363
+ const destinationPoint = snapPoint(
364
+ translationY + context.value.initialPosition,
365
+ velocityY,
366
+ snapPoints
367
+ );
368
+
369
+ /**
370
+ * if destination point is the same as the current position,
371
+ * then no need to perform animation.
372
+ */
373
+ if (destinationPoint === animatedPosition.value) {
374
+ return;
375
+ }
376
+
377
+ const wasGestureHandledByScrollView =
378
+ source === GESTURE_SOURCE.CONTENT && scrollableContentOffsetY > 0;
379
+ /**
380
+ * prevents snapping from top to middle / bottom with repeated interrupted scrolls
381
+ */
382
+ if (wasGestureHandledByScrollView && isSheetAtHighestSnapPoint) {
383
+ return;
384
+ }
385
+
386
+ animateToPosition(
387
+ destinationPoint,
388
+ ANIMATION_SOURCE.GESTURE,
389
+ velocityY / 2
390
+ );
391
+ },
392
+ [
393
+ enablePanDownToClose,
394
+ isInTemporaryPosition,
395
+ animatedScrollableState,
396
+ animatedDetentsState,
397
+ animatedKeyboardState,
398
+ animatedPosition,
399
+ animateToPosition,
400
+ context,
401
+ ]
402
+ );
403
+ const handleOnFinalize: GestureEventHandlerCallbackType = useCallback(
404
+ function handleOnFinalize() {
405
+ 'worklet';
406
+ resetContext(context);
407
+ },
408
+ [context]
409
+ );
410
+ //#endregion
411
+
412
+ return {
413
+ handleOnStart,
414
+ handleOnChange,
415
+ handleOnEnd,
416
+ handleOnFinalize,
417
+ };
418
+ };
@@ -0,0 +1,90 @@
1
+ import { useCallback } from 'react';
2
+ import {
3
+ type GestureStateChangeEvent,
4
+ type GestureUpdateEvent,
5
+ type PanGestureChangeEventPayload,
6
+ type PanGestureHandlerEventPayload,
7
+ State,
8
+ } from 'react-native-gesture-handler';
9
+ import type { SharedValue } from 'react-native-reanimated';
10
+ import { GESTURE_SOURCE } from '../constants';
11
+ import type {
12
+ GestureEventHandlerCallbackType,
13
+ GestureHandlersHookType,
14
+ } from '../types';
15
+
16
+ export const useGestureHandler: GestureHandlersHookType = (
17
+ source: GESTURE_SOURCE,
18
+ state: SharedValue<State>,
19
+ gestureSource: SharedValue<GESTURE_SOURCE>,
20
+ onStart: GestureEventHandlerCallbackType,
21
+ onChange: GestureEventHandlerCallbackType,
22
+ onEnd: GestureEventHandlerCallbackType,
23
+ onFinalize: GestureEventHandlerCallbackType
24
+ ) => {
25
+ const handleOnStart = useCallback(
26
+ (event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
27
+ 'worklet';
28
+ state.value = State.BEGAN;
29
+ gestureSource.value = source;
30
+
31
+ onStart(source, event);
32
+ return;
33
+ },
34
+ [state, gestureSource, source, onStart]
35
+ );
36
+
37
+ const handleOnChange = useCallback(
38
+ (
39
+ event: GestureUpdateEvent<
40
+ PanGestureHandlerEventPayload & PanGestureChangeEventPayload
41
+ >
42
+ ) => {
43
+ 'worklet';
44
+ if (gestureSource.value !== source) {
45
+ return;
46
+ }
47
+
48
+ state.value = event.state;
49
+ onChange(source, event);
50
+ },
51
+ [state, gestureSource, source, onChange]
52
+ );
53
+
54
+ const handleOnEnd = useCallback(
55
+ (event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
56
+ 'worklet';
57
+ if (gestureSource.value !== source) {
58
+ return;
59
+ }
60
+
61
+ state.value = event.state;
62
+ gestureSource.value = GESTURE_SOURCE.UNDETERMINED;
63
+
64
+ onEnd(source, event);
65
+ },
66
+ [state, gestureSource, source, onEnd]
67
+ );
68
+
69
+ const handleOnFinalize = useCallback(
70
+ (event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
71
+ 'worklet';
72
+ if (gestureSource.value !== source) {
73
+ return;
74
+ }
75
+
76
+ state.value = event.state;
77
+ gestureSource.value = GESTURE_SOURCE.UNDETERMINED;
78
+
79
+ onFinalize(source, event);
80
+ },
81
+ [state, gestureSource, source, onFinalize]
82
+ );
83
+
84
+ return {
85
+ handleOnStart,
86
+ handleOnChange,
87
+ handleOnEnd,
88
+ handleOnFinalize,
89
+ };
90
+ };
@@ -0,0 +1,108 @@
1
+ import invariant from 'invariant';
2
+ import { useMemo } from 'react';
3
+ import type { BottomSheetProps } from '../components/bottomSheet';
4
+ import { INITIAL_SNAP_POINT } from '../components/bottomSheet/constants';
5
+
6
+ /**
7
+ * @todo
8
+ * replace this with `prop-types`.
9
+ */
10
+
11
+ export const usePropsValidator = ({
12
+ index,
13
+ snapPoints,
14
+ enableDynamicSizing,
15
+ topInset,
16
+ bottomInset,
17
+ containerHeight,
18
+ containerOffset,
19
+ }: Pick<
20
+ BottomSheetProps,
21
+ | 'index'
22
+ | 'snapPoints'
23
+ | 'enableDynamicSizing'
24
+ | 'topInset'
25
+ | 'bottomInset'
26
+ | 'containerHeight'
27
+ | 'containerOffset'
28
+ >) => {
29
+ useMemo(() => {
30
+ //#region snap points
31
+ const _snapPoints = snapPoints
32
+ ? 'get' in snapPoints
33
+ ? snapPoints.get()
34
+ : snapPoints
35
+ : [];
36
+ invariant(
37
+ _snapPoints || enableDynamicSizing,
38
+ `'snapPoints' was not provided! please provide at least one snap point.`
39
+ );
40
+
41
+ _snapPoints.map(snapPoint => {
42
+ const _snapPoint =
43
+ typeof snapPoint === 'number'
44
+ ? snapPoint
45
+ : Number.parseInt(snapPoint.replace('%', ''), 10);
46
+
47
+ invariant(
48
+ _snapPoint > 0 || _snapPoint === INITIAL_SNAP_POINT,
49
+ `Snap point '${snapPoint}' is invalid. if you want to allow user to close the sheet, Please use 'enablePanDownToClose' prop.`
50
+ );
51
+ });
52
+
53
+ invariant(
54
+ 'value' in _snapPoints || _snapPoints.length > 0 || enableDynamicSizing,
55
+ `'snapPoints' was provided with no points! please provide at least one snap point.`
56
+ );
57
+ //#endregion
58
+
59
+ //#region index
60
+ invariant(
61
+ typeof index === 'number' || typeof index === 'undefined',
62
+ `'index' was provided but with wrong type ! expected type is a number.`
63
+ );
64
+
65
+ invariant(
66
+ enableDynamicSizing ||
67
+ (typeof index === 'number'
68
+ ? index >= -1 && index <= _snapPoints.length - 1
69
+ : true),
70
+ `'index' was provided but out of the provided snap points range! expected value to be between -1, ${
71
+ _snapPoints.length - 1
72
+ }`
73
+ );
74
+ //#endregion
75
+
76
+ //#region insets
77
+ invariant(
78
+ typeof topInset === 'number' || typeof topInset === 'undefined',
79
+ `'topInset' was provided but with wrong type ! expected type is a number.`
80
+ );
81
+ invariant(
82
+ typeof bottomInset === 'number' || typeof bottomInset === 'undefined',
83
+ `'bottomInset' was provided but with wrong type ! expected type is a number.`
84
+ );
85
+ //#endregion
86
+
87
+ //#region container height and offset
88
+ invariant(
89
+ containerHeight === undefined,
90
+ `'containerHeight' is deprecated, please use 'containerLayoutState'.`
91
+ );
92
+
93
+ invariant(
94
+ containerOffset === undefined,
95
+ `'containerHeight' is deprecated, please use 'containerLayoutState'.`
96
+ );
97
+
98
+ // animations
99
+ }, [
100
+ index,
101
+ snapPoints,
102
+ topInset,
103
+ bottomInset,
104
+ enableDynamicSizing,
105
+ containerHeight,
106
+ containerOffset,
107
+ ]);
108
+ };
@@ -0,0 +1,45 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import type { SharedValue } from 'react-native-reanimated';
3
+ import { cancelAnimation, makeMutable } from 'react-native-reanimated';
4
+ import type { Primitive } from '../types';
5
+
6
+ export const useReactiveSharedValue = <T>(
7
+ value: T
8
+ ): T extends Primitive ? SharedValue<T> : T => {
9
+ const initialValueRef = useRef<T>(null);
10
+ const valueRef = useRef<SharedValue<T>>(null);
11
+
12
+ if (value && typeof value === 'object' && 'value' in value) {
13
+ /**
14
+ * if provided value is a shared value,
15
+ * then we do not initialize another one.
16
+ */
17
+ } else if (valueRef.current === null) {
18
+ // @ts-ignore
19
+ initialValueRef.current = value;
20
+ /**
21
+ * if value is an object, then we need to
22
+ * pass a clone.
23
+ */
24
+ if (typeof value === 'object') {
25
+ // @ts-ignore
26
+ valueRef.current = makeMutable({ ...value });
27
+ } else {
28
+ // @ts-ignore
29
+ valueRef.current = makeMutable(value);
30
+ }
31
+ } else if (initialValueRef.current !== value) {
32
+ valueRef.current.value = value as T;
33
+ }
34
+
35
+ useEffect(() => {
36
+ return () => {
37
+ if (valueRef.current) {
38
+ cancelAnimation(valueRef.current);
39
+ }
40
+ };
41
+ }, []);
42
+
43
+ // @ts-ignore
44
+ return valueRef.current ?? value;
45
+ };