import { useMemo, useState, useRef, useEffect } from 'react' import { useTreeEditorStore } from '@/store/treeEditorStore' import type { NodeType } from '@/types' import { cn } from '@/lib/utils' // Special values for creating new nodes const CREATE_PREFIX = '__create_' const CREATE_DECISION = `${CREATE_PREFIX}decision__` const CREATE_ACTION = `${CREATE_PREFIX}action__` const CREATE_SOLUTION = `${CREATE_PREFIX}solution__` // Unicode symbols for node types (works in select options) const NODE_TYPE_SYMBOLS: Record = { decision: '\u24D8', // Information/question symbol action: '\u26A1', // Lightning bolt for action solution: '\u2713', // Checkmark for solution answer: '\u25CC' // Dashed circle for placeholder } // Node type labels for UI const NODE_TYPE_LABELS: Record = { decision: 'Decision', action: 'Action', solution: 'Solution', answer: 'Answer' } interface NodePickerProps { value: string onChange: (nodeId: string) => void /** The parent node ID - new nodes will be added as children of this node */ parentNodeId: string excludeNodeId?: string placeholder?: string className?: string label?: string error?: string /** Callback when a new node is created (receives the new node ID) */ onNodeCreated?: (nodeId: string) => void /** Whether to show the "Create New Node" options. Default: true. * Set to false in inline canvas editing to prevent premature store writes. */ allowCreate?: boolean } export function NodePicker({ value, onChange, parentNodeId, excludeNodeId, placeholder = 'Select a node...', className, label, error, onNodeCreated, allowCreate = true }: NodePickerProps) { const { getAvailableTargetNodes, addNode, updateNode } = useTreeEditorStore() const availableNodes = getAvailableTargetNodes(excludeNodeId) // State for inline node creation const [creatingNodeType, setCreatingNodeType] = useState(null) const [newNodeTitle, setNewNodeTitle] = useState('') const titleInputRef = useRef(null) // Focus the title input when creating a new node useEffect(() => { if (creatingNodeType && titleInputRef.current) { titleInputRef.current.focus() } }, [creatingNodeType]) // Group nodes by type const groupedNodes = useMemo(() => { const decisions = availableNodes.filter(n => n.type === 'decision') const actions = availableNodes.filter(n => n.type === 'action') const solutions = availableNodes.filter(n => n.type === 'solution') return { decisions, actions, solutions } }, [availableNodes]) const handleChange = (selectedValue: string) => { // Check if it's a "create new" option if (selectedValue.startsWith(CREATE_PREFIX)) { let nodeType: NodeType if (selectedValue === CREATE_DECISION) { nodeType = 'decision' } else if (selectedValue === CREATE_ACTION) { nodeType = 'action' } else if (selectedValue === CREATE_SOLUTION) { nodeType = 'solution' } else { return } // Show inline title input instead of immediately creating setCreatingNodeType(nodeType) setNewNodeTitle('') } else { // Normal selection onChange(selectedValue) } } const handleCreateNode = () => { if (!creatingNodeType || !newNodeTitle.trim()) return // Create the new node as a child of the parent const newNodeId = addNode(parentNodeId, creatingNodeType) // Set the title/question on the new node if (creatingNodeType === 'decision') { updateNode(newNodeId, { question: newNodeTitle.trim() }) } else { updateNode(newNodeId, { title: newNodeTitle.trim() }) } // Set this new node as the selected value onChange(newNodeId) // Notify parent if callback provided onNodeCreated?.(newNodeId) // Reset creation state setCreatingNodeType(null) setNewNodeTitle('') } const handleCancelCreate = () => { setCreatingNodeType(null) setNewNodeTitle('') } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && newNodeTitle.trim()) { e.preventDefault() handleCreateNode() } else if (e.key === 'Escape') { e.preventDefault() handleCancelCreate() } } // Find the label for the currently selected node const selectedNode = availableNodes.find(n => n.id === value) return (
{label && ( )} {/* Inline node creation UI */} {creatingNodeType ? (
New {NODE_TYPE_LABELS[creatingNodeType]}: setNewNodeTitle(e.target.value)} onKeyDown={handleKeyDown} placeholder={creatingNodeType === 'decision' ? 'Enter question...' : 'Enter title...'} className={cn( 'flex-1 rounded-md border border-border px-2 py-1 text-sm', 'bg-card text-foreground placeholder:text-muted-foreground', 'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20' )} />
) : ( <> {/* Show what's selected */} {value && selectedNode && (

→ {selectedNode.label}

)} )} {error &&

{error}

}
) } export default NodePicker