Reapply "Always focus the panel when clicked to ensure menu buttons are visible" (#4598)

Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
Chris Hasson 2025-06-17 13:51:56 -07:00 committed by GitHub
parent f18cf3d7ea
commit e484bfff32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 99 additions and 8 deletions

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Always focus the panel when clicked to ensure menu buttons are available

View file

@ -173,7 +173,7 @@ export class TelemetryService {
itemType,
itemName,
target,
... (properties || {}),
...(properties || {}),
})
}

View file

@ -51,6 +51,7 @@ export const commandIds = [
"focusInput",
"acceptInput",
"focusPanel",
] as const
export type CommandId = (typeof commandIds)[number]

View file

@ -8,6 +8,7 @@ import { Package } from "../shared/package"
import { getCommand } from "../utils/commands"
import { ClineProvider } from "../core/webview/ClineProvider"
import { ContextProxy } from "../core/config/ContextProxy"
import { focusPanel } from "../utils/focusPanel"
import { registerHumanRelayCallback, unregisterHumanRelayCallback, handleHumanRelayResponse } from "./humanRelay"
import { handleNewTask } from "./handleTask"
@ -172,20 +173,23 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
},
focusInput: async () => {
try {
const panel = getPanel()
await focusPanel(tabPanel, sidebarPanel)
if (!panel) {
await vscode.commands.executeCommand(`workbench.view.extension.${Package.name}-ActivityBar`)
} else if (panel === tabPanel) {
panel.reveal(vscode.ViewColumn.Active, false)
} else if (panel === sidebarPanel) {
await vscode.commands.executeCommand(`${ClineProvider.sideBarId}.focus`)
// Send focus input message only for sidebar panels
if (sidebarPanel && getPanel() === sidebarPanel) {
provider.postMessageToWebview({ type: "action", action: "focusInput" })
}
} catch (error) {
outputChannel.appendLine(`Error focusing input: ${error}`)
}
},
focusPanel: async () => {
try {
await focusPanel(tabPanel, sidebarPanel)
} catch (error) {
outputChannel.appendLine(`Error focusing panel: ${error}`)
}
},
acceptInput: () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)

View file

@ -1464,6 +1464,11 @@ export const webviewMessageHandler = async (
}
break
}
case "focusPanelRequest": {
// Execute the focusPanel command to focus the WebView
await vscode.commands.executeCommand(getCommand("focusPanel"))
break
}
case "filterMarketplaceItems": {
if (marketplaceManager && message.filters) {
try {

View file

@ -156,6 +156,7 @@ export interface WebviewMessage {
| "clearIndexData"
| "indexingStatusUpdate"
| "indexCleared"
| "focusPanelRequest"
| "codebaseIndexConfig"
| "setHistoryPreviewCollapsed"
| "openExternal"

27
src/utils/focusPanel.ts Normal file
View file

@ -0,0 +1,27 @@
import * as vscode from "vscode"
import { Package } from "../shared/package"
import { ClineProvider } from "../core/webview/ClineProvider"
/**
* Focus the active panel (either tab or sidebar)
* @param tabPanel - The tab panel reference
* @param sidebarPanel - The sidebar panel reference
* @returns Promise that resolves when focus is complete
*/
export async function focusPanel(
tabPanel: vscode.WebviewPanel | undefined,
sidebarPanel: vscode.WebviewView | undefined,
): Promise<void> {
const panel = tabPanel || sidebarPanel
if (!panel) {
// If no panel is open, open the sidebar
await vscode.commands.executeCommand(`workbench.view.extension.${Package.name}-ActivityBar`)
} else if (panel === tabPanel && !panel.active) {
// For tab panels, use reveal to focus
panel.reveal(vscode.ViewColumn.Active, false)
} else if (panel === sidebarPanel) {
// For sidebar panels, focus the sidebar
await vscode.commands.executeCommand(`${ClineProvider.sideBarId}.focus`)
}
}

View file

@ -19,6 +19,7 @@ import { MarketplaceView } from "./components/marketplace/MarketplaceView"
import ModesView from "./components/modes/ModesView"
import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
import { AccountView } from "./components/account/AccountView"
import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick"
type Tab = "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
@ -42,6 +43,7 @@ const App = () => {
machineId,
cloudUserInfo,
cloudIsAuthenticated,
renderContext,
mdmCompliant,
} = useExtensionState()
@ -136,6 +138,15 @@ const App = () => {
// Tell the extension that we are ready to receive messages.
useEffect(() => vscode.postMessage({ type: "webviewDidLaunch" }), [])
// Focus the WebView when non-interactive content is clicked (only in editor/tab mode)
useAddNonInteractiveClickListener(
useCallback(() => {
// Only send focus request if we're in editor (tab) mode, not sidebar
if (renderContext === "editor") {
vscode.postMessage({ type: "focusPanelRequest" })
}
}, [renderContext]),
)
// Track marketplace tab views
useEffect(() => {
if (tab === "marketplace") {

View file

@ -1,2 +1,3 @@
export * from "./useClipboard"
export * from "./useRooPortal"
export * from "./useNonInteractiveClick"

View file

@ -0,0 +1,36 @@
import { useEffect } from "react"
/**
* Hook that listens for clicks on non-interactive elements and calls the provided handler.
*
* Interactive elements (inputs, textareas, selects, contentEditable) are excluded
* to avoid disrupting user typing or form interactions.
*
* @param handler - Function to call when a non-interactive element is clicked
*/
export function useAddNonInteractiveClickListener(handler: () => void) {
useEffect(() => {
const handleContentClick = (e: MouseEvent) => {
const target = e.target as HTMLElement
// Don't trigger for input elements to avoid disrupting typing
if (
target.tagName !== "INPUT" &&
target.tagName !== "SELECT" &&
target.tagName !== "TEXTAREA" &&
target.tagName !== "VSCODE-TEXT-AREA" &&
target.tagName !== "VSCODE-TEXT-FIELD" &&
!target.isContentEditable
) {
handler()
}
}
// Add listener to the document body to handle all clicks
document.body.addEventListener("click", handleContentClick)
return () => {
document.body.removeEventListener("click", handleContentClick)
}
}, [handler])
}