Skip to content

Commit 311b60b

Browse files
authored
fix(ui): prevent accidental prompt clear from tab focus (#461)
## Summary - Fixes #460 by changing the prompt control DOM order so tabbing from the textarea reaches expand before clear. - Clears prompt text through the textarea native delete path so Cmd/Ctrl+Z can restore cleared text where the browser supports native undo. - Keeps the existing app-state fallback for environments where native editing commands are unavailable or throw. ## Validation - npm run typecheck --workspace @codenomad/ui - Reviewed fallback behavior: `document.execCommand` is checked for callability and wrapped in `try/catch`, so unavailable native editing commands still fall back to app-state clearing. ## Manual QA Notes - In a supported desktop/browser host: type text, click clear, then press Cmd/Ctrl+Z and confirm text restores. - Type text, press Tab and confirm focus moves to expand before clear; pressing Tab again reaches clear.
1 parent 283d3d7 commit 311b60b

1 file changed

Lines changed: 31 additions & 8 deletions

File tree

packages/ui/src/components/prompt-input.tsx

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -463,10 +463,33 @@ export default function PromptInput(props: PromptInputProps) {
463463
textareaRef?.focus()
464464
}
465465

466+
function clearTextareaWithUndo() {
467+
const textarea = textareaRef
468+
if (!textarea || textarea.disabled) return false
469+
470+
textarea.focus()
471+
textarea.setSelectionRange(0, textarea.value.length)
472+
473+
let cleared = false
474+
try {
475+
cleared = typeof document !== "undefined" && typeof document.execCommand === "function" && document.execCommand("delete")
476+
} catch {
477+
cleared = false
478+
}
479+
if (!cleared || textarea.value.length > 0) {
480+
textarea.value = ""
481+
}
482+
483+
textarea.dispatchEvent(new Event("input", { bubbles: true }))
484+
return cleared
485+
}
486+
466487
function handleClearPrompt() {
467-
clearPrompt()
468-
clearHistoryDraft()
469488
resetHistoryNavigation()
489+
if (!clearTextareaWithUndo()) {
490+
clearPrompt()
491+
}
492+
clearHistoryDraft()
470493
setShowPicker(false)
471494
setPickerMode("mention")
472495
setAtPosition(null)
@@ -747,6 +770,12 @@ export default function PromptInput(props: PromptInputProps) {
747770
autocomplete="off"
748771
style={inputHeight() !== null ? { height: `${inputHeight()}px`, "min-height": `${inputHeight()}px`, "overflow-y": "auto" } : undefined}
749772
/>
773+
<div class="prompt-expand-button-inline">
774+
<ExpandButton
775+
expandState={expandState}
776+
onToggleExpand={handleExpandToggle}
777+
/>
778+
</div>
750779
<button
751780
type="button"
752781
class="prompt-clear-button prompt-clear-button-inline"
@@ -757,12 +786,6 @@ export default function PromptInput(props: PromptInputProps) {
757786
>
758787
<X class="h-4 w-4" aria-hidden="true" />
759788
</button>
760-
<div class="prompt-expand-button-inline">
761-
<ExpandButton
762-
expandState={expandState}
763-
onToggleExpand={handleExpandToggle}
764-
/>
765-
</div>
766789
<Show when={shouldShowOverlay()}>
767790
<div class={`prompt-input-overlay keyboard-hints ${mode() === "shell" ? "shell-mode" : ""}`}>
768791
<Show

0 commit comments

Comments
 (0)