react-native-screen-transitions 3.3.0-beta.0 → 3.3.0-beta.1
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 +104 -9
- package/lib/commonjs/blank-stack/components/adjusted-screen.js +2 -2
- package/lib/commonjs/blank-stack/components/adjusted-screen.js.map +1 -1
- package/lib/commonjs/shared/constants.js +36 -10
- package/lib/commonjs/shared/constants.js.map +1 -1
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +25 -18
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +0 -25
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js +71 -63
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
- package/lib/commonjs/shared/hooks/navigation/use-screen-state.js +9 -37
- package/lib/commonjs/shared/hooks/navigation/use-screen-state.js.map +1 -1
- package/lib/commonjs/shared/utils/animation/animate-to-progress.js +5 -1
- package/lib/commonjs/shared/utils/animation/animate-to-progress.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +90 -23
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/commonjs/shared/utils/logger.js +22 -0
- package/lib/commonjs/shared/utils/logger.js.map +1 -0
- package/lib/module/blank-stack/components/adjusted-screen.js +1 -1
- package/lib/module/blank-stack/components/adjusted-screen.js.map +1 -1
- package/lib/module/shared/constants.js +34 -9
- package/lib/module/shared/constants.js.map +1 -1
- package/lib/module/shared/hooks/animation/use-screen-animation.js +25 -18
- package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +0 -25
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +66 -58
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
- package/lib/module/shared/hooks/navigation/use-screen-state.js +9 -37
- package/lib/module/shared/hooks/navigation/use-screen-state.js.map +1 -1
- package/lib/module/shared/utils/animation/animate-to-progress.js +5 -1
- package/lib/module/shared/utils/animation/animate-to-progress.js.map +1 -1
- package/lib/module/shared/utils/gesture/check-gesture-activation.js +90 -23
- package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/module/shared/utils/logger.js +17 -0
- package/lib/module/shared/utils/logger.js.map +1 -0
- package/lib/typescript/blank-stack/components/adjusted-screen.d.ts.map +1 -1
- package/lib/typescript/shared/constants.d.ts +9 -0
- package/lib/typescript/shared/constants.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts +1 -16
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts +0 -7
- package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts.map +1 -1
- package/lib/typescript/shared/types/animation.types.d.ts +9 -0
- package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/animation/animate-to-progress.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +4 -5
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
- package/lib/typescript/shared/utils/logger.d.ts +6 -0
- package/lib/typescript/shared/utils/logger.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/blank-stack/components/adjusted-screen.tsx +1 -1
- package/src/shared/__tests__/derivations.test.ts +1 -0
- package/src/shared/__tests__/gesture-activation.test.ts +29 -56
- package/src/shared/constants.ts +36 -9
- package/src/shared/hooks/animation/use-screen-animation.tsx +32 -21
- package/src/shared/hooks/gestures/use-build-gestures.tsx +2 -34
- package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +89 -91
- package/src/shared/hooks/navigation/use-screen-state.tsx +9 -56
- package/src/shared/types/animation.types.ts +10 -0
- package/src/shared/utils/animation/animate-to-progress.ts +4 -1
- package/src/shared/utils/gesture/check-gesture-activation.ts +74 -23
- package/src/shared/utils/logger.ts +15 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
+
import { useWindowDimensions } from "react-native";
|
|
2
3
|
import type {
|
|
3
4
|
GestureStateChangeEvent,
|
|
4
5
|
GestureTouchEvent,
|
|
@@ -8,17 +9,20 @@ import type {
|
|
|
8
9
|
import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
|
|
9
10
|
import { type SharedValue, useSharedValue } from "react-native-reanimated";
|
|
10
11
|
import { DefaultSnapSpec } from "../../configs/specs";
|
|
11
|
-
import { FALSE, TRUE } from "../../constants";
|
|
12
|
-
import type { ScrollConfig } from "../../providers/gestures.provider";
|
|
13
|
-
import type { AnimationStoreMap } from "../../stores/animation.store";
|
|
14
|
-
import type { GestureStoreMap } from "../../stores/gesture.store";
|
|
15
|
-
import type { TransitionSpec } from "../../types/animation.types";
|
|
16
12
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
DEFAULT_GESTURE_ACTIVATION_AREA,
|
|
14
|
+
DEFAULT_GESTURE_DIRECTION,
|
|
15
|
+
DEFAULT_GESTURE_DRIVES_PROGRESS,
|
|
16
|
+
EPSILON,
|
|
17
|
+
FALSE,
|
|
18
|
+
GESTURE_VELOCITY_IMPACT,
|
|
19
|
+
TRUE,
|
|
20
|
+
} from "../../constants";
|
|
21
|
+
import type { ScrollConfig } from "../../providers/gestures.provider";
|
|
22
|
+
import { useKeys } from "../../providers/screen/keys.provider";
|
|
23
|
+
import { AnimationStore } from "../../stores/animation.store";
|
|
24
|
+
import { GestureStore } from "../../stores/gesture.store";
|
|
25
|
+
import { GestureOffsetState } from "../../types/gesture.types";
|
|
22
26
|
import { animateToProgress } from "../../utils/animation/animate-to-progress";
|
|
23
27
|
import {
|
|
24
28
|
applyOffsetRules,
|
|
@@ -30,50 +34,71 @@ import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progres
|
|
|
30
34
|
import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
|
|
31
35
|
import { validateSnapPoints } from "../../utils/gesture/validate-snap-points";
|
|
32
36
|
import { velocity } from "../../utils/gesture/velocity";
|
|
37
|
+
import { logger } from "../../utils/logger";
|
|
33
38
|
import useStableCallbackValue from "../use-stable-callback-value";
|
|
34
39
|
|
|
35
40
|
interface UseScreenGestureHandlersProps {
|
|
36
|
-
dimensions: Layout;
|
|
37
|
-
animations: AnimationStoreMap;
|
|
38
|
-
gestureAnimationValues: GestureStoreMap;
|
|
39
|
-
gestureDirection: GestureDirection | GestureDirection[];
|
|
40
|
-
gestureDrivesProgress: boolean;
|
|
41
|
-
gestureVelocityImpact: number;
|
|
42
41
|
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
43
|
-
gestureActivationArea: GestureActivationArea;
|
|
44
|
-
gestureResponseDistance?: number;
|
|
45
42
|
ancestorIsDismissing?: SharedValue<number> | null;
|
|
46
|
-
snapPoints?: number[];
|
|
47
43
|
canDismiss: boolean;
|
|
48
|
-
transitionSpec?: TransitionSpec;
|
|
49
44
|
handleDismiss: () => void;
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
export const useScreenGestureHandlers = ({
|
|
53
|
-
dimensions,
|
|
54
|
-
animations,
|
|
55
|
-
gestureAnimationValues,
|
|
56
|
-
gestureDirection,
|
|
57
|
-
gestureDrivesProgress,
|
|
58
|
-
gestureVelocityImpact,
|
|
59
48
|
scrollConfig,
|
|
60
|
-
gestureActivationArea,
|
|
61
|
-
gestureResponseDistance,
|
|
62
49
|
ancestorIsDismissing,
|
|
63
|
-
snapPoints: rawSnapPoints,
|
|
64
50
|
canDismiss,
|
|
65
|
-
transitionSpec,
|
|
66
51
|
handleDismiss,
|
|
67
52
|
}: UseScreenGestureHandlersProps) => {
|
|
53
|
+
const dimensions = useWindowDimensions();
|
|
54
|
+
const { current } = useKeys();
|
|
55
|
+
|
|
56
|
+
const animations = AnimationStore.getAll(current.route.key);
|
|
57
|
+
const gestureAnimationValues = GestureStore.getRouteGestures(
|
|
58
|
+
current.route.key,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
gestureDirection = DEFAULT_GESTURE_DIRECTION,
|
|
63
|
+
gestureDrivesProgress = DEFAULT_GESTURE_DRIVES_PROGRESS,
|
|
64
|
+
gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
|
|
65
|
+
gestureActivationArea = DEFAULT_GESTURE_ACTIVATION_AREA,
|
|
66
|
+
gestureResponseDistance,
|
|
67
|
+
transitionSpec,
|
|
68
|
+
snapPoints: rawSnapPoints,
|
|
69
|
+
} = current.options;
|
|
70
|
+
|
|
68
71
|
const { hasSnapPoints, snapPoints, minSnapPoint, maxSnapPoint } = useMemo(
|
|
69
72
|
() => validateSnapPoints({ snapPoints: rawSnapPoints, canDismiss }),
|
|
70
73
|
[rawSnapPoints, canDismiss],
|
|
71
74
|
);
|
|
72
75
|
|
|
73
76
|
const directions = useMemo(() => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
if (hasSnapPoints && Array.isArray(gestureDirection)) {
|
|
78
|
+
/**
|
|
79
|
+
* Unsure if this behavior will change in the future, as I cannot find a use case as to why
|
|
80
|
+
* you would want multiple gesture dismisals for a sheet.
|
|
81
|
+
*
|
|
82
|
+
* e.g. When defining a snap point with a gesture of vertical ( default ), the system
|
|
83
|
+
* assumes that the inverse ( vertical-inverted ), will grow the sheet.
|
|
84
|
+
*/
|
|
85
|
+
logger.warn(
|
|
86
|
+
`gestureDirection array is not supported with snapPoints. ` +
|
|
87
|
+
`Only the first direction "${gestureDirection[0]}" will be used. ` +
|
|
88
|
+
`Snap points define a single axis of movement, so only one gesture direction is needed.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// When snap points are defined, use only the first direction from the array
|
|
93
|
+
const effectiveDirection = hasSnapPoints
|
|
94
|
+
? Array.isArray(gestureDirection)
|
|
95
|
+
? gestureDirection[0]
|
|
96
|
+
: gestureDirection
|
|
97
|
+
: gestureDirection;
|
|
98
|
+
|
|
99
|
+
const directionsArray = Array.isArray(effectiveDirection)
|
|
100
|
+
? effectiveDirection
|
|
101
|
+
: [effectiveDirection];
|
|
77
102
|
|
|
78
103
|
const isBidirectional = directionsArray.includes("bidirectional");
|
|
79
104
|
|
|
@@ -128,7 +153,7 @@ export const useScreenGestureHandlers = ({
|
|
|
128
153
|
|
|
129
154
|
// If an ancestor navigator is already dismissing via gesture, block new gestures here.
|
|
130
155
|
if (ancestorIsDismissing?.value) {
|
|
131
|
-
gestureOffsetState.
|
|
156
|
+
gestureOffsetState.value = GestureOffsetState.FAILED;
|
|
132
157
|
manager.fail();
|
|
133
158
|
return;
|
|
134
159
|
}
|
|
@@ -176,13 +201,13 @@ export const useScreenGestureHandlers = ({
|
|
|
176
201
|
}
|
|
177
202
|
|
|
178
203
|
if (isSwipingDown) {
|
|
179
|
-
gestureAnimationValues.direction.
|
|
204
|
+
gestureAnimationValues.direction.value = "vertical";
|
|
180
205
|
} else if (isSwipingUp) {
|
|
181
|
-
gestureAnimationValues.direction.
|
|
206
|
+
gestureAnimationValues.direction.value = "vertical-inverted";
|
|
182
207
|
} else if (isSwipingRight) {
|
|
183
|
-
gestureAnimationValues.direction.
|
|
208
|
+
gestureAnimationValues.direction.value = "horizontal";
|
|
184
209
|
} else if (isSwipingLeft) {
|
|
185
|
-
gestureAnimationValues.direction.
|
|
210
|
+
gestureAnimationValues.direction.value = "horizontal-inverted";
|
|
186
211
|
}
|
|
187
212
|
|
|
188
213
|
manager.activate();
|
|
@@ -190,18 +215,9 @@ export const useScreenGestureHandlers = ({
|
|
|
190
215
|
}
|
|
191
216
|
|
|
192
217
|
// Touch IS on ScrollView - apply scroll-aware rules
|
|
193
|
-
const scrollX = scrollCfg?.x ?? 0;
|
|
194
|
-
const scrollY = scrollCfg?.y ?? 0;
|
|
195
|
-
const maxScrollX = scrollCfg?.contentWidth
|
|
196
|
-
? scrollCfg.contentWidth - scrollCfg.layoutWidth
|
|
197
|
-
: 0;
|
|
198
|
-
const maxScrollY = scrollCfg?.contentHeight
|
|
199
|
-
? scrollCfg.contentHeight - scrollCfg.layoutHeight
|
|
200
|
-
: 0;
|
|
201
|
-
|
|
202
218
|
// Snap mode: determine if sheet can still expand
|
|
203
219
|
const canExpandMore =
|
|
204
|
-
hasSnapPoints && animations.progress.value < maxSnapPoint -
|
|
220
|
+
hasSnapPoints && animations.progress.value < maxSnapPoint - EPSILON;
|
|
205
221
|
|
|
206
222
|
const { shouldActivate, direction: activatedDirection } =
|
|
207
223
|
checkScrollAwareActivation({
|
|
@@ -212,10 +228,7 @@ export const useScreenGestureHandlers = ({
|
|
|
212
228
|
isSwipingLeft,
|
|
213
229
|
},
|
|
214
230
|
directions,
|
|
215
|
-
|
|
216
|
-
scrollY,
|
|
217
|
-
maxScrollX,
|
|
218
|
-
maxScrollY,
|
|
231
|
+
scrollConfig: scrollCfg,
|
|
219
232
|
hasSnapPoints,
|
|
220
233
|
canExpandMore,
|
|
221
234
|
});
|
|
@@ -242,6 +255,7 @@ export const useScreenGestureHandlers = ({
|
|
|
242
255
|
gestureAnimationValues.isDragging.value = TRUE;
|
|
243
256
|
gestureAnimationValues.isDismissing.value = FALSE;
|
|
244
257
|
gestureStartProgress.value = animations.progress.value;
|
|
258
|
+
animations.animating.value = TRUE;
|
|
245
259
|
});
|
|
246
260
|
|
|
247
261
|
const onUpdate = useStableCallbackValue(
|
|
@@ -251,7 +265,6 @@ export const useScreenGestureHandlers = ({
|
|
|
251
265
|
const { translationX, translationY } = event;
|
|
252
266
|
const { width, height } = dimensions;
|
|
253
267
|
|
|
254
|
-
// Update gesture values (shared across all modes)
|
|
255
268
|
gestureAnimationValues.x.value = translationX;
|
|
256
269
|
gestureAnimationValues.y.value = translationY;
|
|
257
270
|
gestureAnimationValues.normalizedX.value = velocity.normalizeTranslation(
|
|
@@ -264,7 +277,6 @@ export const useScreenGestureHandlers = ({
|
|
|
264
277
|
);
|
|
265
278
|
|
|
266
279
|
if (hasSnapPoints && gestureDrivesProgress) {
|
|
267
|
-
// Snap mode: bidirectional tracking on snap axis
|
|
268
280
|
const isHorizontal = snapAxis === "horizontal";
|
|
269
281
|
const translation = isHorizontal ? translationX : translationY;
|
|
270
282
|
const dimension = isHorizontal ? width : height;
|
|
@@ -283,43 +295,30 @@ export const useScreenGestureHandlers = ({
|
|
|
283
295
|
Math.min(maxSnapPoint, gestureStartProgress.value + progressDelta),
|
|
284
296
|
);
|
|
285
297
|
} else if (gestureDrivesProgress) {
|
|
286
|
-
// Standard mode: find max progress across allowed directions
|
|
287
|
-
const axes = [
|
|
288
|
-
{
|
|
289
|
-
enabled: directions.horizontal,
|
|
290
|
-
translation: translationX,
|
|
291
|
-
dimension: width,
|
|
292
|
-
sign: 1,
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
enabled: directions.horizontalInverted,
|
|
296
|
-
translation: translationX,
|
|
297
|
-
dimension: width,
|
|
298
|
-
sign: -1,
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
enabled: directions.vertical,
|
|
302
|
-
translation: translationY,
|
|
303
|
-
dimension: height,
|
|
304
|
-
sign: 1,
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
enabled: directions.verticalInverted,
|
|
308
|
-
translation: translationY,
|
|
309
|
-
dimension: height,
|
|
310
|
-
sign: -1,
|
|
311
|
-
},
|
|
312
|
-
];
|
|
313
|
-
|
|
314
298
|
let maxProgress = 0;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
299
|
+
|
|
300
|
+
// Horizontal swipe right (positive X)
|
|
301
|
+
if (directions.horizontal && translationX > 0) {
|
|
302
|
+
const progress = mapGestureToProgress(translationX, width);
|
|
303
|
+
maxProgress = Math.max(maxProgress, progress);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Horizontal inverted swipe left (negative X)
|
|
307
|
+
if (directions.horizontalInverted && translationX < 0) {
|
|
308
|
+
const progress = mapGestureToProgress(-translationX, width);
|
|
309
|
+
maxProgress = Math.max(maxProgress, progress);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Vertical swipe down (positive Y)
|
|
313
|
+
if (directions.vertical && translationY > 0) {
|
|
314
|
+
const progress = mapGestureToProgress(translationY, height);
|
|
315
|
+
maxProgress = Math.max(maxProgress, progress);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Vertical inverted swipe up (negative Y)
|
|
319
|
+
if (directions.verticalInverted && translationY < 0) {
|
|
320
|
+
const progress = mapGestureToProgress(-translationY, height);
|
|
321
|
+
maxProgress = Math.max(maxProgress, progress);
|
|
323
322
|
}
|
|
324
323
|
|
|
325
324
|
animations.progress.value = Math.max(
|
|
@@ -394,7 +393,6 @@ export const useScreenGestureHandlers = ({
|
|
|
394
393
|
initialVelocity,
|
|
395
394
|
});
|
|
396
395
|
} else {
|
|
397
|
-
// Standard mode: use determineDismissal
|
|
398
396
|
const result = determineDismissal({
|
|
399
397
|
event,
|
|
400
398
|
directions,
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { Route } from "@react-navigation/native";
|
|
2
2
|
import { useCallback, useMemo } from "react";
|
|
3
|
-
import {
|
|
4
|
-
runOnUI,
|
|
5
|
-
type SharedValue,
|
|
6
|
-
useDerivedValue,
|
|
7
|
-
} from "react-native-reanimated";
|
|
3
|
+
import { runOnUI, useDerivedValue } from "react-native-reanimated";
|
|
8
4
|
import { DefaultSnapSpec } from "../../configs/specs";
|
|
9
5
|
import {
|
|
10
6
|
type BaseDescriptor,
|
|
@@ -62,13 +58,6 @@ export interface ScreenState<
|
|
|
62
58
|
* @param index - The index of the snap point to snap to (0-based)
|
|
63
59
|
*/
|
|
64
60
|
snapTo: (index: number) => void;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Animated value representing the current snap point index.
|
|
68
|
-
* Interpolates between indices during gestures (e.g., 0.5 means halfway between snap 0 and 1).
|
|
69
|
-
* Returns -1 if no snap points are defined.
|
|
70
|
-
*/
|
|
71
|
-
animatedSnapIndex: SharedValue<number>;
|
|
72
61
|
}
|
|
73
62
|
|
|
74
63
|
/**
|
|
@@ -100,63 +89,29 @@ export function useScreenState<
|
|
|
100
89
|
}, [scenes, focusedIndex]);
|
|
101
90
|
|
|
102
91
|
const currentOptions = current.options;
|
|
103
|
-
const snapPoints = currentOptions?.snapPoints;
|
|
104
92
|
const animations = useMemo(
|
|
105
93
|
() => AnimationStore.getAll(current.route.key),
|
|
106
94
|
[current.route.key],
|
|
107
95
|
);
|
|
108
96
|
|
|
109
|
-
// Pre-sort snap points for the derived value (avoids sorting in worklet)
|
|
110
|
-
const sortedSnapPoints = useMemo(
|
|
111
|
-
() => (snapPoints ? [...snapPoints].sort((a, b) => a - b) : []),
|
|
112
|
-
[snapPoints],
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
const animatedSnapIndex = useDerivedValue(() => {
|
|
116
|
-
if (sortedSnapPoints.length === 0) {
|
|
117
|
-
return -1;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const progress = animations.progress.value;
|
|
121
|
-
|
|
122
|
-
// Below first snap point
|
|
123
|
-
if (progress <= sortedSnapPoints[0]) {
|
|
124
|
-
return 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Above last snap point
|
|
128
|
-
if (progress >= sortedSnapPoints[sortedSnapPoints.length - 1]) {
|
|
129
|
-
return sortedSnapPoints.length - 1;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Find segment and interpolate
|
|
133
|
-
for (let i = 0; i < sortedSnapPoints.length - 1; i++) {
|
|
134
|
-
if (progress <= sortedSnapPoints[i + 1]) {
|
|
135
|
-
const t =
|
|
136
|
-
(progress - sortedSnapPoints[i]) /
|
|
137
|
-
(sortedSnapPoints[i + 1] - sortedSnapPoints[i]);
|
|
138
|
-
return i + t;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return sortedSnapPoints.length - 1;
|
|
143
|
-
});
|
|
144
|
-
|
|
145
97
|
const snapTo = useCallback(
|
|
146
98
|
(targetIndex: number) => {
|
|
147
|
-
|
|
99
|
+
const points = currentOptions?.snapPoints;
|
|
100
|
+
if (!points || points.length === 0) {
|
|
148
101
|
console.warn("snapTo called but no snapPoints defined");
|
|
149
102
|
return;
|
|
150
103
|
}
|
|
151
104
|
|
|
152
|
-
|
|
105
|
+
const sorted = [...points].sort((a, b) => a - b);
|
|
106
|
+
|
|
107
|
+
if (targetIndex < 0 || targetIndex >= sorted.length) {
|
|
153
108
|
console.warn(
|
|
154
|
-
`snapTo index ${targetIndex} out of bounds (0-${
|
|
109
|
+
`snapTo index ${targetIndex} out of bounds (0-${sorted.length - 1})`,
|
|
155
110
|
);
|
|
156
111
|
return;
|
|
157
112
|
}
|
|
158
113
|
|
|
159
|
-
const targetProgress =
|
|
114
|
+
const targetProgress = sorted[targetIndex];
|
|
160
115
|
|
|
161
116
|
runOnUI(() => {
|
|
162
117
|
"worklet";
|
|
@@ -174,7 +129,7 @@ export function useScreenState<
|
|
|
174
129
|
});
|
|
175
130
|
})();
|
|
176
131
|
},
|
|
177
|
-
[
|
|
132
|
+
[currentOptions?.snapPoints, animations, focusedScene],
|
|
178
133
|
);
|
|
179
134
|
|
|
180
135
|
return useMemo(
|
|
@@ -187,7 +142,6 @@ export function useScreenState<
|
|
|
187
142
|
meta: focusedScene?.descriptor?.options?.meta,
|
|
188
143
|
navigation: current.navigation as TNavigation,
|
|
189
144
|
snapTo,
|
|
190
|
-
animatedSnapIndex,
|
|
191
145
|
}),
|
|
192
146
|
[
|
|
193
147
|
index,
|
|
@@ -197,7 +151,6 @@ export function useScreenState<
|
|
|
197
151
|
current.navigation,
|
|
198
152
|
current.route,
|
|
199
153
|
snapTo,
|
|
200
|
-
animatedSnapIndex,
|
|
201
154
|
],
|
|
202
155
|
);
|
|
203
156
|
}
|
|
@@ -142,6 +142,16 @@ export interface ScreenInterpolationProps {
|
|
|
142
142
|
*/
|
|
143
143
|
stackProgress: number;
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Animated index of the current snap point.
|
|
147
|
+
* Interpolates between indices during gestures/animations.
|
|
148
|
+
* - Returns -1 if no snap points are defined
|
|
149
|
+
* - Returns 0 when at or below first snap point
|
|
150
|
+
* - Returns fractional values between snap points (e.g., 1.5 = halfway between snap 1 and 2)
|
|
151
|
+
* - Returns length-1 when at or above last snap point
|
|
152
|
+
*/
|
|
153
|
+
snapIndex: number;
|
|
154
|
+
|
|
145
155
|
/**
|
|
146
156
|
* Function that provides access to bounds builders for creating shared element transitions.
|
|
147
157
|
*/
|
|
@@ -73,7 +73,10 @@ export const animateToProgress = ({
|
|
|
73
73
|
runOnJS(onAnimationFinish)(finished);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
animating
|
|
76
|
+
// Delay setting animating=FALSE by one frame to ensure final frame is painted
|
|
77
|
+
requestAnimationFrame(() => {
|
|
78
|
+
animating.set(FALSE);
|
|
79
|
+
});
|
|
77
80
|
}),
|
|
78
81
|
);
|
|
79
82
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
|
|
2
2
|
import type { SharedValue } from "react-native-reanimated";
|
|
3
|
+
import type { ScrollConfig } from "../../providers/gestures.provider";
|
|
3
4
|
import {
|
|
4
5
|
type ActivationArea,
|
|
5
6
|
type GestureActivationArea,
|
|
@@ -13,6 +14,7 @@ type Directions = {
|
|
|
13
14
|
verticalInverted: boolean;
|
|
14
15
|
horizontal: boolean;
|
|
15
16
|
horizontalInverted: boolean;
|
|
17
|
+
snapAxisInverted?: boolean;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
interface CheckGestureActivationProps {
|
|
@@ -317,10 +319,7 @@ interface ScrollAwareActivationParams {
|
|
|
317
319
|
isSwipingLeft: boolean;
|
|
318
320
|
};
|
|
319
321
|
directions: Directions;
|
|
320
|
-
|
|
321
|
-
scrollY: number;
|
|
322
|
-
maxScrollX: number;
|
|
323
|
-
maxScrollY: number;
|
|
322
|
+
scrollConfig: ScrollConfig | null;
|
|
324
323
|
hasSnapPoints?: boolean;
|
|
325
324
|
canExpandMore?: boolean;
|
|
326
325
|
}
|
|
@@ -338,10 +337,7 @@ type GestureDirection =
|
|
|
338
337
|
export function checkScrollAwareActivation({
|
|
339
338
|
swipeInfo,
|
|
340
339
|
directions,
|
|
341
|
-
|
|
342
|
-
scrollY,
|
|
343
|
-
maxScrollX,
|
|
344
|
-
maxScrollY,
|
|
340
|
+
scrollConfig,
|
|
345
341
|
hasSnapPoints,
|
|
346
342
|
canExpandMore,
|
|
347
343
|
}: ScrollAwareActivationParams): {
|
|
@@ -353,6 +349,76 @@ export function checkScrollAwareActivation({
|
|
|
353
349
|
const { isSwipingDown, isSwipingUp, isSwipingRight, isSwipingLeft } =
|
|
354
350
|
swipeInfo;
|
|
355
351
|
|
|
352
|
+
// Extract scroll values from config
|
|
353
|
+
const scrollX = scrollConfig?.x ?? 0;
|
|
354
|
+
const scrollY = scrollConfig?.y ?? 0;
|
|
355
|
+
const maxScrollX = scrollConfig
|
|
356
|
+
? scrollConfig.contentWidth - scrollConfig.layoutWidth
|
|
357
|
+
: 0;
|
|
358
|
+
const maxScrollY = scrollConfig
|
|
359
|
+
? scrollConfig.contentHeight - scrollConfig.layoutHeight
|
|
360
|
+
: 0;
|
|
361
|
+
const snapAxisInverted = directions.snapAxisInverted;
|
|
362
|
+
|
|
363
|
+
// With snap points, gestures should only activate based on the PRIMARY scroll edge
|
|
364
|
+
// (the edge where the sheet originates from), not the opposite edge.
|
|
365
|
+
// This prevents the auto-enabled opposite direction from hijacking scrolls.
|
|
366
|
+
if (hasSnapPoints) {
|
|
367
|
+
const isVerticalAxis = directions.vertical || directions.verticalInverted;
|
|
368
|
+
const isHorizontalAxis =
|
|
369
|
+
directions.horizontal || directions.horizontalInverted;
|
|
370
|
+
|
|
371
|
+
if (isVerticalAxis) {
|
|
372
|
+
if (snapAxisInverted) {
|
|
373
|
+
// Sheet from TOP (vertical-inverted): only activate at scroll BOTTOM
|
|
374
|
+
if (scrollY >= maxScrollY) {
|
|
375
|
+
if (isSwipingUp) {
|
|
376
|
+
return { shouldActivate: true, direction: "vertical-inverted" };
|
|
377
|
+
}
|
|
378
|
+
if (isSwipingDown && canExpandMore) {
|
|
379
|
+
return { shouldActivate: true, direction: "vertical" };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
// Sheet from BOTTOM (vertical): only activate at scroll TOP
|
|
384
|
+
if (scrollY <= 0) {
|
|
385
|
+
if (isSwipingDown) {
|
|
386
|
+
return { shouldActivate: true, direction: "vertical" };
|
|
387
|
+
}
|
|
388
|
+
if (isSwipingUp && canExpandMore) {
|
|
389
|
+
return { shouldActivate: true, direction: "vertical-inverted" };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (isHorizontalAxis) {
|
|
396
|
+
if (snapAxisInverted) {
|
|
397
|
+
// Sheet from LEFT (horizontal-inverted): only activate at scroll RIGHT
|
|
398
|
+
if (scrollX >= maxScrollX) {
|
|
399
|
+
if (isSwipingLeft) {
|
|
400
|
+
return { shouldActivate: true, direction: "horizontal-inverted" };
|
|
401
|
+
}
|
|
402
|
+
if (isSwipingRight && canExpandMore) {
|
|
403
|
+
return { shouldActivate: true, direction: "horizontal" };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
// Sheet from RIGHT (horizontal): only activate at scroll LEFT
|
|
408
|
+
if (scrollX <= 0) {
|
|
409
|
+
if (isSwipingRight) {
|
|
410
|
+
return { shouldActivate: true, direction: "horizontal" };
|
|
411
|
+
}
|
|
412
|
+
if (isSwipingLeft && canExpandMore) {
|
|
413
|
+
return { shouldActivate: true, direction: "horizontal-inverted" };
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return { shouldActivate: false, direction: null };
|
|
420
|
+
}
|
|
421
|
+
|
|
356
422
|
if (directions.vertical && isSwipingDown && scrollY <= 0) {
|
|
357
423
|
return { shouldActivate: true, direction: "vertical" };
|
|
358
424
|
}
|
|
@@ -369,20 +435,5 @@ export function checkScrollAwareActivation({
|
|
|
369
435
|
return { shouldActivate: true, direction: "horizontal-inverted" };
|
|
370
436
|
}
|
|
371
437
|
|
|
372
|
-
if (hasSnapPoints && canExpandMore) {
|
|
373
|
-
// Vertical sheet: swipe up at scroll top → expand
|
|
374
|
-
const enabledYAxis = directions.vertical || directions.verticalInverted;
|
|
375
|
-
const enabledXAxis = directions.horizontal || directions.horizontalInverted;
|
|
376
|
-
|
|
377
|
-
if (enabledYAxis && isSwipingUp && scrollY <= 0) {
|
|
378
|
-
return { shouldActivate: true, direction: "vertical-inverted" };
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Horizontal sheet: swipe left at scroll left → expand
|
|
382
|
-
if (enabledXAxis && isSwipingLeft && scrollX <= 0) {
|
|
383
|
-
return { shouldActivate: true, direction: "horizontal-inverted" };
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
438
|
return { shouldActivate: false, direction: null };
|
|
388
439
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const LIBRARY_NAME = "react-native-screen-transitions";
|
|
2
|
+
|
|
3
|
+
export const logger = {
|
|
4
|
+
error(message: string) {
|
|
5
|
+
"worklet";
|
|
6
|
+
console.error(`[${LIBRARY_NAME}] ${message}`);
|
|
7
|
+
},
|
|
8
|
+
warn(message: string) {
|
|
9
|
+
"worklet";
|
|
10
|
+
console.warn(`[${LIBRARY_NAME}] ${message}`);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const error = (message: string) =>
|
|
15
|
+
new Error(`[${LIBRARY_NAME}] ${message}`);
|