- FieldPlacer.tsx: dnd-kit drag-and-drop field placer with Y-flip coordinate conversion - PdfViewer.tsx: extended with pageInfo state and FieldPlacer integration - @dnd-kit/core and @dnd-kit/utilities installed - Fields persist via PUT /api/documents/[id]/fields on every add/remove - 05-02-SUMMARY.md created, STATE.md and ROADMAP.md updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.6 KiB
4.6 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-pdf-fill-and-field-mapping | 02 | frontend/documents |
|
|
|
|
|
|
Phase 5 Plan 02: Drag-and-Drop Signature Field Placer Summary
One-liner: dnd-kit DndContext with draggable palette token + droppable PDF zone; Y-flip coordinate conversion persists fields to DB via PUT /api/documents/[id]/fields.
What Was Built
A complete drag-and-drop field placement system integrated into the document detail page:
-
FieldPlacer.tsx (321 lines) — New client component providing:
- A
DndContextwrapping both palette and PDF canvas DraggableTokensub-component usinguseDraggablefor the "Signature Field" palette itemDroppableZonesub-component usinguseDroppableto make the PDF canvas a valid drop targetDragOverlayshowing a blue ghost rectangle during dragscreenToPdfCoords()— Y-flip formula converting DOM coordinates to PDF user spacepdfToScreenCoords()— inverse formula for rendering stored fields as overlays- Server sync: GET on mount (load existing), PUT on every add/remove (persist)
- Per-page filtering (
field.page === currentPage) so page 1 fields don't show on page 2
- A
-
PdfViewer.tsx (updated) — Extended with:
pageInfostate (PageInfo | null) populated viaonLoadSuccesscallbackMath.max(page.view[0], page.view[2])/Math.max(page.view[1], page.view[3])for robustoriginalWidth/originalHeight(handles non-standard mediaBox ordering)FieldPlacerwraps theDocument/Pagetree, receivingdocId,pageInfo,currentPage- Only
scaleprop used on<Page>(not bothwidth+scale)
Verification Results
npm run build— compiled successfully (both tasks)- FieldPlacer.tsx: 321 lines (minimum 80 required)
@dnd-kit/coreand@dnd-kit/utilitiespresent in package.json- PdfViewerWrapper.tsx unchanged (dynamic import wrapper still passes docId through)
Coordinate Conversion Details
The Y-flip formula is critical for correct field placement:
- DOM: Y=0 at top, increases downward
- PDF user space: Y=0 at bottom, increases upward
pdfY = ((renderedH - screenY) / renderedH) * originalHeight- This means dragging to the visual top of a page stores a high pdfY value (near originalHeight)
containerRect.width/heightused at drop time (not stale pageInfo) to handle zoom changes correctly
Commits
| Task | Commit | Description |
|---|---|---|
| Task 1 | 6069ae5 |
feat(05-02): install dnd-kit and create FieldPlacer component |
| Task 2 | 7a36736 |
feat(05-02): extend PdfViewer with pageInfo state and FieldPlacer integration |
Deviations from Plan
None — plan executed exactly as written.
The DroppableZone component uses a combined ref callback to assign both the dnd-kit setNodeRef and the containerRef simultaneously to the same DOM node — this was a minor implementation detail not explicitly specified in the plan but required for correct operation.