fix: improve mobile touch event handling for Roo Cloud interaction

- Added touch-manipulation CSS to Button component for better mobile responsiveness
- Added onTouchEnd handlers to FollowUpSuggest buttons to prevent ghost clicks
- Added touch event handlers to all interactive buttons in ChatTextArea
- Ensured clipboard copy button in follow-up suggestions is accessible on mobile

This fixes the issue where users couldn't select answers or send messages on Roo Cloud mobile interface.
This commit is contained in:
Roo Code 2025-10-18 16:42:23 +00:00
parent a8f87d2b1d
commit 6b4d64a650
3 changed files with 49 additions and 9 deletions

View file

@ -1090,13 +1090,21 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
aria-label={t("chat:addImages")}
disabled={shouldDisableImages}
onClick={!shouldDisableImages ? onSelectImages : undefined}
onTouchEnd={
!shouldDisableImages
? (e) => {
e.preventDefault()
onSelectImages()
}
: undefined
}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-1000",
"cursor-pointer",
"cursor-pointer touch-manipulation",
!shouldDisableImages
? "opacity-50 hover:opacity-100 delay-750 pointer-events-auto"
: "opacity-0 pointer-events-none duration-200 delay-0",
@ -1115,13 +1123,17 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
aria-label={t("chat:enhancePrompt")}
disabled={false}
onClick={handleEnhancePrompt}
onTouchEnd={(e) => {
e.preventDefault()
handleEnhancePrompt()
}}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-1000",
"cursor-pointer",
"cursor-pointer touch-manipulation",
hasInputContent
? "opacity-50 hover:opacity-100 delay-750 pointer-events-auto"
: "opacity-0 pointer-events-none duration-200 delay-0",
@ -1139,6 +1151,10 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
aria-label={t("chat:cancel.title")}
disabled={false}
onClick={onCancel}
onTouchEnd={(e) => {
e.preventDefault()
onCancel?.()
}}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
@ -1148,7 +1164,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
"cursor-pointer touch-manipulation",
)}>
<MessageSquareX className="w-4 h-4" />
</button>
@ -1159,6 +1175,10 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
aria-label={t("chat:sendMessage")}
disabled={false}
onClick={onSend}
onTouchEnd={(e) => {
e.preventDefault()
onSend()
}}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
@ -1172,7 +1192,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
hasInputContent && "active:bg-[rgba(255,255,255,0.1)]",
hasInputContent && "cursor-pointer",
hasInputContent && "cursor-pointer touch-manipulation",
)}>
<SendHorizontal className="w-4 h-4" />
</button>
@ -1244,6 +1264,10 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
<button
aria-label={t("chat:stopTts")}
onClick={() => vscode.postMessage({ type: "stopTts" })}
onTouchEnd={(e) => {
e.preventDefault()
vscode.postMessage({ type: "stopTts" })
}}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
@ -1253,7 +1277,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
"cursor-pointer touch-manipulation",
)}>
<VolumeX className="w-4 h-4" />
</button>

View file

@ -113,8 +113,13 @@ export const FollowUpSuggest = ({
className="bg-vscode-editor-background rounded-sm w-full relative group">
<Button
variant="outline"
className="text-left whitespace-normal break-words w-full h-auto px-3 py-2 justify-start pr-8"
className="text-left whitespace-normal break-words w-full h-auto px-3 py-2 justify-start pr-8 touch-manipulation"
onClick={(event) => handleSuggestionClick(suggestion, event)}
onTouchEnd={(event) => {
// Prevent ghost clicks on mobile
event.preventDefault()
handleSuggestionClick(suggestion, event as any)
}}
aria-label={suggestion.answer}>
{suggestion.answer}
{isFirstSuggestion && countdown !== null && !suggestionSelected && !isAnswered && (
@ -133,7 +138,7 @@ export const FollowUpSuggest = ({
)}
<StandardTooltip content={t("chat:followUpSuggest.copyToInput")}>
<div
className="absolute cursor-pointer top-2 right-3 opacity-0 group-hover:opacity-100 transition-opacity"
className="absolute cursor-pointer top-2 right-3 opacity-0 group-hover:opacity-100 transition-opacity touch-manipulation"
onClick={(e) => {
e.stopPropagation()
// Cancel the auto-approve timer when edit button is clicked
@ -141,7 +146,18 @@ export const FollowUpSuggest = ({
onCancelAutoApproval?.()
// Simulate shift-click by directly calling the handler with shiftKey=true.
onSuggestionClick?.(suggestion, { ...e, shiftKey: true })
}}>
}}
onTouchEnd={(e) => {
e.stopPropagation()
e.preventDefault()
// Cancel the auto-approve timer when edit button is clicked
setSuggestionSelected(true)
onCancelAutoApproval?.()
// Simulate shift-click by directly calling the handler with shiftKey=true.
onSuggestionClick?.(suggestion, { ...e, shiftKey: true } as any)
}}
role="button"
tabIndex={0}>
<ClipboardCopy className="w-4" />
</div>
</StandardTooltip>

View file

@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer active:opacity-80",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer active:opacity-80 touch-manipulation",
{
variants: {
variant: {