@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.
- package/CHANGELOG.md +51 -5
- package/README.md +206 -11
- package/dist/{chunk-5BMRKYVY.js → chunk-2UZTAWUQ.js} +20 -2
- package/dist/chunk-BNSFDNLA.js +75 -0
- package/dist/chunk-DXXGWG4Q.js +136 -0
- package/dist/{chunk-PMR25UCT.js → chunk-HGBCIL6X.js} +1 -1
- package/dist/chunk-HI5PHDJY.js +94 -0
- package/dist/{chunk-IG5RXCYR.js → chunk-IH7SQ5X6.js} +11 -15
- package/dist/chunk-MWCIEVTA.js +199 -0
- package/dist/{chunk-F4RHM4ZK.js → chunk-NYDDZD4G.js} +58 -1
- package/dist/chunk-PGOQSKEJ.js +144 -0
- package/dist/{chunk-FR242SUF.js → chunk-TGOGIZDH.js} +13 -7
- package/dist/chunk-VEPUHGPN.js +12 -0
- package/dist/chunk-YZHAQ4XK.js +136 -0
- package/dist/compose/index.d.ts +1 -1
- package/dist/double-tap/index.d.ts +184 -0
- package/dist/double-tap/index.js +5 -0
- package/dist/drag/index.d.ts +18 -13
- package/dist/drag/index.js +3 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +12 -5
- package/dist/long-press/index.d.ts +219 -0
- package/dist/long-press/index.js +4 -0
- package/dist/pan/index.d.ts +221 -0
- package/dist/pan/index.js +4 -0
- package/dist/pinch/index.d.ts +239 -0
- package/dist/pinch/index.js +4 -0
- package/dist/raw/index.d.ts +3 -3
- package/dist/raw/index.js +2 -2
- package/dist/rotate/index.d.ts +263 -0
- package/dist/rotate/index.js +4 -0
- package/dist/swipe/index.d.ts +254 -0
- package/dist/swipe/index.js +4 -0
- package/dist/tap/index.d.ts +34 -29
- package/dist/tap/index.js +4 -3
- package/dist/tapEvent-KSSojt_l.d.ts +28 -0
- package/dist/{types-Ch2HM3aP.d.ts → types-ChGKY28a.d.ts} +27 -1
- package/dist/{useGestureMemo-Ccv8rB0C.d.ts → useGestureMemo-BRW1EKcJ.d.ts} +1 -1
- package/jest-setup.cjs +32 -1
- package/llms.txt +155 -0
- package/package.json +35 -2
- package/src/index.ts +50 -4
- package/src/intents/double-tap/index.ts +6 -0
- package/src/intents/long-press/index.ts +6 -0
- package/src/intents/pan/index.ts +2 -0
- package/src/intents/pinch/index.ts +2 -0
- package/src/intents/rotate/index.ts +6 -0
- package/src/intents/swipe/index.ts +8 -0
- package/src/intents/tapEvent.ts +50 -0
- package/src/intents/useDoubleTap.ts +321 -0
- package/src/intents/useDrag.ts +49 -28
- package/src/intents/useLongPress.ts +391 -0
- package/src/intents/usePan.ts +444 -0
- package/src/intents/usePinch.ts +483 -0
- package/src/intents/useRotate.ts +507 -0
- package/src/intents/useSwipe.ts +585 -0
- package/src/intents/useTap.ts +51 -60
- package/src/internal/intentResult.ts +67 -0
- package/src/internal/phaseCallbacks.ts +51 -0
- package/src/internal/useGestureMemo.ts +32 -4
- package/src/internal/useLatestCallback.ts +2 -2
- package/src/raw/useRawGesture.ts +1 -1
- package/src/relations/index.ts +94 -0
- 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 };
|
package/dist/compose/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComposedGesture } from 'react-native-gesture-handler';
|
|
2
|
-
import { A as AttachableGesture, a as ComposeMode } from '../types-
|
|
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 };
|
package/dist/drag/index.d.ts
CHANGED
|
@@ -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-
|
|
4
|
-
import { P as Point, H as HitSlop, I as IntentResult } from '../types-
|
|
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 `
|
|
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
|
|
158
|
+
* The drag is over. **Runs on the JS thread.**
|
|
159
159
|
*
|
|
160
|
-
* Fires only for a drag that activated
|
|
161
|
-
*
|
|
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
|
-
*
|
|
166
|
-
*
|
|
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 `
|
|
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
|
|
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
|
package/dist/drag/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { useDrag } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import '../chunk-
|
|
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 {
|
|
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 {
|
|
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-
|
|
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-
|
|
3
|
-
export { useTap } from './chunk-
|
|
4
|
-
export {
|
|
5
|
-
import './chunk-
|
|
6
|
-
|
|
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 };
|