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:
committed by
Facebook Github Bot
parent
6939292209
commit
33e6538477
@@ -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},
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -37,6 +37,7 @@ export type ElementAttribute = {|
|
||||
export type ElementExtraInfo = {|
|
||||
nonAXWithAXChild?: boolean,
|
||||
linkedAXNode?: string,
|
||||
focused?: boolean,
|
||||
|};
|
||||
|
||||
export type Element = {|
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user