Skip to content

Commit e484bff

Browse files
hassoncsdaniel-lxsmrubens
authored
Reapply "Always focus the panel when clicked to ensure menu buttons are visible" (#4598)
Co-authored-by: Daniel Riccio <[email protected]> Co-authored-by: Matt Rubens <[email protected]>
1 parent f18cf3d commit e484bff

File tree

10 files changed

+100
-9
lines changed

10 files changed

+100
-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
@@ -1464,6 +1464,11 @@ export const webviewMessageHandler = async (
14641464
}
14651465
break
14661466
}
1467+
case "focusPanelRequest": {
1468+
// Execute the focusPanel command to focus the WebView
1469+
await vscode.commands.executeCommand(getCommand("focusPanel"))
1470+
break
1471+
}
14671472
case "filterMarketplaceItems": {
14681473
if (marketplaceManager && message.filters) {
14691474
try {

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export interface WebviewMessage {
156156
| "clearIndexData"
157157
| "indexingStatusUpdate"
158158
| "indexCleared"
159+
| "focusPanelRequest"
159160
| "codebaseIndexConfig"
160161
| "setHistoryPreviewCollapsed"
161162
| "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 && !panel.active) {
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { MarketplaceView } from "./components/marketplace/MarketplaceView"
1919
import ModesView from "./components/modes/ModesView"
2020
import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
2121
import { AccountView } from "./components/account/AccountView"
22+
import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick"
2223

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

@@ -42,6 +43,7 @@ const App = () => {
4243
machineId,
4344
cloudUserInfo,
4445
cloudIsAuthenticated,
46+
renderContext,
4547
mdmCompliant,
4648
} = useExtensionState()
4749

@@ -136,6 +138,15 @@ const App = () => {
136138
// Tell the extension that we are ready to receive messages.
137139
useEffect(() => vscode.postMessage({ type: "webviewDidLaunch" }), [])
138140

141+
// Focus the WebView when non-interactive content is clicked (only in editor/tab mode)
142+
useAddNonInteractiveClickListener(
143+
useCallback(() => {
144+
// Only send focus request if we're in editor (tab) mode, not sidebar
145+
if (renderContext === "editor") {
146+
vscode.postMessage({ type: "focusPanelRequest" })
147+
}
148+
}, [renderContext]),
149+
)
139150
// Track marketplace tab views
140151
useEffect(() => {
141152
if (tab === "marketplace") {
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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 !== "SELECT" &&
20+
target.tagName !== "TEXTAREA" &&
21+
target.tagName !== "VSCODE-TEXT-AREA" &&
22+
target.tagName !== "VSCODE-TEXT-FIELD" &&
23+
!target.isContentEditable
24+
) {
25+
handler()
26+
}
27+
}
28+
29+
// Add listener to the document body to handle all clicks
30+
document.body.addEventListener("click", handleContentClick)
31+
32+
return () => {
33+
document.body.removeEventListener("click", handleContentClick)
34+
}
35+
}, [handler])
36+
}

0 commit comments

Comments
 (0)