import React, { useState, useRef, useEffect } from "react"; import { createPortal } from "react-dom"; import { Upload, Trash2, AlignLeft, AlignCenter, AlignRight, Image as ImageIcon, CornerDownRight, X } from "lucide-react"; export default function InlineImageEditor({ field, onUpdate, viewMode, setIsDirty, children }) { const [isEditing, setIsEditing] = useState(false); const [toolbarPosition, setToolbarPosition] = useState({ top: 0, left: 0 }); const [showCustomSizePopover, setShowCustomSizePopover] = useState(false); // the reference builder keys read by the canvas and the published form: imageCustomWidth / imageCustomHeight (older // imageWidth / imageHeight are read as a fallback). const [customWidth, setCustomWidth] = useState(field.imageCustomWidth || field.imageWidth || 400); const [customHeight, setCustomHeight] = useState(field.imageCustomHeight || field.imageHeight || 400); const [showRadius, setShowRadius] = useState(false); const containerRef = useRef(null); const toolbarRef = useRef(null); const customSizePopoverRef = useRef(null); useEffect(() => { if (isEditing && containerRef.current) { const updatePosition = () => { if (!containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); const toolbarWidth = 320; let left = rect.left + (rect.width / 2) - (toolbarWidth / 2); let top = rect.bottom + 8; if (left < 10) left = 10; if (left + toolbarWidth > window.innerWidth - 10) left = window.innerWidth - toolbarWidth - 10; if (top + 50 > window.innerHeight) top = rect.top - 50; setToolbarPosition({ top, left }); }; updatePosition(); window.addEventListener('scroll', updatePosition, true); window.addEventListener('resize', updatePosition); return () => { window.removeEventListener('scroll', updatePosition, true); window.removeEventListener('resize', updatePosition); }; } }, [isEditing]); useEffect(() => { const handleClickOutside = (e) => { if (!isEditing) return; if (e.target.closest && e.target.closest(".media-modal, .media-modal-backdrop")) return; if ( containerRef.current && !containerRef.current.contains(e.target) && toolbarRef.current && !toolbarRef.current.contains(e.target) && (!customSizePopoverRef.current || !customSizePopoverRef.current.contains(e.target)) ) { setIsEditing(false); setShowCustomSizePopover(false); } }; // Escape closes the toolbar (the WordPress media modal handles its own Escape). const onKey = (e) => { if (!isEditing || e.key !== "Escape") return; const mediaOpen = Array.from(document.querySelectorAll('.media-modal')).some((el) => el.getClientRects().length > 0); if (mediaOpen) return; e.stopPropagation(); setIsEditing(false); setShowCustomSizePopover(false); setShowRadius(false); }; document.addEventListener("mousedown", handleClickOutside); document.addEventListener("keydown", onKey, true); return () => { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("keydown", onKey, true); }; }, [isEditing]); const handleOpenMediaLibrary = (e) => { e.stopPropagation(); if (window.wp && window.wp.media) { const mediaFrame = window.wp.media({ title: 'Select or Upload Form Image', button: { text: 'Use Image' }, multiple: false }); mediaFrame.on('select', () => { const attachment = mediaFrame.state().get('selection').first().toJSON(); if (attachment?.url) { if (setIsDirty) setIsDirty(true); onUpdate({ imageUrl: attachment.url, url: attachment.url, imageAltText: attachment.alt || attachment.title || '' }); } }); mediaFrame.open(); } else { const url = window.prompt("Enter Image URL:", field.imageUrl || field.url || ""); if (url !== null) { if (setIsDirty) setIsDirty(true); onUpdate({ imageUrl: url, url }); } } }; const handleAlignment = (alignment, e) => { e.stopPropagation(); if (setIsDirty) setIsDirty(true); onUpdate({ imageAlignment: alignment, alignment }); }; const handleSizeChange = (size, e) => { e.stopPropagation(); if (setIsDirty) setIsDirty(true); if (size === 'custom') { setShowCustomSizePopover(true); onUpdate({ imageSize: 'custom' }); } else { setShowCustomSizePopover(false); onUpdate({ imageSize: size }); } }; const handleApplyCustomSize = (e) => { e.stopPropagation(); if (setIsDirty) setIsDirty(true); onUpdate({ imageSize: 'custom', imageCustomWidth: parseInt(customWidth) || 400, imageCustomHeight: parseInt(customHeight) || 400 }); setShowCustomSizePopover(false); }; if (viewMode === 'preview') { return
{children}
; } return (
{ if (viewMode === 'preview') return; e.stopPropagation(); setIsEditing(true); }} > {children} {isEditing && createPortal(
e.stopPropagation()} > {/* Replace / Upload Image */}
{/* Alignment */}
{/* Size Preset */} {/* the reference builder sizes; an unset size renders as Medium on the canvas and the published form */} {field.imageSize === 'custom' && !showCustomSizePopover && ( )}
{/* Corner radius per corner (imageBorderRadiusTopLeft/TopRight/BottomRight/BottomLeft) */}
{showRadius && (
e.stopPropagation()} >
Corner Radius (px)
{[['TopLeft', 'Top Left'], ['TopRight', 'Top Right'], ['BottomLeft', 'Bottom Left'], ['BottomRight', 'Bottom Right']].map(([k, l]) => ( ))}
)}
{/* Custom Size Popover Trigger */} {showCustomSizePopover && (
Custom Dimensions (px)
e.stopPropagation()} value={customWidth} onChange={(e) => setCustomWidth(e.target.value)} className="w-16 px-2 py-1 text-xs border border-gray-200 rounded-lg text-center" /> × e.stopPropagation()} value={customHeight} onChange={(e) => setCustomHeight(e.target.value)} className="w-16 px-2 py-1 text-xs border border-gray-200 rounded-lg text-center" />
)}
{/* Remove Image */}
, document.body )}
); }