diff --git a/RNTester/js/examples/Accessibility/AccessibilityExample.js b/RNTester/js/examples/Accessibility/AccessibilityExample.js
index c2fc33a0ae4eb5..38fa2b3cdb2409 100644
--- a/RNTester/js/examples/Accessibility/AccessibilityExample.js
+++ b/RNTester/js/examples/Accessibility/AccessibilityExample.js
@@ -13,18 +13,30 @@ const React = require('react');
const {
AccessibilityInfo,
Button,
+ Image,
Text,
View,
TouchableOpacity,
TouchableWithoutFeedback,
Alert,
- UIManager,
- findNodeHandle,
- Platform,
+ StyleSheet,
} = require('react-native');
const RNTesterBlock = require('../../components/RNTesterBlock');
+const checkImageSource = require('./check.png');
+const uncheckImageSource = require('./uncheck.png');
+const mixedCheckboxImageSource = require('./mixed.png');
+
+const styles = StyleSheet.create({
+ image: {
+ width: 20,
+ height: 20,
+ resizeMode: 'contain',
+ marginRight: 10,
+ },
+});
+
class AccessibilityExample extends React.Component {
render() {
return (
@@ -161,13 +173,6 @@ class CheckboxExample extends React.Component {
this.setState({
checkboxState: checkboxState,
});
-
- if (Platform.OS === 'android') {
- UIManager.sendAccessibilityEvent(
- findNodeHandle(this),
- UIManager.AccessibilityEventTypes.typeViewClicked,
- );
- }
};
render() {
@@ -195,13 +200,6 @@ class SwitchExample extends React.Component {
this.setState({
switchState: switchState,
});
-
- if (Platform.OS === 'android') {
- UIManager.sendAccessibilityEvent(
- findNodeHandle(this),
- UIManager.AccessibilityEventTypes.typeViewClicked,
- );
- }
};
render() {
@@ -252,13 +250,6 @@ class SelectionExample extends React.Component {
isSelected: !this.state.isSelected,
});
}
-
- if (Platform.OS === 'android') {
- UIManager.sendAccessibilityEvent(
- findNodeHandle(this.selectableElement.current),
- UIManager.AccessibilityEventTypes.typeViewClicked,
- );
- }
}}
accessibilityLabel="element 19"
accessibilityState={{
@@ -292,13 +283,6 @@ class ExpandableElementExample extends React.Component {
this.setState({
expandState: expandState,
});
-
- if (Platform.OS === 'android') {
- UIManager.sendAccessibilityEvent(
- findNodeHandle(this),
- UIManager.AccessibilityEventTypes.typeViewClicked,
- );
- }
};
render() {
@@ -314,6 +298,114 @@ class ExpandableElementExample extends React.Component {
}
}
+class NestedCheckBox extends React.Component {
+ state = {
+ checkbox1: false,
+ checkbox2: false,
+ checkbox3: false,
+ };
+
+ _onPress1 = () => {
+ let checkbox1 = false;
+ if (this.state.checkbox1 === false) {
+ checkbox1 = true;
+ } else if (this.state.checkbox1 === 'mixed') {
+ checkbox1 = false;
+ } else {
+ checkbox1 = false;
+ }
+ setTimeout(() => {
+ this.setState({
+ checkbox1: checkbox1,
+ checkbox2: checkbox1,
+ checkbox3: checkbox1,
+ });
+ }, 2000);
+ };
+
+ _onPress2 = () => {
+ const checkbox2 = !this.state.checkbox2;
+
+ this.setState({
+ checkbox2: checkbox2,
+ checkbox1:
+ checkbox2 && this.state.checkbox3
+ ? true
+ : checkbox2 || this.state.checkbox3
+ ? 'mixed'
+ : false,
+ });
+ };
+
+ _onPress3 = () => {
+ const checkbox3 = !this.state.checkbox3;
+
+ this.setState({
+ checkbox3: checkbox3,
+ checkbox1:
+ this.state.checkbox2 && checkbox3
+ ? true
+ : this.state.checkbox2 || checkbox3
+ ? 'mixed'
+ : false,
+ });
+ };
+
+ render() {
+ return (
+
+
+
+ Meat
+
+
+
+ Beef
+
+
+
+ Bacon
+
+
+ );
+ }
+}
+
class AccessibilityRoleAndStateExample extends React.Component<{}> {
render() {
return (
@@ -412,6 +504,9 @@ class AccessibilityRoleAndStateExample extends React.Component<{}> {
+
+
+
);
}
diff --git a/RNTester/js/examples/Accessibility/check.png b/RNTester/js/examples/Accessibility/check.png
new file mode 100644
index 00000000000000..de4c1492b8fd97
Binary files /dev/null and b/RNTester/js/examples/Accessibility/check.png differ
diff --git a/RNTester/js/examples/Accessibility/mixed.png b/RNTester/js/examples/Accessibility/mixed.png
new file mode 100644
index 00000000000000..5f5957ce375ecf
Binary files /dev/null and b/RNTester/js/examples/Accessibility/mixed.png differ
diff --git a/RNTester/js/examples/Accessibility/uncheck.png b/RNTester/js/examples/Accessibility/uncheck.png
new file mode 100644
index 00000000000000..8c967281c590f9
Binary files /dev/null and b/RNTester/js/examples/Accessibility/uncheck.png differ
diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m
index 771caf6084c7f8..0d1084c3771a42 100644
--- a/React/Views/RCTViewManager.m
+++ b/React/Views/RCTViewManager.m
@@ -206,9 +206,13 @@ - (RCTShadowView *)shadowView
}
if (newState.count > 0) {
view.reactAccessibilityElement.accessibilityState = newState;
+ // Post a layout change notification to make sure VoiceOver get notified for the state
+ // changes that don't happen upon users' click.
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
} else {
view.reactAccessibilityElement.accessibilityState = nil;
}
+
}
RCT_CUSTOM_VIEW_PROPERTY(nativeID, NSString *, RCTView)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java
index 9c8b35b8ea5500..65c2fb3e387dd6 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java
@@ -9,6 +9,7 @@
import android.text.TextUtils;
import android.view.View;
import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
@@ -170,6 +171,13 @@ public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilitySta
&& accessibilityState.getType(STATE_CHECKED) == ReadableType.String)) {
updateViewContentDescription(view);
break;
+ } else if (view.isAccessibilityFocused()) {
+ // Internally Talkback ONLY uses TYPE_VIEW_CLICKED for "checked" and
+ // "selected" announcements. Send a click event to make sure Talkback
+ // get notified for the state changes that don't happen upon users' click.
+ // For the state changes that happens immediately, Talkback will skip
+ // the duplicated click event.
+ view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
}
}
}