From d36f0406dbe4cd67252f19285c772a5f48b4f6e2 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Wed, 18 Feb 2026 02:56:41 -0500 Subject: [PATCH] fix: prevent category Cancel overflow and add Tab/Enter to create options - TreeMetadataForm: add min-w-0 + shrink-0 to keep Cancel button in-panel - NodeFormDecision: Tab or Enter on the last non-empty option input adds a new option and auto-focuses it; empty last input lets Tab pass through normally Co-Authored-By: Claude Sonnet 4.6 --- .../tree-editor/NodeFormDecision.tsx | 30 +++++++++++++++++++ .../tree-editor/TreeMetadataForm.tsx | 5 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/tree-editor/NodeFormDecision.tsx b/frontend/src/components/tree-editor/NodeFormDecision.tsx index 06acf18c..c9570167 100644 --- a/frontend/src/components/tree-editor/NodeFormDecision.tsx +++ b/frontend/src/components/tree-editor/NodeFormDecision.tsx @@ -1,3 +1,4 @@ +import { useRef, useEffect } from 'react' import { Play } from 'lucide-react' import { DynamicArrayField } from './DynamicArrayField' import { useTreeEditorStore } from '@/store/treeEditorStore' @@ -18,6 +19,9 @@ const indexToLetter = (index: number): string => { export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) { const { validationErrors } = useTreeEditorStore() const isRootNode = node.id === 'root' + // Track input elements by index so we can focus the newly added one + const inputRefs = useRef>(new Map()) + const shouldFocusLast = useRef(false) const questionError = validationErrors.find( e => e.nodeId === node.id && e.field === 'question' @@ -27,6 +31,15 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) { e => e.nodeId === node.id && e.field === 'options' ) + // After options array grows (due to keyboard-triggered add), focus the last input + useEffect(() => { + if (shouldFocusLast.current) { + shouldFocusLast.current = false + const lastIndex = (node.options?.length ?? 1) - 1 + inputRefs.current.get(lastIndex)?.focus() + } + }, [node.options?.length]) + const handleAddOption = () => { const newOption: TreeOption = { id: crypto.randomUUID(), @@ -38,6 +51,12 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) { }) } + // Add a new option and focus it (used by keyboard shortcut) + const handleAddOptionAndFocus = () => { + shouldFocusLast.current = true + handleAddOption() + } + const handleRemoveOption = (index: number) => { const newOptions = [...(node.options || [])] newOptions.splice(index, 1) @@ -147,6 +166,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) { e => e.nodeId === node.id && e.field === `options[${index}].label` ) const letter = indexToLetter(index) + const isLastOption = index === (node.options?.length ?? 1) - 1 return (
@@ -158,12 +178,22 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{ + if (el) inputRefs.current.set(index, el) + else inputRefs.current.delete(index) + }} type="text" value={option.label} onChange={(e) => handleUpdateOption(index, { label: e.target.value })} placeholder={isRootNode ? `Branch ${letter}: e.g., "Network Issues"...` : `Option ${letter} label`} + onKeyDown={(e) => { + if ((e.key === 'Tab' || e.key === 'Enter') && isLastOption && option.label.trim()) { + e.preventDefault() + handleAddOptionAndFocus() + } + }} className={cn( 'block w-full rounded-md border px-3 py-2 text-sm', 'bg-background text-foreground placeholder:text-muted-foreground', diff --git a/frontend/src/components/tree-editor/TreeMetadataForm.tsx b/frontend/src/components/tree-editor/TreeMetadataForm.tsx index 01e3a818..2bacbcb1 100644 --- a/frontend/src/components/tree-editor/TreeMetadataForm.tsx +++ b/frontend/src/components/tree-editor/TreeMetadataForm.tsx @@ -132,19 +132,20 @@ export function TreeMetadataForm() { onChange={(e) => setCategory(e.target.value)} placeholder="Enter new category" className={cn( - 'block flex-1 rounded-md border border-border px-3 py-2 text-sm', + 'block min-w-0 flex-1 rounded-md border border-border px-3 py-2 text-sm', 'bg-card text-foreground placeholder:text-muted-foreground', 'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20' )} autoFocus />