fix(web): suppress focus ring on pointer interactions while preserving keyboard accessibility

This commit is contained in:
weishu
2025-12-20 19:52:56 +08:00
parent ce5bdc8bed
commit bf18224d05
3 changed files with 49 additions and 2 deletions
+12 -1
View File
@@ -1,6 +1,7 @@
import { diffLines } from 'diff'
import { useMemo } from 'react'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { usePointerFocusRing } from '@/hooks/usePointerFocusRing'
import { cn } from '@/lib/utils'
export function DiffView(props: {
@@ -10,6 +11,7 @@ export function DiffView(props: {
variant?: 'preview' | 'inline'
}) {
const variant = props.variant ?? 'preview'
const { suppressFocusRing, onTriggerPointerDown, onTriggerKeyDown, onTriggerBlur } = usePointerFocusRing()
const stats = useMemo(() => {
const oldChars = props.oldString.length
@@ -37,7 +39,16 @@ export function DiffView(props: {
return (
<Dialog>
<DialogTrigger asChild>
<button type="button" className="w-full text-left">
<button
type="button"
className={cn(
'w-full text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--app-link)]',
suppressFocusRing && 'focus-visible:ring-0'
)}
onPointerDown={onTriggerPointerDown}
onKeyDown={onTriggerKeyDown}
onBlur={onTriggerBlur}
>
<div className="overflow-hidden rounded-md border border-[var(--app-border)] bg-[var(--app-subtle-bg)] hover:bg-[var(--app-secondary-bg)] transition-colors">
{props.filePath ? (
<div className="border-b border-[var(--app-border)] bg-[var(--app-subtle-bg)] px-2 py-1 text-xs text-[var(--app-hint)] truncate">
+13 -1
View File
@@ -13,6 +13,8 @@ import { isAskUserQuestionToolName } from '@/components/ToolCard/askUserQuestion
import { getToolPresentation } from '@/components/ToolCard/knownTools'
import { getToolFullViewComponent, getToolViewComponent } from '@/components/ToolCard/views/_all'
import { getToolResultViewComponent } from '@/components/ToolCard/views/_results'
import { usePointerFocusRing } from '@/hooks/usePointerFocusRing'
import { cn } from '@/lib/utils'
function isObject(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === 'object'
@@ -341,6 +343,7 @@ function ToolCardInner(props: ToolCardProps) {
))
const hasBody = showInline || taskSummary !== null || showsPermissionFooter
const stateColor = statusColorClass(props.block.tool.state)
const { suppressFocusRing, onTriggerPointerDown, onTriggerKeyDown, onTriggerBlur } = usePointerFocusRing()
const header = (
<div className="flex flex-col gap-1">
@@ -378,7 +381,16 @@ function ToolCardInner(props: ToolCardProps) {
<CardHeader className="p-3 space-y-0">
<Dialog>
<DialogTrigger asChild>
<button type="button" className="w-full text-left">
<button
type="button"
className={cn(
'w-full text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--app-link)]',
suppressFocusRing && 'focus-visible:ring-0'
)}
onPointerDown={onTriggerPointerDown}
onKeyDown={onTriggerKeyDown}
onBlur={onTriggerBlur}
>
{header}
</button>
</DialogTrigger>
+24
View File
@@ -0,0 +1,24 @@
import { useCallback, useState, type FocusEvent, type KeyboardEvent, type PointerEvent } from 'react'
export function usePointerFocusRing() {
const [suppressFocusRing, setSuppressFocusRing] = useState(false)
const onTriggerPointerDown = useCallback((_event: PointerEvent<HTMLElement>) => {
setSuppressFocusRing(true)
}, [])
const onTriggerKeyDown = useCallback((_event: KeyboardEvent<HTMLElement>) => {
setSuppressFocusRing(false)
}, [])
const onTriggerBlur = useCallback((_event: FocusEvent<HTMLElement>) => {
setSuppressFocusRing(false)
}, [])
return {
suppressFocusRing,
onTriggerPointerDown,
onTriggerKeyDown,
onTriggerBlur
}
}