fix: float browser preview zoom controls (#952)

Move the preview zoom control out of the address toolbar and into a bottom-right floating control area inside the browser preview stage. Keep the native WebContentsView bounds clear of the control strip so the DOM control remains interactive on high-DPI Windows displays.

Tested: cd desktop && bun run test -- src/components/browser/BrowserSurface.test.tsx

Tested: cd desktop && bun run lint

Tested: cd desktop && bun run build

Not-tested: Windows high-DPI real-device smoke.

Confidence: medium

Scope-risk: narrow
This commit is contained in:
程序员阿江(Relakkes) 2026-07-08 14:52:52 +08:00
parent e4e7026cde
commit f269887fe7
2 changed files with 26 additions and 11 deletions

View File

@ -182,16 +182,19 @@ describe('BrowserSurface', () => {
expect(bridge.message).toHaveBeenLastCalledWith({ v: 1, type: 'exit-picker' })
})
it('renders toolbar preview zoom controls that update the native preview zoom', async () => {
it('renders floating preview zoom controls that update the native preview zoom', async () => {
useBrowserPanelStore.getState().open('s1', 'http://localhost:5173/')
useBrowserPanelStore.getState().setReady('s1')
render(<BrowserSurface sessionId="s1" />)
const controls = screen.getByTestId('browser-zoom-controls')
const actions = screen.getByTestId('browser-toolbar-actions')
const floatingControls = screen.getByTestId('browser-preview-floating-controls')
expect(controls).toHaveTextContent('100%')
expect(actions).toContainElement(controls)
expect(controls.compareDocumentPosition(screen.getByTestId('preview-host')) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
expect(actions).not.toContainElement(controls)
expect(floatingControls).toContainElement(controls)
expect(screen.getByTestId('browser-preview-stage')).toContainElement(floatingControls)
expect(screen.getByTestId('preview-host').compareDocumentPosition(floatingControls) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
fireEvent.click(screen.getByLabelText('缩小预览'))
expect(useBrowserPanelStore.getState().bySession['s1']!.zoom).toBe(0.9)

View File

@ -208,7 +208,7 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
}
const zoomButtonClass = [
'inline-flex h-7 w-7 items-center justify-center rounded-full transition-colors',
'inline-flex h-8 w-8 items-center justify-center rounded-full transition-colors',
'text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-container-low)] hover:text-[var(--color-text-primary)]',
'disabled:cursor-default disabled:opacity-35 disabled:hover:bg-transparent disabled:hover:text-[var(--color-text-secondary)]',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)]',
@ -217,7 +217,9 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
const zoomControls = (
<div
data-testid="browser-zoom-controls"
className="inline-flex h-8 shrink-0 items-center gap-1 rounded-full border border-[var(--color-border)] bg-[var(--color-surface)] px-1 shadow-sm"
role="group"
aria-label="预览缩放控制"
className="inline-flex h-10 shrink-0 items-center gap-1 rounded-full border border-[var(--color-border)] bg-[var(--color-surface)] px-1.5 shadow-lg"
>
<button
aria-label="缩小预览"
@ -285,7 +287,6 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
>
<MousePointer2 size={16} />
</button>
{zoomControls}
</>
)
@ -317,12 +318,23 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
rightActions={previewActions}
/>
<div className="flex min-h-0 flex-1 flex-col bg-[var(--color-surface)]">
<div ref={hostRef} className="relative min-h-0 flex-1 overflow-hidden" data-testid="preview-host">
{session.loading && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-[var(--color-surface)] text-[var(--color-text-tertiary)]">
<Loader2 size={18} className="animate-spin" aria-label="加载中" />
<div className="relative min-h-0 flex-1 overflow-hidden" data-testid="browser-preview-stage">
{/* WebContentsView renders above DOM, so keep the floating controls outside its bounds. */}
<div ref={hostRef} className="absolute inset-x-0 top-0 bottom-12 overflow-hidden" data-testid="preview-host">
{session.loading && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-[var(--color-surface)] text-[var(--color-text-tertiary)]">
<Loader2 size={18} className="animate-spin" aria-label="加载中" />
</div>
)}
</div>
<div
data-testid="browser-preview-floating-controls"
className="pointer-events-none absolute inset-x-0 bottom-0 z-10 flex h-12 items-end justify-end px-3 pb-2"
>
<div className="pointer-events-auto">
{zoomControls}
</div>
)}
</div>
</div>
</div>
</div>