mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-19 13:33:35 +08:00
Stop zoom slider flicker during drag
Dragging the Settings zoom slider was applying app-wide zoom on every range change. That resized the Settings panel and the range control under the pointer, creating visible flicker and a poor drag feel. The slider now keeps drag movement local to the Settings preview and commits the shared app zoom only when the drag is released or when keyboard/non-pointer changes occur. The control step is also reduced to one percent so adjustments feel less coarse. Constraint: App-wide zoom changes resize the control currently being dragged Rejected: Throttle live app zoom during drag | still resizes the target under the pointer and can flicker on slower WebViews Confidence: high Scope-risk: narrow Directive: Do not apply app-wide zoom continuously during pointer drag; keep live resizing to keyboard shortcuts and final slider commit Tested: bun run check:desktop Tested: agent-browser Settings smoke confirms drag preview keeps cssZoom at 1 until release, then commits 1.23
This commit is contained in:
parent
c279643cce
commit
5965c4a3bd
@ -268,7 +268,7 @@ describe('Settings > General tab', () => {
|
|||||||
expect((uiZoomHeading.compareDocumentPosition(webFetchHeading) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true)
|
expect((uiZoomHeading.compareDocumentPosition(webFetchHeading) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('applies UI zoom while dragging so the slider and app zoom stay synchronized', async () => {
|
it('previews UI zoom while dragging and applies it once on release', async () => {
|
||||||
render(<Settings />)
|
render(<Settings />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('General'))
|
fireEvent.click(screen.getByText('General'))
|
||||||
@ -278,6 +278,7 @@ describe('Settings > General tab', () => {
|
|||||||
expect(screen.getByText('0 resets zoom to 100%.')).toBeInTheDocument()
|
expect(screen.getByText('0 resets zoom to 100%.')).toBeInTheDocument()
|
||||||
|
|
||||||
const slider = screen.getByLabelText('UI Zoom')
|
const slider = screen.getByLabelText('UI Zoom')
|
||||||
|
expect(slider).toHaveAttribute('step', '0.01')
|
||||||
|
|
||||||
fireEvent.pointerDown(slider, { pointerId: 1 })
|
fireEvent.pointerDown(slider, { pointerId: 1 })
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
@ -287,8 +288,8 @@ describe('Settings > General tab', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(screen.getAllByText('125%')).toHaveLength(2)
|
expect(screen.getAllByText('125%')).toHaveLength(2)
|
||||||
expect(useSettingsStore.getState().setUiZoom).toHaveBeenCalledWith(1.25)
|
expect(useSettingsStore.getState().setUiZoom).not.toHaveBeenCalledWith(1.25)
|
||||||
expect(useSettingsStore.getState().uiZoom).toBe(1.25)
|
expect(useSettingsStore.getState().uiZoom).toBe(1)
|
||||||
expect(slider).toHaveValue('1.25')
|
expect(slider).toHaveValue('1.25')
|
||||||
expect(slider).toHaveClass('settings-zoom-range')
|
expect(slider).toHaveClass('settings-zoom-range')
|
||||||
expect(slider.closest('.settings-zoom-control')).toHaveClass('is-dragging')
|
expect(slider.closest('.settings-zoom-control')).toHaveClass('is-dragging')
|
||||||
|
|||||||
@ -4,7 +4,7 @@ export const DEFAULT_APP_ZOOM = 1
|
|||||||
export const MIN_APP_ZOOM = 0.5
|
export const MIN_APP_ZOOM = 0.5
|
||||||
export const MAX_APP_ZOOM = 2
|
export const MAX_APP_ZOOM = 2
|
||||||
export const APP_ZOOM_STEP = 0.1
|
export const APP_ZOOM_STEP = 0.1
|
||||||
export const APP_ZOOM_CONTROL_STEP = 0.05
|
export const APP_ZOOM_CONTROL_STEP = 0.01
|
||||||
|
|
||||||
export type AppZoomAction = 'in' | 'out' | 'reset'
|
export type AppZoomAction = 'in' | 'out' | 'reset'
|
||||||
|
|
||||||
|
|||||||
@ -1394,6 +1394,7 @@ function GeneralSettings() {
|
|||||||
const [notificationActionRunning, setNotificationActionRunning] = useState(false)
|
const [notificationActionRunning, setNotificationActionRunning] = useState(false)
|
||||||
const [uiZoomDraft, setUiZoomDraft] = useState(uiZoom)
|
const [uiZoomDraft, setUiZoomDraft] = useState(uiZoom)
|
||||||
const [isUiZoomDragging, setIsUiZoomDragging] = useState(false)
|
const [isUiZoomDragging, setIsUiZoomDragging] = useState(false)
|
||||||
|
const isUiZoomDraggingRef = useRef(false)
|
||||||
const webSearchDirty = JSON.stringify(webSearchDraft) !== JSON.stringify(webSearch)
|
const webSearchDirty = JSON.stringify(webSearchDraft) !== JSON.stringify(webSearch)
|
||||||
const uiZoomPercent = Math.round(uiZoomDraft * 100)
|
const uiZoomPercent = Math.round(uiZoomDraft * 100)
|
||||||
const uiZoomRangeProgress = `${Math.round(((uiZoomDraft - UI_ZOOM_MIN) / (UI_ZOOM_MAX - UI_ZOOM_MIN)) * 1000) / 10}%`
|
const uiZoomRangeProgress = `${Math.round(((uiZoomDraft - UI_ZOOM_MIN) / (UI_ZOOM_MAX - UI_ZOOM_MIN)) * 1000) / 10}%`
|
||||||
@ -1523,11 +1524,14 @@ function GeneralSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const commitUiZoom = (value: number, options: { keepDragging?: boolean } = {}) => {
|
const setUiZoomDraggingState = (dragging: boolean) => {
|
||||||
|
isUiZoomDraggingRef.current = dragging
|
||||||
|
setIsUiZoomDragging(dragging)
|
||||||
|
}
|
||||||
|
|
||||||
|
const commitUiZoom = (value: number) => {
|
||||||
const nextZoom = Number.isFinite(value) ? value : UI_ZOOM_DEFAULT
|
const nextZoom = Number.isFinite(value) ? value : UI_ZOOM_DEFAULT
|
||||||
if (!options.keepDragging) {
|
setUiZoomDraggingState(false)
|
||||||
setIsUiZoomDragging(false)
|
|
||||||
}
|
|
||||||
setUiZoomDraft(nextZoom)
|
setUiZoomDraft(nextZoom)
|
||||||
setUiZoom(nextZoom)
|
setUiZoom(nextZoom)
|
||||||
}
|
}
|
||||||
@ -1602,16 +1606,27 @@ function GeneralSettings() {
|
|||||||
step={UI_ZOOM_STEP}
|
step={UI_ZOOM_STEP}
|
||||||
value={uiZoomDraft}
|
value={uiZoomDraft}
|
||||||
onPointerDown={() => {
|
onPointerDown={() => {
|
||||||
setIsUiZoomDragging(true)
|
setUiZoomDraggingState(true)
|
||||||
|
}}
|
||||||
|
onPointerUp={(e) => commitUiZoom(e.currentTarget.valueAsNumber)}
|
||||||
|
onPointerCancel={() => {
|
||||||
|
setUiZoomDraggingState(false)
|
||||||
|
setUiZoomDraft(uiZoom)
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
const nextZoom = Number.isFinite(e.currentTarget.valueAsNumber)
|
||||||
|
? e.currentTarget.valueAsNumber
|
||||||
|
: UI_ZOOM_DEFAULT
|
||||||
|
setUiZoomDraft(nextZoom)
|
||||||
|
if (!isUiZoomDraggingRef.current) {
|
||||||
|
setUiZoom(nextZoom)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onPointerUp={() => setIsUiZoomDragging(false)}
|
|
||||||
onPointerCancel={() => setIsUiZoomDragging(false)}
|
|
||||||
onChange={(e) => commitUiZoom(e.currentTarget.valueAsNumber, { keepDragging: isUiZoomDragging })}
|
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
if (uiZoomDraft !== uiZoom) {
|
if (uiZoomDraft !== uiZoom) {
|
||||||
commitUiZoom(e.currentTarget.valueAsNumber)
|
commitUiZoom(e.currentTarget.valueAsNumber)
|
||||||
} else {
|
} else {
|
||||||
setIsUiZoomDragging(false)
|
setUiZoomDraggingState(false)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="settings-zoom-range w-full"
|
className="settings-zoom-range w-full"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user