Skip to content

Commit 99b7052

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
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
1 parent f275514 commit 99b7052

17 files changed

+224
-26
lines changed

Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111
'use strict';
1212

1313
import RCTDeviceEventEmitter from '../../EventEmitter/RCTDeviceEventEmitter';
14-
import UIManager from '../../ReactNative/UIManager';
1514
import NativeAccessibilityInfo from './NativeAccessibilityInfo';
1615
import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
16+
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
17+
import {sendAccessibilityEvent} from '../../Renderer/shims/ReactNative';
18+
import legacySendAccessibilityEvent from './legacySendAccessibilityEvent';
19+
import {type ElementRef} from 'react';
1720

1821
const REDUCE_MOTION_EVENT = 'reduceMotionDidChange';
1922
const TOUCH_EXPLORATION_EVENT = 'touchExplorationDidChange';
@@ -25,6 +28,8 @@ type AccessibilityEventDefinitions = {
2528
change: [boolean],
2629
};
2730

31+
type AccessibilityEventTypes = 'focus';
32+
2833
const _subscriptions = new Map();
2934

3035
/**
@@ -148,10 +153,18 @@ const AccessibilityInfo = {
148153
* See https://reactnative.dev/docs/accessibilityinfo.html#setaccessibilityfocus
149154
*/
150155
setAccessibilityFocus: function(reactTag: number): void {
151-
UIManager.sendAccessibilityEvent(
152-
reactTag,
153-
UIManager.getConstants().AccessibilityEventTypes.typeViewFocused,
154-
);
156+
legacySendAccessibilityEvent(reactTag, 'focus');
157+
},
158+
159+
/**
160+
* Send a named accessibility event to a HostComponent.
161+
*/
162+
sendAccessibilityEvent_unstable: function(
163+
handle: ElementRef<HostComponent<mixed>>,
164+
eventType: AccessibilityEventTypes,
165+
) {
166+
// route through React renderer to distinguish between Fabric and non-Fabric handles
167+
sendAccessibilityEvent(handle, eventType);
155168
},
156169

157170
/**

Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js

+18-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
import RCTDeviceEventEmitter from '../../EventEmitter/RCTDeviceEventEmitter';
1414
import NativeAccessibilityManager from './NativeAccessibilityManager';
1515
import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
16+
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
17+
import {sendAccessibilityEvent} from '../../Renderer/shims/ReactNative';
18+
import legacySendAccessibilityEvent from './legacySendAccessibilityEvent';
19+
import {type ElementRef} from 'react';
1620

1721
const CHANGE_EVENT_NAME = {
1822
announcementFinished: 'announcementFinished',
@@ -41,6 +45,8 @@ type AccessibilityEventDefinitions = {
4145
],
4246
};
4347

48+
type AccessibilityEventTypes = 'focus';
49+
4450
const _subscriptions = new Map();
4551

4652
/**
@@ -241,9 +247,18 @@ const AccessibilityInfo = {
241247
* See https://reactnative.dev/docs/accessibilityinfo.html#setaccessibilityfocus
242248
*/
243249
setAccessibilityFocus: function(reactTag: number): void {
244-
if (NativeAccessibilityManager) {
245-
NativeAccessibilityManager.setAccessibilityFocus(reactTag);
246-
}
250+
legacySendAccessibilityEvent(reactTag, 'focus');
251+
},
252+
253+
/**
254+
* Send a named accessibility event to a HostComponent.
255+
*/
256+
sendAccessibilityEvent_unstable: function(
257+
handle: ElementRef<HostComponent<mixed>>,
258+
eventType: AccessibilityEventTypes,
259+
) {
260+
// route through React renderer to distinguish between Fabric and non-Fabric handles
261+
sendAccessibilityEvent(handle, eventType);
247262
},
248263

249264
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow strict-local
9+
*/
10+
11+
'use strict';
12+
13+
import UIManager from '../../ReactNative/UIManager';
14+
15+
/**
16+
* This is a function exposed to the React Renderer that can be used by the
17+
* pre-Fabric renderer to emit accessibility events to pre-Fabric nodes.
18+
*/
19+
function legacySendAccessibilityEvent(
20+
reactTag: number,
21+
eventType: string,
22+
): void {
23+
if (eventType === 'focus') {
24+
UIManager.sendAccessibilityEvent(
25+
reactTag,
26+
UIManager.getConstants().AccessibilityEventTypes.typeViewFocused,
27+
);
28+
}
29+
}
30+
31+
module.exports = legacySendAccessibilityEvent;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow strict-local
9+
*/
10+
11+
'use strict';
12+
13+
import NativeAccessibilityManager from './NativeAccessibilityManager';
14+
15+
/**
16+
* This is a function exposed to the React Renderer that can be used by the
17+
* pre-Fabric renderer to emit accessibility events to pre-Fabric nodes.
18+
*/
19+
function legacySendAccessibilityEvent(
20+
reactTag: number,
21+
eventType: string,
22+
): void {
23+
if (eventType === 'focus' && NativeAccessibilityManager) {
24+
NativeAccessibilityManager.setAccessibilityFocus(reactTag);
25+
}
26+
}
27+
28+
module.exports = legacySendAccessibilityEvent;

Libraries/Components/Touchable/TouchableHighlight.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -350,4 +350,7 @@ class TouchableHighlight extends React.Component<Props, State> {
350350

351351
module.exports = (React.forwardRef((props, hostRef) => (
352352
<TouchableHighlight {...props} hostRef={hostRef} />
353-
)): React.AbstractComponent<$ReadOnly<$Diff<Props, {|hostRef: mixed|}>>>);
353+
)): React.AbstractComponent<
354+
$ReadOnly<$Diff<Props, {|hostRef: React.Ref<typeof View>|}>>,
355+
React.ElementRef<typeof View>,
356+
>);

Libraries/Components/Touchable/TouchableOpacity.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type Props = $ReadOnly<{|
3838
activeOpacity?: ?number,
3939
style?: ?ViewStyleProp,
4040

41-
hostRef: React.Ref<typeof Animated.View>,
41+
hostRef?: ?React.Ref<typeof Animated.View>,
4242
|}>;
4343

4444
type State = $ReadOnly<{|
@@ -267,6 +267,6 @@ class TouchableOpacity extends React.Component<Props, State> {
267267
}
268268
}
269269

270-
module.exports = (React.forwardRef((props, hostRef) => (
271-
<TouchableOpacity {...props} hostRef={hostRef} />
272-
)): React.AbstractComponent<$ReadOnly<$Diff<Props, {|hostRef: mixed|}>>>);
270+
module.exports = (React.forwardRef((props, ref) => (
271+
<TouchableOpacity {...props} hostRef={ref} />
272+
)): React.AbstractComponent<Props, React.ElementRef<typeof Animated.View>>);

Libraries/ReactNative/FabricUIManager.js

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export type Spec = {|
5757
// $FlowFixMe
5858
errorCallback: (error: Object) => void,
5959
) => void,
60+
+sendAccessibilityEvent: (node: Node, eventType: string) => void,
6061
|};
6162

6263
const FabricUIManager: ?Spec = global.nativeFabricUIManager;

Libraries/ReactPrivate/ReactNativePrivateInterface.js

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import typeof deepFreezeAndThrowOnMutationInDev from '../Utilities/deepFreezeAnd
2020
import typeof flattenStyle from '../StyleSheet/flattenStyle';
2121
import {type DangerouslyImpreciseStyleProp} from '../StyleSheet/StyleSheet';
2222
import typeof ReactFiberErrorDialog from '../Core/ReactFiberErrorDialog';
23+
import typeof legacySendAccessibilityEvent from '../Components/AccessibilityInfo/legacySendAccessibilityEvent';
2324

2425
// flowlint unsafe-getters-setters:off
2526
module.exports = {
@@ -59,4 +60,7 @@ module.exports = {
5960
get ReactFiberErrorDialog(): ReactFiberErrorDialog {
6061
return require('../Core/ReactFiberErrorDialog');
6162
},
63+
get legacySendAccessibilityEvent(): legacySendAccessibilityEvent {
64+
return require('../Components/AccessibilityInfo/legacySendAccessibilityEvent');
65+
},
6266
};

Libraries/Renderer/implementations/ReactFabric-dev.fb.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -3730,7 +3730,8 @@ var _nativeFabricUIManage = nativeFabricUIManager,
37303730
registerEventHandler = _nativeFabricUIManage.registerEventHandler,
37313731
fabricMeasure = _nativeFabricUIManage.measure,
37323732
fabricMeasureInWindow = _nativeFabricUIManage.measureInWindow,
3733-
fabricMeasureLayout = _nativeFabricUIManage.measureLayout;
3733+
fabricMeasureLayout = _nativeFabricUIManage.measureLayout,
3734+
sendAccessibilityEvent = _nativeFabricUIManage.sendAccessibilityEvent;
37343735
var getViewConfigForType =
37353736
ReactNativePrivateInterface.ReactNativeViewConfigRegistry.get; // Counter for uniquely identifying views.
37363737
// % 10 === 1 means it is a rootTag.
@@ -21695,6 +21696,31 @@ function dispatchCommand(handle, command, args) {
2169521696
}
2169621697
}
2169721698

21699+
function sendAccessibilityEvent(handle, eventType) {
21700+
if (handle._nativeTag == null) {
21701+
{
21702+
error(
21703+
"sendAccessibilityEvent was called with a ref that isn't a " +
21704+
"native component. Use React.forwardRef to get access to the underlying native component"
21705+
);
21706+
}
21707+
21708+
return;
21709+
}
21710+
21711+
if (handle._internalInstanceHandle) {
21712+
nativeFabricUIManager.sendAccessibilityEvent(
21713+
handle._internalInstanceHandle.stateNode.node,
21714+
eventType
21715+
);
21716+
} else {
21717+
ReactNativePrivateInterface.legacySendAccessibilityEvent(
21718+
handle._nativeTag,
21719+
eventType
21720+
);
21721+
}
21722+
}
21723+
2169821724
function render(element, containerTag, callback) {
2169921725
var root = roots.get(containerTag);
2170021726

@@ -21750,6 +21776,7 @@ exports.createPortal = createPortal$1;
2175021776
exports.dispatchCommand = dispatchCommand;
2175121777
exports.findHostInstance_DEPRECATED = findHostInstance_DEPRECATED;
2175221778
exports.findNodeHandle = findNodeHandle;
21779+
exports.sendAccessibilityEvent = sendAccessibilityEvent;
2175321780
exports.render = render;
2175421781
exports.stopSurface = stopSurface;
2175521782
exports.unmountComponentAtNode = unmountComponentAtNode;

Libraries/Renderer/implementations/ReactFabric-prod.fb.js

+12
Original file line numberDiff line numberDiff line change
@@ -7702,6 +7702,18 @@ exports.render = function(element, containerTag, callback) {
77027702
else element = null;
77037703
return element;
77047704
};
7705+
exports.sendAccessibilityEvent = function(handle, eventType) {
7706+
null != handle._nativeTag &&
7707+
(handle._internalInstanceHandle
7708+
? nativeFabricUIManager.sendAccessibilityEvent(
7709+
handle._internalInstanceHandle.stateNode.node,
7710+
eventType
7711+
)
7712+
: ReactNativePrivateInterface.legacySendAccessibilityEvent(
7713+
handle._nativeTag,
7714+
eventType
7715+
));
7716+
};
77057717
exports.stopSurface = function(containerTag) {
77067718
var root = roots.get(containerTag);
77077719
root &&

Libraries/Renderer/implementations/ReactFabric-profiling.fb.js

+12
Original file line numberDiff line numberDiff line change
@@ -7997,6 +7997,18 @@ exports.render = function(element, containerTag, callback) {
79977997
else element = null;
79987998
return element;
79997999
};
8000+
exports.sendAccessibilityEvent = function(handle, eventType) {
8001+
null != handle._nativeTag &&
8002+
(handle._internalInstanceHandle
8003+
? nativeFabricUIManager.sendAccessibilityEvent(
8004+
handle._internalInstanceHandle.stateNode.node,
8005+
eventType
8006+
)
8007+
: ReactNativePrivateInterface.legacySendAccessibilityEvent(
8008+
handle._nativeTag,
8009+
eventType
8010+
));
8011+
};
80008012
exports.stopSurface = function(containerTag) {
80018013
var root = roots.get(containerTag);
80028014
root &&

Libraries/Renderer/implementations/ReactNativeRenderer-dev.fb.js

+26
Original file line numberDiff line numberDiff line change
@@ -22323,6 +22323,31 @@ function dispatchCommand(handle, command, args) {
2232322323
}
2232422324
}
2232522325

22326+
function sendAccessibilityEvent(handle, eventType) {
22327+
if (handle._nativeTag == null) {
22328+
{
22329+
error(
22330+
"sendAccessibilityEvent was called with a ref that isn't a " +
22331+
"native component. Use React.forwardRef to get access to the underlying native component"
22332+
);
22333+
}
22334+
22335+
return;
22336+
}
22337+
22338+
if (handle._internalInstanceHandle) {
22339+
nativeFabricUIManager.sendAccessibilityEvent(
22340+
handle._internalInstanceHandle.stateNode.node,
22341+
eventType
22342+
);
22343+
} else {
22344+
ReactNativePrivateInterface.legacySendAccessibilityEvent(
22345+
handle._nativeTag,
22346+
eventType
22347+
);
22348+
}
22349+
}
22350+
2232622351
function render(element, containerTag, callback) {
2232722352
var root = roots.get(containerTag);
2232822353

@@ -22396,6 +22421,7 @@ exports.dispatchCommand = dispatchCommand;
2239622421
exports.findHostInstance_DEPRECATED = findHostInstance_DEPRECATED;
2239722422
exports.findNodeHandle = findNodeHandle;
2239822423
exports.render = render;
22424+
exports.sendAccessibilityEvent = sendAccessibilityEvent;
2239922425
exports.unmountComponentAtNode = unmountComponentAtNode;
2240022426
exports.unmountComponentAtNodeAndRemoveContainer = unmountComponentAtNodeAndRemoveContainer;
2240122427
exports.unstable_batchedUpdates = batchedUpdates;

Libraries/Renderer/implementations/ReactNativeRenderer-prod.fb.js

+12
Original file line numberDiff line numberDiff line change
@@ -7930,6 +7930,18 @@ exports.render = function(element, containerTag, callback) {
79307930
else element = null;
79317931
return element;
79327932
};
7933+
exports.sendAccessibilityEvent = function(handle, eventType) {
7934+
null != handle._nativeTag &&
7935+
(handle._internalInstanceHandle
7936+
? nativeFabricUIManager.sendAccessibilityEvent(
7937+
handle._internalInstanceHandle.stateNode.node,
7938+
eventType
7939+
)
7940+
: ReactNativePrivateInterface.legacySendAccessibilityEvent(
7941+
handle._nativeTag,
7942+
eventType
7943+
));
7944+
};
79337945
exports.unmountComponentAtNode = unmountComponentAtNode;
79347946
exports.unmountComponentAtNodeAndRemoveContainer = function(containerTag) {
79357947
unmountComponentAtNode(containerTag);

Libraries/Renderer/implementations/ReactNativeRenderer-profiling.fb.js

+12
Original file line numberDiff line numberDiff line change
@@ -8222,6 +8222,18 @@ exports.render = function(element, containerTag, callback) {
82228222
else element = null;
82238223
return element;
82248224
};
8225+
exports.sendAccessibilityEvent = function(handle, eventType) {
8226+
null != handle._nativeTag &&
8227+
(handle._internalInstanceHandle
8228+
? nativeFabricUIManager.sendAccessibilityEvent(
8229+
handle._internalInstanceHandle.stateNode.node,
8230+
eventType
8231+
)
8232+
: ReactNativePrivateInterface.legacySendAccessibilityEvent(
8233+
handle._nativeTag,
8234+
eventType
8235+
));
8236+
};
82258237
exports.unmountComponentAtNode = unmountComponentAtNode;
82268238
exports.unmountComponentAtNodeAndRemoveContainer = function(containerTag) {
82278239
unmountComponentAtNode(containerTag);

Libraries/Renderer/shims/ReactNativeTypes.js

+8
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ export type ReactNativeType = {
180180
command: string,
181181
args: Array<mixed>,
182182
): void,
183+
sendAccessibilityEvent(
184+
handle: ElementRef<HostComponent<mixed>>,
185+
eventType: string,
186+
): void,
183187
render(
184188
element: MixedElement,
185189
containerTag: number,
@@ -204,6 +208,10 @@ export type ReactFabricType = {
204208
command: string,
205209
args: Array<mixed>,
206210
): void,
211+
sendAccessibilityEvent(
212+
handle: ElementRef<HostComponent<mixed>>,
213+
eventType: string,
214+
): void,
207215
render(
208216
element: MixedElement,
209217
containerTag: number,

jest/setup.js

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ jest
127127
isScreenReaderEnabled: jest.fn(() => Promise.resolve(false)),
128128
removeEventListener: jest.fn(),
129129
setAccessibilityFocus: jest.fn(),
130+
sendAccessibilityEvent_unstable: jest.fn(),
130131
}))
131132
.mock('../Libraries/Components/RefreshControl/RefreshControl', () =>
132133
jest.requireActual(

0 commit comments

Comments
 (0)