diff --git a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx index a640a8d..dc6fb0e 100644 --- a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx +++ b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx @@ -1,10 +1,14 @@ 'use client'; -import { useState, useEffect, useRef, useCallback } from 'react'; +import { useState, useEffect, useRef, useCallback, useLayoutEffect } from 'react'; import { DndContext, useDraggable, useDroppable, DragOverlay, + MouseSensor, + TouchSensor, + useSensor, + useSensors, type DragEndEvent, } from '@dnd-kit/core'; import { CSS } from '@dnd-kit/utilities'; @@ -21,29 +25,28 @@ interface PageInfo { // Screen (DOM) → PDF user space. Y-axis flip required. // DOM: Y=0 at top, increases downward // PDF: Y=0 at bottom, increases upward +// renderedW/renderedH must be the actual rendered canvas dimensions (from pageInfo.width/height) function screenToPdfCoords( screenX: number, screenY: number, - containerRect: DOMRect, + renderedW: number, + renderedH: number, pageInfo: PageInfo, ) { - // Use containerRect dimensions (current rendered size) not stale pageInfo - const renderedW = containerRect.width; - const renderedH = containerRect.height; const pdfX = (screenX / renderedW) * pageInfo.originalWidth; const pdfY = ((renderedH - screenY) / renderedH) * pageInfo.originalHeight; return { x: pdfX, y: pdfY }; } // PDF user space → screen (for rendering stored fields) +// renderedW/renderedH must be the actual rendered canvas dimensions (from pageInfo.width/height) function pdfToScreenCoords( pdfX: number, pdfY: number, - containerRect: DOMRect, + renderedW: number, + renderedH: number, pageInfo: PageInfo, ) { - const renderedW = containerRect.width; - const renderedH = containerRect.height; const left = (pdfX / pageInfo.originalWidth) * renderedW; // top is measured from DOM top; pdfY is from PDF bottom — reverse flip const top = renderedH - (pdfY / pageInfo.originalHeight) * renderedH; @@ -125,6 +128,15 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children }: FieldPla const [fields, setFields] = useState([]); const [isDraggingToken, setIsDraggingToken] = useState(false); const containerRef = useRef(null); + // Track rendered canvas dimensions in state so renderFields re-runs when they change + const [containerSize, setContainerSize] = useState<{ w: number; h: number } | null>(null); + + // Configure sensors: require a minimum drag distance so clicks on delete buttons + // are not intercepted as drag starts + const sensors = useSensors( + useSensor(MouseSensor, { activationConstraint: { distance: 5 } }), + useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 5 } }), + ); // Load existing fields from server on mount useEffect(() => { @@ -142,11 +154,19 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children }: FieldPla loadFields(); }, [docId]); + // Update containerSize whenever pageInfo changes (page load or zoom change) + // Use pageInfo.width/height (from react-pdf canvas) as the authoritative rendered size. + // getBoundingClientRect() is only used for the drop origin (containerRect.left/top). + useLayoutEffect(() => { + if (!pageInfo) return; + setContainerSize({ w: pageInfo.width, h: pageInfo.height }); + }, [pageInfo]); + const handleDragEnd = useCallback( (event: DragEndEvent) => { setIsDraggingToken(false); - const { active, over, activatorEvent, delta } = event; + const { over, activatorEvent, delta } = event; // Only process if dropped onto the PDF zone if (!over || over.id !== 'pdf-drop-zone') return; @@ -155,6 +175,12 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children }: FieldPla const containerRect = containerRef.current?.getBoundingClientRect(); if (!containerRect) return; + // Use pageInfo.width/height as the authoritative rendered canvas size — + // containerRect.width/height could be slightly off if the wrapper div has + // any extra decoration that doesn't match the canvas exactly. + const renderedW = pageInfo.width; + const renderedH = pageInfo.height; + // Compute the final screen position relative to the container // activatorEvent is the initial MouseEvent/TouchEvent at drag start // delta is the displacement from drag start to drop @@ -169,24 +195,24 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children }: FieldPla clientY = activatorEvent.touches[0].clientY; } else { // Fallback: use center of container - clientX = containerRect.left + containerRect.width / 2; - clientY = containerRect.top + containerRect.height / 2; + clientX = containerRect.left + renderedW / 2; + clientY = containerRect.top + renderedH / 2; } // finalClient = activatorClient + delta (displacement during drag) const finalClientX = clientX + delta.x; const finalClientY = clientY + delta.y; - // Convert to coordinates relative to the container + // Convert to coordinates relative to the container's top-left corner const screenX = finalClientX - containerRect.left; const screenY = finalClientY - containerRect.top; - // Clamp to container bounds - const clampedX = Math.max(0, Math.min(screenX, containerRect.width)); - const clampedY = Math.max(0, Math.min(screenY, containerRect.height)); + // Clamp to canvas bounds + const clampedX = Math.max(0, Math.min(screenX, renderedW)); + const clampedY = Math.max(0, Math.min(screenY, renderedH)); // Convert screen coords to PDF user-space (Y-flip applied) - const { x: pdfX, y: pdfY } = screenToPdfCoords(clampedX, clampedY, containerRect, pageInfo); + const { x: pdfX, y: pdfY } = screenToPdfCoords(clampedX, clampedY, renderedW, renderedH, pageInfo); const newField: SignatureFieldData = { id: crypto.randomUUID(), @@ -205,17 +231,19 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children }: FieldPla ); // Render placed fields for the current page + // Uses pageInfo.width/height (not getBoundingClientRect) for consistent coordinate math const renderFields = () => { - if (!pageInfo) return null; - const containerRect = containerRef.current?.getBoundingClientRect(); - if (!containerRect) return null; + if (!pageInfo || !containerSize) return null; + + const renderedW = containerSize.w; + const renderedH = containerSize.h; return fields .filter((f) => f.page === currentPage) .map((field) => { - const { left, top } = pdfToScreenCoords(field.x, field.y, containerRect, pageInfo); - const widthPx = (field.width / pageInfo.originalWidth) * containerRect.width; - const heightPx = (field.height / pageInfo.originalHeight) * containerRect.height; + const { left, top } = pdfToScreenCoords(field.x, field.y, renderedW, renderedH, pageInfo); + const widthPx = (field.width / pageInfo.originalWidth) * renderedW; + const heightPx = (field.height / pageInfo.originalHeight) * renderedH; return (
Signature