@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,444 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Gesture,
|
|
4
|
+
type GestureStateChangeEvent,
|
|
5
|
+
type GestureUpdateEvent,
|
|
6
|
+
type PanGesture,
|
|
7
|
+
type PanGestureHandlerEventPayload,
|
|
8
|
+
} from 'react-native-gesture-handler'
|
|
9
|
+
import { useSharedValue, type SharedValue } from 'react-native-reanimated'
|
|
10
|
+
import { scheduleOnRN } from 'react-native-worklets'
|
|
11
|
+
import {
|
|
12
|
+
useGestureMemo,
|
|
13
|
+
type GestureMemoOptions,
|
|
14
|
+
} from '../internal/useGestureMemo'
|
|
15
|
+
import { buildIntentResult } from '../internal/intentResult'
|
|
16
|
+
import { useLatestCallback } from '../internal/useLatestCallback'
|
|
17
|
+
import { useStableRecord } from '../internal/useStableRecord'
|
|
18
|
+
import {
|
|
19
|
+
type HitSlop,
|
|
20
|
+
type IntentEndInfo,
|
|
21
|
+
type IntentResult,
|
|
22
|
+
type Point,
|
|
23
|
+
} from '../types'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How far the finger must travel before the pan takes the touch, in points.
|
|
27
|
+
*
|
|
28
|
+
* The same number `useDrag` uses, and for the same reason: a bare
|
|
29
|
+
* `Gesture.Pan()` activates almost immediately, which is what makes a pan
|
|
30
|
+
* inside a scroll view steal the scroll.
|
|
31
|
+
*
|
|
32
|
+
* **This number is a design intention, not a measurement.** The device sweep
|
|
33
|
+
* of 2026-09-20 drove `usePan` and did not test it. See Known gaps in
|
|
34
|
+
* docs/docs/roadmap.md.
|
|
35
|
+
*/
|
|
36
|
+
const DEFAULT_THRESHOLD = 10
|
|
37
|
+
|
|
38
|
+
/** Which way the pan is allowed to move. */
|
|
39
|
+
export type PanAxis = 'x' | 'y' | 'both'
|
|
40
|
+
|
|
41
|
+
/** The intent-shaped payload a {@link usePan} callback receives. */
|
|
42
|
+
export interface PanEvent {
|
|
43
|
+
/**
|
|
44
|
+
* How far the finger has moved since the pan activated.
|
|
45
|
+
*
|
|
46
|
+
* Measured from the activation point, not from touch-down, so it does not
|
|
47
|
+
* include the `threshold` travel the finger spent before the pan existed.
|
|
48
|
+
* It resets to zero at the start of every gesture — a pan reports movement
|
|
49
|
+
* and owns no position.
|
|
50
|
+
*/
|
|
51
|
+
readonly translation: Point
|
|
52
|
+
/**
|
|
53
|
+
* How far the finger moved since the previous frame.
|
|
54
|
+
*
|
|
55
|
+
* The field `useDrag` cannot give, and the reason to reach for this hook:
|
|
56
|
+
* a value the consumer owns is advanced by adding this, rather than by
|
|
57
|
+
* re-deriving it from a translation Impulse already clamped.
|
|
58
|
+
*
|
|
59
|
+
* Zero outside `onUpdate`. The first frame after activation reports the
|
|
60
|
+
* movement since activation, so a value advanced by `change` never jumps
|
|
61
|
+
* by `threshold`.
|
|
62
|
+
*/
|
|
63
|
+
readonly change: Point
|
|
64
|
+
/** Finger speed, in points per second. */
|
|
65
|
+
readonly velocity: Point
|
|
66
|
+
/** The touch point relative to the window. */
|
|
67
|
+
readonly absolute: Point
|
|
68
|
+
/** How many fingers are down. */
|
|
69
|
+
readonly pointers: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Options for {@link usePan}. */
|
|
73
|
+
export interface UsePanOptions extends GestureMemoOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Which way the pan may move. Default `'both'`.
|
|
76
|
+
*
|
|
77
|
+
* The locked axis reports zero for the life of the hook — `axis: 'x'`
|
|
78
|
+
* leaves `y`, `translation.y`, and `change.y` at zero. The axis also
|
|
79
|
+
* decides the activation criterion, which is the part that matters inside
|
|
80
|
+
* a scroll view: see `threshold`.
|
|
81
|
+
*/
|
|
82
|
+
axis?: PanAxis
|
|
83
|
+
/**
|
|
84
|
+
* How far the finger must travel before the pan activates, in points.
|
|
85
|
+
* Default `10`.
|
|
86
|
+
*
|
|
87
|
+
* On a single axis this is a directional threshold, so a pan with
|
|
88
|
+
* `axis: 'x'` ignores vertical movement entirely and a vertical scroll view
|
|
89
|
+
* under it keeps working. On `'both'` it is a radial distance.
|
|
90
|
+
*/
|
|
91
|
+
threshold?: number
|
|
92
|
+
/**
|
|
93
|
+
* Movement across the axis that makes the pan fail, in points. Unset by
|
|
94
|
+
* default, which leaves RNGH's own behaviour in place.
|
|
95
|
+
*
|
|
96
|
+
* `threshold` decides when the pan wins; this decides when it gives up.
|
|
97
|
+
* Ignored when `axis` is `'both'`, which has no cross axis.
|
|
98
|
+
*/
|
|
99
|
+
failOffset?: number
|
|
100
|
+
/**
|
|
101
|
+
* How many fingers must be on the view. Unset by default, which leaves
|
|
102
|
+
* RNGH's range of one to ten in place.
|
|
103
|
+
*
|
|
104
|
+
* Setting it fixes the count exactly, so `pointers: 2` is a two-finger pan
|
|
105
|
+
* that neither starts with one finger nor survives a third.
|
|
106
|
+
*/
|
|
107
|
+
pointers?: number
|
|
108
|
+
/**
|
|
109
|
+
* Extra touchable area around the view, in points.
|
|
110
|
+
*
|
|
111
|
+
* Written inline as an object is fine — the gesture is not rebuilt when the
|
|
112
|
+
* contents are unchanged.
|
|
113
|
+
*/
|
|
114
|
+
hitSlop?: HitSlop
|
|
115
|
+
/**
|
|
116
|
+
* Whether the gesture is recognized at all. Default `true`.
|
|
117
|
+
*
|
|
118
|
+
* Prefer this over unmounting the `<GestureDetector>`: a disabled gesture
|
|
119
|
+
* keeps its identity and its relations, so re-enabling it does not
|
|
120
|
+
* re-attach anything.
|
|
121
|
+
*/
|
|
122
|
+
enabled?: boolean
|
|
123
|
+
/**
|
|
124
|
+
* The pan passed `threshold` and now owns the touch. **Runs on the JS
|
|
125
|
+
* thread** — Impulse owns the `scheduleOnRN` boundary, so this is an
|
|
126
|
+
* ordinary function and may touch React state.
|
|
127
|
+
*
|
|
128
|
+
* This is the first moment the pan has definitely won. `onBegin` fires
|
|
129
|
+
* earlier and promises nothing.
|
|
130
|
+
*/
|
|
131
|
+
onPanStart?: (event: PanEvent) => void
|
|
132
|
+
/**
|
|
133
|
+
* The pan is over. **Runs on the JS thread.**
|
|
134
|
+
*
|
|
135
|
+
* Fires only for a pan that activated, so a touch that never passed the
|
|
136
|
+
* threshold never reaches here on either path.
|
|
137
|
+
*
|
|
138
|
+
* It fires for both endings, and `cancelled` says which. `false` is the
|
|
139
|
+
* finger lifting, and `velocity` then describes the release. `true` is the
|
|
140
|
+
* system taking the pan away — a competing gesture won, or the app went to
|
|
141
|
+
* the background. **There was no release on that path, so the velocity
|
|
142
|
+
* describes the last movement rather than a throw.**
|
|
143
|
+
*/
|
|
144
|
+
onPanEnd?: (event: PanEvent, info: IntentEndInfo) => void
|
|
145
|
+
/**
|
|
146
|
+
* The finger went down and the gesture is now a candidate. **This is a
|
|
147
|
+
* worklet** — mark it with the `'worklet'` directive, and do not touch
|
|
148
|
+
* React state from it.
|
|
149
|
+
*
|
|
150
|
+
* Being a candidate is not the same as winning: the pan has not passed
|
|
151
|
+
* `threshold` yet and may never. Undo whatever it sets in `onFinalize`.
|
|
152
|
+
*/
|
|
153
|
+
onBegin?: (event: PanEvent) => void
|
|
154
|
+
/**
|
|
155
|
+
* The pan moved. **This is a worklet**, and it runs on every frame the
|
|
156
|
+
* finger moves.
|
|
157
|
+
*
|
|
158
|
+
* This is where `change` is read, and it is the reason the hook exists:
|
|
159
|
+
* advance the value the consumer owns from here, on the thread that already
|
|
160
|
+
* holds it.
|
|
161
|
+
*
|
|
162
|
+
* There is no JS-thread counterpart on purpose. A per-frame `scheduleOnRN`
|
|
163
|
+
* is a scheduling cost paid sixty times a second for a value that is
|
|
164
|
+
* already on the thread that needs it.
|
|
165
|
+
*/
|
|
166
|
+
onUpdate?: (event: PanEvent) => void
|
|
167
|
+
/**
|
|
168
|
+
* The gesture is over, whether it activated or not. **This is a worklet.**
|
|
169
|
+
*
|
|
170
|
+
* `success` is `true` when the pan activated and ended normally. This is
|
|
171
|
+
* the right place to clear anything `onBegin` set, because it runs on both
|
|
172
|
+
* paths.
|
|
173
|
+
*/
|
|
174
|
+
onFinalize?: (event: PanEvent, success: boolean) => void
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** What {@link usePan} returns. */
|
|
178
|
+
export interface UsePanResult extends IntentResult<PanGesture> {
|
|
179
|
+
/**
|
|
180
|
+
* How far the finger has moved along the x axis since the pan activated.
|
|
181
|
+
*
|
|
182
|
+
* **This is movement, not a position.** It is set to zero at the start of
|
|
183
|
+
* every gesture, so a second pan does not continue from where the first
|
|
184
|
+
* stopped. `useDrag` is the hook whose value accumulates.
|
|
185
|
+
*
|
|
186
|
+
* It keeps its final number after the gesture ends, so a release animation
|
|
187
|
+
* has something to animate from.
|
|
188
|
+
*
|
|
189
|
+
* Frozen at zero when `axis` is `'y'`.
|
|
190
|
+
*/
|
|
191
|
+
readonly x: SharedValue<number>
|
|
192
|
+
/**
|
|
193
|
+
* How far the finger has moved along the y axis since the pan activated.
|
|
194
|
+
* Same terms as `x`. Frozen at zero when `axis` is `'x'`.
|
|
195
|
+
*/
|
|
196
|
+
readonly y: SharedValue<number>
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Recognize a pan, and report how the finger moved.
|
|
201
|
+
*
|
|
202
|
+
* ```tsx
|
|
203
|
+
* const camera = { x: useSharedValue(0), y: useSharedValue(0) }
|
|
204
|
+
* const pan = usePan({
|
|
205
|
+
* onUpdate: (event) => {
|
|
206
|
+
* 'worklet'
|
|
207
|
+
* camera.x.value += event.change.x
|
|
208
|
+
* camera.y.value += event.change.y
|
|
209
|
+
* },
|
|
210
|
+
* })
|
|
211
|
+
*
|
|
212
|
+
* return (
|
|
213
|
+
* <GestureDetector gesture={pan.gesture}>
|
|
214
|
+
* <Animated.View style={style} />
|
|
215
|
+
* </GestureDetector>
|
|
216
|
+
* )
|
|
217
|
+
* ```
|
|
218
|
+
*
|
|
219
|
+
* **`usePan` reports movement; `useDrag` owns a position.** That is the whole
|
|
220
|
+
* difference, and it decides which one a screen wants. `useDrag` holds `x`
|
|
221
|
+
* and `y` as the place a thing sits: they accumulate across gestures, they
|
|
222
|
+
* are clamped by `bounds`, and `elastic` bends them. `usePan` holds no
|
|
223
|
+
* position at all — it hands over `translation`, `velocity`, and a per-frame
|
|
224
|
+
* `change`, and the consumer advances whatever it drives. Reach for `useDrag`
|
|
225
|
+
* to move a view. Reach for this one to pan a camera, scrub a value, or feed
|
|
226
|
+
* a number Impulse has no business clamping.
|
|
227
|
+
*
|
|
228
|
+
* **Activation criteria.** `threshold` defaults to 10 points. With
|
|
229
|
+
* `axis: 'x'` or `'y'` it is directional, so a horizontal pan inside a
|
|
230
|
+
* vertical `ScrollView` leaves the scroll alone until the finger commits
|
|
231
|
+
* sideways; with `'both'` it is a radial distance. The device sweep did not
|
|
232
|
+
* test the default.
|
|
233
|
+
*
|
|
234
|
+
* **Coexistence.** A threshold decides who moves first; it does not decide
|
|
235
|
+
* who wins a contested touch. Say which gesture the touch belongs to as well
|
|
236
|
+
* — `deferTo` for a pan that is the fallback, `blocks` for one that is the
|
|
237
|
+
* foreground affordance, `alongside` for a pan that shares the touch with a
|
|
238
|
+
* pinch.
|
|
239
|
+
*
|
|
240
|
+
* **Web.** RNGH recognizes pan from pointer events, so a mouse drag behaves
|
|
241
|
+
* the same as a touch drag and `velocity` is reported in the same units. A
|
|
242
|
+
* trackpad's momentum scroll is not a pan and never reaches this hook.
|
|
243
|
+
* `pointers` above 1 is unreliable on web.
|
|
244
|
+
*
|
|
245
|
+
* **Accessibility.** A pan is invisible to a screen reader and unreachable
|
|
246
|
+
* from a keyboard, and this hook does not fix that. Whatever the pan moves
|
|
247
|
+
* must be reachable another way: buttons that step the value, a reset
|
|
248
|
+
* control for a panned canvas, or `accessibilityActions` with
|
|
249
|
+
* `onAccessibilityAction`. A pan-only affordance is a bug, not a trade-off.
|
|
250
|
+
*
|
|
251
|
+
* @param options - Activation criteria, callbacks, and the `alongside` /
|
|
252
|
+
* `blocks` / `deferTo` coexistence options every Impulse hook accepts.
|
|
253
|
+
*/
|
|
254
|
+
export function usePan(options: UsePanOptions = {}): UsePanResult {
|
|
255
|
+
const {
|
|
256
|
+
axis = 'both',
|
|
257
|
+
threshold = DEFAULT_THRESHOLD,
|
|
258
|
+
failOffset,
|
|
259
|
+
pointers,
|
|
260
|
+
enabled,
|
|
261
|
+
onPanStart,
|
|
262
|
+
onPanEnd,
|
|
263
|
+
onBegin,
|
|
264
|
+
onUpdate,
|
|
265
|
+
onFinalize,
|
|
266
|
+
} = options
|
|
267
|
+
|
|
268
|
+
const x = useSharedValue(0)
|
|
269
|
+
const y = useSharedValue(0)
|
|
270
|
+
const isActive = useSharedValue(false)
|
|
271
|
+
// RNGH's `translationX` at the moment the pan activated. Everything this
|
|
272
|
+
// hook reports is measured from here, so the `threshold` travel the finger
|
|
273
|
+
// spent before the pan existed is not counted as movement.
|
|
274
|
+
const originX = useSharedValue(0)
|
|
275
|
+
const originY = useSharedValue(0)
|
|
276
|
+
// The previous frame's translation, so `change` is a delta this hook
|
|
277
|
+
// computes rather than RNGH's `changeX`. RNGH reports the first change as
|
|
278
|
+
// the whole translation, which includes the threshold travel — a consumer
|
|
279
|
+
// accumulating it would jump ten points on the first frame.
|
|
280
|
+
const lastX = useSharedValue(0)
|
|
281
|
+
const lastY = useSharedValue(0)
|
|
282
|
+
|
|
283
|
+
const hitSlop = useStableRecord(options.hitSlop)
|
|
284
|
+
|
|
285
|
+
const movesX = axis !== 'y'
|
|
286
|
+
const movesY = axis !== 'x'
|
|
287
|
+
|
|
288
|
+
// JS-thread callbacks reach the gesture through stable identities, so they
|
|
289
|
+
// are never gesture dependencies. The worklet callbacks stay direct
|
|
290
|
+
// dependencies, because a worklet is captured as written.
|
|
291
|
+
const handlePanStart = useLatestCallback(onPanStart)
|
|
292
|
+
const handlePanEnd = useLatestCallback(onPanEnd)
|
|
293
|
+
// Attaching a handler is not the same as calling it: RNGH decides which
|
|
294
|
+
// thread a gesture's callbacks run on by inspecting the ones it was given,
|
|
295
|
+
// so the gesture does have to change when a handler appears or disappears.
|
|
296
|
+
const hasPanStart = onPanStart !== undefined
|
|
297
|
+
const hasPanEnd = onPanEnd !== undefined
|
|
298
|
+
|
|
299
|
+
const built = useGestureMemo(
|
|
300
|
+
'usePan',
|
|
301
|
+
() => {
|
|
302
|
+
/**
|
|
303
|
+
* Shape RNGH's flat event into the pan payload.
|
|
304
|
+
*
|
|
305
|
+
* `change` is passed in rather than read off the event: only an update
|
|
306
|
+
* event carries a delta at all, and the one RNGH computes counts the
|
|
307
|
+
* threshold travel on the first frame.
|
|
308
|
+
*/
|
|
309
|
+
const toPanEvent = (
|
|
310
|
+
event:
|
|
311
|
+
| GestureStateChangeEvent<PanGestureHandlerEventPayload>
|
|
312
|
+
| GestureUpdateEvent<PanGestureHandlerEventPayload>,
|
|
313
|
+
changeX: number,
|
|
314
|
+
changeY: number,
|
|
315
|
+
): PanEvent => {
|
|
316
|
+
'worklet'
|
|
317
|
+
return {
|
|
318
|
+
translation: { x: x.value, y: y.value },
|
|
319
|
+
change: { x: changeX, y: changeY },
|
|
320
|
+
velocity: { x: event.velocityX, y: event.velocityY },
|
|
321
|
+
absolute: { x: event.absoluteX, y: event.absoluteY },
|
|
322
|
+
pointers: event.numberOfPointers,
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const pan = Gesture.Pan()
|
|
327
|
+
.onBegin((event) => {
|
|
328
|
+
'worklet'
|
|
329
|
+
onBegin?.(toPanEvent(event, 0, 0))
|
|
330
|
+
})
|
|
331
|
+
.onStart((event) => {
|
|
332
|
+
'worklet'
|
|
333
|
+
// Zero the report, and remember where RNGH's own translation stood
|
|
334
|
+
// when it did. A pan owns no position, so every gesture starts from
|
|
335
|
+
// nothing rather than from the last one's total.
|
|
336
|
+
originX.value = event.translationX
|
|
337
|
+
originY.value = event.translationY
|
|
338
|
+
lastX.value = 0
|
|
339
|
+
lastY.value = 0
|
|
340
|
+
x.value = 0
|
|
341
|
+
y.value = 0
|
|
342
|
+
isActive.value = true
|
|
343
|
+
if (hasPanStart) {
|
|
344
|
+
scheduleOnRN(handlePanStart, toPanEvent(event, 0, 0))
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
.onUpdate((event) => {
|
|
348
|
+
'worklet'
|
|
349
|
+
const nextX = movesX ? event.translationX - originX.value : 0
|
|
350
|
+
const nextY = movesY ? event.translationY - originY.value : 0
|
|
351
|
+
const changeX = nextX - lastX.value
|
|
352
|
+
const changeY = nextY - lastY.value
|
|
353
|
+
lastX.value = nextX
|
|
354
|
+
lastY.value = nextY
|
|
355
|
+
x.value = nextX
|
|
356
|
+
y.value = nextY
|
|
357
|
+
onUpdate?.(toPanEvent(event, changeX, changeY))
|
|
358
|
+
})
|
|
359
|
+
.onEnd((event, success) => {
|
|
360
|
+
'worklet'
|
|
361
|
+
// Not guarded on `success`: RNGH calls this for a cancelled pan
|
|
362
|
+
// too, and `cancelled` is what carries that to the JS thread. RNGH
|
|
363
|
+
// reaches `onEnd` only from the ACTIVE state, so a touch that never
|
|
364
|
+
// passed the threshold goes to `onFinalize` and never gets here.
|
|
365
|
+
if (hasPanEnd) {
|
|
366
|
+
scheduleOnRN(handlePanEnd, toPanEvent(event, 0, 0), {
|
|
367
|
+
cancelled: !success,
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
})
|
|
371
|
+
.onFinalize((event, success) => {
|
|
372
|
+
'worklet'
|
|
373
|
+
isActive.value = false
|
|
374
|
+
onFinalize?.(toPanEvent(event, 0, 0), success)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
// A directional threshold on a single axis, a radial one on both. The
|
|
378
|
+
// directional form is what lets a horizontal pan and a vertical
|
|
379
|
+
// scroller share a view: vertical movement never reaches the offset, so
|
|
380
|
+
// the pan never claims the touch.
|
|
381
|
+
if (axis === 'x') {
|
|
382
|
+
pan.activeOffsetX([-threshold, threshold])
|
|
383
|
+
} else if (axis === 'y') {
|
|
384
|
+
pan.activeOffsetY([-threshold, threshold])
|
|
385
|
+
} else {
|
|
386
|
+
pan.minDistance(threshold)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Applied conditionally rather than with a default, so an option the
|
|
390
|
+
// consumer did not set leaves RNGH's own default in place instead of
|
|
391
|
+
// Impulse overwriting it with a guess.
|
|
392
|
+
if (failOffset !== undefined) {
|
|
393
|
+
if (axis === 'x') {
|
|
394
|
+
pan.failOffsetY([-failOffset, failOffset])
|
|
395
|
+
} else if (axis === 'y') {
|
|
396
|
+
pan.failOffsetX([-failOffset, failOffset])
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (pointers !== undefined) {
|
|
400
|
+
pan.minPointers(pointers).maxPointers(pointers)
|
|
401
|
+
}
|
|
402
|
+
if (hitSlop !== undefined) {
|
|
403
|
+
pan.hitSlop(hitSlop)
|
|
404
|
+
}
|
|
405
|
+
if (enabled !== undefined) {
|
|
406
|
+
pan.enabled(enabled)
|
|
407
|
+
}
|
|
408
|
+
return pan
|
|
409
|
+
},
|
|
410
|
+
[
|
|
411
|
+
axis,
|
|
412
|
+
threshold,
|
|
413
|
+
failOffset,
|
|
414
|
+
pointers,
|
|
415
|
+
hitSlop,
|
|
416
|
+
enabled,
|
|
417
|
+
movesX,
|
|
418
|
+
movesY,
|
|
419
|
+
hasPanStart,
|
|
420
|
+
hasPanEnd,
|
|
421
|
+
handlePanStart,
|
|
422
|
+
handlePanEnd,
|
|
423
|
+
x,
|
|
424
|
+
y,
|
|
425
|
+
originX,
|
|
426
|
+
originY,
|
|
427
|
+
lastX,
|
|
428
|
+
lastY,
|
|
429
|
+
isActive,
|
|
430
|
+
onBegin,
|
|
431
|
+
onUpdate,
|
|
432
|
+
onFinalize,
|
|
433
|
+
],
|
|
434
|
+
options,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
// Memoised so a consumer can put the whole hook result in a dependency
|
|
438
|
+
// list. The shared values are stable for the life of the hook, so `built`
|
|
439
|
+
// is the only real input.
|
|
440
|
+
return useMemo(
|
|
441
|
+
() => buildIntentResult(built, { x, y, isActive }),
|
|
442
|
+
[built, x, y, isActive],
|
|
443
|
+
)
|
|
444
|
+
}
|