@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,167 @@
1
+ import { useCallback } from 'react';
2
+ import { State } from 'react-native-gesture-handler';
3
+ import { scrollTo } from 'react-native-reanimated';
4
+ import { ANIMATION_STATUS, SCROLLABLE_STATUS, SHEET_STATE } from '../constants';
5
+ import type {
6
+ ScrollEventHandlerCallbackType,
7
+ ScrollEventsHandlersHookType,
8
+ } from '../types';
9
+ import { useBottomSheetInternal } from './useBottomSheetInternal';
10
+
11
+ export type ScrollEventContextType = {
12
+ initialContentOffsetY: number;
13
+ shouldLockInitialPosition: boolean;
14
+ };
15
+
16
+ export const useScrollEventsHandlersDefault: ScrollEventsHandlersHookType = (
17
+ scrollableRef,
18
+ scrollableContentOffsetY
19
+ ) => {
20
+ // hooks
21
+ const {
22
+ animatedSheetState,
23
+ animatedScrollableState,
24
+ animatedScrollableStatus,
25
+ animatedAnimationState,
26
+ animatedHandleGestureState,
27
+ } = useBottomSheetInternal();
28
+
29
+ //#region callbacks
30
+ const handleOnScroll: ScrollEventHandlerCallbackType<ScrollEventContextType> =
31
+ useCallback(
32
+ ({ contentOffset: { y } }, context) => {
33
+ 'worklet';
34
+ /**
35
+ * if sheet position is extended or fill parent, then we reset
36
+ * `shouldLockInitialPosition` value to false.
37
+ */
38
+ if (
39
+ animatedSheetState.value === SHEET_STATE.EXTENDED ||
40
+ animatedSheetState.value === SHEET_STATE.FILL_PARENT
41
+ ) {
42
+ context.shouldLockInitialPosition = false;
43
+ }
44
+
45
+ /**
46
+ * if handle gesture state is active, then we capture the offset y position
47
+ * and lock the scrollable with it.
48
+ */
49
+ if (animatedHandleGestureState.value === State.ACTIVE) {
50
+ context.shouldLockInitialPosition = true;
51
+ context.initialContentOffsetY = y;
52
+ }
53
+
54
+ if (animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED) {
55
+ const lockPosition = context.shouldLockInitialPosition
56
+ ? (context.initialContentOffsetY ?? 0)
57
+ : 0;
58
+ // @ts-ignore
59
+ scrollTo(scrollableRef, 0, lockPosition, false);
60
+ scrollableContentOffsetY.value = lockPosition;
61
+ return;
62
+ }
63
+ },
64
+ [
65
+ scrollableRef,
66
+ scrollableContentOffsetY,
67
+ animatedScrollableStatus,
68
+ animatedSheetState,
69
+ animatedHandleGestureState,
70
+ ]
71
+ );
72
+ const handleOnBeginDrag: ScrollEventHandlerCallbackType<ScrollEventContextType> =
73
+ useCallback(
74
+ ({ contentOffset: { y } }, context) => {
75
+ 'worklet';
76
+ scrollableContentOffsetY.value = y;
77
+ context.initialContentOffsetY = y;
78
+ animatedScrollableState.set(state => ({
79
+ ...state,
80
+ contentOffsetY: y,
81
+ }));
82
+
83
+ /**
84
+ * if sheet position not extended or fill parent and the scrollable position
85
+ * not at the top, then we should lock the initial scrollable position.
86
+ */
87
+ if (
88
+ animatedSheetState.value !== SHEET_STATE.EXTENDED &&
89
+ animatedSheetState.value !== SHEET_STATE.FILL_PARENT &&
90
+ y > 0
91
+ ) {
92
+ context.shouldLockInitialPosition = true;
93
+ } else {
94
+ context.shouldLockInitialPosition = false;
95
+ }
96
+ },
97
+ [scrollableContentOffsetY, animatedSheetState, animatedScrollableState]
98
+ );
99
+ const handleOnEndDrag: ScrollEventHandlerCallbackType<ScrollEventContextType> =
100
+ useCallback(
101
+ ({ contentOffset: { y } }, context) => {
102
+ 'worklet';
103
+ if (animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED) {
104
+ const lockPosition = context.shouldLockInitialPosition
105
+ ? (context.initialContentOffsetY ?? 0)
106
+ : 0;
107
+ // @ts-ignore
108
+ scrollTo(scrollableRef, 0, lockPosition, false);
109
+ scrollableContentOffsetY.value = lockPosition;
110
+ return;
111
+ }
112
+
113
+ if (animatedAnimationState.get().status !== ANIMATION_STATUS.RUNNING) {
114
+ scrollableContentOffsetY.value = y;
115
+ animatedScrollableState.set(state => ({
116
+ ...state,
117
+ contentOffsetY: y,
118
+ }));
119
+ }
120
+ },
121
+ [
122
+ scrollableRef,
123
+ scrollableContentOffsetY,
124
+ animatedAnimationState,
125
+ animatedScrollableStatus,
126
+ animatedScrollableState,
127
+ ]
128
+ );
129
+ const handleOnMomentumEnd: ScrollEventHandlerCallbackType<ScrollEventContextType> =
130
+ useCallback(
131
+ ({ contentOffset: { y } }, context) => {
132
+ 'worklet';
133
+ if (animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED) {
134
+ const lockPosition = context.shouldLockInitialPosition
135
+ ? (context.initialContentOffsetY ?? 0)
136
+ : 0;
137
+ // @ts-ignore
138
+ scrollTo(scrollableRef, 0, lockPosition, false);
139
+ scrollableContentOffsetY.value = 0;
140
+ return;
141
+ }
142
+
143
+ if (animatedAnimationState.get().status !== ANIMATION_STATUS.RUNNING) {
144
+ scrollableContentOffsetY.value = y;
145
+ animatedScrollableState.set(state => ({
146
+ ...state,
147
+ contentOffsetY: y,
148
+ }));
149
+ }
150
+ },
151
+ [
152
+ scrollableContentOffsetY,
153
+ scrollableRef,
154
+ animatedAnimationState,
155
+ animatedScrollableStatus,
156
+ animatedScrollableState,
157
+ ]
158
+ );
159
+ //#endregion
160
+
161
+ return {
162
+ handleOnScroll,
163
+ handleOnBeginDrag,
164
+ handleOnEndDrag,
165
+ handleOnMomentumEnd,
166
+ };
167
+ };
@@ -0,0 +1,72 @@
1
+ import {
2
+ runOnJS,
3
+ useAnimatedRef,
4
+ useAnimatedScrollHandler,
5
+ useSharedValue,
6
+ } from 'react-native-reanimated';
7
+ import type { Scrollable, ScrollableEvent } from '../types';
8
+ import { workletNoop as noop } from '../utilities';
9
+ import { useScrollEventsHandlersDefault } from './useScrollEventsHandlersDefault';
10
+
11
+ export const useScrollHandler = (
12
+ useScrollEventsHandlers = useScrollEventsHandlersDefault,
13
+ onScroll?: ScrollableEvent,
14
+ onScrollBeginDrag?: ScrollableEvent,
15
+ onScrollEndDrag?: ScrollableEvent
16
+ ) => {
17
+ // refs
18
+ const scrollableRef = useAnimatedRef<Scrollable>();
19
+
20
+ // variables
21
+ const scrollableContentOffsetY = useSharedValue<number>(0);
22
+
23
+ // hooks
24
+ const {
25
+ handleOnScroll = noop,
26
+ handleOnBeginDrag = noop,
27
+ handleOnEndDrag = noop,
28
+ handleOnMomentumEnd = noop,
29
+ handleOnMomentumBegin = noop,
30
+ } = useScrollEventsHandlers(scrollableRef, scrollableContentOffsetY);
31
+
32
+ // callbacks
33
+ const scrollHandler = useAnimatedScrollHandler(
34
+ {
35
+ onScroll: (event, context) => {
36
+ handleOnScroll(event, context);
37
+
38
+ if (onScroll) {
39
+ runOnJS(onScroll)({ nativeEvent: event });
40
+ }
41
+ },
42
+ onBeginDrag: (event, context) => {
43
+ handleOnBeginDrag(event, context);
44
+
45
+ if (onScrollBeginDrag) {
46
+ runOnJS(onScrollBeginDrag)({ nativeEvent: event });
47
+ }
48
+ },
49
+ onEndDrag: (event, context) => {
50
+ handleOnEndDrag(event, context);
51
+
52
+ if (onScrollEndDrag) {
53
+ runOnJS(onScrollEndDrag)({ nativeEvent: event });
54
+ }
55
+ },
56
+ onMomentumBegin: handleOnMomentumBegin,
57
+ onMomentumEnd: handleOnMomentumEnd,
58
+ },
59
+ [
60
+ handleOnScroll,
61
+ handleOnBeginDrag,
62
+ handleOnEndDrag,
63
+ handleOnMomentumBegin,
64
+ handleOnMomentumEnd,
65
+ onScroll,
66
+ onScrollBeginDrag,
67
+ onScrollEndDrag,
68
+ ]
69
+ );
70
+
71
+ return { scrollHandler, scrollableRef, scrollableContentOffsetY };
72
+ };
@@ -0,0 +1,181 @@
1
+ import { type TouchEvent, useEffect, useRef } from 'react';
2
+ import { useSharedValue } from 'react-native-reanimated';
3
+ import { ANIMATION_STATUS, SCROLLABLE_STATUS } from '../constants';
4
+ import type { Scrollable, ScrollableEvent } from '../types';
5
+ import { findNodeHandle } from '../utilities/findNodeHandle.web';
6
+ import { useBottomSheetInternal } from './useBottomSheetInternal';
7
+
8
+ export type ScrollEventContextType = {
9
+ initialContentOffsetY: number;
10
+ shouldLockInitialPosition: boolean;
11
+ };
12
+
13
+ export const useScrollHandler = (_: never, onScroll?: ScrollableEvent) => {
14
+ //#region refs
15
+ const scrollableRef = useRef<Scrollable>(null);
16
+ //#endregion
17
+
18
+ //#region variables
19
+ const scrollableContentOffsetY = useSharedValue<number>(0);
20
+ //#endregion
21
+
22
+ //#region hooks
23
+ const {
24
+ animatedScrollableState,
25
+ animatedScrollableStatus,
26
+ animatedAnimationState,
27
+ } = useBottomSheetInternal();
28
+ //#endregion
29
+
30
+ //#region effects
31
+ useEffect(() => {
32
+ // biome-ignore lint: to be addressed!
33
+ const element = findNodeHandle(scrollableRef.current) as any;
34
+ let scrollOffset = 0;
35
+ let supportsPassive = false;
36
+ let maybePrevent = false;
37
+ let lastTouchY = 0;
38
+
39
+ let initialContentOffsetY = 0;
40
+ const shouldLockInitialPosition = false;
41
+
42
+ function handleOnTouchStart(event: TouchEvent) {
43
+ if (event.touches.length !== 1) {
44
+ return;
45
+ }
46
+
47
+ initialContentOffsetY = element.scrollTop;
48
+ lastTouchY = event.touches[0].clientY;
49
+ maybePrevent = scrollOffset <= 0;
50
+ }
51
+
52
+ function handleOnTouchMove(event: TouchEvent) {
53
+ if (
54
+ animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED &&
55
+ event.cancelable
56
+ ) {
57
+ return event.preventDefault();
58
+ }
59
+
60
+ if (maybePrevent) {
61
+ maybePrevent = false;
62
+
63
+ const touchY = event.touches[0].clientY;
64
+ const touchYDelta = touchY - lastTouchY;
65
+
66
+ if (touchYDelta > 0 && event.cancelable) {
67
+ return event.preventDefault();
68
+ }
69
+ }
70
+
71
+ return true;
72
+ }
73
+
74
+ function handleOnTouchEnd() {
75
+ if (animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED) {
76
+ const lockPosition = shouldLockInitialPosition
77
+ ? (initialContentOffsetY ?? 0)
78
+ : 0;
79
+ element.scroll({
80
+ top: 0,
81
+ left: 0,
82
+ behavior: 'instant',
83
+ });
84
+ scrollableContentOffsetY.value = lockPosition;
85
+ return;
86
+ }
87
+ }
88
+
89
+ function handleOnScroll(event: TouchEvent) {
90
+ scrollOffset = element.scrollTop;
91
+
92
+ if (animatedAnimationState.get().status !== ANIMATION_STATUS.RUNNING) {
93
+ const contentOffsetY = Math.max(0, scrollOffset);
94
+ scrollableContentOffsetY.value = contentOffsetY;
95
+ animatedScrollableState.set(state => ({
96
+ ...state,
97
+ contentOffsetY,
98
+ }));
99
+ }
100
+
101
+ if (scrollOffset <= 0 && event.cancelable) {
102
+ event.preventDefault();
103
+ event.stopPropagation();
104
+ return false;
105
+ }
106
+ return true;
107
+ }
108
+
109
+ try {
110
+ // @ts-ignore
111
+ window.addEventListener('test', null, {
112
+ // @ts-ignore
113
+ // biome-ignore lint: to be addressed
114
+ get passive() {
115
+ supportsPassive = true;
116
+ },
117
+ });
118
+ } catch (_e) {}
119
+
120
+ element.addEventListener(
121
+ 'touchstart',
122
+ handleOnTouchStart,
123
+ supportsPassive
124
+ ? {
125
+ passive: true,
126
+ }
127
+ : false
128
+ );
129
+
130
+ element.addEventListener(
131
+ 'touchmove',
132
+ handleOnTouchMove,
133
+ supportsPassive
134
+ ? {
135
+ passive: false,
136
+ }
137
+ : false
138
+ );
139
+
140
+ element.addEventListener(
141
+ 'touchend',
142
+ handleOnTouchEnd,
143
+ supportsPassive
144
+ ? {
145
+ passive: false,
146
+ }
147
+ : false
148
+ );
149
+
150
+ element.addEventListener(
151
+ 'scroll',
152
+ handleOnScroll,
153
+ supportsPassive
154
+ ? {
155
+ passive: false,
156
+ }
157
+ : false
158
+ );
159
+
160
+ return () => {
161
+ // @ts-ignore
162
+ window.removeEventListener('test', null);
163
+ element.removeEventListener('touchstart', handleOnTouchStart);
164
+ element.removeEventListener('touchmove', handleOnTouchMove);
165
+ element.removeEventListener('touchend', handleOnTouchEnd);
166
+ element.removeEventListener('scroll', handleOnScroll);
167
+ };
168
+ }, [
169
+ animatedAnimationState,
170
+ animatedScrollableState,
171
+ animatedScrollableStatus,
172
+ scrollableContentOffsetY,
173
+ ]);
174
+ //#endregion
175
+
176
+ return {
177
+ scrollHandler: onScroll,
178
+ scrollableRef,
179
+ scrollableContentOffsetY,
180
+ };
181
+ };
@@ -0,0 +1,131 @@
1
+ import { type RefObject, useCallback, useRef } from 'react';
2
+ import type { NodeHandle } from 'react-native';
3
+ import {
4
+ type SharedValue,
5
+ useDerivedValue,
6
+ useSharedValue,
7
+ } from 'react-native-reanimated';
8
+ import {
9
+ ANIMATION_STATUS,
10
+ KEYBOARD_STATUS,
11
+ SCROLLABLE_STATUS,
12
+ SCROLLABLE_TYPE,
13
+ SHEET_STATE,
14
+ } from '../constants';
15
+ import type {
16
+ AnimationState,
17
+ KeyboardState,
18
+ Scrollable,
19
+ ScrollableRef,
20
+ ScrollableState,
21
+ } from '../types';
22
+ import { findNodeHandle } from '../utilities';
23
+
24
+ export const useScrollable = (
25
+ enableContentPanningGesture: boolean,
26
+ animatedSheetState: SharedValue<SHEET_STATE>,
27
+ animatedKeyboardState: SharedValue<KeyboardState>,
28
+ animatedAnimationState: SharedValue<AnimationState>
29
+ ) => {
30
+ //#region refs
31
+ const scrollableRef = useRef<ScrollableRef>(null);
32
+ const previousScrollableRef = useRef<ScrollableRef>(null);
33
+ //#endregion
34
+
35
+ //#region variables
36
+ const state = useSharedValue<ScrollableState>({
37
+ type: SCROLLABLE_TYPE.UNDETERMINED,
38
+ contentOffsetY: 0,
39
+ refreshable: false,
40
+ });
41
+ const status = useDerivedValue<SCROLLABLE_STATUS>(() => {
42
+ /**
43
+ * if user had disabled content panning gesture, then we unlock
44
+ * the scrollable state.
45
+ */
46
+ if (!enableContentPanningGesture) {
47
+ return SCROLLABLE_STATUS.UNLOCKED;
48
+ }
49
+
50
+ /**
51
+ * if sheet state is fill parent, then unlock scrolling
52
+ */
53
+ if (animatedSheetState.value === SHEET_STATE.FILL_PARENT) {
54
+ return SCROLLABLE_STATUS.UNLOCKED;
55
+ }
56
+
57
+ /**
58
+ * if sheet state is extended, then unlock scrolling
59
+ */
60
+ if (animatedSheetState.value === SHEET_STATE.EXTENDED) {
61
+ return SCROLLABLE_STATUS.UNLOCKED;
62
+ }
63
+
64
+ /**
65
+ * if keyboard is shown and sheet is animating
66
+ * then we do not lock the scrolling to not lose
67
+ * current scrollable scroll position.
68
+ */
69
+ if (
70
+ animatedKeyboardState.get().status === KEYBOARD_STATUS.SHOWN &&
71
+ animatedAnimationState.get().status === ANIMATION_STATUS.RUNNING
72
+ ) {
73
+ return SCROLLABLE_STATUS.UNLOCKED;
74
+ }
75
+
76
+ return SCROLLABLE_STATUS.LOCKED;
77
+ }, [
78
+ enableContentPanningGesture,
79
+ animatedSheetState,
80
+ animatedKeyboardState,
81
+ animatedAnimationState,
82
+ state,
83
+ ]);
84
+ //#endregion
85
+
86
+ //#region callbacks
87
+ const setScrollableRef = useCallback((ref: ScrollableRef) => {
88
+ // get current node handle id
89
+ const currentRefId = scrollableRef.current?.id ?? null;
90
+
91
+ if (currentRefId !== ref.id) {
92
+ if (scrollableRef.current) {
93
+ // @ts-ignore
94
+ previousScrollableRef.current = scrollableRef.current;
95
+ }
96
+ // @ts-ignore
97
+ scrollableRef.current = ref;
98
+ }
99
+ }, []);
100
+
101
+ const removeScrollableRef = useCallback((ref: RefObject<Scrollable>) => {
102
+ // find node handle id
103
+ let id: NodeHandle | null;
104
+ try {
105
+ id = findNodeHandle(ref.current);
106
+ } catch {
107
+ return;
108
+ }
109
+
110
+ // get current node handle id
111
+ const currentRefId = scrollableRef.current?.id ?? null;
112
+
113
+ /**
114
+ * @DEV
115
+ * when the incoming node is actually the current node, we reset
116
+ * the current scrollable ref to the previous one.
117
+ */
118
+ if (id === currentRefId) {
119
+ // @ts-ignore
120
+ scrollableRef.current = previousScrollableRef.current;
121
+ }
122
+ }, []);
123
+ //#endregion
124
+
125
+ return {
126
+ state,
127
+ status,
128
+ setScrollableRef,
129
+ removeScrollableRef,
130
+ };
131
+ };
@@ -0,0 +1,56 @@
1
+ import type React from 'react';
2
+ import { useCallback, useEffect } from 'react';
3
+ import type { SharedValue } from 'react-native-reanimated';
4
+ import type { SCROLLABLE_TYPE } from '../constants';
5
+ import type { Scrollable } from '../types';
6
+ import { findNodeHandle } from '../utilities';
7
+ import { useBottomSheetInternal } from './useBottomSheetInternal';
8
+
9
+ export const useScrollableSetter = (
10
+ ref: React.RefObject<Scrollable>,
11
+ type: SCROLLABLE_TYPE,
12
+ contentOffsetY: SharedValue<number>,
13
+ refreshable: boolean,
14
+ useFocusHook = useEffect
15
+ ) => {
16
+ // hooks
17
+ const { animatedScrollableState, setScrollableRef, removeScrollableRef } =
18
+ useBottomSheetInternal();
19
+
20
+ // callbacks
21
+ const handleSettingScrollable = useCallback(() => {
22
+ // set current content offset
23
+ animatedScrollableState.set(state => ({
24
+ ...state,
25
+ contentOffsetY: contentOffsetY.value,
26
+ type,
27
+ refreshable,
28
+ }));
29
+
30
+ // set current scrollable ref
31
+ const id = findNodeHandle(ref.current);
32
+ if (id) {
33
+ setScrollableRef({
34
+ id: id,
35
+ node: ref,
36
+ });
37
+ } else {
38
+ console.warn(`Couldn't find the scrollable node handle id!`);
39
+ }
40
+
41
+ return () => {
42
+ removeScrollableRef(ref);
43
+ };
44
+ }, [
45
+ ref,
46
+ type,
47
+ refreshable,
48
+ contentOffsetY,
49
+ animatedScrollableState,
50
+ setScrollableRef,
51
+ removeScrollableRef,
52
+ ]);
53
+
54
+ // effects
55
+ useFocusHook(handleSettingScrollable);
56
+ };
@@ -0,0 +1,26 @@
1
+ import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
2
+
3
+ type Callback<T extends unknown[], R> = (...args: T) => R;
4
+
5
+ /**
6
+ * Provide a stable version of useCallback.
7
+ */
8
+ export function useStableCallback<T extends unknown[], R>(
9
+ callback: Callback<T, R>
10
+ ) {
11
+ const callbackRef = useRef<Callback<T, R>>();
12
+
13
+ useLayoutEffect(() => {
14
+ callbackRef.current = callback;
15
+ });
16
+
17
+ useEffect(() => {
18
+ return () => {
19
+ callbackRef.current = undefined;
20
+ };
21
+ }, []);
22
+
23
+ return useCallback<Callback<T, R | undefined>>((...args) => {
24
+ return callbackRef.current?.(...args);
25
+ }, []);
26
+ }