react-native-screen-transitions 2.3.2 → 2.3.4
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/README.md +73 -19
- package/lib/commonjs/__configs__/presets.js +7 -3
- package/lib/commonjs/__configs__/presets.js.map +1 -1
- package/lib/commonjs/__configs__/specs.js +1 -0
- package/lib/commonjs/__configs__/specs.js.map +1 -1
- package/lib/commonjs/components/controllers/screen-lifecycle.js +5 -8
- package/lib/commonjs/components/controllers/screen-lifecycle.js.map +1 -1
- package/lib/commonjs/components/create-transition-aware-component.js +2 -2
- package/lib/commonjs/components/create-transition-aware-component.js.map +1 -1
- package/lib/commonjs/hooks/bounds/use-bound-registry.js +11 -10
- package/lib/commonjs/hooks/bounds/use-bound-registry.js.map +1 -1
- package/lib/commonjs/hooks/gestures/use-build-gestures.js +18 -28
- package/lib/commonjs/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/integrations/native-stack/views/NativeStackView.native.js.map +1 -1
- package/lib/commonjs/utils/animation/start-screen-transition.js +8 -6
- package/lib/commonjs/utils/animation/start-screen-transition.js.map +1 -1
- package/lib/commonjs/utils/gesture/determine-dismissal.js +7 -55
- package/lib/commonjs/utils/gesture/determine-dismissal.js.map +1 -1
- package/lib/commonjs/utils/gesture/reset-gesture-values.js +46 -0
- package/lib/commonjs/utils/gesture/reset-gesture-values.js.map +1 -0
- package/lib/commonjs/utils/gesture/velocity.js +89 -0
- package/lib/commonjs/utils/gesture/velocity.js.map +1 -0
- package/lib/commonjs/utils/reanimated/version.js +12 -0
- package/lib/commonjs/utils/reanimated/version.js.map +1 -0
- package/lib/module/__configs__/presets.js +7 -3
- package/lib/module/__configs__/presets.js.map +1 -1
- package/lib/module/__configs__/specs.js +1 -0
- package/lib/module/__configs__/specs.js.map +1 -1
- package/lib/module/components/controllers/screen-lifecycle.js +5 -8
- package/lib/module/components/controllers/screen-lifecycle.js.map +1 -1
- package/lib/module/components/create-transition-aware-component.js +2 -2
- package/lib/module/components/create-transition-aware-component.js.map +1 -1
- package/lib/module/hooks/bounds/use-bound-registry.js +11 -10
- package/lib/module/hooks/bounds/use-bound-registry.js.map +1 -1
- package/lib/module/hooks/gestures/use-build-gestures.js +18 -28
- package/lib/module/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/integrations/native-stack/views/NativeStackView.native.js.map +1 -1
- package/lib/module/utils/animation/start-screen-transition.js +8 -6
- package/lib/module/utils/animation/start-screen-transition.js.map +1 -1
- package/lib/module/utils/gesture/determine-dismissal.js +7 -55
- package/lib/module/utils/gesture/determine-dismissal.js.map +1 -1
- package/lib/module/utils/gesture/reset-gesture-values.js +41 -0
- package/lib/module/utils/gesture/reset-gesture-values.js.map +1 -0
- package/lib/module/utils/gesture/velocity.js +85 -0
- package/lib/module/utils/gesture/velocity.js.map +1 -0
- package/lib/module/utils/reanimated/version.js +7 -0
- package/lib/module/utils/reanimated/version.js.map +1 -0
- package/lib/typescript/__configs__/index.d.ts +1 -1
- package/lib/typescript/__configs__/presets.d.ts.map +1 -1
- package/lib/typescript/__configs__/specs.d.ts.map +1 -1
- package/lib/typescript/components/controllers/screen-lifecycle.d.ts.map +1 -1
- package/lib/typescript/components/create-transition-aware-component.d.ts +6 -12
- package/lib/typescript/components/create-transition-aware-component.d.ts.map +1 -1
- package/lib/typescript/hooks/bounds/use-bound-registry.d.ts +1 -5
- package/lib/typescript/hooks/bounds/use-bound-registry.d.ts.map +1 -1
- package/lib/typescript/hooks/gestures/use-build-gestures.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +98 -62
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/utils/animation/start-screen-transition.d.ts +3 -2
- package/lib/typescript/utils/animation/start-screen-transition.d.ts.map +1 -1
- package/lib/typescript/utils/gesture/determine-dismissal.d.ts +0 -1
- package/lib/typescript/utils/gesture/determine-dismissal.d.ts.map +1 -1
- package/lib/typescript/utils/gesture/reset-gesture-values.d.ts +16 -0
- package/lib/typescript/utils/gesture/reset-gesture-values.d.ts.map +1 -0
- package/lib/typescript/utils/gesture/velocity.d.ts +25 -0
- package/lib/typescript/utils/gesture/velocity.d.ts.map +1 -0
- package/lib/typescript/utils/reanimated/version.d.ts +2 -0
- package/lib/typescript/utils/reanimated/version.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/__configs__/presets.ts +7 -1
- package/src/__configs__/specs.ts +1 -0
- package/src/components/controllers/screen-lifecycle.tsx +5 -7
- package/src/components/create-transition-aware-component.tsx +2 -2
- package/src/hooks/bounds/use-bound-registry.tsx +21 -18
- package/src/hooks/gestures/use-build-gestures.tsx +20 -15
- package/src/integrations/native-stack/views/NativeStackView.native.tsx +1 -1
- package/src/utils/animation/start-screen-transition.ts +13 -4
- package/src/utils/gesture/determine-dismissal.ts +18 -69
- package/src/utils/gesture/reset-gesture-values.ts +48 -0
- package/src/utils/gesture/velocity.ts +144 -0
- package/src/utils/reanimated/version.ts +5 -0
|
@@ -246,11 +246,13 @@ export const SharedIGImage = (
|
|
|
246
246
|
normX,
|
|
247
247
|
[-1, 0, 1],
|
|
248
248
|
[-width * 0.7, 0, width * 0.7],
|
|
249
|
+
"clamp",
|
|
249
250
|
);
|
|
250
251
|
const dragY = interpolate(
|
|
251
252
|
normY,
|
|
252
253
|
[-1, 0, 1],
|
|
253
254
|
[-height * 0.4, 0, height * 0.4],
|
|
255
|
+
"clamp",
|
|
254
256
|
);
|
|
255
257
|
const dragXScale = interpolate(normX, [0, 1], [1, 0.8]);
|
|
256
258
|
const dragYScale = interpolate(normY, [0, 1], [1, 0.8]);
|
|
@@ -328,6 +330,7 @@ export const SharedIGImage = (
|
|
|
328
330
|
damping: 1000,
|
|
329
331
|
mass: 3,
|
|
330
332
|
overshootClamping: true,
|
|
333
|
+
//@ts-expect-error
|
|
331
334
|
restSpeedThreshold: 0.02,
|
|
332
335
|
},
|
|
333
336
|
close: {
|
|
@@ -335,6 +338,7 @@ export const SharedIGImage = (
|
|
|
335
338
|
damping: 1000,
|
|
336
339
|
mass: 3,
|
|
337
340
|
overshootClamping: true,
|
|
341
|
+
//@ts-expect-error
|
|
338
342
|
restSpeedThreshold: 0.02,
|
|
339
343
|
},
|
|
340
344
|
},
|
|
@@ -507,13 +511,15 @@ export const SharedAppleMusic = (
|
|
|
507
511
|
damping: 500,
|
|
508
512
|
mass: 3,
|
|
509
513
|
overshootClamping: true,
|
|
514
|
+
//@ts-expect-error
|
|
510
515
|
restSpeedThreshold: 0.02,
|
|
511
516
|
},
|
|
512
517
|
close: {
|
|
513
518
|
stiffness: 600,
|
|
514
519
|
damping: 60,
|
|
515
|
-
mass:
|
|
520
|
+
mass: 4,
|
|
516
521
|
overshootClamping: false,
|
|
522
|
+
//@ts-expect-error
|
|
517
523
|
restSpeedThreshold: 0.02,
|
|
518
524
|
restDisplacementThreshold: 0.002,
|
|
519
525
|
},
|
package/src/__configs__/specs.ts
CHANGED
|
@@ -22,14 +22,12 @@ export const ScreenLifecycleController = ({
|
|
|
22
22
|
const key = current.navigation.getParent()?.getState().key;
|
|
23
23
|
const requestedDismissOnNavigator = NavigatorDismissState.get(key);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
25
|
+
const isEnabled = current.options.enableTransitions;
|
|
26
|
+
const isRequestedDismissOnNavigator = requestedDismissOnNavigator;
|
|
27
|
+
const isFirstScreen = current.navigation.getState().index === 0;
|
|
30
28
|
|
|
31
|
-
//
|
|
32
|
-
if (
|
|
29
|
+
// If transitions are disabled, or the dismissal was on the local root, or this is the first screen of the stack, reset the stores
|
|
30
|
+
if (!isEnabled || isRequestedDismissOnNavigator || isFirstScreen) {
|
|
33
31
|
resetStoresForScreen(current);
|
|
34
32
|
return;
|
|
35
33
|
}
|
|
@@ -62,7 +62,7 @@ export function createTransitionAwareComponent<P extends object>(
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
const {
|
|
65
|
-
|
|
65
|
+
handleInitialLayout,
|
|
66
66
|
captureActiveOnPress,
|
|
67
67
|
MeasurementSyncProvider,
|
|
68
68
|
} = useBoundsRegistry({
|
|
@@ -79,7 +79,7 @@ export function createTransitionAwareComponent<P extends object>(
|
|
|
79
79
|
ref={animatedRef}
|
|
80
80
|
style={[style, associatedStyles]}
|
|
81
81
|
onPress={captureActiveOnPress}
|
|
82
|
-
onLayout={runOnUI(
|
|
82
|
+
onLayout={runOnUI(handleInitialLayout)}
|
|
83
83
|
collapsable={!sharedBoundTag}
|
|
84
84
|
>
|
|
85
85
|
{children}
|
|
@@ -25,11 +25,15 @@ import useStableCallback from "../use-stable-callback";
|
|
|
25
25
|
interface BoundMeasurerHookProps {
|
|
26
26
|
sharedBoundTag?: string;
|
|
27
27
|
animatedRef: AnimatedRef<View>;
|
|
28
|
-
|
|
29
28
|
style: StyleProps;
|
|
30
29
|
onPress?: ((...args: unknown[]) => void) | undefined;
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
interface MaybeMeasureAndStoreParams {
|
|
33
|
+
onPress?: ((...args: unknown[]) => void) | undefined;
|
|
34
|
+
skipMarkingActive?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
interface MeasurementUpdateContextType {
|
|
34
38
|
updateSignal: SharedValue<number>;
|
|
35
39
|
}
|
|
@@ -43,12 +47,11 @@ export const useBoundsRegistry = ({
|
|
|
43
47
|
style,
|
|
44
48
|
onPress,
|
|
45
49
|
}: BoundMeasurerHookProps) => {
|
|
46
|
-
const { previous, current } = useKeys();
|
|
50
|
+
const { previous, current, next } = useKeys();
|
|
47
51
|
|
|
48
52
|
const ROOT_MEASUREMENT_SIGNAL = useContext(MeasurementUpdateContext);
|
|
49
53
|
const ROOT_SIGNAL = useSharedValue(0);
|
|
50
54
|
const IS_ROOT = !ROOT_MEASUREMENT_SIGNAL;
|
|
51
|
-
const hasMeasured = useSharedValue(false);
|
|
52
55
|
|
|
53
56
|
const emitUpdate = useCallback(() => {
|
|
54
57
|
"worklet";
|
|
@@ -56,15 +59,10 @@ export const useBoundsRegistry = ({
|
|
|
56
59
|
}, [IS_ROOT, ROOT_SIGNAL]);
|
|
57
60
|
|
|
58
61
|
const maybeMeasureAndStore = useCallback(
|
|
59
|
-
({
|
|
60
|
-
onPress,
|
|
61
|
-
skipMarkingActive,
|
|
62
|
-
}: {
|
|
63
|
-
onPress?: () => void;
|
|
64
|
-
skipMarkingActive?: boolean;
|
|
65
|
-
}) => {
|
|
62
|
+
({ onPress, skipMarkingActive }: MaybeMeasureAndStoreParams) => {
|
|
66
63
|
"worklet";
|
|
67
|
-
|
|
64
|
+
// Currently, there's no necessity to measure when the current route is blurred ( could potentially change in the future )
|
|
65
|
+
if (!sharedBoundTag || next) return;
|
|
68
66
|
|
|
69
67
|
const measured = measure(animatedRef);
|
|
70
68
|
|
|
@@ -95,14 +93,15 @@ export const useBoundsRegistry = ({
|
|
|
95
93
|
|
|
96
94
|
if (onPress) runOnJS(onPress)();
|
|
97
95
|
},
|
|
98
|
-
[sharedBoundTag, animatedRef, current.route.key, style, emitUpdate],
|
|
96
|
+
[sharedBoundTag, animatedRef, current.route.key, style, emitUpdate, next],
|
|
99
97
|
);
|
|
100
98
|
|
|
101
|
-
const
|
|
99
|
+
const hasMeasuredOnLayout = useSharedValue(false);
|
|
100
|
+
const handleInitialLayout = useCallback(() => {
|
|
102
101
|
"worklet";
|
|
103
102
|
|
|
104
103
|
const prevKey = previous?.route.key;
|
|
105
|
-
if (!sharedBoundTag ||
|
|
104
|
+
if (!sharedBoundTag || hasMeasuredOnLayout.value || !prevKey) {
|
|
106
105
|
return;
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -112,9 +111,14 @@ export const useBoundsRegistry = ({
|
|
|
112
111
|
// Should skip mark active if we are in a transition
|
|
113
112
|
maybeMeasureAndStore({ skipMarkingActive: true });
|
|
114
113
|
// Should not measure again while in transition
|
|
115
|
-
|
|
114
|
+
hasMeasuredOnLayout.value = true;
|
|
116
115
|
}
|
|
117
|
-
}, [
|
|
116
|
+
}, [
|
|
117
|
+
maybeMeasureAndStore,
|
|
118
|
+
sharedBoundTag,
|
|
119
|
+
previous?.route.key,
|
|
120
|
+
hasMeasuredOnLayout,
|
|
121
|
+
]);
|
|
118
122
|
|
|
119
123
|
const captureActiveOnPress = useStableCallback(() => {
|
|
120
124
|
if (!sharedBoundTag) {
|
|
@@ -152,8 +156,7 @@ export const useBoundsRegistry = ({
|
|
|
152
156
|
);
|
|
153
157
|
|
|
154
158
|
return {
|
|
155
|
-
|
|
156
|
-
handleTransitionLayout,
|
|
159
|
+
handleInitialLayout,
|
|
157
160
|
captureActiveOnPress,
|
|
158
161
|
MeasurementSyncProvider,
|
|
159
162
|
};
|
|
@@ -27,11 +27,12 @@ import { Animations } from "../../stores/animations";
|
|
|
27
27
|
import { Gestures } from "../../stores/gestures";
|
|
28
28
|
import { NavigatorDismissState } from "../../stores/navigator-dismiss-state";
|
|
29
29
|
import { GestureOffsetState } from "../../types/gesture";
|
|
30
|
-
import { animate } from "../../utils/animation/animate";
|
|
31
30
|
import { startScreenTransition } from "../../utils/animation/start-screen-transition";
|
|
32
31
|
import { applyOffsetRules } from "../../utils/gesture/check-gesture-activation";
|
|
33
32
|
import { determineDismissal } from "../../utils/gesture/determine-dismissal";
|
|
34
33
|
import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progress";
|
|
34
|
+
import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
|
|
35
|
+
import { velocity } from "../../utils/gesture/velocity";
|
|
35
36
|
import useStableCallback from "../use-stable-callback";
|
|
36
37
|
|
|
37
38
|
interface BuildGesturesHookProps {
|
|
@@ -51,6 +52,7 @@ export const useBuildGestures = ({
|
|
|
51
52
|
x: 0,
|
|
52
53
|
y: 0,
|
|
53
54
|
});
|
|
55
|
+
|
|
54
56
|
const gestureOffsetState = useSharedValue<GestureOffsetState>(
|
|
55
57
|
GestureOffsetState.PENDING,
|
|
56
58
|
);
|
|
@@ -268,7 +270,7 @@ export const useBuildGestures = ({
|
|
|
268
270
|
(event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
|
|
269
271
|
"worklet";
|
|
270
272
|
|
|
271
|
-
const { shouldDismiss
|
|
273
|
+
const { shouldDismiss } = determineDismissal({
|
|
272
274
|
event,
|
|
273
275
|
directions,
|
|
274
276
|
dimensions,
|
|
@@ -277,29 +279,32 @@ export const useBuildGestures = ({
|
|
|
277
279
|
|
|
278
280
|
const spec = shouldDismiss ? transitionSpec?.close : transitionSpec?.open;
|
|
279
281
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
gestures.x.value = animate(0, { ...spec, velocity: vxPx });
|
|
288
|
-
gestures.y.value = animate(0, { ...spec, velocity: vyPx });
|
|
289
|
-
gestures.normalizedX.value = animate(0, { ...spec, velocity: vxNorm });
|
|
290
|
-
gestures.normalizedY.value = animate(0, { ...spec, velocity: vyNorm });
|
|
291
|
-
gestures.isDragging.value = 0;
|
|
282
|
+
resetGestureValues({
|
|
283
|
+
spec,
|
|
284
|
+
gestures,
|
|
285
|
+
shouldDismiss,
|
|
286
|
+
event,
|
|
287
|
+
dimensions,
|
|
288
|
+
});
|
|
292
289
|
|
|
293
290
|
if (shouldDismiss) {
|
|
294
291
|
runOnJS(setNavigatorDismissal)();
|
|
295
292
|
}
|
|
296
293
|
|
|
294
|
+
const initialVelocity = velocity.calculateProgressVelocity({
|
|
295
|
+
animations,
|
|
296
|
+
shouldDismiss,
|
|
297
|
+
event,
|
|
298
|
+
dimensions,
|
|
299
|
+
directions,
|
|
300
|
+
});
|
|
301
|
+
|
|
297
302
|
startScreenTransition({
|
|
298
303
|
target: shouldDismiss ? "close" : "open",
|
|
299
304
|
onAnimationFinish: shouldDismiss ? handleDismiss : undefined,
|
|
300
305
|
spec: transitionSpec,
|
|
301
|
-
velocity,
|
|
302
306
|
animations,
|
|
307
|
+
initialVelocity,
|
|
303
308
|
});
|
|
304
309
|
},
|
|
305
310
|
[
|
|
@@ -267,12 +267,12 @@ const SceneView = ({
|
|
|
267
267
|
}, [canGoBack, backTitle]);
|
|
268
268
|
|
|
269
269
|
const isRemovePrevented = preventedRoutes[route.key]?.preventRemove;
|
|
270
|
+
|
|
270
271
|
const modifiedPresentation = enableTransitions
|
|
271
272
|
? "containedTransparentModal"
|
|
272
273
|
: presentation === "card"
|
|
273
274
|
? "push"
|
|
274
275
|
: presentation;
|
|
275
|
-
|
|
276
276
|
const modifiedAnimation = enableTransitions ? "none" : animation;
|
|
277
277
|
const modifiedHeaderShown =
|
|
278
278
|
enableTransitions || header !== undefined ? false : headerShown;
|
|
@@ -8,7 +8,8 @@ interface StartScreenTransitionProps {
|
|
|
8
8
|
spec?: TransitionSpec;
|
|
9
9
|
onAnimationFinish?: (finished: boolean) => void;
|
|
10
10
|
animations: AnimationMap;
|
|
11
|
-
velocity
|
|
11
|
+
/** Optional initial velocity for spring-based progress (units: progress/sec). */
|
|
12
|
+
initialVelocity?: number;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export const startScreenTransition = ({
|
|
@@ -16,12 +17,20 @@ export const startScreenTransition = ({
|
|
|
16
17
|
spec,
|
|
17
18
|
onAnimationFinish,
|
|
18
19
|
animations,
|
|
19
|
-
|
|
20
|
+
initialVelocity,
|
|
20
21
|
}: StartScreenTransitionProps) => {
|
|
21
22
|
"worklet";
|
|
22
23
|
const value = target === "open" ? 1 : 0;
|
|
23
24
|
const config = target === "open" ? spec?.open : spec?.close;
|
|
24
25
|
|
|
26
|
+
const isSpringConfig =
|
|
27
|
+
!!config && !("duration" in config) && !("easing" in config);
|
|
28
|
+
|
|
29
|
+
const effectiveConfig =
|
|
30
|
+
isSpringConfig && typeof initialVelocity === "number"
|
|
31
|
+
? { ...config, velocity: initialVelocity }
|
|
32
|
+
: config;
|
|
33
|
+
|
|
25
34
|
const { progress, animating, closing } = animations;
|
|
26
35
|
|
|
27
36
|
if (target === "close") {
|
|
@@ -40,13 +49,13 @@ export const startScreenTransition = ({
|
|
|
40
49
|
|
|
41
50
|
animating.value = 1;
|
|
42
51
|
|
|
43
|
-
progress.value = animate(value,
|
|
52
|
+
progress.value = animate(value, effectiveConfig, (finished) => {
|
|
44
53
|
"worklet";
|
|
45
54
|
if (finished) {
|
|
46
|
-
animating.value = 0;
|
|
47
55
|
if (onAnimationFinish) {
|
|
48
56
|
runOnJS(onAnimationFinish)(finished);
|
|
49
57
|
}
|
|
58
|
+
animating.value = 0;
|
|
50
59
|
}
|
|
51
60
|
});
|
|
52
61
|
};
|
|
@@ -22,10 +22,7 @@ interface DetermineDismissalProps {
|
|
|
22
22
|
gestureVelocityImpact: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
// This produces consistent behavior across devices and enables realistic bounce.
|
|
27
|
-
|
|
28
|
-
const MAX_PROGRESS_VELOCITY = 3.5; // ~3 progress units/second
|
|
25
|
+
import { velocity as V } from "./velocity";
|
|
29
26
|
|
|
30
27
|
const getAxisThreshold = ({
|
|
31
28
|
translation,
|
|
@@ -34,31 +31,14 @@ const getAxisThreshold = ({
|
|
|
34
31
|
gestureVelocityImpact,
|
|
35
32
|
}: GetAxisThresholdProps) => {
|
|
36
33
|
"worklet";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
return V.shouldPassDismissalThreshold(
|
|
35
|
+
translation,
|
|
36
|
+
velocity,
|
|
37
|
+
screenSize,
|
|
38
|
+
gestureVelocityImpact,
|
|
42
39
|
);
|
|
43
40
|
};
|
|
44
41
|
|
|
45
|
-
const getVelocity = (
|
|
46
|
-
dimensions: { width: number; height: number },
|
|
47
|
-
velocityX: number,
|
|
48
|
-
velocityY: number,
|
|
49
|
-
dismissAxis: "x" | "y",
|
|
50
|
-
) => {
|
|
51
|
-
"worklet";
|
|
52
|
-
const axisSize = dismissAxis === "y" ? dimensions.height : dimensions.width;
|
|
53
|
-
const axisVelocityPx = dismissAxis === "y" ? velocityY : velocityX;
|
|
54
|
-
let velocity = axisVelocityPx / Math.max(1, axisSize);
|
|
55
|
-
|
|
56
|
-
if (velocity > MAX_PROGRESS_VELOCITY) velocity = MAX_PROGRESS_VELOCITY;
|
|
57
|
-
if (velocity < -MAX_PROGRESS_VELOCITY) velocity = -MAX_PROGRESS_VELOCITY;
|
|
58
|
-
|
|
59
|
-
return velocity;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
42
|
export const determineDismissal = ({
|
|
63
43
|
event,
|
|
64
44
|
directions,
|
|
@@ -68,45 +48,24 @@ export const determineDismissal = ({
|
|
|
68
48
|
"worklet";
|
|
69
49
|
|
|
70
50
|
let shouldDismiss: boolean = false;
|
|
71
|
-
let dismissAxis: "x" | "y" = "x";
|
|
72
51
|
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
screenSize: dimensions.height,
|
|
78
|
-
gestureVelocityImpact,
|
|
79
|
-
});
|
|
80
|
-
if (dismiss) {
|
|
81
|
-
dismissAxis = "y";
|
|
82
|
-
shouldDismiss = true;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (directions.verticalInverted && event.translationY < 0) {
|
|
52
|
+
if (
|
|
53
|
+
(directions.vertical && event.translationY > 0) ||
|
|
54
|
+
(directions.verticalInverted && event.translationY < 0)
|
|
55
|
+
) {
|
|
86
56
|
const dismiss = getAxisThreshold({
|
|
87
57
|
translation: event.translationY,
|
|
88
58
|
velocity: event.velocityY,
|
|
89
59
|
screenSize: dimensions.height,
|
|
90
60
|
gestureVelocityImpact,
|
|
91
61
|
});
|
|
92
|
-
if (dismiss)
|
|
93
|
-
dismissAxis = "y";
|
|
94
|
-
shouldDismiss = true;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (directions.horizontal && event.translationX > 0) {
|
|
98
|
-
const dismiss = getAxisThreshold({
|
|
99
|
-
translation: event.translationX,
|
|
100
|
-
velocity: event.velocityX,
|
|
101
|
-
screenSize: dimensions.width,
|
|
102
|
-
gestureVelocityImpact,
|
|
103
|
-
});
|
|
104
|
-
if (dismiss) {
|
|
105
|
-
dismissAxis = "x";
|
|
106
|
-
shouldDismiss = true;
|
|
107
|
-
}
|
|
62
|
+
if (dismiss) shouldDismiss = true;
|
|
108
63
|
}
|
|
109
|
-
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
(directions.horizontal && event.translationX > 0) ||
|
|
67
|
+
(directions.horizontalInverted && event.translationX < 0)
|
|
68
|
+
) {
|
|
110
69
|
const dismiss = getAxisThreshold({
|
|
111
70
|
translation: event.translationX,
|
|
112
71
|
velocity: event.velocityX,
|
|
@@ -114,18 +73,8 @@ export const determineDismissal = ({
|
|
|
114
73
|
gestureVelocityImpact,
|
|
115
74
|
});
|
|
116
75
|
|
|
117
|
-
if (dismiss)
|
|
118
|
-
dismissAxis = "x";
|
|
119
|
-
shouldDismiss = true;
|
|
120
|
-
}
|
|
76
|
+
if (dismiss) shouldDismiss = true;
|
|
121
77
|
}
|
|
122
78
|
|
|
123
|
-
|
|
124
|
-
dimensions,
|
|
125
|
-
event.velocityX,
|
|
126
|
-
event.velocityY,
|
|
127
|
-
dismissAxis,
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
return { shouldDismiss, velocity };
|
|
79
|
+
return { shouldDismiss };
|
|
131
80
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GestureStateChangeEvent,
|
|
3
|
+
PanGestureHandlerEventPayload,
|
|
4
|
+
} from "react-native-gesture-handler";
|
|
5
|
+
import type { GestureMap } from "../../stores/gestures";
|
|
6
|
+
import type { AnimationConfig } from "../../types/animation";
|
|
7
|
+
import { animate } from "../animation/animate";
|
|
8
|
+
import { velocity } from "./velocity";
|
|
9
|
+
|
|
10
|
+
interface ResetGestureValuesProps {
|
|
11
|
+
spec?: AnimationConfig;
|
|
12
|
+
gestures: GestureMap;
|
|
13
|
+
shouldDismiss: boolean;
|
|
14
|
+
event: GestureStateChangeEvent<PanGestureHandlerEventPayload>;
|
|
15
|
+
dimensions: { width: number; height: number };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const resetGestureValues = ({
|
|
19
|
+
spec,
|
|
20
|
+
gestures,
|
|
21
|
+
shouldDismiss,
|
|
22
|
+
event,
|
|
23
|
+
dimensions,
|
|
24
|
+
}: ResetGestureValuesProps) => {
|
|
25
|
+
"worklet";
|
|
26
|
+
|
|
27
|
+
const vxNorm = velocity.normalize(event.velocityX, dimensions.width);
|
|
28
|
+
const vyNorm = velocity.normalize(event.velocityY, dimensions.height);
|
|
29
|
+
|
|
30
|
+
// Ensure spring starts moving toward zero using normalized gesture values for direction.
|
|
31
|
+
const nx =
|
|
32
|
+
gestures.normalizedX.value ||
|
|
33
|
+
event.translationX / Math.max(1, dimensions.width);
|
|
34
|
+
const ny =
|
|
35
|
+
gestures.normalizedY.value ||
|
|
36
|
+
event.translationY / Math.max(1, dimensions.height);
|
|
37
|
+
|
|
38
|
+
const vxTowardZero = velocity.calculateRestoreVelocity(nx, vxNorm);
|
|
39
|
+
const vyTowardZero = velocity.calculateRestoreVelocity(ny, vyNorm);
|
|
40
|
+
|
|
41
|
+
gestures.x.value = animate(0, { ...spec, velocity: vxTowardZero });
|
|
42
|
+
gestures.y.value = animate(0, { ...spec, velocity: vyTowardZero });
|
|
43
|
+
|
|
44
|
+
gestures.normalizedX.value = animate(0, { ...spec, velocity: vxTowardZero });
|
|
45
|
+
gestures.normalizedY.value = animate(0, { ...spec, velocity: vyTowardZero });
|
|
46
|
+
gestures.isDragging.value = 0;
|
|
47
|
+
gestures.isDismissing.value = Number(shouldDismiss);
|
|
48
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GestureStateChangeEvent,
|
|
3
|
+
PanGestureHandlerEventPayload,
|
|
4
|
+
} from "react-native-gesture-handler";
|
|
5
|
+
import { clamp } from "react-native-reanimated";
|
|
6
|
+
import type { AnimationMap } from "../../stores/animations";
|
|
7
|
+
|
|
8
|
+
interface CalculateProgressProps {
|
|
9
|
+
animations: AnimationMap;
|
|
10
|
+
shouldDismiss: boolean;
|
|
11
|
+
event: GestureStateChangeEvent<PanGestureHandlerEventPayload>;
|
|
12
|
+
dimensions: { width: number; height: number };
|
|
13
|
+
directions: {
|
|
14
|
+
horizontal: boolean;
|
|
15
|
+
horizontalInverted: boolean;
|
|
16
|
+
vertical: boolean;
|
|
17
|
+
verticalInverted: boolean;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const MAX_VELOCITY_MAGNITUDE = 3.2;
|
|
22
|
+
const NEAR_ZERO_THRESHOLD = 0.01;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Converts velocity from pixels/second to normalized units/second (0-1 range)
|
|
26
|
+
* and caps the result for stability
|
|
27
|
+
*/
|
|
28
|
+
const normalize = (velocityPixelsPerSecond: number, screenSize: number) => {
|
|
29
|
+
"worklet";
|
|
30
|
+
return clamp(
|
|
31
|
+
velocityPixelsPerSecond / Math.max(1, screenSize),
|
|
32
|
+
-MAX_VELOCITY_MAGNITUDE,
|
|
33
|
+
MAX_VELOCITY_MAGNITUDE,
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Calculates a normalized velocity that moves the current value toward zero.
|
|
39
|
+
* Used for spring-back animations when dismissing gestures.
|
|
40
|
+
*/
|
|
41
|
+
const calculateRestoreVelocity = (
|
|
42
|
+
currentValueNormalized: number,
|
|
43
|
+
baseVelocityNormalized: number,
|
|
44
|
+
threshold: number = NEAR_ZERO_THRESHOLD,
|
|
45
|
+
) => {
|
|
46
|
+
"worklet";
|
|
47
|
+
|
|
48
|
+
if (Math.abs(currentValueNormalized) < threshold) return 0;
|
|
49
|
+
|
|
50
|
+
const directionTowardZero = Math.sign(currentValueNormalized) || 1;
|
|
51
|
+
const clampedVelocity = Math.min(Math.abs(baseVelocityNormalized), 1);
|
|
52
|
+
|
|
53
|
+
return -directionTowardZero * clampedVelocity;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const calculateProgressVelocity = ({
|
|
57
|
+
animations,
|
|
58
|
+
shouldDismiss,
|
|
59
|
+
event,
|
|
60
|
+
dimensions,
|
|
61
|
+
directions,
|
|
62
|
+
}: CalculateProgressProps) => {
|
|
63
|
+
"worklet";
|
|
64
|
+
|
|
65
|
+
const currentProgress = animations.progress.value;
|
|
66
|
+
const targetProgress = shouldDismiss ? 0 : 1;
|
|
67
|
+
const progressDelta = targetProgress - currentProgress;
|
|
68
|
+
|
|
69
|
+
const progressDirection = progressDelta === 0 ? 0 : Math.sign(progressDelta);
|
|
70
|
+
|
|
71
|
+
const normalizedVelocityX = normalize(event.velocityX, dimensions.width);
|
|
72
|
+
const normalizedVelocityY = normalize(event.velocityY, dimensions.height);
|
|
73
|
+
|
|
74
|
+
const normalizedTranslationX = Math.abs(
|
|
75
|
+
event.translationX / dimensions.width,
|
|
76
|
+
);
|
|
77
|
+
const normalizedTranslationY = Math.abs(
|
|
78
|
+
event.translationY / dimensions.height,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const supportsHorizontalGestures =
|
|
82
|
+
directions.horizontal || directions.horizontalInverted;
|
|
83
|
+
|
|
84
|
+
const supportsVerticalGestures =
|
|
85
|
+
directions.vertical || directions.verticalInverted;
|
|
86
|
+
|
|
87
|
+
let progressVelocityMagnitude = 0;
|
|
88
|
+
|
|
89
|
+
// Determine which axis should drive the progress velocity
|
|
90
|
+
if (
|
|
91
|
+
supportsHorizontalGestures &&
|
|
92
|
+
(!supportsVerticalGestures ||
|
|
93
|
+
normalizedTranslationX >= normalizedTranslationY)
|
|
94
|
+
) {
|
|
95
|
+
progressVelocityMagnitude = Math.abs(normalizedVelocityX);
|
|
96
|
+
} else if (supportsVerticalGestures) {
|
|
97
|
+
progressVelocityMagnitude = Math.abs(normalizedVelocityY);
|
|
98
|
+
} else {
|
|
99
|
+
progressVelocityMagnitude = Math.max(
|
|
100
|
+
Math.abs(normalizedVelocityX),
|
|
101
|
+
Math.abs(normalizedVelocityY),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Apply direction and clamp to prevent overly energetic springs
|
|
106
|
+
return (
|
|
107
|
+
progressDirection *
|
|
108
|
+
clamp(progressVelocityMagnitude, 0, MAX_VELOCITY_MAGNITUDE)
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Determines if a gesture should trigger dismissal based on combined
|
|
114
|
+
* translation and velocity in normalized screen units (0-1 range).
|
|
115
|
+
*
|
|
116
|
+
* Formula: |translation/screen + clamp(velocity/screen, ±1) * velocityWeight| > 0.5
|
|
117
|
+
*/
|
|
118
|
+
const shouldPassDismissalThreshold = (
|
|
119
|
+
translationPixels: number,
|
|
120
|
+
velocityPixelsPerSecond: number,
|
|
121
|
+
screenSize: number,
|
|
122
|
+
velocityWeight: number,
|
|
123
|
+
) => {
|
|
124
|
+
"worklet";
|
|
125
|
+
|
|
126
|
+
const normalizedTranslation = translationPixels / Math.max(1, screenSize);
|
|
127
|
+
const normalizedVelocity = normalize(velocityPixelsPerSecond, screenSize);
|
|
128
|
+
|
|
129
|
+
const projectedNormalizedPosition =
|
|
130
|
+
normalizedTranslation + normalizedVelocity * velocityWeight;
|
|
131
|
+
|
|
132
|
+
const exceedsThreshold = Math.abs(projectedNormalizedPosition) > 0.5;
|
|
133
|
+
|
|
134
|
+
const hasMovement = translationPixels !== 0 || velocityPixelsPerSecond !== 0;
|
|
135
|
+
|
|
136
|
+
return exceedsThreshold && hasMovement;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const velocity = {
|
|
140
|
+
normalize,
|
|
141
|
+
calculateRestoreVelocity,
|
|
142
|
+
calculateProgressVelocity,
|
|
143
|
+
shouldPassDismissalThreshold,
|
|
144
|
+
};
|