diff --git a/.changeset/magnify-image-clamp-lens.md b/.changeset/magnify-image-clamp-lens.md new file mode 100644 index 0000000..eeb92b6 --- /dev/null +++ b/.changeset/magnify-image-clamp-lens.md @@ -0,0 +1,5 @@ +--- +'@perimetre/ui': patch +--- + +Fix `MagnifyImage` lens being clipped at container edges. The lens center is now clamped to `[lensRadius, size - lensRadius]` so the full circle stays inside the container regardless of cursor position. diff --git a/packages/ui/src/components/MagnifyImage/index.tsx b/packages/ui/src/components/MagnifyImage/index.tsx index e03ada7..e0fe06b 100644 --- a/packages/ui/src/components/MagnifyImage/index.tsx +++ b/packages/ui/src/components/MagnifyImage/index.tsx @@ -66,6 +66,9 @@ function MagnifyImage({ * Runs inside the rAF callback — once per frame at most. * Reads the rect here (post-layout, pre-paint) and writes CSS vars directly, * bypassing React state so no re-render is triggered. + * + * The lens center is clamped to `[lensRadius, size - lensRadius]` so the + * entire lens stays inside the container instead of being clipped at edges. */ const flushPos = useCallback(() => { rafId.current = null; @@ -75,13 +78,23 @@ function MagnifyImage({ const rect = el.getBoundingClientRect(); el.style.setProperty( '--posX', - String(Math.max(0, Math.min(rect.width, coords.clientX - rect.left))) + String( + Math.max( + lensRadius, + Math.min(rect.width - lensRadius, coords.clientX - rect.left) + ) + ) ); el.style.setProperty( '--posY', - String(Math.max(0, Math.min(rect.height, coords.clientY - rect.top))) + String( + Math.max( + lensRadius, + Math.min(rect.height - lensRadius, coords.clientY - rect.top) + ) + ) ); - }, []); + }, [lensRadius]); /** * Called on every pointer event. Stores the latest coords and schedules a