mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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:
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>>);
|
||||
@@ -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,
|
||||
|
||||
@@ -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'}
|
||||
|
||||
Reference in new issue
Block a user