-
Notifications
You must be signed in to change notification settings - Fork 24.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TYPE_VIEW_HOVER_ENTER to AccessibilityNodeInfo sendAccessibilityEvent #34969
Changes from 19 commits
fd0923e
accbbfc
17d7827
5e1e72f
42ab164
5c7ed5e
98b4a94
0fe1e3e
4213491
f89a01f
9e7fe9f
129d152
036e4b5
8a25a6f
dbd01c6
3c0f531
23bf335
43e2ffa
8f2e0f0
d351891
3126e49
ef5e490
4cf1605
e7f0023
fd152d2
bf37a34
dc4c54e
69f6a18
a5c0cd8
8debb60
b5f3b21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,14 +4,22 @@ | |
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow strict-local | ||
* @flow | ||
* @format | ||
*/ | ||
|
||
/* eslint-disable no-alert */ | ||
|
||
import * as React from 'react'; | ||
import {Modal, Platform, StyleSheet, Switch, Text, View} from 'react-native'; | ||
import { | ||
AccessibilityInfo, | ||
Modal, | ||
Platform, | ||
StyleSheet, | ||
Switch, | ||
Text, | ||
View, | ||
} from 'react-native'; | ||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; | ||
import RNTOption from '../../components/RNTOption'; | ||
const RNTesterButton = require('../../components/RNTesterButton'); | ||
|
@@ -35,6 +43,23 @@ const presentationStyles = [ | |
const iOSActions = ['None', 'On Dismiss', 'On Show']; | ||
const noniOSActions = ['None', 'On Show']; | ||
|
||
const TitleComponent = React.forwardRef((props, forwardedRef) => { | ||
return ( | ||
<Text | ||
ref={forwardedRef} | ||
style={{ | ||
width: '100%', | ||
position: 'absolute', | ||
top: 500, | ||
textAlign: 'center', | ||
backgroundColor: 'red', | ||
zIndex: 20, | ||
}}> | ||
My custom title | ||
</Text> | ||
); | ||
}); | ||
|
||
function ModalPresentation() { | ||
const [animationType, setAnimationType] = React.useState('none'); | ||
const [transparent, setTransparent] = React.useState(false); | ||
|
@@ -45,11 +70,14 @@ function ModalPresentation() { | |
React.useState('fullScreen'); | ||
const [supportedOrientationKey, setSupportedOrientationKey] = | ||
React.useState('Portrait'); | ||
const [modalOpened, setModalOpened] = React.useState(null); | ||
const [currentOrientation, setCurrentOrientation] = React.useState('unknown'); | ||
const [action, setAction] = React.useState('None'); | ||
let ref = React.useRef<?React.ElementRef<typeof Text>>(null); | ||
const actions = Platform.OS === 'ios' ? iOSActions : noniOSActions; | ||
const onDismiss = () => { | ||
setVisible(false); | ||
setModalOpened(false); | ||
if (action === 'onDismiss') { | ||
alert('onDismiss'); | ||
} | ||
|
@@ -59,7 +87,34 @@ function ModalPresentation() { | |
if (action === 'onShow') { | ||
alert('onShow'); | ||
} | ||
if (ref != null) { | ||
setModalOpened(true); | ||
} | ||
}; | ||
|
||
React.useEffect(() => { | ||
let timer; | ||
if (ref != null && modalOpened === true) { | ||
if (Platform.OS === 'ios') { | ||
// $FlowFixMe | ||
AccessibilityInfo.sendAccessibilityEvent(ref.current, 'focus'); | ||
} else { | ||
// see https://github.com/facebook/react-native/issues/30097#issuecomment-1285927266 | ||
timer = setTimeout(() => { | ||
if (ref.current != null) { | ||
AccessibilityInfo.sendAccessibilityEvent(ref.current, 'focus'); | ||
} | ||
}, 1000); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's usually a red flag when I see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ryancat @blavalla My solution would be to modify the native implementation of sendAccessibilityEvent, to fix the original Talkback issue. The issue is #30097, but it is not in my sprint. The Pull Request was maybe meant to be an API proposal. I started testing a solution to the native method sendAccessibilityEvent in commit 17d7827#diff-d3d0a4709d26919cf2a549febf6cdb755a1274a489078154a80c04d4ba82e590R969-R980, I can publish and improve the solution in a separate PR (for ex. an alternative implementation of sendAccessibilityEvent in SurfaceMountingManager). The possible solutions are:
I can also further work improving this example or publish an example in the docs, as you suggest in the code-review. Thanks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I agree that generally speaking setTimeout is a red flag, in this case we're actually replicating behavior internal to Talkback that works the same way. When Talkback gets a new "TYPE_WINDOW_STATE_CHANGED" event, it actually waits 550ms before notifying the rest of the system about it, to make sure that the window itself is in a final, idle, state. It also has an up to 1110ms delay while waiting for the screen to be stabilized before responding to that change. So this sort of setTimeout approach is unfortunately not uncommon in the accessibility space. Since we're in Javascript here, we can't just send the native Android event here, but if there is a solution on the native Android side I think simply firing a "TYPE_WINDOW_STATE_CHANGED" when the modal appears would result in the same behavior of focus moving into it after a slight (talkback controlled) delay. This would leave the ugly setTimeout code to Talkback itself, so that we don't end up in a weird race condition later on if Talkback changes its internal logic. Some code pointers that may be helpful:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @blavalla Thanks. You are correct. I fixed the issue by adding |
||
} | ||
} | ||
|
||
return () => { | ||
if (timer) { | ||
clearTimeout(timer); | ||
} | ||
}; | ||
}, [modalOpened]); | ||
|
||
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's | ||
* LTI update could not be added via codemod */ | ||
const onOrientationChange = event => | ||
|
@@ -87,6 +142,7 @@ function ModalPresentation() { | |
onOrientationChange={onOrientationChange} | ||
onDismiss={onDismiss} | ||
onShow={onShow}> | ||
{TitleComponent != null && <TitleComponent ref={ref} />} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When |
||
<View style={[styles.modalContainer, modalBackgroundStyle]}> | ||
<View | ||
style={[ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to leverage visible state instead of creating a new state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ryancat Thanks. I moved the example to the documentation facebook/react-native-website#3438