1+ import { Button } from '@/components/ui/button' ;
12import { Input } from '@/components/ui/input' ;
23import { RAGFlowNodeType } from '@/interfaces/database/agent' ;
34import { PenLine } from 'lucide-react' ;
4- import { useCallback , useState } from 'react' ;
5+ import { useCallback , useLayoutEffect , useRef , useState } from 'react' ;
56import { useTranslation } from 'react-i18next' ;
67import { BeginId , Operator } from '../constant' ;
78import { useHandleNodeNameChange } from '../hooks/use-change-node-name' ;
@@ -13,47 +14,75 @@ type TitleInputProps = {
1314
1415export function TitleInput ( { node } : TitleInputProps ) {
1516 const { t } = useTranslation ( ) ;
17+ const inputRef = useRef < HTMLInputElement > ( null ) ;
1618 const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange ( {
1719 id : node ?. id ,
1820 data : node ?. data ,
1921 } ) ;
2022
2123 const operatorName : Operator = node ?. data . label as Operator ;
22-
2324 const isMcp = useIsMcp ( operatorName ) ;
24-
2525 const [ isEditingMode , setIsEditingMode ] = useState ( false ) ;
2626
2727 const switchIsEditingMode = useCallback ( ( ) => {
2828 setIsEditingMode ( ( prev ) => ! prev ) ;
2929 } , [ ] ) ;
3030
31- const handleBlur = useCallback ( ( ) => {
32- handleNameBlur ( ) ;
33- setIsEditingMode ( false ) ;
34- } , [ handleNameBlur ] ) ;
31+ const handleBlur = useCallback (
32+ ( e : React . FocusEvent < HTMLInputElement > ) => {
33+ if ( handleNameBlur ( ) ) {
34+ setIsEditingMode ( false ) ;
35+ } else {
36+ // Re-focus the input if name doesn't change successfully
37+ e . target . focus ( ) ;
38+ e . target . select ( ) ;
39+ }
40+ } ,
41+ [ handleNameBlur ] ,
42+ ) ;
43+
44+ useLayoutEffect ( ( ) => {
45+ if ( isEditingMode && inputRef . current ) {
46+ inputRef . current . focus ( ) ;
47+ inputRef . current . select ( ) ;
48+ }
49+ } , [ isEditingMode ] ) ;
3550
3651 if ( isMcp ) {
3752 return < div className = "flex-1 text-base" > MCP Config</ div > ;
3853 }
3954
4055 return (
41- < div className = "flex items-center gap-1 flex-1" >
56+ // Give a fixed height to prevent layout shift when switching between edit and view modes
57+ < div className = "flex items-center gap-1 flex-1 h-8 mr-2" >
4258 { node ?. id === BeginId ? (
43- < span > { t ( BeginId ) } </ span >
59+ // Begin node is not editable
60+ < span > { t ( `flow.${ BeginId } ` ) } </ span >
4461 ) : isEditingMode ? (
4562 < Input
63+ ref = { inputRef }
4664 value = { name }
4765 onBlur = { handleBlur }
66+ onKeyDown = { ( e ) => {
67+ // Support committing the value changes by pressing Enter
68+ if ( e . key === 'Enter' ) {
69+ handleBlur ( e as unknown as React . FocusEvent < HTMLInputElement > ) ;
70+ }
71+ } }
4872 onChange = { handleNameChange }
49- > </ Input >
73+ / >
5074 ) : (
5175 < div className = "flex items-center gap-2.5 text-base" >
5276 { name }
53- < PenLine
77+
78+ < Button
79+ variant = "transparent"
80+ size = "icon"
81+ className = "size-6 !p-0 border-0 bg-transparent"
5482 onClick = { switchIsEditingMode }
55- className = "size-3.5 text-text-secondary cursor-pointer"
56- />
83+ >
84+ < PenLine className = "size-3.5 text-text-secondary cursor-pointer" />
85+ </ Button >
5786 </ div >
5887 ) }
5988 </ div >
0 commit comments