Implement sendAccessibilityEvent in the React(Fabric/non-Fabric) renderer

Summary:
`sendAccessibilityEvent_unstable` is a cross-platform, Fabric/non-Fabric replacement for previous APIs (which went through UIManager directly on Android, and a native module on iOS).

Changelog: [Added] sendAccessibilityEvent_unstable API in AccessibilityInfo and sendAccessibilityEvent in React renderer

Reviewed By: kacieb

Differential Revision: D25821052

fbshipit-source-id: 03f7a9878c95e8395f9102b3e596bfc9f03730e0
This commit is contained in:
Joshua Gross authored and Facebook GitHub Bot committed 2021-01-27 17:37:38 -08:00
1 parent f275514f27
commit 99b7052248
17 files changed
+224 -26

No files matched your search

@@ -11,9 +11,12 @@
'use strict';
import RCTDeviceEventEmitter from '../../EventEmitter/RCTDeviceEventEmitter';
import UIManager from '../../ReactNative/UIManager';
import NativeAccessibilityInfo from './NativeAccessibilityInfo';
import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
import {sendAccessibilityEvent} from '../../Renderer/shims/ReactNative';
import legacySendAccessibilityEvent from './legacySendAccessibilityEvent';
import {type ElementRef} from 'react';
const REDUCE_MOTION_EVENT = 'reduceMotionDidChange';
const TOUCH_EXPLORATION_EVENT = 'touchExplorationDidChange';
@@ -25,6 +28,8 @@ type AccessibilityEventDefinitions = {
change: [boolean],
};
type AccessibilityEventTypes = 'focus';
const _subscriptions = new Map();
/**
@@ -148,10 +153,18 @@ const AccessibilityInfo = {
* See https://reactnative.dev/docs/accessibilityinfo.html#setaccessibilityfocus
*/
setAccessibilityFocus: function(reactTag: number): void {
UIManager.sendAccessibilityEvent(
reactTag,
UIManager.getConstants().AccessibilityEventTypes.typeViewFocused,
);
legacySendAccessibilityEvent(reactTag, 'focus');
},
/**
* Send a named accessibility event to a HostComponent.
*/
sendAccessibilityEvent_unstable: function(
handle: ElementRef<HostComponent<mixed>>,
eventType: AccessibilityEventTypes,
) {
// route through React renderer to distinguish between Fabric and non-Fabric handles
sendAccessibilityEvent(handle, eventType);
},
/**
@@ -13,6 +13,10 @@
import RCTDeviceEventEmitter from '../../EventEmitter/RCTDeviceEventEmitter';
import NativeAccessibilityManager from './NativeAccessibilityManager';
import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
import {sendAccessibilityEvent} from '../../Renderer/shims/ReactNative';
import legacySendAccessibilityEvent from './legacySendAccessibilityEvent';
import {type ElementRef} from 'react';
const CHANGE_EVENT_NAME = {
announcementFinished: 'announcementFinished',
@@ -41,6 +45,8 @@ type AccessibilityEventDefinitions = {
],
};
type AccessibilityEventTypes = 'focus';
const _subscriptions = new Map();
/**
@@ -241,9 +247,18 @@ const AccessibilityInfo = {
* See https://reactnative.dev/docs/accessibilityinfo.html#setaccessibilityfocus
*/
setAccessibilityFocus: function(reactTag: number): void {
if (NativeAccessibilityManager) {
NativeAccessibilityManager.setAccessibilityFocus(reactTag);
}
legacySendAccessibilityEvent(reactTag, 'focus');
},
/**
* Send a named accessibility event to a HostComponent.
*/
sendAccessibilityEvent_unstable: function(
handle: ElementRef<HostComponent<mixed>>,
eventType: AccessibilityEventTypes,
) {
// route through React renderer to distinguish between Fabric and non-Fabric handles
sendAccessibilityEvent(handle, eventType);
},
/**
@@ -0,0 +1,31 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
import UIManager from '../../ReactNative/UIManager';
/**
* This is a function exposed to the React Renderer that can be used by the
* pre-Fabric renderer to emit accessibility events to pre-Fabric nodes.
*/
function legacySendAccessibilityEvent(
reactTag: number,
eventType: string,
): void {
if (eventType === 'focus') {
UIManager.sendAccessibilityEvent(
reactTag,
UIManager.getConstants().AccessibilityEventTypes.typeViewFocused,
);
}
}
module.exports = legacySendAccessibilityEvent;
@@ -0,0 +1,28 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
import NativeAccessibilityManager from './NativeAccessibilityManager';
/**
* This is a function exposed to the React Renderer that can be used by the
* pre-Fabric renderer to emit accessibility events to pre-Fabric nodes.
*/
function legacySendAccessibilityEvent(
reactTag: number,
eventType: string,
): void {
if (eventType === 'focus' && NativeAccessibilityManager) {
NativeAccessibilityManager.setAccessibilityFocus(reactTag);
}
}
module.exports = legacySendAccessibilityEvent;
@@ -350,4 +350,7 @@ class TouchableHighlight extends React.Component<Props, State> {
module.exports = (React.forwardRef((props, hostRef) => (
<TouchableHighlight {...props} hostRef={hostRef} />
)): React.AbstractComponent<$ReadOnly<$Diff<Props, {|hostRef: mixed|}>>>);
)): React.AbstractComponent<
$ReadOnly<$Diff<Props, {|hostRef: React.Ref<typeof View>|}>>,
React.ElementRef<typeof View>,
>);
@@ -38,7 +38,7 @@ type Props = $ReadOnly<{|
activeOpacity?: ?number,
style?: ?ViewStyleProp,
hostRef: React.Ref<typeof Animated.View>,
hostRef?: ?React.Ref<typeof Animated.View>,
|}>;
type State = $ReadOnly<{|
@@ -267,6 +267,6 @@ class TouchableOpacity extends React.Component<Props, State> {
}
}
module.exports = (React.forwardRef((props, hostRef) => (
<TouchableOpacity {...props} hostRef={hostRef} />
)): React.AbstractComponent<$ReadOnly<$Diff<Props, {|hostRef: mixed|}>>>);
module.exports = (React.forwardRef((props, ref) => (
<TouchableOpacity {...props} hostRef={ref} />
)): React.AbstractComponent<Props, React.ElementRef<typeof Animated.View>>);
+1
View File
@@ -57,6 +57,7 @@ export type Spec = {|
// $FlowFixMe
errorCallback: (error: Object) => void,
) => void,
+sendAccessibilityEvent: (node: Node, eventType: string) => void,
|};
const FabricUIManager: ?Spec = global.nativeFabricUIManager;
@@ -20,6 +20,7 @@ import typeof deepFreezeAndThrowOnMutationInDev from '../Utilities/deepFreezeAnd
import typeof flattenStyle from '../StyleSheet/flattenStyle';
import {type DangerouslyImpreciseStyleProp} from '../StyleSheet/StyleSheet';
import typeof ReactFiberErrorDialog from '../Core/ReactFiberErrorDialog';
import typeof legacySendAccessibilityEvent from '../Components/AccessibilityInfo/legacySendAccessibilityEvent';
// flowlint unsafe-getters-setters:off
module.exports = {
@@ -59,4 +60,7 @@ module.exports = {
get ReactFiberErrorDialog(): ReactFiberErrorDialog {
return require('../Core/ReactFiberErrorDialog');
},
get legacySendAccessibilityEvent(): legacySendAccessibilityEvent {
return require('../Components/AccessibilityInfo/legacySendAccessibilityEvent');
},
};
@@ -3730,7 +3730,8 @@ var _nativeFabricUIManage = nativeFabricUIManager,
registerEventHandler = _nativeFabricUIManage.registerEventHandler,
fabricMeasure = _nativeFabricUIManage.measure,
fabricMeasureInWindow = _nativeFabricUIManage.measureInWindow,
fabricMeasureLayout = _nativeFabricUIManage.measureLayout;
fabricMeasureLayout = _nativeFabricUIManage.measureLayout,
sendAccessibilityEvent = _nativeFabricUIManage.sendAccessibilityEvent;
var getViewConfigForType =
ReactNativePrivateInterface.ReactNativeViewConfigRegistry.get; // Counter for uniquely identifying views.
// % 10 === 1 means it is a rootTag.
@@ -21695,6 +21696,31 @@ function dispatchCommand(handle, command, args) {
}
}
function sendAccessibilityEvent(handle, eventType) {
if (handle._nativeTag == null) {
{
error(
"sendAccessibilityEvent was called with a ref that isn't a " +
"native component. Use React.forwardRef to get access to the underlying native component"
);
}
return;
}
if (handle._internalInstanceHandle) {
nativeFabricUIManager.sendAccessibilityEvent(
handle._internalInstanceHandle.stateNode.node,
eventType
);
} else {
ReactNativePrivateInterface.legacySendAccessibilityEvent(
handle._nativeTag,
eventType
);
}
}
function render(element, containerTag, callback) {
var root = roots.get(containerTag);
@@ -21750,6 +21776,7 @@ exports.createPortal = createPortal$1;
exports.dispatchCommand = dispatchCommand;
exports.findHostInstance_DEPRECATED = findHostInstance_DEPRECATED;
exports.findNodeHandle = findNodeHandle;
exports.sendAccessibilityEvent = sendAccessibilityEvent;
exports.render = render;
exports.stopSurface = stopSurface;
exports.unmountComponentAtNode = unmountComponentAtNode;
@@ -7702,6 +7702,18 @@ exports.render = function(element, containerTag, callback) {
else element = null;
return element;
};
exports.sendAccessibilityEvent = function(handle, eventType) {
null != handle._nativeTag &&
(handle._internalInstanceHandle
? nativeFabricUIManager.sendAccessibilityEvent(
handle._internalInstanceHandle.stateNode.node,
eventType
)
: ReactNativePrivateInterface.legacySendAccessibilityEvent(
handle._nativeTag,
eventType
));
};
exports.stopSurface = function(containerTag) {
var root = roots.get(containerTag);
root &&
@@ -7997,6 +7997,18 @@ exports.render = function(element, containerTag, callback) {
else element = null;
return element;
};
exports.sendAccessibilityEvent = function(handle, eventType) {
null != handle._nativeTag &&
(handle._internalInstanceHandle
? nativeFabricUIManager.sendAccessibilityEvent(
handle._internalInstanceHandle.stateNode.node,
eventType
)
: ReactNativePrivateInterface.legacySendAccessibilityEvent(
handle._nativeTag,
eventType
));
};
exports.stopSurface = function(containerTag) {
var root = roots.get(containerTag);
root &&
@@ -22323,6 +22323,31 @@ function dispatchCommand(handle, command, args) {
}
}
function sendAccessibilityEvent(handle, eventType) {
if (handle._nativeTag == null) {
{
error(
"sendAccessibilityEvent was called with a ref that isn't a " +
"native component. Use React.forwardRef to get access to the underlying native component"
);
}
return;
}
if (handle._internalInstanceHandle) {
nativeFabricUIManager.sendAccessibilityEvent(
handle._internalInstanceHandle.stateNode.node,
eventType
);
} else {
ReactNativePrivateInterface.legacySendAccessibilityEvent(
handle._nativeTag,
eventType
);
}
}
function render(element, containerTag, callback) {
var root = roots.get(containerTag);
@@ -22396,6 +22421,7 @@ exports.dispatchCommand = dispatchCommand;
exports.findHostInstance_DEPRECATED = findHostInstance_DEPRECATED;
exports.findNodeHandle = findNodeHandle;
exports.render = render;
exports.sendAccessibilityEvent = sendAccessibilityEvent;
exports.unmountComponentAtNode = unmountComponentAtNode;
exports.unmountComponentAtNodeAndRemoveContainer = unmountComponentAtNodeAndRemoveContainer;
exports.unstable_batchedUpdates = batchedUpdates;
@@ -7930,6 +7930,18 @@ exports.render = function(element, containerTag, callback) {
else element = null;
return element;
};
exports.sendAccessibilityEvent = function(handle, eventType) {
null != handle._nativeTag &&
(handle._internalInstanceHandle
? nativeFabricUIManager.sendAccessibilityEvent(
handle._internalInstanceHandle.stateNode.node,
eventType
)
: ReactNativePrivateInterface.legacySendAccessibilityEvent(
handle._nativeTag,
eventType
));
};
exports.unmountComponentAtNode = unmountComponentAtNode;
exports.unmountComponentAtNodeAndRemoveContainer = function(containerTag) {
unmountComponentAtNode(containerTag);
@@ -8222,6 +8222,18 @@ exports.render = function(element, containerTag, callback) {
else element = null;
return element;
};
exports.sendAccessibilityEvent = function(handle, eventType) {
null != handle._nativeTag &&
(handle._internalInstanceHandle
? nativeFabricUIManager.sendAccessibilityEvent(
handle._internalInstanceHandle.stateNode.node,
eventType
)
: ReactNativePrivateInterface.legacySendAccessibilityEvent(
handle._nativeTag,
eventType
));
};
exports.unmountComponentAtNode = unmountComponentAtNode;
exports.unmountComponentAtNodeAndRemoveContainer = function(containerTag) {
unmountComponentAtNode(containerTag);
@@ -180,6 +180,10 @@ export type ReactNativeType = {
command: string,
args: Array<mixed>,
): void,
sendAccessibilityEvent(
handle: ElementRef<HostComponent<mixed>>,
eventType: string,
): void,
render(
element: MixedElement,
containerTag: number,
@@ -204,6 +208,10 @@ export type ReactFabricType = {
command: string,
args: Array<mixed>,
): void,
sendAccessibilityEvent(
handle: ElementRef<HostComponent<mixed>>,
eventType: string,
): void,
render(
element: MixedElement,
containerTag: number,
+1
View File
@@ -127,6 +127,7 @@ jest
isScreenReaderEnabled: jest.fn(() => Promise.resolve(false)),
removeEventListener: jest.fn(),
setAccessibilityFocus: jest.fn(),
sendAccessibilityEvent_unstable: jest.fn(),
}))
.mock('../Libraries/Components/RefreshControl/RefreshControl', () =>
jest.requireActual(
@@ -18,7 +18,6 @@ const {
View,
TouchableOpacity,
TouchableWithoutFeedback,
findNodeHandle,
Alert,
StyleSheet,
Platform,
@@ -712,27 +711,21 @@ class AnnounceForAccessibility extends React.Component<{}> {
}
class SetAccessibilityFocusExample extends React.Component<{}> {
state = {
reactTag: null,
};
render() {
const myRef = createRef();
const onClose = () => {
if (myRef && myRef.current) {
const reactTag = findNodeHandle(myRef.current);
this.setState({reactTag});
AccessibilityInfo.setAccessibilityFocus(reactTag);
AccessibilityInfo.sendAccessiblityEvent_unstable(
myRef.current,
'focus',
);
}
};
return (
<View>
<Text>
SetAccessibilityFocus on ReactTag:{' '}
{this.state.reactTag == null ? 'Null' : this.state.reactTag}
</Text>
<Text>SetAccessibilityFocus on native element</Text>
<Button
ref={myRef}
title={'Click'}