diff --git a/android/src/main/java/com/facebook/sonar/plugins/inspector/InspectorSonarPlugin.java b/android/src/main/java/com/facebook/sonar/plugins/inspector/InspectorSonarPlugin.java index 265da760c..cca578099 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/inspector/InspectorSonarPlugin.java +++ b/android/src/main/java/com/facebook/sonar/plugins/inspector/InspectorSonarPlugin.java @@ -142,6 +142,7 @@ public class InspectorSonarPlugin implements SonarPlugin { connection.receive("getSearchResults", mGetSearchResults); connection.receive("getAXRoot", mGetAXRoot); connection.receive("getAXNodes", mGetAXNodes); + connection.receive("onRequestAXFocus", mOnRequestAXFocus); if (mExtensionCommands != null) { for (ExtensionCommand extensionCommand : mExtensionCommands) { @@ -260,6 +261,22 @@ public class InspectorSonarPlugin implements SonarPlugin { } }; + final SonarReceiver mOnRequestAXFocus = + new MainThreadSonarReceiver(mConnection) { + @Override + public void onReceiveOnMainThread(final SonarObject params, final SonarResponder responder) + throws Exception { + final String nodeId = params.getString("id"); + + final Object obj = mObjectTracker.get(nodeId); + if (obj == null || !(obj instanceof View)) { + return; + } + + ((View) obj).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } + }; + final SonarReceiver mSetData = new MainThreadSonarReceiver(mConnection) { @Override diff --git a/src/fb-stubs/AXLayoutExtender.js b/src/fb-stubs/AXLayoutExtender.js index c581cef61..5394461cd 100644 --- a/src/fb-stubs/AXLayoutExtender.js +++ b/src/fb-stubs/AXLayoutExtender.js @@ -6,7 +6,12 @@ */ import {Component} from 'react'; -import type {Element, ElementID, ElementSearchResultSet} from 'sonar'; +import type { + Element, + ElementID, + ElementSearchResultSet, + ContextMenuExtension, +} from 'sonar'; export class AXElementsInspector extends Component<{ onElementExpanded: (key: ElementID, deep: boolean) => void, @@ -19,6 +24,7 @@ export class AXElementsInspector extends Component<{ root: ?ElementID, elements: {[key: ElementID]: Element}, useAppSidebar?: boolean, + contextMenuExtensions: Array, }> { render() { return null; diff --git a/src/plugins/layout/index.js b/src/plugins/layout/index.js index 59625e1a0..4df7d70d6 100644 --- a/src/plugins/layout/index.js +++ b/src/plugins/layout/index.js @@ -791,6 +791,17 @@ export default class Layout extends SonarPlugin { }); }); + getAXContextMenuExtensions() { + return [ + { + label: 'Focus', + click: (id: ElementID) => { + this.client.send('onRequestAXFocus', {id}); + }, + }, + ]; + } + onDataValueChanged = (path: Array, value: any) => { const selected = this.state.inAXMode ? this.state.AXselected @@ -935,6 +946,7 @@ export default class Layout extends SonarPlugin { searchResults={null} root={AXroot} elements={AXelements} + contextMenuExtensions={this.getAXContextMenuExtensions()} /> ) : null} diff --git a/src/ui/components/elements-inspector/elements.js b/src/ui/components/elements-inspector/elements.js index d05cdc2fe..c4c56907c 100644 --- a/src/ui/components/elements-inspector/elements.js +++ b/src/ui/components/elements-inspector/elements.js @@ -215,6 +215,7 @@ type ElementsRowProps = { childrenCount: number, onElementHovered: ?(key: ?ElementID) => void, style: ?Object, + contextMenuExtensions: Array, }; type ElementsRowState = {| @@ -232,7 +233,7 @@ class ElementsRow extends PureComponent { getContextMenu = (): Array => { const {props} = this; - return [ + const items = [ { type: 'separator', }, @@ -249,6 +250,15 @@ class ElementsRow extends PureComponent { }, }, ]; + + for (const extension of props.contextMenuExtensions) { + items.push({ + label: extension.label, + click: () => extension.click(this.props.id), + }); + } + + return items; }; onClick = () => { @@ -390,6 +400,7 @@ type ElementsProps = {| onElementExpanded: (key: ElementID, deep: boolean) => void, onElementHovered: ?(key: ?ElementID) => void, alternateRowColor?: boolean, + contextMenuExtensions?: Array, |}; type ElementsState = {| @@ -398,6 +409,11 @@ type ElementsState = {| maxDepth: number, |}; +export type ContextMenuExtension = {| + label: string, + click: ElementID => void, +|}; + export class Elements extends PureComponent { static defaultProps = { alternateRowColor: true, @@ -554,6 +570,7 @@ export class Elements extends PureComponent { selected, focused, searchResults, + contextMenuExtensions, } = this.props; const {flatElements} = this.state; const row = flatElements[index]; @@ -593,6 +610,7 @@ export class Elements extends PureComponent { elements={elements} childrenCount={childrenCount} style={style} + contextMenuExtensions={contextMenuExtensions || []} /> ); }; diff --git a/src/ui/index.js b/src/ui/index.js index 6a77bde84..498744487 100644 --- a/src/ui/index.js +++ b/src/ui/index.js @@ -159,6 +159,9 @@ export type { ElementSearchResultSet, } from './components/elements-inspector/ElementsInspector.js'; export {Elements} from './components/elements-inspector/elements.js'; +export type { + ContextMenuExtension, +} from './components/elements-inspector/elements.js'; export { default as ElementsInspector, } from './components/elements-inspector/ElementsInspector.js';