Skip to content

Commit

Permalink
Implement sendAccessibilityEvent in the React(Fabric/non-Fabric) rend…
Browse files Browse the repository at this point in the history
…erer

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
  • Loading branch information
JoshuaGross authored and facebook-github-bot committed Jan 28, 2021
1 parent f275514 commit 99b7052
Show file tree
Hide file tree
Showing 17 changed files with 224 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -25,6 +28,8 @@ type AccessibilityEventDefinitions = {
change: [boolean],
};

type AccessibilityEventTypes = 'focus';

const _subscriptions = new Map();

/**
Expand Down Expand Up @@ -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);
},

/**
Expand Down
21 changes: 18 additions & 3 deletions Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -41,6 +45,8 @@ type AccessibilityEventDefinitions = {
],
};

type AccessibilityEventTypes = 'focus';

const _subscriptions = new Map();

/**
Expand Down Expand Up @@ -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);
},

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 4 additions & 1 deletion Libraries/Components/Touchable/TouchableHighlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
>);
8 changes: 4 additions & 4 deletions Libraries/Components/Touchable/TouchableOpacity.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<{|
Expand Down Expand Up @@ -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 change: 1 addition & 0 deletions Libraries/ReactNative/FabricUIManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export type Spec = {|
// $FlowFixMe
errorCallback: (error: Object) => void,
) => void,
+sendAccessibilityEvent: (node: Node, eventType: string) => void,
|};

const FabricUIManager: ?Spec = global.nativeFabricUIManager;
Expand Down
4 changes: 4 additions & 0 deletions Libraries/ReactPrivate/ReactNativePrivateInterface.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -59,4 +60,7 @@ module.exports = {
get ReactFiberErrorDialog(): ReactFiberErrorDialog {
return require('../Core/ReactFiberErrorDialog');
},
get legacySendAccessibilityEvent(): legacySendAccessibilityEvent {
return require('../Components/AccessibilityInfo/legacySendAccessibilityEvent');
},
};
29 changes: 28 additions & 1 deletion Libraries/Renderer/implementations/ReactFabric-dev.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions Libraries/Renderer/implementations/ReactFabric-prod.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
12 changes: 12 additions & 0 deletions Libraries/Renderer/implementations/ReactFabric-profiling.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
26 changes: 26 additions & 0 deletions Libraries/Renderer/implementations/ReactNativeRenderer-dev.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions Libraries/Renderer/implementations/ReactNativeRenderer-prod.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions Libraries/Renderer/shims/ReactNativeTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions jest/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading

0 comments on commit 99b7052

Please sign in to comment.