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 { Text, Group } from "react-konva";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import type { TextShapeProps } from "./types";
|
import type { TextShapeProps } from "./types";
|
||||||
@@ -41,6 +41,7 @@ const TextShape = ({
|
|||||||
}: TextShapeProps) => {
|
}: TextShapeProps) => {
|
||||||
const shapeRef = useRef<Konva.Text>(null);
|
const shapeRef = useRef<Konva.Text>(null);
|
||||||
const groupRef = useRef<Konva.Group>(null);
|
const groupRef = useRef<Konva.Group>(null);
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
||||||
// Transformer is managed in EditorCanvas for multi-select support
|
// Transformer is managed in EditorCanvas for multi-select support
|
||||||
// Set offset for text node
|
// Set offset for text node
|
||||||
@@ -73,6 +74,130 @@ const TextShape = ({
|
|||||||
const fontWeight = useMemo(() => getFontWeight(obj.fontWeight), [obj.fontWeight]);
|
const fontWeight = useMemo(() => getFontWeight(obj.fontWeight), [obj.fontWeight]);
|
||||||
const fillColor = useMemo(() => getColorWithOpacity(obj.fill, obj.opacity), [obj.fill, obj.opacity]);
|
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 (
|
return (
|
||||||
<Group
|
<Group
|
||||||
ref={groupRef}
|
ref={groupRef}
|
||||||
@@ -83,12 +208,13 @@ const TextShape = ({
|
|||||||
width={obj.width}
|
width={obj.width}
|
||||||
height={obj.height}
|
height={obj.height}
|
||||||
rotation={obj.rotation || 0}
|
rotation={obj.rotation || 0}
|
||||||
draggable={draggable}
|
draggable={draggable && !isEditing}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (groupRef.current) {
|
if (groupRef.current) {
|
||||||
onSelect(obj.id, groupRef.current, e);
|
onSelect(obj.id, groupRef.current, e);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onDblClick={handleDblClick}
|
||||||
onDragEnd={(e) => {
|
onDragEnd={(e) => {
|
||||||
const node = e.target;
|
const node = e.target;
|
||||||
onUpdate(obj.id, {
|
onUpdate(obj.id, {
|
||||||
|
|||||||
Reference in New Issue
Block a user