@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,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
|
+
};
|