0.4.5 • Published 15 days ago

react-native-a11y v0.4.5

Weekly downloads
-
License
MIT
Repository
github
Last release
15 days ago

React Native A11y

This is a React Native A11y Library with following main features

  • 🤖 Reader features: Focus, Order, Reader
  • ⌨️ Keyboard features: Focus
  • 🙌 Others features soon
  • ⚡️ The New Arch support
iOS readerAndroid reader
iOS KeyboardAndroid Keyboard

A11y is important, there are a lot of reasons to support and be compliant with it. First of all, it helps people with disabilities work and use your application easily and live a better life. Banks, medication, shops, and delivery is a small list of what people are usually interested in, and it can be more important for people with limitations.

There are can be other reasons, customer requirements, laws and requirements for specific groups of apps, remote control, etc. Based on this you can find a lot of advantages and benefits to supporting A11y.

Installation

This library is not finished yer and currently on beta stage. We will be glad to issues, questions, and help.

  1. Download package with npm or yarn
npm i react-native-a11y
yarn add react-native-a11y
  1. Install pods cd ios && pod install

  2. Android only

Add to the MainActivity.java lines:

  //android/app/src/main/java/com/project-name/MainActivity.java

  ...
  import android.content.Intent;
  import android.content.res.Configuration;
  ...
  
  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    Intent intent = new Intent("onConfigurationChanged");
    intent.putExtra("newConfig", newConfig);
    this.sendBroadcast(intent);
  }
  1. iOS only Link keyboard(Game) binary with libraries
  • Open xcode
  • Select folder in the project bar
  • Select target project
  • Select Build Phases
  • Expand Link Binary With Libraries
  • Press plus icon
  • You can search for Game
  • Select GameController.framework, GameKit.framework, GameplayKit.framework

See screenshot below:

Hope that we will found solution for work around, or create separate library for work with keyboard.

  1. Add provider to root of your app:
watch: examples/A11ySample/App.tsx

export const App = () => {
  return (
    <A11yProvider>
        // content here
    </A11yProvider>
  );
};

Usage

A11y library consists of different components and hooks, to start work with react-native-a11y you can get familiar with an example app in examples/A11ySample.

A11yModule

The core of the library is A11yModule, A11yModule provides additional functions to work with a11y such as order, reader focus, keyboard focus, announcements, etc

FunctionDescriptionInterface
isA11yReaderEnabledreturn promise with status of a11y reader (TalkBack or VoiceOver) true(enabled)/false(disabled)() => Promise<boolean>
isKeyboardConnectedreturn promise with status of keyboard connection, true(connected)/false(disconnected)() => Promise<boolean>
a11yStatusListenerlistener for a11y reader status((e: { status: boolean }) => void) => void;
keyboardStatusListenerlistener for keyboard connection status((e: { status: boolean }) => void) => void;
announceForAccessibilityPost a string to be announced by the screen reader. Android default, ios specific.(announcement: string) => void;
announceScreenChangeAnnounces new screen name.(announcement: string) => void;
setA11yFocusSet a11y reader focus to the component(ref: React.RefObject<React.Component>) => void;
setKeyboardFocusSet keyboard focus to the component(ref: React.RefObject<React.Component>) => void;
setPreferredKeyboardFocusiOS only, set redirection of keyboard from one component to another one(nativeTag: number, nextTag: number) => void;
focusFirstInteractiveElementFocus first interactive element on a screen(ref: React.RefObject<React.Component>) => void;
setA11yElementsOrderSet a11y reader focus ordersetA11yElementsOrder: <T>(info: { tag?: RefObject<T>; views: T[]; }) => void;

setA11yFocus and setKeyboardFocus works similar to AccessibilityInfo.setAccessibilityFocus, difference is they request refs instead of tags and you don't need use findNodeHandle.

Examples

setA11yFocus

watch: examples/A11ySample/src/screens/ReaderFocusScreen

import { A11yModule } from "react-native-a11y";
...

const App = () => {
  const ref1 = useRef(null);
  const ref3 = useRef(null);
...

return (
 ...
 <Button
    ref={ref1}
    onPress={() => A11yModule.setA11yFocus(ref3)}
    title="1. Set focus to third"
 />
 ...
  <Button
    ref={ref3}
    onPress={() => A11yModule.setA11yFocus(ref2)}
    title="3. Set focus to second"
  />
}

setKeyboardFocus

watch: examples/A11ySample/src/screens/KeyboardFocusScreen

import { A11yModule, KeyboardProvider } from "react-native-a11y";
...

const App = () => {
  const ref1 = useRef(null);
  const ref3 = useRef(null);
...

return (
 ...
 <Button
    ref={ref1}
    onPress={() => A11yModule.setKeyboardFocus(ref3)}
    title="1. Set focus to third"
 />
 ...
  <Button
    ref={ref3}
    onPress={() => A11yModule.setKeyboardFocus(ref2)}
    title="3. Set focus to second"
  />
}

You can but not really need to use A11yModule.setA11yElementsOrder directly, we have specific useful hooks to work with order.

useFocusOrder and useDynamicFocusOrder

To set an order for components we have useFocusOrder, useDynamicFocusOrder

A11yModule.setA11yElementsOrder is a more direct one we just pass refs to components and set order, but there are a lot of questions about when to call and set order. To make a better experience two similar hooks were created useFocusOrder and useDynamicFocusOrder

useDynamicFocusOrder

useDynamicFocusOrder returns target ref, trigger function, and function to register your components.

export type UseDynamicFocusOrder = () => {
  a11yOrder: { 
    ref: RefObject<View>; /// target ref, we need a target ref to a container View
    onLayout: () => void; // trigger, we use onLayout to realize when components are appear on a screen, useEffect and useLayoutEffect don't work properly
  };
  registerOrder: (order: number) => (ref: View) => void; // function to set order
  reset: () => void; // clear function
};
soon

useFocusOrder

useFocusOrder is based on useDynamicFocusOrder but more static and predictable.

(size: number) // count of refs
    => FocusOrderInfo<T> = {
  a11yOrder: { 
    ref: RefObject<T>; // target ref, we need a target ref to a container View
    onLayout: () => void; // trigger, we use onLayout to realize when components are appear on a screen, useEffect and useLayoutEffect don't work properly
  };
  refs: ((ref: T | null) => void)[]; // array of callback refs to use for order 
  reset: () => void; // clear function
};
watch: examples/A11ySample/src/screens/A11yOrderScreen
 
import { A11yOrder, useFocusOrder } from "react-native-a11y";

const App = () => {
    const { a11yOrder, refs } = useFocusOrder(3); // 3 number of wanted refs
    ...
    return (
        <A11yOrder onLayout={onLayoutHandler} a11yOrder=  {a11yOrder}>
            <Text style={styles.font} ref={refs[0]}>
                First
            </Text>
            <Text style={styles.font} ref={refs[2]}>
                Third
            </Text>
            <Text style={styles.font} ref={refs[1]}>
                Second
            </Text>
        </A11yOrder>
    )

The code in example set a new order for components, instead of a direct one it will follow 1 -> 3 -> 2

You also can find a new A11yOrder component it's just shorts for <View {...a11yOrder} />

KeyboardFocusView

KeyboardFocusView is view based component, has additional props and provide possibility to make component focusable by a keyboard. Additionally, you can handle pressing events from keyboard. This system can help to handle Enter press or long press on spacebar.

PropsDescription
onFocusChange?Event to handle focus change, (e: event.nativeEvent.isFocused) => void
canBeFocused?boolean default true, describe whether component can be focused by keyboard
onKeyDownPress?Event to handle a keyboard key down event, (e: OnKeyPress) => void
onKeyUpPress?Event to handle a keyboard key up event(e: OnKeyPress) => void

Where OnKeyPress is:

type OnKeyPress = NativeSyntheticEvent<{
  keyCode: number;
  unicode: number;
  unicodeChar: string;
  isLongPress: boolean;
  isAltPressed: boolean;
  isShiftPressed: boolean;
  isCtrlPressed: boolean;
  isCapsLockOn: boolean;
  hasNoModifiers: boolean;
}>;

Note:

Latest iOS versions has a Commands for a11y support, which override keyboard key presses. If you open Accessibility -> Keyboards -> Full Keyboard Access -> Commands, you can find that Spacebar key id assigned to the Activate command. Because of this, all your spacebar presses will be ignored.

Examples

import { KeyboardFocusView } "react-native-a11y";

const App = () => {

  return <KeyboardFocusView> 
    <Text>Focusable</Text>
  </KeyboardFocusView>
}

Pressable

Almost original pressable, but used KeyboardFocusView instead of View

Provides additional functionality for usual Pressable

PropsDescription
onFocusChange?Event to handle focus change, (e: event.nativeEvent.isFocused) => void
canBeFocused?boolean default true, describe whether component can be focused by keyboard

Examples

watch: examples/A11ySample/src/components/Button

import React, { useState } from "react";
import { StyleSheet, Text, View, ViewStyle } from "react-native";
import { Pressable, FocusStyle, OnFocusChangeFn } from "react-native-a11y";


export const Button = React.forwardRef<View, Props>(
  ({ title, onPress, style, focusStyle, canBeFocused = true }, ref) => {
    ...

    const [focused, setFocusStatus] = useState(false);

    const onFocusChangeHandler: OnFocusChangeFn = event => {
      setFocusStatus(event.nativeEvent.isFocused);
    };

    return (
      <View style={style}>
        <Pressable
          onFocusChange={onFocusChangeHandler}
          canBeFocused={canBeFocused}
          onPress={onPress}
          style={[styles.container]}
          focusStyle={focusStyle || fStyle}
          ref={ref}
        >
          <Text style={[styles.font, focused && styles.focusedFont]}>
            {title}
          </Text>
        </Pressable>
      </View>
    );
  },
);

KeyboardProvider

Specific provider, used to block all focusable views (KeyboardFocusView). Based on value props disable or not KeyboardFocusView. It can be useful to block list of components, on screen for example or on Drawer in React Navigation.

watch: examples/A11ySample/src/screens/KeyboardFocusScreen

const App = () => {
  return <>
     <Button
        style={styles.btn}
        title="Title"
      />
      <KeyboardProvider value={false}>
        <Button title="Disabled focus" />
        <Button title="Disabled focus" />
      </KeyboardProvider>
  </>
}

Android input

There are an issue with focusing by keyboard input on Android https://github.com/facebook/react-native/issues/31820 You can find a fix in docs

Example: |

Roadmap

  • Add more examples, update Readme
  • Add tests
  • Migrate to the new architecture
  • Check React Navigation for A11y and make examples

ReactNative old versions supporting

The library provides default support for RN versions starting from v0.66.1 and up to v0.72.*.

To enable support for versions 0.64. and 0.65., add legacyVer=true in your gradle.properties file

// root/android/gradle.properties

legacyVer=true

If for some reason you need support for older versions, feel free to create an issue.

Problems

iOS

  • remove halo effect for ios keyboard focus, focusEffect = nil doesn't work, overriding to. Note: tested on iphone 8 - 15.5, try to a new one

Android

  • a11y listener for talk back doesn't work perfect, we can not listen TalkBack only, it listen the whole a11y functional in android and can return wrong results.

Contributing

Soon

Acknowledgements

I really appreciate the work and solutions provided by Andrii Koval, Michail Chavkin, Dzmitry Khamitsevich. I think there was not this library without them, I also want to thank Aliaksei Kisel and Herman Tseranevich for help with publishing and reviewing.

License

MIT


Made with create-react-native-library

0.4.5

15 days ago

0.4.4

3 months ago

0.4.4-rc0

3 months ago

0.4.3-rc2

5 months ago

0.4.3-rc1

5 months ago

0.4.3-rc

5 months ago

0.4.3

5 months ago

0.4.2-rc.1

7 months ago

0.4.2-rc.0

7 months ago

0.4.2

7 months ago

0.4.1

8 months ago

0.4.0-rc.0

12 months ago

0.3.2

12 months ago

0.4.0

9 months ago

0.3.0-android.2

1 year ago

0.3.0-beta-.2

1 year ago

0.3.0-android.1

1 year ago

0.3.0-beta-.1

1 year ago

0.3.0

1 year ago

0.3.0-android.0

1 year ago

0.3.0-beta-.3

1 year ago

0.3.1

1 year ago

0.2.1

2 years ago

0.2.0

2 years ago