react-native-screen-transitions 3.2.1 → 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 +105 -10
- 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/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/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 +25 -202
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js +342 -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 +33 -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/utils/animation/{start-screen-transition.js → animate-to-progress.js} +16 -8
- package/lib/commonjs/shared/utils/animation/animate-to-progress.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +138 -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/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/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/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 +25 -201
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +336 -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 +35 -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/utils/animation/{start-screen-transition.js → animate-to-progress.js} +14 -6
- package/lib/module/shared/utils/animation/animate-to-progress.js.map +1 -0
- package/lib/module/shared/utils/gesture/check-gesture-activation.js +137 -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/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/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/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 +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 +19 -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 +7 -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/types/animation.types.d.ts +37 -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 +23 -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/lib/typescript/shared/utils/logger.d.ts +6 -0
- package/lib/typescript/shared/utils/logger.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/blank-stack/components/adjusted-screen.tsx +1 -1
- package/src/shared/__tests__/derivations.test.ts +1 -0
- package/src/shared/__tests__/determine-snap-target.test.ts +268 -0
- package/src/shared/__tests__/gesture-activation.test.ts +220 -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/constants.ts +36 -9
- package/src/shared/hooks/animation/use-screen-animation.tsx +32 -21
- package/src/shared/hooks/gestures/use-build-gestures.tsx +23 -275
- package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +434 -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 +59 -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/types/animation.types.ts +39 -2
- package/src/shared/types/screen.types.ts +29 -0
- package/src/shared/utils/animation/{start-screen-transition.ts → animate-to-progress.ts} +23 -8
- package/src/shared/utils/gesture/check-gesture-activation.ts +129 -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/src/shared/utils/logger.ts +15 -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,434 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useWindowDimensions } from "react-native";
|
|
3
|
+
import type {
|
|
4
|
+
GestureStateChangeEvent,
|
|
5
|
+
GestureTouchEvent,
|
|
6
|
+
GestureUpdateEvent,
|
|
7
|
+
PanGestureHandlerEventPayload,
|
|
8
|
+
} from "react-native-gesture-handler";
|
|
9
|
+
import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
|
|
10
|
+
import { type SharedValue, useSharedValue } from "react-native-reanimated";
|
|
11
|
+
import { DefaultSnapSpec } from "../../configs/specs";
|
|
12
|
+
import {
|
|
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";
|
|
26
|
+
import { animateToProgress } from "../../utils/animation/animate-to-progress";
|
|
27
|
+
import {
|
|
28
|
+
applyOffsetRules,
|
|
29
|
+
checkScrollAwareActivation,
|
|
30
|
+
} from "../../utils/gesture/check-gesture-activation";
|
|
31
|
+
import { determineDismissal } from "../../utils/gesture/determine-dismissal";
|
|
32
|
+
import { determineSnapTarget } from "../../utils/gesture/determine-snap-target";
|
|
33
|
+
import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progress";
|
|
34
|
+
import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
|
|
35
|
+
import { validateSnapPoints } from "../../utils/gesture/validate-snap-points";
|
|
36
|
+
import { velocity } from "../../utils/gesture/velocity";
|
|
37
|
+
import { logger } from "../../utils/logger";
|
|
38
|
+
import useStableCallbackValue from "../use-stable-callback-value";
|
|
39
|
+
|
|
40
|
+
interface UseScreenGestureHandlersProps {
|
|
41
|
+
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
42
|
+
ancestorIsDismissing?: SharedValue<number> | null;
|
|
43
|
+
canDismiss: boolean;
|
|
44
|
+
handleDismiss: () => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const useScreenGestureHandlers = ({
|
|
48
|
+
scrollConfig,
|
|
49
|
+
ancestorIsDismissing,
|
|
50
|
+
canDismiss,
|
|
51
|
+
handleDismiss,
|
|
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
|
+
|
|
71
|
+
const { hasSnapPoints, snapPoints, minSnapPoint, maxSnapPoint } = useMemo(
|
|
72
|
+
() => validateSnapPoints({ snapPoints: rawSnapPoints, canDismiss }),
|
|
73
|
+
[rawSnapPoints, canDismiss],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const directions = useMemo(() => {
|
|
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];
|
|
102
|
+
|
|
103
|
+
const isBidirectional = directionsArray.includes("bidirectional");
|
|
104
|
+
|
|
105
|
+
const hasHorizontalDirection =
|
|
106
|
+
directionsArray.includes("horizontal") ||
|
|
107
|
+
directionsArray.includes("horizontal-inverted");
|
|
108
|
+
|
|
109
|
+
const isSnapAxisInverted = hasHorizontalDirection
|
|
110
|
+
? directionsArray.includes("horizontal-inverted") &&
|
|
111
|
+
!directionsArray.includes("horizontal")
|
|
112
|
+
: directionsArray.includes("vertical-inverted") &&
|
|
113
|
+
!directionsArray.includes("vertical");
|
|
114
|
+
|
|
115
|
+
const enableBothVertical =
|
|
116
|
+
isBidirectional || (hasSnapPoints && !hasHorizontalDirection);
|
|
117
|
+
const enableBothHorizontal =
|
|
118
|
+
isBidirectional || (hasSnapPoints && hasHorizontalDirection);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
vertical: directionsArray.includes("vertical") || enableBothVertical,
|
|
122
|
+
verticalInverted:
|
|
123
|
+
directionsArray.includes("vertical-inverted") || enableBothVertical,
|
|
124
|
+
horizontal:
|
|
125
|
+
directionsArray.includes("horizontal") || enableBothHorizontal,
|
|
126
|
+
horizontalInverted:
|
|
127
|
+
directionsArray.includes("horizontal-inverted") || enableBothHorizontal,
|
|
128
|
+
snapAxisInverted: hasSnapPoints && isSnapAxisInverted,
|
|
129
|
+
};
|
|
130
|
+
}, [gestureDirection, hasSnapPoints]);
|
|
131
|
+
|
|
132
|
+
const snapAxis =
|
|
133
|
+
directions.horizontal || directions.horizontalInverted
|
|
134
|
+
? "horizontal"
|
|
135
|
+
: "vertical";
|
|
136
|
+
|
|
137
|
+
const initialTouch = useSharedValue({ x: 0, y: 0 });
|
|
138
|
+
const gestureOffsetState = useSharedValue<GestureOffsetState>(
|
|
139
|
+
GestureOffsetState.PENDING,
|
|
140
|
+
);
|
|
141
|
+
const gestureStartProgress = useSharedValue(1);
|
|
142
|
+
|
|
143
|
+
const onTouchesDown = useStableCallbackValue((e: GestureTouchEvent) => {
|
|
144
|
+
"worklet";
|
|
145
|
+
const firstTouch = e.changedTouches[0];
|
|
146
|
+
initialTouch.value = { x: firstTouch.x, y: firstTouch.y };
|
|
147
|
+
gestureOffsetState.value = GestureOffsetState.PENDING;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const onTouchesMove = useStableCallbackValue(
|
|
151
|
+
(e: GestureTouchEvent, manager: GestureStateManagerType) => {
|
|
152
|
+
"worklet";
|
|
153
|
+
|
|
154
|
+
// If an ancestor navigator is already dismissing via gesture, block new gestures here.
|
|
155
|
+
if (ancestorIsDismissing?.value) {
|
|
156
|
+
gestureOffsetState.value = GestureOffsetState.FAILED;
|
|
157
|
+
manager.fail();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const touch = e.changedTouches[0];
|
|
162
|
+
|
|
163
|
+
const { isSwipingDown, isSwipingUp, isSwipingRight, isSwipingLeft } =
|
|
164
|
+
applyOffsetRules({
|
|
165
|
+
touch,
|
|
166
|
+
directions,
|
|
167
|
+
manager,
|
|
168
|
+
dimensions,
|
|
169
|
+
gestureOffsetState,
|
|
170
|
+
initialTouch: initialTouch.value,
|
|
171
|
+
activationArea: gestureActivationArea,
|
|
172
|
+
responseDistance: gestureResponseDistance,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (gestureOffsetState.value === GestureOffsetState.FAILED) {
|
|
176
|
+
manager.fail();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Keep pending until thresholds are met; no eager activation.
|
|
181
|
+
if (gestureAnimationValues.isDragging?.value) {
|
|
182
|
+
manager.activate();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const recognizedDirection =
|
|
187
|
+
isSwipingDown || isSwipingUp || isSwipingRight || isSwipingLeft;
|
|
188
|
+
|
|
189
|
+
const scrollCfg = scrollConfig.value;
|
|
190
|
+
const isTouchingScrollView = scrollCfg?.isTouched ?? false;
|
|
191
|
+
|
|
192
|
+
if (!isTouchingScrollView) {
|
|
193
|
+
// Early return if gesture hasn't met activation criteria
|
|
194
|
+
const canActivate =
|
|
195
|
+
recognizedDirection &&
|
|
196
|
+
gestureOffsetState.value === GestureOffsetState.PASSED &&
|
|
197
|
+
!gestureAnimationValues.isDismissing?.value;
|
|
198
|
+
|
|
199
|
+
if (!canActivate) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (isSwipingDown) {
|
|
204
|
+
gestureAnimationValues.direction.value = "vertical";
|
|
205
|
+
} else if (isSwipingUp) {
|
|
206
|
+
gestureAnimationValues.direction.value = "vertical-inverted";
|
|
207
|
+
} else if (isSwipingRight) {
|
|
208
|
+
gestureAnimationValues.direction.value = "horizontal";
|
|
209
|
+
} else if (isSwipingLeft) {
|
|
210
|
+
gestureAnimationValues.direction.value = "horizontal-inverted";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
manager.activate();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Touch IS on ScrollView - apply scroll-aware rules
|
|
218
|
+
// Snap mode: determine if sheet can still expand
|
|
219
|
+
const canExpandMore =
|
|
220
|
+
hasSnapPoints && animations.progress.value < maxSnapPoint - EPSILON;
|
|
221
|
+
|
|
222
|
+
const { shouldActivate, direction: activatedDirection } =
|
|
223
|
+
checkScrollAwareActivation({
|
|
224
|
+
swipeInfo: {
|
|
225
|
+
isSwipingDown,
|
|
226
|
+
isSwipingUp,
|
|
227
|
+
isSwipingRight,
|
|
228
|
+
isSwipingLeft,
|
|
229
|
+
},
|
|
230
|
+
directions,
|
|
231
|
+
scrollConfig: scrollCfg,
|
|
232
|
+
hasSnapPoints,
|
|
233
|
+
canExpandMore,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (recognizedDirection && !shouldActivate) {
|
|
237
|
+
manager.fail();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
shouldActivate &&
|
|
243
|
+
gestureOffsetState.value === GestureOffsetState.PASSED &&
|
|
244
|
+
!gestureAnimationValues.isDismissing?.value
|
|
245
|
+
) {
|
|
246
|
+
gestureAnimationValues.direction.value = activatedDirection;
|
|
247
|
+
manager.activate();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const onStart = useStableCallbackValue(() => {
|
|
254
|
+
"worklet";
|
|
255
|
+
gestureAnimationValues.isDragging.value = TRUE;
|
|
256
|
+
gestureAnimationValues.isDismissing.value = FALSE;
|
|
257
|
+
gestureStartProgress.value = animations.progress.value;
|
|
258
|
+
animations.animating.value = TRUE;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const onUpdate = useStableCallbackValue(
|
|
262
|
+
(event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
|
|
263
|
+
"worklet";
|
|
264
|
+
|
|
265
|
+
const { translationX, translationY } = event;
|
|
266
|
+
const { width, height } = dimensions;
|
|
267
|
+
|
|
268
|
+
gestureAnimationValues.x.value = translationX;
|
|
269
|
+
gestureAnimationValues.y.value = translationY;
|
|
270
|
+
gestureAnimationValues.normalizedX.value = velocity.normalizeTranslation(
|
|
271
|
+
translationX,
|
|
272
|
+
width,
|
|
273
|
+
);
|
|
274
|
+
gestureAnimationValues.normalizedY.value = velocity.normalizeTranslation(
|
|
275
|
+
translationY,
|
|
276
|
+
height,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
if (hasSnapPoints && gestureDrivesProgress) {
|
|
280
|
+
const isHorizontal = snapAxis === "horizontal";
|
|
281
|
+
const translation = isHorizontal ? translationX : translationY;
|
|
282
|
+
const dimension = isHorizontal ? width : height;
|
|
283
|
+
|
|
284
|
+
// Map translation to progress delta:
|
|
285
|
+
// - Positive translation (down/right) = decrease progress (dismiss)
|
|
286
|
+
// - Negative translation (up/left) = increase progress (expand)
|
|
287
|
+
// Inverted directions flip this behavior
|
|
288
|
+
const baseSign = -1;
|
|
289
|
+
const sign = directions.snapAxisInverted ? -baseSign : baseSign;
|
|
290
|
+
const progressDelta = (sign * translation) / dimension;
|
|
291
|
+
|
|
292
|
+
// Use pre-computed bounds (minSnapPoint already accounts for canDismiss)
|
|
293
|
+
animations.progress.value = Math.max(
|
|
294
|
+
minSnapPoint,
|
|
295
|
+
Math.min(maxSnapPoint, gestureStartProgress.value + progressDelta),
|
|
296
|
+
);
|
|
297
|
+
} else if (gestureDrivesProgress) {
|
|
298
|
+
let maxProgress = 0;
|
|
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);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
animations.progress.value = Math.max(
|
|
325
|
+
0,
|
|
326
|
+
Math.min(1, gestureStartProgress.value - maxProgress),
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const onEnd = useStableCallbackValue(
|
|
333
|
+
(event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
|
|
334
|
+
"worklet";
|
|
335
|
+
|
|
336
|
+
if (hasSnapPoints) {
|
|
337
|
+
const isHorizontal = snapAxis === "horizontal";
|
|
338
|
+
const axisVelocity = isHorizontal ? event.velocityX : event.velocityY;
|
|
339
|
+
const axisDimension = isHorizontal
|
|
340
|
+
? dimensions.width
|
|
341
|
+
: dimensions.height;
|
|
342
|
+
|
|
343
|
+
// determineSnapTarget expects positive velocity = toward dismiss (decreasing progress)
|
|
344
|
+
// Positive velocity (down/right) = dismiss for non-inverted
|
|
345
|
+
// Inverted directions need velocity flipped
|
|
346
|
+
const snapVelocity = directions.snapAxisInverted
|
|
347
|
+
? -axisVelocity
|
|
348
|
+
: axisVelocity;
|
|
349
|
+
|
|
350
|
+
const result = determineSnapTarget({
|
|
351
|
+
currentProgress: animations.progress.value,
|
|
352
|
+
snapPoints,
|
|
353
|
+
velocity: snapVelocity,
|
|
354
|
+
dimension: axisDimension,
|
|
355
|
+
canDismiss: canDismiss,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const shouldDismiss = result.shouldDismiss;
|
|
359
|
+
const targetProgress = result.targetProgress;
|
|
360
|
+
const isSnapping = !shouldDismiss;
|
|
361
|
+
|
|
362
|
+
const spec = shouldDismiss
|
|
363
|
+
? transitionSpec?.close
|
|
364
|
+
: transitionSpec?.open;
|
|
365
|
+
|
|
366
|
+
const effectiveSpec = isSnapping
|
|
367
|
+
? {
|
|
368
|
+
open: transitionSpec?.expand ?? DefaultSnapSpec,
|
|
369
|
+
close: transitionSpec?.collapse ?? DefaultSnapSpec,
|
|
370
|
+
}
|
|
371
|
+
: transitionSpec;
|
|
372
|
+
|
|
373
|
+
resetGestureValues({
|
|
374
|
+
spec,
|
|
375
|
+
gestures: gestureAnimationValues,
|
|
376
|
+
shouldDismiss,
|
|
377
|
+
event,
|
|
378
|
+
dimensions,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// For snap transitions, velocity should match gesture direction
|
|
382
|
+
// Positive gesture velocity (down/right) = collapsing (negative progress velocity)
|
|
383
|
+
// Inverted directions flip this
|
|
384
|
+
const velocitySign = directions.snapAxisInverted ? 1 : -1;
|
|
385
|
+
const initialVelocity =
|
|
386
|
+
velocitySign * velocity.normalize(axisVelocity, axisDimension);
|
|
387
|
+
|
|
388
|
+
animateToProgress({
|
|
389
|
+
target: targetProgress,
|
|
390
|
+
onAnimationFinish: shouldDismiss ? handleDismiss : undefined,
|
|
391
|
+
spec: effectiveSpec,
|
|
392
|
+
animations,
|
|
393
|
+
initialVelocity,
|
|
394
|
+
});
|
|
395
|
+
} else {
|
|
396
|
+
const result = determineDismissal({
|
|
397
|
+
event,
|
|
398
|
+
directions,
|
|
399
|
+
dimensions,
|
|
400
|
+
gestureVelocityImpact,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const shouldDismiss = result.shouldDismiss;
|
|
404
|
+
const targetProgress = shouldDismiss ? 0 : gestureStartProgress.value;
|
|
405
|
+
|
|
406
|
+
resetGestureValues({
|
|
407
|
+
spec: shouldDismiss ? transitionSpec?.close : transitionSpec?.open,
|
|
408
|
+
gestures: gestureAnimationValues,
|
|
409
|
+
shouldDismiss,
|
|
410
|
+
event,
|
|
411
|
+
dimensions,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const initialVelocity = velocity.calculateProgressVelocity({
|
|
415
|
+
animations,
|
|
416
|
+
shouldDismiss,
|
|
417
|
+
event,
|
|
418
|
+
dimensions,
|
|
419
|
+
directions,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
animateToProgress({
|
|
423
|
+
target: targetProgress,
|
|
424
|
+
onAnimationFinish: shouldDismiss ? handleDismiss : undefined,
|
|
425
|
+
spec: transitionSpec,
|
|
426
|
+
animations,
|
|
427
|
+
initialVelocity,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
return { onTouchesDown, onTouchesMove, onStart, onUpdate, onEnd };
|
|
434
|
+
};
|
|
@@ -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,
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { Route } from "@react-navigation/native";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
import { useDerivedValue } from "react-native-reanimated";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { runOnUI, useDerivedValue } from "react-native-reanimated";
|
|
4
|
+
import { DefaultSnapSpec } from "../../configs/specs";
|
|
4
5
|
import {
|
|
5
6
|
type BaseDescriptor,
|
|
6
7
|
useKeys,
|
|
7
8
|
} from "../../providers/screen/keys.provider";
|
|
9
|
+
import { AnimationStore } from "../../stores/animation.store";
|
|
8
10
|
import type { ScreenTransitionConfig } from "../../types/screen.types";
|
|
9
11
|
import type { BaseStackNavigation } from "../../types/stack.types";
|
|
12
|
+
import { animateToProgress } from "../../utils/animation/animate-to-progress";
|
|
10
13
|
import { useSharedValueState } from "../reanimated/use-shared-value-state";
|
|
11
14
|
import { type StackContextValue, useStack } from "./use-stack";
|
|
12
15
|
|
|
@@ -47,6 +50,14 @@ export interface ScreenState<
|
|
|
47
50
|
* Navigation object for this screen.
|
|
48
51
|
*/
|
|
49
52
|
navigation: TNavigation;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Programmatically snap to a specific snap point by index.
|
|
56
|
+
* Only works if the screen has snapPoints defined.
|
|
57
|
+
*
|
|
58
|
+
* @param index - The index of the snap point to snap to (0-based)
|
|
59
|
+
*/
|
|
60
|
+
snapTo: (index: number) => void;
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
/**
|
|
@@ -77,6 +88,50 @@ export function useScreenState<
|
|
|
77
88
|
return scenes[focusedIndex] ?? scenes[scenes.length - 1];
|
|
78
89
|
}, [scenes, focusedIndex]);
|
|
79
90
|
|
|
91
|
+
const currentOptions = current.options;
|
|
92
|
+
const animations = useMemo(
|
|
93
|
+
() => AnimationStore.getAll(current.route.key),
|
|
94
|
+
[current.route.key],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const snapTo = useCallback(
|
|
98
|
+
(targetIndex: number) => {
|
|
99
|
+
const points = currentOptions?.snapPoints;
|
|
100
|
+
if (!points || points.length === 0) {
|
|
101
|
+
console.warn("snapTo called but no snapPoints defined");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const sorted = [...points].sort((a, b) => a - b);
|
|
106
|
+
|
|
107
|
+
if (targetIndex < 0 || targetIndex >= sorted.length) {
|
|
108
|
+
console.warn(
|
|
109
|
+
`snapTo index ${targetIndex} out of bounds (0-${sorted.length - 1})`,
|
|
110
|
+
);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const targetProgress = sorted[targetIndex];
|
|
115
|
+
|
|
116
|
+
runOnUI(() => {
|
|
117
|
+
"worklet";
|
|
118
|
+
animateToProgress({
|
|
119
|
+
target: targetProgress,
|
|
120
|
+
animations,
|
|
121
|
+
spec: {
|
|
122
|
+
open:
|
|
123
|
+
focusedScene.descriptor.options.transitionSpec?.expand ??
|
|
124
|
+
DefaultSnapSpec,
|
|
125
|
+
close:
|
|
126
|
+
focusedScene.descriptor.options.transitionSpec?.collapse ??
|
|
127
|
+
DefaultSnapSpec,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
})();
|
|
131
|
+
},
|
|
132
|
+
[currentOptions?.snapPoints, animations, focusedScene],
|
|
133
|
+
);
|
|
134
|
+
|
|
80
135
|
return useMemo(
|
|
81
136
|
() => ({
|
|
82
137
|
index,
|
|
@@ -86,6 +141,7 @@ export function useScreenState<
|
|
|
86
141
|
focusedIndex,
|
|
87
142
|
meta: focusedScene?.descriptor?.options?.meta,
|
|
88
143
|
navigation: current.navigation as TNavigation,
|
|
144
|
+
snapTo,
|
|
89
145
|
}),
|
|
90
146
|
[
|
|
91
147
|
index,
|
|
@@ -94,6 +150,7 @@ export function useScreenState<
|
|
|
94
150
|
focusedIndex,
|
|
95
151
|
current.navigation,
|
|
96
152
|
current.route,
|
|
153
|
+
snapTo,
|
|
97
154
|
],
|
|
98
155
|
);
|
|
99
156
|
}
|