react-native-gesture-handler 3.2.0-nightly-20260811-cc704f09a → 3.2.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.
Files changed (98) hide show
  1. package/android/build.gradle +21 -6
  2. package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +30 -66
  3. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +4 -2
  4. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +1 -2
  5. package/apple/RNGestureHandler.mm +31 -23
  6. package/apple/RNGestureHandlerButtonComponentView.mm +10 -6
  7. package/lib/module/components/GestureHandlerButton.web.js +3 -2
  8. package/lib/module/components/GestureHandlerButton.web.js.map +1 -1
  9. package/lib/module/components/ReanimatedDrawerLayout.js +3 -0
  10. package/lib/module/components/ReanimatedDrawerLayout.js.map +1 -1
  11. package/lib/module/components/touchables/TouchableOpacity.js +1 -0
  12. package/lib/module/components/touchables/TouchableOpacity.js.map +1 -1
  13. package/lib/module/handlers/hitSlop.js +101 -0
  14. package/lib/module/handlers/hitSlop.js.map +1 -0
  15. package/lib/module/handlers/utils.js +3 -7
  16. package/lib/module/handlers/utils.js.map +1 -1
  17. package/lib/module/specs/RNGestureHandlerDetectorNativeComponent.ts +1 -1
  18. package/lib/module/specs/RNGestureHandlerRootViewNativeComponent.ts +1 -1
  19. package/lib/module/v3/components/Pressable.js +23 -312
  20. package/lib/module/v3/components/Pressable.js.map +1 -1
  21. package/lib/module/v3/components/PressableWithTouchable.js +259 -0
  22. package/lib/module/v3/components/PressableWithTouchable.js.map +1 -0
  23. package/lib/module/v3/components/StatefulPressable.js +338 -0
  24. package/lib/module/v3/components/StatefulPressable.js.map +1 -0
  25. package/lib/module/v3/components/pointerStyle.js +6 -0
  26. package/lib/module/v3/components/pointerStyle.js.map +1 -0
  27. package/lib/module/v3/components/pointerStyle.web.js +9 -0
  28. package/lib/module/v3/components/pointerStyle.web.js.map +1 -0
  29. package/lib/module/v3/hooks/utils/configUtils.js +3 -1
  30. package/lib/module/v3/hooks/utils/configUtils.js.map +1 -1
  31. package/lib/module/v3/hooks/utils/reanimatedUtils.js +14 -3
  32. package/lib/module/v3/hooks/utils/reanimatedUtils.js.map +1 -1
  33. package/lib/module/web/handlers/GestureHandler.js +21 -44
  34. package/lib/module/web/handlers/GestureHandler.js.map +1 -1
  35. package/lib/module/web/interfaces.js.map +1 -1
  36. package/lib/typescript/components/GestureComponents.web.d.ts +12 -4
  37. package/lib/typescript/components/GestureComponents.web.d.ts.map +1 -1
  38. package/lib/typescript/components/GestureHandlerButton.web.d.ts.map +1 -1
  39. package/lib/typescript/components/ReanimatedDrawerLayout.d.ts.map +1 -1
  40. package/lib/typescript/components/touchables/TouchableHighlight.d.ts +1 -1
  41. package/lib/typescript/components/touchables/TouchableHighlight.d.ts.map +1 -1
  42. package/lib/typescript/components/touchables/TouchableOpacity.d.ts.map +1 -1
  43. package/lib/typescript/handlers/hitSlop.d.ts +51 -0
  44. package/lib/typescript/handlers/hitSlop.d.ts.map +1 -0
  45. package/lib/typescript/handlers/utils.d.ts.map +1 -1
  46. package/lib/typescript/mocks/hostDetector.d.ts +5 -2
  47. package/lib/typescript/mocks/hostDetector.d.ts.map +1 -1
  48. package/lib/typescript/specs/RNGestureHandlerDetectorNativeComponent.d.ts +1 -1
  49. package/lib/typescript/specs/RNGestureHandlerDetectorNativeComponent.d.ts.map +1 -1
  50. package/lib/typescript/specs/RNGestureHandlerRootViewNativeComponent.d.ts +1 -1
  51. package/lib/typescript/specs/RNGestureHandlerRootViewNativeComponent.d.ts.map +1 -1
  52. package/lib/typescript/v3/components/GestureComponents.web.d.ts +16 -8
  53. package/lib/typescript/v3/components/GestureComponents.web.d.ts.map +1 -1
  54. package/lib/typescript/v3/components/Pressable.d.ts +13 -0
  55. package/lib/typescript/v3/components/Pressable.d.ts.map +1 -1
  56. package/lib/typescript/v3/components/PressableWithTouchable.d.ts +5 -0
  57. package/lib/typescript/v3/components/PressableWithTouchable.d.ts.map +1 -0
  58. package/lib/typescript/v3/components/StatefulPressable.d.ts +5 -0
  59. package/lib/typescript/v3/components/StatefulPressable.d.ts.map +1 -0
  60. package/lib/typescript/v3/components/pointerStyle.d.ts +3 -0
  61. package/lib/typescript/v3/components/pointerStyle.d.ts.map +1 -0
  62. package/lib/typescript/v3/components/pointerStyle.web.d.ts +3 -0
  63. package/lib/typescript/v3/components/pointerStyle.web.d.ts.map +1 -0
  64. package/lib/typescript/v3/detectors/ReanimatedNativeDetector.d.ts +3 -1
  65. package/lib/typescript/v3/detectors/ReanimatedNativeDetector.d.ts.map +1 -1
  66. package/lib/typescript/v3/detectors/ReanimatedNativeDetector.web.d.ts +3 -1
  67. package/lib/typescript/v3/detectors/ReanimatedNativeDetector.web.d.ts.map +1 -1
  68. package/lib/typescript/v3/detectors/common.d.ts +2 -2
  69. package/lib/typescript/v3/detectors/common.d.ts.map +1 -1
  70. package/lib/typescript/v3/hooks/utils/configUtils.d.ts.map +1 -1
  71. package/lib/typescript/v3/hooks/utils/reanimatedUtils.d.ts.map +1 -1
  72. package/lib/typescript/v3/types/ConfigTypes.d.ts +2 -1
  73. package/lib/typescript/v3/types/ConfigTypes.d.ts.map +1 -1
  74. package/lib/typescript/v3/types/EventTypes.d.ts +3 -2
  75. package/lib/typescript/v3/types/EventTypes.d.ts.map +1 -1
  76. package/lib/typescript/web/handlers/GestureHandler.d.ts +0 -1
  77. package/lib/typescript/web/handlers/GestureHandler.d.ts.map +1 -1
  78. package/lib/typescript/web/interfaces.d.ts +3 -12
  79. package/lib/typescript/web/interfaces.d.ts.map +1 -1
  80. package/package.json +6 -6
  81. package/src/components/GestureHandlerButton.web.tsx +3 -2
  82. package/src/components/ReanimatedDrawerLayout.tsx +3 -0
  83. package/src/components/touchables/TouchableOpacity.tsx +1 -0
  84. package/src/handlers/hitSlop.ts +146 -0
  85. package/src/handlers/utils.ts +4 -2
  86. package/src/specs/RNGestureHandlerDetectorNativeComponent.ts +1 -1
  87. package/src/specs/RNGestureHandlerRootViewNativeComponent.ts +1 -1
  88. package/src/v3/components/Pressable.tsx +28 -448
  89. package/src/v3/components/PressableWithTouchable.tsx +326 -0
  90. package/src/v3/components/StatefulPressable.tsx +476 -0
  91. package/src/v3/components/pointerStyle.ts +5 -0
  92. package/src/v3/components/pointerStyle.web.ts +6 -0
  93. package/src/v3/hooks/utils/configUtils.ts +7 -1
  94. package/src/v3/hooks/utils/reanimatedUtils.ts +17 -8
  95. package/src/v3/types/ConfigTypes.ts +2 -1
  96. package/src/v3/types/EventTypes.ts +7 -3
  97. package/src/web/handlers/GestureHandler.ts +26 -77
  98. package/src/web/interfaces.ts +3 -13
@@ -0,0 +1,326 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import type { Insets, LayoutChangeEvent } from 'react-native';
3
+
4
+ import type { ButtonEvent } from '../../components/GestureHandlerButton';
5
+ import type {
6
+ InnerPressableEvent,
7
+ PressableDimensions,
8
+ PressableProps,
9
+ } from '../../components/Pressable/PressableProps';
10
+ import {
11
+ addInsets,
12
+ isTouchWithinInset,
13
+ numberAsInset,
14
+ } from '../../components/Pressable/utils';
15
+ import { PressabilityDebugView } from '../../handlers/PressabilityDebugView';
16
+ import { pointerStyle } from './pointerStyle';
17
+ import { Touchable } from './Touchable/Touchable';
18
+
19
+ // RN's Pressable default. Touchable's own default is 600ms
20
+ const DEFAULT_LONG_PRESS_DURATION = 500;
21
+
22
+ type Timers = {
23
+ press: ReturnType<typeof setTimeout> | null;
24
+ hoverIn: ReturnType<typeof setTimeout> | null;
25
+ hoverOut: ReturnType<typeof setTimeout> | null;
26
+ };
27
+
28
+ function normalizeInset(value: Insets | number | null | undefined): Insets {
29
+ return typeof value === 'number'
30
+ ? numberAsInset(value)
31
+ : (value ?? numberAsInset(0));
32
+ }
33
+
34
+ function buttonToInner(event: ButtonEvent): InnerPressableEvent {
35
+ return {
36
+ identifier: 0,
37
+ locationX: event.x,
38
+ locationY: event.y,
39
+ pageX: event.absoluteX,
40
+ pageY: event.absoluteY,
41
+ target: 0,
42
+ timestamp: Date.now(),
43
+ touches: [],
44
+ changedTouches: [],
45
+ force: undefined,
46
+ };
47
+ }
48
+
49
+ function buttonToPressableEvent(event: ButtonEvent) {
50
+ const inner = buttonToInner(event);
51
+
52
+ return {
53
+ nativeEvent: {
54
+ ...inner,
55
+ touches: [inner],
56
+ changedTouches: [inner],
57
+ },
58
+ };
59
+ }
60
+
61
+ const PressableWithTouchable = (props: PressableProps) => {
62
+ const {
63
+ testOnly_pressed,
64
+ hitSlop,
65
+ pressRetentionOffset,
66
+ delayHoverIn,
67
+ delayHoverOut,
68
+ delayLongPress,
69
+ unstable_pressDelay,
70
+ onHoverIn,
71
+ onHoverOut,
72
+ onPress,
73
+ onPressIn,
74
+ onPressOut,
75
+ onLongPress,
76
+ onLayout,
77
+ style,
78
+ children,
79
+ android_disableSound,
80
+ android_ripple,
81
+ disabled,
82
+ accessible,
83
+ ref,
84
+ ...rest
85
+ } = props;
86
+
87
+ // Drop props Touchable doesn't take: `cancelable`/`dimensionsAfterResize` are
88
+ // unsupported; the relation props are handled by the wrapper.
89
+ //
90
+ /* eslint-disable @typescript-eslint/no-unused-vars */
91
+ const {
92
+ cancelable,
93
+ dimensionsAfterResize,
94
+ simultaneousWith,
95
+ requireToFail,
96
+ block,
97
+ ...remainingProps
98
+ } = rest;
99
+ /* eslint-enable @typescript-eslint/no-unused-vars */
100
+
101
+ const [pressed, setPressed] = useState(false);
102
+ const timers = useRef<Timers>({ press: null, hoverIn: null, hoverOut: null });
103
+ const dimensions = useRef<PressableDimensions>({ width: 0, height: 0 });
104
+
105
+ // Whether the in-progress press activated within hitSlop (see handlePressIn).
106
+ const isActive = useRef(false);
107
+
108
+ useEffect(
109
+ () => () => {
110
+ const pending = timers.current;
111
+
112
+ if (pending.press) {
113
+ clearTimeout(pending.press);
114
+ }
115
+
116
+ if (pending.hoverIn) {
117
+ clearTimeout(pending.hoverIn);
118
+ }
119
+
120
+ if (pending.hoverOut) {
121
+ clearTimeout(pending.hoverOut);
122
+ }
123
+ },
124
+ []
125
+ );
126
+
127
+ // RN's ripple config allows `null` on every field; Touchable's excludes it.
128
+ // Normalize `null` → `undefined` so the types line up.
129
+ const androidRipple = android_ripple
130
+ ? {
131
+ color: android_ripple.color ?? undefined,
132
+ borderless: android_ripple.borderless ?? undefined,
133
+ radius: android_ripple.radius ?? undefined,
134
+ foreground: android_ripple.foreground ?? undefined,
135
+ }
136
+ : undefined;
137
+
138
+ // Activation is gated to `normalizedHitSlop` (see handlePressIn); the native
139
+ // button gets the wider `appliedHitSlop` so an active press is retained out
140
+ // to hitSlop + pressRetentionOffset before it cancels.
141
+ const normalizedHitSlop = normalizeInset(hitSlop);
142
+ const appliedHitSlop = addInsets(
143
+ normalizedHitSlop,
144
+ normalizeInset(pressRetentionOffset)
145
+ );
146
+
147
+ // Long press is measured from onPressIn, which `unstable_pressDelay` defers,
148
+ // so fold it in to match RN / StatefulPressable.
149
+ const resolvedDelayLongPress =
150
+ (delayLongPress ?? DEFAULT_LONG_PRESS_DURATION) +
151
+ (unstable_pressDelay ?? 0);
152
+
153
+ const handleLayout = (event: LayoutChangeEvent) => {
154
+ onLayout?.(event);
155
+ dimensions.current = event.nativeEvent.layout;
156
+ };
157
+
158
+ const firePressIn = (event: ButtonEvent) => {
159
+ setPressed(true);
160
+ onPressIn?.(buttonToPressableEvent(event));
161
+ };
162
+
163
+ const handlePressIn = (event: ButtonEvent) => {
164
+ // A down in the retention-only zone is held by the button but isn't a press.
165
+ isActive.current = isTouchWithinInset(
166
+ dimensions.current,
167
+ normalizedHitSlop,
168
+ buttonToInner(event)
169
+ );
170
+
171
+ if (!isActive.current) {
172
+ return;
173
+ }
174
+
175
+ if (unstable_pressDelay) {
176
+ // Drop a still-pending timer so a re-entrant press can't double-fire.
177
+ if (timers.current.press) {
178
+ clearTimeout(timers.current.press);
179
+ }
180
+
181
+ timers.current.press = setTimeout(() => {
182
+ timers.current.press = null;
183
+ firePressIn(event);
184
+ }, unstable_pressDelay);
185
+ return;
186
+ }
187
+
188
+ firePressIn(event);
189
+ };
190
+
191
+ const handlePressOut = (event: ButtonEvent) => {
192
+ // Not cleared here: onPress fires after onPressOut and must stay suppressed
193
+ // too; isActive resets on the next press-in.
194
+ if (!isActive.current) {
195
+ return;
196
+ }
197
+
198
+ // If the touch is released before `unstable_pressDelay` elapses, RN still
199
+ // emits the deferred `onPressIn` before `onPressOut` — flush it now.
200
+ if (timers.current.press) {
201
+ clearTimeout(timers.current.press);
202
+ timers.current.press = null;
203
+ firePressIn(event);
204
+ }
205
+
206
+ setPressed(false);
207
+ onPressOut?.(buttonToPressableEvent(event));
208
+ };
209
+
210
+ const handlePress = onPress
211
+ ? (event: ButtonEvent) => {
212
+ if (isActive.current) {
213
+ onPress(buttonToPressableEvent(event));
214
+ }
215
+ }
216
+ : undefined;
217
+
218
+ const handleLongPress = onLongPress
219
+ ? (event: ButtonEvent) => {
220
+ if (!isActive.current) {
221
+ return;
222
+ }
223
+ // Flush the deferred onPressIn so it can't arrive after onLongPress
224
+ // (e.g. delayLongPress={0} makes the two timers coincide).
225
+ if (timers.current.press) {
226
+ clearTimeout(timers.current.press);
227
+ timers.current.press = null;
228
+ firePressIn(event);
229
+ }
230
+ onLongPress(buttonToPressableEvent(event));
231
+ }
232
+ : undefined;
233
+
234
+ // Wire each handler whenever the opposite side can leave a pending timer, so a
235
+ // delayed hover callback is cancelled once the pointer leaves/re-enters.
236
+ const needsHoverIn =
237
+ onHoverIn != null || (onHoverOut != null && !!delayHoverOut);
238
+ const needsHoverOut =
239
+ onHoverOut != null || (onHoverIn != null && !!delayHoverIn);
240
+
241
+ const handleHoverIn = needsHoverIn
242
+ ? (event: ButtonEvent) => {
243
+ if (timers.current.hoverOut) {
244
+ clearTimeout(timers.current.hoverOut);
245
+ timers.current.hoverOut = null;
246
+ }
247
+
248
+ if (!onHoverIn) {
249
+ return;
250
+ }
251
+
252
+ if (delayHoverIn) {
253
+ timers.current.hoverIn = setTimeout(() => {
254
+ timers.current.hoverIn = null;
255
+ onHoverIn(buttonToPressableEvent(event));
256
+ }, delayHoverIn);
257
+ return;
258
+ }
259
+
260
+ onHoverIn(buttonToPressableEvent(event));
261
+ }
262
+ : undefined;
263
+
264
+ const handleHoverOut = needsHoverOut
265
+ ? (event: ButtonEvent) => {
266
+ if (timers.current.hoverIn) {
267
+ clearTimeout(timers.current.hoverIn);
268
+ timers.current.hoverIn = null;
269
+ }
270
+
271
+ if (!onHoverOut) {
272
+ return;
273
+ }
274
+
275
+ if (delayHoverOut) {
276
+ timers.current.hoverOut = setTimeout(() => {
277
+ timers.current.hoverOut = null;
278
+ onHoverOut(buttonToPressableEvent(event));
279
+ }, delayHoverOut);
280
+ return;
281
+ }
282
+
283
+ onHoverOut(buttonToPressableEvent(event));
284
+ }
285
+ : undefined;
286
+
287
+ // `testOnly_pressed` forces the pressed state for snapshots/tests. Derive the
288
+ // displayed value from it each render, keeping the interactive `pressed` state
289
+ // independent (seeded to false) so clearing the prop doesn't leave it stuck.
290
+ const displayPressed = testOnly_pressed ?? pressed;
291
+
292
+ const resolvedStyle =
293
+ typeof style === 'function' ? style({ pressed: displayPressed }) : style;
294
+
295
+ const resolvedChildren =
296
+ typeof children === 'function'
297
+ ? children({ pressed: displayPressed })
298
+ : children;
299
+
300
+ return (
301
+ <Touchable
302
+ {...remainingProps}
303
+ ref={ref}
304
+ accessible={accessible !== false}
305
+ disabled={disabled === true}
306
+ hitSlop={appliedHitSlop}
307
+ onLayout={handleLayout}
308
+ androidRipple={androidRipple}
309
+ touchSoundDisabled={android_disableSound ?? undefined}
310
+ delayLongPress={resolvedDelayLongPress}
311
+ style={[pointerStyle, resolvedStyle]}
312
+ onPressIn={handlePressIn}
313
+ onPressOut={handlePressOut}
314
+ onPress={handlePress}
315
+ onLongPress={handleLongPress}
316
+ onHoverIn={handleHoverIn}
317
+ onHoverOut={handleHoverOut}>
318
+ {resolvedChildren}
319
+ {__DEV__ ? (
320
+ <PressabilityDebugView color="red" hitSlop={normalizedHitSlop} />
321
+ ) : null}
322
+ </Touchable>
323
+ );
324
+ };
325
+
326
+ export default PressableWithTouchable;