Skip to content

Commit 5822628

Browse files
committed
Fix Android not resetting disabled state
1 parent a7f81c8 commit 5822628

4 files changed

Lines changed: 104 additions & 2 deletions

File tree

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ public BaseViewManager(@Nullable ReactApplicationContext reactContext) {
8888
view.setTag(R.id.accessibility_actions, null);
8989
view.setTag(R.id.accessibility_value, null);
9090
view.setTag(R.id.accessibility_state_expanded, null);
91+
view.setTag(R.id.accessibility_state_disabled, null);
92+
view.setEnabled(true);
9193
view.setTag(R.id.view_clipped, null);
9294

9395
// This indirectly calls (and resets):
@@ -364,6 +366,7 @@ public void setAccessibilityCollectionItem(
364366
@ReactProp(name = ViewProps.ACCESSIBILITY_STATE)
365367
public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilityState) {
366368
if (accessibilityState == null) {
369+
resetDisabledFromAccessibilityState(view);
367370
return;
368371
}
369372
if (accessibilityState.hasKey("expanded")) {
@@ -385,8 +388,12 @@ public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilitySta
385388
view.setSelected(false);
386389
}
387390
view.setTag(R.id.accessibility_state, accessibilityState);
388-
if (accessibilityState.hasKey("disabled")) {
389-
view.setEnabled(!accessibilityState.getBoolean("disabled"));
391+
if (accessibilityState.hasKey("disabled") && !accessibilityState.isNull("disabled")) {
392+
boolean disabled = accessibilityState.getBoolean("disabled");
393+
view.setEnabled(!disabled);
394+
view.setTag(R.id.accessibility_state_disabled, disabled);
395+
} else {
396+
resetDisabledFromAccessibilityState(view);
390397
}
391398

392399
// For states which don't have corresponding methods in
@@ -413,6 +420,13 @@ public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilitySta
413420
}
414421
}
415422

423+
private static void resetDisabledFromAccessibilityState(@NonNull View view) {
424+
if (Boolean.TRUE.equals(view.getTag(R.id.accessibility_state_disabled))) {
425+
view.setEnabled(true);
426+
}
427+
view.setTag(R.id.accessibility_state_disabled, null);
428+
}
429+
416430
private void updateViewContentDescription(@NonNull T view) {
417431
final String accessibilityLabel = (String) view.getTag(R.id.accessibility_label);
418432
final ReadableMap accessibilityState = (ReadableMap) view.getTag(R.id.accessibility_state);

‎packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
<!--tag is used to store accessibilityStateExpanded -->
3737
<item type="id" name="accessibility_state_expanded"/>
3838

39+
<!--tag is used to store whether accessibilityState.disabled disabled the view -->
40+
<item type="id" name="accessibility_state_disabled"/>
41+
3942
<!--tag is used to store accessibilityLabel tag-->
4043
<item type="id" name="accessibility_label"/>
4144

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,38 @@ class BaseViewManagerTest {
7171
Assertions.assertThat(view.isSelected).isEqualTo(true)
7272
}
7373

74+
@Test
75+
fun testAccessibilityStateDisabledRemovedReenablesView() {
76+
viewManager.setViewState(view, JavaOnlyMap.of("disabled", true))
77+
Assertions.assertThat(view.isEnabled).isFalse()
78+
79+
viewManager.setViewState(view, JavaOnlyMap())
80+
Assertions.assertThat(view.isEnabled).isTrue()
81+
}
82+
83+
@Test
84+
fun testAccessibilityStateNullReenablesView() {
85+
viewManager.setViewState(view, JavaOnlyMap.of("disabled", true))
86+
viewManager.setViewState(view, null)
87+
Assertions.assertThat(view.isEnabled).isTrue()
88+
}
89+
90+
@Test
91+
fun testAccessibilityStateDisabledNullDoesNotCrash() {
92+
viewManager.setViewState(view, JavaOnlyMap.of("disabled", true))
93+
viewManager.setViewState(view, JavaOnlyMap.of("disabled", null))
94+
Assertions.assertThat(view.isEnabled).isTrue()
95+
}
96+
97+
@Test
98+
fun testAccessibilityStateWithoutDisabledKeepsViewDisabledByOtherProps() {
99+
view.isEnabled = false
100+
viewManager.setViewState(view, JavaOnlyMap())
101+
Assertions.assertThat(view.isEnabled).isFalse()
102+
viewManager.setViewState(view, null)
103+
Assertions.assertThat(view.isEnabled).isFalse()
104+
}
105+
74106
@Test
75107
fun testRoleList() {
76108
viewManager.setRole(view, "list")

‎packages/rn-tester/js/examples/Pressable/PressableExample.js‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,17 @@ function PressableNativeMethods() {
255255
);
256256
}
257257

258+
const DISABLED_OPTIONS: Array<{label: string, value: ?boolean}> = [
259+
{label: 'true', value: true},
260+
{label: 'false', value: false},
261+
{label: 'undefined', value: undefined},
262+
{label: 'null', value: null},
263+
];
264+
258265
function PressableDisabled() {
266+
const [disabled, setDisabled] = useState<?boolean>(true);
267+
const [timesPressed, setTimesPressed] = useState(0);
268+
259269
return (
260270
<>
261271
<Pressable disabled={true} style={[styles.row, styles.block]}>
@@ -271,6 +281,33 @@ function PressableDisabled() {
271281
]}>
272282
<Text style={styles.button}>Enabled Pressable</Text>
273283
</Pressable>
284+
285+
<View style={[styles.row, styles.block]}>
286+
{DISABLED_OPTIONS.map(option => (
287+
<Pressable
288+
key={option.label}
289+
onPress={() => setDisabled(option.value)}
290+
style={[
291+
styles.disabledOption,
292+
disabled === option.value && styles.disabledOptionSelected,
293+
]}>
294+
<Text>{option.label}</Text>
295+
</Pressable>
296+
))}
297+
</View>
298+
<Pressable
299+
disabled={disabled}
300+
onPress={() => setTimesPressed(current => current + 1)}
301+
style={({pressed}) => [
302+
{opacity: pressed ? 0.5 : 1},
303+
styles.row,
304+
styles.block,
305+
styles.disabledToggleTarget,
306+
]}>
307+
<Text style={disabled === true ? styles.disabledButton : styles.button}>
308+
{`disabled={${String(disabled)}}`} pressed {timesPressed} times
309+
</Text>
310+
</Pressable>
274311
</>
275312
);
276313
}
@@ -296,6 +333,22 @@ const styles = StyleSheet.create({
296333
color: '#007AFF',
297334
opacity: 0.5,
298335
},
336+
disabledOption: {
337+
paddingHorizontal: 8,
338+
paddingVertical: 4,
339+
marginHorizontal: 4,
340+
borderRadius: 4,
341+
borderWidth: StyleSheet.hairlineWidth,
342+
borderColor: '#007AFF',
343+
},
344+
disabledOptionSelected: {
345+
backgroundColor: '#cce4ff',
346+
},
347+
disabledToggleTarget: {
348+
padding: 20,
349+
borderWidth: StyleSheet.hairlineWidth,
350+
borderColor: '#007AFF',
351+
},
299352
hitSlopButton: {
300353
color: 'white',
301354
},

0 commit comments

Comments
 (0)