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 f32bc44be2d58f..ee6ab32496e6fc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -79,6 +79,7 @@ protected T prepareToRecycleView(@NonNull ThemedReactContext reactContext, T vie view.setTag(R.id.accessibility_state, null); view.setTag(R.id.accessibility_actions, null); view.setTag(R.id.accessibility_value, null); + view.setTag(R.id.accessibility_state_expanded, null); // This indirectly calls (and resets): // setTranslationX @@ -270,6 +271,9 @@ public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilitySta if (accessibilityState == null) { return; } + if (accessibilityState.hasKey("expanded")) { + view.setTag(R.id.accessibility_state_expanded, accessibilityState.getBoolean("expanded")); + } if (accessibilityState.hasKey("selected")) { boolean prevSelected = view.isSelected(); boolean nextSelected = accessibilityState.getBoolean("selected"); @@ -335,13 +339,6 @@ private void updateViewContentDescription(@NonNull T view) { && value.getType() == ReadableType.Boolean && value.asBoolean()) { contentDescription.add(view.getContext().getString(R.string.state_busy_description)); - } else if (state.equals(STATE_EXPANDED) && value.getType() == ReadableType.Boolean) { - contentDescription.add( - view.getContext() - .getString( - value.asBoolean() - ? R.string.state_expanded_description - : R.string.state_collapsed_description)); } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java index 1ec4a23f73fb0e..e6ef3c837975c9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java @@ -67,6 +67,8 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { sActionIdMap.put("longpress", AccessibilityActionCompat.ACTION_LONG_CLICK.getId()); sActionIdMap.put("increment", AccessibilityActionCompat.ACTION_SCROLL_FORWARD.getId()); sActionIdMap.put("decrement", AccessibilityActionCompat.ACTION_SCROLL_BACKWARD.getId()); + sActionIdMap.put("expand", AccessibilityActionCompat.ACTION_EXPAND.getId()); + sActionIdMap.put("collapse", AccessibilityActionCompat.ACTION_COLLAPSE.getId()); } private final View mView; @@ -250,6 +252,14 @@ public void handleMessage(Message msg) { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { super.onInitializeAccessibilityNodeInfo(host, info); + if (host.getTag(R.id.accessibility_state_expanded) != null) { + final boolean accessibilityStateExpanded = + (boolean) host.getTag(R.id.accessibility_state_expanded); + info.addAction( + accessibilityStateExpanded + ? AccessibilityNodeInfoCompat.ACTION_COLLAPSE + : AccessibilityNodeInfoCompat.ACTION_EXPAND); + } final AccessibilityRole accessibilityRole = (AccessibilityRole) host.getTag(R.id.accessibility_role); final String accessibilityHint = (String) host.getTag(R.id.accessibility_hint); @@ -380,6 +390,12 @@ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) @Override public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (action == AccessibilityNodeInfoCompat.ACTION_COLLAPSE) { + host.setTag(R.id.accessibility_state_expanded, false); + } + if (action == AccessibilityNodeInfoCompat.ACTION_EXPAND) { + host.setTag(R.id.accessibility_state_expanded, true); + } if (mAccessibilityActionsMap.containsKey(action)) { final WritableMap event = Arguments.createMap(); event.putString("actionName", mAccessibilityActionsMap.get(action)); diff --git a/ReactAndroid/src/main/res/views/uimanager/values/ids.xml b/ReactAndroid/src/main/res/views/uimanager/values/ids.xml index 998cb3e222caca..6324b85af44673 100644 --- a/ReactAndroid/src/main/res/views/uimanager/values/ids.xml +++ b/ReactAndroid/src/main/res/views/uimanager/values/ids.xml @@ -24,7 +24,10 @@ - + + + + diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js index d5caca95b71530..f80f587e547e05 100644 --- a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js +++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js @@ -70,6 +70,11 @@ const styles = StyleSheet.create({ flexDirection: 'column', justifyContent: 'space-between', }, + button: { + padding: 8, + borderWidth: 1, + borderColor: 'blue', + }, container: { flex: 1, }, @@ -1431,10 +1436,74 @@ function DisplayOptionStatusExample({ ); } +function AccessibilityExpandedExample(): React.Node { + const [expand, setExpanded] = React.useState(false); + const [pressed, setPressed] = React.useState(false); + const expandAction = {name: 'expand'}; + const collapseAction = {name: 'collapse'}; + return ( + <> + + + The following component announces expanded/collapsed state correctly + +