You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 87e0cb4
Browse filesBrowse the repository at this point in the historyBrowse files
Fix View accessibility payloads that Props 2.0 sends wrong or drops (#59079)
Summary:
With Props 2.0 on Android (`enableExclusivePropsUpdateAndroid` and the related flags), `BaseViewManager` receives the View payload from `getDiffProps`. Some accessibility props come out wrong:
- Changing one member of `accessibilityState`, such as `busy`: only that member is sent, and Java treats the map as the whole state, so a selected, disabled view becomes unselected and enabled.
- A removed `accessibilityRole` or `role`: not cleared.
- An explicit `role="none"`: not sent on mount, so the view falls back to `accessibilityRole`.
- A removed `accessibilityValue`: sent as `{}`, so the old value stays in the content description.
This PR sends `accessibilityState` in full, clears a removed role or value, and makes `role` optional so that unset and `"none"` differ. Nothing changes on Android with the flags off. iOS shares the props constructor, so a removed role is now also cleared in its C++ props. I didn't test iOS.
## Changelog:
[ANDROID] [FIXED] - Fix accessibilityState, a removed accessibilityRole, role or accessibilityValue, and role="none" with Props 2.0
Pull Request resolved: #59079
Test Plan:
- New `ViewPropsDiffTest` cases, using the payload helper from #59077. Each test fails on main. Built with the NDK and run on an Android emulator (there is no open-source gtest target).
- RNTester on an Android emulator with the Props 2.0 flags forced on: the Playground screen below, checked with `uiautomator dump`. Setting `busy` keeps the view selected and disabled. Removing the props:
| | Flags off | Main, Props 2.0 | This PR, Props 2.0 |
|---|---|---|---|
| `role` removed | `android.view.ViewGroup` | `android.widget.Button` | `android.view.ViewGroup` |
| `accessibilityRole` removed | `android.view.ViewGroup` | `android.widget.Button` | `android.view.ViewGroup` |
| `accessibilityValue` removed | content description cleared | `"value target, 50%"` | content description cleared |
| `role="none"` + `accessibilityRole="button"` | `android.view.View` | `android.widget.Button` | `android.view.View` |
<details><summary><code>RNTesterPlayground.js</code>: paste into <code>packages/rn-tester/js/examples/Playground/RNTesterPlayground.js</code> and open Playground.</summary>
```js
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* flow strict-local
* format
*/
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import * as React from 'react';
import {useState} from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';
function A11yState() {
const [busy, setBusy] = useState(false);
return (
<View>
<Button
testID="toggle"
title={busy ? 'Set busy: false' : 'Set busy: true'}
onPress={() => setBusy(v => !v)}
/>
<Text testID="state">
accessibilityState: selected true, disabled true, busy {String(busy)}
</Text>
<View style={styles.row}>
<View
accessible={true}
accessibilityLabel="a11y target"
testID="a11yTarget"
accessibilityState={{selected: true, disabled: true, busy}}
style={styles.card}
/>
</View>
</View>
);
}
function A11y() {
const [on, setOn] = useState(true);
return (
<View>
<Button
testID="toggle"
title={on ? 'Remove role, accessibilityRole, value' : 'Set them'}
onPress={() => setOn(v => !v)}
/>
<Text testID="state">
{on ? 'role/accessibilityRole button, value 50%' : 'all unset'}
</Text>
<View style={styles.row}>
<View
accessible={true}
accessibilityLabel="role target"
role={on ? 'button' : undefined}
style={styles.chip}
/>
<View
accessible={true}
accessibilityLabel="accessibilityRole target"
accessibilityRole={on ? 'button' : undefined}
style={styles.chip}
/>
<View
accessible={true}
accessibilityLabel="value target"
accessibilityValue={on ? {text: '50%'} : undefined}
style={styles.chip}
/>
<View
accessible={true}
accessibilityLabel="none target"
role="none"
accessibilityRole="button"
style={styles.chip}
/>
</View>
</View>
);
}
const demos = {
a11yState: A11yState,
a11y: A11y,
};
function Playground() {
const [demo, setDemo] = useState<$Keys<typeof demos>>('a11yState');
const Demo = demos[demo];
return (
<View style={styles.container}>
<View style={styles.tabs}>
{Object.keys(demos).map(name => (
<Button
key={name}
testID={'tab-' + name}
title={name}
onPress={() => setDemo(name)}
/>
))}
</View>
<Demo key={demo} />
</View>
);
}
const styles = StyleSheet.create({
card: {
alignItems: 'center',
backgroundColor: '#1e88e5',
height: 140,
justifyContent: 'center',
width: 200,
},
chip: {backgroundColor: '#90caf9', height: 48, margin: 8, width: 48},
container: {padding: 10},
row: {
alignItems: 'center',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
marginTop: 24,
},
tabs: {flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginBottom: 12},
});
export default {
title: 'Playground',
name: 'playground',
description: 'Test out new features and ideas.',
render: (): React.Node => <Playground />,
} as RNTesterModuleExample;
```
</details>
Differential Revision: D125438453
Pulled By: bartlomiejbloniarz
Copy file name to clipboardExpand all lines: packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp
0 commit comments