react-native-screen-transitions 3.2.0 → 3.3.0-beta.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.
- package/README.md +1 -1
- package/lib/commonjs/shared/components/create-transition-aware-component.js +8 -2
- package/lib/commonjs/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/commonjs/shared/components/{root-transition-aware.js → screen-container.js} +28 -12
- package/lib/commonjs/shared/components/screen-container.js.map +1 -0
- package/lib/commonjs/shared/configs/presets.js +3 -3
- package/lib/commonjs/shared/configs/presets.js.map +1 -1
- package/lib/commonjs/shared/configs/specs.js +6 -1
- package/lib/commonjs/shared/configs/specs.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +36 -188
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js +334 -0
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -0
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +47 -4
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js +3 -3
- package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js.map +1 -1
- package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js +25 -3
- package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js.map +1 -1
- package/lib/commonjs/shared/hooks/navigation/use-screen-state.js +61 -2
- package/lib/commonjs/shared/hooks/navigation/use-screen-state.js.map +1 -1
- package/lib/commonjs/shared/hooks/use-backdrop-pointer-events.js +32 -0
- package/lib/commonjs/shared/hooks/use-backdrop-pointer-events.js.map +1 -0
- package/lib/commonjs/shared/providers/gestures.provider.js +4 -2
- package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/screen-composer.js +2 -2
- package/lib/commonjs/shared/providers/screen/screen-composer.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/styles.provider.js +41 -32
- package/lib/commonjs/shared/providers/screen/styles.provider.js.map +1 -1
- package/lib/commonjs/shared/utils/animation/{start-screen-transition.js → animate-to-progress.js} +11 -7
- package/lib/commonjs/shared/utils/animation/animate-to-progress.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +71 -0
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/determine-snap-target.js +56 -0
- package/lib/commonjs/shared/utils/gesture/determine-snap-target.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/validate-snap-points.js +31 -0
- package/lib/commonjs/shared/utils/gesture/validate-snap-points.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/velocity.js +11 -0
- package/lib/commonjs/shared/utils/gesture/velocity.js.map +1 -1
- package/lib/module/shared/components/create-transition-aware-component.js +8 -2
- package/lib/module/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/module/shared/components/screen-container.js +64 -0
- package/lib/module/shared/components/screen-container.js.map +1 -0
- package/lib/module/shared/configs/presets.js +3 -3
- package/lib/module/shared/configs/presets.js.map +1 -1
- package/lib/module/shared/configs/specs.js +5 -0
- package/lib/module/shared/configs/specs.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +36 -187
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +328 -0
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -0
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js +47 -4
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/module/shared/hooks/lifecycle/use-close-transition.js +3 -3
- package/lib/module/shared/hooks/lifecycle/use-close-transition.js.map +1 -1
- package/lib/module/shared/hooks/lifecycle/use-open-transition.js +25 -3
- package/lib/module/shared/hooks/lifecycle/use-open-transition.js.map +1 -1
- package/lib/module/shared/hooks/navigation/use-screen-state.js +63 -4
- package/lib/module/shared/hooks/navigation/use-screen-state.js.map +1 -1
- package/lib/module/shared/hooks/use-backdrop-pointer-events.js +28 -0
- package/lib/module/shared/hooks/use-backdrop-pointer-events.js.map +1 -0
- package/lib/module/shared/providers/gestures.provider.js +4 -2
- package/lib/module/shared/providers/gestures.provider.js.map +1 -1
- package/lib/module/shared/providers/screen/screen-composer.js +2 -2
- package/lib/module/shared/providers/screen/screen-composer.js.map +1 -1
- package/lib/module/shared/providers/screen/styles.provider.js +41 -32
- package/lib/module/shared/providers/screen/styles.provider.js.map +1 -1
- package/lib/module/shared/utils/animation/{start-screen-transition.js → animate-to-progress.js} +9 -5
- package/lib/module/shared/utils/animation/animate-to-progress.js.map +1 -0
- package/lib/module/shared/utils/gesture/check-gesture-activation.js +70 -0
- package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/module/shared/utils/gesture/determine-snap-target.js +52 -0
- package/lib/module/shared/utils/gesture/determine-snap-target.js.map +1 -0
- package/lib/module/shared/utils/gesture/validate-snap-points.js +26 -0
- package/lib/module/shared/utils/gesture/validate-snap-points.js.map +1 -0
- package/lib/module/shared/utils/gesture/velocity.js +11 -0
- package/lib/module/shared/utils/gesture/velocity.js.map +1 -1
- package/lib/typescript/shared/components/create-transition-aware-component.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-container.d.ts +6 -0
- package/lib/typescript/shared/components/screen-container.d.ts.map +1 -0
- package/lib/typescript/shared/configs/specs.d.ts +1 -0
- package/lib/typescript/shared/configs/specs.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +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 +34 -0
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts +5 -1
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts +14 -0
- package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts +15 -0
- package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts.map +1 -0
- package/lib/typescript/shared/providers/gestures.provider.d.ts +1 -0
- package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/styles.provider.d.ts.map +1 -1
- package/lib/typescript/shared/types/animation.types.d.ts +28 -2
- package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/screen.types.d.ts +26 -0
- package/lib/typescript/shared/types/screen.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/animation/animate-to-progress.d.ts +19 -0
- package/lib/typescript/shared/utils/animation/animate-to-progress.d.ts.map +1 -0
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +24 -0
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts +26 -0
- package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts.map +1 -0
- package/lib/typescript/shared/utils/gesture/validate-snap-points.d.ts +13 -0
- package/lib/typescript/shared/utils/gesture/validate-snap-points.d.ts.map +1 -0
- package/lib/typescript/shared/utils/gesture/velocity.d.ts +1 -0
- package/lib/typescript/shared/utils/gesture/velocity.d.ts.map +1 -1
- package/package.json +29 -2
- package/src/shared/__tests__/determine-snap-target.test.ts +268 -0
- package/src/shared/__tests__/gesture-activation.test.ts +247 -0
- package/src/shared/__tests__/validate-snap-points.test.ts +125 -0
- package/src/shared/components/create-transition-aware-component.tsx +11 -1
- package/src/shared/components/screen-container.tsx +65 -0
- package/src/shared/configs/presets.ts +3 -3
- package/src/shared/configs/specs.ts +6 -0
- package/src/shared/hooks/gestures/use-build-gestures.tsx +33 -253
- package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +436 -0
- package/src/shared/hooks/gestures/use-scroll-registry.tsx +52 -1
- package/src/shared/hooks/lifecycle/use-close-transition.ts +3 -3
- package/src/shared/hooks/lifecycle/use-open-transition.ts +27 -3
- package/src/shared/hooks/navigation/use-screen-state.tsx +106 -2
- package/src/shared/hooks/use-backdrop-pointer-events.ts +32 -0
- package/src/shared/providers/gestures.provider.tsx +3 -2
- package/src/shared/providers/screen/screen-composer.tsx +2 -2
- package/src/shared/providers/screen/styles.provider.tsx +40 -34
- package/src/shared/types/animation.types.ts +29 -2
- package/src/shared/types/screen.types.ts +29 -0
- package/src/shared/utils/animation/{start-screen-transition.ts → animate-to-progress.ts} +19 -7
- package/src/shared/utils/gesture/check-gesture-activation.ts +78 -0
- package/src/shared/utils/gesture/determine-snap-target.ts +75 -0
- package/src/shared/utils/gesture/validate-snap-points.ts +37 -0
- package/src/shared/utils/gesture/velocity.ts +10 -0
- package/lib/commonjs/shared/components/root-transition-aware.js.map +0 -1
- package/lib/commonjs/shared/hooks/use-stack-pointer-events.js +0 -23
- package/lib/commonjs/shared/hooks/use-stack-pointer-events.js.map +0 -1
- package/lib/commonjs/shared/utils/animation/start-screen-transition.js.map +0 -1
- package/lib/module/shared/components/root-transition-aware.js +0 -48
- package/lib/module/shared/components/root-transition-aware.js.map +0 -1
- package/lib/module/shared/hooks/use-stack-pointer-events.js +0 -20
- package/lib/module/shared/hooks/use-stack-pointer-events.js.map +0 -1
- package/lib/module/shared/utils/animation/start-screen-transition.js.map +0 -1
- package/lib/typescript/shared/components/root-transition-aware.d.ts +0 -6
- package/lib/typescript/shared/components/root-transition-aware.d.ts.map +0 -1
- package/lib/typescript/shared/hooks/use-stack-pointer-events.d.ts +0 -10
- package/lib/typescript/shared/hooks/use-stack-pointer-events.d.ts.map +0 -1
- package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts +0 -13
- package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts.map +0 -1
- package/src/shared/components/root-transition-aware.tsx +0 -49
- package/src/shared/hooks/use-stack-pointer-events.ts +0 -15
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import type {
|
|
3
|
+
GestureStateChangeEvent,
|
|
4
|
+
GestureTouchEvent,
|
|
5
|
+
GestureUpdateEvent,
|
|
6
|
+
PanGestureHandlerEventPayload,
|
|
7
|
+
} from "react-native-gesture-handler";
|
|
8
|
+
import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
|
|
9
|
+
import { type SharedValue, useSharedValue } from "react-native-reanimated";
|
|
10
|
+
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
|
+
import {
|
|
17
|
+
type GestureActivationArea,
|
|
18
|
+
type GestureDirection,
|
|
19
|
+
GestureOffsetState,
|
|
20
|
+
} from "../../types/gesture.types";
|
|
21
|
+
import type { Layout } from "../../types/screen.types";
|
|
22
|
+
import { animateToProgress } from "../../utils/animation/animate-to-progress";
|
|
23
|
+
import {
|
|
24
|
+
applyOffsetRules,
|
|
25
|
+
checkScrollAwareActivation,
|
|
26
|
+
} from "../../utils/gesture/check-gesture-activation";
|
|
27
|
+
import { determineDismissal } from "../../utils/gesture/determine-dismissal";
|
|
28
|
+
import { determineSnapTarget } from "../../utils/gesture/determine-snap-target";
|
|
29
|
+
import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progress";
|
|
30
|
+
import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
|
|
31
|
+
import { validateSnapPoints } from "../../utils/gesture/validate-snap-points";
|
|
32
|
+
import { velocity } from "../../utils/gesture/velocity";
|
|
33
|
+
import useStableCallbackValue from "../use-stable-callback-value";
|
|
34
|
+
|
|
35
|
+
interface UseScreenGestureHandlersProps {
|
|
36
|
+
dimensions: Layout;
|
|
37
|
+
animations: AnimationStoreMap;
|
|
38
|
+
gestureAnimationValues: GestureStoreMap;
|
|
39
|
+
gestureDirection: GestureDirection | GestureDirection[];
|
|
40
|
+
gestureDrivesProgress: boolean;
|
|
41
|
+
gestureVelocityImpact: number;
|
|
42
|
+
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
43
|
+
gestureActivationArea: GestureActivationArea;
|
|
44
|
+
gestureResponseDistance?: number;
|
|
45
|
+
ancestorIsDismissing?: SharedValue<number> | null;
|
|
46
|
+
snapPoints?: number[];
|
|
47
|
+
canDismiss: boolean;
|
|
48
|
+
transitionSpec?: TransitionSpec;
|
|
49
|
+
handleDismiss: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const useScreenGestureHandlers = ({
|
|
53
|
+
dimensions,
|
|
54
|
+
animations,
|
|
55
|
+
gestureAnimationValues,
|
|
56
|
+
gestureDirection,
|
|
57
|
+
gestureDrivesProgress,
|
|
58
|
+
gestureVelocityImpact,
|
|
59
|
+
scrollConfig,
|
|
60
|
+
gestureActivationArea,
|
|
61
|
+
gestureResponseDistance,
|
|
62
|
+
ancestorIsDismissing,
|
|
63
|
+
snapPoints: rawSnapPoints,
|
|
64
|
+
canDismiss,
|
|
65
|
+
transitionSpec,
|
|
66
|
+
handleDismiss,
|
|
67
|
+
}: UseScreenGestureHandlersProps) => {
|
|
68
|
+
const { hasSnapPoints, snapPoints, minSnapPoint, maxSnapPoint } = useMemo(
|
|
69
|
+
() => validateSnapPoints({ snapPoints: rawSnapPoints, canDismiss }),
|
|
70
|
+
[rawSnapPoints, canDismiss],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const directions = useMemo(() => {
|
|
74
|
+
const directionsArray = Array.isArray(gestureDirection)
|
|
75
|
+
? gestureDirection
|
|
76
|
+
: [gestureDirection];
|
|
77
|
+
|
|
78
|
+
const isBidirectional = directionsArray.includes("bidirectional");
|
|
79
|
+
|
|
80
|
+
const hasHorizontalDirection =
|
|
81
|
+
directionsArray.includes("horizontal") ||
|
|
82
|
+
directionsArray.includes("horizontal-inverted");
|
|
83
|
+
|
|
84
|
+
const isSnapAxisInverted = hasHorizontalDirection
|
|
85
|
+
? directionsArray.includes("horizontal-inverted") &&
|
|
86
|
+
!directionsArray.includes("horizontal")
|
|
87
|
+
: directionsArray.includes("vertical-inverted") &&
|
|
88
|
+
!directionsArray.includes("vertical");
|
|
89
|
+
|
|
90
|
+
const enableBothVertical =
|
|
91
|
+
isBidirectional || (hasSnapPoints && !hasHorizontalDirection);
|
|
92
|
+
const enableBothHorizontal =
|
|
93
|
+
isBidirectional || (hasSnapPoints && hasHorizontalDirection);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
vertical: directionsArray.includes("vertical") || enableBothVertical,
|
|
97
|
+
verticalInverted:
|
|
98
|
+
directionsArray.includes("vertical-inverted") || enableBothVertical,
|
|
99
|
+
horizontal:
|
|
100
|
+
directionsArray.includes("horizontal") || enableBothHorizontal,
|
|
101
|
+
horizontalInverted:
|
|
102
|
+
directionsArray.includes("horizontal-inverted") || enableBothHorizontal,
|
|
103
|
+
snapAxisInverted: hasSnapPoints && isSnapAxisInverted,
|
|
104
|
+
};
|
|
105
|
+
}, [gestureDirection, hasSnapPoints]);
|
|
106
|
+
|
|
107
|
+
const snapAxis =
|
|
108
|
+
directions.horizontal || directions.horizontalInverted
|
|
109
|
+
? "horizontal"
|
|
110
|
+
: "vertical";
|
|
111
|
+
|
|
112
|
+
const initialTouch = useSharedValue({ x: 0, y: 0 });
|
|
113
|
+
const gestureOffsetState = useSharedValue<GestureOffsetState>(
|
|
114
|
+
GestureOffsetState.PENDING,
|
|
115
|
+
);
|
|
116
|
+
const gestureStartProgress = useSharedValue(1);
|
|
117
|
+
|
|
118
|
+
const onTouchesDown = useStableCallbackValue((e: GestureTouchEvent) => {
|
|
119
|
+
"worklet";
|
|
120
|
+
const firstTouch = e.changedTouches[0];
|
|
121
|
+
initialTouch.value = { x: firstTouch.x, y: firstTouch.y };
|
|
122
|
+
gestureOffsetState.value = GestureOffsetState.PENDING;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const onTouchesMove = useStableCallbackValue(
|
|
126
|
+
(e: GestureTouchEvent, manager: GestureStateManagerType) => {
|
|
127
|
+
"worklet";
|
|
128
|
+
|
|
129
|
+
// If an ancestor navigator is already dismissing via gesture, block new gestures here.
|
|
130
|
+
if (ancestorIsDismissing?.value) {
|
|
131
|
+
gestureOffsetState.set(GestureOffsetState.FAILED);
|
|
132
|
+
manager.fail();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const touch = e.changedTouches[0];
|
|
137
|
+
|
|
138
|
+
const { isSwipingDown, isSwipingUp, isSwipingRight, isSwipingLeft } =
|
|
139
|
+
applyOffsetRules({
|
|
140
|
+
touch,
|
|
141
|
+
directions,
|
|
142
|
+
manager,
|
|
143
|
+
dimensions,
|
|
144
|
+
gestureOffsetState,
|
|
145
|
+
initialTouch: initialTouch.value,
|
|
146
|
+
activationArea: gestureActivationArea,
|
|
147
|
+
responseDistance: gestureResponseDistance,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (gestureOffsetState.value === GestureOffsetState.FAILED) {
|
|
151
|
+
manager.fail();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Keep pending until thresholds are met; no eager activation.
|
|
156
|
+
if (gestureAnimationValues.isDragging?.value) {
|
|
157
|
+
manager.activate();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const recognizedDirection =
|
|
162
|
+
isSwipingDown || isSwipingUp || isSwipingRight || isSwipingLeft;
|
|
163
|
+
|
|
164
|
+
const scrollCfg = scrollConfig.value;
|
|
165
|
+
const isTouchingScrollView = scrollCfg?.isTouched ?? false;
|
|
166
|
+
|
|
167
|
+
if (!isTouchingScrollView) {
|
|
168
|
+
// Early return if gesture hasn't met activation criteria
|
|
169
|
+
const canActivate =
|
|
170
|
+
recognizedDirection &&
|
|
171
|
+
gestureOffsetState.value === GestureOffsetState.PASSED &&
|
|
172
|
+
!gestureAnimationValues.isDismissing?.value;
|
|
173
|
+
|
|
174
|
+
if (!canActivate) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (isSwipingDown) {
|
|
179
|
+
gestureAnimationValues.direction.set("vertical");
|
|
180
|
+
} else if (isSwipingUp) {
|
|
181
|
+
gestureAnimationValues.direction.set("vertical-inverted");
|
|
182
|
+
} else if (isSwipingRight) {
|
|
183
|
+
gestureAnimationValues.direction.set("horizontal");
|
|
184
|
+
} else if (isSwipingLeft) {
|
|
185
|
+
gestureAnimationValues.direction.set("horizontal-inverted");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
manager.activate();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 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
|
+
// Snap mode: determine if sheet can still expand
|
|
203
|
+
const canExpandMore =
|
|
204
|
+
hasSnapPoints && animations.progress.value < maxSnapPoint - 0.01;
|
|
205
|
+
|
|
206
|
+
const { shouldActivate, direction: activatedDirection } =
|
|
207
|
+
checkScrollAwareActivation({
|
|
208
|
+
swipeInfo: {
|
|
209
|
+
isSwipingDown,
|
|
210
|
+
isSwipingUp,
|
|
211
|
+
isSwipingRight,
|
|
212
|
+
isSwipingLeft,
|
|
213
|
+
},
|
|
214
|
+
directions,
|
|
215
|
+
scrollX,
|
|
216
|
+
scrollY,
|
|
217
|
+
maxScrollX,
|
|
218
|
+
maxScrollY,
|
|
219
|
+
hasSnapPoints,
|
|
220
|
+
canExpandMore,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (recognizedDirection && !shouldActivate) {
|
|
224
|
+
manager.fail();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (
|
|
229
|
+
shouldActivate &&
|
|
230
|
+
gestureOffsetState.value === GestureOffsetState.PASSED &&
|
|
231
|
+
!gestureAnimationValues.isDismissing?.value
|
|
232
|
+
) {
|
|
233
|
+
gestureAnimationValues.direction.value = activatedDirection;
|
|
234
|
+
manager.activate();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const onStart = useStableCallbackValue(() => {
|
|
241
|
+
"worklet";
|
|
242
|
+
gestureAnimationValues.isDragging.value = TRUE;
|
|
243
|
+
gestureAnimationValues.isDismissing.value = FALSE;
|
|
244
|
+
gestureStartProgress.value = animations.progress.value;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const onUpdate = useStableCallbackValue(
|
|
248
|
+
(event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
|
|
249
|
+
"worklet";
|
|
250
|
+
|
|
251
|
+
const { translationX, translationY } = event;
|
|
252
|
+
const { width, height } = dimensions;
|
|
253
|
+
|
|
254
|
+
// Update gesture values (shared across all modes)
|
|
255
|
+
gestureAnimationValues.x.value = translationX;
|
|
256
|
+
gestureAnimationValues.y.value = translationY;
|
|
257
|
+
gestureAnimationValues.normalizedX.value = velocity.normalizeTranslation(
|
|
258
|
+
translationX,
|
|
259
|
+
width,
|
|
260
|
+
);
|
|
261
|
+
gestureAnimationValues.normalizedY.value = velocity.normalizeTranslation(
|
|
262
|
+
translationY,
|
|
263
|
+
height,
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
if (hasSnapPoints && gestureDrivesProgress) {
|
|
267
|
+
// Snap mode: bidirectional tracking on snap axis
|
|
268
|
+
const isHorizontal = snapAxis === "horizontal";
|
|
269
|
+
const translation = isHorizontal ? translationX : translationY;
|
|
270
|
+
const dimension = isHorizontal ? width : height;
|
|
271
|
+
|
|
272
|
+
// Map translation to progress delta:
|
|
273
|
+
// - Positive translation (down/right) = decrease progress (dismiss)
|
|
274
|
+
// - Negative translation (up/left) = increase progress (expand)
|
|
275
|
+
// Inverted directions flip this behavior
|
|
276
|
+
const baseSign = -1;
|
|
277
|
+
const sign = directions.snapAxisInverted ? -baseSign : baseSign;
|
|
278
|
+
const progressDelta = (sign * translation) / dimension;
|
|
279
|
+
|
|
280
|
+
// Use pre-computed bounds (minSnapPoint already accounts for canDismiss)
|
|
281
|
+
animations.progress.value = Math.max(
|
|
282
|
+
minSnapPoint,
|
|
283
|
+
Math.min(maxSnapPoint, gestureStartProgress.value + progressDelta),
|
|
284
|
+
);
|
|
285
|
+
} 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
|
+
let maxProgress = 0;
|
|
315
|
+
for (const axis of axes) {
|
|
316
|
+
if (axis.enabled && axis.translation * axis.sign > 0) {
|
|
317
|
+
const progress = mapGestureToProgress(
|
|
318
|
+
Math.abs(axis.translation),
|
|
319
|
+
axis.dimension,
|
|
320
|
+
);
|
|
321
|
+
maxProgress = Math.max(maxProgress, progress);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
animations.progress.value = Math.max(
|
|
326
|
+
0,
|
|
327
|
+
Math.min(1, gestureStartProgress.value - maxProgress),
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const onEnd = useStableCallbackValue(
|
|
334
|
+
(event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
|
|
335
|
+
"worklet";
|
|
336
|
+
|
|
337
|
+
if (hasSnapPoints) {
|
|
338
|
+
const isHorizontal = snapAxis === "horizontal";
|
|
339
|
+
const axisVelocity = isHorizontal ? event.velocityX : event.velocityY;
|
|
340
|
+
const axisDimension = isHorizontal
|
|
341
|
+
? dimensions.width
|
|
342
|
+
: dimensions.height;
|
|
343
|
+
|
|
344
|
+
// determineSnapTarget expects positive velocity = toward dismiss (decreasing progress)
|
|
345
|
+
// Positive velocity (down/right) = dismiss for non-inverted
|
|
346
|
+
// Inverted directions need velocity flipped
|
|
347
|
+
const snapVelocity = directions.snapAxisInverted
|
|
348
|
+
? -axisVelocity
|
|
349
|
+
: axisVelocity;
|
|
350
|
+
|
|
351
|
+
const result = determineSnapTarget({
|
|
352
|
+
currentProgress: animations.progress.value,
|
|
353
|
+
snapPoints,
|
|
354
|
+
velocity: snapVelocity,
|
|
355
|
+
dimension: axisDimension,
|
|
356
|
+
canDismiss: canDismiss,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const shouldDismiss = result.shouldDismiss;
|
|
360
|
+
const targetProgress = result.targetProgress;
|
|
361
|
+
const isSnapping = !shouldDismiss;
|
|
362
|
+
|
|
363
|
+
const spec = shouldDismiss
|
|
364
|
+
? transitionSpec?.close
|
|
365
|
+
: transitionSpec?.open;
|
|
366
|
+
|
|
367
|
+
const effectiveSpec = isSnapping
|
|
368
|
+
? {
|
|
369
|
+
open: transitionSpec?.expand ?? DefaultSnapSpec,
|
|
370
|
+
close: transitionSpec?.collapse ?? DefaultSnapSpec,
|
|
371
|
+
}
|
|
372
|
+
: transitionSpec;
|
|
373
|
+
|
|
374
|
+
resetGestureValues({
|
|
375
|
+
spec,
|
|
376
|
+
gestures: gestureAnimationValues,
|
|
377
|
+
shouldDismiss,
|
|
378
|
+
event,
|
|
379
|
+
dimensions,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// For snap transitions, velocity should match gesture direction
|
|
383
|
+
// Positive gesture velocity (down/right) = collapsing (negative progress velocity)
|
|
384
|
+
// Inverted directions flip this
|
|
385
|
+
const velocitySign = directions.snapAxisInverted ? 1 : -1;
|
|
386
|
+
const initialVelocity =
|
|
387
|
+
velocitySign * velocity.normalize(axisVelocity, axisDimension);
|
|
388
|
+
|
|
389
|
+
animateToProgress({
|
|
390
|
+
target: targetProgress,
|
|
391
|
+
onAnimationFinish: shouldDismiss ? handleDismiss : undefined,
|
|
392
|
+
spec: effectiveSpec,
|
|
393
|
+
animations,
|
|
394
|
+
initialVelocity,
|
|
395
|
+
});
|
|
396
|
+
} else {
|
|
397
|
+
// Standard mode: use determineDismissal
|
|
398
|
+
const result = determineDismissal({
|
|
399
|
+
event,
|
|
400
|
+
directions,
|
|
401
|
+
dimensions,
|
|
402
|
+
gestureVelocityImpact,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const shouldDismiss = result.shouldDismiss;
|
|
406
|
+
const targetProgress = shouldDismiss ? 0 : gestureStartProgress.value;
|
|
407
|
+
|
|
408
|
+
resetGestureValues({
|
|
409
|
+
spec: shouldDismiss ? transitionSpec?.close : transitionSpec?.open,
|
|
410
|
+
gestures: gestureAnimationValues,
|
|
411
|
+
shouldDismiss,
|
|
412
|
+
event,
|
|
413
|
+
dimensions,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const initialVelocity = velocity.calculateProgressVelocity({
|
|
417
|
+
animations,
|
|
418
|
+
shouldDismiss,
|
|
419
|
+
event,
|
|
420
|
+
dimensions,
|
|
421
|
+
directions,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
animateToProgress({
|
|
425
|
+
target: targetProgress,
|
|
426
|
+
onAnimationFinish: shouldDismiss ? handleDismiss : undefined,
|
|
427
|
+
spec: transitionSpec,
|
|
428
|
+
animations,
|
|
429
|
+
initialVelocity,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
return { onTouchesDown, onTouchesMove, onStart, onUpdate, onEnd };
|
|
436
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** biome-ignore-all lint/style/noNonNullAssertion: <Will always consume context from GestureProvider> */
|
|
2
2
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
|
-
import type { LayoutChangeEvent } from "react-native";
|
|
4
|
+
import type { GestureResponderEvent, LayoutChangeEvent } from "react-native";
|
|
5
5
|
import { useAnimatedScrollHandler } from "react-native-reanimated";
|
|
6
6
|
import type { ReanimatedScrollEvent } from "react-native-reanimated/lib/typescript/hook/commonTypes";
|
|
7
7
|
import { useGestureContext } from "../../providers/gestures.provider";
|
|
@@ -11,6 +11,8 @@ interface ScrollProgressHookProps {
|
|
|
11
11
|
onScroll?: (event: ReanimatedScrollEvent) => void;
|
|
12
12
|
onContentSizeChange?: (width: number, height: number) => void;
|
|
13
13
|
onLayout?: (event: LayoutChangeEvent) => void;
|
|
14
|
+
onTouchStart?: (event: GestureResponderEvent) => void;
|
|
15
|
+
onTouchEnd?: (event: GestureResponderEvent) => void;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
@@ -43,6 +45,7 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
43
45
|
contentWidth: 0,
|
|
44
46
|
layoutHeight: 0,
|
|
45
47
|
layoutWidth: 0,
|
|
48
|
+
isTouched: true,
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
51
|
v.x = event.contentOffset.x;
|
|
@@ -73,6 +76,7 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
73
76
|
layoutWidth: 0,
|
|
74
77
|
contentWidth: width,
|
|
75
78
|
contentHeight: height,
|
|
79
|
+
isTouched: false,
|
|
76
80
|
};
|
|
77
81
|
}
|
|
78
82
|
v.contentWidth = width;
|
|
@@ -102,6 +106,7 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
102
106
|
contentWidth: 0,
|
|
103
107
|
layoutHeight: height,
|
|
104
108
|
layoutWidth: width,
|
|
109
|
+
isTouched: false,
|
|
105
110
|
};
|
|
106
111
|
}
|
|
107
112
|
v.layoutHeight = height;
|
|
@@ -116,9 +121,55 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
116
121
|
}
|
|
117
122
|
});
|
|
118
123
|
|
|
124
|
+
const onTouchStart = useStableCallback((event: GestureResponderEvent) => {
|
|
125
|
+
props.onTouchStart?.(event);
|
|
126
|
+
|
|
127
|
+
const setTouched = (v: any) => {
|
|
128
|
+
"worklet";
|
|
129
|
+
if (v === null) {
|
|
130
|
+
return {
|
|
131
|
+
x: 0,
|
|
132
|
+
y: 0,
|
|
133
|
+
contentHeight: 0,
|
|
134
|
+
contentWidth: 0,
|
|
135
|
+
layoutHeight: 0,
|
|
136
|
+
layoutWidth: 0,
|
|
137
|
+
isTouched: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
v.isTouched = true;
|
|
141
|
+
return v;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
scrollConfig.modify(setTouched);
|
|
145
|
+
|
|
146
|
+
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
147
|
+
ancestorConfig.modify(setTouched);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const onTouchEnd = useStableCallback((event: GestureResponderEvent) => {
|
|
152
|
+
props.onTouchEnd?.(event);
|
|
153
|
+
|
|
154
|
+
const clearTouched = (v: any) => {
|
|
155
|
+
"worklet";
|
|
156
|
+
if (v === null) return v;
|
|
157
|
+
v.isTouched = false;
|
|
158
|
+
return v;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
scrollConfig.modify(clearTouched);
|
|
162
|
+
|
|
163
|
+
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
164
|
+
ancestorConfig.modify(clearTouched);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
119
168
|
return {
|
|
120
169
|
scrollHandler,
|
|
121
170
|
onContentSizeChange,
|
|
122
171
|
onLayout,
|
|
172
|
+
onTouchStart,
|
|
173
|
+
onTouchEnd,
|
|
123
174
|
};
|
|
124
175
|
};
|
|
@@ -10,7 +10,7 @@ import { useStackCoreContext } from "../../providers/stack/core.provider";
|
|
|
10
10
|
import { useManagedStackContext } from "../../providers/stack/managed.provider";
|
|
11
11
|
import type { AnimationStoreMap } from "../../stores/animation.store";
|
|
12
12
|
import { StackType } from "../../types/stack.types";
|
|
13
|
-
import {
|
|
13
|
+
import { animateToProgress } from "../../utils/animation/animate-to-progress";
|
|
14
14
|
import { resetStoresForScreen } from "../../utils/reset-stores-for-screen";
|
|
15
15
|
import { useSharedValueState } from "../reanimated/use-shared-value-state";
|
|
16
16
|
import useStableCallback from "../use-stable-callback";
|
|
@@ -49,7 +49,7 @@ const useManagedClose = ({
|
|
|
49
49
|
if (!keys?.includes(current.route.key)) return;
|
|
50
50
|
|
|
51
51
|
runOnJS(activate)();
|
|
52
|
-
|
|
52
|
+
animateToProgress({
|
|
53
53
|
target: "close",
|
|
54
54
|
spec: current.options.transitionSpec,
|
|
55
55
|
animations,
|
|
@@ -96,7 +96,7 @@ const useNativeStackClose = ({
|
|
|
96
96
|
e.preventDefault();
|
|
97
97
|
activate();
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
animateToProgress({
|
|
100
100
|
target: "close",
|
|
101
101
|
spec: current.options.transitionSpec,
|
|
102
102
|
animations,
|
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
import { useLayoutEffect } from "react";
|
|
2
2
|
import type { BaseDescriptor } from "../../providers/screen/keys.provider";
|
|
3
3
|
import type { AnimationStoreMap } from "../../stores/animation.store";
|
|
4
|
-
import {
|
|
4
|
+
import { animateToProgress } from "../../utils/animation/animate-to-progress";
|
|
5
5
|
import { useHighRefreshRate } from "../animation/use-high-refresh-rate";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Calculates the initial progress value based on snap points configuration.
|
|
9
|
+
*/
|
|
10
|
+
function getInitialProgress({
|
|
11
|
+
snapPoints,
|
|
12
|
+
initialSnapIndex,
|
|
13
|
+
}: {
|
|
14
|
+
snapPoints?: number[];
|
|
15
|
+
initialSnapIndex: number;
|
|
16
|
+
}): number | undefined {
|
|
17
|
+
if (!snapPoints) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const clampedIndex = Math.min(
|
|
22
|
+
Math.max(0, initialSnapIndex),
|
|
23
|
+
snapPoints.length - 1,
|
|
24
|
+
);
|
|
25
|
+
return snapPoints[clampedIndex];
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
/**
|
|
8
29
|
* Handles opening animation on mount.
|
|
9
30
|
* Returns activate/deactivate functions for high refresh rate.
|
|
@@ -17,9 +38,12 @@ export function useOpenTransition(
|
|
|
17
38
|
|
|
18
39
|
// biome-ignore lint/correctness/useExhaustiveDependencies: Must only run once on mount
|
|
19
40
|
useLayoutEffect(() => {
|
|
41
|
+
const { snapPoints, initialSnapIndex = 0 } = current.options;
|
|
42
|
+
const targetProgress = getInitialProgress({ snapPoints, initialSnapIndex });
|
|
43
|
+
|
|
20
44
|
activateHighRefreshRate();
|
|
21
|
-
|
|
22
|
-
target: "open",
|
|
45
|
+
animateToProgress({
|
|
46
|
+
target: targetProgress ?? "open",
|
|
23
47
|
spec: current.options.transitionSpec,
|
|
24
48
|
animations,
|
|
25
49
|
onAnimationFinish: deactivateHighRefreshRate,
|