-
-
Save louy/6b66c45ae47bb4e3bac5a104dd0649ff to your computer and use it in GitHub Desktop.
| /** | |
| * @author Louay Alakkad (github.com/louy) | |
| * @license MIT https://opensource.org/licenses/MIT | |
| */ | |
| import React from 'react' | |
| import PropTypes from 'prop-types' | |
| import { | |
| NativeModules, | |
| ViewProps, | |
| ViewPropTypes, | |
| findNodeHandle, | |
| requireNativeComponent, | |
| Platform, | |
| View | |
| } from 'react-native' | |
| const { RNAccessibilityWrapperManager } = NativeModules | |
| const RNAccessibilityWrapper = requireNativeComponent( | |
| 'RNAccessibilityWrapper' | |
| ) as React.ComponentClass<any> | |
| interface AccessibilityWrapperProps extends ViewProps { | |
| fieldsRefs?: React.RefObject<React.Component>[] | |
| } | |
| const AccessibilityWrapperPropTypes = { | |
| ...ViewPropTypes, | |
| fieldsRefs: PropTypes.arrayOf(PropTypes.shape({ | |
| current: PropTypes.object | |
| }) as PropTypes.Validator<React.RefObject<React.Component>>) | |
| } | |
| class AccessibilityWrapperIOS extends React.Component< | |
| AccessibilityWrapperProps | |
| > { | |
| public static propTypes = AccessibilityWrapperPropTypes | |
| private ref = React.createRef<React.Component<any>>() | |
| public componentDidMount() { | |
| if (this.props.fieldsRefs) { | |
| this.setAccessibilityFields(this.props.fieldsRefs.map(ref => ref.current)) | |
| } | |
| } | |
| public componentDidUpdate() { | |
| if (this.props.fieldsRefs) { | |
| this.setAccessibilityFields(this.props.fieldsRefs.map(ref => ref.current)) | |
| } | |
| } | |
| private setAccessibilityFields = ( | |
| fields: (React.Component<any> | null)[] | |
| ) => { | |
| const fieldTags = | |
| fields && fields.map(component => component && findNodeHandle(component)) | |
| return RNAccessibilityWrapperManager.setAccessibilityFields( | |
| findNodeHandle(this.ref.current), | |
| fieldTags | |
| ) | |
| } | |
| public render() { | |
| return <RNAccessibilityWrapper {...this.props} ref={this.ref} /> | |
| } | |
| } | |
| const AccessibilityWrapperAndroid: React.FunctionComponent< | |
| AccessibilityWrapperProps | |
| > = ({ fieldsRefs, ...props }) => <View {...props} /> | |
| AccessibilityWrapperAndroid.propTypes = AccessibilityWrapperPropTypes | |
| /** | |
| * The AccessibilityWrapper component allows you to adjust the behaviour of the native platform | |
| * when it comes to accessibility. Using this component you can tell the native platform to | |
| * group all subviews together for accessibility purposes (since it's not always done by | |
| * default), and you can even override the focus order of subviews | |
| * | |
| * @example | |
| * export default class App extends Component<{}> { | |
| * fooRef = React.createRef<Text>(); | |
| * barRef = React.createRef<Text>(); | |
| * bazRef = React.createRef<Text>(); | |
| * | |
| * public render() { | |
| * return ( | |
| * <AccessibilityWrapper fieldsRefs={[ | |
| * this.barRef, | |
| * this.fooRef, | |
| * this.bazRef, | |
| * ]}> | |
| * <SafeAreaView> | |
| * <Text ref={this.fooRef}>Foo</Text> | |
| * <Text ref={this.barRef}>Bar</Text> | |
| * <Text ref={this.bazRef}>Baz</Text> | |
| * </SafeAreaView> | |
| * </AccessibilityWrapper> | |
| * ); | |
| * } | |
| * } | |
| */ | |
| export default Platform.select<React.ComponentType<AccessibilityWrapperProps>>({ | |
| ios: AccessibilityWrapperIOS as React.ComponentType< | |
| AccessibilityWrapperProps | |
| >, | |
| android: AccessibilityWrapperAndroid as React.ComponentType< | |
| AccessibilityWrapperProps | |
| > | |
| }) |
| // | |
| // RNAccessibilityWrapper.h | |
| // | |
| // Created by Louay Alakkad on 10/04/2019. | |
| // License: MIT https://opensource.org/licenses/MIT | |
| // | |
| #import <UIKit/UIKit.h> | |
| #import <UIKit/UIAccessibilityContainer.h> | |
| #import <React/RCTView.h> | |
| @interface RNAccessibilityWrapper : RCTView | |
| - (void) setAccessibilityFields: (NSArray *)reactTags; | |
| @end |
| // | |
| // RNAccessibilityWrapper.m | |
| // | |
| // Created by Louay Alakkad on 10/04/2019. | |
| // License: MIT https://opensource.org/licenses/MIT | |
| // | |
| #import <Foundation/Foundation.h> | |
| #import "RNAccessibilityWrapper.h" | |
| #import <UIKit/UIKit.h> | |
| @implementation RNAccessibilityWrapper | |
| - (void) setAccessibilityFields: (NSArray *)fields | |
| { | |
| NSMutableArray *accessibleElements = [NSMutableArray arrayWithCapacity:[fields count]]; | |
| [fields enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) { | |
| UIView *field = obj; | |
| [accessibleElements addObject:field]; | |
| }]; | |
| self.accessibilityElements = (NSArray *)accessibleElements; | |
| } | |
| - (bool) shouldGroupAccessibilityChildren { | |
| return YES; | |
| } | |
| @end |
| // | |
| // RNAccessibilityViewManager | |
| // | |
| // Created by Louay Alakkad on 10/04/2019. | |
| // License: MIT https://opensource.org/licenses/MIT | |
| // | |
| #import <React/RCTViewManager.h> | |
| @interface RNAccessibilityWrapperManager : RCTViewManager | |
| @end |
| // | |
| // RNAccessibilityWrapper.m | |
| // | |
| // Created by Louay Alakkad on 10/04/2019. | |
| // License: MIT https://opensource.org/licenses/MIT | |
| // | |
| #import <Foundation/Foundation.h> | |
| #import <React/RCTUIManager.h> | |
| #import "RNAccessibilityWrapper.h" | |
| #import "RNAccessibilityWrapperManager.h" | |
| @implementation RNAccessibilityWrapperManager | |
| RCT_EXPORT_MODULE() | |
| - (UIView *)view { | |
| return [[RNAccessibilityWrapper alloc] init]; | |
| } | |
| RCT_EXPORT_METHOD(setAccessibilityFields:(nonnull NSNumber *)reactTag | |
| fieldsReactTags: (nonnull NSArray *)fieldsReactTags) { | |
| dispatch_async(dispatch_get_main_queue(), ^{ | |
| RNAccessibilityWrapper *component = (RNAccessibilityWrapper *)[self.bridge.uiManager viewForReactTag:reactTag]; | |
| NSMutableArray *fields = [NSMutableArray arrayWithCapacity:[fieldsReactTags count]]; | |
| [fieldsReactTags enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) { | |
| NSNumber *tag = (NSNumber *)obj; | |
| UIView *field = [self.bridge.uiManager viewForReactTag:tag]; | |
| [fields addObject:field]; | |
| }]; | |
| [component setAccessibilityFields: fields]; | |
| }); | |
| } | |
| @end |
@rahulpunchh I'm no longer maintaining this I'm afraid, so I can't help, but you can try asking on stackoverflow or somewhere similar
Hi, tnx for your work man. Probably it's why I can fix bugs.
I wrote a lib, it can be usefull I guess https://www.npmjs.com/package/react-native-a11y (NewArch, Android, iOs ) supported
Hi, tnx for your work man. Probably it's why I can fix bugs. I wrote a lib, it can be usefull I guess https://www.npmjs.com/package/react-native-a11y (NewArch, Android, iOs ) supported
Nice @ArturKalach !! Will check this out. Focus order has long been a pain point with RN and some layouts.
Hello everyone,
I have finished working on an "advanced" ordering system for React Native:
- https://github.com/ArturKalach/react-native-a11y-order
- https://www.npmjs.com/package/react-native-a11y-order
| Ordering | Grouping |
|---|---|
| |
|
It has some nuances, but honestly, it works really well!
API is really simple, we could control the order by web way via indexes.
<A11y.Order>
<A11y.Index index={1}>
<Text style={styles.font}>
First
</Text>
</A11y.Index>
<A11y.Index index={3}>
<Text style={styles.font}>
Third
</Text>
</A11y.Index>
<A11y.Index index={2}>
<Text style={styles.font}>
Second
</Text>
</A11y.Index>
</A11y.Order>NO ScrollView grouping
Additionally, there's no need to group content using ScrollView for horizontal scrolls anymore. A11y.Group functions almost like a bare View, but, it achieves the same effect
@ArturKalach Nice work! Will definitely implement it in my projects. Have needed this for a long time.
@ArturKalach Nice work! Will definitely implement it in my projects. Have needed this for a long time.
@AdamGerthel
Thanks a lot! I spent weeks finishing this, and your words show that it wasn't a waste of time)
The native and new architecture are really tricky, but it's been a good experience. There is still some work to be done, but the API is complete. There's still a lot of work left, but I really enjoy the results.
P.S. I think this page and the work of @louy were a starting point.
Android didn't have voice over controls when I did this, but things might have changed now, IDK sadly