mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(lint): move the doc preview guest-focus handoff into its own hook (#28110)
HtmlDocPreview.tsx reached 403 lines on main (#27608 + #27875), over the 400-line limit for .tsx, so lint and the pre-commit hook fail on any branch that merges main. The focus handoff effect moves unchanged into use-doc-preview-guest-focus.ts. Co-authored-by: Orca Worker <orca-worker@localhost>
This commit is contained in:
@@ -34,12 +34,11 @@ import {
|
||||
import { DocPreviewToolbar } from './doc-preview-toolbar'
|
||||
import { buildDocPreviewShareControl } from './doc-preview-artifact-share'
|
||||
import { useDocPreviewWebviewHistory } from './doc-preview-webview-history'
|
||||
import { useDocPreviewGuestFocus } from './use-doc-preview-guest-focus'
|
||||
import { useDocPreviewGuestTools } from './use-doc-preview-guest-tools'
|
||||
|
||||
type PreviewState = 'loading' | 'ready' | 'unavailable'
|
||||
|
||||
/** Frames a preview keeps offering focus to a guest that is still attaching. */
|
||||
const GUEST_FOCUS_FRAMES = 10
|
||||
const MAX_ASSET_FAILURES = 50
|
||||
|
||||
export function HtmlDocPreview({
|
||||
@@ -301,58 +300,13 @@ export function HtmlDocPreview({
|
||||
.recordWorkspaceDocVisit({ kind: 'workspace-doc', worktreeId, filePath }, null)
|
||||
}, [filePath, worktreeId])
|
||||
|
||||
// Why the guest is handed focus: a preview has no address bar to make the usual handoff, so a
|
||||
// surfaced document would otherwise look active while its keyboard and link input land elsewhere.
|
||||
useEffect(() => {
|
||||
if (!holdsGuestFocus || state !== 'ready') {
|
||||
return
|
||||
}
|
||||
let frameId = 0
|
||||
let attempts = 0
|
||||
let claimedOnly = false
|
||||
const focusGuest = (): void => {
|
||||
const webview = webviewRef.current
|
||||
attempts += 1
|
||||
// Why a re-offer yields: it is a handoff for focus nothing else wanted, and the reader
|
||||
// pressing a tab lands here first. Taking it back would fight them for the keyboard, which
|
||||
// is what shut the tab strip while a preview was open.
|
||||
if (
|
||||
claimedOnly &&
|
||||
document.activeElement !== document.body &&
|
||||
document.activeElement !== webview
|
||||
) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
webview?.focus()
|
||||
} catch {
|
||||
// Why swallowed: WebViewElement.focus() reads null internals once the guest is destroyed.
|
||||
return
|
||||
}
|
||||
// Why retried: the guest takes focus only once it is attached and laid out, a frame or two
|
||||
// after it reports ready.
|
||||
if (document.activeElement !== webview && attempts < GUEST_FOCUS_FRAMES) {
|
||||
frameId = window.requestAnimationFrame(focusGuest)
|
||||
}
|
||||
}
|
||||
const offerFocus = (yieldToOtherClaims: boolean): void => {
|
||||
window.cancelAnimationFrame(frameId)
|
||||
attempts = 0
|
||||
claimedOnly = yieldToOtherClaims
|
||||
frameId = window.requestAnimationFrame(focusGuest)
|
||||
}
|
||||
// Why assertive: the reader just made this preview their surface, so the handoff is the point.
|
||||
offerFocus(false)
|
||||
// Why re-offered on the window's own focus: another app taking the front takes focus out of the
|
||||
// guest, and coming back puts it on the embedder. Nothing hands it on, so the route out of the
|
||||
// preview would stay shut until something remounted it.
|
||||
const reofferFocus = (): void => offerFocus(true)
|
||||
window.addEventListener('focus', reofferFocus)
|
||||
return () => {
|
||||
window.removeEventListener('focus', reofferFocus)
|
||||
window.cancelAnimationFrame(frameId)
|
||||
}
|
||||
}, [holdsGuestFocus, previewId, remintCount, state])
|
||||
useDocPreviewGuestFocus({
|
||||
webviewRef,
|
||||
holdsGuestFocus,
|
||||
ready: state === 'ready',
|
||||
previewId,
|
||||
remintCount
|
||||
})
|
||||
|
||||
// Why: a grant is pinned to the owner ids resolved when it was minted, so after a pairing or
|
||||
// SSH reconnect the old one reads nothing and reloading the guest would just refetch the
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useEffect, type MutableRefObject } from 'react'
|
||||
|
||||
/** Frames a preview keeps offering focus to a guest that is still attaching. */
|
||||
const GUEST_FOCUS_FRAMES = 10
|
||||
|
||||
/**
|
||||
* Hands a ready preview's guest the keyboard. A preview has no address bar to make the usual
|
||||
* handoff, so a surfaced document would otherwise look active while its keyboard and link input
|
||||
* land elsewhere. `remintCount` re-runs the handoff when a re-mint replaces the guest.
|
||||
*/
|
||||
export function useDocPreviewGuestFocus({
|
||||
webviewRef,
|
||||
holdsGuestFocus,
|
||||
ready,
|
||||
previewId,
|
||||
remintCount
|
||||
}: {
|
||||
webviewRef: MutableRefObject<Electron.WebviewTag | null>
|
||||
holdsGuestFocus: boolean
|
||||
ready: boolean
|
||||
previewId: string
|
||||
remintCount: number
|
||||
}): void {
|
||||
useEffect(() => {
|
||||
if (!holdsGuestFocus || !ready) {
|
||||
return
|
||||
}
|
||||
let frameId = 0
|
||||
let attempts = 0
|
||||
let claimedOnly = false
|
||||
const focusGuest = (): void => {
|
||||
const webview = webviewRef.current
|
||||
attempts += 1
|
||||
// Why a re-offer yields: it is a handoff for focus nothing else wanted, and the reader
|
||||
// pressing a tab lands here first. Taking it back would fight them for the keyboard, which
|
||||
// is what shut the tab strip while a preview was open.
|
||||
if (
|
||||
claimedOnly &&
|
||||
document.activeElement !== document.body &&
|
||||
document.activeElement !== webview
|
||||
) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
webview?.focus()
|
||||
} catch {
|
||||
// Why swallowed: WebViewElement.focus() reads null internals once the guest is destroyed.
|
||||
return
|
||||
}
|
||||
// Why retried: the guest takes focus only once it is attached and laid out, a frame or two
|
||||
// after it reports ready.
|
||||
if (document.activeElement !== webview && attempts < GUEST_FOCUS_FRAMES) {
|
||||
frameId = window.requestAnimationFrame(focusGuest)
|
||||
}
|
||||
}
|
||||
const offerFocus = (yieldToOtherClaims: boolean): void => {
|
||||
window.cancelAnimationFrame(frameId)
|
||||
attempts = 0
|
||||
claimedOnly = yieldToOtherClaims
|
||||
frameId = window.requestAnimationFrame(focusGuest)
|
||||
}
|
||||
// Why assertive: the reader just made this preview their surface, so the handoff is the point.
|
||||
offerFocus(false)
|
||||
// Why re-offered on the window's own focus: another app taking the front takes focus out of the
|
||||
// guest, and coming back puts it on the embedder. Nothing hands it on, so the route out of the
|
||||
// preview would stay shut until something remounted it.
|
||||
const reofferFocus = (): void => offerFocus(true)
|
||||
window.addEventListener('focus', reofferFocus)
|
||||
return () => {
|
||||
window.removeEventListener('focus', reofferFocus)
|
||||
window.cancelAnimationFrame(frameId)
|
||||
}
|
||||
}, [holdsGuestFocus, previewId, ready, remintCount, webviewRef])
|
||||
}
|
||||
Reference in New Issue
Block a user