@rootnative/inertia-gestures 0.0.23 → 0.0.24

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,16 @@ This package ships in lockstep with `@rootnative/inertia` — version numbers tr
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.0.24] - 2026-10-31
10
+
11
+ ### Fixed
12
+
13
+ - **`useDrag` springs an `elastic` overshoot back inside its `constraints` on release.** With `elastic` above 0, a drag past a bound stretched the element past it, and the element stayed there after release until the next drag. The docs promised a Framer Motion feel, and that feel includes the return. Now an axis that lands past `constraints` and gets no transition from `onRelease` springs back to the nearest bound, with the default spring and the release velocity. A transition that `onRelease` returns for an axis still replaces the spring back on that axis, and an axis that lands inside the bounds stays where it landed, as before. `useTouchDrag` in `@rootnative/inertia` gets the same fix.
14
+
15
+ ### Changed
16
+
17
+ - The `@rootnative/inertia` peer range moves to `>=0.0.24 <0.1.0`, in lockstep.
18
+
9
19
  ## [0.0.23] - 2026-10-31
10
20
 
11
21
  **Lockstep version bump** alongside `@rootnative/inertia@0.0.23` (the `useHideOnScroll` hook, a 0↔1 shared value from the direction of a scroll). No runtime changes in this adapter; the `@rootnative/inertia` peer range moves to `>=0.0.23 <0.1.0`.
@@ -152,7 +162,8 @@ Initial alpha publish alongside `@rootnative/inertia@0.0.0-alpha.0`. Optional ad
152
162
  - `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.
153
163
  - `useSwipe`, `usePan` hooks composable with any `Motion.*` primitive via `<GestureDetector>`.
154
164
 
155
- [unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.23...HEAD
165
+ [unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.24...HEAD
166
+ [0.0.24]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.24
156
167
  [0.0.23]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.23
157
168
  [0.0.22]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.22
158
169
  [0.0.21]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.21
package/dist/index.d.mts CHANGED
@@ -47,7 +47,9 @@ type ReleaseTransition = (SpringTransition & {
47
47
  });
48
48
  /**
49
49
  * Per-axis release transitions returned by `onRelease`. Omit an axis to leave
50
- * its SV where it landed (no release animation on that axis).
50
+ * its SV where it landed (no release animation on that axis). An omitted axis
51
+ * that landed past `constraints` (an `elastic` overshoot) springs back to the
52
+ * nearest bound.
51
53
  *
52
54
  * `useSwipe`'s `onCommit` returns the same shape with one difference in the
53
55
  * omit semantics: an omitted axis snaps back to zero (the hook's resting
@@ -83,7 +85,9 @@ interface DragOptions {
83
85
  /**
84
86
  * Rubber-band coefficient applied to overshoot past `constraints`. `0`
85
87
  * (default) hard-clamps; `1` is fully elastic (no resistance). Typical
86
- * Framer-Motion-style feel sits around `0.2`–`0.4`.
88
+ * Framer-Motion-style feel sits around `0.2`–`0.4`. On release, an
89
+ * overshoot springs back to the nearest bound with the release velocity,
90
+ * unless `onRelease` returns a transition for that axis.
87
91
  */
88
92
  elastic?: number;
89
93
  /**
@@ -99,7 +103,8 @@ interface DragOptions {
99
103
  * UI-thread callback fired on release. Return per-axis release transitions
100
104
  * to animate the SVs to a settled position via Inertia's transition
101
105
  * resolver — spring snap-to-tick, decay with bounds, timing settle, etc.
102
- * Omit an axis (or return nothing) to leave that SV where it landed.
106
+ * Omit an axis (or return nothing) to leave that SV where it landed, or,
107
+ * after an `elastic` overshoot, to spring it back inside `constraints`.
103
108
  *
104
109
  * This callback runs as a worklet so the release velocity stays on the UI
105
110
  * thread. Author it with the `'worklet'` directive at the top of the body.
package/dist/index.d.ts CHANGED
@@ -47,7 +47,9 @@ type ReleaseTransition = (SpringTransition & {
47
47
  });
48
48
  /**
49
49
  * Per-axis release transitions returned by `onRelease`. Omit an axis to leave
50
- * its SV where it landed (no release animation on that axis).
50
+ * its SV where it landed (no release animation on that axis). An omitted axis
51
+ * that landed past `constraints` (an `elastic` overshoot) springs back to the
52
+ * nearest bound.
51
53
  *
52
54
  * `useSwipe`'s `onCommit` returns the same shape with one difference in the
53
55
  * omit semantics: an omitted axis snaps back to zero (the hook's resting
@@ -83,7 +85,9 @@ interface DragOptions {
83
85
  /**
84
86
  * Rubber-band coefficient applied to overshoot past `constraints`. `0`
85
87
  * (default) hard-clamps; `1` is fully elastic (no resistance). Typical
86
- * Framer-Motion-style feel sits around `0.2`–`0.4`.
88
+ * Framer-Motion-style feel sits around `0.2`–`0.4`. On release, an
89
+ * overshoot springs back to the nearest bound with the release velocity,
90
+ * unless `onRelease` returns a transition for that axis.
87
91
  */
88
92
  elastic?: number;
89
93
  /**
@@ -99,7 +103,8 @@ interface DragOptions {
99
103
  * UI-thread callback fired on release. Return per-axis release transitions
100
104
  * to animate the SVs to a settled position via Inertia's transition
101
105
  * resolver — spring snap-to-tick, decay with bounds, timing settle, etc.
102
- * Omit an axis (or return nothing) to leave that SV where it landed.
106
+ * Omit an axis (or return nothing) to leave that SV where it landed, or,
107
+ * after an `elastic` overshoot, to spring it back inside `constraints`.
103
108
  *
104
109
  * This callback runs as a worklet so the release velocity stays on the UI
105
110
  * thread. Author it with the `'worklet'` directive at the top of the body.
package/dist/index.js CHANGED
@@ -75,6 +75,8 @@ function useDrag(options = {}) {
75
75
  const y = dragY.value;
76
76
  const vx = e.velocityX;
77
77
  const vy = e.velocityY;
78
+ let releasedX = false;
79
+ let releasedY = false;
78
80
  if (onRelease) {
79
81
  const result = onRelease({ x, y, velocity: { x: vx, y: vy } });
80
82
  if (result) {
@@ -84,6 +86,7 @@ function useDrag(options = {}) {
84
86
  result.x,
85
87
  toX
86
88
  );
89
+ releasedX = true;
87
90
  }
88
91
  if (result.y && lockY) {
89
92
  const toY = "to" in result.y ? result.y.to : y;
@@ -91,9 +94,28 @@ function useDrag(options = {}) {
91
94
  result.y,
92
95
  toY
93
96
  );
97
+ releasedY = true;
94
98
  }
95
99
  }
96
100
  }
101
+ if (lockX && !releasedX) {
102
+ const restX = inertia.applyBounds(x, left, right, 0);
103
+ if (restX !== x) {
104
+ dragX.value = inertia.buildReleaseAnimation(
105
+ { type: "spring", velocity: vx },
106
+ restX
107
+ );
108
+ }
109
+ }
110
+ if (lockY && !releasedY) {
111
+ const restY = inertia.applyBounds(y, top, bottom, 0);
112
+ if (restY !== y) {
113
+ dragY.value = inertia.buildReleaseAnimation(
114
+ { type: "spring", velocity: vy },
115
+ restY
116
+ );
117
+ }
118
+ }
97
119
  if (hasDragEnd) {
98
120
  reactNativeReanimated.runOnJS(fireDragEnd)({ x, y, velocity: { x: vx, y: vy } });
99
121
  }
package/dist/index.mjs CHANGED
@@ -73,6 +73,8 @@ function useDrag(options = {}) {
73
73
  const y = dragY.value;
74
74
  const vx = e.velocityX;
75
75
  const vy = e.velocityY;
76
+ let releasedX = false;
77
+ let releasedY = false;
76
78
  if (onRelease) {
77
79
  const result = onRelease({ x, y, velocity: { x: vx, y: vy } });
78
80
  if (result) {
@@ -82,6 +84,7 @@ function useDrag(options = {}) {
82
84
  result.x,
83
85
  toX
84
86
  );
87
+ releasedX = true;
85
88
  }
86
89
  if (result.y && lockY) {
87
90
  const toY = "to" in result.y ? result.y.to : y;
@@ -89,9 +92,28 @@ function useDrag(options = {}) {
89
92
  result.y,
90
93
  toY
91
94
  );
95
+ releasedY = true;
92
96
  }
93
97
  }
94
98
  }
99
+ if (lockX && !releasedX) {
100
+ const restX = applyBounds(x, left, right, 0);
101
+ if (restX !== x) {
102
+ dragX.value = buildReleaseAnimation(
103
+ { type: "spring", velocity: vx },
104
+ restX
105
+ );
106
+ }
107
+ }
108
+ if (lockY && !releasedY) {
109
+ const restY = applyBounds(y, top, bottom, 0);
110
+ if (restY !== y) {
111
+ dragY.value = buildReleaseAnimation(
112
+ { type: "spring", velocity: vy },
113
+ restY
114
+ );
115
+ }
116
+ }
95
117
  if (hasDragEnd) {
96
118
  runOnJS(fireDragEnd)({ x, y, velocity: { x: vx, y: vy } });
97
119
  }
package/llms.txt CHANGED
@@ -45,20 +45,20 @@ function DraggableBox() {
45
45
  }
46
46
  ```
47
47
 
48
- | Option | Type | Default | Notes |
49
- | ------------- | ---------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------- |
50
- | `axis` | `'x' \| 'y' \| 'both'` | `'both'` | Lock the gesture to one axis. |
51
- | `constraints` | `{ left?, right?, top?, bottom? }` | none | Bounds in px from the resting position. Each side independently optional. |
52
- | `elastic` | `number` (0–1) | `0` | Rubber-band coefficient past `constraints`. `0` hard-clamps; `0.4` is a typical Framer-Motion feel. |
53
- | `onDragStart` | `() => void` | none | Fires on JS thread when drag begins. |
54
- | `onDragEnd` | `(info: { x, y, velocity: { x, y } }) => void` | none | Fires on JS thread when drag ends. |
55
- | `onRelease` | `(info) => ReleaseResult \| void` (worklet) | none | UI-thread release transition — see below. |
48
+ | Option | Type | Default | Notes |
49
+ | ------------- | ---------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
50
+ | `axis` | `'x' \| 'y' \| 'both'` | `'both'` | Lock the gesture to one axis. |
51
+ | `constraints` | `{ left?, right?, top?, bottom? }` | none | Bounds in px from the resting position. Each side independently optional. |
52
+ | `elastic` | `number` (0–1) | `0` | Rubber-band coefficient past `constraints`. `0` hard-clamps; `0.4` is a typical Framer-Motion feel. An overshoot springs back to the bound on release. |
53
+ | `onDragStart` | `() => void` | none | Fires on JS thread when drag begins. |
54
+ | `onDragEnd` | `(info: { x, y, velocity: { x, y } }) => void` | none | Fires on JS thread when drag ends. |
55
+ | `onRelease` | `(info) => ReleaseResult \| void` (worklet) | none | UI-thread release transition — see below. |
56
56
 
57
57
  Returns `{ gesture, animatedStyle, dragX, dragY, isDragging }`. The shared values are exposed for power use cases (deriving secondary effects from drag position).
58
58
 
59
59
  ### Release transitions (`onRelease`)
60
60
 
61
- By default `useDrag` leaves the shared values exactly where the finger lifted. Pass `onRelease` to animate them to a settled position via Inertia's transition vocabulary — spring snap-to-tick, decay with bounds, timing settle. The callback runs as a **worklet** so the release velocity stays on the UI thread (no JS round-trip):
61
+ By default `useDrag` leaves the shared values where the finger lifted. The one exception is an `elastic` overshoot: an axis that lifted past `constraints` springs back to the nearest bound with the default spring and the release velocity. Pass `onRelease` to animate them to a settled position via Inertia's transition vocabulary — spring snap-to-tick, decay with bounds, timing settle. The callback runs as a **worklet** so the release velocity stays on the UI thread (no JS round-trip):
62
62
 
63
63
  ```tsx
64
64
  const drag = useDrag({
@@ -75,7 +75,7 @@ const drag = useDrag({
75
75
  })
76
76
  ```
77
77
 
78
- Return per-axis release transitions in a `ReleaseResult` shape — omit an axis (or return nothing) to leave that SV where it landed. The transition shapes mirror the core `transition` prop:
78
+ Return per-axis release transitions in a `ReleaseResult` shape — omit an axis (or return nothing) to leave that SV where it landed, or, after an `elastic` overshoot, to spring it back inside `constraints`. A transition you return for an axis replaces the spring back on that axis. The transition shapes mirror the core `transition` prop:
79
79
 
80
80
  ```ts
81
81
  type ReleaseTransition =
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootnative/inertia-gestures",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "Gesture-handler-driven drag / pan / swipe adapters for @rootnative/inertia.",
5
5
  "license": "MIT",
6
6
  "author": "RootNative",
@@ -50,7 +50,7 @@
50
50
  "!**/*.test.*"
51
51
  ],
52
52
  "peerDependencies": {
53
- "@rootnative/inertia": ">=0.0.23 <0.1.0",
53
+ "@rootnative/inertia": ">=0.0.24 <0.1.0",
54
54
  "react": ">=19.3.0 <20.0.0",
55
55
  "react-native": ">=0.88.0 <0.89.0",
56
56
  "react-native-gesture-handler": ">=3.3.0 <4.0.0",
@@ -71,7 +71,7 @@
71
71
  "react-test-renderer": "19.3.0",
72
72
  "tsup": "^8.3.5",
73
73
  "typescript": "^5.7.3",
74
- "@rootnative/inertia": "0.0.23"
74
+ "@rootnative/inertia": "0.0.24"
75
75
  },
76
76
  "publishConfig": {
77
77
  "access": "public"
package/src/types.ts CHANGED
@@ -48,7 +48,9 @@ export type ReleaseTransition =
48
48
 
49
49
  /**
50
50
  * Per-axis release transitions returned by `onRelease`. Omit an axis to leave
51
- * its SV where it landed (no release animation on that axis).
51
+ * its SV where it landed (no release animation on that axis). An omitted axis
52
+ * that landed past `constraints` (an `elastic` overshoot) springs back to the
53
+ * nearest bound.
52
54
  *
53
55
  * `useSwipe`'s `onCommit` returns the same shape with one difference in the
54
56
  * omit semantics: an omitted axis snaps back to zero (the hook's resting
@@ -89,7 +91,9 @@ export interface DragOptions {
89
91
  /**
90
92
  * Rubber-band coefficient applied to overshoot past `constraints`. `0`
91
93
  * (default) hard-clamps; `1` is fully elastic (no resistance). Typical
92
- * Framer-Motion-style feel sits around `0.2`–`0.4`.
94
+ * Framer-Motion-style feel sits around `0.2`–`0.4`. On release, an
95
+ * overshoot springs back to the nearest bound with the release velocity,
96
+ * unless `onRelease` returns a transition for that axis.
93
97
  */
94
98
  elastic?: number
95
99
  /**
@@ -105,7 +109,8 @@ export interface DragOptions {
105
109
  * UI-thread callback fired on release. Return per-axis release transitions
106
110
  * to animate the SVs to a settled position via Inertia's transition
107
111
  * resolver — spring snap-to-tick, decay with bounds, timing settle, etc.
108
- * Omit an axis (or return nothing) to leave that SV where it landed.
112
+ * Omit an axis (or return nothing) to leave that SV where it landed, or,
113
+ * after an `elastic` overshoot, to spring it back inside `constraints`.
109
114
  *
110
115
  * This callback runs as a worklet so the release velocity stays on the UI
111
116
  * thread. Author it with the `'worklet'` directive at the top of the body.
package/src/useDrag.ts CHANGED
@@ -130,6 +130,8 @@ export function useDrag(options: DragOptions = {}): UseDragResult {
130
130
  const y = dragY.value
131
131
  const vx = e.velocityX
132
132
  const vy = e.velocityY
133
+ let releasedX = false
134
+ let releasedY = false
133
135
  if (onRelease) {
134
136
  const result = onRelease({ x, y, velocity: { x: vx, y: vy } })
135
137
  if (result) {
@@ -142,6 +144,7 @@ export function useDrag(options: DragOptions = {}): UseDragResult {
142
144
  result.x,
143
145
  toX,
144
146
  ) as unknown as number
147
+ releasedX = true
145
148
  }
146
149
  if (result.y && lockY) {
147
150
  const toY = 'to' in result.y ? result.y.to : y
@@ -149,9 +152,31 @@ export function useDrag(options: DragOptions = {}): UseDragResult {
149
152
  result.y,
150
153
  toY,
151
154
  ) as unknown as number
155
+ releasedY = true
152
156
  }
153
157
  }
154
158
  }
159
+ // An elastic overshoot that gets no release transition springs back
160
+ // to the nearest bound with the release velocity. Without this it
161
+ // stays stretched past `constraints` until the next drag.
162
+ if (lockX && !releasedX) {
163
+ const restX = applyBounds(x, left, right, 0)
164
+ if (restX !== x) {
165
+ dragX.value = buildReleaseAnimation(
166
+ { type: 'spring', velocity: vx },
167
+ restX,
168
+ ) as unknown as number
169
+ }
170
+ }
171
+ if (lockY && !releasedY) {
172
+ const restY = applyBounds(y, top, bottom, 0)
173
+ if (restY !== y) {
174
+ dragY.value = buildReleaseAnimation(
175
+ { type: 'spring', velocity: vy },
176
+ restY,
177
+ ) as unknown as number
178
+ }
179
+ }
155
180
  if (hasDragEnd) {
156
181
  runOnJS(fireDragEnd)({ x, y, velocity: { x: vx, y: vy } })
157
182
  }