@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,1885 @@
1
+ import invariant from 'invariant';
2
+ import React, {
3
+ forwardRef,
4
+ memo,
5
+ useCallback,
6
+ useEffect,
7
+ useImperativeHandle,
8
+ useMemo,
9
+ } from 'react';
10
+ import { Platform, StyleSheet } from 'react-native';
11
+ import { State } from 'react-native-gesture-handler';
12
+ import Animated, {
13
+ cancelAnimation,
14
+ Extrapolation,
15
+ interpolate,
16
+ ReduceMotion,
17
+ runOnJS,
18
+ runOnUI,
19
+ useAnimatedReaction,
20
+ useDerivedValue,
21
+ useReducedMotion,
22
+ useSharedValue,
23
+ type WithSpringConfig,
24
+ type WithTimingConfig,
25
+ } from 'react-native-reanimated';
26
+ import {
27
+ ANIMATION_SOURCE,
28
+ ANIMATION_STATUS,
29
+ INITIAL_LAYOUT_VALUE,
30
+ KEYBOARD_BEHAVIOR,
31
+ KEYBOARD_BLUR_BEHAVIOR,
32
+ KEYBOARD_INPUT_MODE,
33
+ KEYBOARD_STATUS,
34
+ SHEET_STATE,
35
+ SNAP_POINT_TYPE,
36
+ } from '../../constants';
37
+ import {
38
+ BottomSheetInternalProvider,
39
+ BottomSheetProvider,
40
+ } from '../../contexts';
41
+ import {
42
+ useAnimatedDetents,
43
+ useAnimatedKeyboard,
44
+ useAnimatedLayout,
45
+ usePropsValidator,
46
+ useReactiveSharedValue,
47
+ useScrollable,
48
+ useStableCallback,
49
+ } from '../../hooks';
50
+ import type { AnimationState, BottomSheetMethods } from '../../types';
51
+ import {
52
+ animate,
53
+ getKeyboardAnimationConfigs,
54
+ normalizeSnapPoint,
55
+ print,
56
+ } from '../../utilities';
57
+ import { BottomSheetBackgroundContainer } from '../bottomSheetBackground';
58
+ // import BottomSheetDebugView from '../bottomSheetDebugView';
59
+ import { BottomSheetFooterContainer } from '../bottomSheetFooter';
60
+ import BottomSheetGestureHandlersProvider from '../bottomSheetGestureHandlersProvider';
61
+ import {
62
+ BottomSheetHandle,
63
+ BottomSheetHandleContainer,
64
+ } from '../bottomSheetHandle';
65
+ import { BottomSheetHostingContainer } from '../bottomSheetHostingContainer';
66
+ import { BottomSheetBody } from './BottomSheetBody';
67
+ import { BottomSheetContent } from './BottomSheetContent';
68
+ import {
69
+ DEFAULT_ACCESSIBILITY_LABEL,
70
+ DEFAULT_ACCESSIBILITY_ROLE,
71
+ DEFAULT_ACCESSIBLE,
72
+ DEFAULT_ANIMATE_ON_MOUNT,
73
+ DEFAULT_DYNAMIC_SIZING,
74
+ DEFAULT_ENABLE_BLUR_KEYBOARD_ON_GESTURE,
75
+ DEFAULT_ENABLE_CONTENT_PANNING_GESTURE,
76
+ DEFAULT_ENABLE_OVER_DRAG,
77
+ DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE,
78
+ DEFAULT_KEYBOARD_BEHAVIOR,
79
+ DEFAULT_KEYBOARD_BLUR_BEHAVIOR,
80
+ DEFAULT_KEYBOARD_INDEX,
81
+ DEFAULT_KEYBOARD_INPUT_MODE,
82
+ DEFAULT_OVER_DRAG_RESISTANCE_FACTOR,
83
+ INITIAL_POSITION,
84
+ INITIAL_VALUE,
85
+ } from './constants';
86
+ import type { AnimateToPositionType, BottomSheetProps } from './types';
87
+
88
+ Animated.addWhitelistedUIProps({
89
+ decelerationRate: true,
90
+ });
91
+
92
+ type BottomSheet = BottomSheetMethods;
93
+
94
+ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
95
+ function BottomSheet(props, ref) {
96
+ //#region extract props
97
+ const {
98
+ // animations configurations
99
+ animationConfigs: _providedAnimationConfigs,
100
+
101
+ // configurations
102
+ index: _providedIndex = 0,
103
+ snapPoints: _providedSnapPoints,
104
+ animateOnMount = DEFAULT_ANIMATE_ON_MOUNT,
105
+ enableContentPanningGesture = DEFAULT_ENABLE_CONTENT_PANNING_GESTURE,
106
+ enableHandlePanningGesture,
107
+ enableOverDrag = DEFAULT_ENABLE_OVER_DRAG,
108
+ enablePanDownToClose = DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE,
109
+ enableDynamicSizing = DEFAULT_DYNAMIC_SIZING,
110
+ overDragResistanceFactor = DEFAULT_OVER_DRAG_RESISTANCE_FACTOR,
111
+ overrideReduceMotion: _providedOverrideReduceMotion,
112
+
113
+ // styles
114
+ style,
115
+ containerStyle: _providedContainerStyle,
116
+ backgroundStyle: _providedBackgroundStyle,
117
+ handleStyle: _providedHandleStyle,
118
+ handleIndicatorStyle: _providedHandleIndicatorStyle,
119
+
120
+ // hooks
121
+ gestureEventsHandlersHook,
122
+
123
+ // keyboard
124
+ keyboardBehavior = DEFAULT_KEYBOARD_BEHAVIOR,
125
+ keyboardBlurBehavior = DEFAULT_KEYBOARD_BLUR_BEHAVIOR,
126
+ android_keyboardInputMode = DEFAULT_KEYBOARD_INPUT_MODE,
127
+ enableBlurKeyboardOnGesture = DEFAULT_ENABLE_BLUR_KEYBOARD_ON_GESTURE,
128
+
129
+ // layout
130
+ containerLayoutState,
131
+ topInset = 0,
132
+ bottomInset = 0,
133
+ maxDynamicContentSize,
134
+ containerHeight,
135
+ containerOffset,
136
+
137
+ // animated callback shared values
138
+ animatedPosition: _providedAnimatedPosition,
139
+ animatedIndex: _providedAnimatedIndex,
140
+
141
+ // gestures
142
+ simultaneousHandlers: _providedSimultaneousHandlers,
143
+ waitFor: _providedWaitFor,
144
+ activeOffsetX: _providedActiveOffsetX,
145
+ activeOffsetY: _providedActiveOffsetY,
146
+ failOffsetX: _providedFailOffsetX,
147
+ failOffsetY: _providedFailOffsetY,
148
+
149
+ // callbacks
150
+ onChange: _providedOnChange,
151
+ onClose: _providedOnClose,
152
+ onAnimate: _providedOnAnimate,
153
+
154
+ // private
155
+ $modal = false,
156
+ detached = false,
157
+
158
+ // components
159
+ handleComponent = BottomSheetHandle,
160
+ backdropComponent: BackdropComponent,
161
+ backgroundComponent,
162
+ footerComponent,
163
+ children,
164
+
165
+ // accessibility
166
+ accessible: _providedAccessible = DEFAULT_ACCESSIBLE,
167
+ accessibilityLabel:
168
+ _providedAccessibilityLabel = DEFAULT_ACCESSIBILITY_LABEL,
169
+ accessibilityRole:
170
+ _providedAccessibilityRole = DEFAULT_ACCESSIBILITY_ROLE,
171
+ } = props;
172
+ //#endregion
173
+
174
+ //#region validate props
175
+ if (__DEV__) {
176
+ // biome-ignore lint/correctness/useHookAtTopLevel: used in development only.
177
+ usePropsValidator({
178
+ index: _providedIndex,
179
+ snapPoints: _providedSnapPoints,
180
+ enableDynamicSizing,
181
+ topInset,
182
+ bottomInset,
183
+ containerHeight,
184
+ containerOffset,
185
+ });
186
+ }
187
+ //#endregion
188
+
189
+ //#region layout variables
190
+ const animatedLayoutState = useAnimatedLayout(
191
+ containerLayoutState,
192
+ topInset,
193
+ bottomInset,
194
+ $modal,
195
+ handleComponent === null
196
+ );
197
+ const animatedDetentsState = useAnimatedDetents(
198
+ _providedSnapPoints,
199
+ animatedLayoutState,
200
+ enableDynamicSizing,
201
+ maxDynamicContentSize,
202
+ detached,
203
+ $modal,
204
+ bottomInset
205
+ );
206
+ const animatedSheetHeight = useDerivedValue(() => {
207
+ const { containerHeight } = animatedLayoutState.get();
208
+ const { highestDetentPosition } = animatedDetentsState.get();
209
+
210
+ if (highestDetentPosition === undefined) {
211
+ return INITIAL_LAYOUT_VALUE;
212
+ }
213
+
214
+ return containerHeight - highestDetentPosition;
215
+ }, [animatedLayoutState, animatedDetentsState]);
216
+ const animatedCurrentIndex = useReactiveSharedValue(
217
+ animateOnMount ? -1 : _providedIndex
218
+ );
219
+ const animatedPosition = useSharedValue(INITIAL_POSITION);
220
+
221
+ // conditional
222
+ const isAnimatedOnMount = useSharedValue(
223
+ !animateOnMount || _providedIndex === -1
224
+ );
225
+ const isLayoutCalculated = useDerivedValue(() => {
226
+ let isContainerHeightCalculated = false;
227
+ const { containerHeight, handleHeight } = animatedLayoutState.get();
228
+ //container height was provided.
229
+ if (containerHeight !== null || containerHeight !== undefined) {
230
+ isContainerHeightCalculated = true;
231
+ }
232
+ // container height did set.
233
+ if (containerHeight !== INITIAL_LAYOUT_VALUE) {
234
+ isContainerHeightCalculated = true;
235
+ }
236
+
237
+ let isHandleHeightCalculated = false;
238
+ // handle component is null.
239
+ if (handleComponent === null) {
240
+ isHandleHeightCalculated = true;
241
+ }
242
+ // handle height did set.
243
+ if (handleHeight !== INITIAL_LAYOUT_VALUE) {
244
+ isHandleHeightCalculated = true;
245
+ }
246
+
247
+ let isSnapPointsNormalized = false;
248
+ const { detents } = animatedDetentsState.get();
249
+ // the first snap point did normalized
250
+ if (detents) {
251
+ isSnapPointsNormalized = true;
252
+ }
253
+
254
+ return (
255
+ isContainerHeightCalculated &&
256
+ isHandleHeightCalculated &&
257
+ isSnapPointsNormalized
258
+ );
259
+ }, [animatedLayoutState, animatedDetentsState, handleComponent]);
260
+ const isInTemporaryPosition = useSharedValue(false);
261
+ const animatedContainerHeightDidChange = useSharedValue(false);
262
+
263
+ // gesture
264
+ const animatedContentGestureState = useSharedValue<State>(
265
+ State.UNDETERMINED
266
+ );
267
+ const animatedHandleGestureState = useSharedValue<State>(
268
+ State.UNDETERMINED
269
+ );
270
+ //#endregion
271
+
272
+ //#region hooks variables
273
+ // keyboard
274
+ const { state: animatedKeyboardState, textInputNodesRef } =
275
+ useAnimatedKeyboard();
276
+ const userReduceMotionSetting = useReducedMotion();
277
+ const reduceMotion = useMemo(() => {
278
+ return !_providedOverrideReduceMotion ||
279
+ _providedOverrideReduceMotion === ReduceMotion.System
280
+ ? userReduceMotionSetting
281
+ : _providedOverrideReduceMotion === ReduceMotion.Always;
282
+ }, [userReduceMotionSetting, _providedOverrideReduceMotion]);
283
+ //#endregion
284
+
285
+ //#region state/dynamic variables
286
+ // states
287
+ const animatedAnimationState = useSharedValue<AnimationState>({
288
+ status: ANIMATION_STATUS.UNDETERMINED,
289
+ source: ANIMATION_SOURCE.MOUNT,
290
+ });
291
+ const animatedSheetState = useDerivedValue(() => {
292
+ const { detents, closedDetentPosition } = animatedDetentsState.get();
293
+
294
+ if (
295
+ !detents ||
296
+ detents.length === 0 ||
297
+ closedDetentPosition === undefined
298
+ ) {
299
+ return SHEET_STATE.CLOSED;
300
+ }
301
+
302
+ // closed position = position >= container height
303
+ if (animatedPosition.value >= closedDetentPosition) {
304
+ return SHEET_STATE.CLOSED;
305
+ }
306
+
307
+ const { containerHeight } = animatedLayoutState.get();
308
+ // extended position = container height - sheet height
309
+ const extendedPosition = containerHeight - animatedSheetHeight.value;
310
+ if (animatedPosition.value === extendedPosition) {
311
+ return SHEET_STATE.EXTENDED;
312
+ }
313
+
314
+ // extended position with keyboard =
315
+ // container height - (sheet height + keyboard height in root container)
316
+ const keyboardHeightInContainer =
317
+ animatedKeyboardState.get().heightWithinContainer;
318
+ const extendedPositionWithKeyboard = Math.max(
319
+ 0,
320
+ containerHeight -
321
+ (animatedSheetHeight.value + keyboardHeightInContainer)
322
+ );
323
+
324
+ // detect if keyboard is open and the sheet is in temporary position
325
+ if (
326
+ keyboardBehavior === KEYBOARD_BEHAVIOR.interactive &&
327
+ isInTemporaryPosition.value &&
328
+ animatedPosition.value === extendedPositionWithKeyboard
329
+ ) {
330
+ return SHEET_STATE.EXTENDED;
331
+ }
332
+
333
+ // fill parent = 0
334
+ if (animatedPosition.value === 0) {
335
+ return SHEET_STATE.FILL_PARENT;
336
+ }
337
+
338
+ // detect if position is below extended point
339
+ if (animatedPosition.value < extendedPosition) {
340
+ return SHEET_STATE.OVER_EXTENDED;
341
+ }
342
+
343
+ return SHEET_STATE.OPENED;
344
+ }, [
345
+ animatedLayoutState,
346
+ animatedDetentsState,
347
+ animatedKeyboardState,
348
+ animatedPosition,
349
+ animatedSheetHeight,
350
+ isInTemporaryPosition,
351
+ keyboardBehavior,
352
+ ]);
353
+ const {
354
+ state: animatedScrollableState,
355
+ status: animatedScrollableStatus,
356
+ setScrollableRef,
357
+ removeScrollableRef,
358
+ } = useScrollable(
359
+ enableContentPanningGesture,
360
+ animatedSheetState,
361
+ animatedKeyboardState,
362
+ animatedAnimationState
363
+ );
364
+ // dynamic
365
+ const animatedIndex = useDerivedValue(() => {
366
+ const { detents } = animatedDetentsState.get();
367
+ if (!detents || detents.length === 0) {
368
+ return -1;
369
+ }
370
+
371
+ const adjustedSnapPoints = detents.slice().reverse();
372
+ const adjustedSnapPointsIndexes = detents
373
+ .slice()
374
+ .map((_, index: number) => index)
375
+ .reverse();
376
+
377
+ const { containerHeight } = animatedLayoutState.get();
378
+ /**
379
+ * we add the close state index `-1`
380
+ */
381
+ adjustedSnapPoints.push(containerHeight);
382
+ adjustedSnapPointsIndexes.push(-1);
383
+
384
+ const currentIndex = isLayoutCalculated.value
385
+ ? interpolate(
386
+ animatedPosition.value,
387
+ adjustedSnapPoints,
388
+ adjustedSnapPointsIndexes,
389
+ Extrapolation.CLAMP
390
+ )
391
+ : -1;
392
+
393
+ const {
394
+ status: animationStatus,
395
+ source: animationSource,
396
+ nextIndex,
397
+ nextPosition,
398
+ } = animatedAnimationState.get();
399
+ /**
400
+ * if the sheet is currently running an animation by the keyboard opening,
401
+ * then we clamp the index on android with resize keyboard mode.
402
+ */
403
+ if (
404
+ android_keyboardInputMode === KEYBOARD_INPUT_MODE.adjustResize &&
405
+ animationStatus === ANIMATION_STATUS.RUNNING &&
406
+ animationSource === ANIMATION_SOURCE.KEYBOARD &&
407
+ isInTemporaryPosition.value
408
+ ) {
409
+ return Math.max(animatedCurrentIndex.value, currentIndex);
410
+ }
411
+
412
+ /**
413
+ * if the sheet is currently running an animation by snap point change - usually caused
414
+ * by dynamic content height -, then we return the next position index.
415
+ */
416
+ if (
417
+ animationStatus === ANIMATION_STATUS.RUNNING &&
418
+ animationSource === ANIMATION_SOURCE.SNAP_POINT_CHANGE &&
419
+ nextIndex !== undefined &&
420
+ nextPosition !== undefined
421
+ ) {
422
+ return nextIndex;
423
+ }
424
+
425
+ return currentIndex;
426
+ }, [
427
+ android_keyboardInputMode,
428
+ animatedAnimationState,
429
+ animatedLayoutState,
430
+ animatedCurrentIndex,
431
+ animatedPosition,
432
+ animatedDetentsState,
433
+ isInTemporaryPosition,
434
+ isLayoutCalculated,
435
+ ]);
436
+ //#endregion
437
+
438
+ //#region private methods
439
+ const handleOnChange = useCallback(
440
+ function handleOnChange(index: number, position: number) {
441
+ if (__DEV__) {
442
+ print({
443
+ component: 'BottomSheet',
444
+ method: 'handleOnChange',
445
+ category: 'callback',
446
+ params: {
447
+ index,
448
+ position,
449
+ },
450
+ });
451
+ }
452
+
453
+ if (!_providedOnChange) {
454
+ return;
455
+ }
456
+
457
+ const { dynamicDetentIndex } = animatedDetentsState.get();
458
+
459
+ _providedOnChange(
460
+ index,
461
+ position,
462
+ index === dynamicDetentIndex
463
+ ? SNAP_POINT_TYPE.DYNAMIC
464
+ : SNAP_POINT_TYPE.PROVIDED
465
+ );
466
+ },
467
+ [_providedOnChange, animatedDetentsState]
468
+ );
469
+ const handleOnAnimate = useCallback(
470
+ function handleOnAnimate(targetIndex: number, targetPosition: number) {
471
+ if (__DEV__) {
472
+ print({
473
+ component: 'BottomSheet',
474
+ method: 'handleOnAnimate',
475
+ category: 'callback',
476
+ params: {
477
+ toIndex: targetIndex,
478
+ toPosition: targetPosition,
479
+ fromIndex: animatedCurrentIndex.value,
480
+ fromPosition: animatedPosition.value,
481
+ },
482
+ });
483
+ }
484
+
485
+ if (targetIndex === animatedCurrentIndex.get()) {
486
+ return;
487
+ }
488
+
489
+ if (!_providedOnAnimate) {
490
+ return;
491
+ }
492
+
493
+ _providedOnAnimate(
494
+ animatedCurrentIndex.value,
495
+ targetIndex,
496
+ animatedPosition.value,
497
+ targetPosition
498
+ );
499
+ },
500
+ [_providedOnAnimate, animatedCurrentIndex, animatedPosition]
501
+ );
502
+ const handleOnClose = useCallback(
503
+ function handleOnClose() {
504
+ if (__DEV__) {
505
+ print({
506
+ component: 'BottomSheet',
507
+ method: 'handleOnClose',
508
+ category: 'callback',
509
+ });
510
+ }
511
+
512
+ if (!_providedOnClose) {
513
+ return;
514
+ }
515
+
516
+ _providedOnClose();
517
+ },
518
+ [_providedOnClose]
519
+ );
520
+ //#endregion
521
+
522
+ //#region animation
523
+ const stopAnimation = useCallback(() => {
524
+ 'worklet';
525
+ cancelAnimation(animatedPosition);
526
+ animatedAnimationState.set({
527
+ status: ANIMATION_STATUS.STOPPED,
528
+ source: ANIMATION_SOURCE.NONE,
529
+ });
530
+ }, [animatedPosition, animatedAnimationState]);
531
+ const animateToPositionCompleted = useCallback(
532
+ function animateToPositionCompleted(isFinished?: boolean) {
533
+ 'worklet';
534
+ if (!isFinished) {
535
+ return;
536
+ }
537
+
538
+ const { nextIndex, nextPosition } = animatedAnimationState.get();
539
+
540
+ if (__DEV__) {
541
+ runOnJS(print)({
542
+ component: 'BottomSheet',
543
+ method: 'animateToPositionCompleted',
544
+ params: {
545
+ currentIndex: animatedCurrentIndex.value,
546
+ nextIndex,
547
+ nextPosition,
548
+ },
549
+ });
550
+ }
551
+
552
+ if (nextIndex === undefined || nextPosition === undefined) {
553
+ return;
554
+ }
555
+
556
+ // callbacks
557
+ if (nextIndex !== animatedCurrentIndex.get()) {
558
+ runOnJS(handleOnChange)(nextIndex, nextPosition);
559
+ }
560
+
561
+ if (nextIndex === -1) {
562
+ runOnJS(handleOnClose)();
563
+ }
564
+
565
+ animatedCurrentIndex.set(nextIndex);
566
+
567
+ // reset values
568
+ animatedContainerHeightDidChange.set(false);
569
+ isAnimatedOnMount.set(true);
570
+ animatedAnimationState.set({
571
+ status: ANIMATION_STATUS.STOPPED,
572
+ source: ANIMATION_SOURCE.NONE,
573
+ nextIndex: undefined,
574
+ nextPosition: undefined,
575
+ isForcedClosing: undefined,
576
+ });
577
+ },
578
+ [
579
+ handleOnChange,
580
+ handleOnClose,
581
+ animatedCurrentIndex,
582
+ animatedAnimationState,
583
+ animatedContainerHeightDidChange,
584
+ isAnimatedOnMount,
585
+ ]
586
+ );
587
+ const animateToPosition: AnimateToPositionType = useCallback(
588
+ function animateToPosition(
589
+ position: number,
590
+ source: ANIMATION_SOURCE,
591
+ velocity = 0,
592
+ configs?: WithTimingConfig | WithSpringConfig
593
+ ) {
594
+ 'worklet';
595
+ if (__DEV__) {
596
+ runOnJS(print)({
597
+ component: 'BottomSheet',
598
+ method: 'animateToPosition',
599
+ params: {
600
+ currentPosition: animatedPosition.value,
601
+ nextPosition: position,
602
+ source,
603
+ },
604
+ });
605
+ }
606
+
607
+ if (position === undefined) {
608
+ return;
609
+ }
610
+
611
+ if (position === animatedPosition.get()) {
612
+ return;
613
+ }
614
+
615
+ // early exit if there is a running animation to
616
+ // the same position
617
+ const { status: animationStatus, nextPosition } =
618
+ animatedAnimationState.get();
619
+ if (
620
+ animationStatus === ANIMATION_STATUS.RUNNING &&
621
+ position === nextPosition
622
+ ) {
623
+ return;
624
+ }
625
+
626
+ // stop animation if it is running
627
+ if (animationStatus === ANIMATION_STATUS.RUNNING) {
628
+ stopAnimation();
629
+ }
630
+
631
+ /**
632
+ * offset the position if keyboard is shown and behavior not extend.
633
+ */
634
+ let offset = 0;
635
+
636
+ const { status, heightWithinContainer } = animatedKeyboardState.get();
637
+ const sheetState = animatedSheetState.get();
638
+ if (
639
+ status === KEYBOARD_STATUS.SHOWN &&
640
+ keyboardBehavior !== KEYBOARD_BEHAVIOR.extend &&
641
+ ([
642
+ ANIMATION_SOURCE.KEYBOARD,
643
+ ANIMATION_SOURCE.SNAP_POINT_CHANGE,
644
+ ].includes(source) ||
645
+ sheetState === SHEET_STATE.OVER_EXTENDED)
646
+ ) {
647
+ offset = heightWithinContainer;
648
+ }
649
+ const { detents, closedDetentPosition, highestDetentPosition } =
650
+ animatedDetentsState.get();
651
+ let index = detents?.indexOf(position + offset) ?? -1;
652
+
653
+ /**
654
+ * because keyboard position is not part of the detents array,
655
+ * we will need to manually set the index to the highest detent index.
656
+ */
657
+ if (
658
+ index === -1 &&
659
+ status === KEYBOARD_STATUS.SHOWN &&
660
+ position !== closedDetentPosition
661
+ ) {
662
+ index = highestDetentPosition ?? DEFAULT_KEYBOARD_INDEX;
663
+ }
664
+
665
+ /**
666
+ * set the animation state
667
+ */
668
+ animatedAnimationState.set(state => {
669
+ 'worklet';
670
+ return {
671
+ ...state,
672
+ status: ANIMATION_STATUS.RUNNING,
673
+ source,
674
+ nextIndex: index,
675
+ nextPosition: position,
676
+ };
677
+ });
678
+
679
+ /**
680
+ * fire `onAnimate` callback
681
+ */
682
+ runOnJS(handleOnAnimate)(index, position);
683
+
684
+ /**
685
+ * start animation
686
+ */
687
+ animatedPosition.value = animate({
688
+ point: position,
689
+ configs: configs || _providedAnimationConfigs,
690
+ velocity,
691
+ overrideReduceMotion: _providedOverrideReduceMotion,
692
+ onComplete: animateToPositionCompleted,
693
+ });
694
+ },
695
+ [
696
+ handleOnAnimate,
697
+ stopAnimation,
698
+ animateToPositionCompleted,
699
+ keyboardBehavior,
700
+ _providedAnimationConfigs,
701
+ _providedOverrideReduceMotion,
702
+ animatedDetentsState,
703
+ animatedAnimationState,
704
+ animatedKeyboardState,
705
+ animatedPosition,
706
+ animatedSheetState,
707
+ ]
708
+ );
709
+ /**
710
+ * Set to position without animation.
711
+ *
712
+ * @param targetPosition position to be set.
713
+ */
714
+ const setToPosition = useCallback(
715
+ function setToPosition(targetPosition: number) {
716
+ 'worklet';
717
+ if (targetPosition === undefined || targetPosition !== targetPosition) {
718
+ return;
719
+ }
720
+
721
+ if (
722
+ targetPosition === animatedPosition.get() &&
723
+ !animatedContainerHeightDidChange.value
724
+ ) {
725
+ return;
726
+ }
727
+
728
+ const { status: animationStatus, nextPosition } =
729
+ animatedAnimationState.get();
730
+
731
+ // early exit if there is a running animation to
732
+ // the same position
733
+ if (
734
+ animationStatus === ANIMATION_STATUS.RUNNING &&
735
+ targetPosition === nextPosition
736
+ ) {
737
+ return;
738
+ }
739
+
740
+ if (__DEV__) {
741
+ runOnJS(print)({
742
+ component: 'BottomSheet',
743
+ method: 'setToPosition',
744
+ params: {
745
+ currentPosition: animatedPosition.value,
746
+ targetPosition,
747
+ },
748
+ });
749
+ }
750
+
751
+ /**
752
+ * store next position
753
+ */
754
+ const { detents } = animatedDetentsState.get();
755
+ const index = detents?.indexOf(targetPosition) ?? -1;
756
+ animatedAnimationState.set(state => {
757
+ 'worklet';
758
+ return {
759
+ ...state,
760
+ nextPosition: targetPosition,
761
+ nextIndex: index,
762
+ };
763
+ });
764
+
765
+ stopAnimation();
766
+
767
+ // set values
768
+ animatedPosition.value = targetPosition;
769
+ animatedContainerHeightDidChange.value = false;
770
+ },
771
+ [
772
+ stopAnimation,
773
+ animatedPosition,
774
+ animatedContainerHeightDidChange,
775
+ animatedAnimationState,
776
+ animatedDetentsState,
777
+ ]
778
+ );
779
+ //#endregion
780
+
781
+ //#region private methods
782
+ /**
783
+ * Calculate and evaluate the current position based on multiple
784
+ * local states.
785
+ */
786
+ const getEvaluatedPosition = useCallback(
787
+ function getEvaluatedPosition(source: ANIMATION_SOURCE) {
788
+ 'worklet';
789
+ const currentIndex = animatedCurrentIndex.value;
790
+ const { detents, highestDetentPosition, closedDetentPosition } =
791
+ animatedDetentsState.get();
792
+ const keyboardStatus = animatedKeyboardState.get().status;
793
+
794
+ if (
795
+ detents === undefined ||
796
+ highestDetentPosition === undefined ||
797
+ closedDetentPosition === undefined
798
+ ) {
799
+ return;
800
+ }
801
+
802
+ /**
803
+ * if the keyboard blur behavior is restore and keyboard is hidden,
804
+ * then we return the previous snap point.
805
+ */
806
+ if (
807
+ source === ANIMATION_SOURCE.KEYBOARD &&
808
+ keyboardBlurBehavior === KEYBOARD_BLUR_BEHAVIOR.restore &&
809
+ keyboardStatus === KEYBOARD_STATUS.HIDDEN &&
810
+ animatedContentGestureState.value !== State.ACTIVE &&
811
+ animatedHandleGestureState.value !== State.ACTIVE
812
+ ) {
813
+ isInTemporaryPosition.value = false;
814
+ const nextPosition = detents[currentIndex];
815
+ return nextPosition;
816
+ }
817
+
818
+ /**
819
+ * if the keyboard appearance behavior is extend and keyboard is shown,
820
+ * then we return the heights snap point.
821
+ */
822
+ if (
823
+ keyboardBehavior === KEYBOARD_BEHAVIOR.extend &&
824
+ keyboardStatus === KEYBOARD_STATUS.SHOWN
825
+ ) {
826
+ return highestDetentPosition;
827
+ }
828
+
829
+ /**
830
+ * if the keyboard appearance behavior is fill parent and keyboard is shown,
831
+ * then we return 0 ( full screen ).
832
+ */
833
+ if (
834
+ keyboardBehavior === KEYBOARD_BEHAVIOR.fillParent &&
835
+ keyboardStatus === KEYBOARD_STATUS.SHOWN
836
+ ) {
837
+ isInTemporaryPosition.value = true;
838
+ return 0;
839
+ }
840
+
841
+ /**
842
+ * if the keyboard appearance behavior is interactive and keyboard is shown,
843
+ * then we return the heights points minus the keyboard in container height.
844
+ */
845
+ if (
846
+ keyboardBehavior === KEYBOARD_BEHAVIOR.interactive &&
847
+ keyboardStatus === KEYBOARD_STATUS.SHOWN &&
848
+ // ensure that this logic does not run on android
849
+ // with resize input mode
850
+ !(
851
+ Platform.OS === 'android' &&
852
+ android_keyboardInputMode === 'adjustResize'
853
+ )
854
+ ) {
855
+ isInTemporaryPosition.value = true;
856
+ const keyboardHeightInContainer =
857
+ animatedKeyboardState.get().heightWithinContainer;
858
+ return Math.max(0, highestDetentPosition - keyboardHeightInContainer);
859
+ }
860
+
861
+ /**
862
+ * if the bottom sheet is in temporary position, then we return
863
+ * the current position.
864
+ */
865
+ if (isInTemporaryPosition.value) {
866
+ return animatedPosition.value;
867
+ }
868
+
869
+ /**
870
+ * if the bottom sheet did not animate on mount,
871
+ * then we return the provided index or the closed position.
872
+ */
873
+ if (!isAnimatedOnMount.value) {
874
+ return _providedIndex === -1
875
+ ? closedDetentPosition
876
+ : detents[_providedIndex];
877
+ }
878
+
879
+ const { status, nextIndex, nextPosition } =
880
+ animatedAnimationState.get();
881
+
882
+ /**
883
+ * if the evaluated position is for a snap change source while the sheet is currently running
884
+ * an animation and the next position is different than the detent at next index,
885
+ * then we return the detent at next index.
886
+ *
887
+ * https://github.com/gorhom/react-native-bottom-sheet/issues/2431
888
+ */
889
+ if (
890
+ source === ANIMATION_SOURCE.SNAP_POINT_CHANGE &&
891
+ status === ANIMATION_STATUS.RUNNING &&
892
+ nextIndex !== undefined &&
893
+ nextPosition !== undefined &&
894
+ detents[nextIndex] !== nextPosition
895
+ ) {
896
+ return detents[nextIndex];
897
+ }
898
+
899
+ /**
900
+ * return the current index position.
901
+ */
902
+ return detents[currentIndex];
903
+ },
904
+ [
905
+ animatedContentGestureState,
906
+ animatedCurrentIndex,
907
+ animatedHandleGestureState,
908
+ animatedAnimationState,
909
+ animatedKeyboardState,
910
+ animatedPosition,
911
+ animatedDetentsState,
912
+ isInTemporaryPosition,
913
+ isAnimatedOnMount,
914
+ keyboardBehavior,
915
+ keyboardBlurBehavior,
916
+ _providedIndex,
917
+ android_keyboardInputMode,
918
+ ]
919
+ );
920
+
921
+ /**
922
+ * Evaluate the bottom sheet position based based on a event source and other local states.
923
+ */
924
+ const evaluatePosition = useCallback(
925
+ function evaluatePosition(
926
+ source: ANIMATION_SOURCE,
927
+ animationConfigs?: WithSpringConfig | WithTimingConfig
928
+ ) {
929
+ 'worklet';
930
+ const {
931
+ status: animationStatus,
932
+ nextIndex,
933
+ isForcedClosing,
934
+ } = animatedAnimationState.get();
935
+
936
+ const { detents, closedDetentPosition } = animatedDetentsState.get();
937
+ if (
938
+ detents === undefined ||
939
+ detents.length === 0 ||
940
+ closedDetentPosition === undefined
941
+ ) {
942
+ return;
943
+ }
944
+
945
+ /**
946
+ * if a force closing is running and source not from user, then we early exit
947
+ */
948
+ if (isForcedClosing && source !== ANIMATION_SOURCE.USER) {
949
+ return;
950
+ }
951
+ /**
952
+ * when evaluating the position while layout is not calculated, then we early exit till it is.
953
+ */
954
+ if (!isLayoutCalculated.value) {
955
+ return;
956
+ }
957
+
958
+ const proposedPosition = getEvaluatedPosition(source);
959
+ if (proposedPosition === undefined) {
960
+ return;
961
+ }
962
+
963
+ /**
964
+ * when evaluating the position while the mount animation not been handled,
965
+ * then we evaluate on mount use cases.
966
+ */
967
+ if (!isAnimatedOnMount.value) {
968
+ /**
969
+ * if animate on mount is set to true, then we animate to the propose position,
970
+ * else, we set the position with out animation.
971
+ */
972
+ if (animateOnMount) {
973
+ animateToPosition(
974
+ proposedPosition,
975
+ ANIMATION_SOURCE.MOUNT,
976
+ undefined,
977
+ animationConfigs
978
+ );
979
+ } else {
980
+ setToPosition(proposedPosition);
981
+ isAnimatedOnMount.value = true;
982
+ }
983
+ return;
984
+ }
985
+
986
+ /**
987
+ * when evaluating the position while the bottom sheet is animating.
988
+ */
989
+ if (animationStatus === ANIMATION_STATUS.RUNNING) {
990
+ const nextPositionIndex = nextIndex ?? INITIAL_VALUE;
991
+ /**
992
+ * when evaluating the position while the bottom sheet is
993
+ * closing, then we force closing the bottom sheet with no animation.
994
+ */
995
+ if (nextPositionIndex === -1 && !isInTemporaryPosition.value) {
996
+ setToPosition(closedDetentPosition);
997
+ return;
998
+ }
999
+
1000
+ /**
1001
+ * when evaluating the position while it's animating to
1002
+ * a position other than the current position, then we
1003
+ * restart the animation.
1004
+ */
1005
+ if (nextPositionIndex !== animatedCurrentIndex.value) {
1006
+ animateToPosition(
1007
+ detents[nextPositionIndex],
1008
+ source,
1009
+ undefined,
1010
+ animationConfigs
1011
+ );
1012
+ return;
1013
+ }
1014
+ }
1015
+
1016
+ /**
1017
+ * when evaluating the position while the bottom sheet is in closed
1018
+ * position and not animating, we re-set the position to closed position.
1019
+ */
1020
+ if (
1021
+ animationStatus !== ANIMATION_STATUS.RUNNING &&
1022
+ animatedCurrentIndex.value === -1
1023
+ ) {
1024
+ /**
1025
+ * early exit if reduce motion is enabled and index is out of sync with position.
1026
+ */
1027
+ if (
1028
+ reduceMotion &&
1029
+ detents[animatedIndex.value] !== animatedPosition.value
1030
+ ) {
1031
+ return;
1032
+ }
1033
+ setToPosition(closedDetentPosition);
1034
+ return;
1035
+ }
1036
+
1037
+ /**
1038
+ * when evaluating the position after the container resize, then we
1039
+ * force the bottom sheet to the proposed position with no
1040
+ * animation.
1041
+ */
1042
+ if (animatedContainerHeightDidChange.value) {
1043
+ setToPosition(proposedPosition);
1044
+ return;
1045
+ }
1046
+
1047
+ /**
1048
+ * we fall back to the proposed position.
1049
+ */
1050
+ animateToPosition(
1051
+ proposedPosition,
1052
+ source,
1053
+ undefined,
1054
+ animationConfigs
1055
+ );
1056
+ },
1057
+ [
1058
+ getEvaluatedPosition,
1059
+ animateToPosition,
1060
+ setToPosition,
1061
+ reduceMotion,
1062
+ animateOnMount,
1063
+ animatedAnimationState,
1064
+ animatedContainerHeightDidChange,
1065
+ animatedCurrentIndex,
1066
+ animatedIndex,
1067
+ animatedPosition,
1068
+ animatedDetentsState,
1069
+ isAnimatedOnMount,
1070
+ isInTemporaryPosition,
1071
+ isLayoutCalculated,
1072
+ ]
1073
+ );
1074
+ //#endregion
1075
+
1076
+ //#region public methods
1077
+ const handleSnapToIndex = useStableCallback(function handleSnapToIndex(
1078
+ index: number,
1079
+ animationConfigs?: WithSpringConfig | WithTimingConfig
1080
+ ) {
1081
+ const { detents } = animatedDetentsState.get();
1082
+ const isLayoutReady = isLayoutCalculated.get();
1083
+
1084
+ if (detents === undefined || detents.length === 0) {
1085
+ return;
1086
+ }
1087
+
1088
+ // early exit if layout is not ready yet.
1089
+ if (!isLayoutReady) {
1090
+ return;
1091
+ }
1092
+
1093
+ invariant(
1094
+ index >= -1 && index <= detents.length - 1,
1095
+ `'index' was provided but out of the provided snap points range! expected value to be between -1, ${
1096
+ detents.length - 1
1097
+ }`
1098
+ );
1099
+
1100
+ if (__DEV__) {
1101
+ print({
1102
+ component: 'BottomSheet',
1103
+ method: 'handleSnapToIndex',
1104
+ params: {
1105
+ index,
1106
+ },
1107
+ });
1108
+ }
1109
+
1110
+ const targetPosition = detents[index];
1111
+
1112
+ /**
1113
+ * exit method if :
1114
+ * - layout is not calculated.
1115
+ * - already animating to next position.
1116
+ * - sheet is forced closing.
1117
+ */
1118
+ const { nextPosition, nextIndex, isForcedClosing } =
1119
+ animatedAnimationState.get();
1120
+ if (
1121
+ !isLayoutCalculated.value ||
1122
+ index === nextIndex ||
1123
+ targetPosition === nextPosition ||
1124
+ isForcedClosing
1125
+ ) {
1126
+ return;
1127
+ }
1128
+
1129
+ /**
1130
+ * reset temporary position boolean.
1131
+ */
1132
+ isInTemporaryPosition.value = false;
1133
+
1134
+ runOnUI(animateToPosition)(
1135
+ targetPosition,
1136
+ ANIMATION_SOURCE.USER,
1137
+ 0,
1138
+ animationConfigs
1139
+ );
1140
+ });
1141
+ const handleSnapToPosition = useCallback(
1142
+ function handleSnapToPosition(
1143
+ position: number | string,
1144
+ animationConfigs?: WithSpringConfig | WithTimingConfig
1145
+ ) {
1146
+ 'worklet';
1147
+ if (__DEV__) {
1148
+ print({
1149
+ component: 'BottomSheet',
1150
+ method: 'handleSnapToPosition',
1151
+ params: {
1152
+ position,
1153
+ },
1154
+ });
1155
+ }
1156
+
1157
+ const { containerHeight } = animatedLayoutState.get();
1158
+ /**
1159
+ * normalized provided position.
1160
+ */
1161
+ const targetPosition = normalizeSnapPoint(position, containerHeight);
1162
+
1163
+ /**
1164
+ * exit method if :
1165
+ * - layout is not calculated.
1166
+ * - already animating to next position.
1167
+ * - sheet is forced closing.
1168
+ */
1169
+ const { nextPosition, isForcedClosing } = animatedAnimationState.get();
1170
+ if (
1171
+ !isLayoutCalculated ||
1172
+ targetPosition === nextPosition ||
1173
+ isForcedClosing
1174
+ ) {
1175
+ return;
1176
+ }
1177
+
1178
+ /**
1179
+ * mark the new position as temporary.
1180
+ */
1181
+ isInTemporaryPosition.value = true;
1182
+
1183
+ runOnUI(animateToPosition)(
1184
+ targetPosition,
1185
+ ANIMATION_SOURCE.USER,
1186
+ 0,
1187
+ animationConfigs
1188
+ );
1189
+ },
1190
+ [
1191
+ animateToPosition,
1192
+ isInTemporaryPosition,
1193
+ isLayoutCalculated,
1194
+ animatedLayoutState,
1195
+ animatedAnimationState,
1196
+ ]
1197
+ );
1198
+ const handleClose = useCallback(
1199
+ function handleClose(
1200
+ animationConfigs?: WithSpringConfig | WithTimingConfig
1201
+ ) {
1202
+ if (__DEV__) {
1203
+ print({
1204
+ component: 'BottomSheet',
1205
+ method: 'handleClose',
1206
+ });
1207
+ }
1208
+
1209
+ const closedDetentPosition =
1210
+ animatedDetentsState.get().closedDetentPosition;
1211
+ if (closedDetentPosition === undefined) {
1212
+ return;
1213
+ }
1214
+
1215
+ const targetPosition = closedDetentPosition;
1216
+
1217
+ /**
1218
+ * exit method if :
1219
+ * - layout is not calculated.
1220
+ * - already animating to next position.
1221
+ * - sheet is forced closing.
1222
+ */
1223
+ const { nextPosition, isForcedClosing } = animatedAnimationState.get();
1224
+ if (
1225
+ !isLayoutCalculated.value ||
1226
+ targetPosition === nextPosition ||
1227
+ isForcedClosing
1228
+ ) {
1229
+ return;
1230
+ }
1231
+
1232
+ /**
1233
+ * reset temporary position variable.
1234
+ */
1235
+ isInTemporaryPosition.value = false;
1236
+
1237
+ runOnUI(animateToPosition)(
1238
+ targetPosition,
1239
+ ANIMATION_SOURCE.USER,
1240
+ 0,
1241
+ animationConfigs
1242
+ );
1243
+ },
1244
+ [
1245
+ animateToPosition,
1246
+ isLayoutCalculated,
1247
+ isInTemporaryPosition,
1248
+ animatedDetentsState,
1249
+ animatedAnimationState,
1250
+ ]
1251
+ );
1252
+ const handleForceClose = useCallback(
1253
+ function handleForceClose(
1254
+ animationConfigs?: WithSpringConfig | WithTimingConfig
1255
+ ) {
1256
+ if (__DEV__) {
1257
+ print({
1258
+ component: 'BottomSheet',
1259
+ method: 'handleForceClose',
1260
+ });
1261
+ }
1262
+
1263
+ const closedDetentPosition =
1264
+ animatedDetentsState.get().closedDetentPosition;
1265
+ if (closedDetentPosition === undefined) {
1266
+ return;
1267
+ }
1268
+
1269
+ const targetPosition = closedDetentPosition;
1270
+
1271
+ /**
1272
+ * exit method if :
1273
+ * - already animating to next position.
1274
+ * - sheet is forced closing.
1275
+ */
1276
+ const { nextPosition, isForcedClosing } = animatedAnimationState.get();
1277
+ if (targetPosition === nextPosition || isForcedClosing) {
1278
+ return;
1279
+ }
1280
+
1281
+ /**
1282
+ * reset temporary position variable.
1283
+ */
1284
+ isInTemporaryPosition.value = false;
1285
+
1286
+ /**
1287
+ * set force closing variable.
1288
+ */
1289
+ animatedAnimationState.set(state => {
1290
+ 'worklet';
1291
+ return {
1292
+ ...state,
1293
+ isForcedClosing: true,
1294
+ };
1295
+ });
1296
+
1297
+ runOnUI(animateToPosition)(
1298
+ targetPosition,
1299
+ ANIMATION_SOURCE.USER,
1300
+ 0,
1301
+ animationConfigs
1302
+ );
1303
+ },
1304
+ [
1305
+ animateToPosition,
1306
+ isInTemporaryPosition,
1307
+ animatedDetentsState,
1308
+ animatedAnimationState,
1309
+ ]
1310
+ );
1311
+ const handleExpand = useCallback(
1312
+ function handleExpand(
1313
+ animationConfigs?: WithSpringConfig | WithTimingConfig
1314
+ ) {
1315
+ if (__DEV__) {
1316
+ print({
1317
+ component: 'BottomSheet',
1318
+ method: 'handleExpand',
1319
+ });
1320
+ }
1321
+
1322
+ const { detents } = animatedDetentsState.get();
1323
+ if (detents === undefined || detents.length === 0) {
1324
+ return;
1325
+ }
1326
+
1327
+ const targetIndex = detents.length - 1;
1328
+ const targetPosition = detents[targetIndex];
1329
+
1330
+ /**
1331
+ * exit method if :
1332
+ * - layout is not calculated.
1333
+ * - already animating to next position.
1334
+ * - sheet is forced closing.
1335
+ */
1336
+ const { nextPosition, nextIndex, isForcedClosing } =
1337
+ animatedAnimationState.get();
1338
+ if (
1339
+ !isLayoutCalculated.value ||
1340
+ targetIndex === nextIndex ||
1341
+ targetPosition === nextPosition ||
1342
+ isForcedClosing
1343
+ ) {
1344
+ return;
1345
+ }
1346
+
1347
+ /**
1348
+ * reset temporary position boolean.
1349
+ */
1350
+ isInTemporaryPosition.value = false;
1351
+
1352
+ runOnUI(animateToPosition)(
1353
+ targetPosition,
1354
+ ANIMATION_SOURCE.USER,
1355
+ 0,
1356
+ animationConfigs
1357
+ );
1358
+ },
1359
+ [
1360
+ animateToPosition,
1361
+ isInTemporaryPosition,
1362
+ isLayoutCalculated,
1363
+ animatedDetentsState,
1364
+ animatedAnimationState,
1365
+ ]
1366
+ );
1367
+ const handleCollapse = useCallback(
1368
+ function handleCollapse(
1369
+ animationConfigs?: WithSpringConfig | WithTimingConfig
1370
+ ) {
1371
+ if (__DEV__) {
1372
+ print({
1373
+ component: 'BottomSheet',
1374
+ method: 'handleCollapse',
1375
+ });
1376
+ }
1377
+
1378
+ const { detents } = animatedDetentsState.get();
1379
+ if (detents === undefined || detents.length === 0) {
1380
+ return;
1381
+ }
1382
+
1383
+ const targetPosition = detents[0];
1384
+
1385
+ /**
1386
+ * exit method if :
1387
+ * - layout is not calculated.
1388
+ * - already animating to next position.
1389
+ * - sheet is forced closing.
1390
+ */
1391
+ const { nextPosition, nextIndex, isForcedClosing } =
1392
+ animatedAnimationState.get();
1393
+ if (
1394
+ !isLayoutCalculated ||
1395
+ nextIndex === 0 ||
1396
+ targetPosition === nextPosition ||
1397
+ isForcedClosing
1398
+ ) {
1399
+ return;
1400
+ }
1401
+
1402
+ /**
1403
+ * reset temporary position boolean.
1404
+ */
1405
+ isInTemporaryPosition.value = false;
1406
+
1407
+ runOnUI(animateToPosition)(
1408
+ targetPosition,
1409
+ ANIMATION_SOURCE.USER,
1410
+ 0,
1411
+ animationConfigs
1412
+ );
1413
+ },
1414
+ [
1415
+ animateToPosition,
1416
+ isLayoutCalculated,
1417
+ isInTemporaryPosition,
1418
+ animatedDetentsState,
1419
+ animatedAnimationState,
1420
+ ]
1421
+ );
1422
+
1423
+ useImperativeHandle(ref, () => ({
1424
+ snapToIndex: handleSnapToIndex,
1425
+ snapToPosition: handleSnapToPosition,
1426
+ expand: handleExpand,
1427
+ collapse: handleCollapse,
1428
+ close: handleClose,
1429
+ forceClose: handleForceClose,
1430
+ }));
1431
+ //#endregion
1432
+
1433
+ //#region contexts variables
1434
+ const internalContextVariables = useMemo(
1435
+ () => ({
1436
+ textInputNodesRef,
1437
+ enableContentPanningGesture,
1438
+ enableDynamicSizing,
1439
+ overDragResistanceFactor,
1440
+ enableOverDrag,
1441
+ enablePanDownToClose,
1442
+ animatedAnimationState,
1443
+ animatedSheetState,
1444
+ animatedScrollableState,
1445
+ animatedScrollableStatus,
1446
+ animatedContentGestureState,
1447
+ animatedHandleGestureState,
1448
+ animatedKeyboardState,
1449
+ animatedLayoutState,
1450
+ animatedIndex,
1451
+ animatedPosition,
1452
+ animatedSheetHeight,
1453
+ animatedDetentsState,
1454
+ isInTemporaryPosition,
1455
+ simultaneousHandlers: _providedSimultaneousHandlers,
1456
+ waitFor: _providedWaitFor,
1457
+ activeOffsetX: _providedActiveOffsetX,
1458
+ activeOffsetY: _providedActiveOffsetY,
1459
+ failOffsetX: _providedFailOffsetX,
1460
+ failOffsetY: _providedFailOffsetY,
1461
+ enableBlurKeyboardOnGesture,
1462
+ animateToPosition,
1463
+ stopAnimation,
1464
+ setScrollableRef,
1465
+ removeScrollableRef,
1466
+ }),
1467
+ [
1468
+ textInputNodesRef,
1469
+ animatedIndex,
1470
+ animatedPosition,
1471
+ animatedSheetHeight,
1472
+ animatedLayoutState,
1473
+ animatedContentGestureState,
1474
+ animatedHandleGestureState,
1475
+ animatedAnimationState,
1476
+ animatedKeyboardState,
1477
+ animatedSheetState,
1478
+ animatedScrollableState,
1479
+ animatedScrollableStatus,
1480
+ animatedDetentsState,
1481
+ isInTemporaryPosition,
1482
+ enableContentPanningGesture,
1483
+ overDragResistanceFactor,
1484
+ enableOverDrag,
1485
+ enablePanDownToClose,
1486
+ enableDynamicSizing,
1487
+ enableBlurKeyboardOnGesture,
1488
+ _providedSimultaneousHandlers,
1489
+ _providedWaitFor,
1490
+ _providedActiveOffsetX,
1491
+ _providedActiveOffsetY,
1492
+ _providedFailOffsetX,
1493
+ _providedFailOffsetY,
1494
+ setScrollableRef,
1495
+ removeScrollableRef,
1496
+ animateToPosition,
1497
+ stopAnimation,
1498
+ ]
1499
+ );
1500
+ const externalContextVariables = useMemo(
1501
+ () => ({
1502
+ animatedIndex,
1503
+ animatedPosition,
1504
+ snapToIndex: handleSnapToIndex,
1505
+ snapToPosition: handleSnapToPosition,
1506
+ expand: handleExpand,
1507
+ collapse: handleCollapse,
1508
+ close: handleClose,
1509
+ forceClose: handleForceClose,
1510
+ }),
1511
+ [
1512
+ animatedIndex,
1513
+ animatedPosition,
1514
+ handleSnapToIndex,
1515
+ handleSnapToPosition,
1516
+ handleExpand,
1517
+ handleCollapse,
1518
+ handleClose,
1519
+ handleForceClose,
1520
+ ]
1521
+ );
1522
+ //#endregion
1523
+
1524
+ //#region effects
1525
+ useAnimatedReaction(
1526
+ () => animatedLayoutState.get().containerHeight,
1527
+ (result, previous) => {
1528
+ if (result === INITIAL_LAYOUT_VALUE) {
1529
+ return;
1530
+ }
1531
+
1532
+ animatedContainerHeightDidChange.value = result !== previous;
1533
+
1534
+ const { closedDetentPosition } = animatedDetentsState.get();
1535
+ if (closedDetentPosition === undefined) {
1536
+ return;
1537
+ }
1538
+
1539
+ /**
1540
+ * When user close the bottom sheet while the keyboard open on Android with
1541
+ * software keyboard layout mode set to resize, the close position would be
1542
+ * set to the container height - the keyboard height, and when the keyboard
1543
+ * closes, the container height and here we restart the animation again.
1544
+ *
1545
+ * [read more](https://github.com/gorhom/react-native-bottom-sheet/issues/2163)
1546
+ */
1547
+ const {
1548
+ status: animationStatus,
1549
+ source: animationSource,
1550
+ nextIndex,
1551
+ } = animatedAnimationState.get();
1552
+ if (
1553
+ animationStatus === ANIMATION_STATUS.RUNNING &&
1554
+ animationSource === ANIMATION_SOURCE.GESTURE &&
1555
+ nextIndex === -1
1556
+ ) {
1557
+ animateToPosition(closedDetentPosition, ANIMATION_SOURCE.GESTURE);
1558
+ }
1559
+
1560
+ /**
1561
+ * On Android with adjustResize, when the container grows back after
1562
+ * keyboard dismiss, force evaluate position to avoid race condition
1563
+ * between keyboardDidHide and container resize events.
1564
+ */
1565
+ if (
1566
+ Platform.OS === 'android' &&
1567
+ android_keyboardInputMode === KEYBOARD_INPUT_MODE.adjustResize &&
1568
+ previous !== null &&
1569
+ previous !== INITIAL_LAYOUT_VALUE &&
1570
+ result > previous
1571
+ ) {
1572
+ evaluatePosition(ANIMATION_SOURCE.CONTAINER_RESIZE);
1573
+ }
1574
+ },
1575
+ [
1576
+ animatedContainerHeightDidChange,
1577
+ animatedAnimationState,
1578
+ animatedDetentsState,
1579
+ android_keyboardInputMode,
1580
+ evaluatePosition,
1581
+ ]
1582
+ );
1583
+
1584
+ /**
1585
+ * Reaction to the `snapPoints` change, to insure that the sheet position reflect
1586
+ * to the current point correctly.
1587
+ *
1588
+ * @alias OnSnapPointsChange
1589
+ */
1590
+ useAnimatedReaction(
1591
+ () => animatedDetentsState.get().detents,
1592
+ (result, previous) => {
1593
+ /**
1594
+ * if values did not change, and did handle on mount animation
1595
+ * then we early exit the method.
1596
+ */
1597
+ if (
1598
+ JSON.stringify(result) === JSON.stringify(previous) &&
1599
+ isAnimatedOnMount.value
1600
+ ) {
1601
+ return;
1602
+ }
1603
+
1604
+ /**
1605
+ * if layout is not calculated yet, then we exit the method.
1606
+ */
1607
+ if (!isLayoutCalculated.value) {
1608
+ return;
1609
+ }
1610
+
1611
+ if (__DEV__) {
1612
+ runOnJS(print)({
1613
+ component: 'BottomSheet',
1614
+ method: 'useAnimatedReaction::OnSnapPointChange',
1615
+ category: 'effect',
1616
+ params: {
1617
+ result,
1618
+ },
1619
+ });
1620
+ }
1621
+
1622
+ evaluatePosition(ANIMATION_SOURCE.SNAP_POINT_CHANGE);
1623
+ },
1624
+ [isLayoutCalculated, isAnimatedOnMount, animatedDetentsState]
1625
+ );
1626
+
1627
+ /**
1628
+ * Reaction to the keyboard state change.
1629
+ *
1630
+ * @alias OnKeyboardStateChange
1631
+ */
1632
+ useAnimatedReaction(
1633
+ () =>
1634
+ animatedKeyboardState.get().status + animatedKeyboardState.get().height,
1635
+ (result, _previousResult) => {
1636
+ /**
1637
+ * if keyboard state is equal to the previous state, then exit the method
1638
+ */
1639
+ if (result === _previousResult) {
1640
+ return;
1641
+ }
1642
+
1643
+ const { status, height, easing, duration, target } =
1644
+ animatedKeyboardState.get();
1645
+
1646
+ /**
1647
+ * if state is undetermined, then we early exit.
1648
+ */
1649
+ if (status === KEYBOARD_STATUS.UNDETERMINED) {
1650
+ return;
1651
+ }
1652
+
1653
+ const { status: animationStatus, source: animationSource } =
1654
+ animatedAnimationState.get();
1655
+ /**
1656
+ * if keyboard is hidden by customer gesture, then we early exit.
1657
+ */
1658
+ if (
1659
+ status === KEYBOARD_STATUS.HIDDEN &&
1660
+ animationStatus === ANIMATION_STATUS.RUNNING &&
1661
+ animationSource === ANIMATION_SOURCE.GESTURE
1662
+ ) {
1663
+ return;
1664
+ }
1665
+
1666
+ /**
1667
+ * Calculate the keyboard height in the container.
1668
+ */
1669
+ const containerOffset = animatedLayoutState.get().containerOffset;
1670
+ let heightWithinContainer =
1671
+ height === 0
1672
+ ? 0
1673
+ : $modal
1674
+ ? Math.abs(
1675
+ height - Math.abs(bottomInset - containerOffset.bottom)
1676
+ )
1677
+ : Math.abs(height - containerOffset.bottom);
1678
+
1679
+ if (__DEV__) {
1680
+ runOnJS(print)({
1681
+ component: 'BottomSheet',
1682
+ method: 'useAnimatedReaction::OnKeyboardStateChange',
1683
+ category: 'effect',
1684
+ params: {
1685
+ status,
1686
+ height,
1687
+ heightWithinContainer,
1688
+ containerOffset,
1689
+ },
1690
+ });
1691
+ }
1692
+
1693
+ /**
1694
+ * if platform is android and the input mode is resize, then exit the method
1695
+ */
1696
+ if (
1697
+ Platform.OS === 'android' &&
1698
+ android_keyboardInputMode === KEYBOARD_INPUT_MODE.adjustResize
1699
+ ) {
1700
+ heightWithinContainer = 0;
1701
+
1702
+ if (keyboardBehavior === KEYBOARD_BEHAVIOR.interactive) {
1703
+ animatedKeyboardState.set({
1704
+ target,
1705
+ status,
1706
+ height,
1707
+ easing,
1708
+ duration,
1709
+ heightWithinContainer,
1710
+ });
1711
+ return;
1712
+ }
1713
+ }
1714
+ animatedKeyboardState.set(state => ({
1715
+ ...state,
1716
+ heightWithinContainer,
1717
+ }));
1718
+
1719
+ /**
1720
+ * if user is interacting with sheet, then exit the method
1721
+ */
1722
+ const hasActiveGesture =
1723
+ animatedContentGestureState.value === State.ACTIVE ||
1724
+ animatedContentGestureState.value === State.BEGAN ||
1725
+ animatedHandleGestureState.value === State.ACTIVE ||
1726
+ animatedHandleGestureState.value === State.BEGAN;
1727
+ if (hasActiveGesture) {
1728
+ return;
1729
+ }
1730
+
1731
+ /**
1732
+ * if new keyboard state is hidden and blur behavior is none, then exit the method
1733
+ */
1734
+ if (
1735
+ status === KEYBOARD_STATUS.HIDDEN &&
1736
+ keyboardBlurBehavior === KEYBOARD_BLUR_BEHAVIOR.none
1737
+ ) {
1738
+ return;
1739
+ }
1740
+
1741
+ const animationConfigs = getKeyboardAnimationConfigs(easing, duration);
1742
+ evaluatePosition(ANIMATION_SOURCE.KEYBOARD, animationConfigs);
1743
+ },
1744
+ [
1745
+ $modal,
1746
+ bottomInset,
1747
+ keyboardBehavior,
1748
+ keyboardBlurBehavior,
1749
+ android_keyboardInputMode,
1750
+ animatedKeyboardState,
1751
+ animatedLayoutState,
1752
+ getEvaluatedPosition,
1753
+ ]
1754
+ );
1755
+
1756
+ /**
1757
+ * sets provided animated position
1758
+ */
1759
+ useAnimatedReaction(
1760
+ () => animatedPosition.value,
1761
+ _animatedPosition => {
1762
+ if (_providedAnimatedPosition) {
1763
+ _providedAnimatedPosition.value = _animatedPosition + topInset;
1764
+ }
1765
+ },
1766
+ [_providedAnimatedPosition, topInset]
1767
+ );
1768
+
1769
+ /**
1770
+ * sets provided animated index
1771
+ */
1772
+ useAnimatedReaction(
1773
+ () => animatedIndex.value,
1774
+ _animatedIndex => {
1775
+ if (_providedAnimatedIndex) {
1776
+ _providedAnimatedIndex.value = _animatedIndex;
1777
+ }
1778
+ },
1779
+ [_providedAnimatedIndex]
1780
+ );
1781
+
1782
+ /**
1783
+ * React to `index` prop to snap the sheet to the new position.
1784
+ *
1785
+ * @alias onIndexChange
1786
+ */
1787
+ useEffect(() => {
1788
+ // early exit, if animate on mount is set and it did not animate yet.
1789
+ if (animateOnMount && !isAnimatedOnMount.value) {
1790
+ return;
1791
+ }
1792
+
1793
+ handleSnapToIndex(_providedIndex);
1794
+ }, [animateOnMount, _providedIndex, isAnimatedOnMount, handleSnapToIndex]);
1795
+ //#endregion
1796
+
1797
+ // render
1798
+ return (
1799
+ <BottomSheetProvider value={externalContextVariables}>
1800
+ <BottomSheetInternalProvider value={internalContextVariables}>
1801
+ <BottomSheetGestureHandlersProvider
1802
+ gestureEventsHandlersHook={gestureEventsHandlersHook}
1803
+ >
1804
+ {BackdropComponent ? (
1805
+ <BackdropComponent
1806
+ animatedIndex={animatedIndex}
1807
+ animatedPosition={animatedPosition}
1808
+ style={StyleSheet.absoluteFillObject}
1809
+ />
1810
+ ) : null}
1811
+ <BottomSheetHostingContainer
1812
+ key="BottomSheetContainer"
1813
+ shouldCalculateHeight={!$modal}
1814
+ layoutState={animatedLayoutState}
1815
+ containerLayoutState={containerLayoutState}
1816
+ topInset={topInset}
1817
+ bottomInset={bottomInset}
1818
+ detached={detached}
1819
+ style={_providedContainerStyle}
1820
+ >
1821
+ <BottomSheetBody style={style}>
1822
+ {backgroundComponent === null ? null : (
1823
+ <BottomSheetBackgroundContainer
1824
+ key="BottomSheetBackgroundContainer"
1825
+ animatedIndex={animatedIndex}
1826
+ animatedPosition={animatedPosition}
1827
+ backgroundComponent={backgroundComponent}
1828
+ backgroundStyle={_providedBackgroundStyle}
1829
+ />
1830
+ )}
1831
+ <BottomSheetContent
1832
+ pointerEvents="box-none"
1833
+ accessible={_providedAccessible ?? undefined}
1834
+ accessibilityRole={_providedAccessibilityRole ?? undefined}
1835
+ accessibilityLabel={_providedAccessibilityLabel ?? undefined}
1836
+ keyboardBehavior={keyboardBehavior}
1837
+ detached={detached}
1838
+ >
1839
+ {children}
1840
+ {footerComponent ? (
1841
+ <BottomSheetFooterContainer
1842
+ footerComponent={footerComponent}
1843
+ />
1844
+ ) : null}
1845
+ </BottomSheetContent>
1846
+ {handleComponent !== null ? (
1847
+ <BottomSheetHandleContainer
1848
+ key="BottomSheetHandleContainer"
1849
+ animatedIndex={animatedIndex}
1850
+ animatedPosition={animatedPosition}
1851
+ enableHandlePanningGesture={enableHandlePanningGesture}
1852
+ enableOverDrag={enableOverDrag}
1853
+ enablePanDownToClose={enablePanDownToClose}
1854
+ overDragResistanceFactor={overDragResistanceFactor}
1855
+ keyboardBehavior={keyboardBehavior}
1856
+ handleComponent={handleComponent}
1857
+ handleStyle={_providedHandleStyle}
1858
+ handleIndicatorStyle={_providedHandleIndicatorStyle}
1859
+ />
1860
+ ) : null}
1861
+ </BottomSheetBody>
1862
+ {/* <BottomSheetDebugView
1863
+ values={{
1864
+ index: animatedIndex,
1865
+ position: animatedPosition,
1866
+ sheetStatus: animatedSheetState,
1867
+ scrollableStatus: animatedScrollableStatus,
1868
+ layoutState: animatedLayoutState,
1869
+ detentsState: animatedDetentsState,
1870
+ sheetHeight: animatedSheetHeight,
1871
+ isLayoutCalculated,
1872
+ }}
1873
+ /> */}
1874
+ </BottomSheetHostingContainer>
1875
+ </BottomSheetGestureHandlersProvider>
1876
+ </BottomSheetInternalProvider>
1877
+ </BottomSheetProvider>
1878
+ );
1879
+ }
1880
+ );
1881
+
1882
+ const BottomSheet = memo(BottomSheetComponent);
1883
+ BottomSheet.displayName = 'BottomSheet';
1884
+
1885
+ export default BottomSheet;