텍스트 인라인 편집 기능 추가
This commit is contained in:
@@ -193,6 +193,11 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||
// 복제 시 원본 컴포넌트들의 위치 저장 (상대적 위치 유지용)
|
||||
const originalPositionsRef = useRef<Map<string, { x: number; y: number }>>(new Map());
|
||||
|
||||
// 인라인 편집 상태
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editValue, setEditValue] = useState("");
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const isSelected = selectedComponentId === component.id;
|
||||
const isMultiSelected = selectedComponentIds.includes(component.id);
|
||||
const isLocked = component.locked === true;
|
||||
@@ -290,15 +295,76 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||
});
|
||||
};
|
||||
|
||||
// 더블 클릭 핸들러 (텍스트 컴포넌트만)
|
||||
// 더블 클릭 핸들러 (텍스트 컴포넌트: 인라인 편집 모드 진입)
|
||||
const handleDoubleClick = (e: React.MouseEvent) => {
|
||||
if (component.type !== "text" && component.type !== "label") return;
|
||||
if (isLocked) return; // 잠긴 컴포넌트는 편집 불가
|
||||
|
||||
e.stopPropagation();
|
||||
fitTextToContent();
|
||||
|
||||
// 인라인 편집 모드 진입
|
||||
setEditValue(component.defaultValue || "");
|
||||
setIsEditing(true);
|
||||
};
|
||||
|
||||
// 인라인 편집 시작 시 textarea에 포커스
|
||||
useEffect(() => {
|
||||
if (isEditing && textareaRef.current) {
|
||||
textareaRef.current.focus();
|
||||
textareaRef.current.select();
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
// 선택 해제 시 편집 모드 종료를 위한 ref
|
||||
const editValueRef = useRef(editValue);
|
||||
const isEditingRef = useRef(isEditing);
|
||||
editValueRef.current = editValue;
|
||||
isEditingRef.current = isEditing;
|
||||
|
||||
// 선택 해제 시 편집 모드 종료 (저장 후 종료)
|
||||
useEffect(() => {
|
||||
if (!isSelected && !isMultiSelected && isEditingRef.current) {
|
||||
// 현재 편집 값으로 저장
|
||||
if (editValueRef.current !== component.defaultValue) {
|
||||
updateComponent(component.id, { defaultValue: editValueRef.current });
|
||||
}
|
||||
setIsEditing(false);
|
||||
}
|
||||
}, [isSelected, isMultiSelected, component.id, component.defaultValue, updateComponent]);
|
||||
|
||||
// 인라인 편집 저장
|
||||
const handleEditSave = () => {
|
||||
if (!isEditing) return;
|
||||
|
||||
updateComponent(component.id, {
|
||||
defaultValue: editValue,
|
||||
});
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
// 인라인 편집 취소
|
||||
const handleEditCancel = () => {
|
||||
setIsEditing(false);
|
||||
setEditValue("");
|
||||
};
|
||||
|
||||
// 인라인 편집 키보드 핸들러
|
||||
const handleEditKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
handleEditCancel();
|
||||
} else if (e.key === "Enter" && !e.shiftKey) {
|
||||
// Enter: 저장 (Shift+Enter는 줄바꿈)
|
||||
e.preventDefault();
|
||||
handleEditSave();
|
||||
}
|
||||
};
|
||||
|
||||
// 드래그 시작
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
// 편집 모드에서는 드래그 비활성화
|
||||
if (isEditing) return;
|
||||
|
||||
if ((e.target as HTMLElement).classList.contains("resize-handle")) {
|
||||
return;
|
||||
}
|
||||
@@ -636,6 +702,27 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||
switch (component.type) {
|
||||
case "text":
|
||||
case "label":
|
||||
// 인라인 편집 모드
|
||||
if (isEditing) {
|
||||
return (
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onBlur={handleEditSave}
|
||||
onKeyDown={handleEditKeyDown}
|
||||
className="h-full w-full resize-none border-none bg-transparent p-0 outline-none focus:ring-2 focus:ring-blue-500"
|
||||
style={{
|
||||
fontSize: `${component.fontSize}px`,
|
||||
color: component.fontColor,
|
||||
fontWeight: component.fontWeight,
|
||||
textAlign: component.textAlign as "left" | "center" | "right",
|
||||
lineHeight: "1.5",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-full w-full"
|
||||
|
||||
@@ -214,6 +214,7 @@ export function ReportDesignerCanvas() {
|
||||
duplicateComponents,
|
||||
copyStyles,
|
||||
pasteStyles,
|
||||
fitSelectedToContent,
|
||||
undo,
|
||||
redo,
|
||||
showRuler,
|
||||
@@ -646,6 +647,13 @@ export function ReportDesignerCanvas() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Shift+F (또는 Cmd+Shift+F): 텍스트 크기 자동 맞춤
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === "f") {
|
||||
e.preventDefault();
|
||||
fitSelectedToContent();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+C (또는 Cmd+C): 복사
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "c") {
|
||||
e.preventDefault();
|
||||
@@ -699,6 +707,7 @@ export function ReportDesignerCanvas() {
|
||||
duplicateComponents,
|
||||
copyStyles,
|
||||
pasteStyles,
|
||||
fitSelectedToContent,
|
||||
undo,
|
||||
redo,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user