-
Notifications
You must be signed in to change notification settings - Fork 24.3k
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
Conversation
There was a problem hiding this 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. Runyarn lint --fix
to automatically fix problems.
onAccessibilityEscape={() => alert('onAccessibilityEscape success')} | ||
accessible={true}> | ||
onAccessibilityAction={event => { | ||
if (event.nativeEvent.actionName === 'escape') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no-alert: Unexpected alert.
|
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()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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. Runyarn lint --fix
to automatically fix problems.
onAccessibilityEscape={() => alert('onAccessibilityEscape success')} | ||
accessible={true}> | ||
onAccessibilityAction={event => { | ||
if (event.nativeEvent.actionName === 'escape') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no-alert: Unexpected alert.
There was a problem hiding this 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.
React/Views/RCTView.h
Outdated
@@ -29,7 +29,8 @@ | |||
/** | |||
* Accessibility properties | |||
*/ | |||
@property (nonatomic, copy) NSArray <NSString *> *accessibilityActions; | |||
@property (nonatomic, copy) NSArray <NSDictionary *> *accessibilityActions; | |||
@property (nonatomic, copy) NSDictionary *accessibilityActionsMap; |
There was a problem hiding this comment.
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
React/Views/RCTView.m
Outdated
@@ -99,6 +99,8 @@ - (UIView *)react_findClipView | |||
@implementation RCTView | |||
{ | |||
UIColor *_backgroundColor; | |||
NSMutableDictionary *accessibilityActionsNameMap; | |||
NSMutableDictionary *accessibilityActionsLabelMap; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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:@[]]
React/Views/RCTView.m
Outdated
if (!actions || !actions.count) { | ||
return; | ||
} | ||
_accessibilityActions = [NSMutableArray array]; |
There was a problem hiding this comment.
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.
React/Views/RCTView.m
Outdated
- (NSArray <UIAccessibilityCustomAction *> *)accessibilityCustomActions | ||
{ | ||
if (!_accessibilityActions.count) { | ||
if (!_accessibilityActions || !_accessibilityActions.count) { |
There was a problem hiding this comment.
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
React/Views/RCTView.m
Outdated
} | ||
else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else {
React/Views/RCTView.m
Outdated
if ([self performAccessibilityAction:@"magicTap"]) { | ||
return YES; | ||
} | ||
else if (_onMagicTap) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else if ...
React/Views/RCTView.m
Outdated
_onMagicTap(nil); | ||
return YES; | ||
} else { | ||
} | ||
else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else {
React/Views/RCTView.m
Outdated
_onAccessibilityEscape(nil); | ||
return YES; | ||
} else { | ||
} | ||
else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else {
React/Views/RCTView.m
Outdated
if ([self performAccessibilityAction:@"escape"]) { | ||
return YES; | ||
} | ||
else if (_onAccessibilityEscape) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else if ... {
There was a problem hiding this 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. Runyarn lint --fix
to automatically fix problems.
onAccessibilityEscape={() => alert('onAccessibilityEscape success')} | ||
accessible={true}> | ||
onAccessibilityAction={event => { | ||
if (event.nativeEvent.actionName === 'escape') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no-alert: Unexpected alert.
@shergin, do you know if there is a way to do this? |
@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 |
Do you mean some kinda imperative API (like |
@@ -99,6 +99,8 @@ - (UIView *)react_findClipView | |||
@implementation RCTView | |||
{ | |||
UIColor *_backgroundColor; | |||
NSMutableDictionary<NSString *, NSDictionary *> *accessibilityActionsNameMap; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
…ions support includes 'increment', 'decrement', 'magticTap', 'escape', and 'activate'.
… way to implement this on iOS.
There was a problem hiding this 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. Runyarn lint --fix
to automatically fix problems.
*/ | ||
onAccessibilityAction?: ?(string) => void, | ||
onAccessibilityAction?: ?(event: AccessibilityActionEvent) => void, |
There was a problem hiding this comment.
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. | |||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cannot resolve name AccessibilityActionInfo
.
RNTester/js/AccessibilityExample.js
Outdated
<Text>Slider</Text> | ||
</View> | ||
</RNTesterBlock> | ||
</View> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prettier/prettier: Delete ··
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. |
@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? |
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
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
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.