import { useEffect, useRef } from 'react'; import { Text as KonvaText } from 'react-konva'; import { TextObject } from '../../types/canvas.types'; import Konva from 'konva'; // Helper function to get fontStyle string based on weight and italic function getFontStyle(bold: boolean, italic: boolean, fontWeight?: 'normal' | 'bold' | 'black'): string { const weight = fontWeight === 'black' ? '900' : bold ? 'bold' : 'normal'; const style = italic ? 'italic' : ''; return `${weight} ${style}`.trim(); } interface TextEditableProps { object: TextObject; isSelected: boolean; onSelect: (e?: Konva.KonvaEventObject) => void; onDragStart?: (e: Konva.KonvaEventObject) => void; onDragMove?: (e: Konva.KonvaEventObject) => void; onDragEnd?: (e: Konva.KonvaEventObject) => void; onTransformEnd?: (e: Konva.KonvaEventObject) => void; onEditingChange: (id: string, isEditing: boolean, clickX?: number, clickY?: number) => void; onMouseEnter?: () => void; onMouseLeave?: () => void; shapeRef?: ((node: Konva.Text | null) => void) | React.RefObject; } export default function TextEditable({ object, isSelected, onSelect, onDragStart, onDragMove, onDragEnd, onTransformEnd, onEditingChange, onMouseEnter, onMouseLeave, shapeRef }: TextEditableProps) { const textNodeRef = useRef(null); // Auto-enter edit mode when text is first created (empty text) useEffect(() => { if (object.text === '' && isSelected && !object.isFixedSize && !object.isEditing) { // Delay to ensure Konva node is fully rendered const timer = setTimeout(() => { onEditingChange(object.id, true); }, 100); return () => clearTimeout(timer); } }, [object.text, isSelected, object.isFixedSize, object.isEditing, object.id, onEditingChange]); // Handle double-click to edit const handleDoubleClick = (e: Konva.KonvaEventObject) => { if (!object.isEditing) { const stage = e.target.getStage(); const pointerPos = stage?.getPointerPosition(); if (pointerPos) { // Get click position relative to the text object const clickX = pointerPos.x - object.x; const clickY = pointerPos.y - object.y; onEditingChange(object.id, true, clickX, clickY); } else { onEditingChange(object.id, true); } } }; // Calculate font size to fit text in fixed box (Stage 2) const calculateFitFontSize = (): number => { if (!object.isFixedSize) return object.fontSize; if (!object.text) return object.fontSize; try { // Create temporary text to measure const tempText = new Konva.Text({ text: object.text || 'M', fontSize: object.fontSize, fontFamily: object.fontFamily, fontStyle: getFontStyle(object.bold, object.italic, object.fontWeight), width: object.width, height: object.height }); let fontSize = object.fontSize; const maxWidth = object.width; const maxHeight = object.height; // Scale down font if text doesn't fit while (fontSize > 10) { tempText.fontSize(fontSize); const size = tempText.measureSize(object.text || 'M'); if (size.width <= maxWidth && size.height <= maxHeight) { break; } fontSize -= 1; } tempText.destroy(); return fontSize; } catch (error) { console.error('Error calculating fit font size:', error); return object.fontSize; } }; const displayFontSize = object.isFixedSize ? calculateFitFontSize() : object.fontSize; return ( { // Call the callback ref if it's a function if (typeof shapeRef === 'function') { shapeRef(node); } else if (shapeRef) { // Set the RefObject if it's an object (shapeRef as React.MutableRefObject).current = node; } // Also store internally for our own use textNodeRef.current = node; }} x={object.x} y={object.y} width={object.width} height={object.height} text={object.text} fontSize={displayFontSize} fontFamily={object.fontFamily} fill={object.fill} fontStyle={getFontStyle(object.bold, object.italic, object.fontWeight)} align={object.align || 'left'} verticalAlign="top" rotation={object.rotation} padding={0} lineHeight={1} draggable={true} onClick={(e) => onSelect(e)} onTap={(e) => onSelect(e)} onDblClick={handleDoubleClick} onDblTap={handleDoubleClick} onDragStart={onDragStart} onDragMove={onDragMove} onDragEnd={onDragEnd} onTransformEnd={onTransformEnd} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} opacity={object.isEditing ? 0 : 1} listening={!object.isEditing} /> ); }