Skip to content

Commit cf5f65f

Browse files
committed
Reapply "Always focus the panel when clicked to ensure menu buttons are visible" (RooCodeInc#4592)
This reverts commit 85fd86e.
1 parent 85fd86e commit cf5f65f

File tree

10 files changed

+95
-9
lines changed

10 files changed

+95
-9
lines changed

.changeset/khaki-clocks-float.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Always focus the panel when clicked to ensure menu buttons are available

packages/telemetry/src/TelemetryService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export class TelemetryService {
173173
itemType,
174174
itemName,
175175
target,
176-
... (properties || {}),
176+
...(properties || {}),
177177
})
178178
}
179179

packages/types/src/vscode.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export const commandIds = [
5151

5252
"focusInput",
5353
"acceptInput",
54+
"focusPanel",
5455
] as const
5556

5657
export type CommandId = (typeof commandIds)[number]

src/activate/registerCommands.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Package } from "../shared/package"
88
import { getCommand } from "../utils/commands"
99
import { ClineProvider } from "../core/webview/ClineProvider"
1010
import { ContextProxy } from "../core/config/ContextProxy"
11+
import { focusPanel } from "../utils/focusPanel"
1112

1213
import { registerHumanRelayCallback, unregisterHumanRelayCallback, handleHumanRelayResponse } from "./humanRelay"
1314
import { handleNewTask } from "./handleTask"
@@ -172,20 +173,23 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
172173
},
173174
focusInput: async () => {
174175
try {
175-
const panel = getPanel()
176-
177-
if (!panel) {
178-
await vscode.commands.executeCommand(`workbench.view.extension.${Package.name}-ActivityBar`)
179-
} else if (panel === tabPanel) {
180-
panel.reveal(vscode.ViewColumn.Active, false)
181-
} else if (panel === sidebarPanel) {
182-
await vscode.commands.executeCommand(`${ClineProvider.sideBarId}.focus`)
176+
await focusPanel(tabPanel, sidebarPanel)
177+
178+
// Send focus input message only for sidebar panels
179+
if (sidebarPanel && getPanel() === sidebarPanel) {
183180
provider.postMessageToWebview({ type: "action", action: "focusInput" })
184181
}
185182
} catch (error) {
186183
outputChannel.appendLine(`Error focusing input: ${error}`)
187184
}
188185
},
186+
focusPanel: async () => {
187+
try {
188+
await focusPanel(tabPanel, sidebarPanel)
189+
} catch (error) {
190+
outputChannel.appendLine(`Error focusing panel: ${error}`)
191+
}
192+
},
189193
acceptInput: () => {
190194
const visibleProvider = getVisibleProviderOrLog(outputChannel)
191195

src/core/webview/webviewMessageHandler.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,11 @@ export const webviewMessageHandler = async (
14751475
}
14761476
break
14771477
}
1478+
case "focusPanelRequest": {
1479+
// Execute the focusPanel command to focus the WebView
1480+
await vscode.commands.executeCommand(getCommand("focusPanel"))
1481+
break
1482+
}
14781483
case "filterMarketplaceItems": {
14791484
// Check if marketplace is enabled before making API calls
14801485
const { experiments } = await provider.getState()

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export interface WebviewMessage {
150150
| "clearIndexData"
151151
| "indexingStatusUpdate"
152152
| "indexCleared"
153+
| "focusPanelRequest"
153154
| "codebaseIndexConfig"
154155
| "setHistoryPreviewCollapsed"
155156
| "openExternal"

src/utils/focusPanel.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as vscode from "vscode"
2+
import { Package } from "../shared/package"
3+
import { ClineProvider } from "../core/webview/ClineProvider"
4+
5+
/**
6+
* Focus the active panel (either tab or sidebar)
7+
* @param tabPanel - The tab panel reference
8+
* @param sidebarPanel - The sidebar panel reference
9+
* @returns Promise that resolves when focus is complete
10+
*/
11+
export async function focusPanel(
12+
tabPanel: vscode.WebviewPanel | undefined,
13+
sidebarPanel: vscode.WebviewView | undefined,
14+
): Promise<void> {
15+
const panel = tabPanel || sidebarPanel
16+
17+
if (!panel) {
18+
// If no panel is open, open the sidebar
19+
await vscode.commands.executeCommand(`workbench.view.extension.${Package.name}-ActivityBar`)
20+
} else if (panel === tabPanel) {
21+
// For tab panels, use reveal to focus
22+
panel.reveal(vscode.ViewColumn.Active, false)
23+
} else if (panel === sidebarPanel) {
24+
// For sidebar panels, focus the sidebar
25+
await vscode.commands.executeCommand(`${ClineProvider.sideBarId}.focus`)
26+
}
27+
}

webview-ui/src/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { MarketplaceView } from "./components/marketplace/MarketplaceView"
1818
import ModesView from "./components/modes/ModesView"
1919
import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
2020
import { AccountView } from "./components/account/AccountView"
21+
import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick"
2122

2223
type Tab = "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
2324

@@ -135,6 +136,13 @@ const App = () => {
135136
// Tell the extension that we are ready to receive messages.
136137
useEffect(() => vscode.postMessage({ type: "webviewDidLaunch" }), [])
137138

139+
// Focus the WebView when non-interactive content is clicked
140+
useAddNonInteractiveClickListener(
141+
useCallback(() => {
142+
vscode.postMessage({ type: "focusPanelRequest" })
143+
}, []),
144+
)
145+
138146
if (!didHydrateState) {
139147
return null
140148
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./useClipboard"
22
export * from "./useRooPortal"
3+
export * from "./useNonInteractiveClick"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useEffect } from "react"
2+
3+
/**
4+
* Hook that listens for clicks on non-interactive elements and calls the provided handler.
5+
*
6+
* Interactive elements (inputs, textareas, selects, contentEditable) are excluded
7+
* to avoid disrupting user typing or form interactions.
8+
*
9+
* @param handler - Function to call when a non-interactive element is clicked
10+
*/
11+
export function useAddNonInteractiveClickListener(handler: () => void) {
12+
useEffect(() => {
13+
const handleContentClick = (e: MouseEvent) => {
14+
const target = e.target as HTMLElement
15+
16+
// Don't trigger for input elements to avoid disrupting typing
17+
if (
18+
target.tagName !== "INPUT" &&
19+
target.tagName !== "TEXTAREA" &&
20+
target.tagName !== "SELECT" &&
21+
!target.isContentEditable
22+
) {
23+
handler()
24+
}
25+
}
26+
27+
// Add listener to the document body to handle all clicks
28+
document.body.addEventListener("click", handleContentClick)
29+
30+
return () => {
31+
document.body.removeEventListener("click", handleContentClick)
32+
}
33+
}, [handler])
34+
}

0 commit comments

Comments
 (0)