@rootnative/impulse 0.0.0-alpha.0 → 0.0.0-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.
Files changed (64) hide show
  1. package/CHANGELOG.md +51 -5
  2. package/README.md +206 -11
  3. package/dist/{chunk-5BMRKYVY.js → chunk-2UZTAWUQ.js} +20 -2
  4. package/dist/chunk-BNSFDNLA.js +75 -0
  5. package/dist/chunk-DXXGWG4Q.js +136 -0
  6. package/dist/{chunk-PMR25UCT.js → chunk-HGBCIL6X.js} +1 -1
  7. package/dist/chunk-HI5PHDJY.js +94 -0
  8. package/dist/{chunk-IG5RXCYR.js → chunk-IH7SQ5X6.js} +11 -15
  9. package/dist/chunk-MWCIEVTA.js +199 -0
  10. package/dist/{chunk-F4RHM4ZK.js → chunk-NYDDZD4G.js} +58 -1
  11. package/dist/chunk-PGOQSKEJ.js +144 -0
  12. package/dist/{chunk-FR242SUF.js → chunk-TGOGIZDH.js} +13 -7
  13. package/dist/chunk-VEPUHGPN.js +12 -0
  14. package/dist/chunk-YZHAQ4XK.js +136 -0
  15. package/dist/compose/index.d.ts +1 -1
  16. package/dist/double-tap/index.d.ts +184 -0
  17. package/dist/double-tap/index.js +5 -0
  18. package/dist/drag/index.d.ts +18 -13
  19. package/dist/drag/index.js +3 -3
  20. package/dist/index.d.ts +10 -3
  21. package/dist/index.js +12 -5
  22. package/dist/long-press/index.d.ts +219 -0
  23. package/dist/long-press/index.js +4 -0
  24. package/dist/pan/index.d.ts +221 -0
  25. package/dist/pan/index.js +4 -0
  26. package/dist/pinch/index.d.ts +239 -0
  27. package/dist/pinch/index.js +4 -0
  28. package/dist/raw/index.d.ts +3 -3
  29. package/dist/raw/index.js +2 -2
  30. package/dist/rotate/index.d.ts +263 -0
  31. package/dist/rotate/index.js +4 -0
  32. package/dist/swipe/index.d.ts +254 -0
  33. package/dist/swipe/index.js +4 -0
  34. package/dist/tap/index.d.ts +34 -29
  35. package/dist/tap/index.js +4 -3
  36. package/dist/tapEvent-KSSojt_l.d.ts +28 -0
  37. package/dist/{types-Ch2HM3aP.d.ts → types-ChGKY28a.d.ts} +27 -1
  38. package/dist/{useGestureMemo-Ccv8rB0C.d.ts → useGestureMemo-BRW1EKcJ.d.ts} +1 -1
  39. package/jest-setup.cjs +32 -1
  40. package/llms.txt +155 -0
  41. package/package.json +35 -2
  42. package/src/index.ts +50 -4
  43. package/src/intents/double-tap/index.ts +6 -0
  44. package/src/intents/long-press/index.ts +6 -0
  45. package/src/intents/pan/index.ts +2 -0
  46. package/src/intents/pinch/index.ts +2 -0
  47. package/src/intents/rotate/index.ts +6 -0
  48. package/src/intents/swipe/index.ts +8 -0
  49. package/src/intents/tapEvent.ts +50 -0
  50. package/src/intents/useDoubleTap.ts +321 -0
  51. package/src/intents/useDrag.ts +49 -28
  52. package/src/intents/useLongPress.ts +391 -0
  53. package/src/intents/usePan.ts +444 -0
  54. package/src/intents/usePinch.ts +483 -0
  55. package/src/intents/useRotate.ts +507 -0
  56. package/src/intents/useSwipe.ts +585 -0
  57. package/src/intents/useTap.ts +51 -60
  58. package/src/internal/intentResult.ts +67 -0
  59. package/src/internal/phaseCallbacks.ts +51 -0
  60. package/src/internal/useGestureMemo.ts +32 -4
  61. package/src/internal/useLatestCallback.ts +2 -2
  62. package/src/raw/useRawGesture.ts +1 -1
  63. package/src/relations/index.ts +94 -0
  64. package/src/types.ts +27 -0
@@ -0,0 +1,263 @@
1
+ import { RotationGesture } from 'react-native-gesture-handler';
2
+ import { SharedValue } from 'react-native-reanimated';
3
+ import { G as GestureMemoOptions } from '../useGestureMemo-BRW1EKcJ.js';
4
+ import { P as Point, H as HitSlop, I as IntentEndInfo, c as IntentResult } from '../types-ChGKY28a.js';
5
+ import 'react';
6
+
7
+ /** The intent-shaped payload a {@link useRotate} callback receives. */
8
+ interface RotateEvent {
9
+ /**
10
+ * How far the thing is turned now, in **degrees**, after `min`, `max` and
11
+ * `elastic` were applied.
12
+ *
13
+ * This accumulates across gestures. A second rotation continues from where
14
+ * the first one stopped, which is why `min` and `max` can be written as the
15
+ * travel of the whole control rather than of one gesture.
16
+ *
17
+ * Positive is clockwise, which is what React Native's `rotate` transform
18
+ * also treats as positive.
19
+ */
20
+ readonly angle: number;
21
+ /**
22
+ * How far the fingers turned during **this gesture alone**, in degrees.
23
+ *
24
+ * Untouched by `min`, `max` and `elastic`, and reset to zero at the start of
25
+ * every gesture. This is RNGH's own `rotation`, converted. Read `angle` for
26
+ * how far the thing being turned actually sits.
27
+ */
28
+ readonly gestureAngle: number;
29
+ /**
30
+ * The point the rotation turns about — RNGH's anchor, the centre between
31
+ * the fingers, relative to the view.
32
+ *
33
+ * A rotation applied about the view's own centre turns the content under
34
+ * the fingers rather than with them. This is the point the transform has to
35
+ * pivot on, and it is the rotation counterpart of `usePinch`'s `focal`.
36
+ */
37
+ readonly anchor: Point;
38
+ /**
39
+ * How fast the angle is changing, in degrees per second.
40
+ *
41
+ * RNGH's own documentation calls this "point units per second" for the
42
+ * rotation handler, which is a copy of the pan handler's wording rather
43
+ * than a description of this value. It is an angular speed.
44
+ */
45
+ readonly velocity: number;
46
+ /**
47
+ * The nearest angle inside `min` and `max`. Equal to `angle` whenever the
48
+ * rotation is in range, which with the default `elastic` of `0` is always.
49
+ *
50
+ * With `elastic` set, the fingers can turn past an end and Impulse leaves
51
+ * the angle there on release — moving it back is an animation, and Impulse
52
+ * owns no animation vocabulary. This field is the destination that
53
+ * animation needs, so the consumer does not have to re-derive the clamp
54
+ * from a range it already handed over.
55
+ */
56
+ readonly settled: number;
57
+ /** How many fingers are down. */
58
+ readonly pointers: number;
59
+ }
60
+ /** Options for {@link useRotate}. */
61
+ interface UseRotateOptions extends GestureMemoOptions {
62
+ /**
63
+ * The angle before any rotation, in degrees. Default `0`.
64
+ *
65
+ * Read once, at mount. `useSharedValue` keeps its first argument and
66
+ * ignores every later one, and that is the behaviour this option
67
+ * documents: after mount `angle` is the rotation's state, and the consumer
68
+ * moves it by writing it.
69
+ */
70
+ initial?: number;
71
+ /**
72
+ * The smallest angle, in degrees. Unset by default, which lets the fingers
73
+ * turn the thing without limit.
74
+ *
75
+ * Impulse does not wrap the angle at a full turn. Without `min` and `max`
76
+ * a second turn reports 360 more degrees than the first, which is what a
77
+ * dial counting revolutions needs and what a photo editor does not.
78
+ */
79
+ min?: number;
80
+ /**
81
+ * The largest angle, in degrees. Unset by default, which lets the fingers
82
+ * turn the thing without limit.
83
+ */
84
+ max?: number;
85
+ /**
86
+ * How much of the travel past `min` or `max` reaches `angle`, from `0` to
87
+ * `1`. Default `0`.
88
+ *
89
+ * `0` stops dead at the end. `1` ignores the end while the fingers are
90
+ * down. Anything between is resistance — the rotation keeps turning and
91
+ * turns less than the fingers do.
92
+ *
93
+ * Impulse does not bring the angle back. The end callback carries
94
+ * `settled` — the angle to animate to — and `@rootnative/inertia` or a
95
+ * `withSpring` of your own does the rest.
96
+ */
97
+ elastic?: number;
98
+ /**
99
+ * Extra touchable area around the view, in points.
100
+ *
101
+ * Written inline as an object is fine — the gesture is not rebuilt when the
102
+ * contents are unchanged.
103
+ */
104
+ hitSlop?: HitSlop;
105
+ /**
106
+ * Whether the gesture is recognized at all. Default `true`.
107
+ *
108
+ * Prefer this over unmounting the `<GestureDetector>`: a disabled gesture
109
+ * keeps its identity and its relations, so re-enabling it does not
110
+ * re-attach anything.
111
+ */
112
+ enabled?: boolean;
113
+ /**
114
+ * The rotation now owns the touch. **Runs on the JS thread** — Impulse owns
115
+ * the `scheduleOnRN` boundary, so this is an ordinary function and may
116
+ * touch React state.
117
+ *
118
+ * This is the first moment the rotation has definitely won. `onBegin` fires
119
+ * earlier and promises nothing.
120
+ */
121
+ onRotateStart?: (event: RotateEvent) => void;
122
+ /**
123
+ * The rotation is over. **Runs on the JS thread.**
124
+ *
125
+ * Fires only for a rotation that activated, so a touch that never became
126
+ * one never reaches here on either path.
127
+ *
128
+ * It fires for both endings, and `cancelled` says which. `false` is the
129
+ * fingers lifting, and `velocity` then describes the release. `true` is the
130
+ * system taking the rotation away — a competing gesture won, or the app
131
+ * went to the background. **There was no release on that path, so the
132
+ * velocity describes the last movement rather than a throw.**
133
+ *
134
+ * Read `settled` on both paths for where an elastic overshoot belongs.
135
+ */
136
+ onRotateEnd?: (event: RotateEvent, info: IntentEndInfo) => void;
137
+ /**
138
+ * A finger went down and the gesture is now a candidate. **This is a
139
+ * worklet** — mark it with the `'worklet'` directive, and do not touch
140
+ * React state from it.
141
+ *
142
+ * Being a candidate is not the same as winning: one finger cannot turn
143
+ * anything, and a second one may never arrive. Undo whatever it sets in
144
+ * `onFinalize`.
145
+ */
146
+ onBegin?: (event: RotateEvent) => void;
147
+ /**
148
+ * The angle changed. **This is a worklet**, and it runs on every frame the
149
+ * fingers turn.
150
+ *
151
+ * `angle` is already written by the time this runs, so a `useAnimatedStyle`
152
+ * reading it needs nothing from here. Use this for the work that turning
153
+ * alone does not do — snapping a readout to whole degrees, say.
154
+ *
155
+ * There is no JS-thread counterpart on purpose. A per-frame `scheduleOnRN`
156
+ * is a scheduling cost paid sixty times a second for a value that is
157
+ * already on the thread that needs it.
158
+ */
159
+ onUpdate?: (event: RotateEvent) => void;
160
+ /**
161
+ * The gesture is over, whether it activated or not. **This is a worklet.**
162
+ *
163
+ * `success` is `true` when the rotation activated and ended normally. This
164
+ * is the right place to clear anything `onBegin` set, because it runs on
165
+ * both paths.
166
+ */
167
+ onFinalize?: (event: RotateEvent, success: boolean) => void;
168
+ }
169
+ /** What {@link useRotate} returns. */
170
+ interface UseRotateResult extends IntentResult<RotationGesture> {
171
+ /**
172
+ * How far the thing is turned, in **degrees**, after `min`, `max` and
173
+ * `elastic`.
174
+ *
175
+ * **This is a position, not a movement.** It accumulates across gestures,
176
+ * so a second rotation continues from where the first stopped. `usePan` is
177
+ * the hook whose value zeroes at every gesture.
178
+ *
179
+ * Writing it is allowed and is how a release animation, or a reset button,
180
+ * hands control back. The next rotation continues from whatever it holds.
181
+ */
182
+ readonly angle: SharedValue<number>;
183
+ /**
184
+ * The point the rotation turns about, relative to the view.
185
+ *
186
+ * It keeps the last gesture's anchor after the fingers lift, so a release
187
+ * animation turns about the same point the gesture did rather than
188
+ * snapping to the view's centre.
189
+ */
190
+ readonly anchor: SharedValue<Point>;
191
+ }
192
+ /**
193
+ * Recognize a two-finger rotation, and own the angle it produces.
194
+ *
195
+ * ```tsx
196
+ * const rotate = useRotate({ min: -45, max: 45 })
197
+ *
198
+ * const style = useAnimatedStyle(() => ({
199
+ * transform: [{ rotate: `${rotate.angle.value}deg` }],
200
+ * }))
201
+ *
202
+ * return (
203
+ * <GestureDetector gesture={rotate.gesture}>
204
+ * <Animated.Image style={style} source={source} />
205
+ * </GestureDetector>
206
+ * )
207
+ * ```
208
+ *
209
+ * **The angle is in degrees, and RNGH's is in radians.** This is the one
210
+ * place Impulse changes a unit rather than passing one through. A range
211
+ * written as `min: -45` is a range a person wrote; `-Math.PI / 4` is one they
212
+ * derived, and deriving it per project is the work this library exists to
213
+ * remove. A React Native style takes either unit, so the choice costs the
214
+ * consumer nothing. `gestureAngle` is the same conversion applied to RNGH's
215
+ * per-gesture value.
216
+ *
217
+ * **The angle accumulates; RNGH's does not.** A bare `Gesture.Rotation()`
218
+ * reports a value that restarts at zero on every gesture, so a control built
219
+ * on it springs back to upright the moment the fingers lift again. Carrying
220
+ * the angle across gestures is the stored start that every consumer otherwise
221
+ * writes, and it is why `min` and `max` can be the travel of the control
222
+ * rather than of one gesture.
223
+ *
224
+ * **It does not wrap at a full turn.** Without `min` and `max`, a second
225
+ * revolution reports 360 degrees more than the first. That is what a dial
226
+ * counting turns needs. A control that should read `10` rather than `370`
227
+ * either sets a range or takes the remainder itself — Impulse does not guess
228
+ * which, because both are correct for something.
229
+ *
230
+ * **`anchor` is the point the turn happens about.** Rotating about the view's
231
+ * own centre turns the content under the fingers rather than with them. It is
232
+ * the rotation counterpart of `usePinch`'s `focal`, and it is read at
233
+ * `onStart` as well as at every update so the first callback does not report
234
+ * the previous gesture's point.
235
+ *
236
+ * **Activation criteria.** There are none to set. RNGH's rotation takes the
237
+ * touch as soon as two fingers turn, and it exposes no threshold, so Impulse
238
+ * has none to pass on. `usePinch` is the other intent in this position.
239
+ *
240
+ * **Coexistence.** A rotation almost always shares its view with a pinch, and
241
+ * neither has a threshold to separate them with. Say it: `useGestures` with
242
+ * `mode: 'simultaneous'` for gestures this screen owns, `alongside` for one
243
+ * it does not.
244
+ *
245
+ * **Web.** RNGH recognizes rotation from pointer events, so it needs two
246
+ * pointers — a touchscreen, or a device that reports them. A trackpad's
247
+ * rotation is not a pointer pair and never reaches this hook, so a desktop
248
+ * browser with a trackpad alone cannot turn anything. Give it a control that
249
+ * writes `angle` directly, which the accessibility fallback needs anyway.
250
+ *
251
+ * **Accessibility.** A rotation is invisible to a screen reader and
252
+ * unreachable from a keyboard, and this hook does not fix that. Whatever the
253
+ * rotation turns must be reachable another way: buttons that step the angle
254
+ * by a documented amount, a control that resets it to zero, or
255
+ * `accessibilityActions` with `onAccessibilityAction`. A rotate-only
256
+ * affordance is a bug, not a trade-off.
257
+ *
258
+ * @param options - The angle range, callbacks, and the `alongside` /
259
+ * `blocks` / `deferTo` coexistence options every Impulse hook accepts.
260
+ */
261
+ declare function useRotate(options?: UseRotateOptions): UseRotateResult;
262
+
263
+ export { type RotateEvent, type UseRotateOptions, type UseRotateResult, useRotate };
@@ -0,0 +1,4 @@
1
+ export { useRotate } from '../chunk-YZHAQ4XK.js';
2
+ import '../chunk-2UZTAWUQ.js';
3
+ import '../chunk-NYDDZD4G.js';
4
+ import '../chunk-ZX7WNICB.js';
@@ -0,0 +1,254 @@
1
+ import { PanGesture } from 'react-native-gesture-handler';
2
+ import { SharedValue } from 'react-native-reanimated';
3
+ import { G as GestureMemoOptions } from '../useGestureMemo-BRW1EKcJ.js';
4
+ import { P as Point, H as HitSlop, I as IntentEndInfo, c as IntentResult } from '../types-ChGKY28a.js';
5
+ import 'react';
6
+
7
+ /** Which way a swipe went. The same vocabulary as the `directions` option. */
8
+ type SwipeDirection = 'left' | 'right' | 'up' | 'down';
9
+ /** The intent-shaped payload a {@link useSwipe} callback receives. */
10
+ interface SwipeEvent {
11
+ /**
12
+ * Which way the swipe went, or `null` when the release did not commit.
13
+ *
14
+ * `onSwipe` receives a payload whose direction is never `null` — that is
15
+ * the whole meaning of that callback. `onSwipeEnd` fires on every release
16
+ * of a swipe that activated, so it is the one that has to read this.
17
+ */
18
+ readonly direction: SwipeDirection | null;
19
+ /**
20
+ * How far the finger travelled along the dominant axis, in points, always
21
+ * positive.
22
+ *
23
+ * Compare it against `commitDistance` to see how close a release that did
24
+ * not commit came.
25
+ */
26
+ readonly distance: number;
27
+ /**
28
+ * How fast the finger was moving along the dominant axis, in points per
29
+ * second, always positive. The scalar counterpart of `velocity`, and what
30
+ * `commitSpeed` is compared against.
31
+ */
32
+ readonly speed: number;
33
+ /**
34
+ * How far the finger moved since the swipe activated, signed and per axis.
35
+ *
36
+ * Measured from the activation point, so it does not include the
37
+ * `threshold` travel the finger spent before the swipe existed.
38
+ */
39
+ readonly translation: Point;
40
+ /** Finger speed, in points per second, signed and per axis. */
41
+ readonly velocity: Point;
42
+ /** The touch point relative to the window. */
43
+ readonly absolute: Point;
44
+ /** How many fingers are down. */
45
+ readonly pointers: number;
46
+ }
47
+ /**
48
+ * A {@link SwipeEvent} that committed, so its direction is known.
49
+ *
50
+ * `onSwipe` takes this rather than `SwipeEvent`, which is what saves every
51
+ * consumer of that callback a null check for a case it cannot be in.
52
+ */
53
+ type CommittedSwipeEvent = SwipeEvent & {
54
+ readonly direction: SwipeDirection;
55
+ };
56
+ /** Options for {@link useSwipe}. */
57
+ interface UseSwipeOptions extends GestureMemoOptions {
58
+ /**
59
+ * Which directions may commit. Every direction by default.
60
+ *
61
+ * **This also sets the activation criterion**, which is the reason to
62
+ * narrow it even when the extra directions would never fire. A list that is
63
+ * entirely horizontal gives the gesture a directional threshold on the x
64
+ * axis, so a vertical scroll view under it keeps working; an entirely
65
+ * vertical list does the same on y. A mixed list has no axis to lock, so
66
+ * the threshold is radial and the swipe competes with a scroller for every
67
+ * touch.
68
+ *
69
+ * Written inline as an array is fine — the gesture is not rebuilt when the
70
+ * contents are unchanged.
71
+ */
72
+ directions?: readonly SwipeDirection[];
73
+ /**
74
+ * How far the finger must travel before the swipe activates and starts
75
+ * tracking, in points. Default `10`.
76
+ *
77
+ * This is not the commit test. It is the point at which the swipe takes the
78
+ * touch and `x` and `y` start reporting — see `commitDistance` for what
79
+ * decides that a release counts.
80
+ */
81
+ threshold?: number;
82
+ /**
83
+ * How far the finger must travel for a release to commit, in points.
84
+ * Default `80`.
85
+ *
86
+ * Measured along the dominant axis, from the activation point.
87
+ */
88
+ commitDistance?: number;
89
+ /**
90
+ * How fast the finger must be moving for a release to commit, in points per
91
+ * second. Default `800`.
92
+ *
93
+ * The flick: a release this fast commits even when it never travelled
94
+ * `commitDistance`. Either test is enough on its own.
95
+ */
96
+ commitSpeed?: number;
97
+ /**
98
+ * Movement across the axis that makes the swipe fail, in points. Unset by
99
+ * default, which leaves RNGH's own behaviour in place.
100
+ *
101
+ * Ignored when `directions` has no single axis, which has no cross axis.
102
+ */
103
+ failOffset?: number;
104
+ /**
105
+ * How many fingers must be on the view. Unset by default, which leaves
106
+ * RNGH's range of one to ten in place.
107
+ */
108
+ pointers?: number;
109
+ /**
110
+ * Extra touchable area around the view, in points.
111
+ *
112
+ * Written inline as an object is fine — the gesture is not rebuilt when the
113
+ * contents are unchanged.
114
+ */
115
+ hitSlop?: HitSlop;
116
+ /**
117
+ * Whether the gesture is recognized at all. Default `true`.
118
+ *
119
+ * Prefer this over unmounting the `<GestureDetector>`: a disabled gesture
120
+ * keeps its identity and its relations, so re-enabling it does not
121
+ * re-attach anything.
122
+ */
123
+ enabled?: boolean;
124
+ /**
125
+ * The swipe committed. **Runs on the JS thread** — Impulse owns the
126
+ * `scheduleOnRN` boundary, so this is an ordinary function and may touch
127
+ * React state.
128
+ *
129
+ * This is the callback almost every consumer wants, and it fires only for
130
+ * the thing it is named after: a release that passed `commitDistance` or
131
+ * `commitSpeed` in a direction `directions` allows. It never fires for a
132
+ * cancelled gesture, because a swipe the system took away did not happen.
133
+ *
134
+ * `event.direction` is never `null` here.
135
+ */
136
+ onSwipe?: (event: CommittedSwipeEvent) => void;
137
+ /**
138
+ * The swipe ended, committed or not. **Runs on the JS thread.**
139
+ *
140
+ * Fires on every release of a swipe that activated, which is what a view
141
+ * that followed the finger needs: `onSwipe` says to act, and this says to
142
+ * put the view back. Read `event.direction` for whether it committed —
143
+ * `null` is the release that did not.
144
+ *
145
+ * It fires for both endings, and `cancelled` says which. A cancelled
146
+ * gesture never commits, so its direction is always `null`.
147
+ */
148
+ onSwipeEnd?: (event: SwipeEvent, info: IntentEndInfo) => void;
149
+ /**
150
+ * The finger went down and the gesture is now a candidate. **This is a
151
+ * worklet** — mark it with the `'worklet'` directive, and do not touch
152
+ * React state from it.
153
+ *
154
+ * The swipe has not passed `threshold` yet and may never. Undo whatever it
155
+ * sets in `onFinalize`.
156
+ */
157
+ onBegin?: (event: SwipeEvent) => void;
158
+ /**
159
+ * The finger moved. **This is a worklet**, and it runs on every frame.
160
+ *
161
+ * `direction` is `null` on every one of these: a swipe is decided at
162
+ * release, not while the finger is down. Read `translation` to show the
163
+ * view following, and `distance` to show how close a commit is.
164
+ */
165
+ onUpdate?: (event: SwipeEvent) => void;
166
+ /**
167
+ * The gesture is over, whether it activated or not. **This is a worklet.**
168
+ *
169
+ * `success` is `true` when the swipe activated and ended normally — which
170
+ * is not the same as committing. A release that stopped short is a
171
+ * successful gesture with no direction.
172
+ */
173
+ onFinalize?: (event: SwipeEvent, success: boolean) => void;
174
+ }
175
+ /** What {@link useSwipe} returns. */
176
+ interface UseSwipeResult extends IntentResult<PanGesture> {
177
+ /**
178
+ * How far the finger has moved along the x axis since the swipe activated.
179
+ *
180
+ * **This is movement, not a position**, on the same terms as `usePan`: it
181
+ * is zeroed at the start of every gesture and keeps its final number after
182
+ * the gesture ends, so a release animation has something to animate from.
183
+ *
184
+ * Impulse does not put it back. That is an animation, and Principle 5 keeps
185
+ * animation out of this library — `onSwipeEnd` is where a consumer springs
186
+ * it home or off the screen.
187
+ */
188
+ readonly x: SharedValue<number>;
189
+ /**
190
+ * How far the finger has moved along the y axis since the swipe activated.
191
+ * Same terms as `x`.
192
+ */
193
+ readonly y: SharedValue<number>;
194
+ }
195
+ /**
196
+ * Recognize a directional swipe.
197
+ *
198
+ * ```tsx
199
+ * const swipe = useSwipe({
200
+ * directions: ['left'],
201
+ * onSwipe: () => archive(item.id),
202
+ * onSwipeEnd: () => {
203
+ * swipe.x.value = withSpring(0)
204
+ * },
205
+ * })
206
+ *
207
+ * return (
208
+ * <GestureDetector gesture={swipe.gesture}>
209
+ * <Animated.View style={style} />
210
+ * </GestureDetector>
211
+ * )
212
+ * ```
213
+ *
214
+ * A swipe is a pan that is judged at release. The finger moves, `x` and `y`
215
+ * report it so the view can follow, and on release the travel and the speed
216
+ * along the dominant axis decide whether it counted. `onSwipe` fires only for
217
+ * a release that counted, in a direction `directions` allows.
218
+ *
219
+ * **Narrowing `directions` is the coexistence setting, not only a filter.**
220
+ * An all-horizontal list gives the gesture a directional threshold on x, so a
221
+ * vertical scroll view under it keeps working without a relation. A mixed
222
+ * list has no axis to lock, so the threshold is radial and the swipe competes
223
+ * for every touch — declare `deferTo` or `blocks` there.
224
+ *
225
+ * **Two thresholds, and they mean different things.** `threshold` is when the
226
+ * swipe takes the touch and starts reporting. `commitDistance` and
227
+ * `commitSpeed` are what a release is measured against. A swipe that
228
+ * activates and stops short reaches `onSwipeEnd` with a `null` direction, and
229
+ * never reaches `onSwipe`.
230
+ *
231
+ * **No style, and no animation.** This hook returns numbers and stops there.
232
+ * Whether a committed row leaves the screen and an uncommitted one springs
233
+ * back is the consumer's decision, taken in `onSwipeEnd`.
234
+ * `@rootnative/inertia-gestures` has a `useSwipe` that owns both, and needs
235
+ * `@rootnative/inertia` to do it.
236
+ *
237
+ * **Web.** RNGH recognizes pan from pointer events, so a mouse drag commits
238
+ * the same way a touch does, and `speed` is reported in the same units. A
239
+ * trackpad's two-finger swipe is a scroll, not a pan, and never reaches this
240
+ * hook.
241
+ *
242
+ * **Accessibility.** A swipe is invisible to a screen reader and unreachable
243
+ * from a keyboard. Whatever it commits must be reachable another way: a
244
+ * visible button for a row action, a paging control for a carousel, or
245
+ * `accessibilityActions` with `onAccessibilityAction`. A swipe-only action is
246
+ * a bug, not a trade-off.
247
+ *
248
+ * @param options - Directions, activation and commit criteria, callbacks, and
249
+ * the `alongside` / `blocks` / `deferTo` coexistence options every Impulse
250
+ * hook accepts.
251
+ */
252
+ declare function useSwipe(options?: UseSwipeOptions): UseSwipeResult;
253
+
254
+ export { type CommittedSwipeEvent, type SwipeDirection, type SwipeEvent, type UseSwipeOptions, type UseSwipeResult, useSwipe };
@@ -0,0 +1,4 @@
1
+ export { useSwipe } from '../chunk-MWCIEVTA.js';
2
+ import '../chunk-2UZTAWUQ.js';
3
+ import '../chunk-NYDDZD4G.js';
4
+ import '../chunk-ZX7WNICB.js';
@@ -1,25 +1,10 @@
1
1
  import { TapGesture } from 'react-native-gesture-handler';
2
- import { G as GestureMemoOptions } from '../useGestureMemo-Ccv8rB0C.js';
3
- import { P as Point, H as HitSlop, I as IntentResult } from '../types-Ch2HM3aP.js';
2
+ import { G as GestureMemoOptions } from '../useGestureMemo-BRW1EKcJ.js';
3
+ import { T as TapEvent } from '../tapEvent-KSSojt_l.js';
4
+ import { H as HitSlop, I as IntentEndInfo, c as IntentResult } from '../types-ChGKY28a.js';
4
5
  import 'react';
5
6
  import 'react-native-reanimated';
6
7
 
7
- /** The intent-shaped payload a {@link useTap} callback receives. */
8
- interface TapEvent {
9
- /** X of the tap, in points, relative to the view the gesture is attached to. */
10
- readonly x: number;
11
- /** Y of the tap, in points, relative to the view the gesture is attached to. */
12
- readonly y: number;
13
- /**
14
- * The same point relative to the window.
15
- *
16
- * Prefer it over `x` / `y` when the view itself is being transformed by the
17
- * gesture — a tap on a view that is mid-animation reports a moving `x`.
18
- */
19
- readonly absolute: Point;
20
- /** How many fingers were down when the tap was recognized. */
21
- readonly pointers: number;
22
- }
23
8
  /** Options for {@link useTap}. */
24
9
  interface UseTapOptions extends GestureMemoOptions {
25
10
  /**
@@ -60,14 +45,23 @@ interface UseTapOptions extends GestureMemoOptions {
60
45
  */
61
46
  enabled?: boolean;
62
47
  /**
63
- * The tap happened. **Runs on the JS thread** — Impulse owns the
64
- * `runOnJS` boundary, so this is an ordinary function and may touch React
48
+ * The tap ended. **Runs on the JS thread** — Impulse owns the
49
+ * `scheduleOnRN` boundary, so this is an ordinary function and may touch React
65
50
  * state.
66
51
  *
67
- * It fires only for a successful tap. A touch that moved too far or stayed
68
- * down too long reaches `onFinalize` with `success: false` instead.
52
+ * It fires only for a tap the recognizer accepted, and `cancelled` says
53
+ * what happened after that. `false` is the ordinary tap. `true` means the
54
+ * system took the recognized tap away before it could be acted on — a
55
+ * competing gesture in a relation won it, or the app went to the
56
+ * background.
57
+ *
58
+ * **Check `cancelled` before you act on the tap.** A handler that navigates
59
+ * or submits should do nothing when it is `true`. The path is rare: a touch
60
+ * that moved past `maxDistance` or stayed down past `maxDuration` was never
61
+ * a tap at all, so it reaches `onFinalize` with `success: false` and never
62
+ * gets here.
69
63
  */
70
- onTap?: (event: TapEvent) => void;
64
+ onTap?: (event: TapEvent, info: IntentEndInfo) => void;
71
65
  /**
72
66
  * The finger went down and the gesture is now a candidate. **This is a
73
67
  * worklet** — mark it with the `'worklet'` directive, and do not touch
@@ -111,7 +105,7 @@ type UseTapResult = IntentResult<TapGesture>;
111
105
  *
112
106
  * `onTap` runs on the JS thread and may set React state directly. `onBegin`
113
107
  * and `onFinalize` are worklets and run on the UI thread — the name states
114
- * the thread, so there is nothing to configure and no `runOnJS` to write.
108
+ * the thread, so there is nothing to configure and no `scheduleOnRN` to write.
115
109
  *
116
110
  * `isActive` is a shared value that is `true` while the finger is down. Drive
117
111
  * a pressed state from it without a re-render:
@@ -124,11 +118,22 @@ type UseTapResult = IntentResult<TapGesture>;
124
118
  * **Activation criteria.** `maxDuration` defaults to 500ms and `maxDistance`
125
119
  * to 10 points. The distance default is Impulse's, not RNGH's: RNGH defers to
126
120
  * the platform there, so the same tap is accepted on one operating system and
127
- * rejected on the other. Neither default has been measured on hardware yet.
121
+ * rejected on the other. The device sweep did not test either default.
122
+ *
123
+ * **Pairing with a double tap.** A single tap and a double tap on one view is
124
+ * a composition, not an option — and the mode is `exclusive`, with the double
125
+ * tap named first:
126
+ *
127
+ * ```tsx
128
+ * useGestures([double, tap], { mode: 'exclusive' })
129
+ * ```
128
130
  *
129
- * **Racing a double tap.** A single tap and a double tap on one view is a
130
- * composition, not an option — `useGestures([tap, double], { mode: 'race' })`.
131
- * Do not reach for `maxDelay` to build it by hand.
131
+ * `race` is the wrong mode here and fails quietly. A single tap recognizes on
132
+ * the first release, so it wins the race every time and the double tap never
133
+ * fires. `exclusive` is what makes the single tap wait to learn whether a
134
+ * second tap is coming — at the cost of `useDoubleTap`'s `maxDelay` in
135
+ * latency on every single tap. Do not reach for `maxDelay` to build the pair
136
+ * by hand.
132
137
  *
133
138
  * **Web.** RNGH's web implementation recognizes tap from pointer events, and
134
139
  * `pointers` above 1 is unreliable there because a mouse reports one pointer
@@ -147,4 +152,4 @@ type UseTapResult = IntentResult<TapGesture>;
147
152
  */
148
153
  declare function useTap(options?: UseTapOptions): UseTapResult;
149
154
 
150
- export { type TapEvent, type UseTapOptions, type UseTapResult, useTap };
155
+ export { TapEvent, type UseTapOptions, type UseTapResult, useTap };
package/dist/tap/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export { useTap } from '../chunk-IG5RXCYR.js';
2
- import '../chunk-5BMRKYVY.js';
3
- import '../chunk-F4RHM4ZK.js';
1
+ export { useTap } from '../chunk-IH7SQ5X6.js';
2
+ import '../chunk-VEPUHGPN.js';
3
+ import '../chunk-2UZTAWUQ.js';
4
+ import '../chunk-NYDDZD4G.js';
4
5
  import '../chunk-ZX7WNICB.js';
@@ -0,0 +1,28 @@
1
+ import { P as Point } from './types-ChGKY28a.js';
2
+
3
+ /**
4
+ * The intent-shaped payload a tap callback receives.
5
+ *
6
+ * Shared by `useTap` and `useDoubleTap`, because the two recognize the same
7
+ * touch and differ only in how many times it happens. One payload rather than
8
+ * two identical ones means a consumer who already knows `useTap`'s event
9
+ * knows this one, and a field added later cannot reach one hook and miss the
10
+ * other.
11
+ */
12
+ interface TapEvent {
13
+ /** X of the tap, in points, relative to the view the gesture is attached to. */
14
+ readonly x: number;
15
+ /** Y of the tap, in points, relative to the view the gesture is attached to. */
16
+ readonly y: number;
17
+ /**
18
+ * The same point relative to the window.
19
+ *
20
+ * Prefer it over `x` / `y` when the view itself is being transformed by the
21
+ * gesture — a tap on a view that is mid-animation reports a moving `x`.
22
+ */
23
+ readonly absolute: Point;
24
+ /** How many fingers were down when the tap was recognized. */
25
+ readonly pointers: number;
26
+ }
27
+
28
+ export type { TapEvent as T };