@react-native-oh-tpl/react-native-gesture-handler 2.12.6-1 → 2.12.6-2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. package/harmony/gesture_handler/LICENSE +21 -0
  2. package/harmony/gesture_handler/OAT.xml +44 -0
  3. package/harmony/gesture_handler/README.OpenSource +11 -0
  4. package/harmony/gesture_handler/README.md +1 -0
  5. package/harmony/gesture_handler/build-profile.json5 +7 -7
  6. package/harmony/gesture_handler/hvigorfile.ts +2 -2
  7. package/harmony/gesture_handler/index.ets +2 -2
  8. package/harmony/gesture_handler/oh-package.json5 +13 -11
  9. package/harmony/gesture_handler/src/main/cpp/CMakeLists.txt +8 -8
  10. package/harmony/gesture_handler/src/main/cpp/GestureHandlerPackage.cpp +33 -33
  11. package/harmony/gesture_handler/src/main/cpp/GestureHandlerPackage.h +14 -14
  12. package/harmony/gesture_handler/src/main/cpp/RNGestureHandlerButtonComponentDescriptor.h +60 -60
  13. package/harmony/gesture_handler/src/main/cpp/RNGestureHandlerModule.cpp +17 -17
  14. package/harmony/gesture_handler/src/main/cpp/RNGestureHandlerModule.h +11 -11
  15. package/harmony/gesture_handler/src/main/cpp/RNGestureHandlerRootViewComponentDescriptor.h +60 -60
  16. package/harmony/gesture_handler/src/main/ets/CircularBuffer.ts +42 -42
  17. package/harmony/gesture_handler/src/main/ets/Event.ts +67 -67
  18. package/harmony/gesture_handler/src/main/ets/EventDispatcher.ts +37 -37
  19. package/harmony/gesture_handler/src/main/ets/GestureHandler.ts +663 -663
  20. package/harmony/gesture_handler/src/main/ets/GestureHandlerArkUIAdapter.ets +201 -201
  21. package/harmony/gesture_handler/src/main/ets/GestureHandlerFactory.ts +44 -44
  22. package/harmony/gesture_handler/src/main/ets/GestureHandlerOrchestrator.ts +280 -280
  23. package/harmony/gesture_handler/src/main/ets/GestureHandlerPackage.ts +22 -22
  24. package/harmony/gesture_handler/src/main/ets/GestureHandlerRegistry.ts +27 -27
  25. package/harmony/gesture_handler/src/main/ets/InteractionManager.ts +108 -108
  26. package/harmony/gesture_handler/src/main/ets/LeastSquareSolver.ts +182 -182
  27. package/harmony/gesture_handler/src/main/ets/NativeViewGestureHandler.ts +114 -114
  28. package/harmony/gesture_handler/src/main/ets/OutgoingEvent.ts +33 -33
  29. package/harmony/gesture_handler/src/main/ets/PanGestureHandler.ts +327 -327
  30. package/harmony/gesture_handler/src/main/ets/PointerTracker.ts +239 -239
  31. package/harmony/gesture_handler/src/main/ets/RNGHError.ts +4 -4
  32. package/harmony/gesture_handler/src/main/ets/RNGHLogger.ts +28 -28
  33. package/harmony/gesture_handler/src/main/ets/RNGHRootTouchHandler.ets +57 -57
  34. package/harmony/gesture_handler/src/main/ets/RNGestureHandlerButton.ets +36 -36
  35. package/harmony/gesture_handler/src/main/ets/RNGestureHandlerModule.ts +125 -125
  36. package/harmony/gesture_handler/src/main/ets/RNGestureHandlerRootView.ets +56 -55
  37. package/harmony/gesture_handler/src/main/ets/RNOHScrollLocker.ts +10 -10
  38. package/harmony/gesture_handler/src/main/ets/State.ts +46 -46
  39. package/harmony/gesture_handler/src/main/ets/TapGestureHandler.ts +205 -205
  40. package/harmony/gesture_handler/src/main/ets/Vector2D.ts +36 -36
  41. package/harmony/gesture_handler/src/main/ets/VelocityTracker.ts +98 -98
  42. package/harmony/gesture_handler/src/main/ets/View.ts +70 -70
  43. package/harmony/gesture_handler/src/main/ets/ViewRegistry.ts +42 -42
  44. package/harmony/gesture_handler/src/main/ets/pages/Index.ets +16 -16
  45. package/harmony/gesture_handler/src/main/ets/webviewability/WebviewAbility.ts +41 -41
  46. package/harmony/gesture_handler/src/main/module.json5 +6 -6
  47. package/harmony/gesture_handler/src/main/resources/base/element/color.json +7 -7
  48. package/harmony/gesture_handler/src/main/resources/base/element/string.json +15 -15
  49. package/harmony/gesture_handler/src/main/resources/base/profile/main_pages.json +5 -5
  50. package/harmony/gesture_handler/src/main/resources/en_US/element/string.json +15 -15
  51. package/harmony/gesture_handler/src/main/resources/zh_CN/element/string.json +15 -15
  52. package/harmony/gesture_handler.har +0 -0
  53. package/lib/commonjs/components/touchables/GenericTouchable.js +9 -9
  54. package/lib/commonjs/components/touchables/TouchableOpacity.js +2 -2
  55. package/lib/commonjs/handlers/createNativeWrapper.js +6 -6
  56. package/lib/commonjs/handlers/gestures/GestureDetector.js +3 -3
  57. package/lib/module/components/touchables/GenericTouchable.js +9 -9
  58. package/lib/module/components/touchables/TouchableOpacity.js +2 -2
  59. package/lib/module/handlers/createNativeWrapper.js +6 -6
  60. package/lib/module/handlers/gestures/GestureDetector.js +3 -3
  61. package/package.json +70 -70
  62. package/src/RNGestureHandlerModule.ts +6 -6
  63. package/src/components/GestureButtons.tsx +334 -334
  64. package/src/components/GestureHandlerButton.tsx +5 -5
  65. package/src/components/GestureHandlerRootView.tsx +34 -34
  66. package/src/components/RNGestureHandlerButton.tsx +23 -23
  67. package/src/components/touchables/GenericTouchable.tsx +301 -301
  68. package/src/components/touchables/TouchableOpacity.tsx +76 -76
  69. package/src/components/touchables/TouchableWithoutFeedback.tsx +14 -14
  70. package/src/components/touchables/index.ts +7 -7
  71. package/src/handlers/NativeViewGestureHandler.ts +55 -55
  72. package/src/handlers/PanGestureHandler.ts +327 -327
  73. package/src/handlers/TapGestureHandler.ts +95 -95
  74. package/src/handlers/createHandler.tsx +535 -535
  75. package/src/handlers/createNativeWrapper.tsx +81 -81
  76. package/src/handlers/gestureHandlerCommon.ts +15 -15
  77. package/src/handlers/gestures/GestureDetector.tsx +823 -823
  78. package/src/index.ts +172 -172
  79. package/src/init.ts +18 -18
@@ -1,823 +1,823 @@
1
- /**
2
- * Why is this file patched?
3
- * - replacing import to RNGestureHandlerModule - original code uses (old) Native Modules but RNOH supports only Turbo Modules
4
- */
5
-
6
- import React, { useContext, useEffect, useRef, useState } from 'react';
7
- import {
8
- GestureType,
9
- HandlerCallbacks,
10
- BaseGesture,
11
- GestureRef,
12
- CALLBACK_TYPE,
13
- } from 'react-native-gesture-handler/src/handlers/gestures/gesture';
14
- import { Reanimated, SharedValue } from 'react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper';
15
- import { registerHandler, unregisterHandler } from 'react-native-gesture-handler/src/handlers/handlersRegistry';
16
- import { RNGestureHandlerModule } from '../../RNGestureHandlerModule'; // RNGH: patch
17
- import {
18
- baseGestureHandlerWithMonitorProps,
19
- filterConfig,
20
- findNodeHandle,
21
- GestureTouchEvent,
22
- GestureUpdateEvent,
23
- GestureStateChangeEvent,
24
- HandlerStateChangeEvent,
25
- UserSelect,
26
- } from 'react-native-gesture-handler/src/handlers/gestureHandlerCommon';
27
- import {
28
- scheduleFlushOperations,
29
- } from '../gestureHandlerCommon'; // RNGH: patch
30
- import {
31
- GestureStateManager,
32
- GestureStateManagerType,
33
- } from 'react-native-gesture-handler/src/handlers/gestures/gestureStateManager';
34
- import { flingGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/FlingGestureHandler';
35
- import { forceTouchGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/ForceTouchGestureHandler';
36
- import { longPressGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/LongPressGestureHandler';
37
- import {
38
- panGestureHandlerProps,
39
- panGestureHandlerCustomNativeProps,
40
- } from 'react-native-gesture-handler/src/handlers/PanGestureHandler';
41
- import { tapGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/TapGestureHandler';
42
- import { State } from 'react-native-gesture-handler/src/State';
43
- import { TouchEventType } from 'react-native-gesture-handler/src/TouchEventType';
44
- import { ComposedGesture } from 'react-native-gesture-handler/src/handlers/gestures/gestureComposition';
45
- import { ActionType } from 'react-native-gesture-handler/src/ActionType';
46
- import {
47
- isFabric,
48
- isJestEnv,
49
- REACT_NATIVE_VERSION,
50
- tagMessage,
51
- } from 'react-native-gesture-handler/src/utils';
52
- import { getShadowNodeFromRef } from 'react-native-gesture-handler/src/getShadowNodeFromRef';
53
- import { Platform } from 'react-native';
54
- import type RNGestureHandlerModuleWeb from 'react-native-gesture-handler/src/RNGestureHandlerModule.web';
55
- import { onGestureHandlerEvent } from 'react-native-gesture-handler/src/handlers/gestures/eventReceiver';
56
- import { RNRenderer } from 'react-native-gesture-handler/src/RNRenderer';
57
- import { isNewWebImplementationEnabled } from 'react-native-gesture-handler/src/EnableNewWebImplementation';
58
- import { nativeViewGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/NativeViewGestureHandler';
59
- import GestureHandlerRootViewContext from 'react-native-gesture-handler/src/GestureHandlerRootViewContext';
60
-
61
- declare const global: {
62
- isFormsStackingContext: (node: unknown) => boolean | null; // JSI function
63
- };
64
-
65
- const ALLOWED_PROPS = [
66
- ...baseGestureHandlerWithMonitorProps,
67
- ...tapGestureHandlerProps,
68
- ...panGestureHandlerProps,
69
- ...panGestureHandlerCustomNativeProps,
70
- ...longPressGestureHandlerProps,
71
- ...forceTouchGestureHandlerProps,
72
- ...flingGestureHandlerProps,
73
- ...nativeViewGestureHandlerProps,
74
- ];
75
-
76
- export type GestureConfigReference = {
77
- config: GestureType[];
78
- animatedEventHandler: unknown;
79
- animatedHandlers: SharedValue<
80
- HandlerCallbacks<Record<string, unknown>>[] | null
81
- > | null;
82
- firstExecution: boolean;
83
- useReanimatedHook: boolean;
84
- };
85
-
86
- function convertToHandlerTag(ref: GestureRef): number {
87
- if (typeof ref === 'number') {
88
- return ref;
89
- } else if (ref instanceof BaseGesture) {
90
- return ref.handlerTag;
91
- } else {
92
- // @ts-ignore in this case it should be a ref either to gesture object or
93
- // a gesture handler component, in both cases handlerTag property exists
94
- return ref.current?.handlerTag ?? -1;
95
- }
96
- }
97
-
98
- function extractValidHandlerTags(interactionGroup: GestureRef[] | undefined) {
99
- return (
100
- interactionGroup?.map(convertToHandlerTag)?.filter((tag) => tag > 0) ?? []
101
- );
102
- }
103
-
104
- function dropHandlers(preparedGesture: GestureConfigReference) {
105
- for (const handler of preparedGesture.config) {
106
- RNGestureHandlerModule.dropGestureHandler(handler.handlerTag);
107
-
108
- unregisterHandler(handler.handlerTag, handler.config.testId);
109
- }
110
-
111
- scheduleFlushOperations();
112
- }
113
-
114
- function checkGestureCallbacksForWorklets(gesture: GestureType) {
115
- // if a gesture is explicitly marked to run on the JS thread there is no need to check
116
- // if callbacks are worklets as the user is aware they will be ran on the JS thread
117
- if (gesture.config.runOnJS) {
118
- return;
119
- }
120
-
121
- const areSomeNotWorklets = gesture.handlers.isWorklet.includes(false);
122
- const areSomeWorklets = gesture.handlers.isWorklet.includes(true);
123
-
124
- // if some of the callbacks are worklets and some are not, and the gesture is not
125
- // explicitly marked with `.runOnJS(true)` show an error
126
- if (areSomeNotWorklets && areSomeWorklets) {
127
- console.error(
128
- tagMessage(
129
- `Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as 'worklet' if you wish to run them on the UI thread or use '.runOnJS(true)' modifier on the gesture explicitly to run all callbacks on the JS thread.`
130
- )
131
- );
132
- }
133
- }
134
-
135
- interface WebEventHandler {
136
- onGestureHandlerEvent: (event: HandlerStateChangeEvent<unknown>) => void;
137
- onGestureHandlerStateChange?: (
138
- event: HandlerStateChangeEvent<unknown>
139
- ) => void;
140
- }
141
-
142
- interface AttachHandlersConfig {
143
- preparedGesture: GestureConfigReference;
144
- gestureConfig: ComposedGesture | GestureType;
145
- gesture: GestureType[];
146
- viewTag: number;
147
- webEventHandlersRef: React.RefObject<WebEventHandler>;
148
- mountedRef: React.RefObject<boolean>;
149
- }
150
-
151
- function attachHandlers({
152
- preparedGesture,
153
- gestureConfig,
154
- gesture,
155
- viewTag,
156
- webEventHandlersRef,
157
- mountedRef,
158
- }: AttachHandlersConfig) {
159
- if (!preparedGesture.firstExecution) {
160
- gestureConfig.initialize();
161
- } else {
162
- preparedGesture.firstExecution = false;
163
- }
164
-
165
- // use queueMicrotask to extract handlerTags, because all refs should be initialized
166
- // when it's ran
167
- queueMicrotask(() => {
168
- if (!mountedRef.current) {
169
- return;
170
- }
171
- gestureConfig.prepare();
172
- });
173
-
174
- for (const handler of gesture) {
175
- checkGestureCallbacksForWorklets(handler);
176
- RNGestureHandlerModule.createGestureHandler(
177
- handler.handlerName,
178
- handler.handlerTag,
179
- filterConfig(handler.config, ALLOWED_PROPS)
180
- );
181
-
182
- registerHandler(handler.handlerTag, handler, handler.config.testId);
183
- }
184
-
185
- // use queueMicrotask to extract handlerTags, because all refs should be initialized
186
- // when it's ran
187
- queueMicrotask(() => {
188
- if (!mountedRef.current) {
189
- return;
190
- }
191
- for (const handler of gesture) {
192
- let requireToFail: number[] = [];
193
- if (handler.config.requireToFail) {
194
- requireToFail = extractValidHandlerTags(handler.config.requireToFail);
195
- }
196
-
197
- let simultaneousWith: number[] = [];
198
- if (handler.config.simultaneousWith) {
199
- simultaneousWith = extractValidHandlerTags(
200
- handler.config.simultaneousWith
201
- );
202
- }
203
-
204
- RNGestureHandlerModule.updateGestureHandler(
205
- handler.handlerTag,
206
- filterConfig(handler.config, ALLOWED_PROPS, {
207
- simultaneousHandlers: simultaneousWith,
208
- waitFor: requireToFail,
209
- })
210
- );
211
- }
212
-
213
- scheduleFlushOperations();
214
- });
215
-
216
- preparedGesture.config = gesture;
217
-
218
- for (const gesture of preparedGesture.config) {
219
- const actionType = gesture.shouldUseReanimated
220
- ? ActionType.REANIMATED_WORKLET
221
- : ActionType.JS_FUNCTION_NEW_API;
222
-
223
- if (Platform.OS === 'web') {
224
- (
225
- RNGestureHandlerModule.attachGestureHandler as typeof RNGestureHandlerModuleWeb.attachGestureHandler
226
- )(
227
- gesture.handlerTag,
228
- viewTag,
229
- ActionType.JS_FUNCTION_OLD_API, // ignored on web
230
- webEventHandlersRef
231
- );
232
- } else {
233
- RNGestureHandlerModule.attachGestureHandler(
234
- gesture.handlerTag,
235
- viewTag,
236
- actionType
237
- );
238
- }
239
- }
240
-
241
- if (preparedGesture.animatedHandlers) {
242
- const isAnimatedGesture = (g: GestureType) => g.shouldUseReanimated;
243
-
244
- preparedGesture.animatedHandlers.value = gesture
245
- .filter(isAnimatedGesture)
246
- .map((g) => g.handlers) as unknown as HandlerCallbacks<
247
- Record<string, unknown>
248
- >[];
249
- }
250
- }
251
-
252
- function updateHandlers(
253
- preparedGesture: GestureConfigReference,
254
- gestureConfig: ComposedGesture | GestureType,
255
- gesture: GestureType[],
256
- mountedRef: React.RefObject<boolean>
257
- ) {
258
- gestureConfig.prepare();
259
-
260
- for (let i = 0; i < gesture.length; i++) {
261
- const handler = preparedGesture.config[i];
262
- checkGestureCallbacksForWorklets(handler);
263
-
264
- // only update handlerTag when it's actually different, it may be the same
265
- // if gesture config object is wrapped with useMemo
266
- if (gesture[i].handlerTag !== handler.handlerTag) {
267
- gesture[i].handlerTag = handler.handlerTag;
268
- gesture[i].handlers.handlerTag = handler.handlerTag;
269
- }
270
- }
271
-
272
- // use queueMicrotask to extract handlerTags, because when it's ran, all refs should be updated
273
- // and handlerTags in BaseGesture references should be updated in the loop above (we need to wait
274
- // in case of external relations)
275
- queueMicrotask(() => {
276
- if (!mountedRef.current) {
277
- return;
278
- }
279
- for (let i = 0; i < gesture.length; i++) {
280
- const handler = preparedGesture.config[i];
281
-
282
- handler.config = gesture[i].config;
283
- handler.handlers = gesture[i].handlers;
284
-
285
- const requireToFail = extractValidHandlerTags(
286
- handler.config.requireToFail
287
- );
288
-
289
- const simultaneousWith = extractValidHandlerTags(
290
- handler.config.simultaneousWith
291
- );
292
-
293
- RNGestureHandlerModule.updateGestureHandler(
294
- handler.handlerTag,
295
- filterConfig(handler.config, ALLOWED_PROPS, {
296
- simultaneousHandlers: simultaneousWith,
297
- waitFor: requireToFail,
298
- })
299
- );
300
-
301
- registerHandler(handler.handlerTag, handler, handler.config.testId);
302
- }
303
-
304
- if (preparedGesture.animatedHandlers) {
305
- const previousHandlersValue =
306
- preparedGesture.animatedHandlers.value ?? [];
307
- const newHandlersValue = preparedGesture.config
308
- .filter((g) => g.shouldUseReanimated) // ignore gestures that shouldn't run on UI
309
- .map((g) => g.handlers) as unknown as HandlerCallbacks<
310
- Record<string, unknown>
311
- >[];
312
-
313
- // if amount of gesture configs changes, we need to update the callbacks in shared value
314
- let shouldUpdateSharedValue =
315
- previousHandlersValue.length !== newHandlersValue.length;
316
-
317
- if (!shouldUpdateSharedValue) {
318
- // if the amount is the same, we need to check if any of the configs inside has changed
319
- for (let i = 0; i < newHandlersValue.length; i++) {
320
- if (
321
- // we can use the `gestureId` prop as it's unique for every config instance
322
- newHandlersValue[i].gestureId !== previousHandlersValue[i].gestureId
323
- ) {
324
- shouldUpdateSharedValue = true;
325
- break;
326
- }
327
- }
328
- }
329
-
330
- if (shouldUpdateSharedValue) {
331
- preparedGesture.animatedHandlers.value = newHandlersValue;
332
- }
333
- }
334
-
335
- scheduleFlushOperations();
336
- });
337
- }
338
-
339
- function needsToReattach(
340
- preparedGesture: GestureConfigReference,
341
- gesture: GestureType[]
342
- ) {
343
- if (gesture.length !== preparedGesture.config.length) {
344
- return true;
345
- }
346
- for (let i = 0; i < gesture.length; i++) {
347
- if (
348
- gesture[i].handlerName !== preparedGesture.config[i].handlerName ||
349
- gesture[i].shouldUseReanimated !==
350
- preparedGesture.config[i].shouldUseReanimated
351
- ) {
352
- return true;
353
- }
354
- }
355
-
356
- return false;
357
- }
358
-
359
- function isStateChangeEvent(
360
- event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent
361
- ): event is GestureStateChangeEvent {
362
- 'worklet';
363
- // @ts-ignore Yes, the oldState prop is missing on GestureTouchEvent, that's the point
364
- return event.oldState != null;
365
- }
366
-
367
- function isTouchEvent(
368
- event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent
369
- ): event is GestureTouchEvent {
370
- 'worklet';
371
- return event.eventType != null;
372
- }
373
-
374
- function getHandler(
375
- type: CALLBACK_TYPE,
376
- gesture: HandlerCallbacks<Record<string, unknown>>
377
- ) {
378
- 'worklet';
379
- switch (type) {
380
- case CALLBACK_TYPE.BEGAN:
381
- return gesture.onBegin;
382
- case CALLBACK_TYPE.START:
383
- return gesture.onStart;
384
- case CALLBACK_TYPE.UPDATE:
385
- return gesture.onUpdate;
386
- case CALLBACK_TYPE.CHANGE:
387
- return gesture.onChange;
388
- case CALLBACK_TYPE.END:
389
- return gesture.onEnd;
390
- case CALLBACK_TYPE.FINALIZE:
391
- return gesture.onFinalize;
392
- case CALLBACK_TYPE.TOUCHES_DOWN:
393
- return gesture.onTouchesDown;
394
- case CALLBACK_TYPE.TOUCHES_MOVE:
395
- return gesture.onTouchesMove;
396
- case CALLBACK_TYPE.TOUCHES_UP:
397
- return gesture.onTouchesUp;
398
- case CALLBACK_TYPE.TOUCHES_CANCELLED:
399
- return gesture.onTouchesCancelled;
400
- }
401
- }
402
-
403
- function touchEventTypeToCallbackType(
404
- eventType: TouchEventType
405
- ): CALLBACK_TYPE {
406
- 'worklet';
407
- switch (eventType) {
408
- case TouchEventType.TOUCHES_DOWN:
409
- return CALLBACK_TYPE.TOUCHES_DOWN;
410
- case TouchEventType.TOUCHES_MOVE:
411
- return CALLBACK_TYPE.TOUCHES_MOVE;
412
- case TouchEventType.TOUCHES_UP:
413
- return CALLBACK_TYPE.TOUCHES_UP;
414
- case TouchEventType.TOUCHES_CANCELLED:
415
- return CALLBACK_TYPE.TOUCHES_CANCELLED;
416
- }
417
- return CALLBACK_TYPE.UNDEFINED;
418
- }
419
-
420
- function runWorklet(
421
- type: CALLBACK_TYPE,
422
- gesture: HandlerCallbacks<Record<string, unknown>>,
423
- event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent,
424
- ...args: any[]
425
- ) {
426
- 'worklet';
427
- const handler = getHandler(type, gesture);
428
- if (gesture.isWorklet[type]) {
429
- // @ts-ignore Logic below makes sure the correct event is send to the
430
- // correct handler.
431
- handler?.(event, ...args);
432
- } else if (handler) {
433
- console.warn(tagMessage('Animated gesture callback must be a worklet'));
434
- }
435
- }
436
-
437
- function useAnimatedGesture(
438
- preparedGesture: GestureConfigReference,
439
- needsRebuild: boolean
440
- ) {
441
- if (!Reanimated) {
442
- return;
443
- }
444
-
445
- // Hooks are called conditionally, but the condition is whether the
446
- // react-native-reanimated is installed, which shouldn't change while running
447
- // eslint-disable-next-line react-hooks/rules-of-hooks
448
- const sharedHandlersCallbacks = Reanimated.useSharedValue<
449
- HandlerCallbacks<Record<string, unknown>>[] | null
450
- >(null);
451
-
452
- // eslint-disable-next-line react-hooks/rules-of-hooks
453
- const lastUpdateEvent = Reanimated.useSharedValue<
454
- (GestureUpdateEvent | undefined)[]
455
- >([]);
456
-
457
- // not every gesture needs a state controller, init them lazily
458
- const stateControllers: GestureStateManagerType[] = [];
459
-
460
- const callback = (
461
- event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent
462
- ) => {
463
- 'worklet';
464
-
465
- const currentCallback = sharedHandlersCallbacks.value;
466
- if (!currentCallback) {
467
- return;
468
- }
469
-
470
- for (let i = 0; i < currentCallback.length; i++) {
471
- const gesture = currentCallback[i];
472
-
473
- if (event.handlerTag === gesture.handlerTag) {
474
- if (isStateChangeEvent(event)) {
475
- if (
476
- event.oldState === State.UNDETERMINED &&
477
- event.state === State.BEGAN
478
- ) {
479
- runWorklet(CALLBACK_TYPE.BEGAN, gesture, event);
480
- } else if (
481
- (event.oldState === State.BEGAN ||
482
- event.oldState === State.UNDETERMINED) &&
483
- event.state === State.ACTIVE
484
- ) {
485
- runWorklet(CALLBACK_TYPE.START, gesture, event);
486
- lastUpdateEvent.value[gesture.handlerTag] = undefined;
487
- } else if (
488
- event.oldState !== event.state &&
489
- event.state === State.END
490
- ) {
491
- if (event.oldState === State.ACTIVE) {
492
- runWorklet(CALLBACK_TYPE.END, gesture, event, true);
493
- }
494
- runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, true);
495
- } else if (
496
- (event.state === State.FAILED || event.state === State.CANCELLED) &&
497
- event.state !== event.oldState
498
- ) {
499
- if (event.oldState === State.ACTIVE) {
500
- runWorklet(CALLBACK_TYPE.END, gesture, event, false);
501
- }
502
- runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, false);
503
- }
504
- } else if (isTouchEvent(event)) {
505
- if (!stateControllers[i]) {
506
- stateControllers[i] = GestureStateManager.create(event.handlerTag);
507
- }
508
-
509
- if (event.eventType !== TouchEventType.UNDETERMINED) {
510
- runWorklet(
511
- touchEventTypeToCallbackType(event.eventType),
512
- gesture,
513
- event,
514
- stateControllers[i]
515
- );
516
- }
517
- } else {
518
- runWorklet(CALLBACK_TYPE.UPDATE, gesture, event);
519
-
520
- if (gesture.onChange && gesture.changeEventCalculator) {
521
- runWorklet(
522
- CALLBACK_TYPE.CHANGE,
523
- gesture,
524
- gesture.changeEventCalculator?.(
525
- event,
526
- lastUpdateEvent.value[gesture.handlerTag]
527
- )
528
- );
529
-
530
- lastUpdateEvent.value[gesture.handlerTag] = event;
531
- }
532
- }
533
- }
534
- }
535
- };
536
-
537
- // eslint-disable-next-line react-hooks/rules-of-hooks
538
- const event = Reanimated.useEvent(
539
- callback,
540
- ['onGestureHandlerStateChange', 'onGestureHandlerEvent'],
541
- needsRebuild
542
- );
543
-
544
- preparedGesture.animatedEventHandler = event;
545
- preparedGesture.animatedHandlers = sharedHandlersCallbacks;
546
- }
547
-
548
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
549
- function validateDetectorChildren(ref: any) {
550
- // finds the first native view under the Wrap component and traverses the fiber tree upwards
551
- // to check whether there is more than one native view as a pseudo-direct child of GestureDetector
552
- // i.e. this is not ok:
553
- // Wrap
554
- // |
555
- // / \
556
- // / \
557
- // / \
558
- // / \
559
- // NativeView NativeView
560
- //
561
- // but this is fine:
562
- // Wrap
563
- // |
564
- // NativeView
565
- // |
566
- // / \
567
- // / \
568
- // / \
569
- // / \
570
- // NativeView NativeView
571
- if (__DEV__ && Platform.OS !== 'web') {
572
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
573
- const wrapType =
574
- REACT_NATIVE_VERSION.minor > 63 || REACT_NATIVE_VERSION.major > 0
575
- ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
576
- ref._reactInternals.elementType
577
- : // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
578
- ref._reactInternalFiber.elementType;
579
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
580
- let instance =
581
- RNRenderer.findHostInstance_DEPRECATED(
582
- ref
583
- )._internalFiberInstanceHandleDEV;
584
-
585
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
586
- while (instance && instance.elementType !== wrapType) {
587
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
588
- if (instance.sibling) {
589
- throw new Error(
590
- 'GestureDetector has more than one native view as its children. This can happen if you are using a custom component that renders multiple views, like React.Fragment. You should wrap content of GestureDetector with a <View> or <Animated.View>.'
591
- );
592
- }
593
-
594
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
595
- instance = instance.return;
596
- }
597
- }
598
- }
599
-
600
- const applyUserSelectProp = (
601
- userSelect: UserSelect,
602
- gesture: ComposedGesture | GestureType
603
- ): void => {
604
- for (const g of gesture.toGestureArray()) {
605
- g.config.userSelect = userSelect;
606
- }
607
- };
608
-
609
- interface GestureDetectorProps {
610
- gesture: ComposedGesture | GestureType;
611
- userSelect?: UserSelect;
612
- children?: React.ReactNode;
613
- }
614
- interface GestureDetectorState {
615
- firstRender: boolean;
616
- viewRef: React.Component | null;
617
- previousViewTag: number;
618
- forceReattach: boolean;
619
- }
620
- export const GestureDetector = (props: GestureDetectorProps) => {
621
- const rootViewContext = useContext(GestureHandlerRootViewContext);
622
- if (__DEV__ && !rootViewContext && !isJestEnv() && Platform.OS !== 'web' && (Platform.OS as any) !== "harmony") { // RNOH: patch
623
- throw new Error(
624
- 'GestureDetector must be used as a descendant of GestureHandlerRootView. Otherwise the gestures will not be recognized. See https://docs.swmansion.com/react-native-gesture-handler/docs/installation for more details.'
625
- );
626
- }
627
-
628
- const gestureConfig = props.gesture;
629
-
630
- if (props.userSelect) {
631
- applyUserSelectProp(props.userSelect, gestureConfig);
632
- }
633
-
634
- const gesture = gestureConfig.toGestureArray();
635
- const useReanimatedHook = gesture.some((g) => g.shouldUseReanimated);
636
-
637
- // store state in ref to prevent unnecessary renders
638
- const state = useRef<GestureDetectorState>({
639
- firstRender: true,
640
- viewRef: null,
641
- previousViewTag: -1,
642
- forceReattach: false,
643
- }).current;
644
- const mountedRef = useRef(false);
645
- const webEventHandlersRef = useRef<WebEventHandler>({
646
- onGestureHandlerEvent: (e: HandlerStateChangeEvent<unknown>) => {
647
- onGestureHandlerEvent(e.nativeEvent);
648
- },
649
- onGestureHandlerStateChange: isNewWebImplementationEnabled()
650
- ? (e: HandlerStateChangeEvent<unknown>) => {
651
- onGestureHandlerEvent(e.nativeEvent);
652
- }
653
- : undefined,
654
- });
655
-
656
- const [renderState, setRenderState] = useState(false);
657
- function forceRender() {
658
- setRenderState(!renderState);
659
- }
660
-
661
- const preparedGesture = React.useRef<GestureConfigReference>({
662
- config: gesture,
663
- animatedEventHandler: null,
664
- animatedHandlers: null,
665
- firstExecution: true,
666
- useReanimatedHook: useReanimatedHook,
667
- }).current;
668
-
669
- if (useReanimatedHook !== preparedGesture.useReanimatedHook) {
670
- throw new Error(
671
- tagMessage(
672
- 'You cannot change the thread the callbacks are ran on while the app is running'
673
- )
674
- );
675
- }
676
-
677
- function onHandlersUpdate(skipConfigUpdate?: boolean) {
678
- // if the underlying view has changed we need to reattach handlers to the new view
679
- const viewTag = findNodeHandle(state.viewRef) as number;
680
- const forceReattach = viewTag !== state.previousViewTag;
681
-
682
- if (forceReattach || needsToReattach(preparedGesture, gesture)) {
683
- validateDetectorChildren(state.viewRef);
684
- dropHandlers(preparedGesture);
685
- attachHandlers({
686
- preparedGesture,
687
- gestureConfig,
688
- gesture,
689
- webEventHandlersRef,
690
- viewTag,
691
- mountedRef,
692
- });
693
-
694
- state.previousViewTag = viewTag;
695
- state.forceReattach = forceReattach;
696
- if (forceReattach) {
697
- forceRender();
698
- }
699
- } else if (!skipConfigUpdate) {
700
- updateHandlers(preparedGesture, gestureConfig, gesture, mountedRef);
701
- }
702
- }
703
-
704
- // Reanimated event should be rebuilt only when gestures are reattached, otherwise
705
- // config update will be enough as all necessary items are stored in shared values anyway
706
- const needsToRebuildReanimatedEvent =
707
- preparedGesture.firstExecution ||
708
- needsToReattach(preparedGesture, gesture) ||
709
- state.forceReattach;
710
-
711
- state.forceReattach = false;
712
-
713
- if (preparedGesture.firstExecution) {
714
- gestureConfig.initialize();
715
- }
716
-
717
- if (useReanimatedHook) {
718
- // Whether animatedGesture or gesture is used shouldn't change while the app is running
719
- // eslint-disable-next-line react-hooks/rules-of-hooks
720
- useAnimatedGesture(preparedGesture, needsToRebuildReanimatedEvent);
721
- }
722
-
723
- useEffect(() => {
724
- const viewTag = findNodeHandle(state.viewRef) as number;
725
- state.firstRender = true;
726
- mountedRef.current = true;
727
-
728
- validateDetectorChildren(state.viewRef);
729
-
730
- attachHandlers({
731
- preparedGesture,
732
- gestureConfig,
733
- gesture,
734
- webEventHandlersRef,
735
- viewTag,
736
- mountedRef,
737
- });
738
-
739
- return () => {
740
- mountedRef.current = false;
741
- dropHandlers(preparedGesture);
742
- };
743
- }, []);
744
-
745
- useEffect(() => {
746
- if (!state.firstRender) {
747
- onHandlersUpdate();
748
- } else {
749
- state.firstRender = false;
750
- }
751
- }, [props]);
752
-
753
- const refFunction = (ref: unknown) => {
754
- if (ref !== null) {
755
- // @ts-ignore Just setting the view ref
756
- state.viewRef = ref;
757
-
758
- // if it's the first render, also set the previousViewTag to prevent reattaching gestures when not needed
759
- if (state.previousViewTag === -1) {
760
- state.previousViewTag = findNodeHandle(state.viewRef) as number;
761
- }
762
-
763
- // pass true as `skipConfigUpdate`, here we only want to trigger the eventual reattaching of handlers
764
- // in case the view has changed, while config update would be handled be the `useEffect` above
765
- onHandlersUpdate(true);
766
-
767
- if (isFabric()) {
768
- const node = getShadowNodeFromRef(ref);
769
- if (global.isFormsStackingContext(node) === false) {
770
- console.error(
771
- tagMessage(
772
- 'GestureDetector has received a child that may get view-flattened. ' +
773
- '\nTo prevent it from misbehaving you need to wrap the child with a `<View collapsable={false}>`.'
774
- )
775
- );
776
- }
777
- }
778
- }
779
- };
780
-
781
- if (useReanimatedHook) {
782
- return (
783
- <AnimatedWrap
784
- ref={refFunction}
785
- onGestureHandlerEvent={preparedGesture.animatedEventHandler}>
786
- {props.children}
787
- </AnimatedWrap>
788
- );
789
- } else {
790
- return <Wrap ref={refFunction}>{props.children}</Wrap>;
791
- }
792
- };
793
-
794
- class Wrap extends React.Component<{
795
- onGestureHandlerEvent?: unknown;
796
- // implicit `children` prop has been removed in @types/react^18.0.0
797
- children?: React.ReactNode;
798
- }> {
799
- render() {
800
- try {
801
- // I don't think that fighting with types over such a simple function is worth it
802
- // The only thing it does is add 'collapsable: false' to the child component
803
- // to make sure it is in the native view hierarchy so the detector can find
804
- // correct viewTag to attach to.
805
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
806
- const child: any = React.Children.only(this.props.children);
807
- return React.cloneElement(
808
- child,
809
- { collapsable: false },
810
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
811
- child.props.children
812
- );
813
- } catch (e) {
814
- throw new Error(
815
- tagMessage(
816
- `GestureDetector got more than one view as a child. If you want the gesture to work on multiple views, wrap them with a common parent and attach the gesture to that view.`
817
- )
818
- );
819
- }
820
- }
821
- }
822
-
823
- const AnimatedWrap = Reanimated?.default?.createAnimatedComponent(Wrap) ?? Wrap;
1
+ /**
2
+ * Why is this file patched?
3
+ * - replacing import to RNGestureHandlerModule - original code uses (old) Native Modules but RNOH supports only Turbo Modules
4
+ */
5
+
6
+ import React, { useContext, useEffect, useRef, useState } from 'react';
7
+ import {
8
+ GestureType,
9
+ HandlerCallbacks,
10
+ BaseGesture,
11
+ GestureRef,
12
+ CALLBACK_TYPE,
13
+ } from 'react-native-gesture-handler/src/handlers/gestures/gesture';
14
+ import { Reanimated, SharedValue } from 'react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper';
15
+ import { registerHandler, unregisterHandler } from 'react-native-gesture-handler/src/handlers/handlersRegistry';
16
+ import { RNGestureHandlerModule } from '../../RNGestureHandlerModule'; // RNGH: patch
17
+ import {
18
+ baseGestureHandlerWithMonitorProps,
19
+ filterConfig,
20
+ findNodeHandle,
21
+ GestureTouchEvent,
22
+ GestureUpdateEvent,
23
+ GestureStateChangeEvent,
24
+ HandlerStateChangeEvent,
25
+ UserSelect,
26
+ } from 'react-native-gesture-handler/src/handlers/gestureHandlerCommon';
27
+ import {
28
+ scheduleFlushOperations,
29
+ } from '../gestureHandlerCommon'; // RNGH: patch
30
+ import {
31
+ GestureStateManager,
32
+ GestureStateManagerType,
33
+ } from 'react-native-gesture-handler/src/handlers/gestures/gestureStateManager';
34
+ import { flingGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/FlingGestureHandler';
35
+ import { forceTouchGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/ForceTouchGestureHandler';
36
+ import { longPressGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/LongPressGestureHandler';
37
+ import {
38
+ panGestureHandlerProps,
39
+ panGestureHandlerCustomNativeProps,
40
+ } from 'react-native-gesture-handler/src/handlers/PanGestureHandler';
41
+ import { tapGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/TapGestureHandler';
42
+ import { State } from 'react-native-gesture-handler/src/State';
43
+ import { TouchEventType } from 'react-native-gesture-handler/src/TouchEventType';
44
+ import { ComposedGesture } from 'react-native-gesture-handler/src/handlers/gestures/gestureComposition';
45
+ import { ActionType } from 'react-native-gesture-handler/src/ActionType';
46
+ import {
47
+ isFabric,
48
+ isJestEnv,
49
+ REACT_NATIVE_VERSION,
50
+ tagMessage,
51
+ } from 'react-native-gesture-handler/src/utils';
52
+ import { getShadowNodeFromRef } from 'react-native-gesture-handler/src/getShadowNodeFromRef';
53
+ import { Platform } from 'react-native';
54
+ import type RNGestureHandlerModuleWeb from 'react-native-gesture-handler/src/RNGestureHandlerModule.web';
55
+ import { onGestureHandlerEvent } from 'react-native-gesture-handler/src/handlers/gestures/eventReceiver';
56
+ import { RNRenderer } from 'react-native-gesture-handler/src/RNRenderer';
57
+ import { isNewWebImplementationEnabled } from 'react-native-gesture-handler/src/EnableNewWebImplementation';
58
+ import { nativeViewGestureHandlerProps } from 'react-native-gesture-handler/src/handlers/NativeViewGestureHandler';
59
+ import GestureHandlerRootViewContext from 'react-native-gesture-handler/src/GestureHandlerRootViewContext';
60
+
61
+ declare const global: {
62
+ isFormsStackingContext: (node: unknown) => boolean | null; // JSI function
63
+ };
64
+
65
+ const ALLOWED_PROPS = [
66
+ ...baseGestureHandlerWithMonitorProps,
67
+ ...tapGestureHandlerProps,
68
+ ...panGestureHandlerProps,
69
+ ...panGestureHandlerCustomNativeProps,
70
+ ...longPressGestureHandlerProps,
71
+ ...forceTouchGestureHandlerProps,
72
+ ...flingGestureHandlerProps,
73
+ ...nativeViewGestureHandlerProps,
74
+ ];
75
+
76
+ export type GestureConfigReference = {
77
+ config: GestureType[];
78
+ animatedEventHandler: unknown;
79
+ animatedHandlers: SharedValue<
80
+ HandlerCallbacks<Record<string, unknown>>[] | null
81
+ > | null;
82
+ firstExecution: boolean;
83
+ useReanimatedHook: boolean;
84
+ };
85
+
86
+ function convertToHandlerTag(ref: GestureRef): number {
87
+ if (typeof ref === 'number') {
88
+ return ref;
89
+ } else if (ref instanceof BaseGesture) {
90
+ return ref.handlerTag;
91
+ } else {
92
+ // @ts-ignore in this case it should be a ref either to gesture object or
93
+ // a gesture handler component, in both cases handlerTag property exists
94
+ return ref.current?.handlerTag ?? -1;
95
+ }
96
+ }
97
+
98
+ function extractValidHandlerTags(interactionGroup: GestureRef[] | undefined) {
99
+ return (
100
+ interactionGroup?.map(convertToHandlerTag)?.filter((tag) => tag > 0) ?? []
101
+ );
102
+ }
103
+
104
+ function dropHandlers(preparedGesture: GestureConfigReference) {
105
+ for (const handler of preparedGesture.config) {
106
+ RNGestureHandlerModule.dropGestureHandler(handler.handlerTag);
107
+
108
+ unregisterHandler(handler.handlerTag, handler.config.testId);
109
+ }
110
+
111
+ scheduleFlushOperations();
112
+ }
113
+
114
+ function checkGestureCallbacksForWorklets(gesture: GestureType) {
115
+ // if a gesture is explicitly marked to run on the JS thread there is no need to check
116
+ // if callbacks are worklets as the user is aware they will be ran on the JS thread
117
+ if (gesture.config.runOnJS) {
118
+ return;
119
+ }
120
+
121
+ const areSomeNotWorklets = gesture.handlers.isWorklet.includes(false);
122
+ const areSomeWorklets = gesture.handlers.isWorklet.includes(true);
123
+
124
+ // if some of the callbacks are worklets and some are not, and the gesture is not
125
+ // explicitly marked with `.runOnJS(true)` show an error
126
+ if (areSomeNotWorklets && areSomeWorklets) {
127
+ console.error(
128
+ tagMessage(
129
+ `Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as 'worklet' if you wish to run them on the UI thread or use '.runOnJS(true)' modifier on the gesture explicitly to run all callbacks on the JS thread.`
130
+ )
131
+ );
132
+ }
133
+ }
134
+
135
+ interface WebEventHandler {
136
+ onGestureHandlerEvent: (event: HandlerStateChangeEvent<unknown>) => void;
137
+ onGestureHandlerStateChange?: (
138
+ event: HandlerStateChangeEvent<unknown>
139
+ ) => void;
140
+ }
141
+
142
+ interface AttachHandlersConfig {
143
+ preparedGesture: GestureConfigReference;
144
+ gestureConfig: ComposedGesture | GestureType;
145
+ gesture: GestureType[];
146
+ viewTag: number;
147
+ webEventHandlersRef: React.RefObject<WebEventHandler>;
148
+ mountedRef: React.RefObject<boolean>;
149
+ }
150
+
151
+ function attachHandlers({
152
+ preparedGesture,
153
+ gestureConfig,
154
+ gesture,
155
+ viewTag,
156
+ webEventHandlersRef,
157
+ mountedRef,
158
+ }: AttachHandlersConfig) {
159
+ if (!preparedGesture.firstExecution) {
160
+ gestureConfig.initialize();
161
+ } else {
162
+ preparedGesture.firstExecution = false;
163
+ }
164
+
165
+ // use queueMicrotask to extract handlerTags, because all refs should be initialized
166
+ // when it's ran
167
+ queueMicrotask(() => {
168
+ if (!mountedRef.current) {
169
+ return;
170
+ }
171
+ gestureConfig.prepare();
172
+ });
173
+
174
+ for (const handler of gesture) {
175
+ checkGestureCallbacksForWorklets(handler);
176
+ RNGestureHandlerModule.createGestureHandler(
177
+ handler.handlerName,
178
+ handler.handlerTag,
179
+ filterConfig(handler.config, ALLOWED_PROPS)
180
+ );
181
+
182
+ registerHandler(handler.handlerTag, handler, handler.config.testId);
183
+ }
184
+
185
+ // use queueMicrotask to extract handlerTags, because all refs should be initialized
186
+ // when it's ran
187
+ queueMicrotask(() => {
188
+ if (!mountedRef.current) {
189
+ return;
190
+ }
191
+ for (const handler of gesture) {
192
+ let requireToFail: number[] = [];
193
+ if (handler.config.requireToFail) {
194
+ requireToFail = extractValidHandlerTags(handler.config.requireToFail);
195
+ }
196
+
197
+ let simultaneousWith: number[] = [];
198
+ if (handler.config.simultaneousWith) {
199
+ simultaneousWith = extractValidHandlerTags(
200
+ handler.config.simultaneousWith
201
+ );
202
+ }
203
+
204
+ RNGestureHandlerModule.updateGestureHandler(
205
+ handler.handlerTag,
206
+ filterConfig(handler.config, ALLOWED_PROPS, {
207
+ simultaneousHandlers: simultaneousWith,
208
+ waitFor: requireToFail,
209
+ })
210
+ );
211
+ }
212
+
213
+ scheduleFlushOperations();
214
+ });
215
+
216
+ preparedGesture.config = gesture;
217
+
218
+ for (const gesture of preparedGesture.config) {
219
+ const actionType = gesture.shouldUseReanimated
220
+ ? ActionType.REANIMATED_WORKLET
221
+ : ActionType.JS_FUNCTION_NEW_API;
222
+
223
+ if (Platform.OS === 'web') {
224
+ (
225
+ RNGestureHandlerModule.attachGestureHandler as typeof RNGestureHandlerModuleWeb.attachGestureHandler
226
+ )(
227
+ gesture.handlerTag,
228
+ viewTag,
229
+ ActionType.JS_FUNCTION_OLD_API, // ignored on web
230
+ webEventHandlersRef
231
+ );
232
+ } else {
233
+ RNGestureHandlerModule.attachGestureHandler(
234
+ gesture.handlerTag,
235
+ viewTag,
236
+ actionType
237
+ );
238
+ }
239
+ }
240
+
241
+ if (preparedGesture.animatedHandlers) {
242
+ const isAnimatedGesture = (g: GestureType) => g.shouldUseReanimated;
243
+
244
+ preparedGesture.animatedHandlers.value = gesture
245
+ .filter(isAnimatedGesture)
246
+ .map((g) => g.handlers) as unknown as HandlerCallbacks<
247
+ Record<string, unknown>
248
+ >[];
249
+ }
250
+ }
251
+
252
+ function updateHandlers(
253
+ preparedGesture: GestureConfigReference,
254
+ gestureConfig: ComposedGesture | GestureType,
255
+ gesture: GestureType[],
256
+ mountedRef: React.RefObject<boolean>
257
+ ) {
258
+ gestureConfig.prepare();
259
+
260
+ for (let i = 0; i < gesture.length; i++) {
261
+ const handler = preparedGesture.config[i];
262
+ checkGestureCallbacksForWorklets(handler);
263
+
264
+ // only update handlerTag when it's actually different, it may be the same
265
+ // if gesture config object is wrapped with useMemo
266
+ if (gesture[i].handlerTag !== handler.handlerTag) {
267
+ gesture[i].handlerTag = handler.handlerTag;
268
+ gesture[i].handlers.handlerTag = handler.handlerTag;
269
+ }
270
+ }
271
+
272
+ // use queueMicrotask to extract handlerTags, because when it's ran, all refs should be updated
273
+ // and handlerTags in BaseGesture references should be updated in the loop above (we need to wait
274
+ // in case of external relations)
275
+ queueMicrotask(() => {
276
+ if (!mountedRef.current) {
277
+ return;
278
+ }
279
+ for (let i = 0; i < gesture.length; i++) {
280
+ const handler = preparedGesture.config[i];
281
+
282
+ handler.config = gesture[i].config;
283
+ handler.handlers = gesture[i].handlers;
284
+
285
+ const requireToFail = extractValidHandlerTags(
286
+ handler.config.requireToFail
287
+ );
288
+
289
+ const simultaneousWith = extractValidHandlerTags(
290
+ handler.config.simultaneousWith
291
+ );
292
+
293
+ RNGestureHandlerModule.updateGestureHandler(
294
+ handler.handlerTag,
295
+ filterConfig(handler.config, ALLOWED_PROPS, {
296
+ simultaneousHandlers: simultaneousWith,
297
+ waitFor: requireToFail,
298
+ })
299
+ );
300
+
301
+ registerHandler(handler.handlerTag, handler, handler.config.testId);
302
+ }
303
+
304
+ if (preparedGesture.animatedHandlers) {
305
+ const previousHandlersValue =
306
+ preparedGesture.animatedHandlers.value ?? [];
307
+ const newHandlersValue = preparedGesture.config
308
+ .filter((g) => g.shouldUseReanimated) // ignore gestures that shouldn't run on UI
309
+ .map((g) => g.handlers) as unknown as HandlerCallbacks<
310
+ Record<string, unknown>
311
+ >[];
312
+
313
+ // if amount of gesture configs changes, we need to update the callbacks in shared value
314
+ let shouldUpdateSharedValue =
315
+ previousHandlersValue.length !== newHandlersValue.length;
316
+
317
+ if (!shouldUpdateSharedValue) {
318
+ // if the amount is the same, we need to check if any of the configs inside has changed
319
+ for (let i = 0; i < newHandlersValue.length; i++) {
320
+ if (
321
+ // we can use the `gestureId` prop as it's unique for every config instance
322
+ newHandlersValue[i].gestureId !== previousHandlersValue[i].gestureId
323
+ ) {
324
+ shouldUpdateSharedValue = true;
325
+ break;
326
+ }
327
+ }
328
+ }
329
+
330
+ if (shouldUpdateSharedValue) {
331
+ preparedGesture.animatedHandlers.value = newHandlersValue;
332
+ }
333
+ }
334
+
335
+ scheduleFlushOperations();
336
+ });
337
+ }
338
+
339
+ function needsToReattach(
340
+ preparedGesture: GestureConfigReference,
341
+ gesture: GestureType[]
342
+ ) {
343
+ if (gesture.length !== preparedGesture.config.length) {
344
+ return true;
345
+ }
346
+ for (let i = 0; i < gesture.length; i++) {
347
+ if (
348
+ gesture[i].handlerName !== preparedGesture.config[i].handlerName ||
349
+ gesture[i].shouldUseReanimated !==
350
+ preparedGesture.config[i].shouldUseReanimated
351
+ ) {
352
+ return true;
353
+ }
354
+ }
355
+
356
+ return false;
357
+ }
358
+
359
+ function isStateChangeEvent(
360
+ event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent
361
+ ): event is GestureStateChangeEvent {
362
+ 'worklet';
363
+ // @ts-ignore Yes, the oldState prop is missing on GestureTouchEvent, that's the point
364
+ return event.oldState != null;
365
+ }
366
+
367
+ function isTouchEvent(
368
+ event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent
369
+ ): event is GestureTouchEvent {
370
+ 'worklet';
371
+ return event.eventType != null;
372
+ }
373
+
374
+ function getHandler(
375
+ type: CALLBACK_TYPE,
376
+ gesture: HandlerCallbacks<Record<string, unknown>>
377
+ ) {
378
+ 'worklet';
379
+ switch (type) {
380
+ case CALLBACK_TYPE.BEGAN:
381
+ return gesture.onBegin;
382
+ case CALLBACK_TYPE.START:
383
+ return gesture.onStart;
384
+ case CALLBACK_TYPE.UPDATE:
385
+ return gesture.onUpdate;
386
+ case CALLBACK_TYPE.CHANGE:
387
+ return gesture.onChange;
388
+ case CALLBACK_TYPE.END:
389
+ return gesture.onEnd;
390
+ case CALLBACK_TYPE.FINALIZE:
391
+ return gesture.onFinalize;
392
+ case CALLBACK_TYPE.TOUCHES_DOWN:
393
+ return gesture.onTouchesDown;
394
+ case CALLBACK_TYPE.TOUCHES_MOVE:
395
+ return gesture.onTouchesMove;
396
+ case CALLBACK_TYPE.TOUCHES_UP:
397
+ return gesture.onTouchesUp;
398
+ case CALLBACK_TYPE.TOUCHES_CANCELLED:
399
+ return gesture.onTouchesCancelled;
400
+ }
401
+ }
402
+
403
+ function touchEventTypeToCallbackType(
404
+ eventType: TouchEventType
405
+ ): CALLBACK_TYPE {
406
+ 'worklet';
407
+ switch (eventType) {
408
+ case TouchEventType.TOUCHES_DOWN:
409
+ return CALLBACK_TYPE.TOUCHES_DOWN;
410
+ case TouchEventType.TOUCHES_MOVE:
411
+ return CALLBACK_TYPE.TOUCHES_MOVE;
412
+ case TouchEventType.TOUCHES_UP:
413
+ return CALLBACK_TYPE.TOUCHES_UP;
414
+ case TouchEventType.TOUCHES_CANCELLED:
415
+ return CALLBACK_TYPE.TOUCHES_CANCELLED;
416
+ }
417
+ return CALLBACK_TYPE.UNDEFINED;
418
+ }
419
+
420
+ function runWorklet(
421
+ type: CALLBACK_TYPE,
422
+ gesture: HandlerCallbacks<Record<string, unknown>>,
423
+ event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent,
424
+ ...args: any[]
425
+ ) {
426
+ 'worklet';
427
+ const handler = getHandler(type, gesture);
428
+ if (gesture.isWorklet[type]) {
429
+ // @ts-ignore Logic below makes sure the correct event is send to the
430
+ // correct handler.
431
+ handler?.(event, ...args);
432
+ } else if (handler) {
433
+ console.warn(tagMessage('Animated gesture callback must be a worklet'));
434
+ }
435
+ }
436
+
437
+ function useAnimatedGesture(
438
+ preparedGesture: GestureConfigReference,
439
+ needsRebuild: boolean
440
+ ) {
441
+ if (!Reanimated) {
442
+ return;
443
+ }
444
+
445
+ // Hooks are called conditionally, but the condition is whether the
446
+ // react-native-reanimated is installed, which shouldn't change while running
447
+ // eslint-disable-next-line react-hooks/rules-of-hooks
448
+ const sharedHandlersCallbacks = Reanimated.useSharedValue<
449
+ HandlerCallbacks<Record<string, unknown>>[] | null
450
+ >(null);
451
+
452
+ // eslint-disable-next-line react-hooks/rules-of-hooks
453
+ const lastUpdateEvent = Reanimated.useSharedValue<
454
+ (GestureUpdateEvent | undefined)[]
455
+ >([]);
456
+
457
+ // not every gesture needs a state controller, init them lazily
458
+ const stateControllers: GestureStateManagerType[] = [];
459
+
460
+ const callback = (
461
+ event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent
462
+ ) => {
463
+ 'worklet';
464
+
465
+ const currentCallback = sharedHandlersCallbacks.value;
466
+ if (!currentCallback) {
467
+ return;
468
+ }
469
+
470
+ for (let i = 0; i < currentCallback.length; i++) {
471
+ const gesture = currentCallback[i];
472
+
473
+ if (event.handlerTag === gesture.handlerTag) {
474
+ if (isStateChangeEvent(event)) {
475
+ if (
476
+ event.oldState === State.UNDETERMINED &&
477
+ event.state === State.BEGAN
478
+ ) {
479
+ runWorklet(CALLBACK_TYPE.BEGAN, gesture, event);
480
+ } else if (
481
+ (event.oldState === State.BEGAN ||
482
+ event.oldState === State.UNDETERMINED) &&
483
+ event.state === State.ACTIVE
484
+ ) {
485
+ runWorklet(CALLBACK_TYPE.START, gesture, event);
486
+ lastUpdateEvent.value[gesture.handlerTag] = undefined;
487
+ } else if (
488
+ event.oldState !== event.state &&
489
+ event.state === State.END
490
+ ) {
491
+ if (event.oldState === State.ACTIVE) {
492
+ runWorklet(CALLBACK_TYPE.END, gesture, event, true);
493
+ }
494
+ runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, true);
495
+ } else if (
496
+ (event.state === State.FAILED || event.state === State.CANCELLED) &&
497
+ event.state !== event.oldState
498
+ ) {
499
+ if (event.oldState === State.ACTIVE) {
500
+ runWorklet(CALLBACK_TYPE.END, gesture, event, false);
501
+ }
502
+ runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, false);
503
+ }
504
+ } else if (isTouchEvent(event)) {
505
+ if (!stateControllers[i]) {
506
+ stateControllers[i] = GestureStateManager.create(event.handlerTag);
507
+ }
508
+
509
+ if (event.eventType !== TouchEventType.UNDETERMINED) {
510
+ runWorklet(
511
+ touchEventTypeToCallbackType(event.eventType),
512
+ gesture,
513
+ event,
514
+ stateControllers[i]
515
+ );
516
+ }
517
+ } else {
518
+ runWorklet(CALLBACK_TYPE.UPDATE, gesture, event);
519
+
520
+ if (gesture.onChange && gesture.changeEventCalculator) {
521
+ runWorklet(
522
+ CALLBACK_TYPE.CHANGE,
523
+ gesture,
524
+ gesture.changeEventCalculator?.(
525
+ event,
526
+ lastUpdateEvent.value[gesture.handlerTag]
527
+ )
528
+ );
529
+
530
+ lastUpdateEvent.value[gesture.handlerTag] = event;
531
+ }
532
+ }
533
+ }
534
+ }
535
+ };
536
+
537
+ // eslint-disable-next-line react-hooks/rules-of-hooks
538
+ const event = Reanimated.useEvent(
539
+ callback,
540
+ ['onGestureHandlerStateChange', 'onGestureHandlerEvent'],
541
+ needsRebuild
542
+ );
543
+
544
+ preparedGesture.animatedEventHandler = event;
545
+ preparedGesture.animatedHandlers = sharedHandlersCallbacks;
546
+ }
547
+
548
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
549
+ function validateDetectorChildren(ref: any) {
550
+ // finds the first native view under the Wrap component and traverses the fiber tree upwards
551
+ // to check whether there is more than one native view as a pseudo-direct child of GestureDetector
552
+ // i.e. this is not ok:
553
+ // Wrap
554
+ // |
555
+ // / \
556
+ // / \
557
+ // / \
558
+ // / \
559
+ // NativeView NativeView
560
+ //
561
+ // but this is fine:
562
+ // Wrap
563
+ // |
564
+ // NativeView
565
+ // |
566
+ // / \
567
+ // / \
568
+ // / \
569
+ // / \
570
+ // NativeView NativeView
571
+ if (__DEV__ && Platform.OS !== 'web') {
572
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
573
+ const wrapType =
574
+ REACT_NATIVE_VERSION.minor > 63 || REACT_NATIVE_VERSION.major > 0
575
+ ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
576
+ ref._reactInternals.elementType
577
+ : // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
578
+ ref._reactInternalFiber.elementType;
579
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
580
+ let instance =
581
+ RNRenderer.findHostInstance_DEPRECATED(
582
+ ref
583
+ )._internalFiberInstanceHandleDEV;
584
+
585
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
586
+ while (instance && instance.elementType !== wrapType) {
587
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
588
+ if (instance.sibling) {
589
+ throw new Error(
590
+ 'GestureDetector has more than one native view as its children. This can happen if you are using a custom component that renders multiple views, like React.Fragment. You should wrap content of GestureDetector with a <View> or <Animated.View>.'
591
+ );
592
+ }
593
+
594
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
595
+ instance = instance.return;
596
+ }
597
+ }
598
+ }
599
+
600
+ const applyUserSelectProp = (
601
+ userSelect: UserSelect,
602
+ gesture: ComposedGesture | GestureType
603
+ ): void => {
604
+ for (const g of gesture.toGestureArray()) {
605
+ g.config.userSelect = userSelect;
606
+ }
607
+ };
608
+
609
+ interface GestureDetectorProps {
610
+ gesture: ComposedGesture | GestureType;
611
+ userSelect?: UserSelect;
612
+ children?: React.ReactNode;
613
+ }
614
+ interface GestureDetectorState {
615
+ firstRender: boolean;
616
+ viewRef: React.Component | null;
617
+ previousViewTag: number;
618
+ forceReattach: boolean;
619
+ }
620
+ export const GestureDetector = (props: GestureDetectorProps) => {
621
+ const rootViewContext = useContext(GestureHandlerRootViewContext);
622
+ if (__DEV__ && !rootViewContext && !isJestEnv() && Platform.OS !== 'web' && (Platform.OS as any) !== "harmony") { // RNOH: patch
623
+ throw new Error(
624
+ 'GestureDetector must be used as a descendant of GestureHandlerRootView. Otherwise the gestures will not be recognized. See https://docs.swmansion.com/react-native-gesture-handler/docs/installation for more details.'
625
+ );
626
+ }
627
+
628
+ const gestureConfig = props.gesture;
629
+
630
+ if (props.userSelect) {
631
+ applyUserSelectProp(props.userSelect, gestureConfig);
632
+ }
633
+
634
+ const gesture = gestureConfig.toGestureArray();
635
+ const useReanimatedHook = gesture.some((g) => g.shouldUseReanimated);
636
+
637
+ // store state in ref to prevent unnecessary renders
638
+ const state = useRef<GestureDetectorState>({
639
+ firstRender: true,
640
+ viewRef: null,
641
+ previousViewTag: -1,
642
+ forceReattach: false,
643
+ }).current;
644
+ const mountedRef = useRef(false);
645
+ const webEventHandlersRef = useRef<WebEventHandler>({
646
+ onGestureHandlerEvent: (e: HandlerStateChangeEvent<unknown>) => {
647
+ onGestureHandlerEvent(e.nativeEvent);
648
+ },
649
+ onGestureHandlerStateChange: isNewWebImplementationEnabled()
650
+ ? (e: HandlerStateChangeEvent<unknown>) => {
651
+ onGestureHandlerEvent(e.nativeEvent);
652
+ }
653
+ : undefined,
654
+ });
655
+
656
+ const [renderState, setRenderState] = useState(false);
657
+ function forceRender() {
658
+ setRenderState(!renderState);
659
+ }
660
+
661
+ const preparedGesture = React.useRef<GestureConfigReference>({
662
+ config: gesture,
663
+ animatedEventHandler: null,
664
+ animatedHandlers: null,
665
+ firstExecution: true,
666
+ useReanimatedHook: useReanimatedHook,
667
+ }).current;
668
+
669
+ if (useReanimatedHook !== preparedGesture.useReanimatedHook) {
670
+ throw new Error(
671
+ tagMessage(
672
+ 'You cannot change the thread the callbacks are ran on while the app is running'
673
+ )
674
+ );
675
+ }
676
+
677
+ function onHandlersUpdate(skipConfigUpdate?: boolean) {
678
+ // if the underlying view has changed we need to reattach handlers to the new view
679
+ const viewTag = findNodeHandle(state.viewRef) as number;
680
+ const forceReattach = viewTag !== state.previousViewTag;
681
+
682
+ if (forceReattach || needsToReattach(preparedGesture, gesture)) {
683
+ validateDetectorChildren(state.viewRef);
684
+ dropHandlers(preparedGesture);
685
+ attachHandlers({
686
+ preparedGesture,
687
+ gestureConfig,
688
+ gesture,
689
+ webEventHandlersRef,
690
+ viewTag,
691
+ mountedRef,
692
+ });
693
+
694
+ state.previousViewTag = viewTag;
695
+ state.forceReattach = forceReattach;
696
+ if (forceReattach) {
697
+ forceRender();
698
+ }
699
+ } else if (!skipConfigUpdate) {
700
+ updateHandlers(preparedGesture, gestureConfig, gesture, mountedRef);
701
+ }
702
+ }
703
+
704
+ // Reanimated event should be rebuilt only when gestures are reattached, otherwise
705
+ // config update will be enough as all necessary items are stored in shared values anyway
706
+ const needsToRebuildReanimatedEvent =
707
+ preparedGesture.firstExecution ||
708
+ needsToReattach(preparedGesture, gesture) ||
709
+ state.forceReattach;
710
+
711
+ state.forceReattach = false;
712
+
713
+ if (preparedGesture.firstExecution) {
714
+ gestureConfig.initialize();
715
+ }
716
+
717
+ if (useReanimatedHook) {
718
+ // Whether animatedGesture or gesture is used shouldn't change while the app is running
719
+ // eslint-disable-next-line react-hooks/rules-of-hooks
720
+ useAnimatedGesture(preparedGesture, needsToRebuildReanimatedEvent);
721
+ }
722
+
723
+ useEffect(() => {
724
+ const viewTag = findNodeHandle(state.viewRef) as number;
725
+ state.firstRender = true;
726
+ mountedRef.current = true;
727
+
728
+ validateDetectorChildren(state.viewRef);
729
+
730
+ attachHandlers({
731
+ preparedGesture,
732
+ gestureConfig,
733
+ gesture,
734
+ webEventHandlersRef,
735
+ viewTag,
736
+ mountedRef,
737
+ });
738
+
739
+ return () => {
740
+ mountedRef.current = false;
741
+ dropHandlers(preparedGesture);
742
+ };
743
+ }, []);
744
+
745
+ useEffect(() => {
746
+ if (!state.firstRender) {
747
+ onHandlersUpdate();
748
+ } else {
749
+ state.firstRender = false;
750
+ }
751
+ }, [props]);
752
+
753
+ const refFunction = (ref: unknown) => {
754
+ if (ref !== null) {
755
+ // @ts-ignore Just setting the view ref
756
+ state.viewRef = ref;
757
+
758
+ // if it's the first render, also set the previousViewTag to prevent reattaching gestures when not needed
759
+ if (state.previousViewTag === -1) {
760
+ state.previousViewTag = findNodeHandle(state.viewRef) as number;
761
+ }
762
+
763
+ // pass true as `skipConfigUpdate`, here we only want to trigger the eventual reattaching of handlers
764
+ // in case the view has changed, while config update would be handled be the `useEffect` above
765
+ onHandlersUpdate(true);
766
+
767
+ if (isFabric()) {
768
+ const node = getShadowNodeFromRef(ref);
769
+ if (global.isFormsStackingContext(node) === false) {
770
+ console.error(
771
+ tagMessage(
772
+ 'GestureDetector has received a child that may get view-flattened. ' +
773
+ '\nTo prevent it from misbehaving you need to wrap the child with a `<View collapsable={false}>`.'
774
+ )
775
+ );
776
+ }
777
+ }
778
+ }
779
+ };
780
+
781
+ if (useReanimatedHook) {
782
+ return (
783
+ <AnimatedWrap
784
+ ref={refFunction}
785
+ onGestureHandlerEvent={preparedGesture.animatedEventHandler}>
786
+ {props.children}
787
+ </AnimatedWrap>
788
+ );
789
+ } else {
790
+ return <Wrap ref={refFunction}>{props.children}</Wrap>;
791
+ }
792
+ };
793
+
794
+ class Wrap extends React.Component<{
795
+ onGestureHandlerEvent?: unknown;
796
+ // implicit `children` prop has been removed in @types/react^18.0.0
797
+ children?: React.ReactNode;
798
+ }> {
799
+ render() {
800
+ try {
801
+ // I don't think that fighting with types over such a simple function is worth it
802
+ // The only thing it does is add 'collapsable: false' to the child component
803
+ // to make sure it is in the native view hierarchy so the detector can find
804
+ // correct viewTag to attach to.
805
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
806
+ const child: any = React.Children.only(this.props.children);
807
+ return React.cloneElement(
808
+ child,
809
+ { collapsable: false },
810
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
811
+ child.props.children
812
+ );
813
+ } catch (e) {
814
+ throw new Error(
815
+ tagMessage(
816
+ `GestureDetector got more than one view as a child. If you want the gesture to work on multiple views, wrap them with a common parent and attach the gesture to that view.`
817
+ )
818
+ );
819
+ }
820
+ }
821
+ }
822
+
823
+ const AnimatedWrap = Reanimated?.default?.createAnimatedComponent(Wrap) ?? Wrap;