stream-chat-react-native-core 9.0.0-beta.17 → 9.0.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/components/UIComponents/BottomSheetModal.js +120 -125
- package/lib/commonjs/components/UIComponents/BottomSheetModal.js.map +1 -1
- package/lib/commonjs/components/UIComponents/PortalWhileClosingView.js +4 -3
- package/lib/commonjs/components/UIComponents/PortalWhileClosingView.js.map +1 -1
- package/lib/commonjs/contexts/overlayContext/MessageOverlayHostLayer.js +70 -31
- package/lib/commonjs/contexts/overlayContext/MessageOverlayHostLayer.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/UIComponents/BottomSheetModal.js +120 -125
- package/lib/module/components/UIComponents/BottomSheetModal.js.map +1 -1
- package/lib/module/components/UIComponents/PortalWhileClosingView.js +4 -3
- package/lib/module/components/UIComponents/PortalWhileClosingView.js.map +1 -1
- package/lib/module/contexts/overlayContext/MessageOverlayHostLayer.js +70 -31
- package/lib/module/contexts/overlayContext/MessageOverlayHostLayer.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/UIComponents/BottomSheetModal.d.ts.map +1 -1
- package/lib/typescript/components/UIComponents/PortalWhileClosingView.d.ts.map +1 -1
- package/lib/typescript/contexts/overlayContext/MessageOverlayHostLayer.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap +15 -60
- package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap +10 -40
- package/src/components/UIComponents/BottomSheetModal.tsx +140 -173
- package/src/components/UIComponents/PortalWhileClosingView.tsx +5 -3
- package/src/contexts/overlayContext/MessageOverlayHostLayer.tsx +86 -31
- package/src/contexts/overlayContext/__tests__/MessageOverlayHostLayer.test.tsx +17 -8
- package/src/version.json +1 -1
|
@@ -789,16 +789,13 @@ exports[`SendButton should render a SendButton 1`] = `
|
|
|
789
789
|
style={
|
|
790
790
|
[
|
|
791
791
|
{
|
|
792
|
-
"
|
|
792
|
+
"opacity": 0,
|
|
793
793
|
},
|
|
794
794
|
{
|
|
795
795
|
"transform": [
|
|
796
796
|
{
|
|
797
797
|
"scale": 0,
|
|
798
798
|
},
|
|
799
|
-
{
|
|
800
|
-
"translateY": 0,
|
|
801
|
-
},
|
|
802
799
|
],
|
|
803
800
|
},
|
|
804
801
|
]
|
|
@@ -821,18 +818,9 @@ exports[`SendButton should render a SendButton 1`] = `
|
|
|
821
818
|
<View
|
|
822
819
|
pointerEvents="box-none"
|
|
823
820
|
style={
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
},
|
|
828
|
-
{
|
|
829
|
-
"transform": [
|
|
830
|
-
{
|
|
831
|
-
"translateY": 0,
|
|
832
|
-
},
|
|
833
|
-
],
|
|
834
|
-
},
|
|
835
|
-
]
|
|
821
|
+
{
|
|
822
|
+
"height": 0,
|
|
823
|
+
}
|
|
836
824
|
}
|
|
837
825
|
testID="message-overlay-message"
|
|
838
826
|
>
|
|
@@ -853,16 +841,13 @@ exports[`SendButton should render a SendButton 1`] = `
|
|
|
853
841
|
style={
|
|
854
842
|
[
|
|
855
843
|
{
|
|
856
|
-
"
|
|
844
|
+
"opacity": 0,
|
|
857
845
|
},
|
|
858
846
|
{
|
|
859
847
|
"transform": [
|
|
860
848
|
{
|
|
861
849
|
"scale": 0,
|
|
862
850
|
},
|
|
863
|
-
{
|
|
864
|
-
"translateY": 0,
|
|
865
|
-
},
|
|
866
851
|
],
|
|
867
852
|
},
|
|
868
853
|
]
|
|
@@ -1676,16 +1661,13 @@ exports[`SendButton should render a disabled SendButton 1`] = `
|
|
|
1676
1661
|
style={
|
|
1677
1662
|
[
|
|
1678
1663
|
{
|
|
1679
|
-
"
|
|
1664
|
+
"opacity": 0,
|
|
1680
1665
|
},
|
|
1681
1666
|
{
|
|
1682
1667
|
"transform": [
|
|
1683
1668
|
{
|
|
1684
1669
|
"scale": 0,
|
|
1685
1670
|
},
|
|
1686
|
-
{
|
|
1687
|
-
"translateY": 0,
|
|
1688
|
-
},
|
|
1689
1671
|
],
|
|
1690
1672
|
},
|
|
1691
1673
|
]
|
|
@@ -1708,18 +1690,9 @@ exports[`SendButton should render a disabled SendButton 1`] = `
|
|
|
1708
1690
|
<View
|
|
1709
1691
|
pointerEvents="box-none"
|
|
1710
1692
|
style={
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
},
|
|
1715
|
-
{
|
|
1716
|
-
"transform": [
|
|
1717
|
-
{
|
|
1718
|
-
"translateY": 0,
|
|
1719
|
-
},
|
|
1720
|
-
],
|
|
1721
|
-
},
|
|
1722
|
-
]
|
|
1693
|
+
{
|
|
1694
|
+
"height": 0,
|
|
1695
|
+
}
|
|
1723
1696
|
}
|
|
1724
1697
|
testID="message-overlay-message"
|
|
1725
1698
|
>
|
|
@@ -1740,16 +1713,13 @@ exports[`SendButton should render a disabled SendButton 1`] = `
|
|
|
1740
1713
|
style={
|
|
1741
1714
|
[
|
|
1742
1715
|
{
|
|
1743
|
-
"
|
|
1716
|
+
"opacity": 0,
|
|
1744
1717
|
},
|
|
1745
1718
|
{
|
|
1746
1719
|
"transform": [
|
|
1747
1720
|
{
|
|
1748
1721
|
"scale": 0,
|
|
1749
1722
|
},
|
|
1750
|
-
{
|
|
1751
|
-
"translateY": 0,
|
|
1752
|
-
},
|
|
1753
1723
|
],
|
|
1754
1724
|
},
|
|
1755
1725
|
]
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
PropsWithChildren,
|
|
3
|
+
useEffect,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
2
9
|
import {
|
|
3
10
|
EventSubscription,
|
|
4
11
|
Keyboard,
|
|
@@ -13,7 +20,6 @@ import {
|
|
|
13
20
|
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
14
21
|
import type { KeyboardEventData } from 'react-native-keyboard-controller';
|
|
15
22
|
import Animated, {
|
|
16
|
-
cancelAnimation,
|
|
17
23
|
Easing,
|
|
18
24
|
FadeIn,
|
|
19
25
|
runOnJS,
|
|
@@ -70,15 +76,21 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
|
|
|
70
76
|
|
|
71
77
|
const baseHeight = Math.min(height, maxHeight);
|
|
72
78
|
const snapPoints = useMemo(() => [baseHeight, maxHeight], [baseHeight, maxHeight]);
|
|
79
|
+
const snapPointsTranslateY = useMemo(
|
|
80
|
+
() => snapPoints.map((point) => maxHeight - point),
|
|
81
|
+
[maxHeight, snapPoints],
|
|
82
|
+
);
|
|
73
83
|
|
|
74
|
-
const
|
|
84
|
+
const sheetTranslateY = useSharedValue(maxHeight);
|
|
75
85
|
const keyboardOffset = useSharedValue(0);
|
|
76
86
|
const currentSnapIndex = useSharedValue(0);
|
|
77
87
|
|
|
78
88
|
const isOpen = useSharedValue(false);
|
|
79
89
|
const isOpening = useSharedValue(false);
|
|
80
90
|
|
|
81
|
-
const
|
|
91
|
+
const panStartTranslateY = useSharedValue(0);
|
|
92
|
+
const hasCommittedVisibilityRef = useRef(false);
|
|
93
|
+
const wasVisibleRef = useRef(false);
|
|
82
94
|
|
|
83
95
|
const [renderContent, setRenderContent] = useState(!lazy);
|
|
84
96
|
|
|
@@ -88,35 +100,47 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
|
|
|
88
100
|
}
|
|
89
101
|
});
|
|
90
102
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
const finishClose = useStableCallback((closeAnimationFinishedCallback?: () => void) => {
|
|
104
|
+
onClose();
|
|
105
|
+
if (closeAnimationFinishedCallback) {
|
|
106
|
+
Platform.OS === 'ios'
|
|
107
|
+
? closeAnimationFinishedCallback()
|
|
108
|
+
: setTimeout(() => closeAnimationFinishedCallback(), 100);
|
|
94
109
|
}
|
|
95
110
|
});
|
|
96
111
|
|
|
112
|
+
const closeFromGesture = useStableCallback(() => {
|
|
113
|
+
requestAnimationFrame(() => {
|
|
114
|
+
isOpen.value = false;
|
|
115
|
+
isOpening.value = false;
|
|
116
|
+
|
|
117
|
+
sheetTranslateY.value = withTiming(
|
|
118
|
+
maxHeight,
|
|
119
|
+
{ duration: 250, easing: Easing.out(Easing.cubic) },
|
|
120
|
+
(finished) => {
|
|
121
|
+
if (finished) {
|
|
122
|
+
runOnJS(onClose)();
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
97
129
|
const close = useStableCallback((closeAnimationFinishedCallback?: () => void) => {
|
|
98
|
-
|
|
99
|
-
|
|
130
|
+
if (!visible || !isOpen.value) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
100
133
|
|
|
101
134
|
isOpen.value = false;
|
|
102
135
|
isOpening.value = false;
|
|
103
136
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const closeCallback = () => {
|
|
107
|
-
onClose();
|
|
108
|
-
if (closeAnimationFinishedCallback) {
|
|
109
|
-
Platform.OS === 'ios'
|
|
110
|
-
? closeAnimationFinishedCallback()
|
|
111
|
-
: setTimeout(() => closeAnimationFinishedCallback(), 100);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
translateY.value = withTiming(
|
|
137
|
+
sheetTranslateY.value = withTiming(
|
|
116
138
|
maxHeight,
|
|
117
139
|
{ duration: 250, easing: Easing.out(Easing.cubic) },
|
|
118
140
|
(finished) => {
|
|
119
|
-
if (finished)
|
|
141
|
+
if (finished) {
|
|
142
|
+
runOnJS(finishClose)(closeAnimationFinishedCallback);
|
|
143
|
+
}
|
|
120
144
|
},
|
|
121
145
|
);
|
|
122
146
|
});
|
|
@@ -124,57 +148,38 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
|
|
|
124
148
|
// modal opening layout effect - we make sure to only show the content
|
|
125
149
|
// after the animation has finished if `lazy` has been set to true
|
|
126
150
|
useLayoutEffect(() => {
|
|
127
|
-
|
|
151
|
+
const wasVisible = hasCommittedVisibilityRef.current ? wasVisibleRef.current : false;
|
|
152
|
+
hasCommittedVisibilityRef.current = true;
|
|
153
|
+
wasVisibleRef.current = visible;
|
|
154
|
+
|
|
155
|
+
if (!visible || wasVisible) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
128
158
|
|
|
129
159
|
isOpen.value = true;
|
|
130
160
|
isOpening.value = true;
|
|
131
161
|
currentSnapIndex.value = 0;
|
|
162
|
+
sheetTranslateY.value = maxHeight;
|
|
132
163
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// start from closed
|
|
136
|
-
translateY.value = maxHeight;
|
|
137
|
-
|
|
138
|
-
// Snapshot current keyboard offset as the open target.
|
|
139
|
-
// If keyboard changes during opening, we’ll adjust after.
|
|
140
|
-
const initialTarget = keyboardOffset.value + (maxHeight - snapPoints[currentSnapIndex.value]);
|
|
141
|
-
|
|
142
|
-
translateY.value = withTiming(
|
|
143
|
-
initialTarget,
|
|
164
|
+
sheetTranslateY.value = withTiming(
|
|
165
|
+
snapPointsTranslateY[0],
|
|
144
166
|
{ duration: 250, easing: Easing.out(Easing.cubic) },
|
|
145
167
|
(finished) => {
|
|
146
168
|
if (!finished) return;
|
|
147
169
|
|
|
148
|
-
// opening the modal has now truly finished
|
|
149
170
|
isOpening.value = false;
|
|
150
|
-
|
|
151
|
-
// reveal the content if we want to load it lazily
|
|
152
171
|
runOnJS(showContent)();
|
|
153
|
-
|
|
154
|
-
// if keyboard offset changed while we were opening, we do a
|
|
155
|
-
// follow-up adjustment (we do not gate the content however)
|
|
156
|
-
const latestTarget =
|
|
157
|
-
keyboardOffset.value + (maxHeight - snapPoints[currentSnapIndex.value]);
|
|
158
|
-
if (latestTarget !== initialTarget && isOpen.value) {
|
|
159
|
-
cancelAnimation(translateY);
|
|
160
|
-
translateY.value = withTiming(latestTarget, {
|
|
161
|
-
duration: 250,
|
|
162
|
-
easing: Easing.inOut(Easing.ease),
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
172
|
},
|
|
166
173
|
);
|
|
167
174
|
}, [
|
|
168
175
|
visible,
|
|
169
|
-
hideContent,
|
|
170
176
|
isOpen,
|
|
171
177
|
isOpening,
|
|
172
|
-
keyboardOffset,
|
|
173
178
|
maxHeight,
|
|
174
|
-
snapPoints,
|
|
175
179
|
showContent,
|
|
176
|
-
|
|
180
|
+
sheetTranslateY,
|
|
177
181
|
currentSnapIndex,
|
|
182
|
+
snapPointsTranslateY,
|
|
178
183
|
]);
|
|
179
184
|
|
|
180
185
|
// if `visible` gets hard changed, we force a cleanup
|
|
@@ -185,39 +190,46 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
|
|
|
185
190
|
isOpening.value = false;
|
|
186
191
|
keyboardOffset.value = 0;
|
|
187
192
|
currentSnapIndex.value = 0;
|
|
193
|
+
sheetTranslateY.value = maxHeight;
|
|
194
|
+
setRenderContent(!lazy);
|
|
195
|
+
}, [
|
|
196
|
+
visible,
|
|
197
|
+
lazy,
|
|
198
|
+
isOpen,
|
|
199
|
+
isOpening,
|
|
200
|
+
keyboardOffset,
|
|
201
|
+
maxHeight,
|
|
202
|
+
sheetTranslateY,
|
|
203
|
+
currentSnapIndex,
|
|
204
|
+
]);
|
|
188
205
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const offset = -event.endCoordinates.height;
|
|
195
|
-
keyboardOffset.value = offset;
|
|
196
|
-
|
|
197
|
-
// We just record the offset, but we avoid cancelling the animation
|
|
198
|
-
// if it's in the process of opening. The same logic applies to all
|
|
199
|
-
// other keyboard related callbacks in this specific conditional.
|
|
200
|
-
if (!isOpen.value || isOpening.value) return;
|
|
206
|
+
// Keep the sheet aligned with the active snap if dimensions change while visible.
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (!visible || !isOpen.value || isOpening.value) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
201
211
|
|
|
202
|
-
|
|
203
|
-
translateY.value = withTiming(offset + (maxHeight - snapPoints[currentSnapIndex.value]), {
|
|
212
|
+
sheetTranslateY.value = withTiming(snapPointsTranslateY[currentSnapIndex.value], {
|
|
204
213
|
duration: 250,
|
|
205
214
|
easing: Easing.inOut(Easing.ease),
|
|
206
215
|
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
const keyboardDidHide = useStableCallback(() => {
|
|
210
|
-
keyboardOffset.value = 0;
|
|
211
|
-
|
|
212
|
-
if (!isOpen.value || isOpening.value) return;
|
|
216
|
+
}, [visible, isOpen, isOpening, sheetTranslateY, currentSnapIndex, snapPointsTranslateY]);
|
|
213
217
|
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
const animateKeyboardOffset = useStableCallback((offset: number) => {
|
|
219
|
+
keyboardOffset.value = withTiming(offset, {
|
|
216
220
|
duration: 250,
|
|
217
221
|
easing: Easing.inOut(Easing.ease),
|
|
218
222
|
});
|
|
219
223
|
});
|
|
220
224
|
|
|
225
|
+
const keyboardDidShowRN = useStableCallback((event: KeyboardEvent) => {
|
|
226
|
+
animateKeyboardOffset(event.endCoordinates.height);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const keyboardDidHide = useStableCallback(() => {
|
|
230
|
+
animateKeyboardOffset(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
221
233
|
useEffect(() => {
|
|
222
234
|
if (!visible) return;
|
|
223
235
|
|
|
@@ -225,114 +237,67 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
|
|
|
225
237
|
|
|
226
238
|
if (KeyboardControllerPackage?.KeyboardEvents) {
|
|
227
239
|
const keyboardDidShowKC = (event: KeyboardEventData) => {
|
|
228
|
-
|
|
229
|
-
keyboardOffset.value = offset;
|
|
230
|
-
|
|
231
|
-
if (!isOpen.value || isOpening.value) return;
|
|
232
|
-
|
|
233
|
-
cancelAnimation(translateY);
|
|
234
|
-
translateY.value = withTiming(offset + (maxHeight - snapPoints[currentSnapIndex.value]), {
|
|
235
|
-
duration: 250,
|
|
236
|
-
easing: Easing.inOut(Easing.ease),
|
|
237
|
-
});
|
|
240
|
+
animateKeyboardOffset(event.height);
|
|
238
241
|
};
|
|
239
242
|
|
|
240
243
|
listeners.push(
|
|
241
244
|
KeyboardControllerPackage.KeyboardEvents.addListener('keyboardDidShow', keyboardDidShowKC),
|
|
242
245
|
KeyboardControllerPackage.KeyboardEvents.addListener('keyboardDidHide', keyboardDidHide),
|
|
243
246
|
);
|
|
244
|
-
} else {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
listeners.push(Keyboard.addListener('keyboardWillHide', keyboardDidHide));
|
|
248
|
-
}
|
|
247
|
+
} else if (Platform.OS === 'ios') {
|
|
248
|
+
listeners.push(Keyboard.addListener('keyboardWillShow', keyboardDidShowRN));
|
|
249
|
+
listeners.push(Keyboard.addListener('keyboardWillHide', keyboardDidHide));
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
return () => listeners.forEach((l) => l.remove());
|
|
252
|
-
}, [
|
|
253
|
-
visible,
|
|
254
|
-
keyboardDidHide,
|
|
255
|
-
keyboardDidShowRN,
|
|
256
|
-
keyboardOffset,
|
|
257
|
-
isOpen,
|
|
258
|
-
isOpening,
|
|
259
|
-
translateY,
|
|
260
|
-
maxHeight,
|
|
261
|
-
snapPoints,
|
|
262
|
-
currentSnapIndex,
|
|
263
|
-
]);
|
|
253
|
+
}, [visible, animateKeyboardOffset, keyboardDidHide, keyboardDidShowRN]);
|
|
264
254
|
|
|
265
|
-
const
|
|
266
|
-
transform: [{ translateY:
|
|
267
|
-
paddingBottom: translateY.value,
|
|
255
|
+
const sheetViewportAnimatedStyle = useAnimatedStyle(() => ({
|
|
256
|
+
transform: [{ translateY: sheetTranslateY.value - keyboardOffset.value }],
|
|
268
257
|
}));
|
|
269
258
|
|
|
270
|
-
const backdropThreshold = baseHeight;
|
|
271
|
-
|
|
272
259
|
const overlayAnimatedStyle = useAnimatedStyle(() => {
|
|
273
|
-
const visibleHeight = Math.max(0, maxHeight -
|
|
274
|
-
const threshold = Math.max(1, Math.min(
|
|
260
|
+
const visibleHeight = Math.max(0, maxHeight - sheetTranslateY.value);
|
|
261
|
+
const threshold = Math.max(1, Math.min(baseHeight, maxHeight));
|
|
275
262
|
const progress = Math.min(1, visibleHeight / threshold);
|
|
276
263
|
return { opacity: progress };
|
|
277
264
|
});
|
|
278
265
|
|
|
279
|
-
const snapPointsTranslateY = useMemo(
|
|
280
|
-
() => snapPoints.map((point) => maxHeight - point),
|
|
281
|
-
[maxHeight, snapPoints],
|
|
282
|
-
);
|
|
283
|
-
|
|
284
266
|
const panGesture = useMemo(
|
|
285
267
|
() =>
|
|
286
268
|
Gesture.Pan()
|
|
287
|
-
// disable pan until content is rendered (prevents canceling the opening timing).
|
|
288
269
|
.enabled(renderContent)
|
|
289
270
|
.onBegin(() => {
|
|
290
|
-
|
|
291
|
-
panStartY.value = translateY.value;
|
|
271
|
+
panStartTranslateY.value = sheetTranslateY.value;
|
|
292
272
|
})
|
|
293
273
|
.onUpdate((event) => {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
translateY.value = Math.max(next, minY);
|
|
274
|
+
const nextTranslateY = panStartTranslateY.value + event.translationY;
|
|
275
|
+
sheetTranslateY.value = Math.min(Math.max(nextTranslateY, 0), maxHeight);
|
|
297
276
|
})
|
|
298
277
|
.onEnd((event) => {
|
|
299
|
-
const
|
|
300
|
-
const draggedDown = Math.max(
|
|
278
|
+
const openTranslateY = snapPointsTranslateY[currentSnapIndex.value];
|
|
279
|
+
const draggedDown = Math.max(sheetTranslateY.value - openTranslateY, 0);
|
|
301
280
|
const topSnapIndex = snapPoints.length - 1;
|
|
302
281
|
const isAtTopSnap = currentSnapIndex.value === topSnapIndex;
|
|
303
|
-
const
|
|
304
|
-
const
|
|
282
|
+
const snap0TranslateY = snapPointsTranslateY[0];
|
|
283
|
+
const projectedTranslateY = sheetTranslateY.value + event.velocityY * 0.2;
|
|
305
284
|
|
|
306
|
-
// From lower snaps, keep the previous close behavior.
|
|
307
285
|
const shouldCloseFromLowerSnap = event.velocityY > 500 || draggedDown > maxHeight / 2;
|
|
308
|
-
// From top snap, close only for clearly hard downward intent.
|
|
309
286
|
const shouldCloseFromTopSnap =
|
|
310
|
-
event.velocityY > 2200 ||
|
|
287
|
+
event.velocityY > 2200 ||
|
|
288
|
+
projectedTranslateY > snap0TranslateY + (maxHeight - snap0TranslateY) * 0.96;
|
|
311
289
|
|
|
312
290
|
const shouldClose = isAtTopSnap ? shouldCloseFromTopSnap : shouldCloseFromLowerSnap;
|
|
313
291
|
|
|
314
|
-
cancelAnimation(translateY);
|
|
315
|
-
|
|
316
292
|
if (shouldClose) {
|
|
317
|
-
|
|
318
|
-
isOpening.value = false;
|
|
319
|
-
|
|
320
|
-
translateY.value = withTiming(
|
|
321
|
-
maxHeight,
|
|
322
|
-
{ duration: 250, easing: Easing.out(Easing.cubic) },
|
|
323
|
-
(finished) => {
|
|
324
|
-
if (finished) runOnJS(onClose)();
|
|
325
|
-
},
|
|
326
|
-
);
|
|
293
|
+
runOnJS(closeFromGesture)();
|
|
327
294
|
} else {
|
|
328
295
|
isOpen.value = true;
|
|
329
|
-
// snap to the nearest point
|
|
330
296
|
let nearestIndex = 0;
|
|
331
297
|
let minDistance = Number.POSITIVE_INFINITY;
|
|
332
|
-
const baseOffset = keyboardOffset.value;
|
|
333
298
|
for (let i = 0; i < snapPointsTranslateY.length; i += 1) {
|
|
334
|
-
const candidate =
|
|
335
|
-
const distance = Math.abs(
|
|
299
|
+
const candidate = snapPointsTranslateY[i];
|
|
300
|
+
const distance = Math.abs(sheetTranslateY.value - candidate);
|
|
336
301
|
if (distance < minDistance) {
|
|
337
302
|
minDistance = distance;
|
|
338
303
|
nearestIndex = i;
|
|
@@ -347,8 +312,9 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
|
|
|
347
312
|
if (isAtTopSnap && event.velocityY > 120) {
|
|
348
313
|
nearestIndex = 0;
|
|
349
314
|
}
|
|
315
|
+
|
|
350
316
|
currentSnapIndex.value = nearestIndex;
|
|
351
|
-
|
|
317
|
+
sheetTranslateY.value = withTiming(snapPointsTranslateY[nearestIndex], {
|
|
352
318
|
duration: 250,
|
|
353
319
|
easing: Easing.inOut(Easing.ease),
|
|
354
320
|
});
|
|
@@ -357,15 +323,13 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
|
|
|
357
323
|
[
|
|
358
324
|
currentSnapIndex,
|
|
359
325
|
isOpen,
|
|
360
|
-
isOpening,
|
|
361
|
-
keyboardOffset,
|
|
362
326
|
maxHeight,
|
|
363
|
-
|
|
364
|
-
|
|
327
|
+
closeFromGesture,
|
|
328
|
+
panStartTranslateY,
|
|
365
329
|
renderContent,
|
|
366
330
|
snapPoints,
|
|
367
331
|
snapPointsTranslateY,
|
|
368
|
-
|
|
332
|
+
sheetTranslateY,
|
|
369
333
|
],
|
|
370
334
|
);
|
|
371
335
|
|
|
@@ -382,30 +346,33 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
|
|
|
382
346
|
return (
|
|
383
347
|
<Modal onRequestClose={onClose} transparent visible={visible}>
|
|
384
348
|
<GestureHandlerRootView style={styles.sheetContentContainer}>
|
|
385
|
-
<
|
|
386
|
-
<View style={[styles.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
<View style={[styles.
|
|
395
|
-
{
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
349
|
+
<View style={[styles.overlay, overlayTheme]}>
|
|
350
|
+
<Animated.View pointerEvents='none' style={[styles.backdrop, overlayAnimatedStyle]} />
|
|
351
|
+
<Pressable onPress={onBackdropPress} style={StyleSheet.absoluteFillObject} />
|
|
352
|
+
|
|
353
|
+
<Animated.View
|
|
354
|
+
pointerEvents='box-none'
|
|
355
|
+
style={[{ height: maxHeight }, sheetViewportAnimatedStyle]}
|
|
356
|
+
>
|
|
357
|
+
<GestureDetector gesture={panGesture}>
|
|
358
|
+
<Animated.View style={[styles.container, { height: maxHeight }, container]}>
|
|
359
|
+
<View style={[styles.handle, handle]} />
|
|
360
|
+
<View style={[styles.contentContainer, contentContainer]}>
|
|
361
|
+
{renderContent ? (
|
|
362
|
+
<BottomSheetProvider value={bottomSheetModalContextValue}>
|
|
363
|
+
<Animated.View
|
|
364
|
+
entering={FadeIn.duration(250)}
|
|
365
|
+
style={styles.sheetContentContainer}
|
|
366
|
+
>
|
|
367
|
+
{children}
|
|
368
|
+
</Animated.View>
|
|
369
|
+
</BottomSheetProvider>
|
|
370
|
+
) : null}
|
|
371
|
+
</View>
|
|
372
|
+
</Animated.View>
|
|
373
|
+
</GestureDetector>
|
|
374
|
+
</Animated.View>
|
|
375
|
+
</View>
|
|
409
376
|
</GestureHandlerRootView>
|
|
410
377
|
</Modal>
|
|
411
378
|
);
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
setClosingPortalLayout,
|
|
13
13
|
useShouldTeleportToClosingPortal,
|
|
14
14
|
useHasActiveId,
|
|
15
|
+
useIsOverlayClosing,
|
|
15
16
|
} from '../../state-store';
|
|
16
17
|
|
|
17
18
|
type PortalWhileClosingViewProps = {
|
|
@@ -116,9 +117,10 @@ const useSyncingApi = (portalHostName: string, registrationId: string) => {
|
|
|
116
117
|
const placeholderLayout = useSharedValue({ h: 0, w: 0 });
|
|
117
118
|
const insets = useSafeAreaInsets();
|
|
118
119
|
const hasActiveId = useHasActiveId();
|
|
120
|
+
const isClosing = useIsOverlayClosing();
|
|
119
121
|
|
|
120
122
|
const syncPortalLayout = useStableCallback(() => {
|
|
121
|
-
if (!hasActiveId) {
|
|
123
|
+
if (!hasActiveId && !isClosing) {
|
|
122
124
|
return;
|
|
123
125
|
}
|
|
124
126
|
|
|
@@ -143,10 +145,10 @@ const useSyncingApi = (portalHostName: string, registrationId: string) => {
|
|
|
143
145
|
});
|
|
144
146
|
|
|
145
147
|
useEffect(() => {
|
|
146
|
-
if (hasActiveId) {
|
|
148
|
+
if (hasActiveId || isClosing) {
|
|
147
149
|
syncPortalLayout();
|
|
148
150
|
}
|
|
149
|
-
}, [insets.bottom, hasActiveId, syncPortalLayout]);
|
|
151
|
+
}, [insets.bottom, isClosing, hasActiveId, syncPortalLayout]);
|
|
150
152
|
|
|
151
153
|
return useMemo(
|
|
152
154
|
() => ({ syncPortalLayout, containerRef, placeholderLayout }),
|