1- import { useRef , useState } from 'react'
1+ import { useEffect , useLayoutEffect , useRef , useState } from 'react'
2+ import { ChevronsUpDown } from 'lucide-react'
23import { useReactFlow } from 'reactflow'
34import { checkEnvVarTrigger , EnvVarDropdown } from '@/components/ui/env-var-dropdown'
45import { formatDisplayText } from '@/components/ui/formatted-text'
@@ -17,14 +18,21 @@ interface LongInputProps {
1718 subBlockId : string
1819 isConnecting : boolean
1920 config : SubBlockConfig
21+ rows ?: number
2022}
2123
24+ // Constants
25+ const DEFAULT_ROWS = 4
26+ const ROW_HEIGHT_PX = 24
27+ const MIN_HEIGHT_PX = 80
28+
2229export function LongInput ( {
2330 placeholder,
2431 blockId,
2532 subBlockId,
2633 isConnecting,
2734 config,
35+ rows,
2836} : LongInputProps ) {
2937 const [ value , setValue ] = useSubBlockValue ( blockId , subBlockId )
3038 const [ showEnvVars , setShowEnvVars ] = useState ( false )
@@ -34,10 +42,32 @@ export function LongInput({
3442 const textareaRef = useRef < HTMLTextAreaElement > ( null )
3543 const overlayRef = useRef < HTMLDivElement > ( null )
3644 const [ activeSourceBlockId , setActiveSourceBlockId ] = useState < string | null > ( null )
45+ const containerRef = useRef < HTMLDivElement > ( null )
46+
47+ // Calculate initial height based on rows prop with reasonable defaults
48+ const getInitialHeight = ( ) => {
49+ // Use provided rows or default, then convert to pixels with a minimum
50+ const rowCount = rows || DEFAULT_ROWS
51+ return Math . max ( rowCount * ROW_HEIGHT_PX , MIN_HEIGHT_PX )
52+ }
53+
54+ const [ height , setHeight ] = useState ( getInitialHeight ( ) )
55+ const isResizing = useRef ( false )
3756
3857 // Get ReactFlow instance for zoom control
3958 const reactFlowInstance = useReactFlow ( )
4059
60+ // Set initial height on first render
61+ useLayoutEffect ( ( ) => {
62+ const initialHeight = getInitialHeight ( )
63+ setHeight ( initialHeight )
64+
65+ if ( textareaRef . current && overlayRef . current ) {
66+ textareaRef . current . style . height = `${ initialHeight } px`
67+ overlayRef . current . style . height = `${ initialHeight } px`
68+ }
69+ } , [ rows ] )
70+
4171 // Handle input changes
4272 const handleChange = ( e : React . ChangeEvent < HTMLTextAreaElement > ) => {
4373 const newValue = e . target . value
@@ -63,6 +93,47 @@ export function LongInput({
6393 }
6494 }
6595
96+ // Ensure overlay updates when content changes
97+ useEffect ( ( ) => {
98+ if ( textareaRef . current && overlayRef . current ) {
99+ // Ensure scrolling is synchronized
100+ overlayRef . current . scrollTop = textareaRef . current . scrollTop
101+ overlayRef . current . scrollLeft = textareaRef . current . scrollLeft
102+ }
103+ } , [ value ] )
104+
105+ // Handle resize functionality
106+ const startResize = ( e : React . MouseEvent ) => {
107+ e . preventDefault ( )
108+ isResizing . current = true
109+
110+ const startY = e . clientY
111+ const startHeight = height
112+
113+ const handleMouseMove = ( moveEvent : MouseEvent ) => {
114+ if ( ! isResizing . current ) return
115+
116+ const deltaY = moveEvent . clientY - startY
117+ const newHeight = Math . max ( MIN_HEIGHT_PX , startHeight + deltaY )
118+
119+ setHeight ( newHeight )
120+
121+ if ( textareaRef . current && overlayRef . current ) {
122+ textareaRef . current . style . height = `${ newHeight } px`
123+ overlayRef . current . style . height = `${ newHeight } px`
124+ }
125+ }
126+
127+ const handleMouseUp = ( ) => {
128+ isResizing . current = false
129+ document . removeEventListener ( 'mousemove' , handleMouseMove )
130+ document . removeEventListener ( 'mouseup' , handleMouseUp )
131+ }
132+
133+ document . addEventListener ( 'mousemove' , handleMouseMove )
134+ document . addEventListener ( 'mouseup' , handleMouseUp )
135+ }
136+
66137 // Drag and Drop handlers
67138 const handleDragOver = ( e : React . DragEvent < HTMLTextAreaElement > ) => {
68139 if ( config ?. connectionDroppable === false ) return
@@ -131,9 +202,7 @@ export function LongInput({
131202 const { x : viewportX , y : viewportY } = reactFlowInstance . getViewport ( )
132203
133204 // Calculate zoom factor based on wheel delta
134- // Use a smaller factor for smoother zooming that matches ReactFlow's native behavior
135205 const delta = e . deltaY > 0 ? 1 : - 1
136- // Using 0.98 instead of 0.95 makes the zoom much slower and more gradual
137206 const zoomFactor = Math . pow ( 0.96 , delta )
138207
139208 // Calculate new zoom level with min/max constraints
@@ -169,16 +238,16 @@ export function LongInput({
169238 }
170239
171240 return (
172- < div className = "relative w-full" >
241+ < div ref = { containerRef } className = "relative w-full" style = { { height : ` ${ height } px` } } >
173242 < Textarea
174243 ref = { textareaRef }
175244 className = { cn (
176- 'w-full placeholder:text-muted-foreground/50 allow-scroll text-transparent caret-foreground break-words whitespace-pre-wrap box-border ' ,
245+ 'w-full min-h-full resize-none text-transparent caret-foreground placeholder:text-muted-foreground/50 allow-scroll ' ,
177246 isConnecting &&
178247 config ?. connectionDroppable !== false &&
179248 'focus-visible:ring-blue-500 ring-2 ring-blue-500 ring-offset-2'
180249 ) }
181- rows = { 4 }
250+ rows = { rows ?? DEFAULT_ROWS }
182251 placeholder = { placeholder ?? '' }
183252 value = { value ?. toString ( ) ?? '' }
184253 onChange = { handleChange }
@@ -192,14 +261,33 @@ export function LongInput({
192261 setShowTags ( false )
193262 setSearchTerm ( '' )
194263 } }
264+ style = { {
265+ fontFamily : 'inherit' ,
266+ lineHeight : 'inherit' ,
267+ height : `${ height } px` ,
268+ } }
195269 />
196270 < div
197271 ref = { overlayRef }
198- className = "absolute inset-0 pointer-events-none px-3 py-2 overflow-auto whitespace-pre-wrap break-words text-sm bg-transparent box-border"
199- style = { { width : 'calc(100% - 2px)' } }
272+ className = "absolute inset-0 pointer-events-none px-3 py-2 overflow-auto whitespace-pre-wrap break-words text-sm bg-transparent"
273+ style = { {
274+ fontFamily : 'inherit' ,
275+ lineHeight : 'inherit' ,
276+ width : textareaRef . current ? `${ textareaRef . current . clientWidth } px` : '100%' ,
277+ height : `${ height } px` ,
278+ } }
200279 >
201280 { formatDisplayText ( value ?. toString ( ) ?? '' , true ) }
202281 </ div >
282+
283+ { /* Custom resize handle */ }
284+ < div
285+ className = "absolute bottom-1 right-1 w-4 h-4 cursor-s-resize flex items-center justify-center bg-background rounded-sm"
286+ onMouseDown = { startResize }
287+ >
288+ < ChevronsUpDown className = "h-3 w-3 text-muted-foreground/70" />
289+ </ div >
290+
203291 < EnvVarDropdown
204292 visible = { showEnvVars }
205293 onSelect = { setValue }
0 commit comments