@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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useDrag.ts","../src/useSwipe.ts","../src/usePan.ts"],"names":["useSharedValue","useMemo","Gesture","runOnJS","useAnimatedStyle","withSpring","meets","withDecay","clamp"],"mappings":"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","file":"index.js","sourcesContent":["import { useMemo } from 'react'\nimport { Gesture, type PanGesture } from 'react-native-gesture-handler'\nimport {\n runOnJS,\n useAnimatedStyle,\n useSharedValue,\n type SharedValue,\n} from 'react-native-reanimated'\nimport type { DragConstraints, DragOptions } from './types'\n\nexport interface UseDragResult {\n /** Pan gesture to pass to a `<GestureDetector>`. */\n gesture: PanGesture\n /**\n * Animated style fragment (a single `transform` entry) to stack onto the\n * dragged Motion primitive's `style` prop. Stable across renders.\n */\n animatedStyle: ReturnType<typeof useAnimatedStyle>\n /** Current x translation in pixels. UI-thread shared value. */\n dragX: SharedValue<number>\n /** Current y translation in pixels. UI-thread shared value. */\n dragY: SharedValue<number>\n /** True while the gesture is active. */\n isDragging: SharedValue<boolean>\n}\n\n/**\n * Drag a Motion primitive with `react-native-gesture-handler`'s pan gesture.\n *\n * The hook owns a pair of shared values (`dragX`, `dragY`) and a `Pan`\n * gesture that updates them on the UI thread. The returned `animatedStyle`\n * is a self-contained `transform: [{ translateX }, { translateY }]` fragment;\n * stack it onto the dragged component without colliding with Motion's own\n * `animate` transforms.\n *\n * Usage:\n * ```tsx\n * const drag = useDrag({ axis: 'x', constraints: { left: -100, right: 100 } })\n * return (\n * <GestureDetector gesture={drag.gesture}>\n * <Motion.View style={drag.animatedStyle} />\n * </GestureDetector>\n * )\n * ```\n */\nexport function useDrag(options: DragOptions = {}): UseDragResult {\n const {\n axis = 'both',\n constraints,\n elastic = 0,\n onDragStart,\n onDragEnd,\n } = options\n\n const dragX = useSharedValue(0)\n const dragY = useSharedValue(0)\n const startX = useSharedValue(0)\n const startY = useSharedValue(0)\n const isDragging = useSharedValue(false)\n\n // Snapshot scalars into local consts so the Pan handlers (worklets) capture\n // primitives, not the closing `options` object — a fresh `options` literal\n // each render would otherwise force a new gesture identity.\n const lockX = axis !== 'y'\n const lockY = axis !== 'x'\n const left = constraints?.left\n const right = constraints?.right\n const top = constraints?.top\n const bottom = constraints?.bottom\n const elasticCoef = elastic\n\n const gesture = useMemo(() => {\n const pan = Gesture.Pan()\n .onStart(() => {\n 'worklet'\n startX.value = dragX.value\n startY.value = dragY.value\n isDragging.value = true\n if (onDragStart) runOnJS(onDragStart)()\n })\n .onUpdate((e) => {\n 'worklet'\n if (lockX) {\n dragX.value = applyBounds(\n startX.value + e.translationX,\n left,\n right,\n elasticCoef,\n )\n }\n if (lockY) {\n dragY.value = applyBounds(\n startY.value + e.translationY,\n top,\n bottom,\n elasticCoef,\n )\n }\n })\n .onEnd((e) => {\n 'worklet'\n isDragging.value = false\n if (onDragEnd) {\n const x = dragX.value\n const y = dragY.value\n const vx = e.velocityX\n const vy = e.velocityY\n runOnJS(onDragEnd)({ x, y, velocity: { x: vx, y: vy } })\n }\n })\n return pan\n }, [\n lockX,\n lockY,\n left,\n right,\n top,\n bottom,\n elasticCoef,\n onDragStart,\n onDragEnd,\n dragX,\n dragY,\n startX,\n startY,\n isDragging,\n ])\n\n const animatedStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: dragX.value }, { translateY: dragY.value }],\n }))\n\n return { gesture, animatedStyle, dragX, dragY, isDragging }\n}\n\n/**\n * Clamp `value` to `[min, max]`. When `elastic > 0` the overshoot beyond a\n * bound is scaled by `elastic` instead of hard-clamped, giving a rubber-band\n * feel. `min` / `max` may be `undefined` to leave that side unbounded.\n *\n * Worklet — runs on the UI thread inside the pan handler.\n */\nfunction applyBounds(\n value: number,\n min: number | undefined,\n max: number | undefined,\n elastic: number,\n): number {\n 'worklet'\n if (min !== undefined && value < min) {\n return elastic > 0 ? min + (value - min) * elastic : min\n }\n if (max !== undefined && value > max) {\n return elastic > 0 ? max + (value - max) * elastic : max\n }\n return value\n}\n\nexport type { DragConstraints, DragOptions }\n","import { useMemo } from 'react'\nimport { Gesture, type PanGesture } from 'react-native-gesture-handler'\nimport {\n runOnJS,\n useAnimatedStyle,\n useSharedValue,\n withSpring,\n type SharedValue,\n} from 'react-native-reanimated'\n\nexport type SwipeDirection = 'left' | 'right' | 'up' | 'down'\n\nexport interface SwipeOptions {\n /**\n * Allowed swipe directions. Defaults to all four. The gesture only commits\n * for directions in this list — a horizontal swipe with `directions:\n * ['up', 'down']` will not fire `onSwipe`.\n */\n directions?: SwipeDirection[]\n /**\n * Pixel distance threshold past which a release commits the swipe. Defaults\n * to `80`.\n */\n distanceThreshold?: number\n /**\n * Velocity threshold (px/sec) past which a release commits the swipe even\n * before the distance threshold is reached — flick-style gestures. Defaults\n * to `800`.\n */\n velocityThreshold?: number\n /**\n * Fired on the JS thread when the gesture commits in an allowed direction.\n */\n onSwipe?: (\n direction: SwipeDirection,\n info: { distance: number; velocity: number },\n ) => void\n}\n\nexport interface UseSwipeResult {\n /** Pan gesture to pass to a `<GestureDetector>`. */\n gesture: PanGesture\n /**\n * Animated style fragment exposing live translation while the gesture is\n * active. Snaps back to `{ 0, 0 }` after release (whether or not the swipe\n * committed) via a default spring.\n */\n animatedStyle: ReturnType<typeof useAnimatedStyle>\n /** Live x translation. */\n swipeX: SharedValue<number>\n /** Live y translation. */\n swipeY: SharedValue<number>\n /** True while the user is actively swiping. */\n isActive: SharedValue<boolean>\n}\n\nconst DEFAULT_DIRECTIONS: SwipeDirection[] = ['left', 'right', 'up', 'down']\n\n/**\n * Directional commit-or-snap-back gesture. Tracks live translation while the\n * user drags and fires `onSwipe(direction)` on release if either the distance\n * or velocity threshold is exceeded in an allowed direction. The position\n * shared values always animate back to zero — the consumer is responsible\n * for whatever side effect the commit drives (delete a row, dismiss a sheet,\n * etc.).\n *\n * Usage:\n * ```tsx\n * const swipe = useSwipe({\n * directions: ['left'],\n * onSwipe: (dir) => deleteRow(),\n * })\n * return (\n * <GestureDetector gesture={swipe.gesture}>\n * <Motion.View style={swipe.animatedStyle}>...</Motion.View>\n * </GestureDetector>\n * )\n * ```\n */\nexport function useSwipe(options: SwipeOptions = {}): UseSwipeResult {\n const {\n directions = DEFAULT_DIRECTIONS,\n distanceThreshold = 80,\n velocityThreshold = 800,\n onSwipe,\n } = options\n\n const swipeX = useSharedValue(0)\n const swipeY = useSharedValue(0)\n const isActive = useSharedValue(false)\n\n const allowLeft = directions.includes('left')\n const allowRight = directions.includes('right')\n const allowUp = directions.includes('up')\n const allowDown = directions.includes('down')\n\n const gesture = useMemo(() => {\n const pan = Gesture.Pan()\n .onStart(() => {\n 'worklet'\n isActive.value = true\n })\n .onUpdate((e) => {\n 'worklet'\n swipeX.value = e.translationX\n swipeY.value = e.translationY\n })\n .onEnd((e) => {\n 'worklet'\n isActive.value = false\n const direction = pickDirection(\n e.translationX,\n e.translationY,\n e.velocityX,\n e.velocityY,\n distanceThreshold,\n velocityThreshold,\n allowLeft,\n allowRight,\n allowUp,\n allowDown,\n )\n if (direction !== null && onSwipe) {\n const isHoriz = direction === 'left' || direction === 'right'\n const distance = isHoriz\n ? Math.abs(e.translationX)\n : Math.abs(e.translationY)\n const velocity = isHoriz\n ? Math.abs(e.velocityX)\n : Math.abs(e.velocityY)\n runOnJS(onSwipe)(direction, { distance, velocity })\n }\n swipeX.value = withSpring(0)\n swipeY.value = withSpring(0)\n })\n .onFinalize(() => {\n 'worklet'\n isActive.value = false\n })\n return pan\n }, [\n distanceThreshold,\n velocityThreshold,\n allowLeft,\n allowRight,\n allowUp,\n allowDown,\n onSwipe,\n swipeX,\n swipeY,\n isActive,\n ])\n\n const animatedStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: swipeX.value }, { translateY: swipeY.value }],\n }))\n\n return { gesture, animatedStyle, swipeX, swipeY, isActive }\n}\n\n/**\n * Decide which (allowed) direction a release commits to, based on the larger\n * axis of motion. Returns `null` if neither distance nor velocity threshold\n * is met along the dominant axis or if that direction is disallowed.\n *\n * Worklet — runs on the UI thread inside the pan handler.\n */\nfunction pickDirection(\n tx: number,\n ty: number,\n vx: number,\n vy: number,\n distanceThreshold: number,\n velocityThreshold: number,\n allowLeft: boolean,\n allowRight: boolean,\n allowUp: boolean,\n allowDown: boolean,\n): SwipeDirection | null {\n 'worklet'\n const absX = Math.abs(tx)\n const absY = Math.abs(ty)\n if (absX >= absY) {\n const meets = absX >= distanceThreshold || Math.abs(vx) >= velocityThreshold\n if (!meets) return null\n if (tx < 0 && allowLeft) return 'left'\n if (tx > 0 && allowRight) return 'right'\n return null\n }\n const meets = absY >= distanceThreshold || Math.abs(vy) >= velocityThreshold\n if (!meets) return null\n if (ty < 0 && allowUp) return 'up'\n if (ty > 0 && allowDown) return 'down'\n return null\n}\n","import { useMemo } from 'react'\nimport { Gesture, type PanGesture } from 'react-native-gesture-handler'\nimport {\n useAnimatedStyle,\n useSharedValue,\n withDecay,\n type SharedValue,\n} from 'react-native-reanimated'\nimport type { DragConstraints } from './types'\n\nexport interface PanOptions {\n /**\n * Translation bounds. Each side is optional; out-of-bounds motion during\n * the active gesture and during the post-release decay is hard-clamped\n * (Reanimated's `withDecay` `clamp` param). Decay-style overshoot is not\n * supported here — for rubber-banded bounds, prefer `useDrag` with\n * `elastic`.\n */\n constraints?: DragConstraints\n /**\n * Deceleration applied to the post-release momentum. Higher = momentum\n * dies faster. Reanimated default is `0.998`; lower values feel more\n * \"slippy\". Range: roughly `0.99` (slow) to `0.999` (long glide).\n */\n deceleration?: number\n /**\n * Disable the post-release momentum entirely. Defaults to `false` — pan\n * coasts after release. Set to `true` for a hard stop on release (drag-like\n * behavior).\n */\n disableMomentum?: boolean\n}\n\nexport interface UsePanResult {\n /** Pan gesture to pass to a `<GestureDetector>`. */\n gesture: PanGesture\n /** Stable animated `transform` style. */\n animatedStyle: ReturnType<typeof useAnimatedStyle>\n /** Live x translation, persistent across gestures. */\n panX: SharedValue<number>\n /** Live y translation, persistent across gestures. */\n panY: SharedValue<number>\n /** True while the user is actively panning. Decay phase reads `false`. */\n isPanning: SharedValue<boolean>\n}\n\n/**\n * Camera-pan-style drag with momentum on release. Translation persists\n * across separate pan gestures (the next pan starts from the current\n * position, not zero), and on release the translation continues to glide\n * via Reanimated's `withDecay` until friction stops it.\n *\n * Use for map / zoom-canvas / large-image navigation. For dragging an\n * element to a position with no momentum, use `useDrag` instead.\n */\nexport function usePan(options: PanOptions = {}): UsePanResult {\n const { constraints, deceleration, disableMomentum = false } = options\n\n const panX = useSharedValue(0)\n const panY = useSharedValue(0)\n const startX = useSharedValue(0)\n const startY = useSharedValue(0)\n const isPanning = useSharedValue(false)\n\n const left = constraints?.left\n const right = constraints?.right\n const top = constraints?.top\n const bottom = constraints?.bottom\n const decel = deceleration\n\n const gesture = useMemo(() => {\n const pan = Gesture.Pan()\n .onStart(() => {\n 'worklet'\n startX.value = panX.value\n startY.value = panY.value\n isPanning.value = true\n })\n .onUpdate((e) => {\n 'worklet'\n panX.value = clamp(startX.value + e.translationX, left, right)\n panY.value = clamp(startY.value + e.translationY, top, bottom)\n })\n .onEnd((e) => {\n 'worklet'\n isPanning.value = false\n if (disableMomentum) return\n const clampX = boundsTuple(left, right)\n const clampY = boundsTuple(top, bottom)\n panX.value = withDecay(decayConfig(e.velocityX, decel, clampX))\n panY.value = withDecay(decayConfig(e.velocityY, decel, clampY))\n })\n .onFinalize(() => {\n 'worklet'\n isPanning.value = false\n })\n return pan\n }, [\n left,\n right,\n top,\n bottom,\n decel,\n disableMomentum,\n panX,\n panY,\n startX,\n startY,\n isPanning,\n ])\n\n const animatedStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: panX.value }, { translateY: panY.value }],\n }))\n\n return { gesture, animatedStyle, panX, panY, isPanning }\n}\n\n/**\n * Hard-clamp `value` between `min` and `max`. Either bound may be undefined\n * to leave that side unbounded. Worklet — UI thread.\n */\nfunction clamp(\n value: number,\n min: number | undefined,\n max: number | undefined,\n): number {\n 'worklet'\n if (min !== undefined && value < min) return min\n if (max !== undefined && value > max) return max\n return value\n}\n\n/**\n * Build the `clamp` tuple Reanimated's `withDecay` expects, or `undefined`\n * when the axis is unbounded. `withDecay` requires both ends present, so a\n * one-sided constraint is widened with `±Infinity`.\n */\nfunction boundsTuple(\n min: number | undefined,\n max: number | undefined,\n): [number, number] | undefined {\n 'worklet'\n if (min === undefined && max === undefined) return undefined\n return [min ?? Number.NEGATIVE_INFINITY, max ?? Number.POSITIVE_INFINITY]\n}\n\nfunction decayConfig(\n velocity: number,\n deceleration: number | undefined,\n clamp: [number, number] | undefined,\n) {\n 'worklet'\n const cfg: {\n velocity: number\n deceleration?: number\n clamp?: [number, number]\n } = {\n velocity,\n }\n if (deceleration !== undefined) cfg.deceleration = deceleration\n if (clamp !== undefined) cfg.clamp = clamp\n return cfg\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,255 @@
1
+ import { useMemo } from 'react';
2
+ import { Gesture } from 'react-native-gesture-handler';
3
+ import { useSharedValue, runOnJS, useAnimatedStyle, withSpring, withDecay } from 'react-native-reanimated';
4
+
5
+ // src/useDrag.ts
6
+ function useDrag(options = {}) {
7
+ const {
8
+ axis = "both",
9
+ constraints,
10
+ elastic = 0,
11
+ onDragStart,
12
+ onDragEnd
13
+ } = options;
14
+ const dragX = useSharedValue(0);
15
+ const dragY = useSharedValue(0);
16
+ const startX = useSharedValue(0);
17
+ const startY = useSharedValue(0);
18
+ const isDragging = useSharedValue(false);
19
+ const lockX = axis !== "y";
20
+ const lockY = axis !== "x";
21
+ const left = constraints?.left;
22
+ const right = constraints?.right;
23
+ const top = constraints?.top;
24
+ const bottom = constraints?.bottom;
25
+ const elasticCoef = elastic;
26
+ const gesture = useMemo(() => {
27
+ const pan = Gesture.Pan().onStart(() => {
28
+ "worklet";
29
+ startX.value = dragX.value;
30
+ startY.value = dragY.value;
31
+ isDragging.value = true;
32
+ if (onDragStart) runOnJS(onDragStart)();
33
+ }).onUpdate((e) => {
34
+ "worklet";
35
+ if (lockX) {
36
+ dragX.value = applyBounds(
37
+ startX.value + e.translationX,
38
+ left,
39
+ right,
40
+ elasticCoef
41
+ );
42
+ }
43
+ if (lockY) {
44
+ dragY.value = applyBounds(
45
+ startY.value + e.translationY,
46
+ top,
47
+ bottom,
48
+ elasticCoef
49
+ );
50
+ }
51
+ }).onEnd((e) => {
52
+ "worklet";
53
+ isDragging.value = false;
54
+ if (onDragEnd) {
55
+ const x = dragX.value;
56
+ const y = dragY.value;
57
+ const vx = e.velocityX;
58
+ const vy = e.velocityY;
59
+ runOnJS(onDragEnd)({ x, y, velocity: { x: vx, y: vy } });
60
+ }
61
+ });
62
+ return pan;
63
+ }, [
64
+ lockX,
65
+ lockY,
66
+ left,
67
+ right,
68
+ top,
69
+ bottom,
70
+ elasticCoef,
71
+ onDragStart,
72
+ onDragEnd,
73
+ dragX,
74
+ dragY,
75
+ startX,
76
+ startY,
77
+ isDragging
78
+ ]);
79
+ const animatedStyle = useAnimatedStyle(() => ({
80
+ transform: [{ translateX: dragX.value }, { translateY: dragY.value }]
81
+ }));
82
+ return { gesture, animatedStyle, dragX, dragY, isDragging };
83
+ }
84
+ function applyBounds(value, min, max, elastic) {
85
+ "worklet";
86
+ if (min !== void 0 && value < min) {
87
+ return elastic > 0 ? min + (value - min) * elastic : min;
88
+ }
89
+ if (max !== void 0 && value > max) {
90
+ return elastic > 0 ? max + (value - max) * elastic : max;
91
+ }
92
+ return value;
93
+ }
94
+ var DEFAULT_DIRECTIONS = ["left", "right", "up", "down"];
95
+ function useSwipe(options = {}) {
96
+ const {
97
+ directions = DEFAULT_DIRECTIONS,
98
+ distanceThreshold = 80,
99
+ velocityThreshold = 800,
100
+ onSwipe
101
+ } = options;
102
+ const swipeX = useSharedValue(0);
103
+ const swipeY = useSharedValue(0);
104
+ const isActive = useSharedValue(false);
105
+ const allowLeft = directions.includes("left");
106
+ const allowRight = directions.includes("right");
107
+ const allowUp = directions.includes("up");
108
+ const allowDown = directions.includes("down");
109
+ const gesture = useMemo(() => {
110
+ const pan = Gesture.Pan().onStart(() => {
111
+ "worklet";
112
+ isActive.value = true;
113
+ }).onUpdate((e) => {
114
+ "worklet";
115
+ swipeX.value = e.translationX;
116
+ swipeY.value = e.translationY;
117
+ }).onEnd((e) => {
118
+ "worklet";
119
+ isActive.value = false;
120
+ const direction = pickDirection(
121
+ e.translationX,
122
+ e.translationY,
123
+ e.velocityX,
124
+ e.velocityY,
125
+ distanceThreshold,
126
+ velocityThreshold,
127
+ allowLeft,
128
+ allowRight,
129
+ allowUp,
130
+ allowDown
131
+ );
132
+ if (direction !== null && onSwipe) {
133
+ const isHoriz = direction === "left" || direction === "right";
134
+ const distance = isHoriz ? Math.abs(e.translationX) : Math.abs(e.translationY);
135
+ const velocity = isHoriz ? Math.abs(e.velocityX) : Math.abs(e.velocityY);
136
+ runOnJS(onSwipe)(direction, { distance, velocity });
137
+ }
138
+ swipeX.value = withSpring(0);
139
+ swipeY.value = withSpring(0);
140
+ }).onFinalize(() => {
141
+ "worklet";
142
+ isActive.value = false;
143
+ });
144
+ return pan;
145
+ }, [
146
+ distanceThreshold,
147
+ velocityThreshold,
148
+ allowLeft,
149
+ allowRight,
150
+ allowUp,
151
+ allowDown,
152
+ onSwipe,
153
+ swipeX,
154
+ swipeY,
155
+ isActive
156
+ ]);
157
+ const animatedStyle = useAnimatedStyle(() => ({
158
+ transform: [{ translateX: swipeX.value }, { translateY: swipeY.value }]
159
+ }));
160
+ return { gesture, animatedStyle, swipeX, swipeY, isActive };
161
+ }
162
+ function pickDirection(tx, ty, vx, vy, distanceThreshold, velocityThreshold, allowLeft, allowRight, allowUp, allowDown) {
163
+ "worklet";
164
+ const absX = Math.abs(tx);
165
+ const absY = Math.abs(ty);
166
+ if (absX >= absY) {
167
+ const meets2 = absX >= distanceThreshold || Math.abs(vx) >= velocityThreshold;
168
+ if (!meets2) return null;
169
+ if (tx < 0 && allowLeft) return "left";
170
+ if (tx > 0 && allowRight) return "right";
171
+ return null;
172
+ }
173
+ const meets = absY >= distanceThreshold || Math.abs(vy) >= velocityThreshold;
174
+ if (!meets) return null;
175
+ if (ty < 0 && allowUp) return "up";
176
+ if (ty > 0 && allowDown) return "down";
177
+ return null;
178
+ }
179
+ function usePan(options = {}) {
180
+ const { constraints, deceleration, disableMomentum = false } = options;
181
+ const panX = useSharedValue(0);
182
+ const panY = useSharedValue(0);
183
+ const startX = useSharedValue(0);
184
+ const startY = useSharedValue(0);
185
+ const isPanning = useSharedValue(false);
186
+ const left = constraints?.left;
187
+ const right = constraints?.right;
188
+ const top = constraints?.top;
189
+ const bottom = constraints?.bottom;
190
+ const decel = deceleration;
191
+ const gesture = useMemo(() => {
192
+ const pan = Gesture.Pan().onStart(() => {
193
+ "worklet";
194
+ startX.value = panX.value;
195
+ startY.value = panY.value;
196
+ isPanning.value = true;
197
+ }).onUpdate((e) => {
198
+ "worklet";
199
+ panX.value = clamp(startX.value + e.translationX, left, right);
200
+ panY.value = clamp(startY.value + e.translationY, top, bottom);
201
+ }).onEnd((e) => {
202
+ "worklet";
203
+ isPanning.value = false;
204
+ if (disableMomentum) return;
205
+ const clampX = boundsTuple(left, right);
206
+ const clampY = boundsTuple(top, bottom);
207
+ panX.value = withDecay(decayConfig(e.velocityX, decel, clampX));
208
+ panY.value = withDecay(decayConfig(e.velocityY, decel, clampY));
209
+ }).onFinalize(() => {
210
+ "worklet";
211
+ isPanning.value = false;
212
+ });
213
+ return pan;
214
+ }, [
215
+ left,
216
+ right,
217
+ top,
218
+ bottom,
219
+ decel,
220
+ disableMomentum,
221
+ panX,
222
+ panY,
223
+ startX,
224
+ startY,
225
+ isPanning
226
+ ]);
227
+ const animatedStyle = useAnimatedStyle(() => ({
228
+ transform: [{ translateX: panX.value }, { translateY: panY.value }]
229
+ }));
230
+ return { gesture, animatedStyle, panX, panY, isPanning };
231
+ }
232
+ function clamp(value, min, max) {
233
+ "worklet";
234
+ if (min !== void 0 && value < min) return min;
235
+ if (max !== void 0 && value > max) return max;
236
+ return value;
237
+ }
238
+ function boundsTuple(min, max) {
239
+ "worklet";
240
+ if (min === void 0 && max === void 0) return void 0;
241
+ return [min ?? Number.NEGATIVE_INFINITY, max ?? Number.POSITIVE_INFINITY];
242
+ }
243
+ function decayConfig(velocity, deceleration, clamp2) {
244
+ "worklet";
245
+ const cfg = {
246
+ velocity
247
+ };
248
+ if (deceleration !== void 0) cfg.deceleration = deceleration;
249
+ if (clamp2 !== void 0) cfg.clamp = clamp2;
250
+ return cfg;
251
+ }
252
+
253
+ export { useDrag, usePan, useSwipe };
254
+ //# sourceMappingURL=index.mjs.map
255
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useDrag.ts","../src/useSwipe.ts","../src/usePan.ts"],"names":["useSharedValue","useMemo","Gesture","runOnJS","useAnimatedStyle","meets","clamp"],"mappings":";;;;;AA6CO,SAAS,OAAA,CAAQ,OAAA,GAAuB,EAAC,EAAkB;AAChE,EAAA,MAAM;AAAA,IACJ,IAAA,GAAO,MAAA;AAAA,IACP,WAAA;AAAA,IACA,OAAA,GAAU,CAAA;AAAA,IACV,WAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,MAAM,KAAA,GAAQ,eAAe,CAAC,CAAA;AAC9B,EAAA,MAAM,KAAA,GAAQ,eAAe,CAAC,CAAA;AAC9B,EAAA,MAAM,MAAA,GAAS,eAAe,CAAC,CAAA;AAC/B,EAAA,MAAM,MAAA,GAAS,eAAe,CAAC,CAAA;AAC/B,EAAA,MAAM,UAAA,GAAa,eAAe,KAAK,CAAA;AAKvC,EAAA,MAAM,QAAQ,IAAA,KAAS,GAAA;AACvB,EAAA,MAAM,QAAQ,IAAA,KAAS,GAAA;AACvB,EAAA,MAAM,OAAO,WAAA,EAAa,IAAA;AAC1B,EAAA,MAAM,QAAQ,WAAA,EAAa,KAAA;AAC3B,EAAA,MAAM,MAAM,WAAA,EAAa,GAAA;AACzB,EAAA,MAAM,SAAS,WAAA,EAAa,MAAA;AAC5B,EAAA,MAAM,WAAA,GAAc,OAAA;AAEpB,EAAA,MAAM,OAAA,GAAU,QAAQ,MAAM;AAC5B,IAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,CACrB,QAAQ,MAAM;AACb,MAAA,SAAA;AACA,MAAA,MAAA,CAAO,QAAQ,KAAA,CAAM,KAAA;AACrB,MAAA,MAAA,CAAO,QAAQ,KAAA,CAAM,KAAA;AACrB,MAAA,UAAA,CAAW,KAAA,GAAQ,IAAA;AACnB,MAAA,IAAI,WAAA,EAAa,OAAA,CAAQ,WAAW,CAAA,EAAE;AAAA,IACxC,CAAC,CAAA,CACA,QAAA,CAAS,CAAC,CAAA,KAAM;AACf,MAAA,SAAA;AACA,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,KAAA,CAAM,KAAA,GAAQ,WAAA;AAAA,UACZ,MAAA,CAAO,QAAQ,CAAA,CAAE,YAAA;AAAA,UACjB,IAAA;AAAA,UACA,KAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AACA,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,KAAA,CAAM,KAAA,GAAQ,WAAA;AAAA,UACZ,MAAA,CAAO,QAAQ,CAAA,CAAE,YAAA;AAAA,UACjB,GAAA;AAAA,UACA,MAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,IACF,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,CAAA,KAAM;AACZ,MAAA,SAAA;AACA,MAAA,UAAA,CAAW,KAAA,GAAQ,KAAA;AACnB,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,MAAM,IAAI,KAAA,CAAM,KAAA;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,KAAA;AAChB,QAAA,MAAM,KAAK,CAAA,CAAE,SAAA;AACb,QAAA,MAAM,KAAK,CAAA,CAAE,SAAA;AACb,QAAA,OAAA,CAAQ,SAAS,CAAA,CAAE,EAAE,CAAA,EAAG,CAAA,EAAG,QAAA,EAAU,EAAE,CAAA,EAAG,EAAA,EAAI,CAAA,EAAG,EAAA,EAAG,EAAG,CAAA;AAAA,MACzD;AAAA,IACF,CAAC,CAAA;AACH,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG;AAAA,IACD,KAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAA;AAAA,IACA,GAAA;AAAA,IACA,MAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,aAAA,GAAgB,iBAAiB,OAAO;AAAA,IAC5C,SAAA,EAAW,CAAC,EAAE,UAAA,EAAY,KAAA,CAAM,KAAA,EAAM,EAAG,EAAE,UAAA,EAAY,KAAA,CAAM,KAAA,EAAO;AAAA,GACtE,CAAE,CAAA;AAEF,EAAA,OAAO,EAAE,OAAA,EAAS,aAAA,EAAe,KAAA,EAAO,OAAO,UAAA,EAAW;AAC5D;AASA,SAAS,WAAA,CACP,KAAA,EACA,GAAA,EACA,GAAA,EACA,OAAA,EACQ;AACR,EAAA,SAAA;AACA,EAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,KAAA,GAAQ,GAAA,EAAK;AACpC,IAAA,OAAO,OAAA,GAAU,CAAA,GAAI,GAAA,GAAA,CAAO,KAAA,GAAQ,OAAO,OAAA,GAAU,GAAA;AAAA,EACvD;AACA,EAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,KAAA,GAAQ,GAAA,EAAK;AACpC,IAAA,OAAO,OAAA,GAAU,CAAA,GAAI,GAAA,GAAA,CAAO,KAAA,GAAQ,OAAO,OAAA,GAAU,GAAA;AAAA,EACvD;AACA,EAAA,OAAO,KAAA;AACT;ACpGA,IAAM,kBAAA,GAAuC,CAAC,MAAA,EAAQ,OAAA,EAAS,MAAM,MAAM,CAAA;AAuBpE,SAAS,QAAA,CAAS,OAAA,GAAwB,EAAC,EAAmB;AACnE,EAAA,MAAM;AAAA,IACJ,UAAA,GAAa,kBAAA;AAAA,IACb,iBAAA,GAAoB,EAAA;AAAA,IACpB,iBAAA,GAAoB,GAAA;AAAA,IACpB;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,MAAM,MAAA,GAASA,eAAe,CAAC,CAAA;AAC/B,EAAA,MAAM,MAAA,GAASA,eAAe,CAAC,CAAA;AAC/B,EAAA,MAAM,QAAA,GAAWA,eAAe,KAAK,CAAA;AAErC,EAAA,MAAM,SAAA,GAAY,UAAA,CAAW,QAAA,CAAS,MAAM,CAAA;AAC5C,EAAA,MAAM,UAAA,GAAa,UAAA,CAAW,QAAA,CAAS,OAAO,CAAA;AAC9C,EAAA,MAAM,OAAA,GAAU,UAAA,CAAW,QAAA,CAAS,IAAI,CAAA;AACxC,EAAA,MAAM,SAAA,GAAY,UAAA,CAAW,QAAA,CAAS,MAAM,CAAA;AAE5C,EAAA,MAAM,OAAA,GAAUC,QAAQ,MAAM;AAC5B,IAAA,MAAM,GAAA,GAAMC,OAAAA,CAAQ,GAAA,EAAI,CACrB,QAAQ,MAAM;AACb,MAAA,SAAA;AACA,MAAA,QAAA,CAAS,KAAA,GAAQ,IAAA;AAAA,IACnB,CAAC,CAAA,CACA,QAAA,CAAS,CAAC,CAAA,KAAM;AACf,MAAA,SAAA;AACA,MAAA,MAAA,CAAO,QAAQ,CAAA,CAAE,YAAA;AACjB,MAAA,MAAA,CAAO,QAAQ,CAAA,CAAE,YAAA;AAAA,IACnB,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,CAAA,KAAM;AACZ,MAAA,SAAA;AACA,MAAA,QAAA,CAAS,KAAA,GAAQ,KAAA;AACjB,MAAA,MAAM,SAAA,GAAY,aAAA;AAAA,QAChB,CAAA,CAAE,YAAA;AAAA,QACF,CAAA,CAAE,YAAA;AAAA,QACF,CAAA,CAAE,SAAA;AAAA,QACF,CAAA,CAAE,SAAA;AAAA,QACF,iBAAA;AAAA,QACA,iBAAA;AAAA,QACA,SAAA;AAAA,QACA,UAAA;AAAA,QACA,OAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,IAAI,SAAA,KAAc,QAAQ,OAAA,EAAS;AACjC,QAAA,MAAM,OAAA,GAAU,SAAA,KAAc,MAAA,IAAU,SAAA,KAAc,OAAA;AACtD,QAAA,MAAM,QAAA,GAAW,OAAA,GACb,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,YAAY,CAAA,GACvB,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,YAAY,CAAA;AAC3B,QAAA,MAAM,QAAA,GAAW,OAAA,GACb,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,SAAS,CAAA,GACpB,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,SAAS,CAAA;AACxB,QAAAC,QAAQ,OAAO,CAAA,CAAE,WAAW,EAAE,QAAA,EAAU,UAAU,CAAA;AAAA,MACpD;AACA,MAAA,MAAA,CAAO,KAAA,GAAQ,WAAW,CAAC,CAAA;AAC3B,MAAA,MAAA,CAAO,KAAA,GAAQ,WAAW,CAAC,CAAA;AAAA,IAC7B,CAAC,CAAA,CACA,UAAA,CAAW,MAAM;AAChB,MAAA,SAAA;AACA,MAAA,QAAA,CAAS,KAAA,GAAQ,KAAA;AAAA,IACnB,CAAC,CAAA;AACH,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG;AAAA,IACD,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,aAAA,GAAgBC,iBAAiB,OAAO;AAAA,IAC5C,SAAA,EAAW,CAAC,EAAE,UAAA,EAAY,MAAA,CAAO,KAAA,EAAM,EAAG,EAAE,UAAA,EAAY,MAAA,CAAO,KAAA,EAAO;AAAA,GACxE,CAAE,CAAA;AAEF,EAAA,OAAO,EAAE,OAAA,EAAS,aAAA,EAAe,MAAA,EAAQ,QAAQ,QAAA,EAAS;AAC5D;AASA,SAAS,aAAA,CACP,EAAA,EACA,EAAA,EACA,EAAA,EACA,EAAA,EACA,mBACA,iBAAA,EACA,SAAA,EACA,UAAA,EACA,OAAA,EACA,SAAA,EACuB;AACvB,EAAA,SAAA;AACA,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA;AACxB,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA;AACxB,EAAA,IAAI,QAAQ,IAAA,EAAM;AAChB,IAAA,MAAMC,SAAQ,IAAA,IAAQ,iBAAA,IAAqB,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA,IAAK,iBAAA;AAC3D,IAAA,IAAI,CAACA,QAAO,OAAO,IAAA;AACnB,IAAA,IAAI,EAAA,GAAK,CAAA,IAAK,SAAA,EAAW,OAAO,MAAA;AAChC,IAAA,IAAI,EAAA,GAAK,CAAA,IAAK,UAAA,EAAY,OAAO,OAAA;AACjC,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,QAAQ,IAAA,IAAQ,iBAAA,IAAqB,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA,IAAK,iBAAA;AAC3D,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,IAAI,EAAA,GAAK,CAAA,IAAK,OAAA,EAAS,OAAO,IAAA;AAC9B,EAAA,IAAI,EAAA,GAAK,CAAA,IAAK,SAAA,EAAW,OAAO,MAAA;AAChC,EAAA,OAAO,IAAA;AACT;AC3IO,SAAS,MAAA,CAAO,OAAA,GAAsB,EAAC,EAAiB;AAC7D,EAAA,MAAM,EAAE,WAAA,EAAa,YAAA,EAAc,eAAA,GAAkB,OAAM,GAAI,OAAA;AAE/D,EAAA,MAAM,IAAA,GAAOL,eAAe,CAAC,CAAA;AAC7B,EAAA,MAAM,IAAA,GAAOA,eAAe,CAAC,CAAA;AAC7B,EAAA,MAAM,MAAA,GAASA,eAAe,CAAC,CAAA;AAC/B,EAAA,MAAM,MAAA,GAASA,eAAe,CAAC,CAAA;AAC/B,EAAA,MAAM,SAAA,GAAYA,eAAe,KAAK,CAAA;AAEtC,EAAA,MAAM,OAAO,WAAA,EAAa,IAAA;AAC1B,EAAA,MAAM,QAAQ,WAAA,EAAa,KAAA;AAC3B,EAAA,MAAM,MAAM,WAAA,EAAa,GAAA;AACzB,EAAA,MAAM,SAAS,WAAA,EAAa,MAAA;AAC5B,EAAA,MAAM,KAAA,GAAQ,YAAA;AAEd,EAAA,MAAM,OAAA,GAAUC,QAAQ,MAAM;AAC5B,IAAA,MAAM,GAAA,GAAMC,OAAAA,CAAQ,GAAA,EAAI,CACrB,QAAQ,MAAM;AACb,MAAA,SAAA;AACA,MAAA,MAAA,CAAO,QAAQ,IAAA,CAAK,KAAA;AACpB,MAAA,MAAA,CAAO,QAAQ,IAAA,CAAK,KAAA;AACpB,MAAA,SAAA,CAAU,KAAA,GAAQ,IAAA;AAAA,IACpB,CAAC,CAAA,CACA,QAAA,CAAS,CAAC,CAAA,KAAM;AACf,MAAA,SAAA;AACA,MAAA,IAAA,CAAK,QAAQ,KAAA,CAAM,MAAA,CAAO,QAAQ,CAAA,CAAE,YAAA,EAAc,MAAM,KAAK,CAAA;AAC7D,MAAA,IAAA,CAAK,QAAQ,KAAA,CAAM,MAAA,CAAO,QAAQ,CAAA,CAAE,YAAA,EAAc,KAAK,MAAM,CAAA;AAAA,IAC/D,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,CAAA,KAAM;AACZ,MAAA,SAAA;AACA,MAAA,SAAA,CAAU,KAAA,GAAQ,KAAA;AAClB,MAAA,IAAI,eAAA,EAAiB;AACrB,MAAA,MAAM,MAAA,GAAS,WAAA,CAAY,IAAA,EAAM,KAAK,CAAA;AACtC,MAAA,MAAM,MAAA,GAAS,WAAA,CAAY,GAAA,EAAK,MAAM,CAAA;AACtC,MAAA,IAAA,CAAK,QAAQ,SAAA,CAAU,WAAA,CAAY,EAAE,SAAA,EAAW,KAAA,EAAO,MAAM,CAAC,CAAA;AAC9D,MAAA,IAAA,CAAK,QAAQ,SAAA,CAAU,WAAA,CAAY,EAAE,SAAA,EAAW,KAAA,EAAO,MAAM,CAAC,CAAA;AAAA,IAChE,CAAC,CAAA,CACA,UAAA,CAAW,MAAM;AAChB,MAAA,SAAA;AACA,MAAA,SAAA,CAAU,KAAA,GAAQ,KAAA;AAAA,IACpB,CAAC,CAAA;AACH,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG;AAAA,IACD,IAAA;AAAA,IACA,KAAA;AAAA,IACA,GAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA;AAAA,IACA,eAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,aAAA,GAAgBE,iBAAiB,OAAO;AAAA,IAC5C,SAAA,EAAW,CAAC,EAAE,UAAA,EAAY,IAAA,CAAK,KAAA,EAAM,EAAG,EAAE,UAAA,EAAY,IAAA,CAAK,KAAA,EAAO;AAAA,GACpE,CAAE,CAAA;AAEF,EAAA,OAAO,EAAE,OAAA,EAAS,aAAA,EAAe,IAAA,EAAM,MAAM,SAAA,EAAU;AACzD;AAMA,SAAS,KAAA,CACP,KAAA,EACA,GAAA,EACA,GAAA,EACQ;AACR,EAAA,SAAA;AACA,EAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,KAAA,GAAQ,GAAA,EAAK,OAAO,GAAA;AAC7C,EAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,KAAA,GAAQ,GAAA,EAAK,OAAO,GAAA;AAC7C,EAAA,OAAO,KAAA;AACT;AAOA,SAAS,WAAA,CACP,KACA,GAAA,EAC8B;AAC9B,EAAA,SAAA;AACA,EAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,GAAA,KAAQ,MAAA,EAAW,OAAO,MAAA;AACnD,EAAA,OAAO,CAAC,GAAA,IAAO,MAAA,CAAO,iBAAA,EAAmB,GAAA,IAAO,OAAO,iBAAiB,CAAA;AAC1E;AAEA,SAAS,WAAA,CACP,QAAA,EACA,YAAA,EACAE,MAAAA,EACA;AACA,EAAA,SAAA;AACA,EAAA,MAAM,GAAA,GAIF;AAAA,IACF;AAAA,GACF;AACA,EAAA,IAAI,YAAA,KAAiB,MAAA,EAAW,GAAA,CAAI,YAAA,GAAe,YAAA;AACnD,EAAA,IAAIA,MAAAA,KAAU,MAAA,EAAW,GAAA,CAAI,KAAA,GAAQA,MAAAA;AACrC,EAAA,OAAO,GAAA;AACT","file":"index.mjs","sourcesContent":["import { useMemo } from 'react'\nimport { Gesture, type PanGesture } from 'react-native-gesture-handler'\nimport {\n runOnJS,\n useAnimatedStyle,\n useSharedValue,\n type SharedValue,\n} from 'react-native-reanimated'\nimport type { DragConstraints, DragOptions } from './types'\n\nexport interface UseDragResult {\n /** Pan gesture to pass to a `<GestureDetector>`. */\n gesture: PanGesture\n /**\n * Animated style fragment (a single `transform` entry) to stack onto the\n * dragged Motion primitive's `style` prop. Stable across renders.\n */\n animatedStyle: ReturnType<typeof useAnimatedStyle>\n /** Current x translation in pixels. UI-thread shared value. */\n dragX: SharedValue<number>\n /** Current y translation in pixels. UI-thread shared value. */\n dragY: SharedValue<number>\n /** True while the gesture is active. */\n isDragging: SharedValue<boolean>\n}\n\n/**\n * Drag a Motion primitive with `react-native-gesture-handler`'s pan gesture.\n *\n * The hook owns a pair of shared values (`dragX`, `dragY`) and a `Pan`\n * gesture that updates them on the UI thread. The returned `animatedStyle`\n * is a self-contained `transform: [{ translateX }, { translateY }]` fragment;\n * stack it onto the dragged component without colliding with Motion's own\n * `animate` transforms.\n *\n * Usage:\n * ```tsx\n * const drag = useDrag({ axis: 'x', constraints: { left: -100, right: 100 } })\n * return (\n * <GestureDetector gesture={drag.gesture}>\n * <Motion.View style={drag.animatedStyle} />\n * </GestureDetector>\n * )\n * ```\n */\nexport function useDrag(options: DragOptions = {}): UseDragResult {\n const {\n axis = 'both',\n constraints,\n elastic = 0,\n onDragStart,\n onDragEnd,\n } = options\n\n const dragX = useSharedValue(0)\n const dragY = useSharedValue(0)\n const startX = useSharedValue(0)\n const startY = useSharedValue(0)\n const isDragging = useSharedValue(false)\n\n // Snapshot scalars into local consts so the Pan handlers (worklets) capture\n // primitives, not the closing `options` object — a fresh `options` literal\n // each render would otherwise force a new gesture identity.\n const lockX = axis !== 'y'\n const lockY = axis !== 'x'\n const left = constraints?.left\n const right = constraints?.right\n const top = constraints?.top\n const bottom = constraints?.bottom\n const elasticCoef = elastic\n\n const gesture = useMemo(() => {\n const pan = Gesture.Pan()\n .onStart(() => {\n 'worklet'\n startX.value = dragX.value\n startY.value = dragY.value\n isDragging.value = true\n if (onDragStart) runOnJS(onDragStart)()\n })\n .onUpdate((e) => {\n 'worklet'\n if (lockX) {\n dragX.value = applyBounds(\n startX.value + e.translationX,\n left,\n right,\n elasticCoef,\n )\n }\n if (lockY) {\n dragY.value = applyBounds(\n startY.value + e.translationY,\n top,\n bottom,\n elasticCoef,\n )\n }\n })\n .onEnd((e) => {\n 'worklet'\n isDragging.value = false\n if (onDragEnd) {\n const x = dragX.value\n const y = dragY.value\n const vx = e.velocityX\n const vy = e.velocityY\n runOnJS(onDragEnd)({ x, y, velocity: { x: vx, y: vy } })\n }\n })\n return pan\n }, [\n lockX,\n lockY,\n left,\n right,\n top,\n bottom,\n elasticCoef,\n onDragStart,\n onDragEnd,\n dragX,\n dragY,\n startX,\n startY,\n isDragging,\n ])\n\n const animatedStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: dragX.value }, { translateY: dragY.value }],\n }))\n\n return { gesture, animatedStyle, dragX, dragY, isDragging }\n}\n\n/**\n * Clamp `value` to `[min, max]`. When `elastic > 0` the overshoot beyond a\n * bound is scaled by `elastic` instead of hard-clamped, giving a rubber-band\n * feel. `min` / `max` may be `undefined` to leave that side unbounded.\n *\n * Worklet — runs on the UI thread inside the pan handler.\n */\nfunction applyBounds(\n value: number,\n min: number | undefined,\n max: number | undefined,\n elastic: number,\n): number {\n 'worklet'\n if (min !== undefined && value < min) {\n return elastic > 0 ? min + (value - min) * elastic : min\n }\n if (max !== undefined && value > max) {\n return elastic > 0 ? max + (value - max) * elastic : max\n }\n return value\n}\n\nexport type { DragConstraints, DragOptions }\n","import { useMemo } from 'react'\nimport { Gesture, type PanGesture } from 'react-native-gesture-handler'\nimport {\n runOnJS,\n useAnimatedStyle,\n useSharedValue,\n withSpring,\n type SharedValue,\n} from 'react-native-reanimated'\n\nexport type SwipeDirection = 'left' | 'right' | 'up' | 'down'\n\nexport interface SwipeOptions {\n /**\n * Allowed swipe directions. Defaults to all four. The gesture only commits\n * for directions in this list — a horizontal swipe with `directions:\n * ['up', 'down']` will not fire `onSwipe`.\n */\n directions?: SwipeDirection[]\n /**\n * Pixel distance threshold past which a release commits the swipe. Defaults\n * to `80`.\n */\n distanceThreshold?: number\n /**\n * Velocity threshold (px/sec) past which a release commits the swipe even\n * before the distance threshold is reached — flick-style gestures. Defaults\n * to `800`.\n */\n velocityThreshold?: number\n /**\n * Fired on the JS thread when the gesture commits in an allowed direction.\n */\n onSwipe?: (\n direction: SwipeDirection,\n info: { distance: number; velocity: number },\n ) => void\n}\n\nexport interface UseSwipeResult {\n /** Pan gesture to pass to a `<GestureDetector>`. */\n gesture: PanGesture\n /**\n * Animated style fragment exposing live translation while the gesture is\n * active. Snaps back to `{ 0, 0 }` after release (whether or not the swipe\n * committed) via a default spring.\n */\n animatedStyle: ReturnType<typeof useAnimatedStyle>\n /** Live x translation. */\n swipeX: SharedValue<number>\n /** Live y translation. */\n swipeY: SharedValue<number>\n /** True while the user is actively swiping. */\n isActive: SharedValue<boolean>\n}\n\nconst DEFAULT_DIRECTIONS: SwipeDirection[] = ['left', 'right', 'up', 'down']\n\n/**\n * Directional commit-or-snap-back gesture. Tracks live translation while the\n * user drags and fires `onSwipe(direction)` on release if either the distance\n * or velocity threshold is exceeded in an allowed direction. The position\n * shared values always animate back to zero — the consumer is responsible\n * for whatever side effect the commit drives (delete a row, dismiss a sheet,\n * etc.).\n *\n * Usage:\n * ```tsx\n * const swipe = useSwipe({\n * directions: ['left'],\n * onSwipe: (dir) => deleteRow(),\n * })\n * return (\n * <GestureDetector gesture={swipe.gesture}>\n * <Motion.View style={swipe.animatedStyle}>...</Motion.View>\n * </GestureDetector>\n * )\n * ```\n */\nexport function useSwipe(options: SwipeOptions = {}): UseSwipeResult {\n const {\n directions = DEFAULT_DIRECTIONS,\n distanceThreshold = 80,\n velocityThreshold = 800,\n onSwipe,\n } = options\n\n const swipeX = useSharedValue(0)\n const swipeY = useSharedValue(0)\n const isActive = useSharedValue(false)\n\n const allowLeft = directions.includes('left')\n const allowRight = directions.includes('right')\n const allowUp = directions.includes('up')\n const allowDown = directions.includes('down')\n\n const gesture = useMemo(() => {\n const pan = Gesture.Pan()\n .onStart(() => {\n 'worklet'\n isActive.value = true\n })\n .onUpdate((e) => {\n 'worklet'\n swipeX.value = e.translationX\n swipeY.value = e.translationY\n })\n .onEnd((e) => {\n 'worklet'\n isActive.value = false\n const direction = pickDirection(\n e.translationX,\n e.translationY,\n e.velocityX,\n e.velocityY,\n distanceThreshold,\n velocityThreshold,\n allowLeft,\n allowRight,\n allowUp,\n allowDown,\n )\n if (direction !== null && onSwipe) {\n const isHoriz = direction === 'left' || direction === 'right'\n const distance = isHoriz\n ? Math.abs(e.translationX)\n : Math.abs(e.translationY)\n const velocity = isHoriz\n ? Math.abs(e.velocityX)\n : Math.abs(e.velocityY)\n runOnJS(onSwipe)(direction, { distance, velocity })\n }\n swipeX.value = withSpring(0)\n swipeY.value = withSpring(0)\n })\n .onFinalize(() => {\n 'worklet'\n isActive.value = false\n })\n return pan\n }, [\n distanceThreshold,\n velocityThreshold,\n allowLeft,\n allowRight,\n allowUp,\n allowDown,\n onSwipe,\n swipeX,\n swipeY,\n isActive,\n ])\n\n const animatedStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: swipeX.value }, { translateY: swipeY.value }],\n }))\n\n return { gesture, animatedStyle, swipeX, swipeY, isActive }\n}\n\n/**\n * Decide which (allowed) direction a release commits to, based on the larger\n * axis of motion. Returns `null` if neither distance nor velocity threshold\n * is met along the dominant axis or if that direction is disallowed.\n *\n * Worklet — runs on the UI thread inside the pan handler.\n */\nfunction pickDirection(\n tx: number,\n ty: number,\n vx: number,\n vy: number,\n distanceThreshold: number,\n velocityThreshold: number,\n allowLeft: boolean,\n allowRight: boolean,\n allowUp: boolean,\n allowDown: boolean,\n): SwipeDirection | null {\n 'worklet'\n const absX = Math.abs(tx)\n const absY = Math.abs(ty)\n if (absX >= absY) {\n const meets = absX >= distanceThreshold || Math.abs(vx) >= velocityThreshold\n if (!meets) return null\n if (tx < 0 && allowLeft) return 'left'\n if (tx > 0 && allowRight) return 'right'\n return null\n }\n const meets = absY >= distanceThreshold || Math.abs(vy) >= velocityThreshold\n if (!meets) return null\n if (ty < 0 && allowUp) return 'up'\n if (ty > 0 && allowDown) return 'down'\n return null\n}\n","import { useMemo } from 'react'\nimport { Gesture, type PanGesture } from 'react-native-gesture-handler'\nimport {\n useAnimatedStyle,\n useSharedValue,\n withDecay,\n type SharedValue,\n} from 'react-native-reanimated'\nimport type { DragConstraints } from './types'\n\nexport interface PanOptions {\n /**\n * Translation bounds. Each side is optional; out-of-bounds motion during\n * the active gesture and during the post-release decay is hard-clamped\n * (Reanimated's `withDecay` `clamp` param). Decay-style overshoot is not\n * supported here — for rubber-banded bounds, prefer `useDrag` with\n * `elastic`.\n */\n constraints?: DragConstraints\n /**\n * Deceleration applied to the post-release momentum. Higher = momentum\n * dies faster. Reanimated default is `0.998`; lower values feel more\n * \"slippy\". Range: roughly `0.99` (slow) to `0.999` (long glide).\n */\n deceleration?: number\n /**\n * Disable the post-release momentum entirely. Defaults to `false` — pan\n * coasts after release. Set to `true` for a hard stop on release (drag-like\n * behavior).\n */\n disableMomentum?: boolean\n}\n\nexport interface UsePanResult {\n /** Pan gesture to pass to a `<GestureDetector>`. */\n gesture: PanGesture\n /** Stable animated `transform` style. */\n animatedStyle: ReturnType<typeof useAnimatedStyle>\n /** Live x translation, persistent across gestures. */\n panX: SharedValue<number>\n /** Live y translation, persistent across gestures. */\n panY: SharedValue<number>\n /** True while the user is actively panning. Decay phase reads `false`. */\n isPanning: SharedValue<boolean>\n}\n\n/**\n * Camera-pan-style drag with momentum on release. Translation persists\n * across separate pan gestures (the next pan starts from the current\n * position, not zero), and on release the translation continues to glide\n * via Reanimated's `withDecay` until friction stops it.\n *\n * Use for map / zoom-canvas / large-image navigation. For dragging an\n * element to a position with no momentum, use `useDrag` instead.\n */\nexport function usePan(options: PanOptions = {}): UsePanResult {\n const { constraints, deceleration, disableMomentum = false } = options\n\n const panX = useSharedValue(0)\n const panY = useSharedValue(0)\n const startX = useSharedValue(0)\n const startY = useSharedValue(0)\n const isPanning = useSharedValue(false)\n\n const left = constraints?.left\n const right = constraints?.right\n const top = constraints?.top\n const bottom = constraints?.bottom\n const decel = deceleration\n\n const gesture = useMemo(() => {\n const pan = Gesture.Pan()\n .onStart(() => {\n 'worklet'\n startX.value = panX.value\n startY.value = panY.value\n isPanning.value = true\n })\n .onUpdate((e) => {\n 'worklet'\n panX.value = clamp(startX.value + e.translationX, left, right)\n panY.value = clamp(startY.value + e.translationY, top, bottom)\n })\n .onEnd((e) => {\n 'worklet'\n isPanning.value = false\n if (disableMomentum) return\n const clampX = boundsTuple(left, right)\n const clampY = boundsTuple(top, bottom)\n panX.value = withDecay(decayConfig(e.velocityX, decel, clampX))\n panY.value = withDecay(decayConfig(e.velocityY, decel, clampY))\n })\n .onFinalize(() => {\n 'worklet'\n isPanning.value = false\n })\n return pan\n }, [\n left,\n right,\n top,\n bottom,\n decel,\n disableMomentum,\n panX,\n panY,\n startX,\n startY,\n isPanning,\n ])\n\n const animatedStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: panX.value }, { translateY: panY.value }],\n }))\n\n return { gesture, animatedStyle, panX, panY, isPanning }\n}\n\n/**\n * Hard-clamp `value` between `min` and `max`. Either bound may be undefined\n * to leave that side unbounded. Worklet — UI thread.\n */\nfunction clamp(\n value: number,\n min: number | undefined,\n max: number | undefined,\n): number {\n 'worklet'\n if (min !== undefined && value < min) return min\n if (max !== undefined && value > max) return max\n return value\n}\n\n/**\n * Build the `clamp` tuple Reanimated's `withDecay` expects, or `undefined`\n * when the axis is unbounded. `withDecay` requires both ends present, so a\n * one-sided constraint is widened with `±Infinity`.\n */\nfunction boundsTuple(\n min: number | undefined,\n max: number | undefined,\n): [number, number] | undefined {\n 'worklet'\n if (min === undefined && max === undefined) return undefined\n return [min ?? Number.NEGATIVE_INFINITY, max ?? Number.POSITIVE_INFINITY]\n}\n\nfunction decayConfig(\n velocity: number,\n deceleration: number | undefined,\n clamp: [number, number] | undefined,\n) {\n 'worklet'\n const cfg: {\n velocity: number\n deceleration?: number\n clamp?: [number, number]\n } = {\n velocity,\n }\n if (deceleration !== undefined) cfg.deceleration = deceleration\n if (clamp !== undefined) cfg.clamp = clamp\n return cfg\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@onlynative/inertia-gestures",
3
+ "version": "0.0.1-alpha.1",
4
+ "description": "Gesture-handler-driven drag / pan / swipe adapters for @onlynative/inertia.",
5
+ "license": "MIT",
6
+ "author": "OnlyNative",
7
+ "homepage": "https://github.com/onlynative/inertia",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/onlynative/inertia.git",
11
+ "directory": "packages/gestures"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/onlynative/inertia/issues"
15
+ },
16
+ "keywords": [
17
+ "react-native",
18
+ "reanimated",
19
+ "gesture-handler",
20
+ "drag",
21
+ "pan",
22
+ "swipe",
23
+ "inertia"
24
+ ],
25
+ "sideEffects": false,
26
+ "main": "./dist/index.js",
27
+ "module": "./dist/index.mjs",
28
+ "types": "./dist/index.d.ts",
29
+ "react-native": "./src/index.ts",
30
+ "source": "./src/index.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "react-native": "./src/index.ts",
35
+ "source": "./src/index.ts",
36
+ "import": "./dist/index.mjs",
37
+ "require": "./dist/index.js"
38
+ },
39
+ "./package.json": "./package.json"
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "src",
44
+ "README.md",
45
+ "LICENSE",
46
+ "CHANGELOG.md",
47
+ "!**/__tests__",
48
+ "!**/*.test.*"
49
+ ],
50
+ "peerDependencies": {
51
+ "react": ">=19.0.0",
52
+ "react-native": ">=0.81.0",
53
+ "react-native-gesture-handler": ">=2.0.0",
54
+ "react-native-reanimated": ">=4.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@react-native/babel-preset": "^0.81.5",
58
+ "@testing-library/react-native": "^13.3.3",
59
+ "@types/jest": "^29.5.14",
60
+ "@types/react": "^19.1.0",
61
+ "jest": "^29.7.0",
62
+ "react": "19.1.0",
63
+ "react-native": "0.81.5",
64
+ "react-native-gesture-handler": "~2.20.0",
65
+ "react-native-reanimated": "~4.1.1",
66
+ "react-test-renderer": "19.1.0",
67
+ "tsup": "^8.3.5",
68
+ "typescript": "^5.7.3"
69
+ },
70
+ "publishConfig": {
71
+ "access": "public"
72
+ },
73
+ "scripts": {
74
+ "build": "tsup",
75
+ "dev": "tsup --watch",
76
+ "typecheck": "tsc --noEmit",
77
+ "test": "jest",
78
+ "lint": "eslint src",
79
+ "clean": "rm -rf dist .turbo *.tsbuildinfo"
80
+ }
81
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@onlynative/inertia-gestures` — gesture-handler-driven adapters for
3
+ * `@onlynative/inertia`.
4
+ *
5
+ * v0.2 surface:
6
+ * - `useDrag` — one- or two-axis drag with optional constraints and
7
+ * rubber-band elasticity.
8
+ * - `useSwipe` — directional commit-or-snap-back gesture (distance + velocity
9
+ * thresholds).
10
+ * - `usePan` — camera-style pan with momentum on release.
11
+ */
12
+ export { useDrag } from './useDrag'
13
+ export type { UseDragResult } from './useDrag'
14
+ export { useSwipe } from './useSwipe'
15
+ export type { SwipeDirection, SwipeOptions, UseSwipeResult } from './useSwipe'
16
+ export { usePan } from './usePan'
17
+ export type { PanOptions, UsePanResult } from './usePan'
18
+ export type { DragConstraints, DragOptions } from './types'
package/src/types.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Public types for `@onlynative/inertia-gestures`.
3
+ */
4
+
5
+ /**
6
+ * Bounds the dragged value can reach. Each side is optional — omit to leave
7
+ * that direction unbounded. Coordinates are in the same space the drag
8
+ * publishes (pixels of translation from the dragged element's resting
9
+ * position), so `{ left: -100, right: 100 }` allows ±100 px of horizontal
10
+ * travel.
11
+ */
12
+ export interface DragConstraints {
13
+ left?: number
14
+ right?: number
15
+ top?: number
16
+ bottom?: number
17
+ }
18
+
19
+ /**
20
+ * Configuration for `useDrag`. All fields are optional; the defaults give an
21
+ * unconstrained two-axis drag with no elasticity.
22
+ */
23
+ export interface DragOptions {
24
+ /**
25
+ * Restrict the drag to one axis. Defaults to `'both'`. When `'x'` is set
26
+ * the y-axis shared value never updates (and vice versa); the gesture
27
+ * still tracks both for velocity reporting on `onDragEnd`.
28
+ */
29
+ axis?: 'x' | 'y' | 'both'
30
+ /**
31
+ * Travel bounds. Out-of-bounds values clamp to the limit unless `elastic`
32
+ * is non-zero, in which case overshoot is dampened (rubber-band feel).
33
+ */
34
+ constraints?: DragConstraints
35
+ /**
36
+ * Rubber-band coefficient applied to overshoot past `constraints`. `0`
37
+ * (default) hard-clamps; `1` is fully elastic (no resistance). Typical
38
+ * Framer-Motion-style feel sits around `0.2`–`0.4`.
39
+ */
40
+ elastic?: number
41
+ /**
42
+ * Fired on the JS thread when the drag begins.
43
+ */
44
+ onDragStart?: () => void
45
+ /**
46
+ * Fired on the JS thread when the drag finishes (release or cancel). The
47
+ * payload is the final translation and the release velocity in px/sec.
48
+ */
49
+ onDragEnd?: (info: {
50
+ x: number
51
+ y: number
52
+ velocity: { x: number; y: number }
53
+ }) => void
54
+ }