Skip to content

Commit e4c8247

Browse files
committed
Fix cleared disabled state in Android view handling
1 parent e79c001 commit e4c8247

12 files changed

Lines changed: 167 additions & 109 deletions

File tree

‎packages/react-native/Libraries/Components/Pressable/Pressable.js‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import type {
1515
LayoutChangeEvent,
1616
MouseEvent,
1717
} from '../../Types/CoreEventTypes';
18-
import type {AccessibilityState} from '../View/ViewAccessibility';
1918
import type {ViewProps} from '../View/ViewPropTypes';
2019

2120
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
@@ -249,10 +248,10 @@ function Pressable({
249248
const shouldUpdatePressed =
250249
typeof children === 'function' || typeof style === 'function';
251250

252-
let _accessibilityState: AccessibilityState = {
251+
let _accessibilityState = {
253252
busy: ariaBusy ?? accessibilityState?.busy,
254253
checked: ariaChecked ?? accessibilityState?.checked,
255-
disabled: ariaDisabled ?? accessibilityState?.disabled ?? false,
254+
disabled: ariaDisabled ?? accessibilityState?.disabled,
256255
expanded: ariaExpanded ?? accessibilityState?.expanded,
257256
selected: ariaSelected ?? accessibilityState?.selected,
258257
};

‎packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js‎

Lines changed: 0 additions & 48 deletions
This file was deleted.

‎packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,7 @@ class TouchableOpacity extends React.Component<
191191
checked:
192192
this.props['aria-checked'] ?? this.props.accessibilityState?.checked,
193193
disabled:
194-
this.props['aria-disabled'] ??
195-
this.props.accessibilityState?.disabled ??
196-
false,
194+
this.props['aria-disabled'] ?? this.props.accessibilityState?.disabled,
197195
expanded:
198196
this.props['aria-expanded'] ?? this.props.accessibilityState?.expanded,
199197
selected:

‎packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js‎

Lines changed: 0 additions & 54 deletions
This file was deleted.

‎packages/react-native/ReactAndroid/api/ReactAndroid.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6540,6 +6540,8 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react
65406540
public fun setTVPreferredFocus (Lcom/facebook/react/views/view/ReactViewGroup;Z)V
65416541
public synthetic fun setTransformProperty (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V
65426542
protected fun setTransformProperty (Lcom/facebook/react/views/view/ReactViewGroup;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V
6543+
public synthetic fun setViewState (Landroid/view/View;Lcom/facebook/react/bridge/ReadableMap;)V
6544+
public fun setViewState (Lcom/facebook/react/views/view/ReactViewGroup;Lcom/facebook/react/bridge/ReadableMap;)V
65436545
}
65446546

65456547
public final class com/facebook/react/views/view/ReactViewManager$Companion {

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ public open class ReactViewGroup public constructor(context: Context?) :
182182
*/
183183
private fun initView() {
184184
clipChildren = false
185+
isEnabled = true
185186

186187
_removeClippedSubviews = false
187188
inSubviewClippingLoop = false

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package com.facebook.react.views.view
1010
import android.graphics.Rect
1111
import android.view.View
1212
import com.facebook.common.logging.FLog
13+
import com.facebook.react.bridge.Arguments
1314
import com.facebook.react.bridge.Dynamic
1415
import com.facebook.react.bridge.DynamicFromObject
1516
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
@@ -97,6 +98,26 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
9798
view.isFocusable = accessible
9899
}
99100

101+
@ReactProp(name = ViewProps.ACCESSIBILITY_STATE)
102+
override fun setViewState(view: ReactViewGroup, accessibilityState: ReadableMap?) {
103+
if (
104+
javaClass != ReactViewManager::class.java ||
105+
accessibilityState != null &&
106+
accessibilityState.hasKey("disabled") &&
107+
!accessibilityState.isNull("disabled")
108+
) {
109+
super.setViewState(view, accessibilityState)
110+
return
111+
}
112+
113+
val normalizedState = Arguments.createMap()
114+
if (accessibilityState != null) {
115+
normalizedState.merge(accessibilityState)
116+
}
117+
normalizedState.putBoolean("disabled", false)
118+
super.setViewState(view, normalizedState)
119+
}
120+
100121
@ReactProp(name = ViewProps.ACCESSIBILITY_ORDER)
101122
public open fun setAccessibilityOrder(view: ReactViewGroup, nativeIds: ReadableArray?) {
102123
if (!ReactNativeFeatureFlags.enableAccessibilityOrder()) {

‎packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
package com.facebook.react.uimanager
1111

1212
import android.view.View.OnFocusChangeListener
13+
import android.widget.FrameLayout
1314
import com.facebook.react.R
1415
import com.facebook.react.bridge.BridgeReactContext
1516
import com.facebook.react.bridge.DynamicFromObject
1617
import com.facebook.react.bridge.JavaOnlyArray
1718
import com.facebook.react.bridge.JavaOnlyMap
19+
import com.facebook.react.bridge.ReadableMap
1820
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
1921
import com.facebook.react.views.view.ReactViewGroup
2022
import com.facebook.react.views.view.ReactViewManager
@@ -67,10 +69,59 @@ class BaseViewManagerTest {
6769
val accessibilityState = JavaOnlyMap()
6870
accessibilityState.putBoolean("selected", true)
6971
viewManager.setViewState(view, accessibilityState)
70-
Assertions.assertThat(view.getTag(R.id.accessibility_state)).isEqualTo(accessibilityState)
72+
Assertions.assertThat(view.getTag(R.id.accessibility_state))
73+
.isEqualTo(JavaOnlyMap.of("selected", true, "disabled", false))
7174
Assertions.assertThat(view.isSelected).isEqualTo(true)
7275
}
7376

77+
@Test
78+
fun testClearingAccessibilityDisabledRestoresTapTarget() {
79+
val root = FrameLayout(themedReactContext)
80+
root.id = 1
81+
root.layout(0, 0, 300, 300)
82+
view.id = 3
83+
root.addView(view)
84+
view.layout(0, 0, 200, 100)
85+
86+
for (state in listOf(JavaOnlyMap(), JavaOnlyMap.of("disabled", null), null)) {
87+
viewManager.setViewState(view, JavaOnlyMap.of("disabled", true))
88+
Assertions.assertThat(TouchTargetHelper.findTargetTagForTouch(20f, 50f, root)).isEqualTo(1)
89+
90+
viewManager.setViewState(view, state)
91+
92+
Assertions.assertThat(view.isEnabled).isTrue()
93+
Assertions.assertThat(TouchTargetHelper.findTargetTagForTouch(20f, 50f, root)).isEqualTo(3)
94+
Assertions.assertThat(
95+
(view.getTag(R.id.accessibility_state) as ReadableMap).getBoolean("disabled")
96+
)
97+
.isFalse()
98+
}
99+
}
100+
101+
@Test
102+
fun testClearingAccessibilityDisabledPreservesOtherState() {
103+
val state = JavaOnlyMap.of("busy", true, "checked", "mixed")
104+
viewManager.setViewState(view, JavaOnlyMap.of("disabled", true))
105+
106+
viewManager.setViewState(view, state)
107+
108+
val nativeState = view.getTag(R.id.accessibility_state) as ReadableMap
109+
Assertions.assertThat(view.isEnabled).isTrue()
110+
Assertions.assertThat(nativeState.getBoolean("busy")).isTrue()
111+
Assertions.assertThat(nativeState.getString("checked")).isEqualTo("mixed")
112+
Assertions.assertThat(state.hasKey("disabled")).isFalse()
113+
}
114+
115+
@Test
116+
fun testViewManagerSubclassPreservesDisabledDuringPartialUpdates() {
117+
val subclassManager = object : ReactViewManager() {}
118+
subclassManager.setViewState(view, JavaOnlyMap.of("disabled", true))
119+
120+
subclassManager.setViewState(view, JavaOnlyMap.of("busy", true))
121+
122+
Assertions.assertThat(view.isEnabled).isFalse()
123+
}
124+
74125
@Test
75126
fun testRoleList() {
76127
viewManager.setRole(view, "list")

‎packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,18 @@ class ReactTextInputPropertyTest {
242242
assertThat(view.isEnabled).isTrue
243243
}
244244

245+
@Test
246+
fun testClearingAccessibilityDisabledPreservesNonEditableInput() {
247+
manager.updateProperties(
248+
view,
249+
buildStyles("editable", false, "accessibilityState", JavaOnlyMap.of("disabled", true)),
250+
)
251+
252+
manager.updateProperties(view, buildStyles("accessibilityState", JavaOnlyMap.of("busy", true)))
253+
254+
assertThat(view.isEnabled).isFalse()
255+
}
256+
245257
@Test
246258
fun testAutoCompleteExtendedHints() {
247259
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {

‎packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ class ReactViewGroupTest {
3434
context = Robolectric.buildActivity(Activity::class.java).create().get()
3535
}
3636

37+
@Test
38+
fun `recycling restores enabled state`() {
39+
val view = ReactViewGroup(context)
40+
view.isEnabled = false
41+
42+
view.recycleView()
43+
44+
assertThat(view.isEnabled).isTrue()
45+
}
46+
3747
@Test
3848
fun `View clipping - ensure allChildren properly resizes when adding views in sequence`() {
3949
val rvg = ReactViewGroup(context)

0 commit comments

Comments
 (0)