mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
fix(vscode): preserve worktree rename focus
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep multi-project worktree rename inputs focused while selection updates are still settling.
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { Window } from "happy-dom"
|
||||
import { focusQuestionOption, hasQuestionOption } from "../../webview-ui/agent-manager/focus"
|
||||
import { focusQuestionOption, hasQuestionOption, preservesTextFocus } from "../../webview-ui/agent-manager/focus"
|
||||
import { isTextControl } from "../../webview-ui/src/utils/focus"
|
||||
|
||||
describe("Agent Manager focus", () => {
|
||||
it("focuses the first enabled question option", () => {
|
||||
@@ -53,4 +54,18 @@ describe("Agent Manager focus", () => {
|
||||
dock.setAttribute("inert", "")
|
||||
expect(hasQuestionOption(root)).toBe(false)
|
||||
})
|
||||
|
||||
it("preserves focus for an active editable control", () => {
|
||||
const window = new Window()
|
||||
const rename = window.document.createElement("input")
|
||||
rename.className = "am-worktree-rename-input"
|
||||
const prompt = window.document.createElement("textarea")
|
||||
prompt.className = "prompt-input"
|
||||
const button = window.document.createElement("button")
|
||||
|
||||
expect(isTextControl(rename)).toBe(true)
|
||||
expect(preservesTextFocus(rename)).toBe(true)
|
||||
expect(preservesTextFocus(prompt)).toBe(false)
|
||||
expect(isTextControl(button)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { isTextControl } from "../src/utils/focus"
|
||||
|
||||
const OPTION = '[data-component="question-dock"] button[data-slot="question-option"]'
|
||||
|
||||
/** Keep an active editor, such as the worktree rename input, in control. */
|
||||
export const preservesTextFocus = (active: Element | null): boolean =>
|
||||
active !== null && isTextControl(active) && !active.classList.contains("prompt-input")
|
||||
|
||||
export function createChatFocus(deps: {
|
||||
term: () => string | undefined
|
||||
history: () => boolean
|
||||
@@ -7,6 +13,7 @@ export function createChatFocus(deps: {
|
||||
}) {
|
||||
const focus = (force: boolean) => {
|
||||
if ((!force && !document.hasFocus()) || deps.term() || deps.history() || deps.review()) return
|
||||
if (preservesTextFocus(document.activeElement)) return
|
||||
if (!force && document.activeElement?.matches('[role="tab"]')) return
|
||||
if (!force && document.activeElement?.closest('[data-component="question-dock"]')) return
|
||||
if (focusQuestionOption()) return
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import { FileIcon } from "@kilocode/kilo-ui/file-icon"
|
||||
import { Icon } from "@kilocode/kilo-ui/icon"
|
||||
import { showToast } from "@kilocode/kilo-ui/toast"
|
||||
import { isTextControl } from "../../utils/focus"
|
||||
import { useSession } from "../../context/session"
|
||||
import { useLocalTabs } from "../../context/local-tabs"
|
||||
import { useServer } from "../../context/server"
|
||||
@@ -418,8 +419,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const onFocusPrompt = (event: Event) => {
|
||||
const defer = () =>
|
||||
event instanceof CustomEvent && event.detail?.deferFocusToQuestion && props.deferFocusToQuestion?.()
|
||||
const ownsFocus = () => {
|
||||
const active = document.activeElement
|
||||
return active !== textareaRef && isTextControl(active)
|
||||
}
|
||||
const focus = () => {
|
||||
if (defer()) return
|
||||
if (defer() || ownsFocus()) return
|
||||
const ref = textareaRef
|
||||
if (!ref) return
|
||||
ref.focus({ preventScroll: true })
|
||||
@@ -427,7 +432,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
focus()
|
||||
if (!(event instanceof CustomEvent) || !event.detail?.restore) return
|
||||
const restore = () => {
|
||||
if (defer()) return
|
||||
if (defer() || ownsFocus()) return
|
||||
window.focus()
|
||||
focus()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const nonText = new Set(["button", "checkbox", "file", "hidden", "image", "radio", "range", "reset", "submit"])
|
||||
|
||||
/** Whether an element owns editable text focus that should not be stolen. */
|
||||
export const isTextControl = (el: Element | null): boolean => {
|
||||
if (!el) return false
|
||||
if (el.tagName === "TEXTAREA" || el.tagName === "SELECT") return true
|
||||
if (el.tagName === "INPUT") return !nonText.has((el as HTMLInputElement).type.toLowerCase())
|
||||
return el.getAttribute("contenteditable") === "true" || el.getAttribute("role") === "textbox"
|
||||
}
|
||||
Reference in New Issue
Block a user