@swmansion/react-native-bottom-sheet 0.5.1 → 0.6.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 (39) hide show
  1. package/lib/module/BottomSheetBase.js +10 -13
  2. package/lib/module/BottomSheetBase.js.map +1 -1
  3. package/lib/module/BottomSheetContext.js.map +1 -1
  4. package/lib/module/BottomSheetFlatList.js +3 -42
  5. package/lib/module/BottomSheetFlatList.js.map +1 -1
  6. package/lib/module/BottomSheetScrollView.js +3 -43
  7. package/lib/module/BottomSheetScrollView.js.map +1 -1
  8. package/lib/module/bottomSheetScrollable.js +35 -0
  9. package/lib/module/bottomSheetScrollable.js.map +1 -0
  10. package/lib/module/index.js +1 -1
  11. package/lib/module/index.js.map +1 -1
  12. package/lib/module/useBottomSheetPanGesture.js +50 -30
  13. package/lib/module/useBottomSheetPanGesture.js.map +1 -1
  14. package/lib/module/useBottomSheetScrollable.js +14 -17
  15. package/lib/module/useBottomSheetScrollable.js.map +1 -1
  16. package/lib/typescript/src/BottomSheetBase.d.ts.map +1 -1
  17. package/lib/typescript/src/BottomSheetContext.d.ts +6 -4
  18. package/lib/typescript/src/BottomSheetContext.d.ts.map +1 -1
  19. package/lib/typescript/src/BottomSheetFlatList.d.ts +7 -12
  20. package/lib/typescript/src/BottomSheetFlatList.d.ts.map +1 -1
  21. package/lib/typescript/src/BottomSheetScrollView.d.ts +7 -14
  22. package/lib/typescript/src/BottomSheetScrollView.d.ts.map +1 -1
  23. package/lib/typescript/src/bottomSheetScrollable.d.ts +9 -0
  24. package/lib/typescript/src/bottomSheetScrollable.d.ts.map +1 -0
  25. package/lib/typescript/src/index.d.ts +3 -3
  26. package/lib/typescript/src/index.d.ts.map +1 -1
  27. package/lib/typescript/src/useBottomSheetPanGesture.d.ts +4 -6
  28. package/lib/typescript/src/useBottomSheetPanGesture.d.ts.map +1 -1
  29. package/lib/typescript/src/useBottomSheetScrollable.d.ts +1 -1
  30. package/lib/typescript/src/useBottomSheetScrollable.d.ts.map +1 -1
  31. package/package.json +2 -1
  32. package/src/BottomSheetBase.tsx +16 -14
  33. package/src/BottomSheetContext.tsx +7 -4
  34. package/src/BottomSheetFlatList.tsx +16 -58
  35. package/src/BottomSheetScrollView.tsx +17 -61
  36. package/src/bottomSheetScrollable.tsx +42 -0
  37. package/src/index.tsx +3 -9
  38. package/src/useBottomSheetPanGesture.ts +61 -52
  39. package/src/useBottomSheetScrollable.ts +16 -23
@@ -4,11 +4,11 @@ import { scheduleOnRN } from 'react-native-worklets';
4
4
  import {
5
5
  measure,
6
6
  scrollTo,
7
- type AnimatedRef,
8
7
  type SharedValue,
9
8
  useSharedValue,
10
9
  } from 'react-native-reanimated';
11
10
 
11
+ import type { ScrollableEntry } from './BottomSheetContext';
12
12
  import { findSnapTarget } from './bottomSheetUtils';
13
13
 
14
14
  interface BottomSheetPanGestureParams {
@@ -18,11 +18,8 @@ interface BottomSheetPanGestureParams {
18
18
  detentsValue: SharedValue<number[]>;
19
19
  isDraggableValue: SharedValue<boolean[]>;
20
20
  currentIndex: SharedValue<number>;
21
- scrollOffset: SharedValue<number>;
22
- hasScrollable: SharedValue<boolean>;
23
- isScrollableGestureActive: SharedValue<boolean>;
21
+ scrollableEntries: ScrollableEntry[];
24
22
  isScrollableLocked: SharedValue<boolean>;
25
- scrollableRef: AnimatedRef<any>;
26
23
  handleIndexChange: (nextIndex: number) => void;
27
24
  animateToIndex: (targetIndex: number, velocity?: number) => void;
28
25
  }
@@ -34,11 +31,8 @@ export const useBottomSheetPanGesture = ({
34
31
  detentsValue,
35
32
  isDraggableValue,
36
33
  currentIndex,
37
- scrollOffset,
38
- hasScrollable,
39
- isScrollableGestureActive,
34
+ scrollableEntries,
40
35
  isScrollableLocked,
41
- scrollableRef,
42
36
  handleIndexChange,
43
37
  animateToIndex,
44
38
  }: BottomSheetPanGestureParams): PanGesture => {
@@ -48,7 +42,7 @@ export const useBottomSheetPanGesture = ({
48
42
  const panStartY = useSharedValue(0);
49
43
  const panActivated = useSharedValue(false);
50
44
  const dragStartTranslateY = useSharedValue(0);
51
- const isTouchWithinScrollable = useSharedValue(false);
45
+ const activeScrollableIndex = useSharedValue(-1);
52
46
 
53
47
  return Gesture.Pan()
54
48
  .manualActivation(true)
@@ -58,21 +52,26 @@ export const useBottomSheetPanGesture = ({
58
52
  isDraggingSheet.set(false);
59
53
  isDraggingFromScrollable.set(false);
60
54
  isScrollableLocked.set(false);
61
- isTouchWithinScrollable.set(false);
55
+ activeScrollableIndex.set(-1);
62
56
  const touch = event.changedTouches[0] ?? event.allTouches[0];
63
57
  if (touch !== undefined) {
64
58
  panStartX.set(touch.absoluteX);
65
59
  panStartY.set(touch.absoluteY);
66
- if (hasScrollable.value) {
67
- const layout = measure(scrollableRef);
68
- if (layout !== null) {
69
- const withinX =
70
- touch.absoluteX >= layout.pageX &&
71
- touch.absoluteX <= layout.pageX + layout.width;
72
- const withinY =
73
- touch.absoluteY >= layout.pageY &&
74
- touch.absoluteY <= layout.pageY + layout.height;
75
- isTouchWithinScrollable.set(withinX && withinY);
60
+ const entries = scrollableEntries;
61
+ for (let i = 0; i < entries.length; i++) {
62
+ const entry = entries[i];
63
+ if (entry === undefined) continue;
64
+ const layout = measure(entry.ref);
65
+ if (layout === null) continue;
66
+ const withinX =
67
+ touch.absoluteX >= layout.pageX &&
68
+ touch.absoluteX <= layout.pageX + layout.width;
69
+ const withinY =
70
+ touch.absoluteY >= layout.pageY &&
71
+ touch.absoluteY <= layout.pageY + layout.height;
72
+ if (withinX && withinY) {
73
+ activeScrollableIndex.set(i);
74
+ break;
76
75
  }
77
76
  }
78
77
  }
@@ -84,12 +83,16 @@ export const useBottomSheetPanGesture = ({
84
83
  if (!touch) return;
85
84
  const deltaX = touch.absoluteX - panStartX.value;
86
85
  const deltaY = touch.absoluteY - panStartY.value;
87
- if (
88
- hasScrollable.value &&
89
- scrollOffset.value > 0 &&
90
- isTouchWithinScrollable.value
91
- ) {
92
- return;
86
+ const activeIdx = activeScrollableIndex.value;
87
+ if (activeIdx !== -1) {
88
+ const active = scrollableEntries[activeIdx];
89
+ if (
90
+ active !== undefined &&
91
+ active.scrollOffset.value > 0 &&
92
+ translateY.value <= 0
93
+ ) {
94
+ return;
95
+ }
93
96
  }
94
97
  if (Math.abs(deltaX) > Math.abs(deltaY)) {
95
98
  stateManager.fail();
@@ -112,51 +115,57 @@ export const useBottomSheetPanGesture = ({
112
115
  })
113
116
  .onUpdate((event) => {
114
117
  'worklet';
118
+ const activeIdx = activeScrollableIndex.value;
119
+ const hasActive = activeIdx !== -1;
120
+ const active = hasActive ? scrollableEntries[activeIdx] : undefined;
121
+ const activeOffset = active !== undefined ? active.scrollOffset.value : 0;
122
+
115
123
  if (isDraggingSheet.value) {
116
- if (isDraggingFromScrollable.value) {
117
- scrollTo(scrollableRef, 0, 0, false);
124
+ if (isDraggingFromScrollable.value && active !== undefined) {
125
+ scrollTo(active.ref, 0, 0, false);
118
126
  }
119
127
  } else {
120
128
  const isDraggingDown = event.translationY > 0;
121
129
  const canStartDrag =
122
- !hasScrollable.value ||
123
- scrollOffset.value <= 0 ||
124
- !isTouchWithinScrollable.value;
130
+ !hasActive || activeOffset <= 0 || translateY.value > 0;
125
131
  if (!canStartDrag || (!isDraggingDown && translateY.value <= 0)) {
126
132
  return;
127
133
  }
128
134
  const isScrollableActive =
129
- hasScrollable.value && isScrollableGestureActive.value;
135
+ hasActive && active !== undefined && active.isGestureActive.value;
130
136
  isDraggingSheet.set(true);
131
- isDraggingFromScrollable.set(
132
- isScrollableActive && isTouchWithinScrollable.value
133
- );
137
+ isDraggingFromScrollable.set(isScrollableActive && activeOffset <= 0);
134
138
  dragStartTranslateY.set(translateY.value - event.translationY);
135
- isScrollableLocked.set(hasScrollable.value);
136
- if (isTouchWithinScrollable.value && hasScrollable.value) {
137
- scrollTo(scrollableRef, 0, 0, false);
139
+ isScrollableLocked.set(hasActive);
140
+ if (hasActive && active !== undefined && activeOffset <= 0) {
141
+ scrollTo(active.ref, 0, 0, false);
138
142
  }
139
143
  }
140
144
  const rawTranslate = dragStartTranslateY.value + event.translationY;
145
+ const resolvedDetents = detentsValue.value;
146
+ const draggable = isDraggableValue.value;
147
+ let maxDraggableTranslateY = sheetHeight.value;
148
+ let foundDraggable = false;
149
+ for (let i = 0; i < resolvedDetents.length; i++) {
150
+ if (!(draggable[i] ?? true)) continue;
151
+ const t = sheetHeight.value - (resolvedDetents[i] ?? 0);
152
+ if (!foundDraggable || t > maxDraggableTranslateY) {
153
+ maxDraggableTranslateY = t;
154
+ foundDraggable = true;
155
+ }
156
+ }
141
157
  const nextTranslate = Math.min(
142
158
  Math.max(rawTranslate, 0),
143
- sheetHeight.value
159
+ maxDraggableTranslateY
144
160
  );
145
161
  translateY.set(nextTranslate);
146
- if (
147
- isDraggingSheet.value &&
148
- rawTranslate < 0 &&
149
- isTouchWithinScrollable.value &&
150
- hasScrollable.value
151
- ) {
162
+ if (isDraggingSheet.value && rawTranslate < 0 && hasActive) {
152
163
  isDraggingSheet.set(false);
153
164
  isScrollableLocked.set(false);
154
- const resolvedDetentValues = detentsValue.value;
155
- const draggable = isDraggableValue.value;
156
165
  let targetSnapIndex = -1;
157
166
  let targetSnapValue = -1;
158
- for (let i = resolvedDetentValues.length - 1; i >= 0; i--) {
159
- const detentValue = resolvedDetentValues[i];
167
+ for (let i = resolvedDetents.length - 1; i >= 0; i--) {
168
+ const detentValue = resolvedDetents[i];
160
169
  if (
161
170
  detentValue !== undefined &&
162
171
  (draggable[i] ?? true) &&
@@ -168,8 +177,8 @@ export const useBottomSheetPanGesture = ({
168
177
  }
169
178
  if (targetSnapIndex === -1) {
170
179
  const maxSnap = sheetHeight.value;
171
- for (let i = resolvedDetentValues.length - 1; i >= 0; i--) {
172
- if (resolvedDetentValues[i] === maxSnap) {
180
+ for (let i = resolvedDetents.length - 1; i >= 0; i--) {
181
+ if (resolvedDetents[i] === maxSnap) {
173
182
  targetSnapIndex = i;
174
183
  break;
175
184
  }
@@ -5,7 +5,9 @@ import { isWorkletFunction, scheduleOnRN } from 'react-native-worklets';
5
5
  import {
6
6
  type SharedValue,
7
7
  useAnimatedProps,
8
+ useAnimatedRef,
8
9
  useAnimatedScrollHandler,
10
+ useSharedValue,
9
11
  } from 'react-native-reanimated';
10
12
 
11
13
  import { useBottomSheetContext } from './BottomSheetContext';
@@ -16,14 +18,11 @@ export const useBottomSheetScrollable = (
16
18
  baseScrollEnabled: boolean | SharedValue<boolean | undefined> = true,
17
19
  onScroll?: ScrollHandler
18
20
  ) => {
19
- const {
20
- scrollOffset,
21
- scrollableRef,
22
- hasScrollable,
23
- isScrollableGestureActive,
24
- isScrollableLocked,
25
- panGesture,
26
- } = useBottomSheetContext();
21
+ const { isScrollableLocked, registerScrollable, panGesture } =
22
+ useBottomSheetContext();
23
+ const scrollableRef = useAnimatedRef();
24
+ const scrollOffset = useSharedValue(0);
25
+ const isGestureActive = useSharedValue(false);
27
26
  const isWorkletScrollHandler =
28
27
  onScroll !== undefined ? isWorkletFunction(onScroll) : false;
29
28
  const scrollHandler = useAnimatedScrollHandler({
@@ -42,11 +41,11 @@ export const useBottomSheetScrollable = (
42
41
  .simultaneousWithExternalGesture(panGesture)
43
42
  .onStart(() => {
44
43
  'worklet';
45
- isScrollableGestureActive.set(true);
44
+ isGestureActive.set(true);
46
45
  })
47
46
  .onFinalize(() => {
48
47
  'worklet';
49
- isScrollableGestureActive.set(false);
48
+ isGestureActive.set(false);
50
49
  });
51
50
  const animatedProps = useAnimatedProps(() => {
52
51
  const resolvedScrollEnabled =
@@ -58,18 +57,12 @@ export const useBottomSheetScrollable = (
58
57
  };
59
58
  });
60
59
  useEffect(() => {
61
- if (__DEV__ && hasScrollable.value) {
62
- console.warn(
63
- 'Multiple scrollables within a single bottom sheet. Only one `BottomSheetScrollView` ' +
64
- 'or `BottomSheetFlatList` is supported per bottom sheet.'
65
- );
66
- }
67
- hasScrollable.set(true);
68
- return () => {
69
- hasScrollable.set(false);
70
- isScrollableGestureActive.set(false);
71
- isScrollableLocked.set(false);
72
- };
73
- }, [hasScrollable, isScrollableGestureActive, isScrollableLocked]);
60
+ const unregister = registerScrollable({
61
+ ref: scrollableRef,
62
+ scrollOffset,
63
+ isGestureActive,
64
+ });
65
+ return unregister;
66
+ }, [registerScrollable, scrollableRef, scrollOffset, isGestureActive]);
74
67
  return { scrollHandler, scrollableRef, nativeGesture, animatedProps };
75
68
  };