Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
7d9de2e
logic detector web
akwasniewski Aug 25, 2025
4ccf49e
avoiding propsRef
akwasniewski Aug 25, 2025
69e1c5d
refactor
akwasniewski Aug 27, 2025
9959d8b
logicChildren in specs
akwasniewski Aug 27, 2025
1f1f681
ios
akwasniewski Sep 1, 2025
a982767
merge with next
akwasniewski Sep 1, 2025
81bf405
fix reanimated version
akwasniewski Sep 1, 2025
a90b83c
yarn lock
akwasniewski Sep 1, 2025
8c03f58
worklets
akwasniewski Sep 1, 2025
a3256d4
detach handlers
akwasniewski Sep 2, 2025
6f45a33
worklets fix
akwasniewski Sep 3, 2025
66390ab
android
akwasniewski Sep 4, 2025
1ef1969
Merge branch 'next' into @akwasniewski/logic-detector
akwasniewski Sep 4, 2025
3379714
yarn lock fix
akwasniewski Sep 4, 2025
e9795cc
removed circular dependency
akwasniewski Sep 4, 2025
7fb8ea9
simplify ios
akwasniewski Sep 5, 2025
5c24151
removed logic events web/android
akwasniewski Sep 8, 2025
593c44f
android and web refactor
akwasniewski Sep 8, 2025
fe1d67d
one path
akwasniewski Sep 8, 2025
b147c7b
merge with next
akwasniewski Sep 8, 2025
4e718b3
merge fix
akwasniewski Sep 8, 2025
8e9d2dc
adjust for reanimated/animated separation
akwasniewski Sep 9, 2025
97b7369
general refactor
akwasniewski Sep 9, 2025
8b6df8c
simplified attaching
akwasniewski Sep 9, 2025
dab9e5f
tag in callback
akwasniewski Sep 10, 2025
089bbf2
not sharing web invoke
akwasniewski Sep 10, 2025
5fbe993
renamed logic props
akwasniewski Sep 10, 2025
ddce1fb
logic animated event
akwasniewski Sep 11, 2025
50c1af4
removed child tag from web
akwasniewski Sep 11, 2025
5f3f1eb
safe detaching
akwasniewski Sep 11, 2025
1f22ef4
typo fix
akwasniewski Sep 11, 2025
3e7bc43
more typos
akwasniewski Sep 11, 2025
cd4d158
comment styling
akwasniewski Sep 12, 2025
164c4e8
moved detector context
akwasniewski Sep 12, 2025
84bdf3d
renamed helper function
akwasniewski Sep 12, 2025
4365e3e
explained invoke method
akwasniewski Sep 12, 2025
33fe818
isV3Api helper function
akwasniewski Sep 12, 2025
6c42eae
renamed logic children
akwasniewski Sep 12, 2025
a11b6d3
renamed var containing logic handlers
akwasniewski Sep 12, 2025
a70f868
action type helper on android
akwasniewski Sep 12, 2025
c0c9bb0
simplify logic children to delete creation
akwasniewski Sep 12, 2025
a2ee13d
removed redundant check
akwasniewski Sep 12, 2025
f0f24b8
renamed parent tag to host detector tag
akwasniewski Sep 12, 2025
f0fb458
removed unnecessary variable
akwasniewski Sep 12, 2025
5e5e917
renamed set
akwasniewski Sep 12, 2025
abcac98
separate function to detach all handlers
akwasniewski Sep 15, 2025
5c367b5
fix testing remnant
akwasniewski Sep 15, 2025
b1d620a
non default useDetectorContext
akwasniewski Sep 15, 2025
157d87b
android action type whitespace
akwasniewski Sep 15, 2025
a1b94d5
clean up extension
akwasniewski Sep 15, 2025
75f218b
removed remnants
akwasniewski Sep 15, 2025
344617a
renamed logic children web
akwasniewski Sep 15, 2025
03b37c7
android whitespace
akwasniewski Sep 15, 2025
9420ead
removed unnecessary annotation
akwasniewski Sep 15, 2025
06bb0e1
whitespace
akwasniewski Sep 15, 2025
474e301
delete obsolete
akwasniewski Sep 15, 2025
1fef44a
Fallthrough comment
akwasniewski Sep 15, 2025
cb9ab7a
defining registry outside of loop
akwasniewski Sep 15, 2025
76fd9ff
merge with next
akwasniewski Sep 15, 2025
2a727cb
cleaner extension function
akwasniewski Sep 16, 2025
e6fbdba
extract repetition
akwasniewski Sep 16, 2025
6fa67e3
moved file
akwasniewski Sep 16, 2025
31fad02
fix bad merge
akwasniewski Sep 16, 2025
c9ffbdb
better error
akwasniewski Sep 16, 2025
f00e0dd
Merge branch 'next' into @akwasniewski/logic-detector
akwasniewski Sep 16, 2025
72e7813
updating logic children
akwasniewski Sep 16, 2025
79f1d96
removed unnecessary return
akwasniewski Sep 16, 2025
86ff725
hostGestureDetector property
akwasniewski Sep 17, 2025
7213108
extracted update logic children
akwasniewski Sep 17, 2025
8015ea5
rename logic children deleton set
akwasniewski Sep 17, 2025
e589043
move invoke detector event
akwasniewski Sep 17, 2025
57b6b57
auto type
akwasniewski Sep 17, 2025
a8fa637
rename isv3api
akwasniewski Sep 17, 2025
468e23f
no put
akwasniewski Sep 17, 2025
63d44f7
simplify mapping
akwasniewski Sep 17, 2025
0b362be
better web detaching
akwasniewski Sep 17, 2025
20038b1
typing
akwasniewski Sep 17, 2025
f3f985c
Merge branch 'next' into @akwasniewski/logic-detector
akwasniewski Sep 17, 2025
832a039
passing helper function directly
akwasniewski Sep 18, 2025
1d96fa1
Merge branch 'next' into @akwasniewski/logic-detector
akwasniewski Sep 18, 2025
590a5cd
resolved workaround
akwasniewski Sep 19, 2025
f25cd9e
Merge branch 'next' into @akwasniewski/logic-detector
akwasniewski Sep 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,26 @@ open class GestureHandler {
var tag = 0
var view: View? = null
private set

// Host detector view is a reference to a Native Detector designated to handle events from a
// Logic Detector to which the gesture is assigned.
var hostDetectorView: RNGestureHandlerDetectorView? = null

val viewForEvents: RNGestureHandlerDetectorView
get() {
assert(actionType == ACTION_TYPE_NATIVE_DETECTOR) {
assert(usesNativeOrLogicDetector(actionType)) {
"[react-native-gesture-handler] `viewForEvents` can only be used with NativeDetector."
}

val detector = if (this is NativeViewGestureHandler) this.view?.parent else view
val detector = if (actionType ==
ACTION_TYPE_LOGIC_DETECTOR
) {
this.hostDetectorView
} else if (this is NativeViewGestureHandler) {
this.view?.parent
} else {
view
}

if (detector !is RNGestureHandlerDetectorView) {
throw Error(
Expand Down Expand Up @@ -1005,6 +1018,7 @@ open class GestureHandler {
const val ACTION_TYPE_JS_FUNCTION_OLD_API = 3
const val ACTION_TYPE_JS_FUNCTION_NEW_API = 4
const val ACTION_TYPE_NATIVE_DETECTOR = 5
const val ACTION_TYPE_LOGIC_DETECTOR = 6
const val POINTER_TYPE_TOUCH = 0
const val POINTER_TYPE_STYLUS = 1
const val POINTER_TYPE_MOUSE = 2
Expand Down Expand Up @@ -1039,6 +1053,9 @@ open class GestureHandler {
}
return null
}

fun usesNativeOrLogicDetector(actionType: Int): Boolean =
actionType == ACTION_TYPE_NATIVE_DETECTOR || actionType == ACTION_TYPE_LOGIC_DETECTOR
}

private data class PointerData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
private var handlersToAttach: List<Int>? = null
private var nativeHandlers: MutableSet<Int> = mutableSetOf()
private var attachedHandlers: MutableSet<Int> = mutableSetOf()
private var attachedLogicHandlers: MutableMap<Int, MutableSet<Int>> = mutableMapOf()
private var moduleId: Int = -1

data class LogicChildren(val handlerTags: List<Int>, val viewTag: Int)

fun setHandlerTags(handlerTags: ReadableArray?) {
val newHandlers = handlerTags?.toArrayList()?.map { (it as Double).toInt() } ?: emptyList()
if (moduleId == -1) {
Expand All @@ -37,6 +40,35 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
handlersToAttach = null
}

fun setLogicChildren(newLogicChildren: ReadableArray?) {
val logicChildrenToDetach = attachedLogicHandlers.keys.toMutableSet()

val mappedChildren = newLogicChildren?.mapLogicChildren().orEmpty()

for (child in mappedChildren) {
if (!attachedLogicHandlers.containsKey(child.viewTag)) {
attachedLogicHandlers[child.viewTag] = mutableSetOf()
}

logicChildrenToDetach.remove(child.viewTag)

attachHandlers(
child.handlerTags,
child.viewTag,
GestureHandler.ACTION_TYPE_LOGIC_DETECTOR,
attachedLogicHandlers[child.viewTag]!!,
)
}

val registry = RNGestureHandlerModule.registries[moduleId]
?: throw Exception("Tried to access a non-existent registry")

for (tag in logicChildrenToDetach) {
registry.detachHandler(tag)
attachedLogicHandlers.remove(tag)
}
}

private fun shouldAttachGestureToChildView(tag: Int): Boolean {
val registry = RNGestureHandlerModule.registries[moduleId]
?: throw Exception("Tried to access a non-existent registry")
Expand All @@ -57,39 +89,40 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
super.removeViewAt(index)
}

private fun attachHandlers(newHandlers: List<Int>) {
private fun attachHandlers(
newHandlers: List<Int>,
viewTag: Int = this.id,
actionType: Int = GestureHandler.ACTION_TYPE_NATIVE_DETECTOR,
attachedHandlers: MutableSet<Int> = this.attachedHandlers,
) {
val registry = RNGestureHandlerModule.registries[moduleId]
?: throw Exception("Tried to access a non-existent registry")

val changes = mutableMapOf<Int, GestureHandlerMutation>()

for (tag in attachedHandlers) {
changes[tag] = GestureHandlerMutation.Detach
}
val handlersToDetach = attachedHandlers.toMutableSet()

for (tag in newHandlers) {
changes[tag] = if (changes.containsKey(tag)) GestureHandlerMutation.Keep else GestureHandlerMutation.Attach
}

for (entry in changes) {
val tag = entry.key

if (entry.value == GestureHandlerMutation.Attach) {
handlersToDetach.remove(tag)
if (!attachedHandlers.contains(tag)) {
if (shouldAttachGestureToChildView(tag)) {
// It might happen that `attachHandlers` will be called before children are added into view hierarchy. In that case we cannot
// attach `NativeViewGestureHandlers` here and we have to do it in `addView` method.
nativeHandlers.add(tag)
} else {
registry.attachHandlerToView(tag, this.id, GestureHandler.ACTION_TYPE_NATIVE_DETECTOR)
registry.attachHandlerToView(tag, viewTag, actionType)
if (actionType == GestureHandler.ACTION_TYPE_LOGIC_DETECTOR) {
registry.getHandler(tag)?.hostDetectorView = this
}
attachedHandlers.add(tag)
}
} else if (entry.value == GestureHandlerMutation.Detach) {
registry.detachHandler(tag)
nativeHandlers.remove(tag)
attachedHandlers.remove(tag)
}
}

for (tag in handlersToDetach) {
registry.detachHandler(tag)
nativeHandlers.remove(tag)
attachedHandlers.remove(tag)
}

val child = getChildAt(0)

// This covers the case where `NativeViewGestureHandlers` are attached after child views were created.
Expand Down Expand Up @@ -132,13 +165,22 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
registry.detachHandler(tag)
attachedHandlers.remove(tag)
}
}

companion object {
private enum class GestureHandlerMutation {
Attach,
Detach,
Keep,
for (child in attachedLogicHandlers) {
for (tag in child.value) {
registry.detachHandler(tag)
}
child.value.clear()
}
}

private fun ReadableArray.mapLogicChildren(): List<LogicChildren> = List(size()) { i ->
val child = getMap(i) ?: return@List null
val handlerTags = child.getArray("handlerTags")?.toIntList().orEmpty()
val viewTag = child.getInt("viewTag")

LogicChildren(handlerTags, viewTag)
}.filterNotNull()

private fun ReadableArray.toIntList(): List<Int> = List(size()) { getInt(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class RNGestureHandlerDetectorViewManager :
view.setModuleId(value)
}

override fun setLogicChildren(view: RNGestureHandlerDetectorView, value: ReadableArray?) {
view.setLogicChildren(value)
}

override fun onDropViewInstance(view: RNGestureHandlerDetectorView) {
view.onViewDrop()
super.onDropViewInstance(view)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class RNGestureHandlerEvent private constructor() : Event<RNGestureHandlerEvent>
dataBuilder: GestureHandlerEventDataBuilder<T>,
eventHandlerType: EventHandlerType,
) {
val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
handler.viewForEvents!!
val view = if (GestureHandler.usesNativeOrLogicDetector(handler.actionType)) {
handler.viewForEvents
} else {
handler.view!!
}
Expand All @@ -45,7 +45,7 @@ class RNGestureHandlerEvent private constructor() : Event<RNGestureHandlerEvent>
EVENTS_POOL.release(this)
}

override fun getEventName() = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
override fun getEventName() = if (GestureHandler.usesNativeOrLogicDetector(actionType)) {
if (eventHandlerType == EventHandlerType.ForAnimated) {
NATIVE_DETECTOR_ANIMATED_EVENT_NAME
} else if (eventHandlerType == EventHandlerType.ForReanimated) {
Expand All @@ -64,7 +64,7 @@ class RNGestureHandlerEvent private constructor() : Event<RNGestureHandlerEvent>

override fun getCoalescingKey() = coalescingKey

override fun getEventData(): WritableMap = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
override fun getEventData(): WritableMap = if (GestureHandler.usesNativeOrLogicDetector(actionType)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is not exact place, but shouldn't we also update canCoalesce above?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I'm not sure about TouchEvents, since they have touchesMove callback (cc @j-piasecki)

createNativeEventData(dataBuilder!!)
} else {
createEventData(dataBuilder!!)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
RNGestureHandlerEvent.createEventData(handlerFactory.createEventBuilder(handler))
sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data)
}
GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> {
GestureHandler.ACTION_TYPE_NATIVE_DETECTOR, GestureHandler.ACTION_TYPE_LOGIC_DETECTOR -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isV3Api?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a when case, so we would have to use sth like
else -> if (usesNativeOrLogicDetector(handler.actionType)) {
and I'm not sure I like it. I think looks cleaner now. Can it be done differently?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah. That would work in when {}, not in when (...) {}

val eventHandlerType = if (handler.dispatchesAnimatedEvents) {
EventHandlerType.ForAnimated
} else if (handler.dispatchesReanimatedEvents) {
Expand All @@ -87,7 +87,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
eventHandlerType,
)

handler.viewForEvents!!.dispatchEvent(event)
handler.viewForEvents.dispatchEvent(event)
}
}
}
Expand Down Expand Up @@ -136,7 +136,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
sendEventForDeviceEvent(RNGestureHandlerStateChangeEvent.EVENT_NAME, data)
}

GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> {
GestureHandler.ACTION_TYPE_NATIVE_DETECTOR, GestureHandler.ACTION_TYPE_LOGIC_DETECTOR -> {
val eventHandlerType = if (handler.dispatchesReanimatedEvents) {
EventHandlerType.ForReanimated
} else {
Expand All @@ -152,7 +152,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
eventHandlerType,
)

handler.viewForEvents!!.dispatchEvent(event)
handler.viewForEvents.dispatchEvent(event)
}
}
}
Expand Down Expand Up @@ -188,15 +188,15 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
val data = RNGestureHandlerTouchEvent.createEventData(handler)
sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data)
}
GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> {
GestureHandler.ACTION_TYPE_NATIVE_DETECTOR, GestureHandler.ACTION_TYPE_LOGIC_DETECTOR -> {
val eventHandlerType = if (handler.dispatchesReanimatedEvents) {
EventHandlerType.ForReanimated
} else {
EventHandlerType.ForJS
}
val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType, eventHandlerType)

handler.viewForEvents!!.dispatchEvent(event)
handler.viewForEvents.dispatchEvent(event)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event<RNGestureHa
dataBuilder: GestureHandlerEventDataBuilder<T>,
eventHandlerType: EventHandlerType,
) {
val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
handler.viewForEvents!!
val view = if (GestureHandler.usesNativeOrLogicDetector(handler.actionType)) {
handler.viewForEvents
} else {
handler.view!!
}
Expand All @@ -51,7 +51,7 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event<RNGestureHa
EVENTS_POOL.release(this)
}

override fun getEventName() = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
override fun getEventName() = if (GestureHandler.usesNativeOrLogicDetector(actionType)) {
if (eventHandlerType == EventHandlerType.ForReanimated) REANIMATED_EVENT_NAME else EVENT_NAME
} else {
EVENT_NAME
Expand All @@ -63,7 +63,7 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event<RNGestureHa
// TODO: coalescing
override fun getCoalescingKey(): Short = 0

override fun getEventData(): WritableMap = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
override fun getEventData(): WritableMap = if (GestureHandler.usesNativeOrLogicDetector(actionType)) {
createNativeEventData(dataBuilder!!, newState, oldState)
} else {
createEventData(dataBuilder!!, newState, oldState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class RNGestureHandlerTouchEvent private constructor() : Event<RNGestureHandlerT
private lateinit var eventHandlerType: EventHandlerType

private fun <T : GestureHandler> init(handler: T, actionType: Int, eventHandlerType: EventHandlerType) {
val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
handler.viewForEvents!!
val view = if (GestureHandler.usesNativeOrLogicDetector(handler.actionType)) {
handler.viewForEvents
} else {
handler.view!!
}
Expand All @@ -33,7 +33,7 @@ class RNGestureHandlerTouchEvent private constructor() : Event<RNGestureHandlerT
EVENTS_POOL.release(this)
}

override fun getEventName() = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
override fun getEventName() = if (GestureHandler.usesNativeOrLogicDetector(actionType)) {
if (eventHandlerType == EventHandlerType.ForReanimated) REANIMATED_EVENT_NAME else NATIVE_EVENT_NAME
} else {
EVENT_NAME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
@property (nonatomic) BOOL manualActivation;
@property (nonatomic) BOOL dispatchesAnimatedEvents;
@property (nonatomic) BOOL dispatchesReanimatedEvents;
@property (nonatomic, nonnull, assign) NSNumber *hostDetectorTag;

- (BOOL)isViewParagraphComponent:(nullable RNGHUIView *)view;
- (nonnull RNGHUIView *)chooseViewForInteraction:(nonnull UIGestureRecognizer *)recognizer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ typedef NS_ENUM(NSInteger, RNGestureHandlerActionType) {
RNGestureHandlerActionTypeJSFunctionNewAPI, // JS function or Animated.event with useNativeDriver: false using new
// RNGH API
RNGestureHandlerActionTypeNativeDetector,
RNGestureHandlerActionTypeLogicDetector,
};
Loading
Loading