From 4f2f49a12fa3e11c84b6be6eddbb0df964f0e645 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Tue, 6 Jan 2026 14:50:48 +0330 Subject: [PATCH] edit text --- .../editor/components/tools/TextShape.tsx | 130 +++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/src/pages/editor/components/tools/TextShape.tsx b/src/pages/editor/components/tools/TextShape.tsx index fa68b9f..cddbacd 100644 --- a/src/pages/editor/components/tools/TextShape.tsx +++ b/src/pages/editor/components/tools/TextShape.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useMemo } from "react"; +import { useEffect, useRef, useMemo, useState } from "react"; import { Text, Group } from "react-konva"; import Konva from "konva"; import type { TextShapeProps } from "./types"; @@ -41,6 +41,7 @@ const TextShape = ({ }: TextShapeProps) => { const shapeRef = useRef(null); const groupRef = useRef(null); + const [isEditing, setIsEditing] = useState(false); // Transformer is managed in EditorCanvas for multi-select support // Set offset for text node @@ -73,6 +74,130 @@ const TextShape = ({ const fontWeight = useMemo(() => getFontWeight(obj.fontWeight), [obj.fontWeight]); const fillColor = useMemo(() => getColorWithOpacity(obj.fill, obj.opacity), [obj.fill, obj.opacity]); + const handleDblClick = () => { + if (!shapeRef.current || !groupRef.current) return; + + setIsEditing(true); + + const textNode = shapeRef.current; + const groupNode = groupRef.current; + const stage = textNode.getStage(); + if (!stage) return; + + // مخفی کردن متن اصلی در حین ویرایش + textNode.hide(); + textNode.getLayer()?.batchDraw(); + + // گرفتن موقعیت مطلق text node روی صفحه با استفاده از getClientRect + // که همه transformها (scale, rotation, position) را در نظر می‌گیرد + const stageBox = stage.container().getBoundingClientRect(); + const box = textNode.getClientRect(); + + // محاسبه موقعیت textarea روی صفحه + const areaPosition = { + x: stageBox.left + box.x, + y: stageBox.top + box.y, + }; + + // ایجاد textarea + const textarea = document.createElement("textarea"); + document.body.appendChild(textarea); + + // تنظیم مقدار + textarea.value = textNode.text(); + + // استایل‌دهی دقیق + const stageScale = stage.scaleX(); + Object.assign(textarea.style, { + position: "absolute", + top: `${areaPosition.y}px`, + left: `${areaPosition.x}px`, + width: `${box.width}px`, + minHeight: `${box.height}px`, + fontSize: `${(obj.fontSize || 24) * stageScale}px`, + fontFamily: fontFamily, + fontWeight: fontWeight, + color: obj.fill || "#000000", + letterSpacing: `${(obj.letterSpacing || 0) * stageScale}px`, + lineHeight: textNode.lineHeight().toString(), + textAlign: "right", + direction: "rtl", + padding: "0", + margin: "0", + border: "none", + outline: "2px solid #3b82f6", + background: "transparent", + resize: "none", + overflow: "auto", + zIndex: "10000", + transformOrigin: "left top", + whiteSpace: "pre-wrap", + wordWrap: "break-word", + }); + + // اعمال rotation + const rotation = groupNode.rotation(); + if (rotation) { + textarea.style.transform = `rotate(${rotation}deg)`; + // تنظیم transform origin برای rotation صحیح + textarea.style.transformOrigin = "left top"; + } + + // Focus و Select + textarea.focus(); + + // Select all text after a small delay + setTimeout(() => { + textarea.select(); + }, 0); + + const removeTextarea = () => { + if (textarea.parentNode) { + textarea.parentNode.removeChild(textarea); + } + window.removeEventListener("click", handleOutsideClick); + setIsEditing(false); + textNode.show(); + textNode.getLayer()?.batchDraw(); + }; + + const saveText = () => { + const newText = textarea.value; + if (newText !== textNode.text()) { + onUpdate(obj.id, { text: newText }); + } + removeTextarea(); + }; + + const handleOutsideClick = (e: MouseEvent) => { + if (e.target !== textarea) { + saveText(); + } + }; + + textarea.addEventListener("keydown", (e) => { + // فقط با Escape خارج بشیم بدون ذخیره + if (e.key === "Escape") { + removeTextarea(); + e.preventDefault(); + } + // Enter عادی برای خط جدید + // Ctrl+Enter یا Cmd+Enter برای ذخیره و خروج + else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { + saveText(); + e.preventDefault(); + } + }); + + textarea.addEventListener("blur", () => { + saveText(); + }); + + setTimeout(() => { + window.addEventListener("click", handleOutsideClick); + }, 10); + }; + return ( { if (groupRef.current) { onSelect(obj.id, groupRef.current, e); } }} + onDblClick={handleDblClick} onDragEnd={(e) => { const node = e.target; onUpdate(obj.id, {