@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,136 @@
1
+ import { useStableRecord, useLatestCallback, buildIntentResult } from './chunk-2UZTAWUQ.js';
2
+ import { useGestureMemo } from './chunk-NYDDZD4G.js';
3
+ import { useMemo } from 'react';
4
+ import { Gesture } from 'react-native-gesture-handler';
5
+ import { useSharedValue } from 'react-native-reanimated';
6
+ import { scheduleOnRN } from 'react-native-worklets';
7
+
8
+ var DEGREES_PER_RADIAN = 180 / Math.PI;
9
+ function resist(value, min, max, elastic) {
10
+ "worklet";
11
+ if (min !== void 0 && value < min) {
12
+ return min + (value - min) * elastic;
13
+ }
14
+ if (max !== void 0 && value > max) {
15
+ return max + (value - max) * elastic;
16
+ }
17
+ return value;
18
+ }
19
+ function clamp(value, min, max) {
20
+ "worklet";
21
+ if (min !== void 0 && value < min) {
22
+ return min;
23
+ }
24
+ if (max !== void 0 && value > max) {
25
+ return max;
26
+ }
27
+ return value;
28
+ }
29
+ function useRotate(options = {}) {
30
+ const {
31
+ initial = 0,
32
+ min,
33
+ max,
34
+ elastic = 0,
35
+ enabled,
36
+ onRotateStart,
37
+ onRotateEnd,
38
+ onBegin,
39
+ onUpdate,
40
+ onFinalize
41
+ } = options;
42
+ const angle = useSharedValue(initial);
43
+ const anchor = useSharedValue({ x: 0, y: 0 });
44
+ const isActive = useSharedValue(false);
45
+ const startAngle = useSharedValue(initial);
46
+ const hitSlop = useStableRecord(options.hitSlop);
47
+ const handleRotateStart = useLatestCallback(onRotateStart);
48
+ const handleRotateEnd = useLatestCallback(onRotateEnd);
49
+ const hasRotateStart = onRotateStart !== void 0;
50
+ const hasRotateEnd = onRotateEnd !== void 0;
51
+ const built = useGestureMemo(
52
+ "useRotate",
53
+ () => {
54
+ const toRotateEvent = (event) => {
55
+ "worklet";
56
+ return {
57
+ angle: angle.value,
58
+ gestureAngle: event.rotation * DEGREES_PER_RADIAN,
59
+ anchor: anchor.value,
60
+ velocity: event.velocity * DEGREES_PER_RADIAN,
61
+ settled: clamp(angle.value, min, max),
62
+ pointers: event.numberOfPointers
63
+ };
64
+ };
65
+ const trackAnchor = (event) => {
66
+ "worklet";
67
+ anchor.value = { x: event.anchorX, y: event.anchorY };
68
+ };
69
+ const rotation = Gesture.Rotation().onBegin((event) => {
70
+ "worklet";
71
+ onBegin?.(toRotateEvent(event));
72
+ }).onStart((event) => {
73
+ "worklet";
74
+ startAngle.value = angle.value - event.rotation * DEGREES_PER_RADIAN;
75
+ trackAnchor(event);
76
+ isActive.value = true;
77
+ if (hasRotateStart) {
78
+ scheduleOnRN(handleRotateStart, toRotateEvent(event));
79
+ }
80
+ }).onUpdate((event) => {
81
+ "worklet";
82
+ trackAnchor(event);
83
+ angle.value = resist(
84
+ startAngle.value + event.rotation * DEGREES_PER_RADIAN,
85
+ min,
86
+ max,
87
+ elastic
88
+ );
89
+ onUpdate?.(toRotateEvent(event));
90
+ }).onEnd((event, success) => {
91
+ "worklet";
92
+ if (hasRotateEnd) {
93
+ scheduleOnRN(handleRotateEnd, toRotateEvent(event), {
94
+ cancelled: !success
95
+ });
96
+ }
97
+ }).onFinalize((event, success) => {
98
+ "worklet";
99
+ isActive.value = false;
100
+ onFinalize?.(toRotateEvent(event), success);
101
+ });
102
+ if (hitSlop !== void 0) {
103
+ rotation.hitSlop(hitSlop);
104
+ }
105
+ if (enabled !== void 0) {
106
+ rotation.enabled(enabled);
107
+ }
108
+ return rotation;
109
+ },
110
+ [
111
+ min,
112
+ max,
113
+ elastic,
114
+ hitSlop,
115
+ enabled,
116
+ hasRotateStart,
117
+ hasRotateEnd,
118
+ handleRotateStart,
119
+ handleRotateEnd,
120
+ angle,
121
+ anchor,
122
+ startAngle,
123
+ isActive,
124
+ onBegin,
125
+ onUpdate,
126
+ onFinalize
127
+ ],
128
+ options
129
+ );
130
+ return useMemo(
131
+ () => buildIntentResult(built, { angle, anchor, isActive }),
132
+ [built, angle, anchor, isActive]
133
+ );
134
+ }
135
+
136
+ export { useRotate };
@@ -1,5 +1,5 @@
1
1
  import { ComposedGesture } from 'react-native-gesture-handler';
2
- import { A as AttachableGesture, a as ComposeMode } from '../types-Ch2HM3aP.js';
2
+ import { A as AttachableGesture, a as ComposeMode } from '../types-ChGKY28a.js';
3
3
  import 'react';
4
4
  import 'react-native-reanimated';
5
5
 
@@ -0,0 +1,184 @@
1
+ import { TapGesture } from 'react-native-gesture-handler';
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';
5
+ import 'react';
6
+ import 'react-native-reanimated';
7
+
8
+ /** Options for {@link useDoubleTap}. */
9
+ interface UseDoubleTapOptions extends GestureMemoOptions {
10
+ /**
11
+ * How many fingers must be down. Default `1`.
12
+ *
13
+ * The count applies to both taps — a two-finger double tap is two taps of
14
+ * two fingers, not a tap of two followed by a tap of one.
15
+ */
16
+ pointers?: number;
17
+ /**
18
+ * How long each tap may hold the finger down, in milliseconds. Default
19
+ * `500`.
20
+ *
21
+ * This is per tap, not for the pair. A finger that stays down past it fails
22
+ * the gesture rather than counting as the second tap.
23
+ */
24
+ maxDuration?: number;
25
+ /**
26
+ * How long the gap between the two taps may be, in milliseconds. Default
27
+ * `500`.
28
+ *
29
+ * **Lowering it is how a composed single tap stops feeling slow.** When a
30
+ * `useTap` is composed `exclusive` behind this hook, the single tap cannot
31
+ * report until this window has passed without a second tap, so every
32
+ * ordinary tap on that view waits this long. 500ms is RNGH's number and is
33
+ * generous; 250 to 300 is closer to what the platforms themselves use.
34
+ *
35
+ * The device sweep of 2026-09-19 found that a deliberate double tap still
36
+ * registers at 250, on an Android emulator and an iOS simulator. Whether
37
+ * the single-tap latency feels acceptable is unmeasured. See Known gaps in
38
+ * docs/docs/roadmap.md.
39
+ */
40
+ maxDelay?: number;
41
+ /**
42
+ * How far the finger may travel within a tap, in points. Default `10`.
43
+ *
44
+ * This does not limit how far the second tap may land from the first — RNGH
45
+ * measures each tap on its own.
46
+ */
47
+ maxDistance?: number;
48
+ /**
49
+ * Extra touchable area around the view, in points.
50
+ *
51
+ * Written inline as an object is fine — the gesture is not rebuilt when the
52
+ * contents are unchanged.
53
+ */
54
+ hitSlop?: HitSlop;
55
+ /**
56
+ * Whether the gesture is recognized at all. Default `true`.
57
+ *
58
+ * Prefer this over unmounting the `<GestureDetector>`: a disabled gesture
59
+ * keeps its identity and its relations, so re-enabling it does not
60
+ * re-attach anything.
61
+ */
62
+ enabled?: boolean;
63
+ /**
64
+ * The double tap ended. **Runs on the JS thread** — Impulse owns the
65
+ * `scheduleOnRN` boundary, so this is an ordinary function and may touch React
66
+ * state.
67
+ *
68
+ * The payload describes the second tap, which is the one the consumer means
69
+ * when they ask where the double tap was.
70
+ *
71
+ * It fires only for a double tap the recognizer accepted, and `cancelled`
72
+ * says what happened after that. `false` is the ordinary double tap. `true`
73
+ * means the system took the recognized double tap away — a competing
74
+ * gesture in a relation won it, or the app went to the background.
75
+ *
76
+ * **Check `cancelled` before you act on it.** A handler that zooms or
77
+ * navigates should do nothing when it is `true`. The path is rare: a single
78
+ * tap that was never followed by a second reaches `onFinalize` with
79
+ * `success: false` and never gets here.
80
+ */
81
+ onDoubleTap?: (event: TapEvent, info: IntentEndInfo) => void;
82
+ /**
83
+ * The first finger went down and the gesture is now a candidate. **This is
84
+ * a worklet** — mark it with the `'worklet'` directive, and do not touch
85
+ * React state from it.
86
+ *
87
+ * It fires once for the pair, on the first touch, not once per tap. Most of
88
+ * the touches that reach it are ordinary single taps that will fail this
89
+ * gesture, so it is a poor place to show anything the user would read as
90
+ * commitment.
91
+ */
92
+ onBegin?: (event: TapEvent) => void;
93
+ /**
94
+ * The gesture is over, whether both taps happened or not. **This is a
95
+ * worklet.**
96
+ *
97
+ * `success` is `true` when the double tap was recognized. This is the right
98
+ * place to clear anything `onBegin` set, because it runs on both paths.
99
+ */
100
+ onFinalize?: (event: TapEvent, success: boolean) => void;
101
+ }
102
+ /**
103
+ * What {@link useDoubleTap} returns.
104
+ *
105
+ * Its own name rather than an alias of `UseTapResult`, so the two can diverge
106
+ * without a breaking rename. A double tap produces no continuous value, so
107
+ * the gesture, the ref, and `isActive` are the whole result.
108
+ */
109
+ type UseDoubleTapResult = IntentResult<TapGesture>;
110
+ /**
111
+ * Recognize two taps in quick succession.
112
+ *
113
+ * ```tsx
114
+ * const double = useDoubleTap({ onDoubleTap: () => zoomIn() })
115
+ *
116
+ * return (
117
+ * <GestureDetector gesture={double.gesture}>
118
+ * <View />
119
+ * </GestureDetector>
120
+ * )
121
+ * ```
122
+ *
123
+ * `onDoubleTap` runs on the JS thread and may set React state directly.
124
+ * `onBegin` and `onFinalize` are worklets and run on the UI thread — the name
125
+ * states the thread, so there is nothing to configure and no `scheduleOnRN` to
126
+ * write.
127
+ *
128
+ * `isActive` is a shared value that is `true` from the first finger down
129
+ * until the pair resolves. It is **not** a useful pressed state on its own:
130
+ * it is `true` for every ordinary single tap on the view as well, and those
131
+ * mostly end in failure. Drive a pressed state from a `useTap` composed with
132
+ * this one, and use `isActive` here only for something that should show while
133
+ * a double tap is genuinely in progress.
134
+ *
135
+ * **Pairing with a single tap.** This is the composition that makes both
136
+ * work, and the mode matters:
137
+ *
138
+ * ```tsx
139
+ * const double = useDoubleTap({ maxDelay: 250, onDoubleTap: zoomIn })
140
+ * const tap = useTap({ onTap: select })
141
+ * const { gesture } = useGestures([double, tap], { mode: 'exclusive' })
142
+ * ```
143
+ *
144
+ * `exclusive` tries the members in order and lets a later one activate only
145
+ * after every earlier one has failed, so the single tap waits to learn
146
+ * whether a second tap is coming. `race` does not work: a single tap
147
+ * recognizes on the first release and would win every time. The cost is
148
+ * latency — the single tap cannot report for `maxDelay` milliseconds — which
149
+ * is why lowering `maxDelay` is the first thing to reach for when a composed
150
+ * single tap feels slow.
151
+ *
152
+ * **A view that only needs a double tap does not need the composition.** Use
153
+ * this hook alone; there is nothing for it to wait on.
154
+ *
155
+ * **Three taps and up are not modelled.** A triple tap is rare enough that a
156
+ * named intent would be dead API. Build it with `useRawGesture` and
157
+ * `Gesture.Tap().numberOfTaps(3)`.
158
+ *
159
+ * **Activation criteria.** `maxDuration` and `maxDelay` default to 500ms, and
160
+ * `maxDistance` to 10 points. The distance default is Impulse's, not RNGH's,
161
+ * and matches `useTap` so the two agree about what counts as a tap on one
162
+ * view. The device sweep found that `maxDelay` works at 500ms and at 250ms.
163
+ * It did not test `maxDuration` or `maxDistance`, and it measured no feel.
164
+ *
165
+ * **Web.** RNGH's web implementation recognizes tap from pointer events, so a
166
+ * double click behaves as a double tap. The browser's own double-click
167
+ * handling — text selection, zoom — is not suppressed by this hook, and
168
+ * `pointers` above 1 is unreliable there because a mouse reports one pointer
169
+ * and touch emulation varies by browser.
170
+ *
171
+ * **Accessibility.** A double tap is invisible to a screen reader and
172
+ * unreachable from a keyboard, and it is worse than a single tap on both
173
+ * counts: VoiceOver and TalkBack both consume a double tap as their own
174
+ * activation gesture, so a screen-reader user cannot reach this at all.
175
+ * Whatever it does must be reachable another way — an explicit control, or
176
+ * `accessibilityActions` with `onAccessibilityAction` on the view the gesture
177
+ * is attached to. A double-tap-only affordance is a bug, not a trade-off.
178
+ *
179
+ * @param options - Activation criteria, callbacks, and the `alongside` /
180
+ * `blocks` / `deferTo` coexistence options every Impulse hook accepts.
181
+ */
182
+ declare function useDoubleTap(options?: UseDoubleTapOptions): UseDoubleTapResult;
183
+
184
+ export { TapEvent, type UseDoubleTapOptions, type UseDoubleTapResult, useDoubleTap };
@@ -0,0 +1,5 @@
1
+ export { useDoubleTap } from '../chunk-BNSFDNLA.js';
2
+ import '../chunk-VEPUHGPN.js';
3
+ import '../chunk-2UZTAWUQ.js';
4
+ import '../chunk-NYDDZD4G.js';
5
+ import '../chunk-ZX7WNICB.js';
@@ -1,7 +1,7 @@
1
1
  import { PanGesture } from 'react-native-gesture-handler';
2
2
  import { SharedValue } from 'react-native-reanimated';
3
- import { G as GestureMemoOptions } from '../useGestureMemo-Ccv8rB0C.js';
4
- import { P as Point, H as HitSlop, I as IntentResult } from '../types-Ch2HM3aP.js';
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
5
  import 'react';
6
6
 
7
7
  /** Which way the drag is allowed to move. */
@@ -147,7 +147,7 @@ interface UseDragOptions extends GestureMemoOptions {
147
147
  enabled?: boolean;
148
148
  /**
149
149
  * The drag passed `threshold` and now owns the touch. **Runs on the JS
150
- * thread** — Impulse owns the `runOnJS` boundary, so this is an ordinary
150
+ * thread** — Impulse owns the `scheduleOnRN` boundary, so this is an ordinary
151
151
  * function and may touch React state.
152
152
  *
153
153
  * This is the first moment the drag has definitely won. `onBegin` fires
@@ -155,17 +155,21 @@ interface UseDragOptions extends GestureMemoOptions {
155
155
  */
156
156
  onDragStart?: (event: DragEvent) => void;
157
157
  /**
158
- * The finger lifted and the drag is over. **Runs on the JS thread.**
158
+ * The drag is over. **Runs on the JS thread.**
159
159
  *
160
- * Fires only for a drag that activated and then released. A drag the system
161
- * took away — a competing gesture won, or the app went to the background —
162
- * reaches `onFinalize` with `success: false` and never gets here, because
163
- * there was no release and so no velocity worth seeding a spring with.
160
+ * Fires only for a drag that activated, so a touch that never passed the
161
+ * threshold never reaches here on either path.
164
162
  *
165
- * Read `velocity` to seed that release animation, and `settled` for where
166
- * an elastic overshoot should return to.
163
+ * It fires for both endings, and `cancelled` says which. `false` is the
164
+ * finger lifting, and `velocity` then seeds the release animation. `true`
165
+ * is the system taking the drag away — a competing gesture won, or the app
166
+ * went to the background. **There was no release on that path, so the
167
+ * velocity describes the last movement rather than a throw.** Return the
168
+ * view to `settled` instead of springing it.
169
+ *
170
+ * Read `settled` on both paths for where an elastic overshoot belongs.
167
171
  */
168
- onDragEnd?: (event: DragEvent) => void;
172
+ onDragEnd?: (event: DragEvent, info: IntentEndInfo) => void;
169
173
  /**
170
174
  * The finger went down and the gesture is now a candidate. **This is a
171
175
  * worklet** — mark it with the `'worklet'` directive, and do not touch
@@ -180,7 +184,7 @@ interface UseDragOptions extends GestureMemoOptions {
180
184
  * The drag moved. **This is a worklet**, and it runs on every frame the
181
185
  * finger moves.
182
186
  *
183
- * There is no JS-thread counterpart on purpose. A per-frame `runOnJS` is a
187
+ * There is no JS-thread counterpart on purpose. A per-frame `scheduleOnRN` is a
184
188
  * scheduling cost paid sixty times a second for a value that is already on
185
189
  * the thread that needs it — read `x` and `y` from a `useAnimatedStyle`
186
190
  * instead, and let this callback handle what the style cannot.
@@ -248,7 +252,8 @@ interface UseDragResult extends IntentResult<PanGesture> {
248
252
  * vertical `ScrollView` leaves the scroll alone until the finger commits
249
253
  * sideways; with `'both'` it is a radial distance. Set `failOffset` as well
250
254
  * when a mostly-diagonal move should go to the other gesture rather than to
251
- * this one. The default has not been measured on hardware yet.
255
+ * this one. The default works mechanically in the device sweep, and its feel
256
+ * is unmeasured.
252
257
  *
253
258
  * **Coexistence.** A threshold decides who moves first; it does not decide
254
259
  * who wins a contested touch. For a drag inside a scroll view, say which one
@@ -1,4 +1,4 @@
1
- export { useDrag } from '../chunk-FR242SUF.js';
2
- import '../chunk-5BMRKYVY.js';
3
- import '../chunk-F4RHM4ZK.js';
1
+ export { useDrag } from '../chunk-TGOGIZDH.js';
2
+ import '../chunk-2UZTAWUQ.js';
3
+ import '../chunk-NYDDZD4G.js';
4
4
  import '../chunk-ZX7WNICB.js';
package/dist/index.d.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  export { GestureDetector } from 'react-native-gesture-handler';
2
2
  export { GestureCarrier, GestureMember, GesturesResult, UseGesturesOptions, useGestures } from './compose/index.js';
3
3
  export { RawGestureResult, UseRawGestureOptions, useRawGesture } from './raw/index.js';
4
- export { TapEvent, UseTapOptions, UseTapResult, useTap } from './tap/index.js';
4
+ export { UseTapOptions, UseTapResult, useTap } from './tap/index.js';
5
+ export { UseDoubleTapOptions, UseDoubleTapResult, useDoubleTap } from './double-tap/index.js';
6
+ export { LongPressEvent, UseLongPressOptions, UseLongPressResult, useLongPress } from './long-press/index.js';
5
7
  export { DragAxis, DragBounds, DragEvent, UseDragOptions, UseDragResult, useDrag } from './drag/index.js';
6
- export { A as AttachableGesture, C as CoexistenceOptions, a as ComposeMode, G as GestureReference, b as GestureReferences, H as HitSlop, I as IntentResult, P as Point } from './types-Ch2HM3aP.js';
8
+ export { PanAxis, PanEvent, UsePanOptions, UsePanResult, usePan } from './pan/index.js';
9
+ export { CommittedSwipeEvent, SwipeDirection, SwipeEvent, UseSwipeOptions, UseSwipeResult, useSwipe } from './swipe/index.js';
10
+ export { PinchEvent, UsePinchOptions, UsePinchResult, usePinch } from './pinch/index.js';
11
+ export { RotateEvent, UseRotateOptions, UseRotateResult, useRotate } from './rotate/index.js';
12
+ export { A as AttachableGesture, C as CoexistenceOptions, a as ComposeMode, G as GestureReference, b as GestureReferences, H as HitSlop, I as IntentEndInfo, c as IntentResult, P as Point } from './types-ChGKY28a.js';
13
+ export { T as TapEvent } from './tapEvent-KSSojt_l.js';
7
14
  import 'react';
8
- import './useGestureMemo-Ccv8rB0C.js';
15
+ import './useGestureMemo-BRW1EKcJ.js';
9
16
  import 'react-native-reanimated';
package/dist/index.js CHANGED
@@ -1,8 +1,15 @@
1
+ export { useSwipe } from './chunk-MWCIEVTA.js';
2
+ export { usePinch } from './chunk-DXXGWG4Q.js';
3
+ export { useRotate } from './chunk-YZHAQ4XK.js';
1
4
  export { useGestures } from './chunk-LM645QQT.js';
2
- export { useRawGesture } from './chunk-PMR25UCT.js';
3
- export { useTap } from './chunk-IG5RXCYR.js';
4
- export { useDrag } from './chunk-FR242SUF.js';
5
- import './chunk-5BMRKYVY.js';
6
- import './chunk-F4RHM4ZK.js';
5
+ export { useRawGesture } from './chunk-HGBCIL6X.js';
6
+ export { useTap } from './chunk-IH7SQ5X6.js';
7
+ export { useDoubleTap } from './chunk-BNSFDNLA.js';
8
+ import './chunk-VEPUHGPN.js';
9
+ export { useLongPress } from './chunk-HI5PHDJY.js';
10
+ export { useDrag } from './chunk-TGOGIZDH.js';
11
+ export { usePan } from './chunk-PGOQSKEJ.js';
12
+ import './chunk-2UZTAWUQ.js';
13
+ import './chunk-NYDDZD4G.js';
7
14
  import './chunk-ZX7WNICB.js';
8
15
  export { GestureDetector } from 'react-native-gesture-handler';
@@ -0,0 +1,219 @@
1
+ import { LongPressGesture } from 'react-native-gesture-handler';
2
+ import { G as GestureMemoOptions } from '../useGestureMemo-BRW1EKcJ.js';
3
+ import { P as Point, H as HitSlop, I as IntentEndInfo, c as IntentResult } from '../types-ChGKY28a.js';
4
+ import 'react';
5
+ import 'react-native-reanimated';
6
+
7
+ /** The intent-shaped payload a {@link useLongPress} callback receives. */
8
+ interface LongPressEvent {
9
+ /** X of the press, in points, relative to the view the gesture is attached to. */
10
+ readonly x: number;
11
+ /** Y of the press, 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 press on a view that is mid-animation reports a moving `x`.
18
+ */
19
+ readonly absolute: Point;
20
+ /**
21
+ * How long the finger has been down, in milliseconds.
22
+ *
23
+ * At `onLongPress` this is roughly `minDuration`, because that is the
24
+ * moment the press was recognized. At `onLongPressEnd` it is the whole hold
25
+ * — which is the number a hold-to-record affordance wants, and the reason
26
+ * this field is in the payload rather than derived by the consumer from two
27
+ * timestamps.
28
+ */
29
+ readonly duration: number;
30
+ /** How many fingers are down. */
31
+ readonly pointers: number;
32
+ }
33
+ /** Options for {@link useLongPress}. */
34
+ interface UseLongPressOptions extends GestureMemoOptions {
35
+ /**
36
+ * How long the finger must stay down before the press is recognized, in
37
+ * milliseconds. Default `500`.
38
+ *
39
+ * Lower it for an affordance the user is expected to discover; raise it for
40
+ * one that must not fire by accident. Below roughly 200ms it stops being
41
+ * distinguishable from a tap that was held a moment too long.
42
+ */
43
+ minDuration?: number;
44
+ /**
45
+ * How far the finger may travel, in points. Default `10`.
46
+ *
47
+ * **What this bounds is platform-dependent, and the split is verified.**
48
+ * RNGH documents it as bounding the wait only: "if the finger travels
49
+ * further than the defined distance and the handler hasn't yet activated,
50
+ * it will fail". Its web implementation does not match that — it checks the
51
+ * distance on every pointer move and *cancels* a press that is already
52
+ * active.
53
+ *
54
+ * So on web the finger may not travel past this once the press is
55
+ * recognized, and hold-then-drag needs this raised explicitly rather than
56
+ * relying on the documented behaviour. Native is unverified.
57
+ */
58
+ maxDistance?: number;
59
+ /**
60
+ * Exactly how many fingers must be down. Unset by default, which leaves
61
+ * RNGH's own behaviour in place.
62
+ *
63
+ * Setting it fixes the count exactly rather than setting a minimum.
64
+ */
65
+ pointers?: number;
66
+ /**
67
+ * Extra touchable area around the view, in points.
68
+ *
69
+ * Written inline as an object is fine — the gesture is not rebuilt when the
70
+ * contents are unchanged.
71
+ */
72
+ hitSlop?: HitSlop;
73
+ /**
74
+ * Whether the gesture is recognized at all. Default `true`.
75
+ *
76
+ * Prefer this over unmounting the `<GestureDetector>`: a disabled gesture
77
+ * keeps its identity and its relations, so re-enabling it does not
78
+ * re-attach anything.
79
+ */
80
+ enabled?: boolean;
81
+ /**
82
+ * The press was held long enough and is now recognized. **Runs on the JS
83
+ * thread** — Impulse owns the `scheduleOnRN` boundary, so this is an ordinary
84
+ * function and may touch React state.
85
+ *
86
+ * **The finger is still down when this fires.** That is the point: a
87
+ * context menu opens under a finger that has not lifted, which is what
88
+ * makes a long press feel like a long press rather than like a slow tap.
89
+ * Fire the haptic here.
90
+ */
91
+ onLongPress?: (event: LongPressEvent) => void;
92
+ /**
93
+ * A recognized press ended. **Runs on the JS thread.**
94
+ *
95
+ * Always preceded by `onLongPress`, because a touch that never became a
96
+ * long press never reaches here on either path.
97
+ *
98
+ * It fires for both endings, and `cancelled` says which. `false` is the
99
+ * finger lifting. `true` is the system taking the press away — a competing
100
+ * gesture won, the app went to the background, or the finger moved past
101
+ * `maxDistance` while still down. Read it before you treat the press as
102
+ * completed: a hold-to-record affordance stops the recording on both paths
103
+ * but keeps the take only on the first.
104
+ *
105
+ * `duration` carries the whole hold, which is what that affordance stops
106
+ * on.
107
+ */
108
+ onLongPressEnd?: (event: LongPressEvent, info: IntentEndInfo) => void;
109
+ /**
110
+ * The finger went down and the gesture is now a candidate. **This is a
111
+ * worklet** — mark it with the `'worklet'` directive, and do not touch
112
+ * React state from it.
113
+ *
114
+ * It fires on every touch, including the ordinary taps that will never
115
+ * become a long press. Use it to show a candidate state, and undo that
116
+ * state in `onFinalize`.
117
+ */
118
+ onBegin?: (event: LongPressEvent) => void;
119
+ /**
120
+ * The gesture is over, whether it was recognized or not. **This is a
121
+ * worklet.**
122
+ *
123
+ * `success` is `true` when the press was recognized and ended normally.
124
+ * This is the right place to clear anything `onBegin` set, because it runs
125
+ * on both paths.
126
+ */
127
+ onFinalize?: (event: LongPressEvent, success: boolean) => void;
128
+ }
129
+ /**
130
+ * What {@link useLongPress} returns.
131
+ *
132
+ * An alias rather than an extending interface, because a long press produces
133
+ * no continuous value of its own — the duration is in the payload, not in a
134
+ * shared value, because nothing on the UI thread can read a clock per frame
135
+ * without Impulse starting an animation to drive it.
136
+ */
137
+ type UseLongPressResult = IntentResult<LongPressGesture>;
138
+ /**
139
+ * Recognize a press held past a duration.
140
+ *
141
+ * ```tsx
142
+ * const hold = useLongPress({ minDuration: 400, onLongPress: openMenu })
143
+ *
144
+ * return (
145
+ * <GestureDetector gesture={hold.gesture}>
146
+ * <View />
147
+ * </GestureDetector>
148
+ * )
149
+ * ```
150
+ *
151
+ * `onLongPress` runs on the JS thread and may set React state directly, and
152
+ * it fires **while the finger is still down** — that is the moment a context
153
+ * menu should open and a haptic should fire. `onLongPressEnd` runs on the JS
154
+ * thread too, when the finger lifts. `onBegin` and `onFinalize` are worklets
155
+ * and run on the UI thread — the name states the thread, so there is nothing
156
+ * to configure and no `scheduleOnRN` to write.
157
+ *
158
+ * `isActive` is a shared value that is `true` from the moment the press is
159
+ * recognized until the finger lifts — **not** from the moment the finger goes
160
+ * down. It is the held state, not a pressed state: every ordinary tap on the
161
+ * view would set a finger-down flag, and almost none of them are this
162
+ * gesture. Drive a pressed state from a `useTap` instead, and use this for
163
+ * whatever should show only while the press is genuinely being held.
164
+ *
165
+ * **Pairing with a tap.** A tap and a long press on one view race, and the
166
+ * race resolves itself: a tap that is held too long fails, and a press
167
+ * released too early never activates.
168
+ *
169
+ * ```tsx
170
+ * useGestures([useLongPress({ onLongPress: openMenu }), tap], { mode: 'race' })
171
+ * ```
172
+ *
173
+ * **Hold, then drag.** RNGH has no "activate after this one activates"
174
+ * relation, so this is two gestures and a gate rather than one option. Run
175
+ * them `alongside` each other and let the drag read a flag the press sets:
176
+ *
177
+ * ```tsx
178
+ * const hold = useLongPress({ alongside: dragRef })
179
+ * const drag = useDrag({
180
+ * alongside: hold.ref,
181
+ * onUpdate: () => {
182
+ * 'worklet'
183
+ * // ignore the movement until the press has been held
184
+ * },
185
+ * })
186
+ * ```
187
+ *
188
+ * `hold.isActive` is the flag, and it is a shared value precisely so the
189
+ * drag's worklets can read it without a round trip to the JS thread. Raise
190
+ * the drag's `threshold` as well, or the drag activates before the press ever
191
+ * does — and raise this hook's `maxDistance`, or the press is cancelled by
192
+ * the drag's own movement on web.
193
+ *
194
+ * **Activation criteria.** `minDuration` defaults to 500ms and `maxDistance`
195
+ * to 10 points, and both are RNGH's own numbers restated. What `maxDistance`
196
+ * bounds differs by platform: RNGH documents it as bounding the wait only,
197
+ * but its web implementation cancels a press that is already active once the
198
+ * finger travels past it. Raise it explicitly rather than relying on travel
199
+ * being free after recognition.
200
+ *
201
+ * **Web.** RNGH recognizes long press from pointer events, so a held mouse
202
+ * button works. The browser's own context menu is not suppressed by this
203
+ * hook, so a long press with the right button — or a held touch on some
204
+ * browsers — can open both. Suppress it on the view if that matters.
205
+ *
206
+ * **Accessibility.** A long press is invisible to a screen reader and
207
+ * unreachable from a keyboard, and this hook does not fix that. It has the
208
+ * best fallback of any gesture in the library, so use it: React Native's
209
+ * `<Pressable>` takes `onLongPress` directly and is reachable by every
210
+ * assistive technology, and `accessibilityActions` with
211
+ * `onAccessibilityAction` names the same action explicitly. A long-press-only
212
+ * affordance is a bug, not a trade-off.
213
+ *
214
+ * @param options - Activation criteria, callbacks, and the `alongside` /
215
+ * `blocks` / `deferTo` coexistence options every Impulse hook accepts.
216
+ */
217
+ declare function useLongPress(options?: UseLongPressOptions): UseLongPressResult;
218
+
219
+ export { type LongPressEvent, type UseLongPressOptions, type UseLongPressResult, useLongPress };