Files
zui/frontend/src/components/graph/NodeHeaderTitle.tsx
2026-03-12 18:01:27 +01:00

85 lines
2.7 KiB
TypeScript

import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'
import { GraphContext, FlowUIContext } from '@/lib/graph/flowContext'
import type { AppNode } from '@/lib/graph/nodeTypes'
import { replaceNodeIdInGraph } from '@/lib/graph/flowUtils'
type Props = {
nodeId: string
displayTitle: string
}
export function NodeHeaderTitle({ nodeId, displayTitle }: Props) {
const graphCtx = useContext(GraphContext)
const uiCtx = useContext(FlowUIContext)
const nodes = graphCtx?.nodes ?? []
const setNodes = graphCtx?.setNodes
const edges = graphCtx?.edges ?? []
const setEdges = graphCtx?.setEdges
const renamingNodeId = uiCtx?.renamingNodeId ?? null
const setRenamingNodeId = uiCtx?.setRenamingNodeId
const [inputValue, setInputValue] = useState(nodeId)
const inputRef = useRef<HTMLInputElement>(null)
const isRenaming = renamingNodeId === nodeId
useEffect(() => {
if (isRenaming) {
setInputValue(nodeId)
inputRef.current?.focus()
inputRef.current?.select()
}
}, [isRenaming, nodeId])
const applyRename = useCallback(() => {
if (!setNodes || !setEdges || !setRenamingNodeId) return
const newId = inputValue.trim()
if (!newId || newId === nodeId) {
setRenamingNodeId(null)
return
}
const existingIds = nodes.map((n: any) => n.id)
if (existingIds.includes(newId)) {
return
}
const { nodes: nextNodes, edges: nextEdges } = replaceNodeIdInGraph(nodes, edges, nodeId, newId)
setNodes(nextNodes as AppNode[])
setEdges(nextEdges)
setRenamingNodeId(null)
}, [nodeId, inputValue, nodes, edges, setNodes, setEdges, setRenamingNodeId])
const cancelRename = useCallback(() => {
setRenamingNodeId?.(null)
}, [setRenamingNodeId])
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault()
applyRename()
} else if (e.key === 'Escape') {
e.preventDefault()
cancelRename()
}
},
[applyRename, cancelRename]
)
if (!isRenaming) {
return <>{displayTitle}</>
}
return (
<input
ref={inputRef}
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={cancelRename}
className="nodrag nopan flex-1 min-w-0 rounded border border-input bg-background px-1.5 py-0 text-sm font-semibold outline-none focus:ring-1 focus:ring-ring"
data-slot="base-node-title"
/>
)
}