Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended accessibility actions support #24190

Closed
wants to merge 8 commits into from

Conversation

marcmulcahy
Copy link

Summary

Extend accessibility actions to include both name (non-localized) and label (localized). this lets components provide programmatic access to custom actions, as well as standard actions such as slider adjustment and scrolling. This also means that when applications process actions, they need not compare against a localized name, which will be different for every language.

Changelog

[general] [change] - Extend accessibility actions support.

Test Plan

We have added tests in RNTester which exercise both custom actions, as well as standard actions.

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Amazon Partner: Amazon labels Mar 28, 2019
Copy link

@analysis-bot analysis-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code analysis results:

  • eslint found some issues. Run yarn lint --fix to automatically fix problems.

onAccessibilityEscape={() => alert('onAccessibilityEscape success')}
accessible={true}>
onAccessibilityAction={event => {
if (event.nativeEvent.actionName === 'escape') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no-alert: Unexpected alert.

@pull-bot
Copy link

pull-bot commented Mar 28, 2019

Messages
📖

📋 Changelog Format - Did you include a Changelog? A changelog entry has the following format: [CATEGORY] [TYPE] - Message.

CATEGORY may be:
  • General
  • iOS
  • Android

TYPE may be:

  • Added, for new features.
  • Changed, for changes in existing functionality.
  • Deprecated, for soon-to-be removed features.
  • Removed, for now removed features.
  • Fixed, for any bug fixes.
  • Security, in case of vulnerabilities.

MESSAGE may answer "what and why" on a feature level. Use this to briefly tell React Native users about notable changes.

Generated by 🚫 dangerJS against 4f0bfb7

@marcmulcahy
Copy link
Author

One of the challenges with the current design is the inability for action callback functions to return success or failure. So the implementation assumes that actions always succeed.

This generally works for most types of custom actions, but is problematic for actions like slider adjustment which might not always work.

Instead of specifying actions as a list, we could consider adding API to component to allow actions to be added and removed dynamically. This would allow components to remove actions that aren't expected to work ("increment" when slider is at max, "cut" when no text on clipboard, etc.).

Short of that, is there a way for JavaScript code to return a value to native code?

static {
sActionIdMap.put("activate", AccessibilityActionCompat.ACTION_CLICK.getId());
sActionIdMap.put("increment", AccessibilityActionCompat.ACTION_SCROLL_FORWARD.getId());
sActionIdMap.put("decrement", AccessibilityActionCompat.ACTION_SCROLL_BACKWARD.getId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the idea here that these are common actions that you might want to override the label for? For example instead of saying "Double tap to [activate]" you'd set your own label on the activate action and have it say "Double tap to [custom label]"? If so, I think AccessibilityActionCompat.ACTION_LONG_CLICK should definitely be included on this list as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort of. The purpose of actions isn't to customize the usage hint ("double tap to...").

Android conflates the two ideas together, in that in order to add a usage hint to a component, you add a description to it's click action. And that's how usage hints are hooked up in our proposed React Native implementation of usage hints on Android.

Adding long click to the list of supported standard actions might be a good idea, but there wouldn't be any way to have that action force TalkBack to say something.

The other complication is that because of the way Android implements usage hints, all usage hints start with "double tap to...". On iOS, usage hints are simply strings, so can start with anything.

I suspect adding a "longclick" standard action name makes sense-- I'll look to see what the iOS equivalent would be.

Copy link

@analysis-bot analysis-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code analysis results:

  • eslint found some issues. Run yarn lint --fix to automatically fix problems.

onAccessibilityEscape={() => alert('onAccessibilityEscape success')}
accessible={true}>
onAccessibilityAction={event => {
if (event.nativeEvent.actionName === 'escape') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no-alert: Unexpected alert.

Copy link
Contributor

@ikenwoo ikenwoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks!

Just a few comments regarding type safety and some implied logic that isn't clear from the js side.

@@ -29,7 +29,8 @@
/**
* Accessibility properties
*/
@property (nonatomic, copy) NSArray <NSString *> *accessibilityActions;
@property (nonatomic, copy) NSArray <NSDictionary *> *accessibilityActions;
@property (nonatomic, copy) NSDictionary *accessibilityActionsMap;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add the type information for the NSDictionary

@@ -99,6 +99,8 @@ - (UIView *)react_findClipView
@implementation RCTView
{
UIColor *_backgroundColor;
NSMutableDictionary *accessibilityActionsNameMap;
NSMutableDictionary *accessibilityActionsLabelMap;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

annotate the type of the collections here as well

-(void)setAccessibilityActions:(NSArray *)actions
{
if (!actions || !actions.count) {
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like the dictionaries get emptied out if someone tries to remove actions, e.g. [view setAccessibilityActions:@[]]

if (!actions || !actions.count) {
return;
}
_accessibilityActions = [NSMutableArray array];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the assignment here is not needed since you set this with a copy at L175.

- (NSArray <UIAccessibilityCustomAction *> *)accessibilityCustomActions
{
if (!_accessibilityActions.count) {
if (!_accessibilityActions || !_accessibilityActions.count) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra nil check isn't needed

}
else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else {

if ([self performAccessibilityAction:@"magicTap"]) {
return YES;
}
else if (_onMagicTap) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else if ...

_onMagicTap(nil);
return YES;
} else {
}
else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else {

_onAccessibilityEscape(nil);
return YES;
} else {
}
else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else {

if ([self performAccessibilityAction:@"escape"]) {
return YES;
}
else if (_onAccessibilityEscape) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else if ... {

Copy link

@analysis-bot analysis-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code analysis results:

  • eslint found some issues. Run yarn lint --fix to automatically fix problems.

onAccessibilityEscape={() => alert('onAccessibilityEscape success')}
accessible={true}>
onAccessibilityAction={event => {
if (event.nativeEvent.actionName === 'escape') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no-alert: Unexpected alert.

@blavalla
Copy link
Contributor

blavalla commented Apr 10, 2019

Instead of specifying actions as a list, we could consider adding API to component to allow actions to be added and removed dynamically. This would allow components to remove actions that aren't expected to work ("increment" when slider is at max, "cut" when no text on clipboard, etc.).

Short of that, is there a way for JavaScript code to return a value to native code?

@shergin, do you know if there is a way to do this?

@shergin
Copy link
Contributor

shergin commented Apr 29, 2019

@marcmulcahy Totally unrelated to this diff: Feel free to implement the same for Fabric here: https://github.com/facebook/react-native/blob/master/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm#L522-L570

@shergin
Copy link
Contributor

shergin commented Apr 29, 2019

Instead of specifying actions as a list, we could consider adding API to component to allow actions to be added and removed dynamically. This would allow components to remove actions that aren't expected to work ("increment" when slider is at max, "cut" when no text on clipboard, etc.).

Do you mean some kinda imperative API (like scrollTo(x,y) or .focus())? If so, IMHO, no. It's totally overkill. Props is a the most simple and idiomatic way to deal with it.

@@ -99,6 +99,8 @@ - (UIView *)react_findClipView
@implementation RCTView
{
UIColor *_backgroundColor;
NSMutableDictionary<NSString *, NSDictionary *> *accessibilityActionsNameMap;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand why we need to have two maps here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shergin In this design, actions have a non-localized name and a localized label.

in iOS, custom accessibility actions only have a localized label, which is what is passed to didActivateAccessibilityCustomAction. The label map is used to find the action name to include with the event to JS.

For standard actions like increment, decrement, and activate, we get specific calls from iOS (such as didActivate). We use the name map to validate whether the JS component claims to support that standard action.

Copy link

@analysis-bot analysis-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code analysis results:

  • flow found some issues.
  • eslint found some issues. Run yarn lint --fix to automatically fix problems.

*/
onAccessibilityAction?: ?(string) => void,
onAccessibilityAction?: ?(event: AccessibilityActionEvent) => void,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot resolve name AccessibilityActionEvent.

@@ -417,6 +409,12 @@ export type ViewProps = $ReadOnly<{|
*/
accessibilityStates?: ?AccessibilityStates,

/**
* Provides an array of custom actions available for accessibility.
*

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot resolve name AccessibilityActionInfo.

<Text>Slider</Text>
</View>
</RNTesterBlock>
</View>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prettier/prettier: Delete ··

@marcmulcahy marcmulcahy closed this May 1, 2019
@marcmulcahy
Copy link
Author

This breaks double tap on Android 9.0. closing while I investigate and fix the issue, and will submit a new pull request once finished.

@marcmulcahy
Copy link
Author

@janicduplessis Can you make sure I haven't broken things again. I've moved actions back into RCTView, as we need to keep some class-specific maps around to match action names to labels and vice versa.

If you think UIView+React.* is the right place for actions, could you suggest how setAccessibilityActions (and the associated actionsN ameMNap and actionsLabelMap) could be factored out of RCTView?

facebook-github-bot pushed a commit that referenced this pull request May 20, 2019
Summary:
This is a reconstitution of #24190. It extends accessibility actions to include both a name and user facing label. These extensions support both standard and custom actions.

We've also added actions support on Android, and added examples to RNTester showing how both standard and custom accessibility actions are used.

## Changelog

[general] [changed] - Enhanced accessibility actions support
Pull Request resolved: #24695

Differential Revision: D15391408

Pulled By: cpojer

fbshipit-source-id: 5ed48004d46d9887da53baea7fdcd0e7e15c5739
M-i-k-e-l pushed a commit to M-i-k-e-l/react-native that referenced this pull request Mar 10, 2020
Summary:
This is a reconstitution of facebook#24190. It extends accessibility actions to include both a name and user facing label. These extensions support both standard and custom actions.

We've also added actions support on Android, and added examples to RNTester showing how both standard and custom accessibility actions are used.

## Changelog

[general] [changed] - Enhanced accessibility actions support
Pull Request resolved: facebook#24695

Differential Revision: D15391408

Pulled By: cpojer

fbshipit-source-id: 5ed48004d46d9887da53baea7fdcd0e7e15c5739
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Amazon Partner: Amazon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants