@rootnative/impulse 0.0.0-alpha.0
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 +48 -0
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/chunk-5BMRKYVY.js +39 -0
- package/dist/chunk-F4RHM4ZK.js +77 -0
- package/dist/chunk-FR242SUF.js +174 -0
- package/dist/chunk-IG5RXCYR.js +74 -0
- package/dist/chunk-LM645QQT.js +37 -0
- package/dist/chunk-PMR25UCT.js +8 -0
- package/dist/chunk-ZX7WNICB.js +39 -0
- package/dist/compose/index.d.ts +80 -0
- package/dist/compose/index.js +2 -0
- package/dist/drag/index.d.ts +277 -0
- package/dist/drag/index.js +4 -0
- package/dist/gesture-handler/index.d.ts +1 -0
- package/dist/gesture-handler/index.js +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/raw/index.d.ts +63 -0
- package/dist/raw/index.js +3 -0
- package/dist/tap/index.d.ts +150 -0
- package/dist/tap/index.js +4 -0
- package/dist/types-Ch2HM3aP.d.ts +142 -0
- package/dist/useGestureMemo-Ccv8rB0C.d.ts +38 -0
- package/jest-preset.cjs +60 -0
- package/jest-setup.cjs +56 -0
- package/package.json +115 -0
- package/src/compose/index.ts +7 -0
- package/src/compose/useGestures.ts +136 -0
- package/src/gesture-handler/index.ts +18 -0
- package/src/index.ts +53 -0
- package/src/intents/drag/index.ts +8 -0
- package/src/intents/tap/index.ts +2 -0
- package/src/intents/useDrag.ts +563 -0
- package/src/intents/useTap.ts +285 -0
- package/src/internal/useGestureMemo.ts +110 -0
- package/src/internal/useLatestCallback.ts +64 -0
- package/src/internal/useStableList.ts +62 -0
- package/src/internal/useStableRecord.ts +72 -0
- package/src/internal/warnOnce.ts +60 -0
- package/src/raw/index.ts +2 -0
- package/src/raw/useRawGesture.ts +71 -0
- package/src/relations/index.ts +120 -0
- package/src/types.ts +187 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/internal/warnOnce.ts
|
|
4
|
+
var warned = /* @__PURE__ */ new Set();
|
|
5
|
+
function isDevBuild() {
|
|
6
|
+
return typeof __DEV__ !== "undefined" && __DEV__;
|
|
7
|
+
}
|
|
8
|
+
function warnOnce(key, message) {
|
|
9
|
+
if (!isDevBuild() || warned.has(key)) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
warned.add(key);
|
|
13
|
+
console.warn(`[impulse] ${message}`);
|
|
14
|
+
}
|
|
15
|
+
var EMPTY = [];
|
|
16
|
+
function sameContents(a, b) {
|
|
17
|
+
if (a === b) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (a.length !== b.length) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
24
|
+
if (!Object.is(a[index], b[index])) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function useStableList(value) {
|
|
31
|
+
const next = value === void 0 ? EMPTY : Array.isArray(value) ? value : [value];
|
|
32
|
+
const held = useRef(EMPTY);
|
|
33
|
+
if (!sameContents(held.current, next)) {
|
|
34
|
+
held.current = next;
|
|
35
|
+
}
|
|
36
|
+
return held.current;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { isDevBuild, useStableList, warnOnce };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ComposedGesture } from 'react-native-gesture-handler';
|
|
2
|
+
import { A as AttachableGesture, a as ComposeMode } from '../types-Ch2HM3aP.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
import 'react-native-reanimated';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Anything carrying a gesture — every Impulse hook result, including the one
|
|
8
|
+
* `useGestures` itself returns.
|
|
9
|
+
*/
|
|
10
|
+
interface GestureCarrier {
|
|
11
|
+
readonly gesture: AttachableGesture;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A member of a composition: a hook result, or a bare gesture from
|
|
15
|
+
* `@rootnative/impulse/gesture-handler`.
|
|
16
|
+
*/
|
|
17
|
+
type GestureMember = GestureCarrier | AttachableGesture;
|
|
18
|
+
/** What `useGestures` returns. It is itself a valid composition member. */
|
|
19
|
+
interface GesturesResult {
|
|
20
|
+
/** The composed gesture. Hand it to `<GestureDetector>`. */
|
|
21
|
+
readonly gesture: ComposedGesture;
|
|
22
|
+
}
|
|
23
|
+
/** Options for {@link useGestures}. */
|
|
24
|
+
interface UseGesturesOptions {
|
|
25
|
+
/** How the members relate to one another. */
|
|
26
|
+
mode: ComposeMode;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compose gestures under one relation.
|
|
30
|
+
*
|
|
31
|
+
* ```tsx
|
|
32
|
+
* // tap and double-tap race; the winner runs alongside the drag
|
|
33
|
+
* const { gesture } = useGestures(
|
|
34
|
+
* [useGestures([tap, double], { mode: 'race' }), drag],
|
|
35
|
+
* { mode: 'simultaneous' },
|
|
36
|
+
* )
|
|
37
|
+
*
|
|
38
|
+
* return (
|
|
39
|
+
* <GestureDetector gesture={gesture}>
|
|
40
|
+
* <View />
|
|
41
|
+
* </GestureDetector>
|
|
42
|
+
* )
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* Composition is **data, not nesting**. RNGH expresses the same thing as
|
|
46
|
+
* `Gesture.Simultaneous(Gesture.Race(tap, doubleTap), drag)`, which has to be
|
|
47
|
+
* read backwards to learn what wins and gains a level of nesting per
|
|
48
|
+
* relation. Here each call is one flat list plus the relation that holds over
|
|
49
|
+
* it, and the result is itself a member, so precedence reads left to right
|
|
50
|
+
* and depth is a choice rather than a consequence.
|
|
51
|
+
*
|
|
52
|
+
* The modes:
|
|
53
|
+
*
|
|
54
|
+
* - `race` — the first member to activate wins and cancels the rest. This is
|
|
55
|
+
* what you want for mutually exclusive readings of one touch.
|
|
56
|
+
* - `simultaneous` — every member recognizes independently. A pinch and a
|
|
57
|
+
* rotate on one image.
|
|
58
|
+
* - `exclusive` — members are tried in order, and a later one activates only
|
|
59
|
+
* after every earlier one has failed. A tap and a double-tap, where the tap
|
|
60
|
+
* must wait to learn whether a second one is coming.
|
|
61
|
+
*
|
|
62
|
+
* **Coexistence options are not accepted here, and that is a limitation
|
|
63
|
+
* rather than a choice.** RNGH's three external-gesture relations are methods
|
|
64
|
+
* on a single gesture; a composed gesture does not have them. Put
|
|
65
|
+
* `alongside` / `blocks` / `deferTo` on the member hooks instead — Impulse
|
|
66
|
+
* cannot apply them for you without mutating gestures another hook owns and
|
|
67
|
+
* memoised.
|
|
68
|
+
*
|
|
69
|
+
* **Accessibility.** A composition is as reachable as its members, which is
|
|
70
|
+
* to say a screen reader and a keyboard see none of it. Each member hook
|
|
71
|
+
* documents its own fallback; a composition needs the union of them.
|
|
72
|
+
*
|
|
73
|
+
* @param members - The gestures to compose, in precedence order. The order
|
|
74
|
+
* matters for `exclusive` and is ignored by the other two modes.
|
|
75
|
+
* @param options - The relation to compose under.
|
|
76
|
+
* @returns The composed gesture, stable while the members and the mode are.
|
|
77
|
+
*/
|
|
78
|
+
declare function useGestures(members: readonly GestureMember[], options: UseGesturesOptions): GesturesResult;
|
|
79
|
+
|
|
80
|
+
export { type GestureCarrier, type GestureMember, type GesturesResult, type UseGesturesOptions, useGestures };
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { PanGesture } from 'react-native-gesture-handler';
|
|
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';
|
|
5
|
+
import 'react';
|
|
6
|
+
|
|
7
|
+
/** Which way the drag is allowed to move. */
|
|
8
|
+
type DragAxis = 'x' | 'y' | 'both';
|
|
9
|
+
/**
|
|
10
|
+
* How far the drag may travel, in the same coordinates as `x` and `y`.
|
|
11
|
+
*
|
|
12
|
+
* Every edge is optional, and an omitted edge is unbounded. The names are the
|
|
13
|
+
* edges of the travel, not of the view: `left` is the smallest `x`, `bottom`
|
|
14
|
+
* is the largest `y`.
|
|
15
|
+
*/
|
|
16
|
+
interface DragBounds {
|
|
17
|
+
/** Smallest `x`. */
|
|
18
|
+
left?: number;
|
|
19
|
+
/** Largest `x`. */
|
|
20
|
+
right?: number;
|
|
21
|
+
/** Smallest `y`. */
|
|
22
|
+
top?: number;
|
|
23
|
+
/** Largest `y`. */
|
|
24
|
+
bottom?: number;
|
|
25
|
+
}
|
|
26
|
+
/** The intent-shaped payload a {@link useDrag} callback receives. */
|
|
27
|
+
interface DragEvent {
|
|
28
|
+
/**
|
|
29
|
+
* Where the drag is now — the same numbers as `x` and `y`, after `bounds`
|
|
30
|
+
* and `elastic` were applied.
|
|
31
|
+
*
|
|
32
|
+
* This accumulates across gestures. A second drag continues from where the
|
|
33
|
+
* first one stopped, which is why `bounds` can be written against a layout
|
|
34
|
+
* rather than against one gesture's travel.
|
|
35
|
+
*/
|
|
36
|
+
readonly position: Point;
|
|
37
|
+
/**
|
|
38
|
+
* How far the finger moved since this gesture activated, raw.
|
|
39
|
+
*
|
|
40
|
+
* Untouched by `bounds` and `elastic`, and reset to zero at the start of
|
|
41
|
+
* every gesture. Read `position` for where the thing being dragged actually
|
|
42
|
+
* sits.
|
|
43
|
+
*/
|
|
44
|
+
readonly translation: Point;
|
|
45
|
+
/** Finger speed, in points per second. The input a release spring needs. */
|
|
46
|
+
readonly velocity: Point;
|
|
47
|
+
/** The touch point relative to the window. */
|
|
48
|
+
readonly absolute: Point;
|
|
49
|
+
/**
|
|
50
|
+
* The nearest point inside `bounds`. Equal to `position` whenever the drag
|
|
51
|
+
* is in bounds, which with the default `elastic` of `0` is always.
|
|
52
|
+
*
|
|
53
|
+
* With `elastic` set, the finger can pull the value past an edge and
|
|
54
|
+
* Impulse leaves it there on release — moving it back is an animation, and
|
|
55
|
+
* Impulse owns no animation vocabulary. This field is the destination that
|
|
56
|
+
* animation needs, so the consumer does not have to re-derive the clamp
|
|
57
|
+
* from bounds it already handed over.
|
|
58
|
+
*/
|
|
59
|
+
readonly settled: Point;
|
|
60
|
+
/** How many fingers are down. */
|
|
61
|
+
readonly pointers: number;
|
|
62
|
+
}
|
|
63
|
+
/** Options for {@link useDrag}. */
|
|
64
|
+
interface UseDragOptions extends GestureMemoOptions {
|
|
65
|
+
/**
|
|
66
|
+
* Which way the drag may move. Default `'both'`.
|
|
67
|
+
*
|
|
68
|
+
* The locked axis's shared value never changes — `axis: 'x'` leaves `y` at
|
|
69
|
+
* its initial value for the life of the hook. The axis also decides the
|
|
70
|
+
* activation criterion, which is the part that matters inside a scroll
|
|
71
|
+
* view: see `threshold`.
|
|
72
|
+
*/
|
|
73
|
+
axis?: DragAxis;
|
|
74
|
+
/**
|
|
75
|
+
* How far the finger must travel before the drag activates, in points.
|
|
76
|
+
* Default `10`.
|
|
77
|
+
*
|
|
78
|
+
* On a single axis this is a directional threshold, so a drag with
|
|
79
|
+
* `axis: 'x'` ignores vertical movement entirely and a vertical scroll view
|
|
80
|
+
* under it keeps working. On `'both'` it is a radial distance.
|
|
81
|
+
*
|
|
82
|
+
* Lower it for a drag that must feel immediate and owns its view. Raise it
|
|
83
|
+
* when the drag shares the view with something that should usually win.
|
|
84
|
+
*/
|
|
85
|
+
threshold?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Movement across the axis that makes the drag fail, in points. Unset by
|
|
88
|
+
* default, which leaves RNGH's own behaviour in place.
|
|
89
|
+
*
|
|
90
|
+
* `threshold` decides when the drag wins; this decides when it gives up.
|
|
91
|
+
* Set it when the cross-axis gesture must win a diagonal — a horizontal
|
|
92
|
+
* row action inside a vertical list, where a mostly-vertical drag should
|
|
93
|
+
* scroll rather than half-open the row. Ignored when `axis` is `'both'`,
|
|
94
|
+
* which has no cross axis.
|
|
95
|
+
*/
|
|
96
|
+
failOffset?: number;
|
|
97
|
+
/**
|
|
98
|
+
* How far the drag may travel. Unbounded by default.
|
|
99
|
+
*
|
|
100
|
+
* Written inline as an object is fine — the gesture is not rebuilt when the
|
|
101
|
+
* contents are unchanged.
|
|
102
|
+
*/
|
|
103
|
+
bounds?: DragBounds;
|
|
104
|
+
/**
|
|
105
|
+
* How much of the finger's movement survives past a bound, `0` to `1`.
|
|
106
|
+
* Default `0`.
|
|
107
|
+
*
|
|
108
|
+
* `0` clamps hard at the edge. `1` ignores the bound while the finger is
|
|
109
|
+
* down. `0.3` or so gives the rubber-band pull an over-scroll has.
|
|
110
|
+
*
|
|
111
|
+
* **Impulse does not put the value back on release.** That is an animation,
|
|
112
|
+
* and Principle 5 keeps animation out of this library. `onDragEnd` carries
|
|
113
|
+
* `settled` — the point to animate to — and `@rootnative/inertia` or a
|
|
114
|
+
* plain `withSpring` does the moving.
|
|
115
|
+
*/
|
|
116
|
+
elastic?: number;
|
|
117
|
+
/**
|
|
118
|
+
* Where `x` and `y` start. Default `{ x: 0, y: 0 }`.
|
|
119
|
+
*
|
|
120
|
+
* Read once, when the hook mounts. Changing it later does nothing, because
|
|
121
|
+
* the shared values are the drag's state from then on — write
|
|
122
|
+
* `drag.x.value` to move it instead.
|
|
123
|
+
*/
|
|
124
|
+
initial?: Point;
|
|
125
|
+
/**
|
|
126
|
+
* How many fingers must be on the view. Unset by default, which leaves
|
|
127
|
+
* RNGH's range of one to ten in place.
|
|
128
|
+
*
|
|
129
|
+
* Setting it fixes the count exactly, so `pointers: 2` is a two-finger drag
|
|
130
|
+
* that neither starts with one finger nor survives a third.
|
|
131
|
+
*/
|
|
132
|
+
pointers?: number;
|
|
133
|
+
/**
|
|
134
|
+
* Extra touchable area around the view, in points.
|
|
135
|
+
*
|
|
136
|
+
* Written inline as an object is fine — the gesture is not rebuilt when the
|
|
137
|
+
* contents are unchanged.
|
|
138
|
+
*/
|
|
139
|
+
hitSlop?: HitSlop;
|
|
140
|
+
/**
|
|
141
|
+
* Whether the gesture is recognized at all. Default `true`.
|
|
142
|
+
*
|
|
143
|
+
* Prefer this over unmounting the `<GestureDetector>`: a disabled gesture
|
|
144
|
+
* keeps its identity and its relations, so re-enabling it does not
|
|
145
|
+
* re-attach anything.
|
|
146
|
+
*/
|
|
147
|
+
enabled?: boolean;
|
|
148
|
+
/**
|
|
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
|
|
151
|
+
* function and may touch React state.
|
|
152
|
+
*
|
|
153
|
+
* This is the first moment the drag has definitely won. `onBegin` fires
|
|
154
|
+
* earlier and promises nothing.
|
|
155
|
+
*/
|
|
156
|
+
onDragStart?: (event: DragEvent) => void;
|
|
157
|
+
/**
|
|
158
|
+
* The finger lifted and the drag is over. **Runs on the JS thread.**
|
|
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.
|
|
164
|
+
*
|
|
165
|
+
* Read `velocity` to seed that release animation, and `settled` for where
|
|
166
|
+
* an elastic overshoot should return to.
|
|
167
|
+
*/
|
|
168
|
+
onDragEnd?: (event: DragEvent) => void;
|
|
169
|
+
/**
|
|
170
|
+
* The finger went down and the gesture is now a candidate. **This is a
|
|
171
|
+
* worklet** — mark it with the `'worklet'` directive, and do not touch
|
|
172
|
+
* React state from it.
|
|
173
|
+
*
|
|
174
|
+
* Being a candidate is not the same as winning: the drag has not passed
|
|
175
|
+
* `threshold` yet and may never. Use it to show a grabbed state, and undo
|
|
176
|
+
* that state in `onFinalize`.
|
|
177
|
+
*/
|
|
178
|
+
onBegin?: (event: DragEvent) => void;
|
|
179
|
+
/**
|
|
180
|
+
* The drag moved. **This is a worklet**, and it runs on every frame the
|
|
181
|
+
* finger moves.
|
|
182
|
+
*
|
|
183
|
+
* There is no JS-thread counterpart on purpose. A per-frame `runOnJS` is a
|
|
184
|
+
* scheduling cost paid sixty times a second for a value that is already on
|
|
185
|
+
* the thread that needs it — read `x` and `y` from a `useAnimatedStyle`
|
|
186
|
+
* instead, and let this callback handle what the style cannot.
|
|
187
|
+
*/
|
|
188
|
+
onUpdate?: (event: DragEvent) => void;
|
|
189
|
+
/**
|
|
190
|
+
* The gesture is over, whether it activated or not. **This is a worklet.**
|
|
191
|
+
*
|
|
192
|
+
* `success` is `true` when the drag activated and ended normally. This is
|
|
193
|
+
* the right place to clear anything `onBegin` set, because it runs on both
|
|
194
|
+
* paths.
|
|
195
|
+
*/
|
|
196
|
+
onFinalize?: (event: DragEvent, success: boolean) => void;
|
|
197
|
+
}
|
|
198
|
+
/** What {@link useDrag} returns. */
|
|
199
|
+
interface UseDragResult extends IntentResult<PanGesture> {
|
|
200
|
+
/**
|
|
201
|
+
* Where the drag is along the x axis, after `bounds` and `elastic`.
|
|
202
|
+
*
|
|
203
|
+
* Writable: assigning `drag.x.value` moves the drag, and the next gesture
|
|
204
|
+
* continues from the new number rather than snapping back. That is how a
|
|
205
|
+
* release animation hands control back — animate this value, and the drag
|
|
206
|
+
* picks up wherever the animation left it.
|
|
207
|
+
*
|
|
208
|
+
* Frozen at its initial value when `axis` is `'y'`.
|
|
209
|
+
*/
|
|
210
|
+
readonly x: SharedValue<number>;
|
|
211
|
+
/**
|
|
212
|
+
* Where the drag is along the y axis, after `bounds` and `elastic`.
|
|
213
|
+
*
|
|
214
|
+
* Writable, on the same terms as `x`. Frozen at its initial value when
|
|
215
|
+
* `axis` is `'x'`.
|
|
216
|
+
*/
|
|
217
|
+
readonly y: SharedValue<number>;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Recognize a drag, and stream where it is.
|
|
221
|
+
*
|
|
222
|
+
* ```tsx
|
|
223
|
+
* const drag = useDrag({ axis: 'x', bounds: { left: -120, right: 0 } })
|
|
224
|
+
* const style = useAnimatedStyle(() => ({
|
|
225
|
+
* transform: [{ translateX: drag.x.value }],
|
|
226
|
+
* }))
|
|
227
|
+
*
|
|
228
|
+
* return (
|
|
229
|
+
* <GestureDetector gesture={drag.gesture}>
|
|
230
|
+
* <Animated.View style={style} />
|
|
231
|
+
* </GestureDetector>
|
|
232
|
+
* )
|
|
233
|
+
* ```
|
|
234
|
+
*
|
|
235
|
+
* `x` and `y` are shared values, so the view follows the finger on the UI
|
|
236
|
+
* thread with no re-render. They accumulate across gestures: a second drag
|
|
237
|
+
* continues from where the first stopped.
|
|
238
|
+
*
|
|
239
|
+
* **No style, and no animation.** This hook returns numbers and stops there.
|
|
240
|
+
* Building the `transform` is the consumer's call, and so is any spring back.
|
|
241
|
+
* `@rootnative/inertia-gestures` has a `useDrag` that does both, and needs
|
|
242
|
+
* `@rootnative/inertia` to do it — reach for that one when a `Motion.View`
|
|
243
|
+
* should follow a finger and spring home, and for this one when the values
|
|
244
|
+
* are what you want.
|
|
245
|
+
*
|
|
246
|
+
* **Activation criteria.** `threshold` defaults to 10 points. With
|
|
247
|
+
* `axis: 'x'` or `'y'` it is directional, so a horizontal drag inside a
|
|
248
|
+
* vertical `ScrollView` leaves the scroll alone until the finger commits
|
|
249
|
+
* sideways; with `'both'` it is a radial distance. Set `failOffset` as well
|
|
250
|
+
* 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.
|
|
252
|
+
*
|
|
253
|
+
* **Coexistence.** A threshold decides who moves first; it does not decide
|
|
254
|
+
* who wins a contested touch. For a drag inside a scroll view, say which one
|
|
255
|
+
* the touch belongs to as well — `deferTo: scrollRef` for a drag that is the
|
|
256
|
+
* fallback, `blocks: listRef` for one that is the foreground affordance.
|
|
257
|
+
*
|
|
258
|
+
* **Web.** RNGH recognizes pan from pointer events, so a mouse drag behaves
|
|
259
|
+
* the same as a touch drag and `velocity` is reported in the same units. A
|
|
260
|
+
* trackpad's momentum scroll is not a pan and never reaches this hook.
|
|
261
|
+
* `pointers` above 1 is unreliable on web.
|
|
262
|
+
*
|
|
263
|
+
* **Accessibility.** A drag is invisible to a screen reader and unreachable
|
|
264
|
+
* from a keyboard, and this hook does not fix that. Whatever the drag
|
|
265
|
+
* adjusts must be reachable another way: a pair of buttons for a slider, a
|
|
266
|
+
* visible action for a swipeable row, or `accessibilityActions` with
|
|
267
|
+
* `onAccessibilityAction` — `increment` and `decrement` for a value,
|
|
268
|
+
* `magicTap` or a named action for a dismissal. A drag-only affordance is a
|
|
269
|
+
* bug, not a trade-off.
|
|
270
|
+
*
|
|
271
|
+
* @param options - Activation criteria, bounds, callbacks, and the
|
|
272
|
+
* `alongside` / `blocks` / `deferTo` coexistence options every Impulse hook
|
|
273
|
+
* accepts.
|
|
274
|
+
*/
|
|
275
|
+
declare function useDrag(options?: UseDragOptions): UseDragResult;
|
|
276
|
+
|
|
277
|
+
export { type DragAxis, type DragBounds, type DragEvent, type UseDragOptions, type UseDragResult, useDrag };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'react-native-gesture-handler';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'react-native-gesture-handler';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { GestureDetector } from 'react-native-gesture-handler';
|
|
2
|
+
export { GestureCarrier, GestureMember, GesturesResult, UseGesturesOptions, useGestures } from './compose/index.js';
|
|
3
|
+
export { RawGestureResult, UseRawGestureOptions, useRawGesture } from './raw/index.js';
|
|
4
|
+
export { TapEvent, UseTapOptions, UseTapResult, useTap } from './tap/index.js';
|
|
5
|
+
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';
|
|
7
|
+
import 'react';
|
|
8
|
+
import './useGestureMemo-Ccv8rB0C.js';
|
|
9
|
+
import 'react-native-reanimated';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
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';
|
|
7
|
+
import './chunk-ZX7WNICB.js';
|
|
8
|
+
export { GestureDetector } from 'react-native-gesture-handler';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DependencyList } from 'react';
|
|
2
|
+
import { GestureType } from 'react-native-gesture-handler';
|
|
3
|
+
import { B as BuiltGesture, G as GestureMemoOptions } from '../useGestureMemo-Ccv8rB0C.js';
|
|
4
|
+
import '../types-Ch2HM3aP.js';
|
|
5
|
+
import 'react-native-reanimated';
|
|
6
|
+
|
|
7
|
+
/** Options for {@link useRawGesture}. */
|
|
8
|
+
type UseRawGestureOptions = GestureMemoOptions;
|
|
9
|
+
/** What {@link useRawGesture} returns. */
|
|
10
|
+
type RawGestureResult<G extends GestureType> = BuiltGesture<G>;
|
|
11
|
+
/**
|
|
12
|
+
* Build an RNGH gesture by hand, with Impulse's memoisation and coexistence
|
|
13
|
+
* handling applied to it.
|
|
14
|
+
*
|
|
15
|
+
* The mechanism-level escape hatch. Impulse's intent hooks cover the common
|
|
16
|
+
* cases; this covers a recognizer they do not model, or a configuration they
|
|
17
|
+
* do not expose, without giving up the two things that are tedious to get
|
|
18
|
+
* right by hand:
|
|
19
|
+
*
|
|
20
|
+
* ```tsx
|
|
21
|
+
* const fling = useRawGesture(
|
|
22
|
+
* () => Gesture.Fling().direction(Directions.RIGHT).onEnd(onFling),
|
|
23
|
+
* [onFling],
|
|
24
|
+
* { deferTo: scrollRef },
|
|
25
|
+
* )
|
|
26
|
+
*
|
|
27
|
+
* <GestureDetector gesture={fling.gesture}>…</GestureDetector>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* What you still own here, because you are building the gesture yourself:
|
|
31
|
+
*
|
|
32
|
+
* - **The dependency list.** `build` runs only when `deps` change, and a
|
|
33
|
+
* stale capture is a stale gesture. A worklet callback belongs in `deps`,
|
|
34
|
+
* because a worklet is captured as written. A JS-thread callback does not —
|
|
35
|
+
* put it through `useLatestCallback` and depend on the stable result.
|
|
36
|
+
* - **The thread.** RNGH decides per callback, by whether it carries the
|
|
37
|
+
* `'worklet'` directive, and warns in development when a gesture mixes the
|
|
38
|
+
* two. Impulse does not insert a `runOnJS` boundary for you here; that is
|
|
39
|
+
* something the intent hooks do because they know what each callback means.
|
|
40
|
+
* - **The payload.** You get RNGH's flat event, not an intent-shaped one.
|
|
41
|
+
*
|
|
42
|
+
* What Impulse still owns:
|
|
43
|
+
*
|
|
44
|
+
* - Gesture identity across renders, so an inline option cannot re-attach a
|
|
45
|
+
* gesture mid-drag.
|
|
46
|
+
* - `alongside` / `blocks` / `deferTo` resolution, so the three RNGH
|
|
47
|
+
* relations are chosen by outcome rather than by method name.
|
|
48
|
+
* - A `ref` other hooks can name in their own coexistence options.
|
|
49
|
+
*
|
|
50
|
+
* **Accessibility.** Nothing here is reachable by a screen reader or a
|
|
51
|
+
* keyboard, and Impulse cannot name a fallback for a gesture it did not
|
|
52
|
+
* design. Whatever this gesture does must also be doable another way — an
|
|
53
|
+
* `accessibilityActions` entry, or a visible control.
|
|
54
|
+
*
|
|
55
|
+
* @param build - Constructs the gesture. Do not call the relation methods
|
|
56
|
+
* here; pass `alongside` / `blocks` / `deferTo` in `options` instead, so
|
|
57
|
+
* the three-way choice stays in one place and is applied exactly once.
|
|
58
|
+
* @param deps - What the gesture depends on, in `useMemo` terms.
|
|
59
|
+
* @param options - Coexistence options and `testId`.
|
|
60
|
+
*/
|
|
61
|
+
declare function useRawGesture<G extends GestureType>(build: () => G, deps: DependencyList, options?: UseRawGestureOptions): RawGestureResult<G>;
|
|
62
|
+
|
|
63
|
+
export { type RawGestureResult, type UseRawGestureOptions, useRawGesture };
|
|
@@ -0,0 +1,150 @@
|
|
|
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';
|
|
4
|
+
import 'react';
|
|
5
|
+
import 'react-native-reanimated';
|
|
6
|
+
|
|
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
|
+
/** Options for {@link useTap}. */
|
|
24
|
+
interface UseTapOptions extends GestureMemoOptions {
|
|
25
|
+
/**
|
|
26
|
+
* How many fingers must be down. Default `1`.
|
|
27
|
+
*
|
|
28
|
+
* A two-finger tap is a common "undo" or "zoom out" affordance, and it is
|
|
29
|
+
* the same intent with a different pointer count rather than a separate
|
|
30
|
+
* one.
|
|
31
|
+
*/
|
|
32
|
+
pointers?: number;
|
|
33
|
+
/**
|
|
34
|
+
* How long the finger may stay down, in milliseconds. Default `500`.
|
|
35
|
+
*
|
|
36
|
+
* Past this the gesture fails rather than firing, which is what leaves the
|
|
37
|
+
* touch available to a `useLongPress` racing against it.
|
|
38
|
+
*/
|
|
39
|
+
maxDuration?: number;
|
|
40
|
+
/**
|
|
41
|
+
* How far the finger may travel, in points. Default `10`.
|
|
42
|
+
*
|
|
43
|
+
* Raising it makes the tap more forgiving and makes it harder for a drag in
|
|
44
|
+
* the same view to win the touch.
|
|
45
|
+
*/
|
|
46
|
+
maxDistance?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Extra touchable area around the view, in points.
|
|
49
|
+
*
|
|
50
|
+
* Written inline as an object is fine — the gesture is not rebuilt when the
|
|
51
|
+
* contents are unchanged.
|
|
52
|
+
*/
|
|
53
|
+
hitSlop?: HitSlop;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the gesture is recognized at all. Default `true`.
|
|
56
|
+
*
|
|
57
|
+
* Prefer this over unmounting the `<GestureDetector>`: a disabled gesture
|
|
58
|
+
* keeps its identity and its relations, so re-enabling it does not
|
|
59
|
+
* re-attach anything.
|
|
60
|
+
*/
|
|
61
|
+
enabled?: boolean;
|
|
62
|
+
/**
|
|
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
|
|
65
|
+
* state.
|
|
66
|
+
*
|
|
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.
|
|
69
|
+
*/
|
|
70
|
+
onTap?: (event: TapEvent) => void;
|
|
71
|
+
/**
|
|
72
|
+
* The finger went down and the gesture is now a candidate. **This is a
|
|
73
|
+
* worklet** — mark it with the `'worklet'` directive, and do not touch
|
|
74
|
+
* React state from it.
|
|
75
|
+
*
|
|
76
|
+
* Being a candidate is not the same as winning: in a race with a
|
|
77
|
+
* long press or a drag, this fires and the gesture may still fail. Use it
|
|
78
|
+
* to show a pressed state, and undo that state in `onFinalize`.
|
|
79
|
+
*/
|
|
80
|
+
onBegin?: (event: TapEvent) => void;
|
|
81
|
+
/**
|
|
82
|
+
* The gesture is over, whether it was recognized or not. **This is a
|
|
83
|
+
* worklet.**
|
|
84
|
+
*
|
|
85
|
+
* `success` is `true` when the tap was recognized. This is the right place
|
|
86
|
+
* to clear anything `onBegin` set, because it runs on both paths.
|
|
87
|
+
*/
|
|
88
|
+
onFinalize?: (event: TapEvent, success: boolean) => void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* What {@link useTap} returns.
|
|
92
|
+
*
|
|
93
|
+
* An alias rather than an extending interface, because a tap produces no
|
|
94
|
+
* continuous value of its own: the gesture, the ref, and `isActive` are the
|
|
95
|
+
* whole result. An intent that does produce one — a drag's `x`, a pinch's
|
|
96
|
+
* `scale` — extends {@link IntentResult} instead.
|
|
97
|
+
*/
|
|
98
|
+
type UseTapResult = IntentResult<TapGesture>;
|
|
99
|
+
/**
|
|
100
|
+
* Recognize a single tap.
|
|
101
|
+
*
|
|
102
|
+
* ```tsx
|
|
103
|
+
* const tap = useTap({ onTap: () => select(item.id) })
|
|
104
|
+
*
|
|
105
|
+
* return (
|
|
106
|
+
* <GestureDetector gesture={tap.gesture}>
|
|
107
|
+
* <View />
|
|
108
|
+
* </GestureDetector>
|
|
109
|
+
* )
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* `onTap` runs on the JS thread and may set React state directly. `onBegin`
|
|
113
|
+
* 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.
|
|
115
|
+
*
|
|
116
|
+
* `isActive` is a shared value that is `true` while the finger is down. Drive
|
|
117
|
+
* a pressed state from it without a re-render:
|
|
118
|
+
*
|
|
119
|
+
* ```tsx
|
|
120
|
+
* const tap = useTap({ onTap: select })
|
|
121
|
+
* const style = useAnimatedStyle(() => ({ opacity: tap.isActive.value ? 0.6 : 1 }))
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* **Activation criteria.** `maxDuration` defaults to 500ms and `maxDistance`
|
|
125
|
+
* to 10 points. The distance default is Impulse's, not RNGH's: RNGH defers to
|
|
126
|
+
* 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.
|
|
128
|
+
*
|
|
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.
|
|
132
|
+
*
|
|
133
|
+
* **Web.** RNGH's web implementation recognizes tap from pointer events, and
|
|
134
|
+
* `pointers` above 1 is unreliable there because a mouse reports one pointer
|
|
135
|
+
* and touch emulation varies by browser. A single-finger tap behaves the same
|
|
136
|
+
* as on native.
|
|
137
|
+
*
|
|
138
|
+
* **Accessibility.** A tap gesture is invisible to a screen reader and
|
|
139
|
+
* unreachable from a keyboard. This hook does not fix that, and it cannot.
|
|
140
|
+
* Whatever the tap does must also be reachable another way: put the same
|
|
141
|
+
* action on a `<Pressable>`, or declare it with `accessibilityActions` and
|
|
142
|
+
* `onAccessibilityAction` on the view the gesture is attached to. A tap-only
|
|
143
|
+
* affordance is a bug, not a trade-off.
|
|
144
|
+
*
|
|
145
|
+
* @param options - Activation criteria, callbacks, and the `alongside` /
|
|
146
|
+
* `blocks` / `deferTo` coexistence options every Impulse hook accepts.
|
|
147
|
+
*/
|
|
148
|
+
declare function useTap(options?: UseTapOptions): UseTapResult;
|
|
149
|
+
|
|
150
|
+
export { type TapEvent, type UseTapOptions, type UseTapResult, useTap };
|