mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
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:
parent
f18cf3d7ea
commit
e484bfff32
10 changed files with 99 additions and 8 deletions
5
.changeset/khaki-clocks-float.md
Normal file
5
.changeset/khaki-clocks-float.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"roo-cline": patch
|
||||
---
|
||||
|
||||
Always focus the panel when clicked to ensure menu buttons are available
|
||||
|
|
@ -173,7 +173,7 @@ export class TelemetryService {
|
|||
itemType,
|
||||
itemName,
|
||||
target,
|
||||
... (properties || {}),
|
||||
...(properties || {}),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export const commandIds = [
|
|||
|
||||
"focusInput",
|
||||
"acceptInput",
|
||||
"focusPanel",
|
||||
] as const
|
||||
|
||||
export type CommandId = (typeof commandIds)[number]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ export interface WebviewMessage {
|
|||
| "clearIndexData"
|
||||
| "indexingStatusUpdate"
|
||||
| "indexCleared"
|
||||
| "focusPanelRequest"
|
||||
| "codebaseIndexConfig"
|
||||
| "setHistoryPreviewCollapsed"
|
||||
| "openExternal"
|
||||
|
|
|
|||
27
src/utils/focusPanel.ts
Normal file
27
src/utils/focusPanel.ts
Normal 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`)
|
||||
}
|
||||
}
|
||||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * from "./useClipboard"
|
||||
export * from "./useRooPortal"
|
||||
export * from "./useNonInteractiveClick"
|
||||
|
|
|
|||
36
webview-ui/src/components/ui/hooks/useNonInteractiveClick.ts
Normal file
36
webview-ui/src/components/ui/hooks/useNonInteractiveClick.ts
Normal 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])
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue