react-native-gesture-handler 1.2.1 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. package/DrawerLayout.js +5 -4
  2. package/GestureButtons.js +166 -0
  3. package/GestureComponents.js +63 -0
  4. package/GestureComponents.web.js +35 -0
  5. package/GestureHandler.js +10 -621
  6. package/GestureHandlerButton.web.js +4 -12
  7. package/GestureHandlerPropTypes.js +45 -0
  8. package/Gestures.js +278 -0
  9. package/NativeViewGestureHandler.js +14 -0
  10. package/PlatformConstants.web.js +3 -1
  11. package/RNGestureHandler.podspec +1 -1
  12. package/RNGestureHandlerModule.web.js +49 -0
  13. package/State.js +12 -1
  14. package/Swipeable.js +6 -11
  15. package/android/build.gradle +3 -7
  16. package/android/lib/src/main/java/com/swmansion/gesturehandler/GestureHandlerOrchestrator.java +1 -1
  17. package/android/lib/src/main/java/com/swmansion/gesturehandler/PanGestureHandler.java +1 -1
  18. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEnabledRootView.java +1 -1
  19. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.java +2 -2
  20. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.java +1 -1
  21. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerPackage.java +1 -1
  22. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRegistry.java +1 -1
  23. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootInterface.java +1 -1
  24. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.java +1 -1
  25. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootViewManager.java +1 -1
  26. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerStateChangeEvent.java +2 -2
  27. package/createHandler.js +46 -20
  28. package/createNativeWrapper.js +86 -0
  29. package/ios/RNGestureHandler.xcodeproj/project.pbxproj +4 -4
  30. package/package.json +20 -17
  31. package/react-native-gesture-handler.d.ts +25 -3
  32. package/touchables/GenericTouchable.js +3 -1
  33. package/touchables/TouchableHighlight.js +1 -3
  34. package/touchables/TouchableOpacity.web.js +2 -0
  35. package/touchables/TouchableWithoutFeedback.js +4 -2
  36. package/web/DiscreteGestureHandler.js +66 -0
  37. package/web/DraggingGestureHandler.js +22 -0
  38. package/web/Errors.js +5 -0
  39. package/web/FlingGestureHandler.js +137 -0
  40. package/web/GestureHandler.js +442 -0
  41. package/web/IndiscreteGestureHandler.js +33 -0
  42. package/web/LongPressGestureHandler.js +50 -0
  43. package/web/NativeViewGestureHandler.js +38 -0
  44. package/web/NodeManager.js +24 -0
  45. package/web/PanGestureHandler.js +213 -0
  46. package/web/PinchGestureHandler.js +24 -0
  47. package/web/PressGestureHandler.js +147 -0
  48. package/web/RotationGestureHandler.js +24 -0
  49. package/web/TapGestureHandler.js +160 -0
  50. package/web/constants.js +48 -0
  51. package/web/utils.js +14 -0
  52. package/Directions.web.js +0 -6
  53. package/Swipeable.web.js +0 -4
  54. package/createHandler.web.js +0 -205
@@ -0,0 +1,442 @@
1
+ import Hammer from 'hammerjs';
2
+ import { findNodeHandle } from 'react-native';
3
+
4
+ import State from '../State';
5
+ import { EventMap } from './constants';
6
+ import * as NodeManager from './NodeManager';
7
+
8
+ let _gestureInstances = 0;
9
+
10
+ class GestureHandler {
11
+ isGestureRunning = false;
12
+ hasGestureFailed = false;
13
+ view = null;
14
+ config = {};
15
+ hammer = null;
16
+ pendingGestures = {};
17
+ oldState = State.UNDETERMINED;
18
+ previousState = State.UNDETERMINED;
19
+
20
+ get id() {
21
+ return `${this.name}${this._gestureInstance}`;
22
+ }
23
+
24
+ get isDiscrete() {
25
+ return false;
26
+ }
27
+
28
+ get shouldEnableGestureOnSetup() {
29
+ throw new Error('Must override GestureHandler.shouldEnableGestureOnSetup');
30
+ }
31
+
32
+ constructor() {
33
+ this._gestureInstance = _gestureInstances++;
34
+ }
35
+
36
+ getConfig() {
37
+ return this.config;
38
+ }
39
+
40
+ onWaitingEnded(gesture) {}
41
+
42
+ removePendingGesture(id) {
43
+ delete this.pendingGestures[id];
44
+ }
45
+
46
+ addPendingGesture(gesture) {
47
+ this.pendingGestures[gesture.id] = gesture;
48
+ }
49
+
50
+ isGestureEnabledForEvent() {
51
+ return { success: true };
52
+ }
53
+
54
+ parseNativeEvent(nativeEvent) {
55
+ return nativeEvent;
56
+ }
57
+
58
+ get NativeGestureClass() {
59
+ throw new Error('Must override GestureHandler.NativeGestureClass');
60
+ }
61
+
62
+ updateHasCustomActivationCriteria(config) {
63
+ return true;
64
+ }
65
+
66
+ clearSelfAsPending = () => {
67
+ if (Array.isArray(this.config.waitFor)) {
68
+ for (const gesture of this.config.waitFor) {
69
+ gesture.removePendingGesture(this.id);
70
+ }
71
+ }
72
+ };
73
+
74
+ updateGestureConfig({ enabled = true, ...props }) {
75
+ this.clearSelfAsPending();
76
+
77
+ this.config = ensureConfig({ enabled, ...props });
78
+ this._hasCustomActivationCriteria = this.updateHasCustomActivationCriteria(this.config);
79
+ if (Array.isArray(this.config.waitFor)) {
80
+ for (const gesture of this.config.waitFor) {
81
+ gesture.addPendingGesture(this);
82
+ }
83
+ }
84
+
85
+ if (this.hammer) {
86
+ this.sync();
87
+ }
88
+ return this.config;
89
+ }
90
+
91
+ destroy = () => {
92
+ this.clearSelfAsPending();
93
+
94
+ if (this.hammer) {
95
+ this.hammer.stop();
96
+ this.hammer.destroy();
97
+ }
98
+ this.hammer = null;
99
+ };
100
+
101
+ isPointInView = ({ x, y }) => {
102
+ const rect = this.view.getBoundingClientRect();
103
+ const pointerInside = x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
104
+ return pointerInside;
105
+ };
106
+
107
+ getState(type) {
108
+ return EventMap[type];
109
+ }
110
+
111
+ transformEventData(event) {
112
+ const { eventType, maxPointers: numberOfPointers } = event;
113
+ // const direction = DirectionMap[ev.direction];
114
+ const changedTouch = event.changedPointers[0];
115
+ const pointerInside = this.isPointInView({ x: changedTouch.clientX, y: changedTouch.clientY });
116
+
117
+ const state = this.getState(eventType);
118
+ if (state !== this.previousState) {
119
+ this.oldState = this.previousState;
120
+ this.previousState = state;
121
+ }
122
+
123
+ return {
124
+ nativeEvent: {
125
+ numberOfPointers,
126
+ state,
127
+ pointerInside,
128
+ ...this.transformNativeEvent(event),
129
+ // onHandlerStateChange only
130
+ handlerTag: this.handlerTag,
131
+ target: this.ref,
132
+ oldState: this.oldState,
133
+ },
134
+ timeStamp: Date.now(),
135
+ };
136
+ }
137
+
138
+ transformNativeEvent(event) {
139
+ return {};
140
+ }
141
+
142
+ sendEvent = nativeEvent => {
143
+ const {
144
+ onGestureHandlerStateChange: onHandlerStateChange,
145
+ onGestureHandlerEvent: onGestureEvent,
146
+ } = this.ref.props;
147
+
148
+ const event = this.transformEventData(nativeEvent);
149
+
150
+ // Reset the state for the next gesture
151
+ if (nativeEvent.isFinal) {
152
+ this.oldState = State.UNDETERMINED;
153
+ this.previousState = State.UNDETERMINED;
154
+ }
155
+
156
+ invokeNullableMethod('onGestureEvent', onGestureEvent, event);
157
+ invokeNullableMethod('onHandlerStateChange', onHandlerStateChange, event);
158
+ };
159
+
160
+ cancelPendingGestures(event) {
161
+ for (const gesture of Object.values(this.pendingGestures)) {
162
+ if (gesture && gesture.isGestureRunning) {
163
+ gesture.hasGestureFailed = true;
164
+ gesture.cancelEvent(event);
165
+ }
166
+ }
167
+ }
168
+
169
+ notifyPendingGestures() {
170
+ for (const gesture of Object.values(this.pendingGestures)) {
171
+ if (gesture) {
172
+ gesture.onWaitingEnded(this);
173
+ }
174
+ }
175
+ }
176
+
177
+ onGestureEnded(event) {
178
+ this.isGestureRunning = false;
179
+ this.cancelPendingGestures(event);
180
+ }
181
+
182
+ forceInvalidate(event) {
183
+ if (this.isGestureRunning) {
184
+ this.hasGestureFailed = true;
185
+ this.cancelEvent(event);
186
+ }
187
+ }
188
+
189
+ cancelEvent(event) {
190
+ this.notifyPendingGestures();
191
+ this.sendEvent({
192
+ ...event,
193
+ eventType: Hammer.INPUT_CANCEL,
194
+ isFinal: true,
195
+ });
196
+ this.onGestureEnded(event);
197
+ }
198
+
199
+ onRawEvent({ isFirst }) {
200
+ if (isFirst) {
201
+ this.hasGestureFailed = false;
202
+ }
203
+ }
204
+
205
+ setView(ref) {
206
+ if (ref == null) {
207
+ this.destroy();
208
+ this.view = null;
209
+ return;
210
+ }
211
+
212
+ this.ref = ref;
213
+
214
+ this.view = findNodeHandle(ref);
215
+ this.hammer = new Hammer.Manager(this.view);
216
+
217
+ this.oldState = State.UNDETERMINED;
218
+ this.previousState = State.UNDETERMINED;
219
+
220
+ const { NativeGestureClass } = this;
221
+ const gesture = new NativeGestureClass(this.getHammerConfig());
222
+ this.hammer.add(gesture);
223
+
224
+ this.hammer.on('hammer.input', ev => {
225
+ if (!this.config.enabled) {
226
+ this.hasGestureFailed = false;
227
+ this.isGestureRunning = false;
228
+ return;
229
+ }
230
+
231
+ this.onRawEvent(ev);
232
+
233
+ // TODO: Bacon: Check against something other than null
234
+ // The isFirst value is not called when the first rotation is calculated.
235
+ if (this.initialRotation === null && ev.rotation !== 0) {
236
+ this.initialRotation = ev.rotation;
237
+ }
238
+ if (ev.isFinal) {
239
+ // in favor of a willFail otherwise the last frame of the gesture will be captured.
240
+ setTimeout(() => {
241
+ this.initialRotation = null;
242
+ this.hasGestureFailed = false;
243
+ });
244
+ }
245
+ });
246
+
247
+ this.setupEvents();
248
+ this.sync();
249
+ }
250
+
251
+ setupEvents() {
252
+ if (!this.isDiscrete) {
253
+ this.hammer.on(`${this.name}start`, event => this.onStart(event));
254
+ this.hammer.on(`${this.name}end ${this.name}cancel`, event => this.onGestureEnded(event));
255
+ }
256
+ this.hammer.on(this.name, ev => this.onGestureActivated(ev));
257
+ }
258
+
259
+ onStart({ deltaX, deltaY, rotation }) {
260
+ this.isGestureRunning = true;
261
+ this.__initialX = deltaX;
262
+ this.__initialY = deltaY;
263
+ this.initialRotation = rotation;
264
+ }
265
+
266
+ onGestureActivated(ev) {
267
+ this.sendEvent(ev);
268
+ }
269
+
270
+ onSuccess() {}
271
+
272
+ _getPendingGestures() {
273
+ if (Array.isArray(this.config.waitFor) && this.config.waitFor.length) {
274
+ // Get the list of gestures that this gesture is still waiting for.
275
+ // Use `=== false` in case a ref that isn't a gesture handler is used.
276
+ const stillWaiting = this.config.waitFor.filter(
277
+ ({ hasGestureFailed }) => hasGestureFailed === false
278
+ );
279
+ return stillWaiting;
280
+ }
281
+ return [];
282
+ }
283
+
284
+ getHammerConfig() {
285
+ const pointers =
286
+ this.config.minPointers === this.config.maxPointers ? this.config.minPointers : 0;
287
+ return {
288
+ pointers,
289
+ };
290
+ }
291
+
292
+ sync = () => {
293
+ const gesture = this.hammer.get(this.name);
294
+ if (!gesture) return;
295
+
296
+ const enable = (recognizer, inputData) => {
297
+ if (!this.config.enabled) {
298
+ this.isGestureRunning = false;
299
+ this.hasGestureFailed = false;
300
+ return false;
301
+ }
302
+
303
+ // Prevent events before the system is ready.
304
+ if (!inputData || !recognizer.options || typeof inputData.maxPointers === 'undefined') {
305
+ return this.shouldEnableGestureOnSetup;
306
+ }
307
+
308
+ if (this.hasGestureFailed) {
309
+ return false;
310
+ }
311
+
312
+ if (!this.isDiscrete) {
313
+ if (this.isGestureRunning) {
314
+ return true;
315
+ }
316
+ // The built-in hammer.js "waitFor" doesn't work across multiple views.
317
+ // Only process if there are views to wait for.
318
+ this._stillWaiting = this._getPendingGestures();
319
+ // This gesture should continue waiting.
320
+ if (this._stillWaiting.length) {
321
+ // Check to see if one of the gestures you're waiting for has started.
322
+ // If it has then the gesture should fail.
323
+ for (const gesture of this._stillWaiting) {
324
+ // When the target gesture has started, this gesture must force fail.
325
+ if (!gesture.isDiscrete && gesture.isGestureRunning) {
326
+ this.hasGestureFailed = true;
327
+ this.isGestureRunning = false;
328
+ return false;
329
+ }
330
+ }
331
+ // This gesture shouldn't start until the others have finished.
332
+ return false;
333
+ }
334
+ }
335
+
336
+ // Use default behaviour
337
+ if (!this._hasCustomActivationCriteria) {
338
+ return true;
339
+ }
340
+
341
+ const deltaRotation =
342
+ this.initialRotation == null ? 0 : inputData.rotation - this.initialRotation;
343
+ const { success, failed } = this.isGestureEnabledForEvent(this.getConfig(), recognizer, {
344
+ ...inputData,
345
+ deltaRotation,
346
+ });
347
+
348
+ if (failed) {
349
+ this.simulateCancelEvent(inputData);
350
+ this.hasGestureFailed = true;
351
+ }
352
+ return success;
353
+ };
354
+
355
+ const params = this.getHammerConfig();
356
+ gesture.set({ ...params, enable });
357
+ };
358
+
359
+ simulateCancelEvent(inputData) {}
360
+ }
361
+
362
+ // Used for sending data to a callback or AnimatedEvent
363
+ function invokeNullableMethod(name, method, event) {
364
+ if (method) {
365
+ if (typeof method === 'function') {
366
+ method(event);
367
+ } else {
368
+ // For use with reanimated's AnimatedEvent
369
+ if ('__getHandler' in method && typeof method.__getHandler === 'function') {
370
+ const handler = method.__getHandler();
371
+ invokeNullableMethod(name, handler, event);
372
+ } else {
373
+ if ('__nodeConfig' in method) {
374
+ const { argMapping } = method.__nodeConfig;
375
+ if (Array.isArray(argMapping)) {
376
+ for (const index in argMapping) {
377
+ const [key] = argMapping[index];
378
+ if (key in event.nativeEvent) {
379
+ method.__nodeConfig.argMapping[index] = [key, event.nativeEvent[key]];
380
+ }
381
+ }
382
+ }
383
+ }
384
+ }
385
+ }
386
+ }
387
+ }
388
+
389
+ // Validate the props
390
+ function ensureConfig(config) {
391
+ const props = { ...config };
392
+
393
+ if ('minDist' in config) {
394
+ props.minDist = config.minDist;
395
+ props.minDistSq = props.minDist * props.minDist;
396
+ }
397
+ if ('minVelocity' in config) {
398
+ props.minVelocity = config.minVelocity;
399
+ props.minVelocitySq = props.minVelocity * props.minVelocity;
400
+ }
401
+ if ('maxDist' in config) {
402
+ props.maxDist = config.maxDist;
403
+ props.maxDistSq = config.maxDist * config.maxDist;
404
+ }
405
+ if ('waitFor' in config) {
406
+ props.waitFor = asArray(config.waitFor)
407
+ .map(({ _handlerTag }) => NodeManager.getHandler(_handlerTag))
408
+ .filter(v => v);
409
+ } else {
410
+ props.waitFor = null;
411
+ }
412
+
413
+ [
414
+ 'minPointers',
415
+ 'maxPointers',
416
+ 'minDist',
417
+ 'maxDist',
418
+ 'maxDistSq',
419
+ 'minVelocitySq',
420
+ 'minDistSq',
421
+ 'minVelocity',
422
+ 'failOffsetXStart',
423
+ 'failOffsetYStart',
424
+ 'failOffsetXEnd',
425
+ 'failOffsetYEnd',
426
+ 'activeOffsetXStart',
427
+ 'activeOffsetXEnd',
428
+ 'activeOffsetYStart',
429
+ 'activeOffsetYEnd',
430
+ ].forEach(prop => {
431
+ if (typeof props[prop] === 'undefined') {
432
+ props[prop] = Number.NaN;
433
+ }
434
+ });
435
+ return props;
436
+ }
437
+
438
+ function asArray(value) {
439
+ return value == null ? [] : Array.isArray(value) ? value : [value];
440
+ }
441
+
442
+ export default GestureHandler;
@@ -0,0 +1,33 @@
1
+ import GestureHandler from './GestureHandler';
2
+
3
+ /**
4
+ * The base class for **Rotation** and **Pinch** gesture handlers.
5
+ */
6
+ class IndiscreteGestureHandler extends GestureHandler {
7
+ get shouldEnableGestureOnSetup() {
8
+ return false;
9
+ }
10
+
11
+ updateGestureConfig({ minPointers = 2, maxPointers = 2, ...props }) {
12
+ return super.updateGestureConfig({
13
+ minPointers,
14
+ maxPointers,
15
+ ...props,
16
+ });
17
+ }
18
+
19
+ isGestureEnabledForEvent(
20
+ { minPointers, maxPointers },
21
+ recognizer,
22
+ { maxPointers: pointerLength }
23
+ ) {
24
+ if (pointerLength > maxPointers) {
25
+ return { failed: true };
26
+ }
27
+ const validPointerCount = pointerLength >= minPointers;
28
+ return {
29
+ success: validPointerCount,
30
+ };
31
+ }
32
+ }
33
+ export default IndiscreteGestureHandler;
@@ -0,0 +1,50 @@
1
+ import Hammer from 'hammerjs';
2
+
3
+ import State from '../State';
4
+ import PressGestureHandler from './PressGestureHandler';
5
+ import { isnan } from './utils';
6
+
7
+ class LongPressGestureHandler extends PressGestureHandler {
8
+ get minDurationMs() {
9
+ return isnan(this.config.minDurationMs) ? 251 : this.config.minDurationMs;
10
+ }
11
+
12
+ get maxDist() {
13
+ return isnan(this.config.maxDist) ? 9 : this.config.maxDist;
14
+ }
15
+
16
+ updateHasCustomActivationCriteria({ maxDistSq }) {
17
+ return !isnan(maxDistSq);
18
+ }
19
+
20
+ getConfig() {
21
+ if (!this._hasCustomActivationCriteria) {
22
+ // Default config
23
+ // If no params have been defined then this config should emulate the native gesture as closely as possible.
24
+ return {
25
+ shouldCancelWhenOutside: true,
26
+ maxDistSq: 10,
27
+ };
28
+ }
29
+ return this.config;
30
+ }
31
+
32
+ getHammerConfig() {
33
+ return {
34
+ ...super.getHammerConfig(),
35
+ // threshold: this.maxDist,
36
+ time: this.minDurationMs,
37
+ };
38
+ }
39
+
40
+ getState(type) {
41
+ return {
42
+ [Hammer.INPUT_START]: State.ACTIVE,
43
+ [Hammer.INPUT_MOVE]: State.ACTIVE,
44
+ [Hammer.INPUT_END]: State.END,
45
+ [Hammer.INPUT_CANCEL]: State.FAILED,
46
+ }[type];
47
+ }
48
+ }
49
+
50
+ export default LongPressGestureHandler;
@@ -0,0 +1,38 @@
1
+ import DiscreteGestureHandler from './DiscreteGestureHandler';
2
+ import * as NodeManager from './NodeManager';
3
+ import PressGestureHandler from './PressGestureHandler';
4
+ import { TEST_MIN_IF_NOT_NAN, VEC_LEN_SQ } from './utils';
5
+
6
+ class NativeViewGestureHandler extends PressGestureHandler {
7
+ onRawEvent(ev) {
8
+ super.onRawEvent(ev);
9
+ if (!ev.isFinal) {
10
+ // if (this.ref instanceof ScrollView) {
11
+ if (TEST_MIN_IF_NOT_NAN(VEC_LEN_SQ({ x: ev.deltaX, y: ev.deltaY }), 10)) {
12
+ if (this.config.disallowInterruption) {
13
+ const gestures = Object.values(NodeManager.getNodes()).filter(gesture => {
14
+ const { handlerTag, view, isGestureRunning } = gesture;
15
+ return (
16
+ // Check if this gesture isn't self
17
+ handlerTag !== this.handlerTag &&
18
+ // Ensure the gesture needs to be cancelled
19
+ isGestureRunning &&
20
+ // ScrollView can cancel discrete gestures like taps and presses
21
+ gesture instanceof DiscreteGestureHandler &&
22
+ // Ensure a view exists and is a child of the current view
23
+ view &&
24
+ this.view.contains(view)
25
+ );
26
+ });
27
+ // Cancel all of the gestures that passed the filter
28
+ for (const gesture of gestures) {
29
+ // TODO: Bacon: Send some cached event.
30
+ gesture.forceInvalidate(ev);
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ export default NativeViewGestureHandler;
@@ -0,0 +1,24 @@
1
+ let gestures = {};
2
+
3
+ export function getHandler(tag) {
4
+ if (tag in gestures) return gestures[tag];
5
+
6
+ throw new Error('No handler for tag ' + tag);
7
+ }
8
+
9
+ export function createGestureHandler(handlerTag, handler) {
10
+ if (handlerTag in gestures) {
11
+ throw new Error('Handler with tag ' + handlerTag + ' already exists');
12
+ }
13
+ gestures[handlerTag] = handler;
14
+ gestures[handlerTag].handlerTag = handlerTag;
15
+ }
16
+
17
+ export function dropGestureHandler(handlerTag) {
18
+ getHandler(handlerTag).destroy();
19
+ delete gestures[handlerTag];
20
+ }
21
+
22
+ export function getNodes() {
23
+ return { ...gestures };
24
+ }