edit text
This commit is contained in:
@@ -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<Konva.Text>(null);
|
||||
const groupRef = useRef<Konva.Group>(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 (
|
||||
<Group
|
||||
ref={groupRef}
|
||||
@@ -83,12 +208,13 @@ const TextShape = ({
|
||||
width={obj.width}
|
||||
height={obj.height}
|
||||
rotation={obj.rotation || 0}
|
||||
draggable={draggable}
|
||||
draggable={draggable && !isEditing}
|
||||
onClick={(e) => {
|
||||
if (groupRef.current) {
|
||||
onSelect(obj.id, groupRef.current, e);
|
||||
}
|
||||
}}
|
||||
onDblClick={handleDblClick}
|
||||
onDragEnd={(e) => {
|
||||
const node = e.target;
|
||||
onUpdate(obj.id, {
|
||||
|
||||
Reference in New Issue
Block a user