diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index a5fa388742a7..526f6f266bbc 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3201,6 +3201,7 @@ public abstract class com/facebook/react/uimanager/BaseViewManager : com/faceboo public fun onDropViewInstance (Landroid/view/View;)V public fun onLayoutChange (Landroid/view/View;IIIIIIII)V protected fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View; + protected fun resetDisabledFromAccessibilityState (Landroid/view/View;)V public fun setAccessibilityActions (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V public fun setAccessibilityCollection (Landroid/view/View;Lcom/facebook/react/bridge/ReadableMap;)V public fun setAccessibilityCollectionItem (Landroid/view/View;Lcom/facebook/react/bridge/ReadableMap;)V @@ -6520,6 +6521,8 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react public synthetic fun receiveCommand (Landroid/view/View;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V public fun receiveCommand (Lcom/facebook/react/views/view/ReactViewGroup;ILcom/facebook/react/bridge/ReadableArray;)V public fun receiveCommand (Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V + public synthetic fun resetDisabledFromAccessibilityState (Landroid/view/View;)V + protected fun resetDisabledFromAccessibilityState (Lcom/facebook/react/views/view/ReactViewGroup;)V public fun setAccessibilityOrder (Lcom/facebook/react/views/view/ReactViewGroup;Lcom/facebook/react/bridge/ReadableArray;)V public fun setAccessible (Lcom/facebook/react/views/view/ReactViewGroup;Z)V public fun setBackfaceVisibility (Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index bef69e2102f3..6c416464aad6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -397,8 +397,19 @@ public void setAccessibilityCollectionItem( @ReactProp(name = ViewProps.ACCESSIBILITY_STATE) public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilityState) { + if (accessibilityState != null + && accessibilityState.hasKey("disabled") + && !accessibilityState.isNull("disabled")) { + boolean disabled = accessibilityState.getBoolean("disabled"); + view.setEnabled(!disabled); + view.setTag(R.id.accessibility_state_disabled, disabled); + } else { + if (Boolean.TRUE.equals(view.getTag(R.id.accessibility_state_disabled))) { + resetDisabledFromAccessibilityState(view); + } + view.setTag(R.id.accessibility_state_disabled, null); + } if (accessibilityState == null) { - resetDisabledFromAccessibilityState(view); return; } if (accessibilityState.hasKey("expanded")) { @@ -420,13 +431,6 @@ public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilitySta view.setSelected(false); } view.setTag(R.id.accessibility_state, accessibilityState); - if (accessibilityState.hasKey("disabled") && !accessibilityState.isNull("disabled")) { - boolean disabled = accessibilityState.getBoolean("disabled"); - view.setEnabled(!disabled); - view.setTag(R.id.accessibility_state_disabled, disabled); - } else { - resetDisabledFromAccessibilityState(view); - } // For states which don't have corresponding methods in // AccessibilityNodeInfo, update the view's content description @@ -452,12 +456,7 @@ public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilitySta } } - private static void resetDisabledFromAccessibilityState(@NonNull View view) { - if (Boolean.TRUE.equals(view.getTag(R.id.accessibility_state_disabled))) { - view.setEnabled(true); - } - view.setTag(R.id.accessibility_state_disabled, null); - } + protected void resetDisabledFromAccessibilityState(@NonNull T view) {} private void updateViewContentDescription(@NonNull T view) { final String accessibilityLabel = (String) view.getTag(R.id.accessibility_label); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt index 21ef11182da3..94d1e25c8b1a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt @@ -97,6 +97,10 @@ public open class ReactViewManager : ReactClippingViewManager() view.isFocusable = accessible } + override fun resetDisabledFromAccessibilityState(view: ReactViewGroup) { + view.isEnabled = true + } + @ReactProp(name = ViewProps.ACCESSIBILITY_ORDER) public open fun setAccessibilityOrder(view: ReactViewGroup, nativeIds: ReadableArray?) { if (!ReactNativeFeatureFlags.enableAccessibilityOrder()) { diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt index 666aa2bd71e2..654707d717d3 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt @@ -13,12 +13,14 @@ import android.util.DisplayMetrics import android.view.View import android.view.View.OnFocusChangeListener import android.view.accessibility.AccessibilityNodeInfo +import android.widget.FrameLayout import androidx.core.view.ViewCompat import com.facebook.react.R import com.facebook.react.bridge.BridgeReactContext import com.facebook.react.bridge.DynamicFromObject import com.facebook.react.bridge.JavaOnlyArray import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableMap import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests @@ -95,6 +97,52 @@ class BaseViewManagerTest { Assertions.assertThat(view.isSelected).isEqualTo(true) } + @Test + fun testClearingAccessibilityDisabledRestoresTapTarget() { + val root = FrameLayout(themedReactContext) + root.id = 1 + root.layout(0, 0, 300, 300) + view.id = 3 + root.addView(view) + view.layout(0, 0, 200, 100) + + for (state in listOf(JavaOnlyMap(), JavaOnlyMap.of("disabled", null), null)) { + viewManager.setViewState(view, JavaOnlyMap.of("disabled", true)) + Assertions.assertThat(TouchTargetHelper.findTargetTagForTouch(20f, 50f, root)).isEqualTo(1) + + viewManager.setViewState(view, state) + + Assertions.assertThat(view.isEnabled).isTrue() + Assertions.assertThat(TouchTargetHelper.findTargetTagForTouch(20f, 50f, root)).isEqualTo(3) + Assertions.assertThat(view.getTag(R.id.accessibility_state_disabled)).isNull() + } + } + + @Test + fun testClearingAccessibilityDisabledPreservesOtherState() { + val state = JavaOnlyMap.of("busy", true, "checked", "mixed") + viewManager.setViewState(view, JavaOnlyMap.of("disabled", true)) + + viewManager.setViewState(view, state) + + val nativeState = view.getTag(R.id.accessibility_state) as ReadableMap + Assertions.assertThat(view.isEnabled).isTrue() + Assertions.assertThat(nativeState.getBoolean("busy")).isTrue() + Assertions.assertThat(nativeState.getString("checked")).isEqualTo("mixed") + Assertions.assertThat(state.hasKey("disabled")).isFalse() + } + + @Test + fun testViewManagerSubclassReenablesViewWhenAccessibilityDisabledIsCleared() { + val subclassManager = object : ReactViewManager() {} + subclassManager.setViewState(view, JavaOnlyMap.of("disabled", true)) + + subclassManager.setViewState(view, JavaOnlyMap.of("busy", true)) + + Assertions.assertThat(view.isEnabled).isTrue() + Assertions.assertThat(view.getTag(R.id.accessibility_state_disabled)).isNull() + } + @Test fun testAccessibilityStateDisabledRemovedReenablesView() { viewManager.setViewState(view, JavaOnlyMap.of("disabled", true)) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index dac9993d5ecc..79d06d709e2a 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -238,6 +238,20 @@ class ReactTextInputPropertyTest { assertThat(view.isEnabled).isTrue } + @Test + fun testClearingAccessibilityDisabledPreservesNonEditableInput() { + for (state in listOf(JavaOnlyMap(), JavaOnlyMap.of("disabled", null), null)) { + manager.updateProperties( + view, + buildStyles("editable", false, "accessibilityState", JavaOnlyMap.of("disabled", true)), + ) + + manager.updateProperties(view, buildStyles("accessibilityState", state)) + + assertThat(view.isEnabled).isFalse() + } + } + @Test fun testAutoCompleteExtendedHints() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {