From b1b30a2287ee5f13d2f04c921ce505c5032518f1 Mon Sep 17 00:00:00 2001 From: Camiel van Schoonhoven Date: Fri, 20 Feb 2026 16:39:33 -0800 Subject: [PATCH] feat: Edit FlexNode Titles via Double-Click --- .../FlowCanvas/FlexNode/FlexNode.tsx | 66 +++++++++++++++---- .../FlowCanvas/FlexNode/InlineTextEditor.tsx | 20 +++++- 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/components/shared/ReactFlow/FlowCanvas/FlexNode/FlexNode.tsx b/src/components/shared/ReactFlow/FlowCanvas/FlexNode/FlexNode.tsx index 8060db564..9deef7db1 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/FlexNode/FlexNode.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/FlexNode/FlexNode.tsx @@ -32,8 +32,9 @@ const FlexNode = ({ data, id, selected }: FlexNodeProps) => { contentFontSize = 10, } = properties; - const [isInlineEditing, setIsInlineEditing] = useState(false); const [isContextPanelFocus, setIsContextPanelFocus] = useState(false); + const [isInlineEditingContent, setIsInlineEditingContent] = useState(false); + const [isInlineEditingTitle, setIsInlineEditingTitle] = useState(false); const { setContent, @@ -72,7 +73,20 @@ const FlexNode = ({ data, id, selected }: FlexNodeProps) => { } if (!readOnly) { - setIsInlineEditing(true); + setIsInlineEditingTitle(false); + setIsInlineEditingContent(true); + } + }; + + const handleDoubleClickTitle = (e: MouseEvent) => { + e.stopPropagation(); + if (locked) { + toggleLock(); + return; + } + if (!readOnly) { + setIsInlineEditingContent(false); + setIsInlineEditingTitle(true); } }; @@ -90,6 +104,19 @@ const FlexNode = ({ data, id, selected }: FlexNodeProps) => { updateProperties({ content: newContent, }); + setIsInlineEditingContent(false); + }; + + const handleSaveTitle = (newTitle: string) => { + updateProperties({ + title: newTitle, + }); + setIsInlineEditingTitle(false); + }; + + const switchEditor = () => { + setIsInlineEditingTitle((prev) => !prev); + setIsInlineEditingContent((prev) => !prev); }; useEffect(() => { @@ -177,22 +204,35 @@ const FlexNode = ({ data, id, selected }: FlexNodeProps) => { className="absolute top-1 right-1" /> - {title && ( -

- {title} -

- )} - - {isInlineEditing ? ( + {title && + (isInlineEditingTitle ? ( + setIsInlineEditingTitle(false)} + onTab={switchEditor} + className="font-bold" + /> + ) : ( +

+ {title} +

+ ))} + + {isInlineEditingContent ? ( setIsInlineEditing(false)} + onCancel={() => setIsInlineEditingContent(false)} + onTab={switchEditor} /> ) : (

void; onCancel: () => void; + onTab?: () => void; } export const InlineTextEditor = ({ value, placeholder = "Enter text...", textSize, + className, onSave, onCancel, + onTab, }: InlineTextEditorProps) => { const [text, setText] = useState(value); const textareaRef = useRef(null); @@ -55,6 +60,11 @@ export const InlineTextEditor = ({ e.preventDefault(); e.stopPropagation(); onSave(text); + } else if (e.key === "Tab") { + e.preventDefault(); + e.stopPropagation(); + onSave(text); + onTab?.(); } }; @@ -66,8 +76,14 @@ export const InlineTextEditor = ({ onChange={handleChange} onBlur={handleBlur} onKeyDown={handleKeyDown} - className="min-h-10 resize-none nodrag nopan focus-visible:ring-0 focus-visible:border-0 focus-visible:text-xs text-xs shadow-none p-0 rounded-none" - style={{ fontSize: textSize }} + className={cn( + "min-h-10 resize-none nodrag nopan ring-0! border-0! text-xs shadow-none p-0 rounded-none", + className, + )} + style={{ + fontSize: textSize, + minHeight: textSize ? textSize * 1.5 : undefined, + }} onMouseDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()} />