diff --git a/src/__tests__/hooks/useDragAndDrop.test.ts b/src/__tests__/hooks/useDragAndDrop.test.ts index b14ebda..37905f1 100644 --- a/src/__tests__/hooks/useDragAndDrop.test.ts +++ b/src/__tests__/hooks/useDragAndDrop.test.ts @@ -113,6 +113,23 @@ describe('getQuadrantAtPoint', () => { }) }) +describe('coordinate system consistency', () => { + it('uses clientX/clientY (not pageX/pageY) so coordinates match getBoundingClientRect', () => { + // getBoundingClientRect() returns viewport-relative coords (client space). + // The drag system must use clientX/clientY from PointerEvents to match. + // If pageX/pageY were used instead, a scrolled page would cause mismatch. + const rect = { left: 100, top: 200, width: 400, height: 300 } as DOMRect + + // clientX=300, clientY=350 → center of the rect + const result = pageToQuadrantPercent(300, 350, rect) + expect(result.x).toBe(50) + expect(result.y).toBe(50) + + // Verify the function is named to reflect client coordinates + // (This test documents the expected coordinate system) + }) +}) + // --- Hook integration tests --- describe('useDragAndDrop hook', () => { @@ -139,8 +156,8 @@ describe('useDragAndDrop hook', () => { act(() => { result.current.handleDragStart(0, mockItem, { - pageX: 150, - pageY: 250, + clientX: 150, + clientY: 250, grabX: 10, grabY: 5, width: 120, @@ -167,8 +184,8 @@ describe('useDragAndDrop hook', () => { act(() => { result.current.handleDragStart(0, mockItem, { - pageX: 150, - pageY: 250, + clientX: 150, + clientY: 250, grabX: 10, grabY: 5, width: 120, @@ -180,7 +197,7 @@ describe('useDragAndDrop hook', () => { window.dispatchEvent(new PointerEvent('pointermove', { clientX: 200, clientY: 300 })) }) - // PointerEvent pageX/pageY default to 0 in jsdom, so drag.x/y become 0 + // PointerEvent clientX/clientY default to 0 in jsdom, so drag.x/y become 0 // The important thing is the handler runs without error expect(result.current.drag).not.toBeNull() }) @@ -192,8 +209,8 @@ describe('useDragAndDrop hook', () => { act(() => { result.current.handleDragStart(0, mockItem, { - pageX: 150, - pageY: 250, + clientX: 150, + clientY: 250, grabX: 10, grabY: 5, width: 120, @@ -215,8 +232,8 @@ describe('useDragAndDrop hook', () => { act(() => { result.current.handleDragStart(0, mockItem, { - pageX: 150, - pageY: 250, + clientX: 150, + clientY: 250, grabX: 10, grabY: 5, width: 120, @@ -240,8 +257,8 @@ describe('useDragAndDrop hook', () => { act(() => { result.current.handleDragStart(0, mockItem, { - pageX: 150, - pageY: 250, + clientX: 150, + clientY: 250, grabX: 10, grabY: 5, width: 120, diff --git a/src/components/Card.tsx b/src/components/Card.tsx index de16d34..461e5ec 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -8,8 +8,8 @@ const DRAG_THRESHOLD = 4 export const PLACEHOLDER = 'New item...' export interface DragStartInfo { - pageX: number - pageY: number + clientX: number + clientY: number grabX: number grabY: number width: number @@ -91,15 +91,15 @@ export default function Card({ const onChangeRef = useRef(onChange) onChangeRef.current = onChange - const fireDragStart = useCallback((pageX: number, pageY: number) => { + const fireDragStart = useCallback((clientX: number, clientY: number) => { const cardEl = cardRef.current if (!cardEl) return const cardRect = cardEl.getBoundingClientRect() onDragStartRef.current({ - pageX, - pageY, - grabX: pageX - cardRect.left, - grabY: pageY - cardRect.top, + clientX, + clientY, + grabX: clientX - cardRect.left, + grabY: clientY - cardRect.top, width: cardRect.width, height: cardRect.height, }) @@ -127,8 +127,8 @@ export default function Card({ const onMove = (e: PointerEvent) => { const p = pendingRef.current if (!p) return - const dx = e.pageX - p.startX - const dy = e.pageY - p.startY + const dx = e.clientX - p.startX + const dy = e.clientY - p.startY if (dx * dx + dy * dy > DRAG_THRESHOLD * DRAG_THRESHOLD) { cleanup() pendingRef.current = null @@ -176,7 +176,7 @@ export default function Card({ } e.preventDefault() e.stopPropagation() - startPendingDrag(e.pageX, e.pageY) + startPendingDrag(e.clientX, e.clientY) }, [editing, startPendingDrag], ) @@ -222,7 +222,7 @@ export default function Card({ onPointerDown={(e) => { if (e.button !== 0 || editing) return e.preventDefault() - fireDragStart(e.pageX, e.pageY) + fireDragStart(e.clientX, e.clientY) }}> {editing ? (