react-native-gesture-handler 2.24.0 → 2.25.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/README.md +3 -0
- package/android/build.gradle +39 -12
- package/android/fabric/src/main/java/com/swmansion/gesturehandler/ReactContextExtensions.kt +2 -0
- package/android/nosvg/src/main/java/com/swmansion/gesturehandler/RNSVGHitTester.kt +13 -0
- package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +6 -1
- package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +13 -3
- package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +1 -1
- package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt +8 -2
- package/android/svg/src/main/java/com/swmansion/gesturehandler/RNSVGHitTester.kt +67 -0
- package/apple/RNGestureHandlerButtonComponentView.h +2 -1
- package/apple/RNGestureHandlerButtonComponentView.mm +95 -0
- package/lib/commonjs/components/GestureButtons.js +5 -4
- package/lib/commonjs/components/GestureButtons.js.map +1 -1
- package/lib/commonjs/components/Pressable/Pressable.js +76 -40
- package/lib/commonjs/components/Pressable/Pressable.js.map +1 -1
- package/lib/commonjs/handlers/createHandler.js +5 -8
- package/lib/commonjs/handlers/createHandler.js.map +1 -1
- package/lib/commonjs/handlers/gestures/GestureDetector/updateHandlers.js +12 -5
- package/lib/commonjs/handlers/gestures/GestureDetector/updateHandlers.js.map +1 -1
- package/lib/commonjs/handlers/gestures/GestureDetector/utils.js +3 -7
- package/lib/commonjs/handlers/gestures/GestureDetector/utils.js.map +1 -1
- package/lib/commonjs/web/tools/GestureHandlerOrchestrator.js +0 -1
- package/lib/commonjs/web/tools/GestureHandlerOrchestrator.js.map +1 -1
- package/lib/commonjs/web/tools/InteractionManager.js +1 -3
- package/lib/commonjs/web/tools/InteractionManager.js.map +1 -1
- package/lib/module/components/GestureButtons.js +4 -3
- package/lib/module/components/GestureButtons.js.map +1 -1
- package/lib/module/components/Pressable/Pressable.js +75 -39
- package/lib/module/components/Pressable/Pressable.js.map +1 -1
- package/lib/module/handlers/createHandler.js +5 -8
- package/lib/module/handlers/createHandler.js.map +1 -1
- package/lib/module/handlers/gestures/GestureDetector/updateHandlers.js +12 -5
- package/lib/module/handlers/gestures/GestureDetector/updateHandlers.js.map +1 -1
- package/lib/module/handlers/gestures/GestureDetector/utils.js +3 -6
- package/lib/module/handlers/gestures/GestureDetector/utils.js.map +1 -1
- package/lib/module/web/tools/GestureHandlerOrchestrator.js +0 -1
- package/lib/module/web/tools/GestureHandlerOrchestrator.js.map +1 -1
- package/lib/module/web/tools/InteractionManager.js +1 -2
- package/lib/module/web/tools/InteractionManager.js.map +1 -1
- package/lib/typescript/components/GestureButtons.d.ts +3 -3
- package/lib/typescript/components/Pressable/Pressable.d.ts +3 -1
- package/package.json +4 -2
- package/src/components/GestureButtons.tsx +13 -9
- package/src/components/Pressable/Pressable.tsx +400 -351
- package/src/handlers/createHandler.tsx +4 -8
- package/src/handlers/gestures/GestureDetector/updateHandlers.ts +11 -3
- package/src/handlers/gestures/GestureDetector/utils.ts +3 -7
- package/src/web/tools/GestureHandlerOrchestrator.ts +0 -1
- package/src/web/tools/InteractionManager.ts +1 -2
- package/lib/commonjs/getReactNativeVersion.js +0 -22
- package/lib/commonjs/getReactNativeVersion.js.map +0 -1
- package/lib/commonjs/getReactNativeVersion.web.js +0 -11
- package/lib/commonjs/getReactNativeVersion.web.js.map +0 -1
- package/lib/module/getReactNativeVersion.js +0 -10
- package/lib/module/getReactNativeVersion.js.map +0 -1
- package/lib/module/getReactNativeVersion.web.js +0 -4
- package/lib/module/getReactNativeVersion.web.js.map +0 -1
- package/lib/typescript/getReactNativeVersion.d.ts +0 -4
- package/lib/typescript/getReactNativeVersion.web.d.ts +0 -1
- package/src/getReactNativeVersion.ts +0 -11
- package/src/getReactNativeVersion.web.ts +0 -3
@@ -1,4 +1,12 @@
|
|
1
|
-
import React, {
|
1
|
+
import React, {
|
2
|
+
ForwardedRef,
|
3
|
+
forwardRef,
|
4
|
+
RefObject,
|
5
|
+
useCallback,
|
6
|
+
useMemo,
|
7
|
+
useRef,
|
8
|
+
useState,
|
9
|
+
} from 'react';
|
2
10
|
import { GestureObjects as Gesture } from '../../handlers/gestures/gestureObjects';
|
3
11
|
import { GestureDetector } from '../../handlers/gestures/GestureDetector';
|
4
12
|
import { PressableEvent, PressableProps } from './PressableProps';
|
@@ -27,392 +35,433 @@ const IS_TEST_ENV = isTestEnv();
|
|
27
35
|
|
28
36
|
let IS_FABRIC: null | boolean = null;
|
29
37
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
const pressDelayTimeoutRef = useRef<number | null>(null);
|
114
|
-
const isTouchPropagationAllowed = useRef<boolean>(false);
|
115
|
-
|
116
|
-
// iOS only: due to varying flow of gestures, events sometimes have to be saved for later use
|
117
|
-
const deferredEventPayload = useRef<PressableEvent | null>(null);
|
38
|
+
const Pressable = forwardRef(
|
39
|
+
(props: PressableProps, pressableRef: ForwardedRef<View>) => {
|
40
|
+
const {
|
41
|
+
testOnly_pressed,
|
42
|
+
hitSlop,
|
43
|
+
pressRetentionOffset,
|
44
|
+
delayHoverIn,
|
45
|
+
onHoverIn,
|
46
|
+
delayHoverOut,
|
47
|
+
onHoverOut,
|
48
|
+
delayLongPress,
|
49
|
+
unstable_pressDelay,
|
50
|
+
onPress,
|
51
|
+
onPressIn,
|
52
|
+
onPressOut,
|
53
|
+
onLongPress,
|
54
|
+
style,
|
55
|
+
children,
|
56
|
+
android_disableSound,
|
57
|
+
android_ripple,
|
58
|
+
disabled,
|
59
|
+
accessible,
|
60
|
+
...remainingProps
|
61
|
+
} = props;
|
62
|
+
|
63
|
+
const [pressedState, setPressedState] = useState(testOnly_pressed ?? false);
|
64
|
+
|
65
|
+
// Disabled when onLongPress has been called
|
66
|
+
const isPressCallbackEnabled = useRef<boolean>(true);
|
67
|
+
const hasPassedBoundsChecks = useRef<boolean>(false);
|
68
|
+
const shouldPreventNativeEffects = useRef<boolean>(false);
|
69
|
+
|
70
|
+
const normalizedHitSlop: Insets = useMemo(
|
71
|
+
() =>
|
72
|
+
typeof hitSlop === 'number' ? numberAsInset(hitSlop) : (hitSlop ?? {}),
|
73
|
+
[hitSlop]
|
74
|
+
);
|
75
|
+
|
76
|
+
const normalizedPressRetentionOffset: Insets = useMemo(
|
77
|
+
() =>
|
78
|
+
typeof pressRetentionOffset === 'number'
|
79
|
+
? numberAsInset(pressRetentionOffset)
|
80
|
+
: (pressRetentionOffset ?? {}),
|
81
|
+
[pressRetentionOffset]
|
82
|
+
);
|
83
|
+
|
84
|
+
const hoverInTimeout = useRef<number | null>(null);
|
85
|
+
const hoverOutTimeout = useRef<number | null>(null);
|
86
|
+
|
87
|
+
const hoverGesture = useMemo(
|
88
|
+
() =>
|
89
|
+
Gesture.Hover()
|
90
|
+
.manualActivation(true) // Stops Hover from blocking Native gesture activation on web
|
91
|
+
.cancelsTouchesInView(false)
|
92
|
+
.onBegin((event) => {
|
93
|
+
if (hoverOutTimeout.current) {
|
94
|
+
clearTimeout(hoverOutTimeout.current);
|
95
|
+
}
|
96
|
+
if (delayHoverIn) {
|
97
|
+
hoverInTimeout.current = setTimeout(
|
98
|
+
() => onHoverIn?.(gestureToPressableEvent(event)),
|
99
|
+
delayHoverIn
|
100
|
+
);
|
101
|
+
return;
|
102
|
+
}
|
103
|
+
onHoverIn?.(gestureToPressableEvent(event));
|
104
|
+
})
|
105
|
+
.onFinalize((event) => {
|
106
|
+
if (hoverInTimeout.current) {
|
107
|
+
clearTimeout(hoverInTimeout.current);
|
108
|
+
}
|
109
|
+
if (delayHoverOut) {
|
110
|
+
hoverOutTimeout.current = setTimeout(
|
111
|
+
() => onHoverOut?.(gestureToPressableEvent(event)),
|
112
|
+
delayHoverOut
|
113
|
+
);
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
onHoverOut?.(gestureToPressableEvent(event));
|
117
|
+
}),
|
118
|
+
[delayHoverIn, delayHoverOut, onHoverIn, onHoverOut]
|
119
|
+
);
|
118
120
|
|
119
|
-
|
120
|
-
|
121
|
-
if (handlingOnTouchesDown.current) {
|
122
|
-
deferredEventPayload.current = event;
|
123
|
-
}
|
121
|
+
const pressDelayTimeoutRef = useRef<number | null>(null);
|
122
|
+
const isTouchPropagationAllowed = useRef<boolean>(false);
|
124
123
|
|
125
|
-
|
126
|
-
|
127
|
-
}
|
124
|
+
// iOS only: due to varying flow of gestures, events sometimes have to be saved for later use
|
125
|
+
const deferredEventPayload = useRef<PressableEvent | null>(null);
|
128
126
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
setPressedState(true);
|
135
|
-
},
|
136
|
-
[onPressIn]
|
137
|
-
);
|
138
|
-
|
139
|
-
const pressOutHandler = useCallback(
|
140
|
-
(event: PressableEvent) => {
|
141
|
-
if (
|
142
|
-
!hasPassedBoundsChecks.current ||
|
143
|
-
event.nativeEvent.touches.length >
|
144
|
-
event.nativeEvent.changedTouches.length
|
145
|
-
) {
|
146
|
-
return;
|
147
|
-
}
|
127
|
+
const pressInHandler = useCallback(
|
128
|
+
(event: PressableEvent) => {
|
129
|
+
if (handlingOnTouchesDown.current) {
|
130
|
+
deferredEventPayload.current = event;
|
131
|
+
}
|
148
132
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
// even though we are located at the pressOutHandler
|
153
|
-
clearTimeout(pressDelayTimeoutRef.current);
|
154
|
-
pressInHandler(event);
|
155
|
-
}
|
133
|
+
if (!isTouchPropagationAllowed.current) {
|
134
|
+
return;
|
135
|
+
}
|
156
136
|
|
157
|
-
if (deferredEventPayload.current) {
|
158
|
-
onPressIn?.(deferredEventPayload.current);
|
159
137
|
deferredEventPayload.current = null;
|
160
|
-
}
|
161
|
-
|
162
|
-
onPressOut?.(event);
|
163
138
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
139
|
+
onPressIn?.(event);
|
140
|
+
isPressCallbackEnabled.current = true;
|
141
|
+
pressDelayTimeoutRef.current = null;
|
142
|
+
setPressedState(true);
|
143
|
+
},
|
144
|
+
[onPressIn]
|
145
|
+
);
|
146
|
+
|
147
|
+
const pressOutHandler = useCallback(
|
148
|
+
(event: PressableEvent) => {
|
149
|
+
if (!isTouchPropagationAllowed.current) {
|
150
|
+
hasPassedBoundsChecks.current = false;
|
151
|
+
isPressCallbackEnabled.current = true;
|
152
|
+
deferredEventPayload.current = null;
|
153
|
+
|
154
|
+
if (longPressTimeoutRef.current) {
|
155
|
+
clearTimeout(longPressTimeoutRef.current);
|
156
|
+
longPressTimeoutRef.current = null;
|
157
|
+
}
|
172
158
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
},
|
178
|
-
[onPress, onPressIn, onPressOut, pressInHandler, unstable_pressDelay]
|
179
|
-
);
|
180
|
-
|
181
|
-
const handlingOnTouchesDown = useRef<boolean>(false);
|
182
|
-
const onEndHandlingTouchesDown = useRef<(() => void) | null>(null);
|
183
|
-
const cancelledMidPress = useRef<boolean>(false);
|
184
|
-
|
185
|
-
const activateLongPress = useCallback(
|
186
|
-
(event: GestureTouchEvent) => {
|
187
|
-
if (!isTouchPropagationAllowed.current) {
|
188
|
-
return;
|
189
|
-
}
|
159
|
+
if (pressDelayTimeoutRef.current) {
|
160
|
+
clearTimeout(pressDelayTimeoutRef.current);
|
161
|
+
pressDelayTimeoutRef.current = null;
|
162
|
+
}
|
190
163
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
164
|
+
return;
|
165
|
+
}
|
166
|
+
|
167
|
+
if (
|
168
|
+
!hasPassedBoundsChecks.current ||
|
169
|
+
event.nativeEvent.touches.length >
|
170
|
+
event.nativeEvent.changedTouches.length
|
171
|
+
) {
|
172
|
+
return;
|
173
|
+
}
|
174
|
+
|
175
|
+
if (unstable_pressDelay && pressDelayTimeoutRef.current !== null) {
|
176
|
+
// When delay is preemptively finished by lifting touches,
|
177
|
+
// we want to immediately activate it's effects - pressInHandler,
|
178
|
+
// even though we are located at the pressOutHandler
|
179
|
+
clearTimeout(pressDelayTimeoutRef.current);
|
180
|
+
pressInHandler(event);
|
181
|
+
}
|
182
|
+
|
183
|
+
if (deferredEventPayload.current) {
|
184
|
+
onPressIn?.(deferredEventPayload.current);
|
185
|
+
deferredEventPayload.current = null;
|
186
|
+
}
|
187
|
+
|
188
|
+
onPressOut?.(event);
|
189
|
+
|
190
|
+
if (isPressCallbackEnabled.current) {
|
191
|
+
onPress?.(event);
|
192
|
+
}
|
193
|
+
|
194
|
+
if (longPressTimeoutRef.current) {
|
195
|
+
clearTimeout(longPressTimeoutRef.current);
|
196
|
+
longPressTimeoutRef.current = null;
|
197
|
+
}
|
198
|
+
|
199
|
+
isTouchPropagationAllowed.current = false;
|
200
|
+
hasPassedBoundsChecks.current = false;
|
201
|
+
isPressCallbackEnabled.current = true;
|
202
|
+
setPressedState(false);
|
203
|
+
},
|
204
|
+
[onPress, onPressIn, onPressOut, pressInHandler, unstable_pressDelay]
|
205
|
+
);
|
206
|
+
|
207
|
+
const handlingOnTouchesDown = useRef<boolean>(false);
|
208
|
+
const onEndHandlingTouchesDown = useRef<(() => void) | null>(null);
|
209
|
+
const cancelledMidPress = useRef<boolean>(false);
|
210
|
+
|
211
|
+
const activateLongPress = useCallback(
|
212
|
+
(event: GestureTouchEvent) => {
|
213
|
+
if (!isTouchPropagationAllowed.current) {
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
|
217
|
+
if (hasPassedBoundsChecks.current) {
|
218
|
+
onLongPress?.(gestureTouchToPressableEvent(event));
|
219
|
+
isPressCallbackEnabled.current = false;
|
220
|
+
}
|
221
|
+
|
222
|
+
if (longPressTimeoutRef.current) {
|
223
|
+
clearTimeout(longPressTimeoutRef.current);
|
224
|
+
longPressTimeoutRef.current = null;
|
225
|
+
}
|
226
|
+
},
|
227
|
+
[onLongPress]
|
228
|
+
);
|
229
|
+
|
230
|
+
const longPressTimeoutRef = useRef<number | null>(null);
|
231
|
+
const longPressMinDuration =
|
232
|
+
(delayLongPress ?? DEFAULT_LONG_PRESS_DURATION) +
|
233
|
+
(unstable_pressDelay ?? 0);
|
234
|
+
|
235
|
+
const innerPressableRef = useRef<View>(null);
|
236
|
+
|
237
|
+
const measureCallback = useCallback(
|
238
|
+
(width: number, height: number, event: GestureTouchEvent) => {
|
239
|
+
if (
|
240
|
+
!isTouchWithinInset(
|
241
|
+
{
|
242
|
+
width,
|
243
|
+
height,
|
244
|
+
},
|
245
|
+
normalizedHitSlop,
|
246
|
+
event.changedTouches.at(-1)
|
247
|
+
) ||
|
248
|
+
hasPassedBoundsChecks.current ||
|
249
|
+
cancelledMidPress.current
|
250
|
+
) {
|
251
|
+
cancelledMidPress.current = false;
|
252
|
+
onEndHandlingTouchesDown.current = null;
|
253
|
+
handlingOnTouchesDown.current = false;
|
254
|
+
return;
|
255
|
+
}
|
256
|
+
|
257
|
+
hasPassedBoundsChecks.current = true;
|
258
|
+
|
259
|
+
// In case of multiple touches, the first one starts long press gesture
|
260
|
+
if (longPressTimeoutRef.current === null) {
|
261
|
+
// Start long press gesture timer
|
262
|
+
longPressTimeoutRef.current = setTimeout(
|
263
|
+
() => activateLongPress(event),
|
264
|
+
longPressMinDuration
|
265
|
+
);
|
266
|
+
}
|
267
|
+
|
268
|
+
if (unstable_pressDelay) {
|
269
|
+
pressDelayTimeoutRef.current = setTimeout(() => {
|
270
|
+
pressInHandler(gestureTouchToPressableEvent(event));
|
271
|
+
}, unstable_pressDelay);
|
272
|
+
} else {
|
273
|
+
pressInHandler(gestureTouchToPressableEvent(event));
|
274
|
+
}
|
275
|
+
|
276
|
+
onEndHandlingTouchesDown.current?.();
|
277
|
+
onEndHandlingTouchesDown.current = null;
|
278
|
+
handlingOnTouchesDown.current = false;
|
279
|
+
},
|
280
|
+
[
|
281
|
+
activateLongPress,
|
282
|
+
longPressMinDuration,
|
283
|
+
normalizedHitSlop,
|
284
|
+
pressInHandler,
|
285
|
+
unstable_pressDelay,
|
286
|
+
]
|
287
|
+
);
|
288
|
+
|
289
|
+
const pressAndTouchGesture = useMemo(
|
290
|
+
() =>
|
291
|
+
Gesture.LongPress()
|
292
|
+
.minDuration(INT32_MAX) // Stops long press from blocking native gesture
|
293
|
+
.maxDistance(INT32_MAX) // Stops long press from cancelling after set distance
|
294
|
+
.cancelsTouchesInView(false)
|
295
|
+
.onTouchesDown((event) => {
|
296
|
+
handlingOnTouchesDown.current = true;
|
297
|
+
if (pressableRef) {
|
298
|
+
(pressableRef as RefObject<View>).current?.measure(
|
299
|
+
(_x, _y, width, height) => {
|
300
|
+
measureCallback(width, height, event);
|
301
|
+
}
|
302
|
+
);
|
303
|
+
} else {
|
304
|
+
innerPressableRef.current?.measure((_x, _y, width, height) => {
|
305
|
+
measureCallback(width, height, event);
|
306
|
+
});
|
307
|
+
}
|
308
|
+
})
|
309
|
+
.onTouchesUp((event) => {
|
310
|
+
if (handlingOnTouchesDown.current) {
|
311
|
+
onEndHandlingTouchesDown.current = () =>
|
312
|
+
pressOutHandler(gestureTouchToPressableEvent(event));
|
313
|
+
return;
|
314
|
+
}
|
315
|
+
// On iOS, short taps will make LongPress gesture call onTouchesUp before Native gesture calls onStart
|
316
|
+
// This variable ensures that onStart isn't detected as the first gesture since Pressable is pressed.
|
317
|
+
if (deferredEventPayload.current !== null) {
|
318
|
+
shouldPreventNativeEffects.current = true;
|
319
|
+
}
|
320
|
+
pressOutHandler(gestureTouchToPressableEvent(event));
|
321
|
+
})
|
322
|
+
.onTouchesCancelled((event) => {
|
323
|
+
isPressCallbackEnabled.current = false;
|
324
|
+
|
325
|
+
if (handlingOnTouchesDown.current) {
|
326
|
+
cancelledMidPress.current = true;
|
327
|
+
onEndHandlingTouchesDown.current = () =>
|
328
|
+
pressOutHandler(gestureTouchToPressableEvent(event));
|
329
|
+
return;
|
330
|
+
}
|
195
331
|
|
196
|
-
if (longPressTimeoutRef.current) {
|
197
|
-
clearTimeout(longPressTimeoutRef.current);
|
198
|
-
longPressTimeoutRef.current = null;
|
199
|
-
}
|
200
|
-
},
|
201
|
-
[onLongPress]
|
202
|
-
);
|
203
|
-
|
204
|
-
const longPressTimeoutRef = useRef<number | null>(null);
|
205
|
-
const longPressMinDuration =
|
206
|
-
(delayLongPress ?? DEFAULT_LONG_PRESS_DURATION) +
|
207
|
-
(unstable_pressDelay ?? 0);
|
208
|
-
|
209
|
-
const pressAndTouchGesture = useMemo(
|
210
|
-
() =>
|
211
|
-
Gesture.LongPress()
|
212
|
-
.minDuration(INT32_MAX) // Stops long press from blocking native gesture
|
213
|
-
.maxDistance(INT32_MAX) // Stops long press from cancelling after set distance
|
214
|
-
.cancelsTouchesInView(false)
|
215
|
-
.onTouchesDown((event) => {
|
216
|
-
handlingOnTouchesDown.current = true;
|
217
|
-
pressableRef.current?.measure((_x, _y, width, height) => {
|
218
332
|
if (
|
219
|
-
!
|
220
|
-
|
221
|
-
width,
|
222
|
-
height,
|
223
|
-
},
|
224
|
-
normalizedHitSlop,
|
225
|
-
event.changedTouches.at(-1)
|
226
|
-
) ||
|
227
|
-
hasPassedBoundsChecks.current ||
|
228
|
-
cancelledMidPress.current
|
333
|
+
!hasPassedBoundsChecks.current ||
|
334
|
+
event.allTouches.length > event.changedTouches.length
|
229
335
|
) {
|
230
|
-
cancelledMidPress.current = false;
|
231
|
-
onEndHandlingTouchesDown.current = null;
|
232
|
-
handlingOnTouchesDown.current = false;
|
233
336
|
return;
|
234
337
|
}
|
235
338
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
339
|
+
pressOutHandler(gestureTouchToPressableEvent(event));
|
340
|
+
}),
|
341
|
+
[pressableRef, measureCallback, pressOutHandler]
|
342
|
+
);
|
343
|
+
|
344
|
+
// RNButton is placed inside ButtonGesture to enable Android's ripple and to capture non-propagating events
|
345
|
+
const buttonGesture = useMemo(
|
346
|
+
() =>
|
347
|
+
Gesture.Native()
|
348
|
+
.onBegin(() => {
|
349
|
+
// Android sets BEGAN state on press down
|
350
|
+
if (Platform.OS === 'android' || Platform.OS === 'macos') {
|
351
|
+
isTouchPropagationAllowed.current = true;
|
245
352
|
}
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
}, unstable_pressDelay);
|
251
|
-
} else {
|
252
|
-
pressInHandler(gestureTouchToPressableEvent(event));
|
353
|
+
})
|
354
|
+
.onStart(() => {
|
355
|
+
if (Platform.OS === 'web') {
|
356
|
+
isTouchPropagationAllowed.current = true;
|
253
357
|
}
|
254
358
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
})
|
260
|
-
.onTouchesUp((event) => {
|
261
|
-
if (handlingOnTouchesDown.current) {
|
262
|
-
onEndHandlingTouchesDown.current = () =>
|
263
|
-
pressOutHandler(gestureTouchToPressableEvent(event));
|
264
|
-
return;
|
265
|
-
}
|
266
|
-
// On iOS, short taps will make LongPress gesture call onTouchesUp before Native gesture calls onStart
|
267
|
-
// This variable ensures that onStart isn't detected as the first gesture since Pressable is pressed.
|
268
|
-
if (deferredEventPayload.current !== null) {
|
269
|
-
shouldPreventNativeEffects.current = true;
|
270
|
-
}
|
271
|
-
pressOutHandler(gestureTouchToPressableEvent(event));
|
272
|
-
})
|
273
|
-
.onTouchesCancelled((event) => {
|
274
|
-
isPressCallbackEnabled.current = false;
|
275
|
-
|
276
|
-
if (handlingOnTouchesDown.current) {
|
277
|
-
cancelledMidPress.current = true;
|
278
|
-
onEndHandlingTouchesDown.current = () =>
|
279
|
-
pressOutHandler(gestureTouchToPressableEvent(event));
|
280
|
-
return;
|
281
|
-
}
|
359
|
+
// iOS sets ACTIVE state on press down
|
360
|
+
if (Platform.OS !== 'ios') {
|
361
|
+
return;
|
362
|
+
}
|
282
363
|
|
283
|
-
|
284
|
-
|
285
|
-
event.allTouches.length > event.changedTouches.length
|
286
|
-
) {
|
287
|
-
return;
|
288
|
-
}
|
364
|
+
if (deferredEventPayload.current) {
|
365
|
+
isTouchPropagationAllowed.current = true;
|
289
366
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
pressOutHandler,
|
298
|
-
unstable_pressDelay,
|
299
|
-
]
|
300
|
-
);
|
301
|
-
|
302
|
-
// RNButton is placed inside ButtonGesture to enable Android's ripple and to capture non-propagating events
|
303
|
-
const buttonGesture = useMemo(
|
304
|
-
() =>
|
305
|
-
Gesture.Native()
|
306
|
-
.onBegin(() => {
|
307
|
-
// Android sets BEGAN state on press down
|
308
|
-
if (Platform.OS === 'android' || Platform.OS === 'macos') {
|
309
|
-
isTouchPropagationAllowed.current = true;
|
310
|
-
}
|
311
|
-
})
|
312
|
-
.onStart(() => {
|
313
|
-
if (Platform.OS === 'web') {
|
314
|
-
isTouchPropagationAllowed.current = true;
|
315
|
-
}
|
367
|
+
if (hasPassedBoundsChecks.current) {
|
368
|
+
pressInHandler(deferredEventPayload.current);
|
369
|
+
deferredEventPayload.current = null;
|
370
|
+
} else {
|
371
|
+
pressOutHandler(deferredEventPayload.current);
|
372
|
+
isTouchPropagationAllowed.current = false;
|
373
|
+
}
|
316
374
|
|
317
|
-
|
318
|
-
|
319
|
-
return;
|
320
|
-
}
|
321
|
-
|
322
|
-
if (deferredEventPayload.current) {
|
323
|
-
isTouchPropagationAllowed.current = true;
|
375
|
+
return;
|
376
|
+
}
|
324
377
|
|
325
378
|
if (hasPassedBoundsChecks.current) {
|
326
|
-
|
327
|
-
|
328
|
-
} else {
|
329
|
-
pressOutHandler(deferredEventPayload.current);
|
330
|
-
isTouchPropagationAllowed.current = false;
|
379
|
+
isTouchPropagationAllowed.current = true;
|
380
|
+
return;
|
331
381
|
}
|
332
382
|
|
333
|
-
|
334
|
-
|
383
|
+
if (shouldPreventNativeEffects.current) {
|
384
|
+
shouldPreventNativeEffects.current = false;
|
385
|
+
if (!handlingOnTouchesDown.current) {
|
386
|
+
return;
|
387
|
+
}
|
388
|
+
}
|
335
389
|
|
336
|
-
if (hasPassedBoundsChecks.current) {
|
337
390
|
isTouchPropagationAllowed.current = true;
|
338
|
-
|
339
|
-
|
391
|
+
}),
|
392
|
+
[pressInHandler, pressOutHandler]
|
393
|
+
);
|
340
394
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
395
|
+
const appliedHitSlop = addInsets(
|
396
|
+
normalizedHitSlop,
|
397
|
+
normalizedPressRetentionOffset
|
398
|
+
);
|
345
399
|
|
346
|
-
|
347
|
-
}),
|
348
|
-
[pressInHandler, pressOutHandler]
|
349
|
-
);
|
400
|
+
const isPressableEnabled = disabled !== true;
|
350
401
|
|
351
|
-
|
352
|
-
normalizedHitSlop,
|
353
|
-
normalizedPressRetentionOffset
|
354
|
-
);
|
402
|
+
const gestures = [buttonGesture, pressAndTouchGesture, hoverGesture];
|
355
403
|
|
356
|
-
|
404
|
+
for (const gesture of gestures) {
|
405
|
+
gesture.enabled(isPressableEnabled);
|
406
|
+
gesture.runOnJS(true);
|
407
|
+
gesture.hitSlop(appliedHitSlop);
|
408
|
+
gesture.shouldCancelWhenOutside(Platform.OS === 'web' ? false : true);
|
409
|
+
}
|
357
410
|
|
358
|
-
|
411
|
+
// Uses different hitSlop, to activate on hitSlop area instead of pressRetentionOffset area
|
412
|
+
buttonGesture.hitSlop(normalizedHitSlop);
|
359
413
|
|
360
|
-
|
361
|
-
gesture.enabled(isPressableEnabled);
|
362
|
-
gesture.runOnJS(true);
|
363
|
-
gesture.hitSlop(appliedHitSlop);
|
364
|
-
gesture.shouldCancelWhenOutside(Platform.OS === 'web' ? false : true);
|
365
|
-
}
|
414
|
+
const gesture = Gesture.Simultaneous(...gestures);
|
366
415
|
|
367
|
-
|
368
|
-
|
416
|
+
// `cursor: 'pointer'` on `RNButton` crashes iOS
|
417
|
+
const pointerStyle: StyleProp<ViewStyle> =
|
418
|
+
Platform.OS === 'web' ? { cursor: 'pointer' } : {};
|
369
419
|
|
370
|
-
|
420
|
+
const styleProp =
|
421
|
+
typeof style === 'function' ? style({ pressed: pressedState }) : style;
|
371
422
|
|
372
|
-
|
373
|
-
|
374
|
-
|
423
|
+
const childrenProp =
|
424
|
+
typeof children === 'function'
|
425
|
+
? children({ pressed: pressedState })
|
426
|
+
: children;
|
375
427
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
typeof children === 'function'
|
381
|
-
? children({ pressed: pressedState })
|
382
|
-
: children;
|
428
|
+
const rippleColor = useMemo(() => {
|
429
|
+
if (IS_FABRIC === null) {
|
430
|
+
IS_FABRIC = isFabric();
|
431
|
+
}
|
383
432
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
433
|
+
const defaultRippleColor = android_ripple ? undefined : 'transparent';
|
434
|
+
const unprocessedRippleColor =
|
435
|
+
android_ripple?.color ?? defaultRippleColor;
|
436
|
+
return IS_FABRIC
|
437
|
+
? unprocessedRippleColor
|
438
|
+
: processColor(unprocessedRippleColor);
|
439
|
+
}, [android_ripple]);
|
440
|
+
|
441
|
+
return (
|
442
|
+
<GestureDetector gesture={gesture}>
|
443
|
+
<NativeButton
|
444
|
+
{...remainingProps}
|
445
|
+
ref={pressableRef ?? innerPressableRef}
|
446
|
+
accessible={accessible !== false}
|
447
|
+
hitSlop={appliedHitSlop}
|
448
|
+
enabled={isPressableEnabled}
|
449
|
+
touchSoundDisabled={android_disableSound ?? undefined}
|
450
|
+
rippleColor={rippleColor}
|
451
|
+
rippleRadius={android_ripple?.radius ?? undefined}
|
452
|
+
style={[pointerStyle, styleProp]}
|
453
|
+
testOnly_onPress={IS_TEST_ENV ? onPress : undefined}
|
454
|
+
testOnly_onPressIn={IS_TEST_ENV ? onPressIn : undefined}
|
455
|
+
testOnly_onPressOut={IS_TEST_ENV ? onPressOut : undefined}
|
456
|
+
testOnly_onLongPress={IS_TEST_ENV ? onLongPress : undefined}>
|
457
|
+
{childrenProp}
|
458
|
+
{__DEV__ ? (
|
459
|
+
<PressabilityDebugView color="red" hitSlop={normalizedHitSlop} />
|
460
|
+
) : null}
|
461
|
+
</NativeButton>
|
462
|
+
</GestureDetector>
|
463
|
+
);
|
464
|
+
}
|
465
|
+
);
|
388
466
|
|
389
|
-
|
390
|
-
const unprocessedRippleColor = android_ripple?.color ?? defaultRippleColor;
|
391
|
-
return IS_FABRIC
|
392
|
-
? unprocessedRippleColor
|
393
|
-
: processColor(unprocessedRippleColor);
|
394
|
-
}, [android_ripple]);
|
395
|
-
|
396
|
-
return (
|
397
|
-
<GestureDetector gesture={gesture}>
|
398
|
-
<NativeButton
|
399
|
-
{...remainingProps}
|
400
|
-
ref={pressableRef}
|
401
|
-
hitSlop={appliedHitSlop}
|
402
|
-
enabled={isPressableEnabled}
|
403
|
-
touchSoundDisabled={android_disableSound ?? undefined}
|
404
|
-
rippleColor={rippleColor}
|
405
|
-
rippleRadius={android_ripple?.radius ?? undefined}
|
406
|
-
style={[pointerStyle, styleProp]}
|
407
|
-
testOnly_onPress={IS_TEST_ENV ? onPress : undefined}
|
408
|
-
testOnly_onPressIn={IS_TEST_ENV ? onPressIn : undefined}
|
409
|
-
testOnly_onPressOut={IS_TEST_ENV ? onPressOut : undefined}
|
410
|
-
testOnly_onLongPress={IS_TEST_ENV ? onLongPress : undefined}>
|
411
|
-
{childrenProp}
|
412
|
-
{__DEV__ ? (
|
413
|
-
<PressabilityDebugView color="red" hitSlop={normalizedHitSlop} />
|
414
|
-
) : null}
|
415
|
-
</NativeButton>
|
416
|
-
</GestureDetector>
|
417
|
-
);
|
418
|
-
}
|
467
|
+
export default Pressable;
|