fix: cancel review dialog focus frames (#3493)

This commit is contained in:
Neil
2026-05-29 23:09:20 -07:00
committed by GitHub
parent 386fc42564
commit df6a1c1521
2 changed files with 96 additions and 10 deletions
@@ -478,6 +478,34 @@ function PRReviewersPanel({
const patchWorkItem = useAppStore((s) => s.patchWorkItem)
const settings = useAppStore((s) => s.settings)
const reviewerInputRef = useRef<HTMLInputElement | null>(null)
const reviewerInputFocusFrameRef = useRef<number | null>(null)
const reviewerPanelMountedRef = useRef(true)
const cancelReviewerInputFocusFrame = useCallback((): void => {
if (reviewerInputFocusFrameRef.current !== null) {
cancelAnimationFrame(reviewerInputFocusFrameRef.current)
reviewerInputFocusFrameRef.current = null
}
}, [])
const scheduleReviewerInputFocus = useCallback((): void => {
if (!reviewerPanelMountedRef.current) {
return
}
cancelReviewerInputFocusFrame()
reviewerInputFocusFrameRef.current = requestAnimationFrame(() => {
reviewerInputFocusFrameRef.current = null
reviewerInputRef.current?.focus()
})
}, [cancelReviewerInputFocusFrame])
useEffect(() => {
reviewerPanelMountedRef.current = true
return () => {
reviewerPanelMountedRef.current = false
cancelReviewerInputFocusFrame()
}
}, [cancelReviewerInputFocusFrame])
useEffect(() => {
setLocalReviewRequests(item.reviewRequests ?? [])
@@ -736,7 +764,7 @@ function PRReviewersPanel({
await (selectedReviewerLogins.has(reviewer.login.toLowerCase())
? handleRemoveReviewers([reviewer.login])
: handleRequestReview([reviewer.login]))
requestAnimationFrame(() => reviewerInputRef.current?.focus())
scheduleReviewerInputFocus()
}
const handleReviewerPickerOpenChange = (nextOpen: boolean): void => {
@@ -745,7 +773,7 @@ function PRReviewersPanel({
}
setOpen(nextOpen)
if (nextOpen) {
requestAnimationFrame(() => reviewerInputRef.current?.focus())
scheduleReviewerInputFocus()
return
}
setReviewerInput('')
@@ -2318,6 +2346,7 @@ function ConversationTab({
const [bodyEditing, setBodyEditing] = useState(false)
const [bodySaving, setBodySaving] = useState(false)
const bodyTextareaRef = useRef<HTMLTextAreaElement>(null)
const bodyTextareaFocusFrameRef = useRef<number | null>(null)
const repoAssignees = useRepoAssignees(repoPath, item.repoId)
const commentCounts = useMemo(() => getPRCommentAudienceCounts(comments), [comments])
const visibleComments = useMemo(
@@ -2336,6 +2365,13 @@ function ConversationTab({
[comments, detailsParticipants, item, repoAssignees.data]
)
const cancelBodyTextareaFocusFrame = useCallback((): void => {
if (bodyTextareaFocusFrameRef.current !== null) {
cancelAnimationFrame(bodyTextareaFocusFrameRef.current)
bodyTextareaFocusFrameRef.current = null
}
}, [])
useEffect(() => {
if (replyingTo !== null && !visibleComments.some((comment) => comment.id === replyingTo)) {
setReplyingTo(null)
@@ -2349,10 +2385,17 @@ function ConversationTab({
}, [body, bodyEditing, item.id])
useEffect(() => {
if (bodyEditing) {
requestAnimationFrame(() => bodyTextareaRef.current?.focus())
if (!bodyEditing) {
cancelBodyTextareaFocusFrame()
return cancelBodyTextareaFocusFrame
}
}, [bodyEditing])
cancelBodyTextareaFocusFrame()
bodyTextareaFocusFrameRef.current = requestAnimationFrame(() => {
bodyTextareaFocusFrameRef.current = null
bodyTextareaRef.current?.focus()
})
return cancelBodyTextareaFocusFrame
}, [bodyEditing, cancelBodyTextareaFocusFrame])
const bodySlug = useMemo(() => parseOwnerRepoFromItemUrl(item.url), [item.url])
const markdownGitHubRepo = useMemo(
@@ -478,6 +478,34 @@ function PRReviewersPanel({
const patchWorkItem = useAppStore((s) => s.patchWorkItem)
const settings = useAppStore((s) => s.settings)
const reviewerInputRef = useRef<HTMLInputElement | null>(null)
const reviewerInputFocusFrameRef = useRef<number | null>(null)
const reviewerPanelMountedRef = useRef(true)
const cancelReviewerInputFocusFrame = useCallback((): void => {
if (reviewerInputFocusFrameRef.current !== null) {
cancelAnimationFrame(reviewerInputFocusFrameRef.current)
reviewerInputFocusFrameRef.current = null
}
}, [])
const scheduleReviewerInputFocus = useCallback((): void => {
if (!reviewerPanelMountedRef.current) {
return
}
cancelReviewerInputFocusFrame()
reviewerInputFocusFrameRef.current = requestAnimationFrame(() => {
reviewerInputFocusFrameRef.current = null
reviewerInputRef.current?.focus()
})
}, [cancelReviewerInputFocusFrame])
useEffect(() => {
reviewerPanelMountedRef.current = true
return () => {
reviewerPanelMountedRef.current = false
cancelReviewerInputFocusFrame()
}
}, [cancelReviewerInputFocusFrame])
useEffect(() => {
setLocalReviewRequests(item.reviewRequests ?? [])
@@ -736,7 +764,7 @@ function PRReviewersPanel({
await (selectedReviewerLogins.has(reviewer.login.toLowerCase())
? handleRemoveReviewers([reviewer.login])
: handleRequestReview([reviewer.login]))
requestAnimationFrame(() => reviewerInputRef.current?.focus())
scheduleReviewerInputFocus()
}
const handleReviewerPickerOpenChange = (nextOpen: boolean): void => {
@@ -745,7 +773,7 @@ function PRReviewersPanel({
}
setOpen(nextOpen)
if (nextOpen) {
requestAnimationFrame(() => reviewerInputRef.current?.focus())
scheduleReviewerInputFocus()
return
}
setReviewerInput('')
@@ -2447,6 +2475,7 @@ function ConversationTab({
const [bodyEditing, setBodyEditing] = useState(false)
const [bodySaving, setBodySaving] = useState(false)
const bodyTextareaRef = useRef<HTMLTextAreaElement>(null)
const bodyTextareaFocusFrameRef = useRef<number | null>(null)
const repoAssignees = useRepoAssignees(repoPath, item.repoId)
const commentCounts = useMemo(() => getPRCommentAudienceCounts(comments), [comments])
const visibleComments = useMemo(
@@ -2465,6 +2494,13 @@ function ConversationTab({
[comments, detailsParticipants, item, repoAssignees.data]
)
const cancelBodyTextareaFocusFrame = useCallback((): void => {
if (bodyTextareaFocusFrameRef.current !== null) {
cancelAnimationFrame(bodyTextareaFocusFrameRef.current)
bodyTextareaFocusFrameRef.current = null
}
}, [])
useEffect(() => {
if (replyingTo !== null && !visibleComments.some((comment) => comment.id === replyingTo)) {
setReplyingTo(null)
@@ -2478,10 +2514,17 @@ function ConversationTab({
}, [body, bodyEditing, item.id])
useEffect(() => {
if (bodyEditing) {
requestAnimationFrame(() => bodyTextareaRef.current?.focus())
if (!bodyEditing) {
cancelBodyTextareaFocusFrame()
return cancelBodyTextareaFocusFrame
}
}, [bodyEditing])
cancelBodyTextareaFocusFrame()
bodyTextareaFocusFrameRef.current = requestAnimationFrame(() => {
bodyTextareaFocusFrameRef.current = null
bodyTextareaRef.current?.focus()
})
return cancelBodyTextareaFocusFrame
}, [bodyEditing, cancelBodyTextareaFocusFrame])
const bodySlug = useMemo(() => parseOwnerRepoFromItemUrl(item.url), [item.url])
const markdownGitHubRepo = useMemo(