@react-native-oh-tpl/react-native-gesture-handler 2.12.6-1 → 2.12.6-2
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/harmony/gesture_handler/LICENSE +21 -0
- package/harmony/gesture_handler/OAT.xml +44 -0
- package/harmony/gesture_handler/README.OpenSource +11 -0
- package/harmony/gesture_handler/README.md +1 -0
- package/harmony/gesture_handler/build-profile.json5 +7 -7
- package/harmony/gesture_handler/hvigorfile.ts +2 -2
- package/harmony/gesture_handler/index.ets +2 -2
- package/harmony/gesture_handler/oh-package.json5 +13 -11
- package/harmony/gesture_handler/src/main/cpp/CMakeLists.txt +8 -8
- package/harmony/gesture_handler/src/main/cpp/GestureHandlerPackage.cpp +33 -33
- package/harmony/gesture_handler/src/main/cpp/GestureHandlerPackage.h +14 -14
- package/harmony/gesture_handler/src/main/cpp/RNGestureHandlerButtonComponentDescriptor.h +60 -60
- package/harmony/gesture_handler/src/main/cpp/RNGestureHandlerModule.cpp +17 -17
- package/harmony/gesture_handler/src/main/cpp/RNGestureHandlerModule.h +11 -11
- package/harmony/gesture_handler/src/main/cpp/RNGestureHandlerRootViewComponentDescriptor.h +60 -60
- package/harmony/gesture_handler/src/main/ets/CircularBuffer.ts +42 -42
- package/harmony/gesture_handler/src/main/ets/Event.ts +67 -67
- package/harmony/gesture_handler/src/main/ets/EventDispatcher.ts +37 -37
- package/harmony/gesture_handler/src/main/ets/GestureHandler.ts +663 -663
- package/harmony/gesture_handler/src/main/ets/GestureHandlerArkUIAdapter.ets +201 -201
- package/harmony/gesture_handler/src/main/ets/GestureHandlerFactory.ts +44 -44
- package/harmony/gesture_handler/src/main/ets/GestureHandlerOrchestrator.ts +280 -280
- package/harmony/gesture_handler/src/main/ets/GestureHandlerPackage.ts +22 -22
- package/harmony/gesture_handler/src/main/ets/GestureHandlerRegistry.ts +27 -27
- package/harmony/gesture_handler/src/main/ets/InteractionManager.ts +108 -108
- package/harmony/gesture_handler/src/main/ets/LeastSquareSolver.ts +182 -182
- package/harmony/gesture_handler/src/main/ets/NativeViewGestureHandler.ts +114 -114
- package/harmony/gesture_handler/src/main/ets/OutgoingEvent.ts +33 -33
- package/harmony/gesture_handler/src/main/ets/PanGestureHandler.ts +327 -327
- package/harmony/gesture_handler/src/main/ets/PointerTracker.ts +239 -239
- package/harmony/gesture_handler/src/main/ets/RNGHError.ts +4 -4
- package/harmony/gesture_handler/src/main/ets/RNGHLogger.ts +28 -28
- package/harmony/gesture_handler/src/main/ets/RNGHRootTouchHandler.ets +57 -57
- package/harmony/gesture_handler/src/main/ets/RNGestureHandlerButton.ets +36 -36
- package/harmony/gesture_handler/src/main/ets/RNGestureHandlerModule.ts +125 -125
- package/harmony/gesture_handler/src/main/ets/RNGestureHandlerRootView.ets +56 -55
- package/harmony/gesture_handler/src/main/ets/RNOHScrollLocker.ts +10 -10
- package/harmony/gesture_handler/src/main/ets/State.ts +46 -46
- package/harmony/gesture_handler/src/main/ets/TapGestureHandler.ts +205 -205
- package/harmony/gesture_handler/src/main/ets/Vector2D.ts +36 -36
- package/harmony/gesture_handler/src/main/ets/VelocityTracker.ts +98 -98
- package/harmony/gesture_handler/src/main/ets/View.ts +70 -70
- package/harmony/gesture_handler/src/main/ets/ViewRegistry.ts +42 -42
- package/harmony/gesture_handler/src/main/ets/pages/Index.ets +16 -16
- package/harmony/gesture_handler/src/main/ets/webviewability/WebviewAbility.ts +41 -41
- package/harmony/gesture_handler/src/main/module.json5 +6 -6
- package/harmony/gesture_handler/src/main/resources/base/element/color.json +7 -7
- package/harmony/gesture_handler/src/main/resources/base/element/string.json +15 -15
- package/harmony/gesture_handler/src/main/resources/base/profile/main_pages.json +5 -5
- package/harmony/gesture_handler/src/main/resources/en_US/element/string.json +15 -15
- package/harmony/gesture_handler/src/main/resources/zh_CN/element/string.json +15 -15
- package/harmony/gesture_handler.har +0 -0
- package/lib/commonjs/components/touchables/GenericTouchable.js +9 -9
- package/lib/commonjs/components/touchables/TouchableOpacity.js +2 -2
- package/lib/commonjs/handlers/createNativeWrapper.js +6 -6
- package/lib/commonjs/handlers/gestures/GestureDetector.js +3 -3
- package/lib/module/components/touchables/GenericTouchable.js +9 -9
- package/lib/module/components/touchables/TouchableOpacity.js +2 -2
- package/lib/module/handlers/createNativeWrapper.js +6 -6
- package/lib/module/handlers/gestures/GestureDetector.js +3 -3
- package/package.json +70 -70
- package/src/RNGestureHandlerModule.ts +6 -6
- package/src/components/GestureButtons.tsx +334 -334
- package/src/components/GestureHandlerButton.tsx +5 -5
- package/src/components/GestureHandlerRootView.tsx +34 -34
- package/src/components/RNGestureHandlerButton.tsx +23 -23
- package/src/components/touchables/GenericTouchable.tsx +301 -301
- package/src/components/touchables/TouchableOpacity.tsx +76 -76
- package/src/components/touchables/TouchableWithoutFeedback.tsx +14 -14
- package/src/components/touchables/index.ts +7 -7
- package/src/handlers/NativeViewGestureHandler.ts +55 -55
- package/src/handlers/PanGestureHandler.ts +327 -327
- package/src/handlers/TapGestureHandler.ts +95 -95
- package/src/handlers/createHandler.tsx +535 -535
- package/src/handlers/createNativeWrapper.tsx +81 -81
- package/src/handlers/gestureHandlerCommon.ts +15 -15
- package/src/handlers/gestures/GestureDetector.tsx +823 -823
- package/src/index.ts +172 -172
- package/src/init.ts +18 -18
|
@@ -1,301 +1,301 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { Component } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
Animated,
|
|
5
|
-
Platform,
|
|
6
|
-
StyleProp,
|
|
7
|
-
ViewStyle,
|
|
8
|
-
TouchableWithoutFeedbackProps,
|
|
9
|
-
Insets,
|
|
10
|
-
} from 'react-native';
|
|
11
|
-
|
|
12
|
-
import { State } from 'react-native-gesture-handler/src/State';
|
|
13
|
-
import { BaseButton } from '../GestureButtons';
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
GestureEvent,
|
|
17
|
-
HandlerStateChangeEvent,
|
|
18
|
-
} from 'react-native-gesture-handler/src/handlers/gestureHandlerCommon';
|
|
19
|
-
import { NativeViewGestureHandlerPayload } from 'react-native-gesture-handler/src/handlers/NativeViewGestureHandler';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Each touchable is a states' machine which preforms transitions.
|
|
23
|
-
* On very beginning (and on the very end or recognition) touchable is
|
|
24
|
-
* UNDETERMINED. Then it moves to BEGAN. If touchable recognizes that finger
|
|
25
|
-
* travel outside it transits to special MOVED_OUTSIDE state. Gesture recognition
|
|
26
|
-
* finishes in UNDETERMINED state.
|
|
27
|
-
*/
|
|
28
|
-
export const TOUCHABLE_STATE = {
|
|
29
|
-
UNDETERMINED: 0,
|
|
30
|
-
BEGAN: 1,
|
|
31
|
-
MOVED_OUTSIDE: 2,
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
type TouchableState = (typeof TOUCHABLE_STATE)[keyof typeof TOUCHABLE_STATE];
|
|
35
|
-
|
|
36
|
-
export interface GenericTouchableProps
|
|
37
|
-
extends Omit<TouchableWithoutFeedbackProps, 'hitSlop'> {
|
|
38
|
-
// Decided to drop not used fields from RN's implementation.
|
|
39
|
-
// e.g. onBlur and onFocus as well as deprecated props. - TODO: this comment may be unuseful in this moment
|
|
40
|
-
|
|
41
|
-
// TODO: in RN these events get native event parameter, which prolly could be used in our implementation too
|
|
42
|
-
onPress?: () => void;
|
|
43
|
-
onPressIn?: () => void;
|
|
44
|
-
onPressOut?: () => void;
|
|
45
|
-
onLongPress?: () => void;
|
|
46
|
-
|
|
47
|
-
nativeID?: string;
|
|
48
|
-
shouldActivateOnStart?: boolean;
|
|
49
|
-
disallowInterruption?: boolean;
|
|
50
|
-
|
|
51
|
-
containerStyle?: StyleProp<ViewStyle>;
|
|
52
|
-
hitSlop?: Insets | number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface InternalProps {
|
|
56
|
-
extraButtonProps: any;
|
|
57
|
-
onStateChange?: (oldState: TouchableState, newState: TouchableState) => void;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// TODO: maybe can be better
|
|
61
|
-
// TODO: all clearTimeout have ! added, maybe they shouldn't ?
|
|
62
|
-
type Timeout = ReturnType<typeof setTimeout> | null | undefined;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* GenericTouchable is not intented to be used as it is.
|
|
66
|
-
* Should be treated as a source for the rest of touchables
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
|
-
export default class GenericTouchable extends Component<
|
|
70
|
-
GenericTouchableProps & InternalProps
|
|
71
|
-
> {
|
|
72
|
-
static defaultProps = {
|
|
73
|
-
delayLongPress: 600,
|
|
74
|
-
extraButtonProps: {
|
|
75
|
-
rippleColor: 'transparent',
|
|
76
|
-
exclusive: true,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// timeout handlers
|
|
81
|
-
pressInTimeout: Timeout;
|
|
82
|
-
pressOutTimeout: Timeout;
|
|
83
|
-
longPressTimeout: Timeout;
|
|
84
|
-
|
|
85
|
-
// This flag is required since recognition of longPress implies not-invoking onPress
|
|
86
|
-
longPressDetected = false;
|
|
87
|
-
|
|
88
|
-
pointerInside = true;
|
|
89
|
-
|
|
90
|
-
// State of touchable
|
|
91
|
-
STATE: TouchableState = TOUCHABLE_STATE.UNDETERMINED;
|
|
92
|
-
|
|
93
|
-
// handlePressIn in called on first touch on traveling inside component.
|
|
94
|
-
// Handles state transition with delay.
|
|
95
|
-
handlePressIn() {
|
|
96
|
-
if (this.props.delayPressIn) {
|
|
97
|
-
this.pressInTimeout = setTimeout(() => {
|
|
98
|
-
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
99
|
-
this.pressInTimeout = null;
|
|
100
|
-
}, this.props.delayPressIn);
|
|
101
|
-
} else {
|
|
102
|
-
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
103
|
-
}
|
|
104
|
-
if (this.props.onLongPress) {
|
|
105
|
-
const time =
|
|
106
|
-
(this.props.delayPressIn || 0) + (this.props.delayLongPress || 0);
|
|
107
|
-
this.longPressTimeout = setTimeout(this.onLongPressDetected, time);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// handleMoveOutside in called on traveling outside component.
|
|
111
|
-
// Handles state transition with delay.
|
|
112
|
-
handleMoveOutside() {
|
|
113
|
-
if (this.props.delayPressOut) {
|
|
114
|
-
this.pressOutTimeout =
|
|
115
|
-
this.pressOutTimeout ||
|
|
116
|
-
setTimeout(() => {
|
|
117
|
-
this.moveToState(TOUCHABLE_STATE.MOVED_OUTSIDE);
|
|
118
|
-
this.pressOutTimeout = null;
|
|
119
|
-
}, this.props.delayPressOut);
|
|
120
|
-
} else {
|
|
121
|
-
this.moveToState(TOUCHABLE_STATE.MOVED_OUTSIDE);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// handleGoToUndetermined transits to UNDETERMINED state with proper delay
|
|
126
|
-
handleGoToUndetermined() {
|
|
127
|
-
clearTimeout(this.pressOutTimeout!); // TODO: maybe it can be undefined
|
|
128
|
-
if (this.props.delayPressOut) {
|
|
129
|
-
this.pressOutTimeout = setTimeout(() => {
|
|
130
|
-
if (this.STATE === TOUCHABLE_STATE.UNDETERMINED) {
|
|
131
|
-
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
132
|
-
}
|
|
133
|
-
this.moveToState(TOUCHABLE_STATE.UNDETERMINED);
|
|
134
|
-
this.pressOutTimeout = null;
|
|
135
|
-
}, this.props.delayPressOut);
|
|
136
|
-
} else {
|
|
137
|
-
if (this.STATE === TOUCHABLE_STATE.UNDETERMINED) {
|
|
138
|
-
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
139
|
-
}
|
|
140
|
-
this.moveToState(TOUCHABLE_STATE.UNDETERMINED);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
componentDidMount() {
|
|
145
|
-
this.reset();
|
|
146
|
-
}
|
|
147
|
-
// reset timeout to prevent memory leaks.
|
|
148
|
-
reset() {
|
|
149
|
-
this.longPressDetected = false;
|
|
150
|
-
this.pointerInside = true;
|
|
151
|
-
clearTimeout(this.pressInTimeout!);
|
|
152
|
-
clearTimeout(this.pressOutTimeout!);
|
|
153
|
-
clearTimeout(this.longPressTimeout!);
|
|
154
|
-
this.pressOutTimeout = null;
|
|
155
|
-
this.longPressTimeout = null;
|
|
156
|
-
this.pressInTimeout = null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// All states' transitions are defined here.
|
|
160
|
-
moveToState(newState: TouchableState) {
|
|
161
|
-
if (newState === this.STATE) {
|
|
162
|
-
// Ignore dummy transitions
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (newState === TOUCHABLE_STATE.BEGAN) {
|
|
166
|
-
// First touch and moving inside
|
|
167
|
-
this.props.onPressIn?.();
|
|
168
|
-
} else if (newState === TOUCHABLE_STATE.MOVED_OUTSIDE) {
|
|
169
|
-
// Moving outside
|
|
170
|
-
this.props.onPressOut?.();
|
|
171
|
-
} else if (newState === TOUCHABLE_STATE.UNDETERMINED) {
|
|
172
|
-
// Need to reset each time on transition to UNDETERMINED
|
|
173
|
-
this.reset();
|
|
174
|
-
if (this.STATE === TOUCHABLE_STATE.BEGAN) {
|
|
175
|
-
// ... and if it happens inside button.
|
|
176
|
-
this.props.onPressOut?.();
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
// Finally call lister (used by subclasses)
|
|
180
|
-
this.props.onStateChange?.(this.STATE, newState);
|
|
181
|
-
// ... and make transition.
|
|
182
|
-
this.STATE = newState;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
onGestureEvent = ({
|
|
186
|
-
nativeEvent: { pointerInside },
|
|
187
|
-
}: GestureEvent<NativeViewGestureHandlerPayload>) => {
|
|
188
|
-
if (this.pointerInside !== pointerInside) {
|
|
189
|
-
if (pointerInside) {
|
|
190
|
-
this.onMoveIn();
|
|
191
|
-
} else {
|
|
192
|
-
this.onMoveOut();
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
this.pointerInside = pointerInside;
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
onHandlerStateChange = ({
|
|
199
|
-
nativeEvent,
|
|
200
|
-
}: HandlerStateChangeEvent<NativeViewGestureHandlerPayload>) => {
|
|
201
|
-
const { state } = nativeEvent;
|
|
202
|
-
if (state === State.CANCELLED || state === State.FAILED) {
|
|
203
|
-
// Need to handle case with external cancellation (e.g. by ScrollView)
|
|
204
|
-
this.moveToState(TOUCHABLE_STATE.UNDETERMINED);
|
|
205
|
-
} else if (
|
|
206
|
-
// This platform check is an implication of slightly different behavior of handlers on different platform.
|
|
207
|
-
// And Android "Active" state is achieving on first move of a finger, not on press in.
|
|
208
|
-
// On iOS event on "Began" is not delivered.
|
|
209
|
-
state === (Platform.OS !== 'android' ? State.ACTIVE : State.BEGAN) &&
|
|
210
|
-
this.STATE === TOUCHABLE_STATE.UNDETERMINED
|
|
211
|
-
) {
|
|
212
|
-
// Moving inside requires
|
|
213
|
-
this.handlePressIn();
|
|
214
|
-
} else if (state === State.END) {
|
|
215
|
-
const shouldCallOnPress =
|
|
216
|
-
!this.longPressDetected &&
|
|
217
|
-
this.STATE !== TOUCHABLE_STATE.MOVED_OUTSIDE &&
|
|
218
|
-
this.pressOutTimeout === null;
|
|
219
|
-
this.handleGoToUndetermined();
|
|
220
|
-
if (shouldCallOnPress) {
|
|
221
|
-
// Calls only inside component whether no long press was called previously
|
|
222
|
-
this.props.onPress?.();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
onLongPressDetected = () => {
|
|
228
|
-
this.longPressDetected = true;
|
|
229
|
-
// checked for in the caller of `onLongPressDetected`, but better to check twice
|
|
230
|
-
this.props.onLongPress?.();
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
componentWillUnmount() {
|
|
234
|
-
// to prevent memory leaks
|
|
235
|
-
this.reset();
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
onMoveIn() {
|
|
239
|
-
if (this.STATE === TOUCHABLE_STATE.MOVED_OUTSIDE) {
|
|
240
|
-
// This call is not throttled with delays (like in RN's implementation).
|
|
241
|
-
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
onMoveOut() {
|
|
246
|
-
// long press should no longer be detected
|
|
247
|
-
clearTimeout(this.longPressTimeout!);
|
|
248
|
-
this.longPressTimeout = null;
|
|
249
|
-
if (this.STATE === TOUCHABLE_STATE.BEGAN) {
|
|
250
|
-
this.handleMoveOutside();
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
render() {
|
|
255
|
-
const hitSlop =
|
|
256
|
-
(typeof this.props.hitSlop === 'number'
|
|
257
|
-
? {
|
|
258
|
-
top: this.props.hitSlop,
|
|
259
|
-
left: this.props.hitSlop,
|
|
260
|
-
bottom: this.props.hitSlop,
|
|
261
|
-
right: this.props.hitSlop,
|
|
262
|
-
}
|
|
263
|
-
: this.props.hitSlop) ?? undefined;
|
|
264
|
-
|
|
265
|
-
const coreProps = {
|
|
266
|
-
accessible: this.props.accessible !== false,
|
|
267
|
-
accessibilityLabel: this.props.accessibilityLabel,
|
|
268
|
-
accessibilityHint: this.props.accessibilityHint,
|
|
269
|
-
accessibilityRole: this.props.accessibilityRole,
|
|
270
|
-
// TODO: check if changed to no 's' correctly, also removed 2 props that are no longer available: `accessibilityComponentType` and `accessibilityTraits`,
|
|
271
|
-
// would be good to check if it is ok for sure, see: https://github.com/facebook/react-native/issues/24016
|
|
272
|
-
accessibilityState: this.props.accessibilityState,
|
|
273
|
-
accessibilityActions: this.props.accessibilityActions,
|
|
274
|
-
onAccessibilityAction: this.props.onAccessibilityAction,
|
|
275
|
-
nativeID: this.props.nativeID,
|
|
276
|
-
onLayout: this.props.onLayout,
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
return (
|
|
280
|
-
<BaseButton
|
|
281
|
-
style={this.props.containerStyle}
|
|
282
|
-
onHandlerStateChange={
|
|
283
|
-
// TODO: not sure if it can be undefined instead of null
|
|
284
|
-
this.props.disabled ? undefined : this.onHandlerStateChange
|
|
285
|
-
}
|
|
286
|
-
onGestureEvent={this.onGestureEvent}
|
|
287
|
-
hitSlop={hitSlop}
|
|
288
|
-
shouldActivateOnStart={this.props.shouldActivateOnStart}
|
|
289
|
-
disallowInterruption={this.props.disallowInterruption}
|
|
290
|
-
testID={this.props.testID}
|
|
291
|
-
touchSoundDisabled={this.props.touchSoundDisabled ?? false}
|
|
292
|
-
enabled={!this.props.disabled}
|
|
293
|
-
{...this.props.extraButtonProps}
|
|
294
|
-
>
|
|
295
|
-
<Animated.View {...coreProps} style={this.props.style}>
|
|
296
|
-
{this.props.children}
|
|
297
|
-
</Animated.View>
|
|
298
|
-
</BaseButton>
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Component } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Animated,
|
|
5
|
+
Platform,
|
|
6
|
+
StyleProp,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
TouchableWithoutFeedbackProps,
|
|
9
|
+
Insets,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
import { State } from 'react-native-gesture-handler/src/State';
|
|
13
|
+
import { BaseButton } from '../GestureButtons';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
GestureEvent,
|
|
17
|
+
HandlerStateChangeEvent,
|
|
18
|
+
} from 'react-native-gesture-handler/src/handlers/gestureHandlerCommon';
|
|
19
|
+
import { NativeViewGestureHandlerPayload } from 'react-native-gesture-handler/src/handlers/NativeViewGestureHandler';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Each touchable is a states' machine which preforms transitions.
|
|
23
|
+
* On very beginning (and on the very end or recognition) touchable is
|
|
24
|
+
* UNDETERMINED. Then it moves to BEGAN. If touchable recognizes that finger
|
|
25
|
+
* travel outside it transits to special MOVED_OUTSIDE state. Gesture recognition
|
|
26
|
+
* finishes in UNDETERMINED state.
|
|
27
|
+
*/
|
|
28
|
+
export const TOUCHABLE_STATE = {
|
|
29
|
+
UNDETERMINED: 0,
|
|
30
|
+
BEGAN: 1,
|
|
31
|
+
MOVED_OUTSIDE: 2,
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
type TouchableState = (typeof TOUCHABLE_STATE)[keyof typeof TOUCHABLE_STATE];
|
|
35
|
+
|
|
36
|
+
export interface GenericTouchableProps
|
|
37
|
+
extends Omit<TouchableWithoutFeedbackProps, 'hitSlop'> {
|
|
38
|
+
// Decided to drop not used fields from RN's implementation.
|
|
39
|
+
// e.g. onBlur and onFocus as well as deprecated props. - TODO: this comment may be unuseful in this moment
|
|
40
|
+
|
|
41
|
+
// TODO: in RN these events get native event parameter, which prolly could be used in our implementation too
|
|
42
|
+
onPress?: () => void;
|
|
43
|
+
onPressIn?: () => void;
|
|
44
|
+
onPressOut?: () => void;
|
|
45
|
+
onLongPress?: () => void;
|
|
46
|
+
|
|
47
|
+
nativeID?: string;
|
|
48
|
+
shouldActivateOnStart?: boolean;
|
|
49
|
+
disallowInterruption?: boolean;
|
|
50
|
+
|
|
51
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
52
|
+
hitSlop?: Insets | number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface InternalProps {
|
|
56
|
+
extraButtonProps: any;
|
|
57
|
+
onStateChange?: (oldState: TouchableState, newState: TouchableState) => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// TODO: maybe can be better
|
|
61
|
+
// TODO: all clearTimeout have ! added, maybe they shouldn't ?
|
|
62
|
+
type Timeout = ReturnType<typeof setTimeout> | null | undefined;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* GenericTouchable is not intented to be used as it is.
|
|
66
|
+
* Should be treated as a source for the rest of touchables
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
export default class GenericTouchable extends Component<
|
|
70
|
+
GenericTouchableProps & InternalProps
|
|
71
|
+
> {
|
|
72
|
+
static defaultProps = {
|
|
73
|
+
delayLongPress: 600,
|
|
74
|
+
extraButtonProps: {
|
|
75
|
+
rippleColor: 'transparent',
|
|
76
|
+
exclusive: true,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// timeout handlers
|
|
81
|
+
pressInTimeout: Timeout;
|
|
82
|
+
pressOutTimeout: Timeout;
|
|
83
|
+
longPressTimeout: Timeout;
|
|
84
|
+
|
|
85
|
+
// This flag is required since recognition of longPress implies not-invoking onPress
|
|
86
|
+
longPressDetected = false;
|
|
87
|
+
|
|
88
|
+
pointerInside = true;
|
|
89
|
+
|
|
90
|
+
// State of touchable
|
|
91
|
+
STATE: TouchableState = TOUCHABLE_STATE.UNDETERMINED;
|
|
92
|
+
|
|
93
|
+
// handlePressIn in called on first touch on traveling inside component.
|
|
94
|
+
// Handles state transition with delay.
|
|
95
|
+
handlePressIn() {
|
|
96
|
+
if (this.props.delayPressIn) {
|
|
97
|
+
this.pressInTimeout = setTimeout(() => {
|
|
98
|
+
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
99
|
+
this.pressInTimeout = null;
|
|
100
|
+
}, this.props.delayPressIn);
|
|
101
|
+
} else {
|
|
102
|
+
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
103
|
+
}
|
|
104
|
+
if (this.props.onLongPress) {
|
|
105
|
+
const time =
|
|
106
|
+
(this.props.delayPressIn || 0) + (this.props.delayLongPress || 0);
|
|
107
|
+
this.longPressTimeout = setTimeout(this.onLongPressDetected, time);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// handleMoveOutside in called on traveling outside component.
|
|
111
|
+
// Handles state transition with delay.
|
|
112
|
+
handleMoveOutside() {
|
|
113
|
+
if (this.props.delayPressOut) {
|
|
114
|
+
this.pressOutTimeout =
|
|
115
|
+
this.pressOutTimeout ||
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
this.moveToState(TOUCHABLE_STATE.MOVED_OUTSIDE);
|
|
118
|
+
this.pressOutTimeout = null;
|
|
119
|
+
}, this.props.delayPressOut);
|
|
120
|
+
} else {
|
|
121
|
+
this.moveToState(TOUCHABLE_STATE.MOVED_OUTSIDE);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// handleGoToUndetermined transits to UNDETERMINED state with proper delay
|
|
126
|
+
handleGoToUndetermined() {
|
|
127
|
+
clearTimeout(this.pressOutTimeout!); // TODO: maybe it can be undefined
|
|
128
|
+
if (this.props.delayPressOut) {
|
|
129
|
+
this.pressOutTimeout = setTimeout(() => {
|
|
130
|
+
if (this.STATE === TOUCHABLE_STATE.UNDETERMINED) {
|
|
131
|
+
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
132
|
+
}
|
|
133
|
+
this.moveToState(TOUCHABLE_STATE.UNDETERMINED);
|
|
134
|
+
this.pressOutTimeout = null;
|
|
135
|
+
}, this.props.delayPressOut);
|
|
136
|
+
} else {
|
|
137
|
+
if (this.STATE === TOUCHABLE_STATE.UNDETERMINED) {
|
|
138
|
+
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
139
|
+
}
|
|
140
|
+
this.moveToState(TOUCHABLE_STATE.UNDETERMINED);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
componentDidMount() {
|
|
145
|
+
this.reset();
|
|
146
|
+
}
|
|
147
|
+
// reset timeout to prevent memory leaks.
|
|
148
|
+
reset() {
|
|
149
|
+
this.longPressDetected = false;
|
|
150
|
+
this.pointerInside = true;
|
|
151
|
+
clearTimeout(this.pressInTimeout!);
|
|
152
|
+
clearTimeout(this.pressOutTimeout!);
|
|
153
|
+
clearTimeout(this.longPressTimeout!);
|
|
154
|
+
this.pressOutTimeout = null;
|
|
155
|
+
this.longPressTimeout = null;
|
|
156
|
+
this.pressInTimeout = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// All states' transitions are defined here.
|
|
160
|
+
moveToState(newState: TouchableState) {
|
|
161
|
+
if (newState === this.STATE) {
|
|
162
|
+
// Ignore dummy transitions
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (newState === TOUCHABLE_STATE.BEGAN) {
|
|
166
|
+
// First touch and moving inside
|
|
167
|
+
this.props.onPressIn?.();
|
|
168
|
+
} else if (newState === TOUCHABLE_STATE.MOVED_OUTSIDE) {
|
|
169
|
+
// Moving outside
|
|
170
|
+
this.props.onPressOut?.();
|
|
171
|
+
} else if (newState === TOUCHABLE_STATE.UNDETERMINED) {
|
|
172
|
+
// Need to reset each time on transition to UNDETERMINED
|
|
173
|
+
this.reset();
|
|
174
|
+
if (this.STATE === TOUCHABLE_STATE.BEGAN) {
|
|
175
|
+
// ... and if it happens inside button.
|
|
176
|
+
this.props.onPressOut?.();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Finally call lister (used by subclasses)
|
|
180
|
+
this.props.onStateChange?.(this.STATE, newState);
|
|
181
|
+
// ... and make transition.
|
|
182
|
+
this.STATE = newState;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onGestureEvent = ({
|
|
186
|
+
nativeEvent: { pointerInside },
|
|
187
|
+
}: GestureEvent<NativeViewGestureHandlerPayload>) => {
|
|
188
|
+
if (this.pointerInside !== pointerInside) {
|
|
189
|
+
if (pointerInside) {
|
|
190
|
+
this.onMoveIn();
|
|
191
|
+
} else {
|
|
192
|
+
this.onMoveOut();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
this.pointerInside = pointerInside;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
onHandlerStateChange = ({
|
|
199
|
+
nativeEvent,
|
|
200
|
+
}: HandlerStateChangeEvent<NativeViewGestureHandlerPayload>) => {
|
|
201
|
+
const { state } = nativeEvent;
|
|
202
|
+
if (state === State.CANCELLED || state === State.FAILED) {
|
|
203
|
+
// Need to handle case with external cancellation (e.g. by ScrollView)
|
|
204
|
+
this.moveToState(TOUCHABLE_STATE.UNDETERMINED);
|
|
205
|
+
} else if (
|
|
206
|
+
// This platform check is an implication of slightly different behavior of handlers on different platform.
|
|
207
|
+
// And Android "Active" state is achieving on first move of a finger, not on press in.
|
|
208
|
+
// On iOS event on "Began" is not delivered.
|
|
209
|
+
state === (Platform.OS !== 'android' ? State.ACTIVE : State.BEGAN) &&
|
|
210
|
+
this.STATE === TOUCHABLE_STATE.UNDETERMINED
|
|
211
|
+
) {
|
|
212
|
+
// Moving inside requires
|
|
213
|
+
this.handlePressIn();
|
|
214
|
+
} else if (state === State.END) {
|
|
215
|
+
const shouldCallOnPress =
|
|
216
|
+
!this.longPressDetected &&
|
|
217
|
+
this.STATE !== TOUCHABLE_STATE.MOVED_OUTSIDE &&
|
|
218
|
+
this.pressOutTimeout === null;
|
|
219
|
+
this.handleGoToUndetermined();
|
|
220
|
+
if (shouldCallOnPress) {
|
|
221
|
+
// Calls only inside component whether no long press was called previously
|
|
222
|
+
this.props.onPress?.();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
onLongPressDetected = () => {
|
|
228
|
+
this.longPressDetected = true;
|
|
229
|
+
// checked for in the caller of `onLongPressDetected`, but better to check twice
|
|
230
|
+
this.props.onLongPress?.();
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
componentWillUnmount() {
|
|
234
|
+
// to prevent memory leaks
|
|
235
|
+
this.reset();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
onMoveIn() {
|
|
239
|
+
if (this.STATE === TOUCHABLE_STATE.MOVED_OUTSIDE) {
|
|
240
|
+
// This call is not throttled with delays (like in RN's implementation).
|
|
241
|
+
this.moveToState(TOUCHABLE_STATE.BEGAN);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
onMoveOut() {
|
|
246
|
+
// long press should no longer be detected
|
|
247
|
+
clearTimeout(this.longPressTimeout!);
|
|
248
|
+
this.longPressTimeout = null;
|
|
249
|
+
if (this.STATE === TOUCHABLE_STATE.BEGAN) {
|
|
250
|
+
this.handleMoveOutside();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
render() {
|
|
255
|
+
const hitSlop =
|
|
256
|
+
(typeof this.props.hitSlop === 'number'
|
|
257
|
+
? {
|
|
258
|
+
top: this.props.hitSlop,
|
|
259
|
+
left: this.props.hitSlop,
|
|
260
|
+
bottom: this.props.hitSlop,
|
|
261
|
+
right: this.props.hitSlop,
|
|
262
|
+
}
|
|
263
|
+
: this.props.hitSlop) ?? undefined;
|
|
264
|
+
|
|
265
|
+
const coreProps = {
|
|
266
|
+
accessible: this.props.accessible !== false,
|
|
267
|
+
accessibilityLabel: this.props.accessibilityLabel,
|
|
268
|
+
accessibilityHint: this.props.accessibilityHint,
|
|
269
|
+
accessibilityRole: this.props.accessibilityRole,
|
|
270
|
+
// TODO: check if changed to no 's' correctly, also removed 2 props that are no longer available: `accessibilityComponentType` and `accessibilityTraits`,
|
|
271
|
+
// would be good to check if it is ok for sure, see: https://github.com/facebook/react-native/issues/24016
|
|
272
|
+
accessibilityState: this.props.accessibilityState,
|
|
273
|
+
accessibilityActions: this.props.accessibilityActions,
|
|
274
|
+
onAccessibilityAction: this.props.onAccessibilityAction,
|
|
275
|
+
nativeID: this.props.nativeID,
|
|
276
|
+
onLayout: this.props.onLayout,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<BaseButton
|
|
281
|
+
style={this.props.containerStyle}
|
|
282
|
+
onHandlerStateChange={
|
|
283
|
+
// TODO: not sure if it can be undefined instead of null
|
|
284
|
+
this.props.disabled ? undefined : this.onHandlerStateChange
|
|
285
|
+
}
|
|
286
|
+
onGestureEvent={this.onGestureEvent}
|
|
287
|
+
hitSlop={hitSlop}
|
|
288
|
+
shouldActivateOnStart={this.props.shouldActivateOnStart}
|
|
289
|
+
disallowInterruption={this.props.disallowInterruption}
|
|
290
|
+
testID={this.props.testID}
|
|
291
|
+
touchSoundDisabled={this.props.touchSoundDisabled ?? false}
|
|
292
|
+
enabled={!this.props.disabled}
|
|
293
|
+
{...this.props.extraButtonProps}
|
|
294
|
+
>
|
|
295
|
+
<Animated.View {...coreProps} style={this.props.style}>
|
|
296
|
+
{this.props.children}
|
|
297
|
+
</Animated.View>
|
|
298
|
+
</BaseButton>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|