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,476 @@
1
+ import React, {
2
+ use,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import type {
10
+ Insets,
11
+ LayoutChangeEvent,
12
+ StyleProp,
13
+ ViewStyle,
14
+ } from 'react-native';
15
+ import { Platform } from 'react-native';
16
+
17
+ import type {
18
+ PressableDimensions,
19
+ PressableEvent,
20
+ PressableProps,
21
+ } from '../../components/Pressable/PressableProps';
22
+ import {
23
+ getStatesConfig,
24
+ StateMachineEvent,
25
+ } from '../../components/Pressable/stateDefinitions';
26
+ import { PressableStateMachine } from '../../components/Pressable/StateMachine';
27
+ import {
28
+ addInsets,
29
+ gestureToPressableEvent,
30
+ gestureTouchToPressableEvent,
31
+ isTouchWithinInset,
32
+ numberAsInset,
33
+ viewCenterToPressableEvent,
34
+ } from '../../components/Pressable/utils';
35
+ import { getTVProps } from '../../components/utils';
36
+ import { PressabilityDebugView } from '../../handlers/PressabilityDebugView';
37
+ import { useIsScreenReaderEnabled } from '../../useIsScreenReaderEnabled';
38
+ import { INT32_MAX, isTestEnv } from '../../utils';
39
+ import { GestureDetector } from '../detectors';
40
+ import {
41
+ useHoverGesture,
42
+ useLongPressGesture,
43
+ useNativeGesture,
44
+ useSimultaneousGestures,
45
+ } from '../hooks';
46
+ import {
47
+ isKeyboardDismissingTap,
48
+ JSResponderContext,
49
+ } from '../scrollViewInterop';
50
+ import { PureNativeButton } from './GestureButtons';
51
+
52
+ const DEFAULT_LONG_PRESS_DURATION = 500;
53
+ const IS_TEST_ENV = isTestEnv();
54
+
55
+ const StatefulPressable = (props: PressableProps) => {
56
+ const {
57
+ testOnly_pressed,
58
+ hitSlop,
59
+ pressRetentionOffset,
60
+ delayHoverIn,
61
+ delayHoverOut,
62
+ delayLongPress,
63
+ unstable_pressDelay,
64
+ onHoverIn,
65
+ onHoverOut,
66
+ onPress,
67
+ onPressIn,
68
+ onPressOut,
69
+ onLongPress,
70
+ onLayout,
71
+ style,
72
+ children,
73
+ android_disableSound,
74
+ android_ripple,
75
+ disabled,
76
+ accessible,
77
+ simultaneousWith,
78
+ requireToFail,
79
+ block,
80
+ ref,
81
+ ...remainingProps
82
+ } = props;
83
+
84
+ const [pressedState, setPressedState] = useState(false);
85
+
86
+ const longPressTimeoutRef = useRef<number | null>(null);
87
+ const pressDelayTimeoutRef = useRef<number | null>(null);
88
+ const isOnPressAllowed = useRef<boolean>(true);
89
+ const jsResponderContext = use(JSResponderContext);
90
+ const isCurrentlyPressed = useRef<boolean>(false);
91
+ const dimensions = useRef<PressableDimensions>({
92
+ width: 0,
93
+ height: 0,
94
+ });
95
+
96
+ // When the touch that begins a press is the one dismissing the keyboard
97
+ // (keyboardShouldPersistTaps="never"), the press is swallowed to match RN's
98
+ // touchables.
99
+ const dropKeyboardTapRef = useRef<boolean | null>(null);
100
+
101
+ const normalizedHitSlop: Insets = useMemo(
102
+ () =>
103
+ typeof hitSlop === 'number'
104
+ ? numberAsInset(hitSlop)
105
+ : (hitSlop ?? numberAsInset(0)),
106
+ [hitSlop]
107
+ );
108
+ const normalizedPressRetentionOffset: Insets = useMemo(
109
+ () =>
110
+ typeof pressRetentionOffset === 'number'
111
+ ? numberAsInset(pressRetentionOffset)
112
+ : (pressRetentionOffset ?? {}),
113
+ [pressRetentionOffset]
114
+ );
115
+ const appliedHitSlop = addInsets(
116
+ normalizedHitSlop,
117
+ normalizedPressRetentionOffset
118
+ );
119
+
120
+ const cancelLongPress = useCallback(() => {
121
+ if (longPressTimeoutRef.current) {
122
+ clearTimeout(longPressTimeoutRef.current);
123
+ longPressTimeoutRef.current = null;
124
+ isOnPressAllowed.current = true;
125
+ }
126
+ }, []);
127
+
128
+ const cancelDelayedPress = useCallback(() => {
129
+ if (pressDelayTimeoutRef.current) {
130
+ clearTimeout(pressDelayTimeoutRef.current);
131
+ pressDelayTimeoutRef.current = null;
132
+ }
133
+ }, []);
134
+
135
+ const startLongPress = useCallback(
136
+ (event: PressableEvent) => {
137
+ if (onLongPress) {
138
+ cancelLongPress();
139
+ longPressTimeoutRef.current = setTimeout(() => {
140
+ isOnPressAllowed.current = false;
141
+ onLongPress(event);
142
+ }, delayLongPress ?? DEFAULT_LONG_PRESS_DURATION);
143
+ }
144
+ },
145
+ [onLongPress, cancelLongPress, delayLongPress]
146
+ );
147
+ const innerHandlePressIn = useCallback(
148
+ (event: PressableEvent) => {
149
+ onPressIn?.(event);
150
+ startLongPress(event);
151
+ setPressedState(true);
152
+ if (pressDelayTimeoutRef.current) {
153
+ clearTimeout(pressDelayTimeoutRef.current);
154
+ pressDelayTimeoutRef.current = null;
155
+ }
156
+ },
157
+ [onPressIn, startLongPress]
158
+ );
159
+
160
+ const handleFinalize = useCallback(() => {
161
+ isCurrentlyPressed.current = false;
162
+ dropKeyboardTapRef.current = null;
163
+ cancelLongPress();
164
+ cancelDelayedPress();
165
+ setPressedState(false);
166
+ }, [cancelDelayedPress, cancelLongPress]);
167
+
168
+ const captureKeyboardDismiss = useCallback(() => {
169
+ dropKeyboardTapRef.current ??= isKeyboardDismissingTap(jsResponderContext);
170
+ }, [jsResponderContext]);
171
+
172
+ const handlePressIn = useCallback(
173
+ (event: PressableEvent, skipBoundsCheck = false) => {
174
+ if (
175
+ !skipBoundsCheck &&
176
+ !isTouchWithinInset(
177
+ dimensions.current,
178
+ normalizedHitSlop,
179
+ event.nativeEvent.changedTouches.at(-1)
180
+ )
181
+ ) {
182
+ // Ignoring pressIn within pressRetentionOffset
183
+ return;
184
+ }
185
+
186
+ isCurrentlyPressed.current = true;
187
+ if (unstable_pressDelay) {
188
+ pressDelayTimeoutRef.current = setTimeout(() => {
189
+ innerHandlePressIn(event);
190
+ }, unstable_pressDelay);
191
+ } else {
192
+ innerHandlePressIn(event);
193
+ }
194
+ },
195
+ [innerHandlePressIn, normalizedHitSlop, unstable_pressDelay]
196
+ );
197
+
198
+ const handlePressOut = useCallback(
199
+ (event: PressableEvent, success: boolean = true) => {
200
+ if (!isCurrentlyPressed.current) {
201
+ // Some prop configurations may lead to handlePressOut being called multiple times.
202
+ return;
203
+ }
204
+
205
+ isCurrentlyPressed.current = false;
206
+
207
+ if (pressDelayTimeoutRef.current) {
208
+ innerHandlePressIn(event);
209
+ }
210
+
211
+ onPressOut?.(event);
212
+
213
+ if (isOnPressAllowed.current && success) {
214
+ onPress?.(event);
215
+ }
216
+
217
+ handleFinalize();
218
+ },
219
+ [handleFinalize, innerHandlePressIn, onPress, onPressOut]
220
+ );
221
+
222
+ const stateMachine = useMemo(() => new PressableStateMachine(), []);
223
+ const isScreenReaderEnabled = useIsScreenReaderEnabled();
224
+
225
+ useEffect(() => {
226
+ const configuration = getStatesConfig(
227
+ handlePressIn,
228
+ handlePressOut,
229
+ isScreenReaderEnabled
230
+ );
231
+ stateMachine.setStates(configuration);
232
+ }, [handlePressIn, handlePressOut, stateMachine, isScreenReaderEnabled]);
233
+
234
+ const hoverInTimeout = useRef<number | null>(null);
235
+ const hoverOutTimeout = useRef<number | null>(null);
236
+
237
+ useEffect(
238
+ () => () => {
239
+ if (longPressTimeoutRef.current) {
240
+ clearTimeout(longPressTimeoutRef.current);
241
+ }
242
+ if (pressDelayTimeoutRef.current) {
243
+ clearTimeout(pressDelayTimeoutRef.current);
244
+ }
245
+ if (hoverInTimeout.current) {
246
+ clearTimeout(hoverInTimeout.current);
247
+ }
248
+ if (hoverOutTimeout.current) {
249
+ clearTimeout(hoverOutTimeout.current);
250
+ }
251
+ },
252
+ []
253
+ );
254
+
255
+ const hoverGesture = useHoverGesture({
256
+ manualActivation: true, // Prevents Hover blocking Native gesture on web
257
+ cancelsTouchesInView: false,
258
+ onBegin: (event) => {
259
+ if (hoverOutTimeout.current) {
260
+ clearTimeout(hoverOutTimeout.current);
261
+ }
262
+ if (delayHoverIn) {
263
+ hoverInTimeout.current = setTimeout(
264
+ () => onHoverIn?.(gestureToPressableEvent(event)),
265
+ delayHoverIn
266
+ );
267
+ return;
268
+ }
269
+ onHoverIn?.(gestureToPressableEvent(event));
270
+ },
271
+ onFinalize: (event) => {
272
+ if (hoverInTimeout.current) {
273
+ clearTimeout(hoverInTimeout.current);
274
+ }
275
+ if (delayHoverOut) {
276
+ hoverOutTimeout.current = setTimeout(
277
+ () => onHoverOut?.(gestureToPressableEvent(event)),
278
+ delayHoverOut
279
+ );
280
+ return;
281
+ }
282
+ onHoverOut?.(gestureToPressableEvent(event));
283
+ },
284
+ enabled: disabled !== true,
285
+ disableReanimated: true,
286
+ simultaneousWith,
287
+ block,
288
+ requireToFail,
289
+ hitSlop: appliedHitSlop,
290
+ });
291
+
292
+ const pressAndTouchGesture = useLongPressGesture({
293
+ minDuration: Platform.OS === 'web' ? 0 : INT32_MAX, // Long press handles finalize on web, thus it must activate right away
294
+ maxDistance: INT32_MAX, // Stops long press from cancelling on touch move
295
+ cancelsTouchesInView: false,
296
+ onTouchesDown: (event) => {
297
+ captureKeyboardDismiss();
298
+
299
+ if (dropKeyboardTapRef.current) {
300
+ return;
301
+ }
302
+
303
+ const pressableEvent = gestureTouchToPressableEvent(event);
304
+ stateMachine.handleEvent(
305
+ StateMachineEvent.LONG_PRESS_TOUCHES_DOWN,
306
+ pressableEvent
307
+ );
308
+ },
309
+ onTouchesUp: () => {
310
+ if (Platform.OS === 'android' && !isScreenReaderEnabled) {
311
+ // Prevents potential soft-locks
312
+ stateMachine.reset();
313
+ handleFinalize();
314
+ }
315
+ },
316
+ onTouchesCancel: (event) => {
317
+ const pressableEvent = gestureTouchToPressableEvent(event);
318
+ stateMachine.reset();
319
+ handlePressOut(pressableEvent, false);
320
+ },
321
+ onFinalize: (event) => {
322
+ if (Platform.OS !== 'web') {
323
+ return;
324
+ }
325
+
326
+ stateMachine.handleEvent(
327
+ event.canceled ? StateMachineEvent.CANCEL : StateMachineEvent.FINALIZE
328
+ );
329
+
330
+ handleFinalize();
331
+ },
332
+ enabled: disabled !== true,
333
+ disableReanimated: true,
334
+ simultaneousWith: simultaneousWith,
335
+ block: block,
336
+ requireToFail: requireToFail,
337
+ hitSlop: appliedHitSlop,
338
+ });
339
+
340
+ // RNButton is placed inside ButtonGesture to enable Android's ripple and to capture non-propagating events
341
+ const buttonGesture = useNativeGesture({
342
+ onTouchesCancel: (event) => {
343
+ if (Platform.OS !== 'macos' && Platform.OS !== 'web') {
344
+ // On MacOS cancel occurs in middle of gesture
345
+ // On Web cancel occurs on mouse move, which is unwanted
346
+ const pressableEvent = gestureTouchToPressableEvent(event);
347
+ stateMachine.reset();
348
+ handlePressOut(pressableEvent, false);
349
+ }
350
+ },
351
+ onBegin: () => {
352
+ captureKeyboardDismiss();
353
+
354
+ if (dropKeyboardTapRef.current) {
355
+ return;
356
+ }
357
+
358
+ if (Platform.isTV) {
359
+ // tvOS drives this native gesture from the focus-engine Select press.
360
+ // The press state machine is touch-based and never
361
+ // receives LONG_PRESS_TOUCHES_DOWN here, so bypass it and drive the press handlers directly.
362
+ // A focus-driven press has no coordinates, so skip the hit-slop bounds check entirely.
363
+ handlePressIn(viewCenterToPressableEvent(dimensions.current), true);
364
+ return;
365
+ }
366
+ if (Platform.OS === 'android' && isScreenReaderEnabled) {
367
+ stateMachine.handleEvent(
368
+ StateMachineEvent.NATIVE_BEGIN,
369
+ viewCenterToPressableEvent(dimensions.current)
370
+ );
371
+ return;
372
+ }
373
+ stateMachine.handleEvent(StateMachineEvent.NATIVE_BEGIN);
374
+ },
375
+ onActivate: () => {
376
+ if (!Platform.isTV && Platform.OS !== 'android') {
377
+ stateMachine.handleEvent(StateMachineEvent.NATIVE_START);
378
+ }
379
+ },
380
+ onFinalize: (event) => {
381
+ // On Web we use LongPress.onFinalize instead of Native.onFinalize,
382
+ // as Native cancels on mouse move, and LongPress does not.
383
+ if (Platform.OS === 'web') {
384
+ return;
385
+ }
386
+
387
+ if (Platform.isTV) {
388
+ handlePressOut(
389
+ viewCenterToPressableEvent(dimensions.current),
390
+ !event.canceled
391
+ );
392
+ handleFinalize();
393
+ return;
394
+ }
395
+
396
+ stateMachine.handleEvent(
397
+ event.canceled ? StateMachineEvent.CANCEL : StateMachineEvent.FINALIZE
398
+ );
399
+
400
+ handleFinalize();
401
+ },
402
+ enabled: disabled !== true,
403
+ disableReanimated: true,
404
+ simultaneousWith,
405
+ block,
406
+ requireToFail,
407
+ hitSlop: appliedHitSlop,
408
+ shouldActivateOnStart: Platform.OS === 'web',
409
+ });
410
+
411
+ const gesture = useSimultaneousGestures(
412
+ buttonGesture,
413
+ pressAndTouchGesture,
414
+ hoverGesture
415
+ );
416
+
417
+ // `cursor: 'pointer'` on `RNButton` crashes iOS
418
+ const pointerStyle: StyleProp<ViewStyle> =
419
+ Platform.OS === 'web' ? { cursor: 'pointer' } : {};
420
+
421
+ // `testOnly_pressed` forces the pressed state for snapshots/tests. Derive the
422
+ // displayed value from it each render, keeping the interactive `pressedState`
423
+ // independent (seeded to false) so clearing the prop doesn't leave it stuck.
424
+ const displayPressed = testOnly_pressed ?? pressedState;
425
+
426
+ const styleProp =
427
+ typeof style === 'function' ? style({ pressed: displayPressed }) : style;
428
+
429
+ const childrenProp =
430
+ typeof children === 'function'
431
+ ? children({ pressed: displayPressed })
432
+ : children;
433
+
434
+ const rippleColor = useMemo(() => {
435
+ const defaultRippleColor = android_ripple ? undefined : 'transparent';
436
+ return android_ripple?.color ?? defaultRippleColor;
437
+ }, [android_ripple]);
438
+
439
+ const setDimensions = useCallback(
440
+ (event: LayoutChangeEvent) => {
441
+ onLayout?.(event);
442
+ dimensions.current = event.nativeEvent.layout;
443
+ },
444
+ [onLayout]
445
+ );
446
+
447
+ const tvProps = getTVProps(remainingProps);
448
+
449
+ return (
450
+ <GestureDetector gesture={gesture}>
451
+ <PureNativeButton
452
+ {...remainingProps}
453
+ ref={ref as React.Ref<React.ComponentRef<typeof PureNativeButton>>}
454
+ {...tvProps}
455
+ onLayout={setDimensions}
456
+ accessible={accessible !== false}
457
+ hitSlop={appliedHitSlop}
458
+ enabled={disabled !== true}
459
+ touchSoundDisabled={android_disableSound ?? undefined}
460
+ rippleColor={rippleColor}
461
+ rippleRadius={android_ripple?.radius ?? undefined}
462
+ style={[pointerStyle, styleProp]}
463
+ testOnly_onPress={IS_TEST_ENV ? onPress : undefined}
464
+ testOnly_onPressIn={IS_TEST_ENV ? onPressIn : undefined}
465
+ testOnly_onPressOut={IS_TEST_ENV ? onPressOut : undefined}
466
+ testOnly_onLongPress={IS_TEST_ENV ? onLongPress : undefined}>
467
+ {childrenProp}
468
+ {__DEV__ ? (
469
+ <PressabilityDebugView color="red" hitSlop={normalizedHitSlop} />
470
+ ) : null}
471
+ </PureNativeButton>
472
+ </GestureDetector>
473
+ );
474
+ };
475
+
476
+ export default StatefulPressable;
@@ -0,0 +1,5 @@
1
+ import type { ViewStyle } from 'react-native';
2
+
3
+ // A pointer cursor only applies on the web (see the `.web` counterpart); other
4
+ // platforms contribute nothing.
5
+ export const pointerStyle: ViewStyle = {};
@@ -0,0 +1,6 @@
1
+ import type { ViewStyle } from 'react-native';
2
+
3
+ // react-native-web sets `cursor: 'pointer'` on its interactive components
4
+ // (Pressable, TouchableOpacity, …) but not on `View`, which the native button
5
+ // renders — so the Touchable-based Pressable adds it here to match RN Pressable.
6
+ export const pointerStyle: ViewStyle = { cursor: 'pointer' };
@@ -1,6 +1,8 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
+ import type { HitSlop } from '../../../handlers/gestureHandlerCommon';
3
4
  import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper';
5
+ import { normalizeHitSlop } from '../../../handlers/hitSlop';
4
6
  import { isTestEnv, tagMessage } from '../../../utils';
5
7
  import type {
6
8
  BaseGestureConfig,
@@ -108,8 +110,12 @@ export function prepareConfigForNativeSide<
108
110
  for (const [key, value] of Object.entries(config)) {
109
111
  // @ts-ignore That's the point, we want to see if key exists in the whitelists
110
112
  if (allowedNativeProps.has(key) || handlerPropsWhiteList.has(key)) {
113
+ const unpackedValue = maybeUnpackValue(value);
114
+
111
115
  (filteredConfig as Record<string, unknown>)[key] =
112
- Reanimated?.isSharedValue(value) ? value.value : value;
116
+ key === 'hitSlop'
117
+ ? normalizeHitSlop(unpackedValue as HitSlop)
118
+ : unpackedValue;
113
119
  } else if (PropsToFilter.has(key)) {
114
120
  continue;
115
121
  } else {
@@ -1,7 +1,9 @@
1
+ import type { HitSlop } from '../../../handlers/gestureHandlerCommon';
1
2
  import {
2
3
  Reanimated,
3
4
  Worklets,
4
5
  } from '../../../handlers/gestures/reanimatedWrapper';
6
+ import { normalizeHitSlop } from '../../../handlers/hitSlop';
5
7
  import { NativeProxy } from '../../NativeProxy';
6
8
  import type {
7
9
  BaseGestureConfig,
@@ -53,14 +55,21 @@ export function bindSharedValues<
53
55
  const listenerId = baseListenerId + keyHash;
54
56
 
55
57
  sharedValue.addListener(listenerId, (value) => {
56
- updateGestureHandlerConfig(
57
- handlerTag,
58
- configKey === 'runOnJS'
59
- ? {
60
- dispatchesReanimatedEvents: shouldUseReanimatedDetector && !value,
61
- }
62
- : { [configKey]: value }
63
- );
58
+ if (configKey === 'runOnJS') {
59
+ updateGestureHandlerConfig(handlerTag, {
60
+ dispatchesReanimatedEvents: shouldUseReanimatedDetector && !value,
61
+ });
62
+ return;
63
+ }
64
+
65
+ if (configKey === 'hitSlop') {
66
+ updateGestureHandlerConfig(handlerTag, {
67
+ hitSlop: normalizeHitSlop(value as HitSlop),
68
+ });
69
+ return;
70
+ }
71
+
72
+ updateGestureHandlerConfig(handlerTag, { [configKey]: value });
64
73
  });
65
74
  };
66
75
 
@@ -6,6 +6,7 @@ import type {
6
6
  TouchAction,
7
7
  UserSelect,
8
8
  } from '../../handlers/gestureHandlerCommon';
9
+ import type { NormalizedHitSlop } from '../../handlers/hitSlop';
9
10
  import type {
10
11
  AnimatedEvent,
11
12
  ChangeCalculatorType,
@@ -73,7 +74,7 @@ export type CommonGestureConfig = {
73
74
  runOnJS?: boolean | undefined;
74
75
  enabled?: boolean | undefined;
75
76
  shouldCancelWhenOutside?: boolean | undefined;
76
- hitSlop?: HitSlop | undefined;
77
+ hitSlop?: HitSlop | NormalizedHitSlop | undefined;
77
78
  activeCursor?: ActiveCursor | undefined;
78
79
  mouseButton?: MouseButton | undefined;
79
80
  cancelsTouchesInView?: boolean | undefined;
@@ -1,10 +1,14 @@
1
- import type { BaseSyntheticEvent } from 'react';
2
-
3
1
  import type { GestureTouchEvent } from '../../handlers/gestureHandlerCommon';
4
2
  import type { PointerType } from '../../PointerType';
5
3
  import type { State } from '../../State';
6
4
 
7
- export type NativeEventWrapper<T> = BaseSyntheticEvent<T, unknown, unknown>;
5
+ // Structural stand-in for a synthetic event carrying `nativeEvent`. Kept
6
+ // minimal on purpose: RN 0.87's Strict TS API makes the other synthetic-event
7
+ // fields optional, so any richer shape stops being a superset of the real
8
+ // `NativeSyntheticEvent` across RN versions. We only ever read `nativeEvent`.
9
+ export type NativeEventWrapper<T> = {
10
+ nativeEvent: T;
11
+ };
8
12
 
9
13
  // Payload of the managed button's press events. The codegen spec
10
14
  // (`specs/RNGestureHandlerButtonNativeComponent`) declares its own