Highlight the current talkback-focused element in the accessibility tree

Summary: Highlights the element corresponding to the view talkback is focused on in green in the ax tree (and updates live as talkback moves).

Reviewed By: blavalla

Differential Revision: D9021542

fbshipit-source-id: c3bf6f5625aacb0cd054032b33a50541b88b2eaf
This commit is contained in:
Sara Valderrama
2018-07-27 16:16:54 -07:00
committed by Facebook Github Bot
parent 6939292209
commit 33e6538477
10 changed files with 154 additions and 18 deletions

View File

@@ -14,6 +14,7 @@ export class AXElementsInspector extends Component<{
onElementHovered: ?(key: ?ElementID) => void,
onValueChanged: ?(path: Array<string>, val: any) => void,
selected: ?ElementID,
focused: ?ElementID,
searchResults?: ?ElementSearchResultSet,
root: ?ElementID,
elements: {[key: ElementID]: Element},

View File

@@ -38,6 +38,7 @@ export type InspectorState = {|
AXinitialised: boolean,
selected: ?ElementID,
AXselected: ?ElementID,
AXfocused: ?ElementID,
root: ?ElementID,
AXroot: ?ElementID,
elements: {[key: ElementID]: Element},
@@ -66,6 +67,15 @@ type UpdateElementsArgs = {|
elements: Array<$Shape<Element>>,
|};
type UpdateAXElementsArgs = {|
elements: Array<$Shape<Element>>,
forFocusEvent: boolean,
|};
type AXFocusEventResult = {|
isFocus: boolean,
|};
type SetRootArgs = {|
root: ElementID,
|};
@@ -159,6 +169,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
AXroot: null,
selected: null,
AXselected: null,
AXfocused: null,
searchResults: null,
outstandingSearchQuery: null,
AXtoNonAXMapping: {},
@@ -278,17 +289,30 @@ export default class Layout extends SonarPlugin<InspectorState> {
return {elements: updatedElements, AXtoNonAXMapping: updatedMapping};
},
UpdateAXElements(state: InspectorState, {elements}: UpdateElementsArgs) {
UpdateAXElements(
state: InspectorState,
{elements, forFocusEvent}: UpdateAXElementsArgs,
) {
const updatedElements = state.AXelements;
// if focusEvent, previously focused element can be reset
let updatedFocus = forFocusEvent ? null : state.AXfocused;
for (const element of elements) {
if (element.extraInfo.focused) {
updatedFocus = element.id;
}
const current = updatedElements[element.id] || {};
updatedElements[element.id] = {
...current,
...element,
};
}
return {AXelements: updatedElements};
return {
AXelements: updatedElements,
AXfocused: updatedFocus,
};
},
SetRoot(state: InspectorState, {root}: SetRootArgs) {
@@ -442,6 +466,34 @@ export default class Layout extends SonarPlugin<InspectorState> {
});
}
this.client.subscribe('axFocusEvent', (focusEvent: AXFocusEventResult) => {
if (AXToggleButtonEnabled) {
// if focusing, need to update all elements in the tree because
// we don't know which one now has focus
const keys = focusEvent.isFocus
? Object.keys(this.state.AXelements)
: [];
// if unfocusing and currently focused element exists, update only the
// focused element (and only if it is loaded in tree)
if (
!focusEvent.isFocus &&
this.state.AXfocused &&
this.state.AXelements[this.state.AXfocused]
) {
keys.push(this.state.AXfocused);
}
this.getNodes(keys, true, true).then((elements: Array<Element>) => {
this.dispatchAction({
elements,
forFocusEvent: true,
type: 'UpdateAXElements',
});
});
}
});
this.client.subscribe(
'invalidate',
({nodes}: {nodes: Array<{id: ElementID}>}) => {
@@ -713,6 +765,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
AXinitialised,
selected,
AXselected,
AXfocused,
root,
AXroot,
elements,
@@ -792,6 +845,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
onElementExpanded={this.onElementExpanded}
onValueChanged={this.onDataValueChanged}
selected={AXselected}
focused={AXfocused}
searchResults={null}
root={AXroot}
elements={AXelements}

View File

@@ -37,6 +37,7 @@ export type ElementAttribute = {|
export type ElementExtraInfo = {|
nonAXWithAXChild?: boolean,
linkedAXNode?: string,
focused?: boolean,
|};
export type Element = {|

View File

@@ -31,6 +31,8 @@ const ElementsRowContainer = ContextMenu.extends(
backgroundColor: props => {
if (props.selected) {
return colors.macOSTitleBarIconSelected;
} else if (props.focused) {
return colors.lime;
} else if (props.even) {
return colors.light02;
} else {
@@ -47,12 +49,20 @@ const ElementsRowContainer = ContextMenu.extends(
position: 'relative',
'& *': {
color: props => (props.selected ? `${colors.white} !important` : ''),
color: props =>
props.selected || props.focused ? `${colors.white} !important` : '',
},
'&:hover': {
backgroundColor: props =>
props.selected ? colors.macOSTitleBarIconSelected : '#EBF1FB',
backgroundColor: props => {
if (props.selected) {
return colors.macOSTitleBarIconSelected;
} else if (props.focused) {
return colors.lime;
} else {
return '#EBF1FB';
}
},
},
},
{
@@ -122,7 +132,9 @@ class PartialHighlight extends PureComponent<{
static HighlightedText = styled.text({
backgroundColor: '#ffff33',
color: props =>
props.selected ? `${colors.grapeDark3} !important` : 'auto',
props.selected || props.focused
? `${colors.grapeDark3} !important`
: 'auto',
});
render() {
@@ -162,6 +174,7 @@ class ElementsRowAttribute extends PureComponent<{
value: string,
matchingSearchQuery: ?string,
selected: boolean,
focused: boolean,
}> {
render() {
const {name, value, matchingSearchQuery, selected} = this.props;
@@ -195,6 +208,7 @@ type ElementsRowProps = {
id: ElementID,
level: number,
selected: boolean,
focused: boolean,
matchingSearchQuery: ?string,
element: Element,
even: boolean,
@@ -268,6 +282,7 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
id,
level,
selected,
focused,
style,
even,
matchingSearchQuery,
@@ -295,6 +310,7 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
value={attr.value}
matchingSearchQuery={matchingSearchQuery}
selected={selected}
focused={focused}
/>
))
: [];
@@ -327,6 +343,7 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
key={id}
level={level}
selected={selected}
focused={focused}
matchingSearchQuery={matchingSearchQuery}
even={even}
onClick={this.onClick}
@@ -368,6 +385,7 @@ const ElementsBox = FlexColumn.extends({
type ElementsProps = {|
root: ?ElementID,
selected: ?ElementID,
focused?: ?ElementID,
searchResults: ?ElementSearchResultSet,
elements: {[key: ElementID]: Element},
onElementSelected: (key: ElementID) => void,
@@ -532,6 +550,7 @@ export class Elements extends PureComponent<ElementsProps, ElementsState> {
onElementHovered,
onElementSelected,
selected,
focused,
searchResults,
} = this.props;
const {flatElements} = this.state;
@@ -557,6 +576,7 @@ export class Elements extends PureComponent<ElementsProps, ElementsState> {
onElementHovered={onElementHovered}
onElementSelected={onElementSelected}
selected={selected === row.key}
focused={focused === row.key}
matchingSearchQuery={
searchResults && searchResults.matches.has(row.key)
? searchResults.query