react-native-gesture-handler 3.0.0-beta.4 → 3.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +12 -4
- package/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt +6 -2
- package/android/src/main/java/com/swmansion/gesturehandler/react/Extensions.kt +21 -0
- package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +113 -49
- package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +75 -98
- package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +7 -10
- package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRegistry.kt +64 -2
- package/apple/RNGestureHandler.mm +50 -27
- package/apple/RNGestureHandlerButton.h +4 -2
- package/apple/RNGestureHandlerButton.mm +106 -27
- package/apple/RNGestureHandlerButtonComponentView.mm +17 -2
- package/apple/RNGestureHandlerDetector.mm +99 -75
- package/apple/RNGestureHandlerModule.mm +11 -14
- package/apple/RNGestureHandlerRegistry.h +14 -0
- package/apple/RNGestureHandlerRegistry.m +56 -0
- package/lib/module/RNGestureHandlerModule.web.js +5 -1
- package/lib/module/RNGestureHandlerModule.web.js.map +1 -1
- package/lib/module/components/GestureButtons.js +16 -5
- package/lib/module/components/GestureButtons.js.map +1 -1
- package/lib/module/components/GestureHandlerButton.js.map +1 -1
- package/lib/module/components/GestureHandlerButton.web.js +63 -23
- package/lib/module/components/GestureHandlerButton.web.js.map +1 -1
- package/lib/module/components/Pressable/Pressable.js +1 -0
- package/lib/module/components/Pressable/Pressable.js.map +1 -1
- package/lib/module/components/ReanimatedDrawerLayout.js.map +1 -1
- package/lib/module/components/ReanimatedSwipeable/ReanimatedSwipeable.js +38 -5
- package/lib/module/components/ReanimatedSwipeable/ReanimatedSwipeable.js.map +1 -1
- package/lib/module/handlers/gestures/GestureDetector/useDetectorUpdater.js +1 -2
- package/lib/module/handlers/gestures/GestureDetector/useDetectorUpdater.js.map +1 -1
- package/lib/module/handlers/gestures/GestureDetector/utils.js +0 -47
- package/lib/module/handlers/gestures/GestureDetector/utils.js.map +1 -1
- package/lib/module/handlers/gestures/reanimatedWrapper.js +14 -2
- package/lib/module/handlers/gestures/reanimatedWrapper.js.map +1 -1
- package/lib/module/mocks/module.js +3 -2
- package/lib/module/mocks/module.js.map +1 -1
- package/lib/module/specs/NativeRNGestureHandlerModule.js.map +1 -1
- package/lib/module/specs/RNGestureHandlerButtonNativeComponent.ts +28 -13
- package/lib/module/v3/NativeProxy.js +5 -3
- package/lib/module/v3/NativeProxy.js.map +1 -1
- package/lib/module/v3/NativeProxy.web.js +2 -2
- package/lib/module/v3/NativeProxy.web.js.map +1 -1
- package/lib/module/v3/components/GestureButtons.js +8 -3
- package/lib/module/v3/components/GestureButtons.js.map +1 -1
- package/lib/module/v3/components/Touchable/Touchable.js +53 -4
- package/lib/module/v3/components/Touchable/Touchable.js.map +1 -1
- package/lib/module/v3/detectors/HostGestureDetector.web.js +178 -59
- package/lib/module/v3/detectors/HostGestureDetector.web.js.map +1 -1
- package/lib/module/v3/detectors/NativeDetector.js +3 -2
- package/lib/module/v3/detectors/NativeDetector.js.map +1 -1
- package/lib/module/v3/detectors/VirtualDetector/InterceptingGestureDetector.js +3 -4
- package/lib/module/v3/detectors/VirtualDetector/InterceptingGestureDetector.js.map +1 -1
- package/lib/module/v3/detectors/VirtualDetector/VirtualDetector.js +2 -2
- package/lib/module/v3/detectors/VirtualDetector/VirtualDetector.js.map +1 -1
- package/lib/module/v3/detectors/useGestureRelationsUpdater.js +23 -0
- package/lib/module/v3/detectors/useGestureRelationsUpdater.js.map +1 -0
- package/lib/module/v3/detectors/utils.js +10 -8
- package/lib/module/v3/detectors/utils.js.map +1 -1
- package/lib/module/v3/hooks/useGesture.js +3 -18
- package/lib/module/v3/hooks/useGesture.js.map +1 -1
- package/lib/module/v3/hooks/utils/configUtils.js +1 -3
- package/lib/module/v3/hooks/utils/configUtils.js.map +1 -1
- package/lib/module/v3/hooks/utils/eventHandlersUtils.js +31 -29
- package/lib/module/v3/hooks/utils/eventHandlersUtils.js.map +1 -1
- package/lib/module/v3/hooks/utils/reanimatedUtils.js +8 -2
- package/lib/module/v3/hooks/utils/reanimatedUtils.js.map +1 -1
- package/lib/module/web/tools/NodeManager.js +44 -0
- package/lib/module/web/tools/NodeManager.js.map +1 -1
- package/lib/typescript/RNGestureHandlerModule.web.d.ts +1 -1
- package/lib/typescript/RNGestureHandlerModule.web.d.ts.map +1 -1
- package/lib/typescript/components/GestureButtons.d.ts +14 -6
- package/lib/typescript/components/GestureButtons.d.ts.map +1 -1
- package/lib/typescript/components/GestureHandlerButton.d.ts +62 -8
- package/lib/typescript/components/GestureHandlerButton.d.ts.map +1 -1
- package/lib/typescript/components/GestureHandlerButton.web.d.ts +10 -3
- package/lib/typescript/components/GestureHandlerButton.web.d.ts.map +1 -1
- package/lib/typescript/components/Pressable/Pressable.d.ts.map +1 -1
- package/lib/typescript/components/Pressable/PressableProps.d.ts +1 -1
- package/lib/typescript/components/Pressable/PressableProps.d.ts.map +1 -1
- package/lib/typescript/components/ReanimatedDrawerLayout.d.ts +16 -14
- package/lib/typescript/components/ReanimatedDrawerLayout.d.ts.map +1 -1
- package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeable.d.ts +2 -1
- package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeable.d.ts.map +1 -1
- package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeableProps.d.ts +30 -34
- package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeableProps.d.ts.map +1 -1
- package/lib/typescript/handlers/gestures/GestureDetector/useDetectorUpdater.d.ts.map +1 -1
- package/lib/typescript/handlers/gestures/GestureDetector/utils.d.ts +0 -1
- package/lib/typescript/handlers/gestures/GestureDetector/utils.d.ts.map +1 -1
- package/lib/typescript/handlers/gestures/reanimatedWrapper.d.ts.map +1 -1
- package/lib/typescript/mocks/module.d.ts +1 -1
- package/lib/typescript/mocks/module.d.ts.map +1 -1
- package/lib/typescript/specs/NativeRNGestureHandlerModule.d.ts +2 -2
- package/lib/typescript/specs/NativeRNGestureHandlerModule.d.ts.map +1 -1
- package/lib/typescript/specs/RNGestureHandlerButtonNativeComponent.d.ts +19 -11
- package/lib/typescript/specs/RNGestureHandlerButtonNativeComponent.d.ts.map +1 -1
- package/lib/typescript/v3/NativeProxy.d.ts +1 -1
- package/lib/typescript/v3/NativeProxy.d.ts.map +1 -1
- package/lib/typescript/v3/NativeProxy.web.d.ts +1 -1
- package/lib/typescript/v3/NativeProxy.web.d.ts.map +1 -1
- package/lib/typescript/v3/components/GestureButtons.d.ts +1 -38
- package/lib/typescript/v3/components/GestureButtons.d.ts.map +1 -1
- package/lib/typescript/v3/components/GestureButtonsProps.d.ts +1 -1
- package/lib/typescript/v3/components/GestureButtonsProps.d.ts.map +1 -1
- package/lib/typescript/v3/components/Touchable/Touchable.d.ts.map +1 -1
- package/lib/typescript/v3/components/Touchable/TouchableProps.d.ts +39 -1
- package/lib/typescript/v3/components/Touchable/TouchableProps.d.ts.map +1 -1
- package/lib/typescript/v3/detectors/HostGestureDetector.web.d.ts.map +1 -1
- package/lib/typescript/v3/detectors/NativeDetector.d.ts.map +1 -1
- package/lib/typescript/v3/detectors/VirtualDetector/InterceptingGestureDetector.d.ts.map +1 -1
- package/lib/typescript/v3/detectors/useGestureRelationsUpdater.d.ts +3 -0
- package/lib/typescript/v3/detectors/useGestureRelationsUpdater.d.ts.map +1 -0
- package/lib/typescript/v3/detectors/utils.d.ts +3 -3
- package/lib/typescript/v3/detectors/utils.d.ts.map +1 -1
- package/lib/typescript/v3/hooks/useGesture.d.ts.map +1 -1
- package/lib/typescript/v3/hooks/utils/configUtils.d.ts.map +1 -1
- package/lib/typescript/v3/hooks/utils/eventHandlersUtils.d.ts.map +1 -1
- package/lib/typescript/v3/hooks/utils/reanimatedUtils.d.ts +1 -0
- package/lib/typescript/v3/hooks/utils/reanimatedUtils.d.ts.map +1 -1
- package/lib/typescript/web/tools/NodeManager.d.ts +7 -0
- package/lib/typescript/web/tools/NodeManager.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/RNGestureHandlerModule.web.ts +5 -1
- package/src/components/GestureButtons.tsx +23 -7
- package/src/components/GestureHandlerButton.tsx +70 -8
- package/src/components/GestureHandlerButton.web.tsx +97 -29
- package/src/components/Pressable/Pressable.tsx +1 -0
- package/src/components/Pressable/PressableProps.tsx +2 -1
- package/src/components/ReanimatedDrawerLayout.tsx +27 -23
- package/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +51 -5
- package/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts +31 -39
- package/src/handlers/gestures/GestureDetector/useDetectorUpdater.ts +1 -2
- package/src/handlers/gestures/GestureDetector/utils.ts +0 -52
- package/src/handlers/gestures/reanimatedWrapper.ts +20 -2
- package/src/mocks/module.tsx +4 -2
- package/src/specs/NativeRNGestureHandlerModule.ts +2 -4
- package/src/specs/RNGestureHandlerButtonNativeComponent.ts +28 -13
- package/src/v3/NativeProxy.ts +9 -7
- package/src/v3/NativeProxy.web.ts +2 -2
- package/src/v3/components/GestureButtons.tsx +13 -5
- package/src/v3/components/GestureButtonsProps.ts +1 -0
- package/src/v3/components/Touchable/Touchable.tsx +65 -4
- package/src/v3/components/Touchable/TouchableProps.ts +49 -1
- package/src/v3/detectors/HostGestureDetector.web.tsx +265 -108
- package/src/v3/detectors/NativeDetector.tsx +3 -2
- package/src/v3/detectors/VirtualDetector/InterceptingGestureDetector.tsx +3 -4
- package/src/v3/detectors/VirtualDetector/VirtualDetector.tsx +2 -2
- package/src/v3/detectors/useGestureRelationsUpdater.ts +30 -0
- package/src/v3/detectors/utils.ts +28 -12
- package/src/v3/hooks/useGesture.ts +4 -14
- package/src/v3/hooks/utils/configUtils.ts +2 -3
- package/src/v3/hooks/utils/eventHandlersUtils.ts +43 -32
- package/src/v3/hooks/utils/reanimatedUtils.ts +10 -10
- package/src/web/tools/NodeManager.ts +57 -0
- package/lib/module/RNRenderer.js +0 -6
- package/lib/module/RNRenderer.js.map +0 -1
- package/lib/module/RNRenderer.web.js +0 -6
- package/lib/module/RNRenderer.web.js.map +0 -1
- package/lib/typescript/RNRenderer.d.ts +0 -2
- package/lib/typescript/RNRenderer.d.ts.map +0 -1
- package/lib/typescript/RNRenderer.web.d.ts +0 -4
- package/lib/typescript/RNRenderer.web.d.ts.map +0 -1
- package/src/RNRenderer.ts +0 -3
- package/src/RNRenderer.web.ts +0 -3
|
@@ -28,7 +28,6 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
|
|
|
28
28
|
@DoNotStrip
|
|
29
29
|
@Suppress("unused")
|
|
30
30
|
private var mHybridData: HybridData = initHybrid()
|
|
31
|
-
private var isReanimatedAvailable = false
|
|
32
31
|
private var uiRuntimeDecorated = false
|
|
33
32
|
private val registry: RNGestureHandlerRegistry
|
|
34
33
|
get() = registries[moduleId]!!
|
|
@@ -61,16 +60,10 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
@ReactMethod
|
|
64
|
-
override fun createGestureHandler(handlerName: String, handlerTagDouble: Double, config: ReadableMap)
|
|
65
|
-
if (isReanimatedAvailable && !uiRuntimeDecorated) {
|
|
66
|
-
uiRuntimeDecorated = decorateUIRuntime()
|
|
67
|
-
}
|
|
68
|
-
|
|
63
|
+
override fun createGestureHandler(handlerName: String, handlerTagDouble: Double, config: ReadableMap) {
|
|
69
64
|
val handlerTag = handlerTagDouble.toInt()
|
|
70
65
|
|
|
71
66
|
createGestureHandlerHelper<GestureHandler>(handlerName, handlerTag, config)
|
|
72
|
-
|
|
73
|
-
return true
|
|
74
67
|
}
|
|
75
68
|
|
|
76
69
|
@ReactMethod
|
|
@@ -125,8 +118,12 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
|
|
|
125
118
|
override fun flushOperations() = Unit
|
|
126
119
|
|
|
127
120
|
@ReactMethod
|
|
128
|
-
override fun
|
|
129
|
-
|
|
121
|
+
override fun installUIRuntimeBindings(): Boolean {
|
|
122
|
+
if (!uiRuntimeDecorated) {
|
|
123
|
+
uiRuntimeDecorated = decorateUIRuntime()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return uiRuntimeDecorated
|
|
130
127
|
}
|
|
131
128
|
|
|
132
129
|
@DoNotStrip
|
package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRegistry.kt
CHANGED
|
@@ -11,10 +11,72 @@ class RNGestureHandlerRegistry : GestureHandlerRegistry {
|
|
|
11
11
|
private val handlers = SparseArray<GestureHandler>()
|
|
12
12
|
private val attachedTo = SparseArray<Int?>()
|
|
13
13
|
private val handlersForView = SparseArray<ArrayList<GestureHandler>>()
|
|
14
|
+
private val observers = mutableMapOf<Int, MutableMap<Any, (GestureHandler) -> Unit>>()
|
|
14
15
|
|
|
15
|
-
@Synchronized
|
|
16
16
|
fun registerHandler(handler: GestureHandler) {
|
|
17
|
-
|
|
17
|
+
val hasObservers = synchronized(this) {
|
|
18
|
+
handlers.put(handler.tag, handler)
|
|
19
|
+
observers[handler.tag]?.isNotEmpty() == true
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!hasObservers) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// `createGestureHandler` runs on the JS thread, but observer callbacks read detector
|
|
27
|
+
// view state (childCount, getChildAt) and may attach native handlers, so they must run
|
|
28
|
+
// on the UI thread. Re-resolve the observer list on the UI thread so a cancellation that
|
|
29
|
+
// happens between this post and `notify` running (e.g. detector detach) actually prevents
|
|
30
|
+
// the callback.
|
|
31
|
+
val notify = {
|
|
32
|
+
val callbacks = synchronized(this) {
|
|
33
|
+
observers[handler.tag]?.values?.toList().orEmpty()
|
|
34
|
+
}
|
|
35
|
+
for (callback in callbacks) {
|
|
36
|
+
callback(handler)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (UiThreadUtil.isOnUiThread()) {
|
|
41
|
+
notify()
|
|
42
|
+
} else {
|
|
43
|
+
UiThreadUtil.runOnUiThread(notify)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Invokes `block` every time a handler with `tag` is registered, and synchronously once now if
|
|
48
|
+
// the handler already exists. The observation persists until explicitly cancelled: the registry
|
|
49
|
+
// holds both `owner` and `block` strongly, so callers MUST call `cancelObservation` or
|
|
50
|
+
// `cancelAllObservationsForOwner` when the owner is going away (typically in detach / dispose
|
|
51
|
+
// paths) to avoid leaking the owner. Observing the same tag twice with the same `owner` replaces
|
|
52
|
+
// the previous block.
|
|
53
|
+
fun observeHandler(tag: Int, owner: Any, block: (GestureHandler) -> Unit) {
|
|
54
|
+
val existing = synchronized(this) {
|
|
55
|
+
observers.getOrPut(tag) { mutableMapOf() }[owner] = block
|
|
56
|
+
handlers[tag]
|
|
57
|
+
}
|
|
58
|
+
existing?.let { block(it) }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Synchronized
|
|
62
|
+
fun cancelObservation(tag: Int, owner: Any) {
|
|
63
|
+
val observersForTag = observers[tag] ?: return
|
|
64
|
+
observersForTag.remove(owner)
|
|
65
|
+
if (observersForTag.isEmpty()) {
|
|
66
|
+
observers.remove(tag)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Synchronized
|
|
71
|
+
fun cancelAllObservationsForOwner(owner: Any) {
|
|
72
|
+
val iterator = observers.entries.iterator()
|
|
73
|
+
while (iterator.hasNext()) {
|
|
74
|
+
val entry = iterator.next()
|
|
75
|
+
entry.value.remove(owner)
|
|
76
|
+
if (entry.value.isEmpty()) {
|
|
77
|
+
iterator.remove()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
18
80
|
}
|
|
19
81
|
|
|
20
82
|
@Synchronized
|
|
@@ -254,6 +254,43 @@ static NSHashTable<RNGestureHandler *> *allGestureHandlers;
|
|
|
254
254
|
return [view isKindOfClass:[RCTParagraphComponentView class]];
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Recursively searches the view subtree rooted at `view` for any descendant whose
|
|
259
|
+
* `touchEventEmitterAtPoint:` returns an emitter tag matching `virtualViewTag`.
|
|
260
|
+
* `point` must be in `view`'s coordinate space.
|
|
261
|
+
*
|
|
262
|
+
* Most Fabric views inherit a base `touchEventEmitterAtPoint:` that returns their own emitter
|
|
263
|
+
* (tag == their own reactTag). Views that render multiple logical children — like
|
|
264
|
+
* `RCTParagraphComponentView` for inline text spans — override the method to return
|
|
265
|
+
* per-child emitters, making them distinguishable by tag. This helper exploits that
|
|
266
|
+
* property without hardcoding any specific view class.
|
|
267
|
+
*/
|
|
268
|
+
- (BOOL)isVirtualViewTag:(NSNumber *)virtualViewTag touchedAtPoint:(CGPoint)point inView:(RNGHUIView *)view
|
|
269
|
+
{
|
|
270
|
+
if (!CGRectContainsPoint(view.bounds, point)) {
|
|
271
|
+
return NO;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if ([view respondsToSelector:@selector(touchEventEmitterAtPoint:)]) {
|
|
275
|
+
auto emitter = [(id<RCTTouchableComponentViewProtocol>)view touchEventEmitterAtPoint:point];
|
|
276
|
+
if (emitter) {
|
|
277
|
+
auto eventTarget = emitter->getEventTarget();
|
|
278
|
+
if (eventTarget != nullptr && eventTarget->getTag() == [virtualViewTag intValue]) {
|
|
279
|
+
return YES;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (RNGHUIView *subview in view.subviews) {
|
|
285
|
+
CGPoint pointInSubview = [view convertPoint:point toView:subview];
|
|
286
|
+
if ([self isVirtualViewTag:virtualViewTag touchedAtPoint:pointInSubview inView:subview]) {
|
|
287
|
+
return YES;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return NO;
|
|
292
|
+
}
|
|
293
|
+
|
|
257
294
|
- (void)bindToView:(RNGHUIView *)view
|
|
258
295
|
{
|
|
259
296
|
self.recognizer.delegate = self;
|
|
@@ -753,6 +790,11 @@ static NSHashTable<RNGestureHandler *> *allGestureHandlers;
|
|
|
753
790
|
|
|
754
791
|
- (BOOL)containsPointInView
|
|
755
792
|
{
|
|
793
|
+
if (_actionType == RNGestureHandlerActionTypeVirtualDetector && _virtualViewTag != nil) {
|
|
794
|
+
CGPoint point = [_recognizer locationInView:_recognizer.view];
|
|
795
|
+
return [self isVirtualViewTag:_virtualViewTag touchedAtPoint:point inView:_recognizer.view];
|
|
796
|
+
}
|
|
797
|
+
|
|
756
798
|
RNGHUIView *viewToHitTest = _recognizer.view;
|
|
757
799
|
|
|
758
800
|
if (_shouldCancelWhenOutside && [self usesNativeOrVirtualDetector] && [_recognizer.view.subviews count] > 0) {
|
|
@@ -767,6 +809,11 @@ static NSHashTable<RNGestureHandler *> *allGestureHandlers;
|
|
|
767
809
|
|
|
768
810
|
- (BOOL)wantsToHandleEventsAtPoint:(CGPoint)point
|
|
769
811
|
{
|
|
812
|
+
if (_actionType == RNGestureHandlerActionTypeVirtualDetector && _virtualViewTag != nil) {
|
|
813
|
+
// point is in _recognizer.view (detector) coordinate space; search the whole subtree
|
|
814
|
+
return [self isVirtualViewTag:_virtualViewTag touchedAtPoint:point inView:_recognizer.view];
|
|
815
|
+
}
|
|
816
|
+
|
|
770
817
|
RNGHUIView *viewToHitTest = _recognizer.view;
|
|
771
818
|
|
|
772
819
|
if ([self usesNativeOrVirtualDetector] && [_recognizer.view.subviews count] > 0) {
|
|
@@ -774,20 +821,6 @@ static NSHashTable<RNGestureHandler *> *allGestureHandlers;
|
|
|
774
821
|
point = [_recognizer.view convertPoint:point toView:viewToHitTest];
|
|
775
822
|
}
|
|
776
823
|
|
|
777
|
-
if (_actionType == RNGestureHandlerActionTypeVirtualDetector && _virtualViewTag != nil) {
|
|
778
|
-
// In this case, logic detector is attached to the DetectorView, which has a single subview representing
|
|
779
|
-
// the actual target view in the RN hierarchy
|
|
780
|
-
if ([viewToHitTest respondsToSelector:@selector(touchEventEmitterAtPoint:)]) {
|
|
781
|
-
// If the view has touchEventEmitterAtPoint: method, it can be used to determine the viewtag
|
|
782
|
-
// of the view under the touch point
|
|
783
|
-
facebook::react::SharedTouchEventEmitter eventEmitter =
|
|
784
|
-
[(id<RCTTouchableComponentViewProtocol>)viewToHitTest touchEventEmitterAtPoint:point];
|
|
785
|
-
auto viewUnderTouch = eventEmitter->getEventTarget()->getTag();
|
|
786
|
-
|
|
787
|
-
return viewUnderTouch == [_virtualViewTag intValue];
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
824
|
CGRect hitFrame = RNGHHitSlopInsetRect(viewToHitTest.bounds, _hitSlop);
|
|
792
825
|
return CGRectContainsPoint(hitFrame, point);
|
|
793
826
|
}
|
|
@@ -810,19 +843,9 @@ static NSHashTable<RNGestureHandler *> *allGestureHandlers;
|
|
|
810
843
|
|
|
811
844
|
// Logic detector has a virtual view tag set only if the real hierarchy was folded into a single View
|
|
812
845
|
if (_actionType == RNGestureHandlerActionTypeVirtualDetector && _virtualViewTag != nil) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
if ([view respondsToSelector:@selector(touchEventEmitterAtPoint:)]) {
|
|
817
|
-
// If the view has touchEventEmitterAtPoint: method, it can be used to determine the viewtag
|
|
818
|
-
// of the view under the touch point
|
|
819
|
-
facebook::react::SharedTouchEventEmitter eventEmitter =
|
|
820
|
-
[(id<RCTTouchableComponentViewProtocol>)view touchEventEmitterAtPoint:[_recognizer locationInView:view]];
|
|
821
|
-
auto viewUnderTouch = eventEmitter->getEventTarget()->getTag();
|
|
822
|
-
|
|
823
|
-
if (viewUnderTouch != [_virtualViewTag intValue]) {
|
|
824
|
-
return NO;
|
|
825
|
-
}
|
|
846
|
+
CGPoint point = [_recognizer locationInView:_recognizer.view];
|
|
847
|
+
if (![self isVirtualViewTag:_virtualViewTag touchedAtPoint:point inView:_recognizer.view]) {
|
|
848
|
+
return NO;
|
|
826
849
|
}
|
|
827
850
|
}
|
|
828
851
|
|
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
@property (nonatomic) BOOL userEnabled;
|
|
28
28
|
@property (nonatomic, assign) RNGestureHandlerPointerEvents pointerEvents;
|
|
29
29
|
|
|
30
|
-
@property (nonatomic, assign) NSInteger
|
|
31
|
-
@property (nonatomic, assign) NSInteger
|
|
30
|
+
@property (nonatomic, assign) NSInteger tapAnimationInDuration;
|
|
31
|
+
@property (nonatomic, assign) NSInteger tapAnimationOutDuration;
|
|
32
|
+
@property (nonatomic, assign) NSInteger longPressDuration;
|
|
33
|
+
@property (nonatomic, assign) NSInteger longPressAnimationOutDuration;
|
|
32
34
|
@property (nonatomic, assign) CGFloat activeOpacity;
|
|
33
35
|
@property (nonatomic, assign) CGFloat defaultOpacity;
|
|
34
36
|
@property (nonatomic, assign) CGFloat activeScale;
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
dispatch_block_t _pendingPressOutBlock;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
@synthesize
|
|
52
|
+
@synthesize longPressAnimationOutDuration = _longPressAnimationOutDuration;
|
|
53
53
|
|
|
54
54
|
- (void)commonInit
|
|
55
55
|
{
|
|
@@ -57,8 +57,10 @@
|
|
|
57
57
|
_hitTestEdgeInsets = UIEdgeInsetsZero;
|
|
58
58
|
_userEnabled = YES;
|
|
59
59
|
_pointerEvents = RNGestureHandlerPointerEventsAuto;
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
_tapAnimationInDuration = 50;
|
|
61
|
+
_tapAnimationOutDuration = 100;
|
|
62
|
+
_longPressDuration = -1;
|
|
63
|
+
_longPressAnimationOutDuration = -1;
|
|
62
64
|
_activeOpacity = 1.0;
|
|
63
65
|
_defaultOpacity = 1.0;
|
|
64
66
|
_activeScale = 1.0;
|
|
@@ -145,6 +147,10 @@
|
|
|
145
147
|
[super viewWillMoveToWindow:newWindow];
|
|
146
148
|
if (newWindow == nil) {
|
|
147
149
|
[self cancelPendingPressOutAnimation];
|
|
150
|
+
[self applyStartAnimationState];
|
|
151
|
+
_isTouchInsideBounds = NO;
|
|
152
|
+
_suppressSuperControlActionDispatch = NO;
|
|
153
|
+
_pressInTimestamp = 0;
|
|
148
154
|
}
|
|
149
155
|
}
|
|
150
156
|
#else
|
|
@@ -153,13 +159,17 @@
|
|
|
153
159
|
[super willMoveToWindow:newWindow];
|
|
154
160
|
if (newWindow == nil) {
|
|
155
161
|
[self cancelPendingPressOutAnimation];
|
|
162
|
+
[self applyStartAnimationState];
|
|
163
|
+
_isTouchInsideBounds = NO;
|
|
164
|
+
_suppressSuperControlActionDispatch = NO;
|
|
165
|
+
_pressInTimestamp = 0;
|
|
156
166
|
}
|
|
157
167
|
}
|
|
158
168
|
#endif
|
|
159
169
|
|
|
160
|
-
- (NSInteger)
|
|
170
|
+
- (NSInteger)longPressAnimationOutDuration
|
|
161
171
|
{
|
|
162
|
-
return
|
|
172
|
+
return _longPressAnimationOutDuration < 0 ? _tapAnimationOutDuration : _longPressAnimationOutDuration;
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
- (void)setUnderlayColor:(RNGHColor *)underlayColor
|
|
@@ -193,8 +203,13 @@
|
|
|
193
203
|
|
|
194
204
|
- (void)animateUnderlayToOpacity:(float)toOpacity duration:(NSTimeInterval)durationMs
|
|
195
205
|
{
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
// Only sync the model from the presentation layer when an animation is actually
|
|
207
|
+
// in flight.
|
|
208
|
+
CALayer *presentation = _underlayLayer.presentationLayer;
|
|
209
|
+
BOOL hasInFlightAnimation = presentation != nil && _underlayLayer.animationKeys.count > 0;
|
|
210
|
+
if (hasInFlightAnimation) {
|
|
211
|
+
_underlayLayer.opacity = presentation.opacity;
|
|
212
|
+
}
|
|
198
213
|
[_underlayLayer removeAllAnimations];
|
|
199
214
|
|
|
200
215
|
// CABasicAnimation with duration 0 resolves to the current CATransaction's
|
|
@@ -256,19 +271,60 @@ static CATransform3D RNGHCenterScaleTransform(NSRect bounds, CGFloat scale)
|
|
|
256
271
|
#endif
|
|
257
272
|
}
|
|
258
273
|
|
|
274
|
+
// Duration of a single frame at the current screen's max refresh rate, in ms.
|
|
275
|
+
- (NSTimeInterval)minFrameDurationMs
|
|
276
|
+
{
|
|
277
|
+
#if !TARGET_OS_OSX
|
|
278
|
+
UIScreen *screen = self.window.screen ?: UIScreen.mainScreen;
|
|
279
|
+
NSInteger maxFps = screen.maximumFramesPerSecond;
|
|
280
|
+
#else
|
|
281
|
+
NSScreen *screen = self.window.screen ?: NSScreen.mainScreen;
|
|
282
|
+
NSInteger maxFps = 60;
|
|
283
|
+
if (@available(macOS 12.0, *)) {
|
|
284
|
+
maxFps = screen.maximumFramesPerSecond;
|
|
285
|
+
}
|
|
286
|
+
#endif
|
|
287
|
+
return maxFps > 0 ? 1000.0 / (NSTimeInterval)maxFps : 1000.0 / 60.0;
|
|
288
|
+
}
|
|
289
|
+
|
|
259
290
|
- (void)animateTarget:(RNGHUIView *)target
|
|
260
291
|
toOpacity:(CGFloat)opacity
|
|
261
292
|
scale:(CGFloat)scale
|
|
262
293
|
duration:(NSTimeInterval)durationMs
|
|
263
294
|
{
|
|
264
|
-
target.layer
|
|
265
|
-
|
|
266
|
-
NSTimeInterval
|
|
295
|
+
CALayer *layer = target.layer;
|
|
296
|
+
CALayer *presentation = layer.presentationLayer;
|
|
297
|
+
NSTimeInterval snapThresholdMs = [self minFrameDurationMs];
|
|
298
|
+
|
|
299
|
+
// Only snap to the presentation layer when an animation is in flight,
|
|
300
|
+
// that's the only case where it tells us something the model layer doesn't.
|
|
301
|
+
BOOL hasInFlightAnimation = presentation != nil && layer.animationKeys.count > 0;
|
|
302
|
+
if (hasInFlightAnimation) {
|
|
303
|
+
layer.transform = presentation.transform;
|
|
304
|
+
}
|
|
267
305
|
|
|
268
306
|
#if !TARGET_OS_OSX
|
|
269
|
-
|
|
270
|
-
|
|
307
|
+
if (hasInFlightAnimation) {
|
|
308
|
+
target.alpha = presentation.opacity;
|
|
309
|
+
}
|
|
310
|
+
[layer removeAllAnimations];
|
|
311
|
+
|
|
312
|
+
// Sub-frame durations: snap with implicit actions disabled instead of
|
|
313
|
+
// routing through UIView.animate. Same rationale as animateUnderlayToOpacity.
|
|
314
|
+
if (durationMs < snapThresholdMs) {
|
|
315
|
+
[CATransaction begin];
|
|
316
|
+
[CATransaction setDisableActions:YES];
|
|
317
|
+
if (_activeOpacity != 1.0 || _defaultOpacity != 1.0) {
|
|
318
|
+
target.alpha = opacity;
|
|
319
|
+
}
|
|
320
|
+
if (_activeScale != 1.0 || _defaultScale != 1.0) {
|
|
321
|
+
layer.transform = CATransform3DMakeScale(scale, scale, 1.0);
|
|
322
|
+
}
|
|
323
|
+
[CATransaction commit];
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
271
326
|
|
|
327
|
+
NSTimeInterval duration = durationMs / 1000.0;
|
|
272
328
|
[UIView animateWithDuration:duration
|
|
273
329
|
delay:0
|
|
274
330
|
options:UIViewAnimationOptionCurveEaseInOut
|
|
@@ -283,9 +339,25 @@ static CATransform3D RNGHCenterScaleTransform(NSRect bounds, CGFloat scale)
|
|
|
283
339
|
completion:nil];
|
|
284
340
|
#else
|
|
285
341
|
target.wantsLayer = YES;
|
|
286
|
-
|
|
287
|
-
|
|
342
|
+
if (hasInFlightAnimation) {
|
|
343
|
+
target.alphaValue = presentation.opacity;
|
|
344
|
+
}
|
|
345
|
+
[layer removeAllAnimations];
|
|
288
346
|
|
|
347
|
+
if (durationMs < snapThresholdMs) {
|
|
348
|
+
[CATransaction begin];
|
|
349
|
+
[CATransaction setDisableActions:YES];
|
|
350
|
+
if (_activeOpacity != 1.0 || _defaultOpacity != 1.0) {
|
|
351
|
+
target.alphaValue = opacity;
|
|
352
|
+
}
|
|
353
|
+
if (_activeScale != 1.0 || _defaultScale != 1.0) {
|
|
354
|
+
layer.transform = RNGHCenterScaleTransform(target.bounds, scale);
|
|
355
|
+
}
|
|
356
|
+
[CATransaction commit];
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
NSTimeInterval duration = durationMs / 1000.0;
|
|
289
361
|
[NSAnimationContext
|
|
290
362
|
runAnimationGroup:^(NSAnimationContext *context) {
|
|
291
363
|
context.allowsImplicitAnimation = YES;
|
|
@@ -310,9 +382,9 @@ static CATransform3D RNGHCenterScaleTransform(NSRect bounds, CGFloat scale)
|
|
|
310
382
|
}
|
|
311
383
|
_pressInTimestamp = CACurrentMediaTime();
|
|
312
384
|
RNGHUIView *target = self.animationTarget ?: self;
|
|
313
|
-
[self animateTarget:target toOpacity:_activeOpacity scale:_activeScale duration:
|
|
385
|
+
[self animateTarget:target toOpacity:_activeOpacity scale:_activeScale duration:_tapAnimationInDuration];
|
|
314
386
|
if (_activeUnderlayOpacity != _defaultUnderlayOpacity) {
|
|
315
|
-
[self animateUnderlayToOpacity:_activeUnderlayOpacity duration:
|
|
387
|
+
[self animateUnderlayToOpacity:_activeUnderlayOpacity duration:_tapAnimationInDuration];
|
|
316
388
|
}
|
|
317
389
|
}
|
|
318
390
|
|
|
@@ -323,17 +395,24 @@ static CATransform3D RNGHCenterScaleTransform(NSRect bounds, CGFloat scale)
|
|
|
323
395
|
}
|
|
324
396
|
|
|
325
397
|
NSTimeInterval elapsed = (CACurrentMediaTime() - _pressInTimestamp) * 1000.0;
|
|
326
|
-
NSInteger pressAndHoldAnimationDuration = self.pressAndHoldAnimationDuration;
|
|
327
398
|
|
|
328
|
-
if (elapsed >=
|
|
329
|
-
//
|
|
399
|
+
if (_longPressDuration >= 0 && elapsed >= _longPressDuration) {
|
|
400
|
+
// Long-press release - use the configured long-press out duration.
|
|
401
|
+
NSInteger longPressOut = self.longPressAnimationOutDuration;
|
|
402
|
+
RNGHUIView *target = self.animationTarget ?: self;
|
|
403
|
+
[self animateTarget:target toOpacity:_defaultOpacity scale:_defaultScale duration:longPressOut];
|
|
404
|
+
if (_activeUnderlayOpacity != _defaultUnderlayOpacity) {
|
|
405
|
+
[self animateUnderlayToOpacity:_defaultUnderlayOpacity duration:longPressOut];
|
|
406
|
+
}
|
|
407
|
+
} else if (elapsed >= _tapAnimationInDuration) {
|
|
408
|
+
// Press-in animation fully finished - release with the configured out duration.
|
|
330
409
|
RNGHUIView *target = self.animationTarget ?: self;
|
|
331
|
-
[self animateTarget:target toOpacity:_defaultOpacity scale:_defaultScale duration:
|
|
410
|
+
[self animateTarget:target toOpacity:_defaultOpacity scale:_defaultScale duration:_tapAnimationOutDuration];
|
|
332
411
|
if (_activeUnderlayOpacity != _defaultUnderlayOpacity) {
|
|
333
|
-
[self animateUnderlayToOpacity:_defaultUnderlayOpacity duration:
|
|
412
|
+
[self animateUnderlayToOpacity:_defaultUnderlayOpacity duration:_tapAnimationOutDuration];
|
|
334
413
|
}
|
|
335
|
-
// elapsed * 2 to ensure there is at least half of the
|
|
336
|
-
} else if (elapsed * 2 >=
|
|
414
|
+
// elapsed * 2 to ensure there is at least half of the tapAnimationOutDuration left for the animation to play
|
|
415
|
+
} else if (elapsed * 2 >= _tapAnimationOutDuration) {
|
|
337
416
|
// Past minimum but press-in animation still playing, animate out in elapsed time
|
|
338
417
|
RNGHUIView *target = self.animationTarget ?: self;
|
|
339
418
|
[self animateTarget:target toOpacity:_defaultOpacity scale:_defaultScale duration:elapsed];
|
|
@@ -341,8 +420,8 @@ static CATransform3D RNGHCenterScaleTransform(NSRect bounds, CGFloat scale)
|
|
|
341
420
|
[self animateUnderlayToOpacity:_defaultUnderlayOpacity duration:elapsed];
|
|
342
421
|
}
|
|
343
422
|
} else {
|
|
344
|
-
// Before minimum duration, finish press-in in remaining time then animate out in
|
|
345
|
-
NSTimeInterval remaining =
|
|
423
|
+
// Before minimum duration, finish press-in in remaining time then animate out in tapAnimationOutDuration.
|
|
424
|
+
NSTimeInterval remaining = _tapAnimationInDuration - elapsed;
|
|
346
425
|
|
|
347
426
|
RNGHUIView *target = self.animationTarget ?: self;
|
|
348
427
|
[self animateTarget:target toOpacity:_activeOpacity scale:_activeScale duration:remaining];
|
|
@@ -359,10 +438,10 @@ static CATransform3D RNGHCenterScaleTransform(NSRect bounds, CGFloat scale)
|
|
|
359
438
|
[strongSelf animateTarget:target
|
|
360
439
|
toOpacity:strongSelf->_defaultOpacity
|
|
361
440
|
scale:strongSelf->_defaultScale
|
|
362
|
-
duration:strongSelf->
|
|
441
|
+
duration:strongSelf->_tapAnimationOutDuration];
|
|
363
442
|
if (strongSelf->_activeUnderlayOpacity != strongSelf->_defaultUnderlayOpacity) {
|
|
364
443
|
[strongSelf animateUnderlayToOpacity:strongSelf->_defaultUnderlayOpacity
|
|
365
|
-
duration:strongSelf->
|
|
444
|
+
duration:strongSelf->_tapAnimationOutDuration];
|
|
366
445
|
}
|
|
367
446
|
}
|
|
368
447
|
});
|
|
@@ -323,8 +323,10 @@ static RNGestureHandlerPointerEvents RCTPointerEventsToEnum(facebook::react::Poi
|
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
_buttonView.userEnabled = newProps.enabled;
|
|
326
|
-
_buttonView.
|
|
327
|
-
_buttonView.
|
|
326
|
+
_buttonView.tapAnimationInDuration = newProps.tapAnimationInDuration > 0 ? newProps.tapAnimationInDuration : 0;
|
|
327
|
+
_buttonView.tapAnimationOutDuration = newProps.tapAnimationOutDuration > 0 ? newProps.tapAnimationOutDuration : 0;
|
|
328
|
+
_buttonView.longPressDuration = newProps.longPressDuration;
|
|
329
|
+
_buttonView.longPressAnimationOutDuration = newProps.longPressAnimationOutDuration;
|
|
328
330
|
_buttonView.activeOpacity = newProps.activeOpacity;
|
|
329
331
|
_buttonView.defaultOpacity = newProps.defaultOpacity;
|
|
330
332
|
_buttonView.activeScale = newProps.activeScale;
|
|
@@ -358,6 +360,19 @@ static RNGestureHandlerPointerEvents RCTPointerEventsToEnum(facebook::react::Poi
|
|
|
358
360
|
}
|
|
359
361
|
|
|
360
362
|
[super updateProps:props oldProps:oldProps];
|
|
363
|
+
|
|
364
|
+
#if !TARGET_OS_TV && !TARGET_OS_OSX
|
|
365
|
+
// super's updateProps sets self.accessibilityIdentifier from testID via the
|
|
366
|
+
// standard Fabric mechanism. However, setAccessibilityProps already forwards
|
|
367
|
+
// testID to _buttonView.accessibilityIdentifier (the actual button element).
|
|
368
|
+
// Having the identifier on both views causes testing frameworks (e.g. Detox)
|
|
369
|
+
// to report multiple matches for the same testID. Clear it from the wrapper so
|
|
370
|
+
// only _buttonView carries the identifier.
|
|
371
|
+
if (!newProps.testId.empty()) {
|
|
372
|
+
self.accessibilityIdentifier = nil;
|
|
373
|
+
}
|
|
374
|
+
#endif
|
|
375
|
+
|
|
361
376
|
if (shouldApplyStartAnimationState) {
|
|
362
377
|
[_buttonView applyStartAnimationState];
|
|
363
378
|
}
|