@rootnative/inertia-gestures 0.0.19 → 0.0.21

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/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ This package ships in lockstep with `@rootnative/inertia` — version numbers tr
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.0.21] - 2026-10-26
10
+
11
+ **Lockstep version bump** alongside `@rootnative/inertia@0.0.21` (`useSpring` and `useInView` no longer cause a Reanimated warning on native). No runtime changes in this adapter; the `@rootnative/inertia` peer range moves to `>=0.0.21 <0.1.0`.
12
+
13
+ ## [0.0.20] - 2026-10-23
14
+
15
+ ### Changed
16
+
17
+ - **The supported band is Expo SDK 58.** The peer ranges are `react >=19.3.0 <20.0.0`, `react-native >=0.88.0 <0.89.0`, `react-native-reanimated >=4.7.0 <4.8.0` and `react-native-gesture-handler >=3.3.0 <4.0.0`. SDK 57 stays on `0.0.19`. See the `@rootnative/inertia` entry for the reason.
18
+ - **`useDrag`, `useSwipe` and `usePan` build their gesture with the Gesture Handler 3 hook API.** Each hook calls `usePanGesture` on a memoized config instead of the `Gesture.Pan()` builder, which Gesture Handler 3 marks as deprecated. The returned `gesture` is the `PanGesture` of that hook; pass it to `<GestureDetector>` as before. The lifecycle callbacks moved to the new names (`onActivate`, `onUpdate`, `onDeactivate`, `onFinalize`) with no change in when they fire. **Breaking for one case:** the legacy composition helpers (`Gesture.Simultaneous`, `Gesture.Race`, `Gesture.Exclusive`) do not accept a hook gesture. Compose with the Gesture Handler 3 composition hooks instead.
19
+
9
20
  ## [0.0.19] - 2026-10-17
10
21
 
11
22
  **Lockstep version bump** alongside `@rootnative/inertia@0.0.19` (`focusVisible` follows the browser `:focus-visible` on web, and a control that mounts disabled starts at its `useGestureLayer` `disabled` layer). No runtime changes in this adapter; the `@rootnative/inertia` peer range moves to `>=0.0.19 <0.1.0`.
@@ -133,7 +144,9 @@ Initial alpha publish alongside `@rootnative/inertia@0.0.0-alpha.0`. Optional ad
133
144
  - `useDrag({ onRelease })` — release worklet returns per-axis Inertia transitions (snap-to-tick spring, decay with bounds, etc.). Velocity stays on the UI thread; no JS round-trip.
134
145
  - `useSwipe`, `usePan` hooks composable with any `Motion.*` primitive via `<GestureDetector>`.
135
146
 
136
- [unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.19...HEAD
147
+ [unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.21...HEAD
148
+ [0.0.21]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.21
149
+ [0.0.20]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.20
137
150
  [0.0.19]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.19
138
151
  [0.0.18]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.18
139
152
  [0.0.17]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.17
package/README.md CHANGED
@@ -18,7 +18,7 @@ pnpm add @rootnative/inertia-gestures react-native-gesture-handler
18
18
 
19
19
  Then follow the [`react-native-gesture-handler` install guide](https://docs.swmansion.com/react-native-gesture-handler/docs/installation) — it needs `<GestureHandlerRootView>` near the root of your app.
20
20
 
21
- **Peer dependencies:** `@rootnative/inertia` (workspace or installed), `react >=19.2.3`, `react-native >=0.83.0 <0.87.0`, `react-native-reanimated >=4.5.0 <4.6.0`, `react-native-gesture-handler >=2.30.0 <3.0.0`.
21
+ **Peer dependencies:** `@rootnative/inertia` (workspace or installed), `react >=19.3.0`, `react-native >=0.88.0 <0.89.0`, `react-native-reanimated >=4.7.0 <4.8.0`, `react-native-gesture-handler >=3.3.0 <4.0.0`.
22
22
 
23
23
  ## What ships
24
24
 
package/dist/index.js CHANGED
@@ -40,81 +40,86 @@ function useDrag(options = {}) {
40
40
  const hasDragEnd = onDragEnd !== void 0;
41
41
  const fireDragStart = useLatestCallback(onDragStart);
42
42
  const fireDragEnd = useLatestCallback(onDragEnd);
43
- const gesture = react.useMemo(() => {
44
- const pan = reactNativeGestureHandler.Gesture.Pan().onStart(() => {
45
- "worklet";
46
- startX.value = dragX.value;
47
- startY.value = dragY.value;
48
- isDragging.value = true;
49
- if (hasDragStart) reactNativeReanimated.runOnJS(fireDragStart)();
50
- }).onUpdate((e) => {
51
- "worklet";
52
- if (lockX) {
53
- dragX.value = inertia.applyBounds(
54
- startX.value + e.translationX,
55
- left,
56
- right,
57
- elasticCoef
58
- );
59
- }
60
- if (lockY) {
61
- dragY.value = inertia.applyBounds(
62
- startY.value + e.translationY,
63
- top,
64
- bottom,
65
- elasticCoef
66
- );
67
- }
68
- }).onEnd((e) => {
69
- "worklet";
70
- isDragging.value = false;
71
- const x = dragX.value;
72
- const y = dragY.value;
73
- const vx = e.velocityX;
74
- const vy = e.velocityY;
75
- if (onRelease) {
76
- const result = onRelease({ x, y, velocity: { x: vx, y: vy } });
77
- if (result) {
78
- if (result.x && lockX) {
79
- const toX = "to" in result.x ? result.x.to : x;
80
- dragX.value = inertia.buildReleaseAnimation(
81
- result.x,
82
- toX
83
- );
84
- }
85
- if (result.y && lockY) {
86
- const toY = "to" in result.y ? result.y.to : y;
87
- dragY.value = inertia.buildReleaseAnimation(
88
- result.y,
89
- toY
90
- );
43
+ const config = react.useMemo(
44
+ () => ({
45
+ onActivate: () => {
46
+ "worklet";
47
+ startX.value = dragX.value;
48
+ startY.value = dragY.value;
49
+ isDragging.value = true;
50
+ if (hasDragStart) reactNativeReanimated.runOnJS(fireDragStart)();
51
+ },
52
+ onUpdate: (e) => {
53
+ "worklet";
54
+ if (lockX) {
55
+ dragX.value = inertia.applyBounds(
56
+ startX.value + e.translationX,
57
+ left,
58
+ right,
59
+ elasticCoef
60
+ );
61
+ }
62
+ if (lockY) {
63
+ dragY.value = inertia.applyBounds(
64
+ startY.value + e.translationY,
65
+ top,
66
+ bottom,
67
+ elasticCoef
68
+ );
69
+ }
70
+ },
71
+ onDeactivate: (e) => {
72
+ "worklet";
73
+ isDragging.value = false;
74
+ const x = dragX.value;
75
+ const y = dragY.value;
76
+ const vx = e.velocityX;
77
+ const vy = e.velocityY;
78
+ if (onRelease) {
79
+ const result = onRelease({ x, y, velocity: { x: vx, y: vy } });
80
+ if (result) {
81
+ if (result.x && lockX) {
82
+ const toX = "to" in result.x ? result.x.to : x;
83
+ dragX.value = inertia.buildReleaseAnimation(
84
+ result.x,
85
+ toX
86
+ );
87
+ }
88
+ if (result.y && lockY) {
89
+ const toY = "to" in result.y ? result.y.to : y;
90
+ dragY.value = inertia.buildReleaseAnimation(
91
+ result.y,
92
+ toY
93
+ );
94
+ }
91
95
  }
92
96
  }
97
+ if (hasDragEnd) {
98
+ reactNativeReanimated.runOnJS(fireDragEnd)({ x, y, velocity: { x: vx, y: vy } });
99
+ }
93
100
  }
94
- if (hasDragEnd) {
95
- reactNativeReanimated.runOnJS(fireDragEnd)({ x, y, velocity: { x: vx, y: vy } });
96
- }
97
- });
98
- return pan;
99
- }, [
100
- lockX,
101
- lockY,
102
- left,
103
- right,
104
- top,
105
- bottom,
106
- elasticCoef,
107
- hasDragStart,
108
- hasDragEnd,
109
- fireDragStart,
110
- fireDragEnd,
111
- onRelease,
112
- dragX,
113
- dragY,
114
- startX,
115
- startY,
116
- isDragging
117
- ]);
101
+ }),
102
+ [
103
+ lockX,
104
+ lockY,
105
+ left,
106
+ right,
107
+ top,
108
+ bottom,
109
+ elasticCoef,
110
+ hasDragStart,
111
+ hasDragEnd,
112
+ fireDragStart,
113
+ fireDragEnd,
114
+ onRelease,
115
+ dragX,
116
+ dragY,
117
+ startX,
118
+ startY,
119
+ isDragging
120
+ ]
121
+ );
122
+ const gesture = reactNativeGestureHandler.usePanGesture(config);
118
123
  const animatedStyle = inertia.useTranslateStyle(dragX, dragY);
119
124
  return { gesture, animatedStyle, dragX, dragY, isDragging };
120
125
  }
@@ -157,103 +162,109 @@ function useSwipe(options = {}) {
157
162
  const hasSwipeEnd = onSwipeEnd !== void 0;
158
163
  const fireSwipe = useLatestCallback(onSwipe);
159
164
  const fireSwipeEnd = useLatestCallback(onSwipeEnd);
160
- const gesture = react.useMemo(() => {
161
- const pan = reactNativeGestureHandler.Gesture.Pan().onStart(() => {
162
- "worklet";
163
- isActive.value = true;
164
- }).onUpdate((e) => {
165
- "worklet";
166
- swipeX.value = e.translationX;
167
- swipeY.value = e.translationY;
168
- }).onEnd((e) => {
169
- "worklet";
170
- isActive.value = false;
171
- const direction = pickDirection(
172
- e.translationX,
173
- e.translationY,
174
- e.velocityX,
175
- e.velocityY,
176
- distanceThreshold,
177
- velocityThreshold,
178
- allowLeft,
179
- allowRight,
180
- allowUp,
181
- allowDown
182
- );
183
- if (direction !== null && hasSwipe) {
184
- const isHoriz = direction === "left" || direction === "right";
185
- const distance = isHoriz ? Math.abs(e.translationX) : Math.abs(e.translationY);
186
- const velocity = isHoriz ? Math.abs(e.velocityX) : Math.abs(e.velocityY);
187
- reactNativeReanimated.runOnJS(fireSwipe)(direction, { distance, velocity });
188
- }
189
- let exit = void 0;
190
- if (direction !== null && onCommit) {
191
- exit = onCommit(direction, {
192
- x: swipeX.value,
193
- y: swipeY.value,
194
- velocity: { x: e.velocityX, y: e.velocityY }
195
- });
196
- }
197
- let settle;
198
- if (direction !== null && hasSwipeEnd) {
199
- const dir = direction;
200
- settle = (finished) => {
201
- reactNativeReanimated.runOnJS(fireSwipeEnd)(dir, { finished: finished === true });
202
- };
203
- }
204
- const isHorizontalCommit = direction === "left" || direction === "right";
205
- const settleX = isHorizontalCommit ? settle : void 0;
206
- const settleY = isHorizontalCommit ? void 0 : settle;
207
- const exitX = exit ? exit.x : void 0;
208
- const exitY = exit ? exit.y : void 0;
209
- if (exitX) {
210
- swipeX.value = inertia.buildReleaseAnimation(
211
- exitX,
212
- "to" in exitX ? exitX.to : swipeX.value,
213
- settleX
214
- );
215
- } else {
216
- swipeX.value = inertia.buildReleaseAnimation(
217
- withReleaseVelocity(snapBack, e.velocityX),
218
- 0,
219
- settleX
220
- );
221
- }
222
- if (exitY) {
223
- swipeY.value = inertia.buildReleaseAnimation(
224
- exitY,
225
- "to" in exitY ? exitY.to : swipeY.value,
226
- settleY
227
- );
228
- } else {
229
- swipeY.value = inertia.buildReleaseAnimation(
230
- withReleaseVelocity(snapBack, e.velocityY),
231
- 0,
232
- settleY
165
+ const config = react.useMemo(
166
+ () => ({
167
+ onActivate: () => {
168
+ "worklet";
169
+ isActive.value = true;
170
+ },
171
+ onUpdate: (e) => {
172
+ "worklet";
173
+ swipeX.value = e.translationX;
174
+ swipeY.value = e.translationY;
175
+ },
176
+ onDeactivate: (e) => {
177
+ "worklet";
178
+ isActive.value = false;
179
+ const direction = pickDirection(
180
+ e.translationX,
181
+ e.translationY,
182
+ e.velocityX,
183
+ e.velocityY,
184
+ distanceThreshold,
185
+ velocityThreshold,
186
+ allowLeft,
187
+ allowRight,
188
+ allowUp,
189
+ allowDown
233
190
  );
191
+ if (direction !== null && hasSwipe) {
192
+ const isHoriz = direction === "left" || direction === "right";
193
+ const distance = isHoriz ? Math.abs(e.translationX) : Math.abs(e.translationY);
194
+ const velocity = isHoriz ? Math.abs(e.velocityX) : Math.abs(e.velocityY);
195
+ reactNativeReanimated.runOnJS(fireSwipe)(direction, { distance, velocity });
196
+ }
197
+ let exit = void 0;
198
+ if (direction !== null && onCommit) {
199
+ exit = onCommit(direction, {
200
+ x: swipeX.value,
201
+ y: swipeY.value,
202
+ velocity: { x: e.velocityX, y: e.velocityY }
203
+ });
204
+ }
205
+ let settle;
206
+ if (direction !== null && hasSwipeEnd) {
207
+ const dir = direction;
208
+ settle = (finished) => {
209
+ reactNativeReanimated.runOnJS(fireSwipeEnd)(dir, { finished: finished === true });
210
+ };
211
+ }
212
+ const isHorizontalCommit = direction === "left" || direction === "right";
213
+ const settleX = isHorizontalCommit ? settle : void 0;
214
+ const settleY = isHorizontalCommit ? void 0 : settle;
215
+ const exitX = exit ? exit.x : void 0;
216
+ const exitY = exit ? exit.y : void 0;
217
+ if (exitX) {
218
+ swipeX.value = inertia.buildReleaseAnimation(
219
+ exitX,
220
+ "to" in exitX ? exitX.to : swipeX.value,
221
+ settleX
222
+ );
223
+ } else {
224
+ swipeX.value = inertia.buildReleaseAnimation(
225
+ withReleaseVelocity(snapBack, e.velocityX),
226
+ 0,
227
+ settleX
228
+ );
229
+ }
230
+ if (exitY) {
231
+ swipeY.value = inertia.buildReleaseAnimation(
232
+ exitY,
233
+ "to" in exitY ? exitY.to : swipeY.value,
234
+ settleY
235
+ );
236
+ } else {
237
+ swipeY.value = inertia.buildReleaseAnimation(
238
+ withReleaseVelocity(snapBack, e.velocityY),
239
+ 0,
240
+ settleY
241
+ );
242
+ }
243
+ },
244
+ onFinalize: () => {
245
+ "worklet";
246
+ isActive.value = false;
234
247
  }
235
- }).onFinalize(() => {
236
- "worklet";
237
- isActive.value = false;
238
- });
239
- return pan;
240
- }, [
241
- distanceThreshold,
242
- velocityThreshold,
243
- allowLeft,
244
- allowRight,
245
- allowUp,
246
- allowDown,
247
- snapBack,
248
- hasSwipe,
249
- hasSwipeEnd,
250
- fireSwipe,
251
- fireSwipeEnd,
252
- onCommit,
253
- swipeX,
254
- swipeY,
255
- isActive
256
- ]);
248
+ }),
249
+ [
250
+ distanceThreshold,
251
+ velocityThreshold,
252
+ allowLeft,
253
+ allowRight,
254
+ allowUp,
255
+ allowDown,
256
+ snapBack,
257
+ hasSwipe,
258
+ hasSwipeEnd,
259
+ fireSwipe,
260
+ fireSwipeEnd,
261
+ onCommit,
262
+ swipeX,
263
+ swipeY,
264
+ isActive
265
+ ]
266
+ );
267
+ const gesture = reactNativeGestureHandler.usePanGesture(config);
257
268
  const animatedStyle = inertia.useTranslateStyle(swipeX, swipeY);
258
269
  const reset = react.useCallback(() => {
259
270
  swipeX.value = 0;
@@ -296,42 +307,48 @@ function usePan(options = {}) {
296
307
  const top = constraints?.top;
297
308
  const bottom = constraints?.bottom;
298
309
  const decel = deceleration;
299
- const gesture = react.useMemo(() => {
300
- const pan = reactNativeGestureHandler.Gesture.Pan().onStart(() => {
301
- "worklet";
302
- startX.value = panX.value;
303
- startY.value = panY.value;
304
- isPanning.value = true;
305
- }).onUpdate((e) => {
306
- "worklet";
307
- panX.value = clamp(startX.value + e.translationX, left, right);
308
- panY.value = clamp(startY.value + e.translationY, top, bottom);
309
- }).onEnd((e) => {
310
- "worklet";
311
- isPanning.value = false;
312
- if (disableMomentum) return;
313
- const clampX = boundsTuple(left, right);
314
- const clampY = boundsTuple(top, bottom);
315
- panX.value = reactNativeReanimated.withDecay(decayConfig(e.velocityX, decel, clampX));
316
- panY.value = reactNativeReanimated.withDecay(decayConfig(e.velocityY, decel, clampY));
317
- }).onFinalize(() => {
318
- "worklet";
319
- isPanning.value = false;
320
- });
321
- return pan;
322
- }, [
323
- left,
324
- right,
325
- top,
326
- bottom,
327
- decel,
328
- disableMomentum,
329
- panX,
330
- panY,
331
- startX,
332
- startY,
333
- isPanning
334
- ]);
310
+ const config = react.useMemo(
311
+ () => ({
312
+ onActivate: () => {
313
+ "worklet";
314
+ startX.value = panX.value;
315
+ startY.value = panY.value;
316
+ isPanning.value = true;
317
+ },
318
+ onUpdate: (e) => {
319
+ "worklet";
320
+ panX.value = clamp(startX.value + e.translationX, left, right);
321
+ panY.value = clamp(startY.value + e.translationY, top, bottom);
322
+ },
323
+ onDeactivate: (e) => {
324
+ "worklet";
325
+ isPanning.value = false;
326
+ if (disableMomentum) return;
327
+ const clampX = boundsTuple(left, right);
328
+ const clampY = boundsTuple(top, bottom);
329
+ panX.value = reactNativeReanimated.withDecay(decayConfig(e.velocityX, decel, clampX));
330
+ panY.value = reactNativeReanimated.withDecay(decayConfig(e.velocityY, decel, clampY));
331
+ },
332
+ onFinalize: () => {
333
+ "worklet";
334
+ isPanning.value = false;
335
+ }
336
+ }),
337
+ [
338
+ left,
339
+ right,
340
+ top,
341
+ bottom,
342
+ decel,
343
+ disableMomentum,
344
+ panX,
345
+ panY,
346
+ startX,
347
+ startY,
348
+ isPanning
349
+ ]
350
+ );
351
+ const gesture = reactNativeGestureHandler.usePanGesture(config);
335
352
  const animatedStyle = inertia.useTranslateStyle(panX, panY);
336
353
  return { gesture, animatedStyle, panX, panY, isPanning };
337
354
  }
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { useMemo, useCallback, useRef } from 'react';
2
- import { Gesture } from 'react-native-gesture-handler';
2
+ import { usePanGesture } from 'react-native-gesture-handler';
3
3
  import { useSharedValue, runOnJS, withDecay } from 'react-native-reanimated';
4
- import { applyBounds, buildReleaseAnimation, useTranslateStyle, useNamedTransitions, resolveNamedTransition } from '@rootnative/inertia';
4
+ import { buildReleaseAnimation, applyBounds, useTranslateStyle, useNamedTransitions, resolveNamedTransition } from '@rootnative/inertia';
5
5
 
6
6
  // src/useDrag.ts
7
7
  function useLatestCallback(fn) {
@@ -38,81 +38,86 @@ function useDrag(options = {}) {
38
38
  const hasDragEnd = onDragEnd !== void 0;
39
39
  const fireDragStart = useLatestCallback(onDragStart);
40
40
  const fireDragEnd = useLatestCallback(onDragEnd);
41
- const gesture = useMemo(() => {
42
- const pan = Gesture.Pan().onStart(() => {
43
- "worklet";
44
- startX.value = dragX.value;
45
- startY.value = dragY.value;
46
- isDragging.value = true;
47
- if (hasDragStart) runOnJS(fireDragStart)();
48
- }).onUpdate((e) => {
49
- "worklet";
50
- if (lockX) {
51
- dragX.value = applyBounds(
52
- startX.value + e.translationX,
53
- left,
54
- right,
55
- elasticCoef
56
- );
57
- }
58
- if (lockY) {
59
- dragY.value = applyBounds(
60
- startY.value + e.translationY,
61
- top,
62
- bottom,
63
- elasticCoef
64
- );
65
- }
66
- }).onEnd((e) => {
67
- "worklet";
68
- isDragging.value = false;
69
- const x = dragX.value;
70
- const y = dragY.value;
71
- const vx = e.velocityX;
72
- const vy = e.velocityY;
73
- if (onRelease) {
74
- const result = onRelease({ x, y, velocity: { x: vx, y: vy } });
75
- if (result) {
76
- if (result.x && lockX) {
77
- const toX = "to" in result.x ? result.x.to : x;
78
- dragX.value = buildReleaseAnimation(
79
- result.x,
80
- toX
81
- );
82
- }
83
- if (result.y && lockY) {
84
- const toY = "to" in result.y ? result.y.to : y;
85
- dragY.value = buildReleaseAnimation(
86
- result.y,
87
- toY
88
- );
41
+ const config = useMemo(
42
+ () => ({
43
+ onActivate: () => {
44
+ "worklet";
45
+ startX.value = dragX.value;
46
+ startY.value = dragY.value;
47
+ isDragging.value = true;
48
+ if (hasDragStart) runOnJS(fireDragStart)();
49
+ },
50
+ onUpdate: (e) => {
51
+ "worklet";
52
+ if (lockX) {
53
+ dragX.value = applyBounds(
54
+ startX.value + e.translationX,
55
+ left,
56
+ right,
57
+ elasticCoef
58
+ );
59
+ }
60
+ if (lockY) {
61
+ dragY.value = applyBounds(
62
+ startY.value + e.translationY,
63
+ top,
64
+ bottom,
65
+ elasticCoef
66
+ );
67
+ }
68
+ },
69
+ onDeactivate: (e) => {
70
+ "worklet";
71
+ isDragging.value = false;
72
+ const x = dragX.value;
73
+ const y = dragY.value;
74
+ const vx = e.velocityX;
75
+ const vy = e.velocityY;
76
+ if (onRelease) {
77
+ const result = onRelease({ x, y, velocity: { x: vx, y: vy } });
78
+ if (result) {
79
+ if (result.x && lockX) {
80
+ const toX = "to" in result.x ? result.x.to : x;
81
+ dragX.value = buildReleaseAnimation(
82
+ result.x,
83
+ toX
84
+ );
85
+ }
86
+ if (result.y && lockY) {
87
+ const toY = "to" in result.y ? result.y.to : y;
88
+ dragY.value = buildReleaseAnimation(
89
+ result.y,
90
+ toY
91
+ );
92
+ }
89
93
  }
90
94
  }
95
+ if (hasDragEnd) {
96
+ runOnJS(fireDragEnd)({ x, y, velocity: { x: vx, y: vy } });
97
+ }
91
98
  }
92
- if (hasDragEnd) {
93
- runOnJS(fireDragEnd)({ x, y, velocity: { x: vx, y: vy } });
94
- }
95
- });
96
- return pan;
97
- }, [
98
- lockX,
99
- lockY,
100
- left,
101
- right,
102
- top,
103
- bottom,
104
- elasticCoef,
105
- hasDragStart,
106
- hasDragEnd,
107
- fireDragStart,
108
- fireDragEnd,
109
- onRelease,
110
- dragX,
111
- dragY,
112
- startX,
113
- startY,
114
- isDragging
115
- ]);
99
+ }),
100
+ [
101
+ lockX,
102
+ lockY,
103
+ left,
104
+ right,
105
+ top,
106
+ bottom,
107
+ elasticCoef,
108
+ hasDragStart,
109
+ hasDragEnd,
110
+ fireDragStart,
111
+ fireDragEnd,
112
+ onRelease,
113
+ dragX,
114
+ dragY,
115
+ startX,
116
+ startY,
117
+ isDragging
118
+ ]
119
+ );
120
+ const gesture = usePanGesture(config);
116
121
  const animatedStyle = useTranslateStyle(dragX, dragY);
117
122
  return { gesture, animatedStyle, dragX, dragY, isDragging };
118
123
  }
@@ -155,103 +160,109 @@ function useSwipe(options = {}) {
155
160
  const hasSwipeEnd = onSwipeEnd !== void 0;
156
161
  const fireSwipe = useLatestCallback(onSwipe);
157
162
  const fireSwipeEnd = useLatestCallback(onSwipeEnd);
158
- const gesture = useMemo(() => {
159
- const pan = Gesture.Pan().onStart(() => {
160
- "worklet";
161
- isActive.value = true;
162
- }).onUpdate((e) => {
163
- "worklet";
164
- swipeX.value = e.translationX;
165
- swipeY.value = e.translationY;
166
- }).onEnd((e) => {
167
- "worklet";
168
- isActive.value = false;
169
- const direction = pickDirection(
170
- e.translationX,
171
- e.translationY,
172
- e.velocityX,
173
- e.velocityY,
174
- distanceThreshold,
175
- velocityThreshold,
176
- allowLeft,
177
- allowRight,
178
- allowUp,
179
- allowDown
180
- );
181
- if (direction !== null && hasSwipe) {
182
- const isHoriz = direction === "left" || direction === "right";
183
- const distance = isHoriz ? Math.abs(e.translationX) : Math.abs(e.translationY);
184
- const velocity = isHoriz ? Math.abs(e.velocityX) : Math.abs(e.velocityY);
185
- runOnJS(fireSwipe)(direction, { distance, velocity });
186
- }
187
- let exit = void 0;
188
- if (direction !== null && onCommit) {
189
- exit = onCommit(direction, {
190
- x: swipeX.value,
191
- y: swipeY.value,
192
- velocity: { x: e.velocityX, y: e.velocityY }
193
- });
194
- }
195
- let settle;
196
- if (direction !== null && hasSwipeEnd) {
197
- const dir = direction;
198
- settle = (finished) => {
199
- runOnJS(fireSwipeEnd)(dir, { finished: finished === true });
200
- };
201
- }
202
- const isHorizontalCommit = direction === "left" || direction === "right";
203
- const settleX = isHorizontalCommit ? settle : void 0;
204
- const settleY = isHorizontalCommit ? void 0 : settle;
205
- const exitX = exit ? exit.x : void 0;
206
- const exitY = exit ? exit.y : void 0;
207
- if (exitX) {
208
- swipeX.value = buildReleaseAnimation(
209
- exitX,
210
- "to" in exitX ? exitX.to : swipeX.value,
211
- settleX
212
- );
213
- } else {
214
- swipeX.value = buildReleaseAnimation(
215
- withReleaseVelocity(snapBack, e.velocityX),
216
- 0,
217
- settleX
218
- );
219
- }
220
- if (exitY) {
221
- swipeY.value = buildReleaseAnimation(
222
- exitY,
223
- "to" in exitY ? exitY.to : swipeY.value,
224
- settleY
225
- );
226
- } else {
227
- swipeY.value = buildReleaseAnimation(
228
- withReleaseVelocity(snapBack, e.velocityY),
229
- 0,
230
- settleY
163
+ const config = useMemo(
164
+ () => ({
165
+ onActivate: () => {
166
+ "worklet";
167
+ isActive.value = true;
168
+ },
169
+ onUpdate: (e) => {
170
+ "worklet";
171
+ swipeX.value = e.translationX;
172
+ swipeY.value = e.translationY;
173
+ },
174
+ onDeactivate: (e) => {
175
+ "worklet";
176
+ isActive.value = false;
177
+ const direction = pickDirection(
178
+ e.translationX,
179
+ e.translationY,
180
+ e.velocityX,
181
+ e.velocityY,
182
+ distanceThreshold,
183
+ velocityThreshold,
184
+ allowLeft,
185
+ allowRight,
186
+ allowUp,
187
+ allowDown
231
188
  );
189
+ if (direction !== null && hasSwipe) {
190
+ const isHoriz = direction === "left" || direction === "right";
191
+ const distance = isHoriz ? Math.abs(e.translationX) : Math.abs(e.translationY);
192
+ const velocity = isHoriz ? Math.abs(e.velocityX) : Math.abs(e.velocityY);
193
+ runOnJS(fireSwipe)(direction, { distance, velocity });
194
+ }
195
+ let exit = void 0;
196
+ if (direction !== null && onCommit) {
197
+ exit = onCommit(direction, {
198
+ x: swipeX.value,
199
+ y: swipeY.value,
200
+ velocity: { x: e.velocityX, y: e.velocityY }
201
+ });
202
+ }
203
+ let settle;
204
+ if (direction !== null && hasSwipeEnd) {
205
+ const dir = direction;
206
+ settle = (finished) => {
207
+ runOnJS(fireSwipeEnd)(dir, { finished: finished === true });
208
+ };
209
+ }
210
+ const isHorizontalCommit = direction === "left" || direction === "right";
211
+ const settleX = isHorizontalCommit ? settle : void 0;
212
+ const settleY = isHorizontalCommit ? void 0 : settle;
213
+ const exitX = exit ? exit.x : void 0;
214
+ const exitY = exit ? exit.y : void 0;
215
+ if (exitX) {
216
+ swipeX.value = buildReleaseAnimation(
217
+ exitX,
218
+ "to" in exitX ? exitX.to : swipeX.value,
219
+ settleX
220
+ );
221
+ } else {
222
+ swipeX.value = buildReleaseAnimation(
223
+ withReleaseVelocity(snapBack, e.velocityX),
224
+ 0,
225
+ settleX
226
+ );
227
+ }
228
+ if (exitY) {
229
+ swipeY.value = buildReleaseAnimation(
230
+ exitY,
231
+ "to" in exitY ? exitY.to : swipeY.value,
232
+ settleY
233
+ );
234
+ } else {
235
+ swipeY.value = buildReleaseAnimation(
236
+ withReleaseVelocity(snapBack, e.velocityY),
237
+ 0,
238
+ settleY
239
+ );
240
+ }
241
+ },
242
+ onFinalize: () => {
243
+ "worklet";
244
+ isActive.value = false;
232
245
  }
233
- }).onFinalize(() => {
234
- "worklet";
235
- isActive.value = false;
236
- });
237
- return pan;
238
- }, [
239
- distanceThreshold,
240
- velocityThreshold,
241
- allowLeft,
242
- allowRight,
243
- allowUp,
244
- allowDown,
245
- snapBack,
246
- hasSwipe,
247
- hasSwipeEnd,
248
- fireSwipe,
249
- fireSwipeEnd,
250
- onCommit,
251
- swipeX,
252
- swipeY,
253
- isActive
254
- ]);
246
+ }),
247
+ [
248
+ distanceThreshold,
249
+ velocityThreshold,
250
+ allowLeft,
251
+ allowRight,
252
+ allowUp,
253
+ allowDown,
254
+ snapBack,
255
+ hasSwipe,
256
+ hasSwipeEnd,
257
+ fireSwipe,
258
+ fireSwipeEnd,
259
+ onCommit,
260
+ swipeX,
261
+ swipeY,
262
+ isActive
263
+ ]
264
+ );
265
+ const gesture = usePanGesture(config);
255
266
  const animatedStyle = useTranslateStyle(swipeX, swipeY);
256
267
  const reset = useCallback(() => {
257
268
  swipeX.value = 0;
@@ -294,42 +305,48 @@ function usePan(options = {}) {
294
305
  const top = constraints?.top;
295
306
  const bottom = constraints?.bottom;
296
307
  const decel = deceleration;
297
- const gesture = useMemo(() => {
298
- const pan = Gesture.Pan().onStart(() => {
299
- "worklet";
300
- startX.value = panX.value;
301
- startY.value = panY.value;
302
- isPanning.value = true;
303
- }).onUpdate((e) => {
304
- "worklet";
305
- panX.value = clamp(startX.value + e.translationX, left, right);
306
- panY.value = clamp(startY.value + e.translationY, top, bottom);
307
- }).onEnd((e) => {
308
- "worklet";
309
- isPanning.value = false;
310
- if (disableMomentum) return;
311
- const clampX = boundsTuple(left, right);
312
- const clampY = boundsTuple(top, bottom);
313
- panX.value = withDecay(decayConfig(e.velocityX, decel, clampX));
314
- panY.value = withDecay(decayConfig(e.velocityY, decel, clampY));
315
- }).onFinalize(() => {
316
- "worklet";
317
- isPanning.value = false;
318
- });
319
- return pan;
320
- }, [
321
- left,
322
- right,
323
- top,
324
- bottom,
325
- decel,
326
- disableMomentum,
327
- panX,
328
- panY,
329
- startX,
330
- startY,
331
- isPanning
332
- ]);
308
+ const config = useMemo(
309
+ () => ({
310
+ onActivate: () => {
311
+ "worklet";
312
+ startX.value = panX.value;
313
+ startY.value = panY.value;
314
+ isPanning.value = true;
315
+ },
316
+ onUpdate: (e) => {
317
+ "worklet";
318
+ panX.value = clamp(startX.value + e.translationX, left, right);
319
+ panY.value = clamp(startY.value + e.translationY, top, bottom);
320
+ },
321
+ onDeactivate: (e) => {
322
+ "worklet";
323
+ isPanning.value = false;
324
+ if (disableMomentum) return;
325
+ const clampX = boundsTuple(left, right);
326
+ const clampY = boundsTuple(top, bottom);
327
+ panX.value = withDecay(decayConfig(e.velocityX, decel, clampX));
328
+ panY.value = withDecay(decayConfig(e.velocityY, decel, clampY));
329
+ },
330
+ onFinalize: () => {
331
+ "worklet";
332
+ isPanning.value = false;
333
+ }
334
+ }),
335
+ [
336
+ left,
337
+ right,
338
+ top,
339
+ bottom,
340
+ decel,
341
+ disableMomentum,
342
+ panX,
343
+ panY,
344
+ startX,
345
+ startY,
346
+ isPanning
347
+ ]
348
+ );
349
+ const gesture = usePanGesture(config);
333
350
  const animatedStyle = useTranslateStyle(panX, panY);
334
351
  return { gesture, animatedStyle, panX, panY, isPanning };
335
352
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootnative/inertia-gestures",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "Gesture-handler-driven drag / pan / swipe adapters for @rootnative/inertia.",
5
5
  "license": "MIT",
6
6
  "author": "RootNative",
@@ -50,28 +50,28 @@
50
50
  "!**/*.test.*"
51
51
  ],
52
52
  "peerDependencies": {
53
- "@rootnative/inertia": ">=0.0.19 <0.1.0",
54
- "react": ">=19.2.3 <20.0.0",
55
- "react-native": ">=0.83.0 <0.87.0",
56
- "react-native-gesture-handler": ">=2.30.0 <3.0.0",
57
- "react-native-reanimated": ">=4.5.0 <4.6.0"
53
+ "@rootnative/inertia": ">=0.0.21 <0.1.0",
54
+ "react": ">=19.3.0 <20.0.0",
55
+ "react-native": ">=0.88.0 <0.89.0",
56
+ "react-native-gesture-handler": ">=3.3.0 <4.0.0",
57
+ "react-native-reanimated": ">=4.7.0 <4.8.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@react-native/babel-preset": "^0.86.3",
61
- "@react-native/jest-preset": "^0.86.3",
60
+ "@react-native/babel-preset": "^0.88.0",
61
+ "@react-native/jest-preset": "^0.88.0",
62
62
  "@testing-library/react-native": "^13.3.3",
63
63
  "@types/jest": "^29.5.14",
64
- "@types/react": "^19.2.0",
64
+ "@types/react": "^19.3.0",
65
65
  "jest": "^29.7.0",
66
- "react": "19.2.3",
67
- "react-native": "0.86.3",
68
- "react-native-gesture-handler": "~2.32.0",
69
- "react-native-reanimated": "4.5.1",
70
- "react-native-worklets": "0.10.1",
71
- "react-test-renderer": "19.2.3",
66
+ "react": "19.3.0",
67
+ "react-native": "0.88.0",
68
+ "react-native-gesture-handler": "~3.3.0",
69
+ "react-native-reanimated": "4.7.1",
70
+ "react-native-worklets": "0.13.0",
71
+ "react-test-renderer": "19.3.0",
72
72
  "tsup": "^8.3.5",
73
73
  "typescript": "^5.7.3",
74
- "@rootnative/inertia": "0.0.19"
74
+ "@rootnative/inertia": "0.0.21"
75
75
  },
76
76
  "publishConfig": {
77
77
  "access": "public"
package/src/useDrag.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { useMemo } from 'react'
2
- import { Gesture, type PanGesture } from 'react-native-gesture-handler'
2
+ import {
3
+ usePanGesture,
4
+ type PanGesture,
5
+ type PanGestureConfig,
6
+ } from 'react-native-gesture-handler'
3
7
  import {
4
8
  runOnJS,
5
9
  useSharedValue,
@@ -91,16 +95,16 @@ export function useDrag(options: DragOptions = {}): UseDragResult {
91
95
  const fireDragStart = useLatestCallback(onDragStart)
92
96
  const fireDragEnd = useLatestCallback(onDragEnd)
93
97
 
94
- const gesture = useMemo(() => {
95
- const pan = Gesture.Pan()
96
- .onStart(() => {
98
+ const config = useMemo<PanGestureConfig>(
99
+ () => ({
100
+ onActivate: () => {
97
101
  'worklet'
98
102
  startX.value = dragX.value
99
103
  startY.value = dragY.value
100
104
  isDragging.value = true
101
105
  if (hasDragStart) runOnJS(fireDragStart)()
102
- })
103
- .onUpdate((e) => {
106
+ },
107
+ onUpdate: (e) => {
104
108
  'worklet'
105
109
  if (lockX) {
106
110
  dragX.value = applyBounds(
@@ -118,8 +122,8 @@ export function useDrag(options: DragOptions = {}): UseDragResult {
118
122
  elasticCoef,
119
123
  )
120
124
  }
121
- })
122
- .onEnd((e) => {
125
+ },
126
+ onDeactivate: (e) => {
123
127
  'worklet'
124
128
  isDragging.value = false
125
129
  const x = dragX.value
@@ -151,27 +155,30 @@ export function useDrag(options: DragOptions = {}): UseDragResult {
151
155
  if (hasDragEnd) {
152
156
  runOnJS(fireDragEnd)({ x, y, velocity: { x: vx, y: vy } })
153
157
  }
154
- })
155
- return pan
156
- }, [
157
- lockX,
158
- lockY,
159
- left,
160
- right,
161
- top,
162
- bottom,
163
- elasticCoef,
164
- hasDragStart,
165
- hasDragEnd,
166
- fireDragStart,
167
- fireDragEnd,
168
- onRelease,
169
- dragX,
170
- dragY,
171
- startX,
172
- startY,
173
- isDragging,
174
- ])
158
+ },
159
+ }),
160
+ [
161
+ lockX,
162
+ lockY,
163
+ left,
164
+ right,
165
+ top,
166
+ bottom,
167
+ elasticCoef,
168
+ hasDragStart,
169
+ hasDragEnd,
170
+ fireDragStart,
171
+ fireDragEnd,
172
+ onRelease,
173
+ dragX,
174
+ dragY,
175
+ startX,
176
+ startY,
177
+ isDragging,
178
+ ],
179
+ )
180
+
181
+ const gesture = usePanGesture(config)
175
182
 
176
183
  const animatedStyle = useTranslateStyle(dragX, dragY)
177
184
 
package/src/usePan.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { useMemo } from 'react'
2
- import { Gesture, type PanGesture } from 'react-native-gesture-handler'
2
+ import {
3
+ usePanGesture,
4
+ type PanGesture,
5
+ type PanGestureConfig,
6
+ } from 'react-native-gesture-handler'
3
7
  import {
4
8
  useSharedValue,
5
9
  withDecay,
@@ -73,20 +77,20 @@ export function usePan(options: PanOptions = {}): UsePanResult {
73
77
  const bottom = constraints?.bottom
74
78
  const decel = deceleration
75
79
 
76
- const gesture = useMemo(() => {
77
- const pan = Gesture.Pan()
78
- .onStart(() => {
80
+ const config = useMemo<PanGestureConfig>(
81
+ () => ({
82
+ onActivate: () => {
79
83
  'worklet'
80
84
  startX.value = panX.value
81
85
  startY.value = panY.value
82
86
  isPanning.value = true
83
- })
84
- .onUpdate((e) => {
87
+ },
88
+ onUpdate: (e) => {
85
89
  'worklet'
86
90
  panX.value = clamp(startX.value + e.translationX, left, right)
87
91
  panY.value = clamp(startY.value + e.translationY, top, bottom)
88
- })
89
- .onEnd((e) => {
92
+ },
93
+ onDeactivate: (e) => {
90
94
  'worklet'
91
95
  isPanning.value = false
92
96
  if (disableMomentum) return
@@ -94,25 +98,28 @@ export function usePan(options: PanOptions = {}): UsePanResult {
94
98
  const clampY = boundsTuple(top, bottom)
95
99
  panX.value = withDecay(decayConfig(e.velocityX, decel, clampX))
96
100
  panY.value = withDecay(decayConfig(e.velocityY, decel, clampY))
97
- })
98
- .onFinalize(() => {
101
+ },
102
+ onFinalize: () => {
99
103
  'worklet'
100
104
  isPanning.value = false
101
- })
102
- return pan
103
- }, [
104
- left,
105
- right,
106
- top,
107
- bottom,
108
- decel,
109
- disableMomentum,
110
- panX,
111
- panY,
112
- startX,
113
- startY,
114
- isPanning,
115
- ])
105
+ },
106
+ }),
107
+ [
108
+ left,
109
+ right,
110
+ top,
111
+ bottom,
112
+ decel,
113
+ disableMomentum,
114
+ panX,
115
+ panY,
116
+ startX,
117
+ startY,
118
+ isPanning,
119
+ ],
120
+ )
121
+
122
+ const gesture = usePanGesture(config)
116
123
 
117
124
  const animatedStyle = useTranslateStyle(panX, panY)
118
125
 
package/src/useSwipe.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { useCallback, useMemo } from 'react'
2
- import { Gesture, type PanGesture } from 'react-native-gesture-handler'
2
+ import {
3
+ usePanGesture,
4
+ type PanGesture,
5
+ type PanGestureConfig,
6
+ } from 'react-native-gesture-handler'
3
7
  import {
4
8
  runOnJS,
5
9
  useSharedValue,
@@ -230,18 +234,18 @@ export function useSwipe(options: SwipeOptions = {}): UseSwipeResult {
230
234
  const fireSwipe = useLatestCallback(onSwipe)
231
235
  const fireSwipeEnd = useLatestCallback(onSwipeEnd)
232
236
 
233
- const gesture = useMemo(() => {
234
- const pan = Gesture.Pan()
235
- .onStart(() => {
237
+ const config = useMemo<PanGestureConfig>(
238
+ () => ({
239
+ onActivate: () => {
236
240
  'worklet'
237
241
  isActive.value = true
238
- })
239
- .onUpdate((e) => {
242
+ },
243
+ onUpdate: (e) => {
240
244
  'worklet'
241
245
  swipeX.value = e.translationX
242
246
  swipeY.value = e.translationY
243
- })
244
- .onEnd((e) => {
247
+ },
248
+ onDeactivate: (e) => {
245
249
  'worklet'
246
250
  isActive.value = false
247
251
  const direction = pickDirection(
@@ -322,29 +326,32 @@ export function useSwipe(options: SwipeOptions = {}): UseSwipeResult {
322
326
  settleY,
323
327
  ) as unknown as number
324
328
  }
325
- })
326
- .onFinalize(() => {
329
+ },
330
+ onFinalize: () => {
327
331
  'worklet'
328
332
  isActive.value = false
329
- })
330
- return pan
331
- }, [
332
- distanceThreshold,
333
- velocityThreshold,
334
- allowLeft,
335
- allowRight,
336
- allowUp,
337
- allowDown,
338
- snapBack,
339
- hasSwipe,
340
- hasSwipeEnd,
341
- fireSwipe,
342
- fireSwipeEnd,
343
- onCommit,
344
- swipeX,
345
- swipeY,
346
- isActive,
347
- ])
333
+ },
334
+ }),
335
+ [
336
+ distanceThreshold,
337
+ velocityThreshold,
338
+ allowLeft,
339
+ allowRight,
340
+ allowUp,
341
+ allowDown,
342
+ snapBack,
343
+ hasSwipe,
344
+ hasSwipeEnd,
345
+ fireSwipe,
346
+ fireSwipeEnd,
347
+ onCommit,
348
+ swipeX,
349
+ swipeY,
350
+ isActive,
351
+ ],
352
+ )
353
+
354
+ const gesture = usePanGesture(config)
348
355
 
349
356
  const animatedStyle = useTranslateStyle(swipeX, swipeY)
350
357