insyd-bottom-sheet 0.4.1 → 0.4.2
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/package.json +1 -1
- package/src/SmoothSheet.tsx +45 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "insyd-bottom-sheet",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "A smooth, physics-based bottom sheet for React Native — by Ishan Gupta. Works with Expo Go, bare workflow, and React Native CLI.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
package/src/SmoothSheet.tsx
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
GestureDetector,
|
|
15
15
|
} from "react-native-gesture-handler";
|
|
16
16
|
import Animated, {
|
|
17
|
-
runOnJS,
|
|
18
17
|
useAnimatedStyle,
|
|
19
18
|
useSharedValue,
|
|
20
19
|
withSpring,
|
|
@@ -170,9 +169,31 @@ export function SmoothSheet({
|
|
|
170
169
|
// Guards against double-dismiss (back button mashing, backdrop tap during close)
|
|
171
170
|
const isClosing = useRef(false);
|
|
172
171
|
|
|
172
|
+
// Unmount is sequenced with a JS-side timer instead of a Reanimated
|
|
173
|
+
// animation-completion callback. runOnJS/scheduleOnRN from the UI thread has
|
|
174
|
+
// a use-after-free race in react-native-worklets 0.10.0 (reanimated #9776):
|
|
175
|
+
// the remote-function registry entry can be freed before the pending call
|
|
176
|
+
// runs, segfaulting — bottom sheets (rapid mount/unmount) are the canonical
|
|
177
|
+
// trigger. The backdrop fade is a fixed 200ms withTiming, so a 220ms timer
|
|
178
|
+
// lands just after it and the swap is invisible.
|
|
179
|
+
const CLOSE_MS = 220;
|
|
180
|
+
const closeTimer = useRef<ReturnType<
|
|
181
|
+
typeof setTimeout
|
|
182
|
+
> | null>(null);
|
|
183
|
+
|
|
184
|
+
function clearCloseTimer() {
|
|
185
|
+
if (closeTimer.current) {
|
|
186
|
+
clearTimeout(closeTimer.current);
|
|
187
|
+
closeTimer.current = null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
useEffect(() => clearCloseTimer, []);
|
|
192
|
+
|
|
173
193
|
useEffect(() => {
|
|
174
194
|
if (isVisible) {
|
|
175
195
|
isClosing.current = false;
|
|
196
|
+
clearCloseTimer();
|
|
176
197
|
setMounted(true);
|
|
177
198
|
translateY.value = withSpring(0, spring);
|
|
178
199
|
backdropOpacity.value = withTiming(1, {
|
|
@@ -180,13 +201,14 @@ export function SmoothSheet({
|
|
|
180
201
|
});
|
|
181
202
|
} else {
|
|
182
203
|
translateY.value = withSpring(windowHeight, spring);
|
|
183
|
-
backdropOpacity.value = withTiming(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
204
|
+
backdropOpacity.value = withTiming(0, {
|
|
205
|
+
duration: 200,
|
|
206
|
+
});
|
|
207
|
+
clearCloseTimer();
|
|
208
|
+
closeTimer.current = setTimeout(() => {
|
|
209
|
+
closeTimer.current = null;
|
|
210
|
+
setMounted(false);
|
|
211
|
+
}, CLOSE_MS);
|
|
190
212
|
}
|
|
191
213
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
192
214
|
}, [isVisible]);
|
|
@@ -195,16 +217,15 @@ export function SmoothSheet({
|
|
|
195
217
|
if (isClosing.current) return;
|
|
196
218
|
isClosing.current = true;
|
|
197
219
|
translateY.value = withSpring(windowHeight, spring);
|
|
198
|
-
backdropOpacity.value = withTiming(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
220
|
+
backdropOpacity.value = withTiming(0, {
|
|
221
|
+
duration: 200,
|
|
222
|
+
});
|
|
223
|
+
clearCloseTimer();
|
|
224
|
+
closeTimer.current = setTimeout(() => {
|
|
225
|
+
closeTimer.current = null;
|
|
226
|
+
setMounted(false);
|
|
227
|
+
onDismiss();
|
|
228
|
+
}, CLOSE_MS);
|
|
208
229
|
}
|
|
209
230
|
|
|
210
231
|
// Android hardware back closes the sheet (replaces Modal's onRequestClose).
|
|
@@ -221,7 +242,12 @@ export function SmoothSheet({
|
|
|
221
242
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
222
243
|
}, [mounted]);
|
|
223
244
|
|
|
245
|
+
// runOnJS(true): callbacks run on the JS thread, so no worklet→JS
|
|
246
|
+
// remote-function crossing (see the worklets 0.10.0 use-after-free note
|
|
247
|
+
// above). Setting a shared value from JS still animates on the UI thread;
|
|
248
|
+
// for a drag handle the one-frame latency is imperceptible.
|
|
224
249
|
const panGesture = Gesture.Pan()
|
|
250
|
+
.runOnJS(true)
|
|
225
251
|
.onUpdate((e) => {
|
|
226
252
|
if (e.translationY > 0) {
|
|
227
253
|
translateY.value = e.translationY;
|
|
@@ -232,7 +258,7 @@ export function SmoothSheet({
|
|
|
232
258
|
e.translationY > dismissThreshold ||
|
|
233
259
|
e.velocityY > dismissVelocityThreshold
|
|
234
260
|
) {
|
|
235
|
-
|
|
261
|
+
dismiss();
|
|
236
262
|
} else {
|
|
237
263
|
translateY.value = withSpring(0, spring);
|
|
238
264
|
}
|