85 lines
2.7 KiB
TypeScript
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"
|
|
/>
|
|
)
|
|
}
|