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.
- package/android/build.gradle +21 -6
- package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +30 -66
- package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +4 -2
- package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +1 -2
- package/apple/RNGestureHandler.mm +31 -23
- package/apple/RNGestureHandlerButtonComponentView.mm +10 -6
- package/lib/module/components/GestureHandlerButton.web.js +3 -2
- package/lib/module/components/GestureHandlerButton.web.js.map +1 -1
- package/lib/module/components/ReanimatedDrawerLayout.js +3 -0
- package/lib/module/components/ReanimatedDrawerLayout.js.map +1 -1
- package/lib/module/components/touchables/TouchableOpacity.js +1 -0
- package/lib/module/components/touchables/TouchableOpacity.js.map +1 -1
- package/lib/module/handlers/hitSlop.js +101 -0
- package/lib/module/handlers/hitSlop.js.map +1 -0
- package/lib/module/handlers/utils.js +3 -7
- package/lib/module/handlers/utils.js.map +1 -1
- package/lib/module/specs/RNGestureHandlerDetectorNativeComponent.ts +1 -1
- package/lib/module/specs/RNGestureHandlerRootViewNativeComponent.ts +1 -1
- package/lib/module/v3/components/Pressable.js +23 -312
- package/lib/module/v3/components/Pressable.js.map +1 -1
- package/lib/module/v3/components/PressableWithTouchable.js +259 -0
- package/lib/module/v3/components/PressableWithTouchable.js.map +1 -0
- package/lib/module/v3/components/StatefulPressable.js +338 -0
- package/lib/module/v3/components/StatefulPressable.js.map +1 -0
- package/lib/module/v3/components/pointerStyle.js +6 -0
- package/lib/module/v3/components/pointerStyle.js.map +1 -0
- package/lib/module/v3/components/pointerStyle.web.js +9 -0
- package/lib/module/v3/components/pointerStyle.web.js.map +1 -0
- package/lib/module/v3/hooks/utils/configUtils.js +3 -1
- package/lib/module/v3/hooks/utils/configUtils.js.map +1 -1
- package/lib/module/v3/hooks/utils/reanimatedUtils.js +14 -3
- package/lib/module/v3/hooks/utils/reanimatedUtils.js.map +1 -1
- package/lib/module/web/handlers/GestureHandler.js +21 -44
- package/lib/module/web/handlers/GestureHandler.js.map +1 -1
- package/lib/module/web/interfaces.js.map +1 -1
- package/lib/typescript/components/GestureComponents.web.d.ts +12 -4
- package/lib/typescript/components/GestureComponents.web.d.ts.map +1 -1
- package/lib/typescript/components/GestureHandlerButton.web.d.ts.map +1 -1
- package/lib/typescript/components/ReanimatedDrawerLayout.d.ts.map +1 -1
- package/lib/typescript/components/touchables/TouchableHighlight.d.ts +1 -1
- package/lib/typescript/components/touchables/TouchableHighlight.d.ts.map +1 -1
- package/lib/typescript/components/touchables/TouchableOpacity.d.ts.map +1 -1
- package/lib/typescript/handlers/hitSlop.d.ts +51 -0
- package/lib/typescript/handlers/hitSlop.d.ts.map +1 -0
- package/lib/typescript/handlers/utils.d.ts.map +1 -1
- package/lib/typescript/mocks/hostDetector.d.ts +5 -2
- package/lib/typescript/mocks/hostDetector.d.ts.map +1 -1
- package/lib/typescript/specs/RNGestureHandlerDetectorNativeComponent.d.ts +1 -1
- package/lib/typescript/specs/RNGestureHandlerDetectorNativeComponent.d.ts.map +1 -1
- package/lib/typescript/specs/RNGestureHandlerRootViewNativeComponent.d.ts +1 -1
- package/lib/typescript/specs/RNGestureHandlerRootViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/v3/components/GestureComponents.web.d.ts +16 -8
- package/lib/typescript/v3/components/GestureComponents.web.d.ts.map +1 -1
- package/lib/typescript/v3/components/Pressable.d.ts +13 -0
- package/lib/typescript/v3/components/Pressable.d.ts.map +1 -1
- package/lib/typescript/v3/components/PressableWithTouchable.d.ts +5 -0
- package/lib/typescript/v3/components/PressableWithTouchable.d.ts.map +1 -0
- package/lib/typescript/v3/components/StatefulPressable.d.ts +5 -0
- package/lib/typescript/v3/components/StatefulPressable.d.ts.map +1 -0
- package/lib/typescript/v3/components/pointerStyle.d.ts +3 -0
- package/lib/typescript/v3/components/pointerStyle.d.ts.map +1 -0
- package/lib/typescript/v3/components/pointerStyle.web.d.ts +3 -0
- package/lib/typescript/v3/components/pointerStyle.web.d.ts.map +1 -0
- package/lib/typescript/v3/detectors/ReanimatedNativeDetector.d.ts +3 -1
- package/lib/typescript/v3/detectors/ReanimatedNativeDetector.d.ts.map +1 -1
- package/lib/typescript/v3/detectors/ReanimatedNativeDetector.web.d.ts +3 -1
- package/lib/typescript/v3/detectors/ReanimatedNativeDetector.web.d.ts.map +1 -1
- package/lib/typescript/v3/detectors/common.d.ts +2 -2
- package/lib/typescript/v3/detectors/common.d.ts.map +1 -1
- package/lib/typescript/v3/hooks/utils/configUtils.d.ts.map +1 -1
- package/lib/typescript/v3/hooks/utils/reanimatedUtils.d.ts.map +1 -1
- package/lib/typescript/v3/types/ConfigTypes.d.ts +2 -1
- package/lib/typescript/v3/types/ConfigTypes.d.ts.map +1 -1
- package/lib/typescript/v3/types/EventTypes.d.ts +3 -2
- package/lib/typescript/v3/types/EventTypes.d.ts.map +1 -1
- package/lib/typescript/web/handlers/GestureHandler.d.ts +0 -1
- package/lib/typescript/web/handlers/GestureHandler.d.ts.map +1 -1
- package/lib/typescript/web/interfaces.d.ts +3 -12
- package/lib/typescript/web/interfaces.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/components/GestureHandlerButton.web.tsx +3 -2
- package/src/components/ReanimatedDrawerLayout.tsx +3 -0
- package/src/components/touchables/TouchableOpacity.tsx +1 -0
- package/src/handlers/hitSlop.ts +146 -0
- package/src/handlers/utils.ts +4 -2
- package/src/specs/RNGestureHandlerDetectorNativeComponent.ts +1 -1
- package/src/specs/RNGestureHandlerRootViewNativeComponent.ts +1 -1
- package/src/v3/components/Pressable.tsx +28 -448
- package/src/v3/components/PressableWithTouchable.tsx +326 -0
- package/src/v3/components/StatefulPressable.tsx +476 -0
- package/src/v3/components/pointerStyle.ts +5 -0
- package/src/v3/components/pointerStyle.web.ts +6 -0
- package/src/v3/hooks/utils/configUtils.ts +7 -1
- package/src/v3/hooks/utils/reanimatedUtils.ts +17 -8
- package/src/v3/types/ConfigTypes.ts +2 -1
- package/src/v3/types/EventTypes.ts +7 -3
- package/src/web/handlers/GestureHandler.ts +26 -77
- 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;
|