@rootnative/impulse 0.0.0-alpha.0 → 0.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +51 -5
  2. package/README.md +206 -11
  3. package/dist/{chunk-5BMRKYVY.js → chunk-2UZTAWUQ.js} +20 -2
  4. package/dist/chunk-BNSFDNLA.js +75 -0
  5. package/dist/chunk-DXXGWG4Q.js +136 -0
  6. package/dist/{chunk-PMR25UCT.js → chunk-HGBCIL6X.js} +1 -1
  7. package/dist/chunk-HI5PHDJY.js +94 -0
  8. package/dist/{chunk-IG5RXCYR.js → chunk-IH7SQ5X6.js} +11 -15
  9. package/dist/chunk-MWCIEVTA.js +199 -0
  10. package/dist/{chunk-F4RHM4ZK.js → chunk-NYDDZD4G.js} +58 -1
  11. package/dist/chunk-PGOQSKEJ.js +144 -0
  12. package/dist/{chunk-FR242SUF.js → chunk-TGOGIZDH.js} +13 -7
  13. package/dist/chunk-VEPUHGPN.js +12 -0
  14. package/dist/chunk-YZHAQ4XK.js +136 -0
  15. package/dist/compose/index.d.ts +1 -1
  16. package/dist/double-tap/index.d.ts +184 -0
  17. package/dist/double-tap/index.js +5 -0
  18. package/dist/drag/index.d.ts +18 -13
  19. package/dist/drag/index.js +3 -3
  20. package/dist/index.d.ts +10 -3
  21. package/dist/index.js +12 -5
  22. package/dist/long-press/index.d.ts +219 -0
  23. package/dist/long-press/index.js +4 -0
  24. package/dist/pan/index.d.ts +221 -0
  25. package/dist/pan/index.js +4 -0
  26. package/dist/pinch/index.d.ts +239 -0
  27. package/dist/pinch/index.js +4 -0
  28. package/dist/raw/index.d.ts +3 -3
  29. package/dist/raw/index.js +2 -2
  30. package/dist/rotate/index.d.ts +263 -0
  31. package/dist/rotate/index.js +4 -0
  32. package/dist/swipe/index.d.ts +254 -0
  33. package/dist/swipe/index.js +4 -0
  34. package/dist/tap/index.d.ts +34 -29
  35. package/dist/tap/index.js +4 -3
  36. package/dist/tapEvent-KSSojt_l.d.ts +28 -0
  37. package/dist/{types-Ch2HM3aP.d.ts → types-ChGKY28a.d.ts} +27 -1
  38. package/dist/{useGestureMemo-Ccv8rB0C.d.ts → useGestureMemo-BRW1EKcJ.d.ts} +1 -1
  39. package/jest-setup.cjs +32 -1
  40. package/llms.txt +155 -0
  41. package/package.json +35 -2
  42. package/src/index.ts +50 -4
  43. package/src/intents/double-tap/index.ts +6 -0
  44. package/src/intents/long-press/index.ts +6 -0
  45. package/src/intents/pan/index.ts +2 -0
  46. package/src/intents/pinch/index.ts +2 -0
  47. package/src/intents/rotate/index.ts +6 -0
  48. package/src/intents/swipe/index.ts +8 -0
  49. package/src/intents/tapEvent.ts +50 -0
  50. package/src/intents/useDoubleTap.ts +321 -0
  51. package/src/intents/useDrag.ts +49 -28
  52. package/src/intents/useLongPress.ts +391 -0
  53. package/src/intents/usePan.ts +444 -0
  54. package/src/intents/usePinch.ts +483 -0
  55. package/src/intents/useRotate.ts +507 -0
  56. package/src/intents/useSwipe.ts +585 -0
  57. package/src/intents/useTap.ts +51 -60
  58. package/src/internal/intentResult.ts +67 -0
  59. package/src/internal/phaseCallbacks.ts +51 -0
  60. package/src/internal/useGestureMemo.ts +32 -4
  61. package/src/internal/useLatestCallback.ts +2 -2
  62. package/src/raw/useRawGesture.ts +1 -1
  63. package/src/relations/index.ts +94 -0
  64. package/src/types.ts +27 -0
@@ -138,5 +138,31 @@ interface IntentResult<G extends GestureType> {
138
138
  */
139
139
  readonly isActive: SharedValue<boolean>;
140
140
  }
141
+ /**
142
+ * How a gesture ended, handed to every intent's end callback as its second
143
+ * argument.
144
+ *
145
+ * The end callbacks — `onTap`, `onDoubleTap`, `onLongPressEnd`, `onDragEnd` —
146
+ * fire on both paths: a gesture the user completed, and one the system took
147
+ * away. This says which. Without it, a cancel is reported only by
148
+ * `onFinalize`, which is a worklet, so a consumer holding phase in React
149
+ * state has to write `'worklet'` plus `scheduleOnRN` by hand.
150
+ *
151
+ * It is an object rather than a bare boolean so a later field — a reason for
152
+ * the cancel, say — does not break the signature a second time.
153
+ */
154
+ interface IntentEndInfo {
155
+ /**
156
+ * `true` when the system took the gesture away instead of the user
157
+ * completing it: a competing gesture won, the app went to the background,
158
+ * or a relation handed the touch to another recognizer.
159
+ *
160
+ * A gesture that never activated at all does not reach an end callback on
161
+ * either path, so this is never `true` for a touch that was never the
162
+ * intent. It separates "this ended, but not by the user" from "the user
163
+ * did it".
164
+ */
165
+ readonly cancelled: boolean;
166
+ }
141
167
 
142
- export type { AttachableGesture as A, CoexistenceOptions as C, GestureReference as G, HitSlop as H, IntentResult as I, Point as P, ComposeMode as a, GestureReferences as b };
168
+ export type { AttachableGesture as A, CoexistenceOptions as C, GestureReference as G, HitSlop as H, IntentEndInfo as I, Point as P, ComposeMode as a, GestureReferences as b, IntentResult as c };
@@ -1,6 +1,6 @@
1
1
  import { RefObject } from 'react';
2
2
  import { GestureType } from 'react-native-gesture-handler';
3
- import { C as CoexistenceOptions } from './types-Ch2HM3aP.js';
3
+ import { C as CoexistenceOptions } from './types-ChGKY28a.js';
4
4
 
5
5
  /** What every hook built on this helper accepts on top of its own options. */
6
6
  interface GestureMemoOptions extends CoexistenceOptions {
package/jest-setup.cjs CHANGED
@@ -32,7 +32,8 @@ require('react-native-gesture-handler/jestSetup')
32
32
  //
33
33
  // What this means for tests:
34
34
  // ✅ assert a shared value's `.value` after driving a gesture
35
- // ✅ assert that a JS-thread callback fired, because `runOnJS` is identity
35
+ // ✅ assert that a JS-thread callback fired, because `scheduleOnRN` calls
36
+ // it synchronously
36
37
  // ❌ frame-level timing is not observable — nothing schedules or animates
37
38
  jest.mock('react-native-reanimated', () => {
38
39
  const React = require('react')
@@ -54,3 +55,33 @@ jest.mock('react-native-reanimated', () => {
54
55
  setGestureState: () => {},
55
56
  }
56
57
  })
58
+
59
+ // Worklets mock. Impulse crosses to the JS thread with `scheduleOnRN` from
60
+ // `react-native-worklets`, not with Reanimated's deprecated `runOnJS`, so the
61
+ // mock above no longer covers that call — source importing from this package
62
+ // would load the real module, which needs its native module and cannot run
63
+ // under Jest.
64
+ //
65
+ // `scheduleOnRN` calls the function synchronously here. The real one schedules
66
+ // it on the JS thread, so a test asserts that a callback ran, never when it
67
+ // ran relative to a frame. That limit is the same one the Reanimated mock
68
+ // already carried.
69
+ //
70
+ // Only what Impulse imports is mocked. `runOnJS` is included because the
71
+ // undeprecated curried form lives in this package too, and a consumer's own
72
+ // code may use it under this setup file.
73
+ //
74
+ // `isWorkletFunction` reports every function as a worklet. The real one reads
75
+ // a hash that only the Worklets Babel plugin writes, and a Jest run without
76
+ // the plugin would then flag every phase callback. Here nothing runs on a UI
77
+ // thread, so a plain function is correct and the phase-callback warning must
78
+ // stay silent.
79
+ jest.mock('react-native-worklets', () => ({
80
+ __esModule: true,
81
+ scheduleOnRN: (fn, ...args) => fn(...args),
82
+ isWorkletFunction: (value) => typeof value === 'function',
83
+ runOnJS:
84
+ (fn) =>
85
+ (...args) =>
86
+ fn(...args),
87
+ }))
package/llms.txt ADDED
@@ -0,0 +1,155 @@
1
+ # @rootnative/impulse
2
+
3
+ > Declarative gesture primitives for React Native, built on
4
+ > react-native-gesture-handler. One hook per intent. Hooks return shared values
5
+ > and a gesture; Impulse starts no animation and ships no components.
6
+
7
+ Status: 0.0.0-alpha.1. Eight intents ship. A device sweep on an Android
8
+ emulator and an iOS simulator drove every intent. It proves mechanics, not
9
+ feel, so every default below is still a design intention.
10
+
11
+ Docs: https://rootnative.github.io/impulse/
12
+ Full: https://rootnative.github.io/impulse/llms-full.txt
13
+ Source: https://github.com/rootnative/impulse
14
+
15
+ ## Install
16
+
17
+ npm install @rootnative/impulse
18
+
19
+ Peers: react, react-native, react-native-gesture-handler,
20
+ react-native-reanimated, react-native-worklets. Render a
21
+ <GestureHandlerRootView> above any component using a gesture — if it is
22
+ missing, gestures never fire and nothing warns.
23
+
24
+ ## What ships
25
+
26
+ useTap, useDoubleTap, useLongPress, useDrag, usePan, useSwipe, usePinch,
27
+ useRotate — intent hooks. useGestures — composition. useRawGesture — the
28
+ escape hatch. GestureDetector is re-exported from the package entry.
29
+
30
+ NOT IMPLEMENTED, do not import: useHover, useEdgeSwipe.
31
+ They are designed only.
32
+
33
+ usePan and useDrag are not synonyms. useDrag owns a position: x and y
34
+ accumulate across gestures and bounds clamps them. usePan owns nothing: it
35
+ reports translation, velocity, and a per-frame change the consumer adds up,
36
+ and it zeroes at every gesture.
37
+
38
+ useSwipe is a pan judged at release. directions is also the activation
39
+ criterion — an all-horizontal list gives a directional threshold, so the
40
+ swipe shares a view with a vertical scroller with no relation declared.
41
+
42
+ usePinch owns a scale that accumulates across gestures, so min and max are
43
+ the viewer's zoom range, not one gesture's. focal is the midpoint between the
44
+ fingers and the point the zoom must happen about — scaling about the view's
45
+ centre slides the content out from under them. It has NO activation criteria:
46
+ RNGH's pinch exposes no threshold, so a pinch and a pan are separated by a
47
+ relation alone — useGestures([pinch, pan], { mode: 'simultaneous' }).
48
+
49
+ useRotate reports DEGREES, not the radians RNGH reports. This is the one
50
+ place Impulse changes a unit: min: -45, not -Math.PI / 4. It applies to
51
+ angle, gestureAngle, velocity and the min/max range. The angle accumulates
52
+ and does NOT wrap — a second full turn reports 720. anchor is the point the
53
+ turn happens about, the counterpart of pinch's focal. It has NO activation
54
+ criteria either, so rotate and pinch are composed simultaneous.
55
+
56
+ Subpaths: @rootnative/impulse/{tap,double-tap,long-press,drag,pan,swipe,pinch,rotate,compose,raw,gesture-handler}
57
+
58
+ ## The shape of every hook
59
+
60
+ const tap = useTap({ onTap: select })
61
+ tap.gesture // hand to <GestureDetector>
62
+ tap.ref // name it in another hook's alongside / blocks / deferTo
63
+ tap.isActive // SharedValue<boolean>
64
+
65
+ useDrag adds x and y, both SharedValue<number>, both writable, both
66
+ accumulating across gestures.
67
+
68
+ ## Threads — the callback name states the thread
69
+
70
+ JS thread: onTap, onDoubleTap, onLongPress, onLongPressEnd, onDragStart,
71
+ onDragEnd, onPanStart, onPanEnd, onSwipe, onSwipeEnd, onPinchStart,
72
+ onPinchEnd, onRotateStart, onRotateEnd. Ordinary functions; Impulse owns the
73
+ scheduleOnRN boundary.
74
+
75
+ Every callback that reports an ending takes a second argument, { cancelled }:
76
+ onTap, onDoubleTap, onLongPressEnd, onDragEnd, onPanEnd, onSwipeEnd,
77
+ onPinchEnd, onRotateEnd. It fires on both endings. cancelled is true when the
78
+ system took the gesture away — a competing gesture won, the app went to the
79
+ background, or a relation handed the touch on. onSwipe fires for a commit
80
+ only and takes no second argument.
81
+
82
+ Worklets: onBegin, onUpdate, onFinalize. Mark with 'worklet'. A plain
83
+ function there makes Impulse warn once in development, and the warning names
84
+ the hook and the callback. Without the directive, the first touch that
85
+ reaches the phase throws "Tried to synchronously call a Remote Function".
86
+ Worklets are direct gesture dependencies, so keep their identity stable — an
87
+ inline worklet rebuilds the gesture every render. An inline JS-thread
88
+ callback does not.
89
+
90
+ ## Composition
91
+
92
+ useGestures(members, { mode }) where mode is race | simultaneous | exclusive.
93
+ A composed result is itself a member. Precedence reads left to right.
94
+
95
+ A tap and a double tap are exclusive with the double tap FIRST, never race — a
96
+ single tap recognizes on first release and would win every race. The composed
97
+ single tap then pays maxDelay in latency.
98
+
99
+ useGestures does not accept coexistence options. Put them on the members.
100
+
101
+ ## Coexistence
102
+
103
+ alongside -> both recognize (simultaneousWithExternalGesture)
104
+ blocks -> this gesture wins (blocksExternalGesture)
105
+ deferTo -> the named gesture wins (requireExternalGestureToFail)
106
+
107
+ Name another hook's .ref, not its .gesture.
108
+
109
+ TRAP: a relation target must carry a handlerTag. React Native's ScrollView has
110
+ none, so the relation is SILENTLY dropped on every platform. Use
111
+ gesture-handler's ScrollView / FlatList, from @rootnative/impulse/gesture-handler.
112
+
113
+ TRAP: a component ref needs `as unknown as GestureReference`. RNGH types the
114
+ target as a ref to a component type, which is not what ref={} produces.
115
+
116
+ ## Defaults
117
+
118
+ useTap: pointers 1, maxDuration 500ms, maxDistance 10pt
119
+ useDoubleTap: same, plus maxDelay 500ms (probably too generous; 250-300 is
120
+ closer to what platforms use)
121
+ useLongPress: minDuration 500ms, maxDistance 10pt
122
+ useDrag: axis both, threshold 10pt, elastic 0, unbounded
123
+ usePan: axis both, threshold 10pt
124
+ useSwipe: directions all four, threshold 10pt, commitDistance 80pt,
125
+ commitSpeed 800pt/s
126
+ usePinch: initial 1, unbounded, no activation criteria
127
+ useRotate: initial 0, unbounded, no activation criteria
128
+
129
+ No default has a feel result. The device sweep proved that the drag
130
+ threshold, commitDistance, and maxDelay at 500ms and 250ms work. commitSpeed
131
+ is not tested, because neither harness can make a swipe that fast.
132
+
133
+ ## isActive does not mean the same thing in every hook
134
+
135
+ useTap and useDoubleTap set it at touch-down. For useTap that is a real
136
+ pressed state. For useDoubleTap it is true for every ordinary single tap on
137
+ the view too, so do not drive a pressed state from it.
138
+ useLongPress, useDrag, usePan, useSwipe, usePinch and useRotate set it at
139
+ recognition.
140
+
141
+ ## Known gaps a consumer can hit
142
+
143
+ - A cancel is reported, but its velocity is not a throw. The finger never
144
+ lifted on a cancelled gesture, so springing on velocity when cancelled is
145
+ true flings a view the user never released.
146
+ - useLongPress maxDistance cancels an ACTIVE press on web, against RNGH's own
147
+ documented contract. Hold-then-drag does not work there at the default.
148
+ - A missing <GestureHandlerRootView> fails silently and Impulse cannot warn.
149
+ - Web is surveyed by hand; no jsdom test pins any of it.
150
+
151
+ ## Out of scope, by decision
152
+
153
+ No components. No animation vocabulary, springs or easing. No haptics policy.
154
+ useDrag returns values and no style — @rootnative/inertia-gestures has a
155
+ useDrag that returns an animatedStyle and requires @rootnative/inertia.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@rootnative/impulse",
3
- "version": "0.0.0-alpha.0",
3
+ "version": "0.0.0-alpha.1",
4
4
  "description": "Declarative gesture primitives for React Native, built on react-native-gesture-handler.",
5
5
  "license": "MIT",
6
6
  "author": "RootNative",
7
- "homepage": "https://github.com/rootnative/impulse",
7
+ "homepage": "https://rootnative.github.io/impulse/",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/rootnative/impulse.git",
@@ -21,6 +21,8 @@
21
21
  "drag",
22
22
  "pinch",
23
23
  "tap",
24
+ "double-tap",
25
+ "long-press",
24
26
  "expo",
25
27
  "declarative"
26
28
  ],
@@ -53,11 +55,41 @@
53
55
  "react-native": "./dist/tap/index.js",
54
56
  "default": "./dist/tap/index.js"
55
57
  },
58
+ "./double-tap": {
59
+ "types": "./dist/double-tap/index.d.ts",
60
+ "react-native": "./dist/double-tap/index.js",
61
+ "default": "./dist/double-tap/index.js"
62
+ },
63
+ "./long-press": {
64
+ "types": "./dist/long-press/index.d.ts",
65
+ "react-native": "./dist/long-press/index.js",
66
+ "default": "./dist/long-press/index.js"
67
+ },
56
68
  "./drag": {
57
69
  "types": "./dist/drag/index.d.ts",
58
70
  "react-native": "./dist/drag/index.js",
59
71
  "default": "./dist/drag/index.js"
60
72
  },
73
+ "./pan": {
74
+ "types": "./dist/pan/index.d.ts",
75
+ "react-native": "./dist/pan/index.js",
76
+ "default": "./dist/pan/index.js"
77
+ },
78
+ "./swipe": {
79
+ "types": "./dist/swipe/index.d.ts",
80
+ "react-native": "./dist/swipe/index.js",
81
+ "default": "./dist/swipe/index.js"
82
+ },
83
+ "./pinch": {
84
+ "types": "./dist/pinch/index.d.ts",
85
+ "react-native": "./dist/pinch/index.js",
86
+ "default": "./dist/pinch/index.js"
87
+ },
88
+ "./rotate": {
89
+ "types": "./dist/rotate/index.d.ts",
90
+ "react-native": "./dist/rotate/index.js",
91
+ "default": "./dist/rotate/index.js"
92
+ },
61
93
  "./gesture-handler": {
62
94
  "types": "./dist/gesture-handler/index.d.ts",
63
95
  "react-native": "./dist/gesture-handler/index.js",
@@ -71,6 +103,7 @@
71
103
  "src",
72
104
  "jest-preset.cjs",
73
105
  "jest-setup.cjs",
106
+ "llms.txt",
74
107
  "README.md",
75
108
  "LICENSE",
76
109
  "CHANGELOG.md",
package/src/index.ts CHANGED
@@ -6,11 +6,12 @@
6
6
  * RNGH models and Impulse does not, and the `alongside` / `blocks` /
7
7
  * `deferTo` options both accept.
8
8
  *
9
- * `useTap` and `useDrag` are the two intent hooks, and the pattern the rest
10
- * follow: an intent-shaped payload, JS-thread callbacks named for what
9
+ * `useTap`, `useDoubleTap`, `useLongPress`, `useDrag`, `usePan`, `useSwipe`,
10
+ * `usePinch`, and `useRotate` are the intent hooks, and they share one
11
+ * pattern: an intent-shaped payload, JS-thread callbacks named for what
11
12
  * happened, worklet phase callbacks named for when, and documented activation
12
- * criteria. The remaining intents — `useSwipe`, `usePinch`, and the rest — are
13
- * designed but not implemented; see the repository README for the roadmap.
13
+ * criteria. The remaining intents — `useHover`, `useEdgeSwipe` — are designed
14
+ * but not implemented; see the repository README for the roadmap.
14
15
  */
15
16
 
16
17
  // Re-exported because every hook's result has to be handed to it, and
@@ -32,6 +33,19 @@ export type { RawGestureResult, UseRawGestureOptions } from './raw'
32
33
  export { useTap } from './intents/tap'
33
34
  export type { TapEvent, UseTapOptions, UseTapResult } from './intents/tap'
34
35
 
36
+ export { useDoubleTap } from './intents/double-tap'
37
+ export type {
38
+ UseDoubleTapOptions,
39
+ UseDoubleTapResult,
40
+ } from './intents/double-tap'
41
+
42
+ export { useLongPress } from './intents/long-press'
43
+ export type {
44
+ LongPressEvent,
45
+ UseLongPressOptions,
46
+ UseLongPressResult,
47
+ } from './intents/long-press'
48
+
35
49
  export { useDrag } from './intents/drag'
36
50
  export type {
37
51
  DragAxis,
@@ -41,6 +55,37 @@ export type {
41
55
  UseDragResult,
42
56
  } from './intents/drag'
43
57
 
58
+ export { usePan } from './intents/pan'
59
+ export type {
60
+ PanAxis,
61
+ PanEvent,
62
+ UsePanOptions,
63
+ UsePanResult,
64
+ } from './intents/pan'
65
+
66
+ export { useSwipe } from './intents/swipe'
67
+ export type {
68
+ CommittedSwipeEvent,
69
+ SwipeDirection,
70
+ SwipeEvent,
71
+ UseSwipeOptions,
72
+ UseSwipeResult,
73
+ } from './intents/swipe'
74
+
75
+ export { usePinch } from './intents/pinch'
76
+ export type {
77
+ PinchEvent,
78
+ UsePinchOptions,
79
+ UsePinchResult,
80
+ } from './intents/pinch'
81
+
82
+ export { useRotate } from './intents/rotate'
83
+ export type {
84
+ RotateEvent,
85
+ UseRotateOptions,
86
+ UseRotateResult,
87
+ } from './intents/rotate'
88
+
44
89
  export type {
45
90
  AttachableGesture,
46
91
  ComposeMode,
@@ -48,6 +93,7 @@ export type {
48
93
  GestureReference,
49
94
  GestureReferences,
50
95
  HitSlop,
96
+ IntentEndInfo,
51
97
  IntentResult,
52
98
  Point,
53
99
  } from './types'
@@ -0,0 +1,6 @@
1
+ export { useDoubleTap } from '../useDoubleTap'
2
+ export type {
3
+ TapEvent,
4
+ UseDoubleTapOptions,
5
+ UseDoubleTapResult,
6
+ } from '../useDoubleTap'
@@ -0,0 +1,6 @@
1
+ export { useLongPress } from '../useLongPress'
2
+ export type {
3
+ LongPressEvent,
4
+ UseLongPressOptions,
5
+ UseLongPressResult,
6
+ } from '../useLongPress'
@@ -0,0 +1,2 @@
1
+ export { usePan } from '../usePan'
2
+ export type { PanAxis, PanEvent, UsePanOptions, UsePanResult } from '../usePan'
@@ -0,0 +1,2 @@
1
+ export { usePinch } from '../usePinch'
2
+ export type { PinchEvent, UsePinchOptions, UsePinchResult } from '../usePinch'
@@ -0,0 +1,6 @@
1
+ export { useRotate } from '../useRotate'
2
+ export type {
3
+ RotateEvent,
4
+ UseRotateOptions,
5
+ UseRotateResult,
6
+ } from '../useRotate'
@@ -0,0 +1,8 @@
1
+ export { useSwipe } from '../useSwipe'
2
+ export type {
3
+ CommittedSwipeEvent,
4
+ SwipeDirection,
5
+ SwipeEvent,
6
+ UseSwipeOptions,
7
+ UseSwipeResult,
8
+ } from '../useSwipe'
@@ -0,0 +1,50 @@
1
+ import {
2
+ type GestureStateChangeEvent,
3
+ type TapGestureHandlerEventPayload,
4
+ } from 'react-native-gesture-handler'
5
+ import { type Point } from '../types'
6
+
7
+ /**
8
+ * The intent-shaped payload a tap callback receives.
9
+ *
10
+ * Shared by `useTap` and `useDoubleTap`, because the two recognize the same
11
+ * touch and differ only in how many times it happens. One payload rather than
12
+ * two identical ones means a consumer who already knows `useTap`'s event
13
+ * knows this one, and a field added later cannot reach one hook and miss the
14
+ * other.
15
+ */
16
+ export interface TapEvent {
17
+ /** X of the tap, in points, relative to the view the gesture is attached to. */
18
+ readonly x: number
19
+ /** Y of the tap, in points, relative to the view the gesture is attached to. */
20
+ readonly y: number
21
+ /**
22
+ * The same point relative to the window.
23
+ *
24
+ * Prefer it over `x` / `y` when the view itself is being transformed by the
25
+ * gesture — a tap on a view that is mid-animation reports a moving `x`.
26
+ */
27
+ readonly absolute: Point
28
+ /** How many fingers were down when the tap was recognized. */
29
+ readonly pointers: number
30
+ }
31
+
32
+ /**
33
+ * Shape RNGH's flat state-change event into the tap payload.
34
+ *
35
+ * A worklet, because every caller is one. Keeping the normalizer in one
36
+ * module means the call sites across both tap hooks cannot disagree about
37
+ * which RNGH field means what — which is the defect the intent payload exists
38
+ * to remove.
39
+ */
40
+ export function toTapEvent(
41
+ event: GestureStateChangeEvent<TapGestureHandlerEventPayload>,
42
+ ): TapEvent {
43
+ 'worklet'
44
+ return {
45
+ x: event.x,
46
+ y: event.y,
47
+ absolute: { x: event.absoluteX, y: event.absoluteY },
48
+ pointers: event.numberOfPointers,
49
+ }
50
+ }