@onlynative/inertia-gestures 0.0.1-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OnlyNative
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,207 @@
1
+ import { PanGesture } from 'react-native-gesture-handler';
2
+ import { useAnimatedStyle, SharedValue } from 'react-native-reanimated';
3
+
4
+ /**
5
+ * Public types for `@onlynative/inertia-gestures`.
6
+ */
7
+ /**
8
+ * Bounds the dragged value can reach. Each side is optional — omit to leave
9
+ * that direction unbounded. Coordinates are in the same space the drag
10
+ * publishes (pixels of translation from the dragged element's resting
11
+ * position), so `{ left: -100, right: 100 }` allows ±100 px of horizontal
12
+ * travel.
13
+ */
14
+ interface DragConstraints {
15
+ left?: number;
16
+ right?: number;
17
+ top?: number;
18
+ bottom?: number;
19
+ }
20
+ /**
21
+ * Configuration for `useDrag`. All fields are optional; the defaults give an
22
+ * unconstrained two-axis drag with no elasticity.
23
+ */
24
+ interface DragOptions {
25
+ /**
26
+ * Restrict the drag to one axis. Defaults to `'both'`. When `'x'` is set
27
+ * the y-axis shared value never updates (and vice versa); the gesture
28
+ * still tracks both for velocity reporting on `onDragEnd`.
29
+ */
30
+ axis?: 'x' | 'y' | 'both';
31
+ /**
32
+ * Travel bounds. Out-of-bounds values clamp to the limit unless `elastic`
33
+ * is non-zero, in which case overshoot is dampened (rubber-band feel).
34
+ */
35
+ constraints?: DragConstraints;
36
+ /**
37
+ * Rubber-band coefficient applied to overshoot past `constraints`. `0`
38
+ * (default) hard-clamps; `1` is fully elastic (no resistance). Typical
39
+ * Framer-Motion-style feel sits around `0.2`–`0.4`.
40
+ */
41
+ elastic?: number;
42
+ /**
43
+ * Fired on the JS thread when the drag begins.
44
+ */
45
+ onDragStart?: () => void;
46
+ /**
47
+ * Fired on the JS thread when the drag finishes (release or cancel). The
48
+ * payload is the final translation and the release velocity in px/sec.
49
+ */
50
+ onDragEnd?: (info: {
51
+ x: number;
52
+ y: number;
53
+ velocity: {
54
+ x: number;
55
+ y: number;
56
+ };
57
+ }) => void;
58
+ }
59
+
60
+ interface UseDragResult {
61
+ /** Pan gesture to pass to a `<GestureDetector>`. */
62
+ gesture: PanGesture;
63
+ /**
64
+ * Animated style fragment (a single `transform` entry) to stack onto the
65
+ * dragged Motion primitive's `style` prop. Stable across renders.
66
+ */
67
+ animatedStyle: ReturnType<typeof useAnimatedStyle>;
68
+ /** Current x translation in pixels. UI-thread shared value. */
69
+ dragX: SharedValue<number>;
70
+ /** Current y translation in pixels. UI-thread shared value. */
71
+ dragY: SharedValue<number>;
72
+ /** True while the gesture is active. */
73
+ isDragging: SharedValue<boolean>;
74
+ }
75
+ /**
76
+ * Drag a Motion primitive with `react-native-gesture-handler`'s pan gesture.
77
+ *
78
+ * The hook owns a pair of shared values (`dragX`, `dragY`) and a `Pan`
79
+ * gesture that updates them on the UI thread. The returned `animatedStyle`
80
+ * is a self-contained `transform: [{ translateX }, { translateY }]` fragment;
81
+ * stack it onto the dragged component without colliding with Motion's own
82
+ * `animate` transforms.
83
+ *
84
+ * Usage:
85
+ * ```tsx
86
+ * const drag = useDrag({ axis: 'x', constraints: { left: -100, right: 100 } })
87
+ * return (
88
+ * <GestureDetector gesture={drag.gesture}>
89
+ * <Motion.View style={drag.animatedStyle} />
90
+ * </GestureDetector>
91
+ * )
92
+ * ```
93
+ */
94
+ declare function useDrag(options?: DragOptions): UseDragResult;
95
+
96
+ type SwipeDirection = 'left' | 'right' | 'up' | 'down';
97
+ interface SwipeOptions {
98
+ /**
99
+ * Allowed swipe directions. Defaults to all four. The gesture only commits
100
+ * for directions in this list — a horizontal swipe with `directions:
101
+ * ['up', 'down']` will not fire `onSwipe`.
102
+ */
103
+ directions?: SwipeDirection[];
104
+ /**
105
+ * Pixel distance threshold past which a release commits the swipe. Defaults
106
+ * to `80`.
107
+ */
108
+ distanceThreshold?: number;
109
+ /**
110
+ * Velocity threshold (px/sec) past which a release commits the swipe even
111
+ * before the distance threshold is reached — flick-style gestures. Defaults
112
+ * to `800`.
113
+ */
114
+ velocityThreshold?: number;
115
+ /**
116
+ * Fired on the JS thread when the gesture commits in an allowed direction.
117
+ */
118
+ onSwipe?: (direction: SwipeDirection, info: {
119
+ distance: number;
120
+ velocity: number;
121
+ }) => void;
122
+ }
123
+ interface UseSwipeResult {
124
+ /** Pan gesture to pass to a `<GestureDetector>`. */
125
+ gesture: PanGesture;
126
+ /**
127
+ * Animated style fragment exposing live translation while the gesture is
128
+ * active. Snaps back to `{ 0, 0 }` after release (whether or not the swipe
129
+ * committed) via a default spring.
130
+ */
131
+ animatedStyle: ReturnType<typeof useAnimatedStyle>;
132
+ /** Live x translation. */
133
+ swipeX: SharedValue<number>;
134
+ /** Live y translation. */
135
+ swipeY: SharedValue<number>;
136
+ /** True while the user is actively swiping. */
137
+ isActive: SharedValue<boolean>;
138
+ }
139
+ /**
140
+ * Directional commit-or-snap-back gesture. Tracks live translation while the
141
+ * user drags and fires `onSwipe(direction)` on release if either the distance
142
+ * or velocity threshold is exceeded in an allowed direction. The position
143
+ * shared values always animate back to zero — the consumer is responsible
144
+ * for whatever side effect the commit drives (delete a row, dismiss a sheet,
145
+ * etc.).
146
+ *
147
+ * Usage:
148
+ * ```tsx
149
+ * const swipe = useSwipe({
150
+ * directions: ['left'],
151
+ * onSwipe: (dir) => deleteRow(),
152
+ * })
153
+ * return (
154
+ * <GestureDetector gesture={swipe.gesture}>
155
+ * <Motion.View style={swipe.animatedStyle}>...</Motion.View>
156
+ * </GestureDetector>
157
+ * )
158
+ * ```
159
+ */
160
+ declare function useSwipe(options?: SwipeOptions): UseSwipeResult;
161
+
162
+ interface PanOptions {
163
+ /**
164
+ * Translation bounds. Each side is optional; out-of-bounds motion during
165
+ * the active gesture and during the post-release decay is hard-clamped
166
+ * (Reanimated's `withDecay` `clamp` param). Decay-style overshoot is not
167
+ * supported here — for rubber-banded bounds, prefer `useDrag` with
168
+ * `elastic`.
169
+ */
170
+ constraints?: DragConstraints;
171
+ /**
172
+ * Deceleration applied to the post-release momentum. Higher = momentum
173
+ * dies faster. Reanimated default is `0.998`; lower values feel more
174
+ * "slippy". Range: roughly `0.99` (slow) to `0.999` (long glide).
175
+ */
176
+ deceleration?: number;
177
+ /**
178
+ * Disable the post-release momentum entirely. Defaults to `false` — pan
179
+ * coasts after release. Set to `true` for a hard stop on release (drag-like
180
+ * behavior).
181
+ */
182
+ disableMomentum?: boolean;
183
+ }
184
+ interface UsePanResult {
185
+ /** Pan gesture to pass to a `<GestureDetector>`. */
186
+ gesture: PanGesture;
187
+ /** Stable animated `transform` style. */
188
+ animatedStyle: ReturnType<typeof useAnimatedStyle>;
189
+ /** Live x translation, persistent across gestures. */
190
+ panX: SharedValue<number>;
191
+ /** Live y translation, persistent across gestures. */
192
+ panY: SharedValue<number>;
193
+ /** True while the user is actively panning. Decay phase reads `false`. */
194
+ isPanning: SharedValue<boolean>;
195
+ }
196
+ /**
197
+ * Camera-pan-style drag with momentum on release. Translation persists
198
+ * across separate pan gestures (the next pan starts from the current
199
+ * position, not zero), and on release the translation continues to glide
200
+ * via Reanimated's `withDecay` until friction stops it.
201
+ *
202
+ * Use for map / zoom-canvas / large-image navigation. For dragging an
203
+ * element to a position with no momentum, use `useDrag` instead.
204
+ */
205
+ declare function usePan(options?: PanOptions): UsePanResult;
206
+
207
+ export { type DragConstraints, type DragOptions, type PanOptions, type SwipeDirection, type SwipeOptions, type UseDragResult, type UsePanResult, type UseSwipeResult, useDrag, usePan, useSwipe };
@@ -0,0 +1,207 @@
1
+ import { PanGesture } from 'react-native-gesture-handler';
2
+ import { useAnimatedStyle, SharedValue } from 'react-native-reanimated';
3
+
4
+ /**
5
+ * Public types for `@onlynative/inertia-gestures`.
6
+ */
7
+ /**
8
+ * Bounds the dragged value can reach. Each side is optional — omit to leave
9
+ * that direction unbounded. Coordinates are in the same space the drag
10
+ * publishes (pixels of translation from the dragged element's resting
11
+ * position), so `{ left: -100, right: 100 }` allows ±100 px of horizontal
12
+ * travel.
13
+ */
14
+ interface DragConstraints {
15
+ left?: number;
16
+ right?: number;
17
+ top?: number;
18
+ bottom?: number;
19
+ }
20
+ /**
21
+ * Configuration for `useDrag`. All fields are optional; the defaults give an
22
+ * unconstrained two-axis drag with no elasticity.
23
+ */
24
+ interface DragOptions {
25
+ /**
26
+ * Restrict the drag to one axis. Defaults to `'both'`. When `'x'` is set
27
+ * the y-axis shared value never updates (and vice versa); the gesture
28
+ * still tracks both for velocity reporting on `onDragEnd`.
29
+ */
30
+ axis?: 'x' | 'y' | 'both';
31
+ /**
32
+ * Travel bounds. Out-of-bounds values clamp to the limit unless `elastic`
33
+ * is non-zero, in which case overshoot is dampened (rubber-band feel).
34
+ */
35
+ constraints?: DragConstraints;
36
+ /**
37
+ * Rubber-band coefficient applied to overshoot past `constraints`. `0`
38
+ * (default) hard-clamps; `1` is fully elastic (no resistance). Typical
39
+ * Framer-Motion-style feel sits around `0.2`–`0.4`.
40
+ */
41
+ elastic?: number;
42
+ /**
43
+ * Fired on the JS thread when the drag begins.
44
+ */
45
+ onDragStart?: () => void;
46
+ /**
47
+ * Fired on the JS thread when the drag finishes (release or cancel). The
48
+ * payload is the final translation and the release velocity in px/sec.
49
+ */
50
+ onDragEnd?: (info: {
51
+ x: number;
52
+ y: number;
53
+ velocity: {
54
+ x: number;
55
+ y: number;
56
+ };
57
+ }) => void;
58
+ }
59
+
60
+ interface UseDragResult {
61
+ /** Pan gesture to pass to a `<GestureDetector>`. */
62
+ gesture: PanGesture;
63
+ /**
64
+ * Animated style fragment (a single `transform` entry) to stack onto the
65
+ * dragged Motion primitive's `style` prop. Stable across renders.
66
+ */
67
+ animatedStyle: ReturnType<typeof useAnimatedStyle>;
68
+ /** Current x translation in pixels. UI-thread shared value. */
69
+ dragX: SharedValue<number>;
70
+ /** Current y translation in pixels. UI-thread shared value. */
71
+ dragY: SharedValue<number>;
72
+ /** True while the gesture is active. */
73
+ isDragging: SharedValue<boolean>;
74
+ }
75
+ /**
76
+ * Drag a Motion primitive with `react-native-gesture-handler`'s pan gesture.
77
+ *
78
+ * The hook owns a pair of shared values (`dragX`, `dragY`) and a `Pan`
79
+ * gesture that updates them on the UI thread. The returned `animatedStyle`
80
+ * is a self-contained `transform: [{ translateX }, { translateY }]` fragment;
81
+ * stack it onto the dragged component without colliding with Motion's own
82
+ * `animate` transforms.
83
+ *
84
+ * Usage:
85
+ * ```tsx
86
+ * const drag = useDrag({ axis: 'x', constraints: { left: -100, right: 100 } })
87
+ * return (
88
+ * <GestureDetector gesture={drag.gesture}>
89
+ * <Motion.View style={drag.animatedStyle} />
90
+ * </GestureDetector>
91
+ * )
92
+ * ```
93
+ */
94
+ declare function useDrag(options?: DragOptions): UseDragResult;
95
+
96
+ type SwipeDirection = 'left' | 'right' | 'up' | 'down';
97
+ interface SwipeOptions {
98
+ /**
99
+ * Allowed swipe directions. Defaults to all four. The gesture only commits
100
+ * for directions in this list — a horizontal swipe with `directions:
101
+ * ['up', 'down']` will not fire `onSwipe`.
102
+ */
103
+ directions?: SwipeDirection[];
104
+ /**
105
+ * Pixel distance threshold past which a release commits the swipe. Defaults
106
+ * to `80`.
107
+ */
108
+ distanceThreshold?: number;
109
+ /**
110
+ * Velocity threshold (px/sec) past which a release commits the swipe even
111
+ * before the distance threshold is reached — flick-style gestures. Defaults
112
+ * to `800`.
113
+ */
114
+ velocityThreshold?: number;
115
+ /**
116
+ * Fired on the JS thread when the gesture commits in an allowed direction.
117
+ */
118
+ onSwipe?: (direction: SwipeDirection, info: {
119
+ distance: number;
120
+ velocity: number;
121
+ }) => void;
122
+ }
123
+ interface UseSwipeResult {
124
+ /** Pan gesture to pass to a `<GestureDetector>`. */
125
+ gesture: PanGesture;
126
+ /**
127
+ * Animated style fragment exposing live translation while the gesture is
128
+ * active. Snaps back to `{ 0, 0 }` after release (whether or not the swipe
129
+ * committed) via a default spring.
130
+ */
131
+ animatedStyle: ReturnType<typeof useAnimatedStyle>;
132
+ /** Live x translation. */
133
+ swipeX: SharedValue<number>;
134
+ /** Live y translation. */
135
+ swipeY: SharedValue<number>;
136
+ /** True while the user is actively swiping. */
137
+ isActive: SharedValue<boolean>;
138
+ }
139
+ /**
140
+ * Directional commit-or-snap-back gesture. Tracks live translation while the
141
+ * user drags and fires `onSwipe(direction)` on release if either the distance
142
+ * or velocity threshold is exceeded in an allowed direction. The position
143
+ * shared values always animate back to zero — the consumer is responsible
144
+ * for whatever side effect the commit drives (delete a row, dismiss a sheet,
145
+ * etc.).
146
+ *
147
+ * Usage:
148
+ * ```tsx
149
+ * const swipe = useSwipe({
150
+ * directions: ['left'],
151
+ * onSwipe: (dir) => deleteRow(),
152
+ * })
153
+ * return (
154
+ * <GestureDetector gesture={swipe.gesture}>
155
+ * <Motion.View style={swipe.animatedStyle}>...</Motion.View>
156
+ * </GestureDetector>
157
+ * )
158
+ * ```
159
+ */
160
+ declare function useSwipe(options?: SwipeOptions): UseSwipeResult;
161
+
162
+ interface PanOptions {
163
+ /**
164
+ * Translation bounds. Each side is optional; out-of-bounds motion during
165
+ * the active gesture and during the post-release decay is hard-clamped
166
+ * (Reanimated's `withDecay` `clamp` param). Decay-style overshoot is not
167
+ * supported here — for rubber-banded bounds, prefer `useDrag` with
168
+ * `elastic`.
169
+ */
170
+ constraints?: DragConstraints;
171
+ /**
172
+ * Deceleration applied to the post-release momentum. Higher = momentum
173
+ * dies faster. Reanimated default is `0.998`; lower values feel more
174
+ * "slippy". Range: roughly `0.99` (slow) to `0.999` (long glide).
175
+ */
176
+ deceleration?: number;
177
+ /**
178
+ * Disable the post-release momentum entirely. Defaults to `false` — pan
179
+ * coasts after release. Set to `true` for a hard stop on release (drag-like
180
+ * behavior).
181
+ */
182
+ disableMomentum?: boolean;
183
+ }
184
+ interface UsePanResult {
185
+ /** Pan gesture to pass to a `<GestureDetector>`. */
186
+ gesture: PanGesture;
187
+ /** Stable animated `transform` style. */
188
+ animatedStyle: ReturnType<typeof useAnimatedStyle>;
189
+ /** Live x translation, persistent across gestures. */
190
+ panX: SharedValue<number>;
191
+ /** Live y translation, persistent across gestures. */
192
+ panY: SharedValue<number>;
193
+ /** True while the user is actively panning. Decay phase reads `false`. */
194
+ isPanning: SharedValue<boolean>;
195
+ }
196
+ /**
197
+ * Camera-pan-style drag with momentum on release. Translation persists
198
+ * across separate pan gestures (the next pan starts from the current
199
+ * position, not zero), and on release the translation continues to glide
200
+ * via Reanimated's `withDecay` until friction stops it.
201
+ *
202
+ * Use for map / zoom-canvas / large-image navigation. For dragging an
203
+ * element to a position with no momentum, use `useDrag` instead.
204
+ */
205
+ declare function usePan(options?: PanOptions): UsePanResult;
206
+
207
+ export { type DragConstraints, type DragOptions, type PanOptions, type SwipeDirection, type SwipeOptions, type UseDragResult, type UsePanResult, type UseSwipeResult, useDrag, usePan, useSwipe };
package/dist/index.js ADDED
@@ -0,0 +1,259 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var reactNativeGestureHandler = require('react-native-gesture-handler');
5
+ var reactNativeReanimated = require('react-native-reanimated');
6
+
7
+ // src/useDrag.ts
8
+ function useDrag(options = {}) {
9
+ const {
10
+ axis = "both",
11
+ constraints,
12
+ elastic = 0,
13
+ onDragStart,
14
+ onDragEnd
15
+ } = options;
16
+ const dragX = reactNativeReanimated.useSharedValue(0);
17
+ const dragY = reactNativeReanimated.useSharedValue(0);
18
+ const startX = reactNativeReanimated.useSharedValue(0);
19
+ const startY = reactNativeReanimated.useSharedValue(0);
20
+ const isDragging = reactNativeReanimated.useSharedValue(false);
21
+ const lockX = axis !== "y";
22
+ const lockY = axis !== "x";
23
+ const left = constraints?.left;
24
+ const right = constraints?.right;
25
+ const top = constraints?.top;
26
+ const bottom = constraints?.bottom;
27
+ const elasticCoef = elastic;
28
+ const gesture = react.useMemo(() => {
29
+ const pan = reactNativeGestureHandler.Gesture.Pan().onStart(() => {
30
+ "worklet";
31
+ startX.value = dragX.value;
32
+ startY.value = dragY.value;
33
+ isDragging.value = true;
34
+ if (onDragStart) reactNativeReanimated.runOnJS(onDragStart)();
35
+ }).onUpdate((e) => {
36
+ "worklet";
37
+ if (lockX) {
38
+ dragX.value = applyBounds(
39
+ startX.value + e.translationX,
40
+ left,
41
+ right,
42
+ elasticCoef
43
+ );
44
+ }
45
+ if (lockY) {
46
+ dragY.value = applyBounds(
47
+ startY.value + e.translationY,
48
+ top,
49
+ bottom,
50
+ elasticCoef
51
+ );
52
+ }
53
+ }).onEnd((e) => {
54
+ "worklet";
55
+ isDragging.value = false;
56
+ if (onDragEnd) {
57
+ const x = dragX.value;
58
+ const y = dragY.value;
59
+ const vx = e.velocityX;
60
+ const vy = e.velocityY;
61
+ reactNativeReanimated.runOnJS(onDragEnd)({ x, y, velocity: { x: vx, y: vy } });
62
+ }
63
+ });
64
+ return pan;
65
+ }, [
66
+ lockX,
67
+ lockY,
68
+ left,
69
+ right,
70
+ top,
71
+ bottom,
72
+ elasticCoef,
73
+ onDragStart,
74
+ onDragEnd,
75
+ dragX,
76
+ dragY,
77
+ startX,
78
+ startY,
79
+ isDragging
80
+ ]);
81
+ const animatedStyle = reactNativeReanimated.useAnimatedStyle(() => ({
82
+ transform: [{ translateX: dragX.value }, { translateY: dragY.value }]
83
+ }));
84
+ return { gesture, animatedStyle, dragX, dragY, isDragging };
85
+ }
86
+ function applyBounds(value, min, max, elastic) {
87
+ "worklet";
88
+ if (min !== void 0 && value < min) {
89
+ return elastic > 0 ? min + (value - min) * elastic : min;
90
+ }
91
+ if (max !== void 0 && value > max) {
92
+ return elastic > 0 ? max + (value - max) * elastic : max;
93
+ }
94
+ return value;
95
+ }
96
+ var DEFAULT_DIRECTIONS = ["left", "right", "up", "down"];
97
+ function useSwipe(options = {}) {
98
+ const {
99
+ directions = DEFAULT_DIRECTIONS,
100
+ distanceThreshold = 80,
101
+ velocityThreshold = 800,
102
+ onSwipe
103
+ } = options;
104
+ const swipeX = reactNativeReanimated.useSharedValue(0);
105
+ const swipeY = reactNativeReanimated.useSharedValue(0);
106
+ const isActive = reactNativeReanimated.useSharedValue(false);
107
+ const allowLeft = directions.includes("left");
108
+ const allowRight = directions.includes("right");
109
+ const allowUp = directions.includes("up");
110
+ const allowDown = directions.includes("down");
111
+ const gesture = react.useMemo(() => {
112
+ const pan = reactNativeGestureHandler.Gesture.Pan().onStart(() => {
113
+ "worklet";
114
+ isActive.value = true;
115
+ }).onUpdate((e) => {
116
+ "worklet";
117
+ swipeX.value = e.translationX;
118
+ swipeY.value = e.translationY;
119
+ }).onEnd((e) => {
120
+ "worklet";
121
+ isActive.value = false;
122
+ const direction = pickDirection(
123
+ e.translationX,
124
+ e.translationY,
125
+ e.velocityX,
126
+ e.velocityY,
127
+ distanceThreshold,
128
+ velocityThreshold,
129
+ allowLeft,
130
+ allowRight,
131
+ allowUp,
132
+ allowDown
133
+ );
134
+ if (direction !== null && onSwipe) {
135
+ const isHoriz = direction === "left" || direction === "right";
136
+ const distance = isHoriz ? Math.abs(e.translationX) : Math.abs(e.translationY);
137
+ const velocity = isHoriz ? Math.abs(e.velocityX) : Math.abs(e.velocityY);
138
+ reactNativeReanimated.runOnJS(onSwipe)(direction, { distance, velocity });
139
+ }
140
+ swipeX.value = reactNativeReanimated.withSpring(0);
141
+ swipeY.value = reactNativeReanimated.withSpring(0);
142
+ }).onFinalize(() => {
143
+ "worklet";
144
+ isActive.value = false;
145
+ });
146
+ return pan;
147
+ }, [
148
+ distanceThreshold,
149
+ velocityThreshold,
150
+ allowLeft,
151
+ allowRight,
152
+ allowUp,
153
+ allowDown,
154
+ onSwipe,
155
+ swipeX,
156
+ swipeY,
157
+ isActive
158
+ ]);
159
+ const animatedStyle = reactNativeReanimated.useAnimatedStyle(() => ({
160
+ transform: [{ translateX: swipeX.value }, { translateY: swipeY.value }]
161
+ }));
162
+ return { gesture, animatedStyle, swipeX, swipeY, isActive };
163
+ }
164
+ function pickDirection(tx, ty, vx, vy, distanceThreshold, velocityThreshold, allowLeft, allowRight, allowUp, allowDown) {
165
+ "worklet";
166
+ const absX = Math.abs(tx);
167
+ const absY = Math.abs(ty);
168
+ if (absX >= absY) {
169
+ const meets2 = absX >= distanceThreshold || Math.abs(vx) >= velocityThreshold;
170
+ if (!meets2) return null;
171
+ if (tx < 0 && allowLeft) return "left";
172
+ if (tx > 0 && allowRight) return "right";
173
+ return null;
174
+ }
175
+ const meets = absY >= distanceThreshold || Math.abs(vy) >= velocityThreshold;
176
+ if (!meets) return null;
177
+ if (ty < 0 && allowUp) return "up";
178
+ if (ty > 0 && allowDown) return "down";
179
+ return null;
180
+ }
181
+ function usePan(options = {}) {
182
+ const { constraints, deceleration, disableMomentum = false } = options;
183
+ const panX = reactNativeReanimated.useSharedValue(0);
184
+ const panY = reactNativeReanimated.useSharedValue(0);
185
+ const startX = reactNativeReanimated.useSharedValue(0);
186
+ const startY = reactNativeReanimated.useSharedValue(0);
187
+ const isPanning = reactNativeReanimated.useSharedValue(false);
188
+ const left = constraints?.left;
189
+ const right = constraints?.right;
190
+ const top = constraints?.top;
191
+ const bottom = constraints?.bottom;
192
+ const decel = deceleration;
193
+ const gesture = react.useMemo(() => {
194
+ const pan = reactNativeGestureHandler.Gesture.Pan().onStart(() => {
195
+ "worklet";
196
+ startX.value = panX.value;
197
+ startY.value = panY.value;
198
+ isPanning.value = true;
199
+ }).onUpdate((e) => {
200
+ "worklet";
201
+ panX.value = clamp(startX.value + e.translationX, left, right);
202
+ panY.value = clamp(startY.value + e.translationY, top, bottom);
203
+ }).onEnd((e) => {
204
+ "worklet";
205
+ isPanning.value = false;
206
+ if (disableMomentum) return;
207
+ const clampX = boundsTuple(left, right);
208
+ const clampY = boundsTuple(top, bottom);
209
+ panX.value = reactNativeReanimated.withDecay(decayConfig(e.velocityX, decel, clampX));
210
+ panY.value = reactNativeReanimated.withDecay(decayConfig(e.velocityY, decel, clampY));
211
+ }).onFinalize(() => {
212
+ "worklet";
213
+ isPanning.value = false;
214
+ });
215
+ return pan;
216
+ }, [
217
+ left,
218
+ right,
219
+ top,
220
+ bottom,
221
+ decel,
222
+ disableMomentum,
223
+ panX,
224
+ panY,
225
+ startX,
226
+ startY,
227
+ isPanning
228
+ ]);
229
+ const animatedStyle = reactNativeReanimated.useAnimatedStyle(() => ({
230
+ transform: [{ translateX: panX.value }, { translateY: panY.value }]
231
+ }));
232
+ return { gesture, animatedStyle, panX, panY, isPanning };
233
+ }
234
+ function clamp(value, min, max) {
235
+ "worklet";
236
+ if (min !== void 0 && value < min) return min;
237
+ if (max !== void 0 && value > max) return max;
238
+ return value;
239
+ }
240
+ function boundsTuple(min, max) {
241
+ "worklet";
242
+ if (min === void 0 && max === void 0) return void 0;
243
+ return [min ?? Number.NEGATIVE_INFINITY, max ?? Number.POSITIVE_INFINITY];
244
+ }
245
+ function decayConfig(velocity, deceleration, clamp2) {
246
+ "worklet";
247
+ const cfg = {
248
+ velocity
249
+ };
250
+ if (deceleration !== void 0) cfg.deceleration = deceleration;
251
+ if (clamp2 !== void 0) cfg.clamp = clamp2;
252
+ return cfg;
253
+ }
254
+
255
+ exports.useDrag = useDrag;
256
+ exports.usePan = usePan;
257
+ exports.useSwipe = useSwipe;
258
+ //# sourceMappingURL=index.js.map
259
+ //# sourceMappingURL=index.js.map