@@ -26,9 +26,11 @@ import android.view.accessibility.AccessibilityNodeInfo
2626import androidx.core.view.children
2727import androidx.interpolator.view.animation.FastOutSlowInInterpolator
2828import com.facebook.react.R
29+ import com.facebook.react.bridge.Arguments
2930import com.facebook.react.bridge.Dynamic
3031import com.facebook.react.bridge.ReactContext
3132import com.facebook.react.bridge.ReadableArray
33+ import com.facebook.react.bridge.ReadableMap
3234import com.facebook.react.module.annotations.ReactModule
3335import com.facebook.react.uimanager.BackgroundStyleApplicator
3436import com.facebook.react.uimanager.LengthPercentage
@@ -66,6 +68,85 @@ class RNGestureHandlerButtonViewManager :
6668
6769 public override fun createViewInstance (context : ThemedReactContext ) = ButtonViewGroup (context)
6870
71+ override fun onDropViewInstance (view : ButtonViewGroup ) {
72+ view.managedHandlerTag?.let { tag ->
73+ getGestureHandlerModule(view).dropGestureHandler(tag)
74+ }
75+
76+ super .onDropViewInstance(view)
77+ }
78+
79+ private fun getGestureHandlerModule (view : ButtonViewGroup ) =
80+ (view.context as ReactContext ).getNativeModule(RNGestureHandlerModule ::class .java)!!
81+
82+ private fun updateManagedHandlerConfig (view : ButtonViewGroup , config : ReadableMap ) {
83+ view.managedHandlerTag?.let { getGestureHandlerModule(view).updateGestureHandlerConfig(it, config) }
84+ }
85+
86+ @ReactProp(name = " handlerTag" )
87+ override fun setHandlerTag (view : ButtonViewGroup , handlerTag : Double ) {
88+ if (view.managedHandlerTag == handlerTag) {
89+ return
90+ }
91+
92+ view.managedHandlerTag?.let { oldTag ->
93+ getGestureHandlerModule(view).dropGestureHandler(oldTag)
94+ }
95+
96+ view.managedHandlerTag = handlerTag
97+ val module = getGestureHandlerModule(view)
98+
99+ module.createGestureHandler(
100+ " NativeViewGestureHandler" ,
101+ handlerTag,
102+ Arguments .createMap().apply {
103+ putBoolean(" shouldActivateOnStart" , false )
104+ putBoolean(" disallowInterruption" , true )
105+ putBoolean(" yieldsToContinuousGestures" , true )
106+ putBoolean(" enabled" , view.isEnabled)
107+ view.managedHandlerCancelOnLeave?.let { putBoolean(" shouldCancelWhenOutside" , it) }
108+ view.managedHandlerTestID?.let { putString(" testID" , it) }
109+ view.managedHandlerHitSlop?.let { putMap(" hitSlop" , it) }
110+ },
111+ )
112+ module.attachGestureHandler(handlerTag, view.id.toDouble(), GestureHandler .ACTION_TYPE_NONE .toDouble())
113+ }
114+
115+ @ReactProp(name = " cancelOnLeave" )
116+ override fun setCancelOnLeave (view : ButtonViewGroup , cancelOnLeave : Boolean ) {
117+ view.managedHandlerCancelOnLeave = cancelOnLeave
118+ updateManagedHandlerConfig(
119+ view,
120+ Arguments .createMap().apply {
121+ putBoolean(" shouldCancelWhenOutside" , cancelOnLeave)
122+ },
123+ )
124+ }
125+
126+ @ReactProp(name = " gestureTestID" )
127+ override fun setGestureTestID (view : ButtonViewGroup , gestureTestID : String? ) {
128+ view.managedHandlerTestID = gestureTestID
129+ updateManagedHandlerConfig(
130+ view,
131+ Arguments .createMap().apply {
132+ putString(" testID" , gestureTestID)
133+ },
134+ )
135+ }
136+
137+ @ReactProp(name = " gestureHitSlop" )
138+ override fun setGestureHitSlop (view : ButtonViewGroup , gestureHitSlop : ReadableMap ? ) {
139+ view.managedHandlerHitSlop = gestureHitSlop
140+ if (gestureHitSlop != null ) {
141+ updateManagedHandlerConfig(
142+ view,
143+ Arguments .createMap().apply {
144+ putMap(" hitSlop" , gestureHitSlop)
145+ },
146+ )
147+ }
148+ }
149+
69150 @ReactProp(name = " hasLongPressHandler" )
70151 override fun setHasLongPressHandler (view : ButtonViewGroup , hasLongPressHandler : Boolean ) {
71152 view.hasLongPressHandler = hasLongPressHandler
@@ -89,6 +170,13 @@ class RNGestureHandlerButtonViewManager :
89170 @ReactProp(name = " enabled" )
90171 override fun setEnabled (view : ButtonViewGroup , enabled : Boolean ) {
91172 view.isEnabled = enabled
173+
174+ updateManagedHandlerConfig(
175+ view,
176+ Arguments .createMap().apply {
177+ putBoolean(" enabled" , enabled)
178+ },
179+ )
92180 }
93181
94182 @ReactProp(name = " borderWidth" )
@@ -405,6 +493,16 @@ class RNGestureHandlerButtonViewManager :
405493 }
406494 var useBorderlessDrawable = false
407495
496+ var managedHandlerTag: Double? = null
497+
498+ // Config props may be applied before `handlerTag` (prop order is not guaranteed) and
499+ // `updateManagedHandlerConfig` no-ops until the handler exists, so the values are cached
500+ // here and seeded into the config when `setHandlerTag` creates the handler. This also
501+ // re-applies them when the handler is recreated for a new tag.
502+ var managedHandlerCancelOnLeave: Boolean? = null
503+ var managedHandlerTestID: String? = null
504+ var managedHandlerHitSlop: ReadableMap ? = null
505+
408506 var exclusive = true
409507 var hasLongPressHandler = false
410508 var tapAnimationInDuration: Int = 50
@@ -538,7 +636,7 @@ class RNGestureHandlerButtonViewManager :
538636 private var lastEventWasInside = false
539637
540638 override fun onHandlerUpdate (handler : NativeViewGestureHandler ) {
541- if (handler.isWithinBounds == lastEventWasInside) {
639+ if (managedHandlerTag == null || handler.isWithinBounds == lastEventWasInside) {
542640 return
543641 }
544642
@@ -555,6 +653,10 @@ class RNGestureHandlerButtonViewManager :
555653 }
556654
557655 override fun onHandlerStateChange (handler : NativeViewGestureHandler , newState : Int , prevState : Int ) {
656+ if (managedHandlerTag == null ) {
657+ return
658+ }
659+
558660 // Capture local copy, since lastEventWasInside can change during this method
559661 // Specifically PressOut -> Press scenario on STATE_END
560662 val localLastEventWasInside = lastEventWasInside
0 commit comments