perf: fold pet size clamp into resize effect (#4129)

This commit is contained in:
Neil
2026-05-31 05:24:08 -07:00
committed by GitHub
parent 7a1baf73cf
commit ae5348b330
2 changed files with 47 additions and 16 deletions
@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest'
import { clampPositionToViewport } from './PetOverlay'
describe('clampPositionToViewport', () => {
it('keeps positions inside the viewport minus pet size', () => {
expect(
clampPositionToViewport({ x: 480, y: 390 }, 120, {
width: 500,
height: 400
})
).toEqual({ x: 380, y: 280 })
})
it('clamps negative positions to the origin', () => {
expect(
clampPositionToViewport({ x: -25, y: -40 }, 120, {
width: 500,
height: 400
})
).toEqual({ x: 0, y: 0 })
})
})
+24 -16
View File
@@ -183,18 +183,29 @@ const SIZE = 180
const POSITION_STORAGE_KEY = 'pet-overlay-position'
const LEGACY_POSITION_STORAGE_KEY = 'sidekick-overlay-position'
type Position = { x: number; y: number }
export type Position = { x: number; y: number }
export function clampPositionToViewport(
pos: Position,
size: number,
viewport: { width: number; height: number }
): Position {
const maxX = Math.max(0, viewport.width - size)
const maxY = Math.max(0, viewport.height - size)
return {
x: Math.min(Math.max(0, pos.x), maxX),
y: Math.min(Math.max(0, pos.y), maxY)
}
}
function clampToViewport(pos: Position, size: number = SIZE): Position {
if (typeof window === 'undefined') {
return pos
}
const maxX = Math.max(0, window.innerWidth - size)
const maxY = Math.max(0, window.innerHeight - size)
return {
x: Math.min(Math.max(0, pos.x), maxX),
y: Math.min(Math.max(0, pos.y), maxY)
}
return clampPositionToViewport(pos, size, {
width: window.innerWidth,
height: window.innerHeight
})
}
function loadStoredPosition(size: number = SIZE): Position | null {
@@ -262,15 +273,12 @@ export function PetOverlay(): React.JSX.Element {
const dragOffsetRef = useRef<Position>({ x: 0, y: 0 })
useEffect(() => {
const onResize = (): void => setPosition((prev) => clampToViewport(prev, size))
window.addEventListener('resize', onResize)
return () => window.removeEventListener('resize', onResize)
}, [size])
// Why: when the user shrinks/grows the overlay, re-clamp so the box never
// overflows the viewport edges (which would otherwise leave it un-draggable).
useEffect(() => {
setPosition((prev) => clampToViewport(prev, size))
const clampPosition = (): void => setPosition((prev) => clampToViewport(prev, size))
// Why: resizing the pet or the window uses the same viewport boundary; keep
// that clamp in one Effect so size changes do not need a second render pass.
clampPosition()
window.addEventListener('resize', clampPosition)
return () => window.removeEventListener('resize', clampPosition)
}, [size])
useEffect(() => {