mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Always focus the panel when clicked to ensure menu buttons are visible (#4511)
feat: Clicking the panel will always focus it Adds a new `focusPanel` command and utility function to allow focusing the active webview panel (tab or sidebar). This command is now triggered when a user clicks on non-interactive content within the webview, improving user experience by automatically bringing the panel into focus. This fixes an issue where sometimes the menu options do not appear because the panel is not in focus: https://github.com/orgs/Kilo-Org/projects/4/views/1?pane=issue&itemId=113936749&issue=Kilo-Org%7Ckilocode%7C619 **Details** - **New Command:** Introduced `focusPanel` command to programmatically focus the webview panel. - **Utility Function:** Created `src/utils/focusPanel.ts` to encapsulate the logic for revealing tab panels or focusing sidebar panels. - **Webview Integration:** Added a `focusPanelRequest` message type to `WebviewMessage` and implemented a handler in `webviewMessageHandler` to execute the new `focusPanel` command. - **User Experience:** Implemented a `useAddNonInteractiveClickListener` hook in `webview-ui` that sends a `focusPanelRequest` message to the extension when a click occurs on non-interactive elements, ensuring the webview gains focus. - **Refactor:** Modified the `focusInput` command to utilize the new `focusPanel` utility, reducing code duplication and centralizing panel focus logic. Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
parent
d5f862a6e0
commit
1b1e5a20f1
10 changed files with 94 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1475,6 +1475,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": {
|
||||
// Check if marketplace is enabled before making API calls
|
||||
const { experiments } = await provider.getState()
|
||||
|
|
|
|||
|
|
@ -150,6 +150,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) {
|
||||
// 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`)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,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"
|
||||
|
||||
|
|
@ -135,6 +136,13 @@ 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
|
||||
useAddNonInteractiveClickListener(
|
||||
useCallback(() => {
|
||||
vscode.postMessage({ type: "focusPanelRequest" })
|
||||
}, []),
|
||||
)
|
||||
|
||||
if (!didHydrateState) {
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * from "./useClipboard"
|
||||
export * from "./useRooPortal"
|
||||
export * from "./useNonInteractiveClick"
|
||||
|
|
|
|||
34
webview-ui/src/components/ui/hooks/useNonInteractiveClick.ts
Normal file
34
webview-ui/src/components/ui/hooks/useNonInteractiveClick.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
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 !== "TEXTAREA" &&
|
||||
target.tagName !== "SELECT" &&
|
||||
!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