1- import { useRef , useState } from 'react'
1+ import { useEffect , useRef , useState } from 'react'
22import { ChevronDown , Plus , Trash } from 'lucide-react'
33import { Badge } from '@/components/ui/badge'
44import { Button } from '@/components/ui/button'
@@ -8,10 +8,16 @@ import {
88 DropdownMenuItem ,
99 DropdownMenuTrigger ,
1010} from '@/components/ui/dropdown-menu'
11- import { formatDisplayText } from '@/components/ui/formatted-text'
1211import { Input } from '@/components/ui/input'
1312import { Label } from '@/components/ui/label'
14- import { checkTagTrigger , TagDropdown } from '@/components/ui/tag-dropdown'
13+ import {
14+ Select ,
15+ SelectContent ,
16+ SelectItem ,
17+ SelectTrigger ,
18+ SelectValue ,
19+ } from '@/components/ui/select'
20+ import { Textarea } from '@/components/ui/textarea'
1521import { cn } from '@/lib/utils'
1622import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value'
1723
@@ -64,22 +70,26 @@ export function FieldFormat({
6470 config,
6571} : FieldFormatProps ) {
6672 const [ storeValue , setStoreValue ] = useSubBlockValue < Field [ ] > ( blockId , subBlockId )
67- const [ tagDropdownStates , setTagDropdownStates ] = useState <
68- Record <
69- string ,
70- {
71- visible : boolean
72- cursorPosition : number
73- }
74- >
75- > ( { } )
7673 const [ dragHighlight , setDragHighlight ] = useState < Record < string , boolean > > ( { } )
77- const valueInputRefs = useRef < Record < string , HTMLInputElement > > ( { } )
74+ const valueInputRefs = useRef < Record < string , HTMLInputElement | HTMLTextAreaElement > > ( { } )
75+ const [ localValues , setLocalValues ] = useState < Record < string , string > > ( { } )
7876
7977 // Use preview value when in preview mode, otherwise use store value
8078 const value = isPreview ? previewValue : storeValue
8179 const fields : Field [ ] = value || [ ]
8280
81+ useEffect ( ( ) => {
82+ const initial : Record < string , string > = { }
83+ ; ( fields || [ ] ) . forEach ( ( f ) => {
84+ if ( localValues [ f . id ] === undefined ) {
85+ initial [ f . id ] = ( f . value as string ) || ''
86+ }
87+ } )
88+ if ( Object . keys ( initial ) . length > 0 ) {
89+ setLocalValues ( ( prev ) => ( { ...prev , ...initial } ) )
90+ }
91+ } , [ fields ] )
92+
8393 // Field operations
8494 const addField = ( ) => {
8595 if ( isPreview || disabled ) return
@@ -88,12 +98,12 @@ export function FieldFormat({
8898 ...DEFAULT_FIELD ,
8999 id : crypto . randomUUID ( ) ,
90100 }
91- setStoreValue ( [ ...fields , newField ] )
101+ setStoreValue ( [ ...( fields || [ ] ) , newField ] )
92102 }
93103
94104 const removeField = ( id : string ) => {
95105 if ( isPreview || disabled ) return
96- setStoreValue ( fields . filter ( ( field : Field ) => field . id !== id ) )
106+ setStoreValue ( ( fields || [ ] ) . filter ( ( field : Field ) => field . id !== id ) )
97107 }
98108
99109 // Validate field name for API safety
@@ -103,38 +113,22 @@ export function FieldFormat({
103113 return name . replace ( / [ \x00 - \x1F " \\ ] / g, '' ) . trim ( )
104114 }
105115
106- // Tag dropdown handlers
107116 const handleValueInputChange = ( fieldId : string , newValue : string ) => {
108- const input = valueInputRefs . current [ fieldId ]
109- if ( ! input ) return
110-
111- const cursorPosition = input . selectionStart || 0
112- const shouldShow = checkTagTrigger ( newValue , cursorPosition )
117+ setLocalValues ( ( prev ) => ( { ...prev , [ fieldId ] : newValue } ) )
118+ }
113119
114- setTagDropdownStates ( ( prev ) => ( {
115- ...prev ,
116- [ fieldId ] : {
117- visible : shouldShow . show ,
118- cursorPosition,
119- } ,
120- } ) )
120+ // Value normalization: keep it simple for string types
121121
122- updateField ( fieldId , 'value' , newValue )
123- }
122+ const handleValueInputBlur = ( field : Field ) => {
123+ if ( isPreview || disabled ) return
124124
125- const handleTagSelect = ( fieldId : string , newValue : string ) => {
126- updateField ( fieldId , 'value' , newValue )
127- setTagDropdownStates ( ( prev ) => ( {
128- ...prev ,
129- [ fieldId ] : { ...prev [ fieldId ] , visible : false } ,
130- } ) )
131- }
125+ const inputEl = valueInputRefs . current [ field . id ]
126+ if ( ! inputEl ) return
132127
133- const handleTagDropdownClose = ( fieldId : string ) => {
134- setTagDropdownStates ( ( prev ) => ( {
135- ...prev ,
136- [ fieldId ] : { ...prev [ fieldId ] , visible : false } ,
137- } ) )
128+ const current = localValues [ field . id ] ?? inputEl . value ?? ''
129+ const trimmed = current . trim ( )
130+ if ( ! trimmed ) return
131+ updateField ( field . id , 'value' , current )
138132 }
139133
140134 // Drag and drop handlers for connection blocks
@@ -152,47 +146,8 @@ export function FieldFormat({
152146 const handleDrop = ( e : React . DragEvent , fieldId : string ) => {
153147 e . preventDefault ( )
154148 setDragHighlight ( ( prev ) => ( { ...prev , [ fieldId ] : false } ) )
155-
156- try {
157- const data = JSON . parse ( e . dataTransfer . getData ( 'application/json' ) )
158- if ( data . type === 'connectionBlock' && data . connectionData ) {
159- const input = valueInputRefs . current [ fieldId ]
160- if ( ! input ) return
161-
162- // Focus the input first
163- input . focus ( )
164-
165- // Get current cursor position or use end of field
166- const dropPosition = input . selectionStart ?? ( input . value ?. length || 0 )
167-
168- // Insert '<' at drop position to trigger the dropdown
169- const currentValue = input . value || ''
170- const newValue = `${ currentValue . slice ( 0 , dropPosition ) } <${ currentValue . slice ( dropPosition ) } `
171-
172- // Update the field value
173- updateField ( fieldId , 'value' , newValue )
174-
175- // Set cursor position and show dropdown
176- setTimeout ( ( ) => {
177- input . selectionStart = dropPosition + 1
178- input . selectionEnd = dropPosition + 1
179-
180- // Trigger dropdown by simulating the tag check
181- const cursorPosition = dropPosition + 1
182- const shouldShow = checkTagTrigger ( newValue , cursorPosition )
183-
184- setTagDropdownStates ( ( prev ) => ( {
185- ...prev ,
186- [ fieldId ] : {
187- visible : shouldShow . show ,
188- cursorPosition,
189- } ,
190- } ) )
191- } , 0 )
192- }
193- } catch ( error ) {
194- console . error ( 'Error handling drop:' , error )
195- }
149+ const input = valueInputRefs . current [ fieldId ]
150+ input ?. focus ( )
196151 }
197152
198153 // Update handlers
@@ -204,12 +159,14 @@ export function FieldFormat({
204159 value = validateFieldName ( value )
205160 }
206161
207- setStoreValue ( fields . map ( ( f : Field ) => ( f . id === id ? { ...f , [ field ] : value } : f ) ) )
162+ setStoreValue ( ( fields || [ ] ) . map ( ( f : Field ) => ( f . id === id ? { ...f , [ field ] : value } : f ) ) )
208163 }
209164
210165 const toggleCollapse = ( id : string ) => {
211166 if ( isPreview || disabled ) return
212- setStoreValue ( fields . map ( ( f : Field ) => ( f . id === id ? { ...f , collapsed : ! f . collapsed } : f ) ) )
167+ setStoreValue (
168+ ( fields || [ ] ) . map ( ( f : Field ) => ( f . id === id ? { ...f , collapsed : ! f . collapsed } : f ) )
169+ )
213170 }
214171
215172 // Field header
@@ -371,54 +328,66 @@ export function FieldFormat({
371328 < div className = 'space-y-1.5' >
372329 < Label className = 'text-xs' > Value</ Label >
373330 < div className = 'relative' >
374- < Input
375- ref = { ( el ) => {
376- if ( el ) valueInputRefs . current [ field . id ] = el
377- } }
378- name = 'value'
379- value = { field . value || '' }
380- onChange = { ( e ) => handleValueInputChange ( field . id , e . target . value ) }
381- onKeyDown = { ( e ) => {
382- if ( e . key === 'Escape' ) {
383- handleTagDropdownClose ( field . id )
331+ { field . type === 'boolean' ? (
332+ < Select
333+ value = { localValues [ field . id ] ?? ( field . value as string ) ?? '' }
334+ onValueChange = { ( v ) => {
335+ setLocalValues ( ( prev ) => ( { ...prev , [ field . id ] : v } ) )
336+ if ( ! isPreview && ! disabled ) updateField ( field . id , 'value' , v )
337+ } }
338+ >
339+ < SelectTrigger className = 'h-9 w-full justify-between font-normal' >
340+ < SelectValue placeholder = 'Select value' className = 'truncate' />
341+ </ SelectTrigger >
342+ < SelectContent >
343+ < SelectItem value = 'true' > true</ SelectItem >
344+ < SelectItem value = 'false' > false</ SelectItem >
345+ </ SelectContent >
346+ </ Select >
347+ ) : field . type === 'object' || field . type === 'array' ? (
348+ < Textarea
349+ ref = { ( el ) => {
350+ if ( el ) valueInputRefs . current [ field . id ] = el
351+ } }
352+ name = 'value'
353+ value = { localValues [ field . id ] ?? ( field . value as string ) ?? '' }
354+ onChange = { ( e ) => handleValueInputChange ( field . id , e . target . value ) }
355+ onBlur = { ( ) => handleValueInputBlur ( field ) }
356+ placeholder = {
357+ field . type === 'object' ? '{\n "key": "value"\n}' : '[\n 1, 2, 3\n]'
384358 }
385- } }
386- onDragOver = { ( e ) => handleDragOver ( e , field . id ) }
387- onDragLeave = { ( e ) => handleDragLeave ( e , field . id ) }
388- onDrop = { ( e ) => handleDrop ( e , field . id ) }
389- placeholder = { valuePlaceholder }
390- disabled = { isPreview || disabled }
391- className = { cn (
392- 'h-9 text-transparent caret-foreground placeholder:text-muted-foreground/50' ,
393- dragHighlight [ field . id ] && 'ring-2 ring-blue-500 ring-offset-2' ,
394- isConnecting &&
395- config ?. connectionDroppable !== false &&
396- 'ring-2 ring-blue-500 ring-offset-2 focus-visible:ring-blue-500'
397- ) }
398- />
399- { field . value && (
400- < div className = 'pointer-events-none absolute inset-0 flex items-center px-3 py-2' >
401- < div className = 'w-full overflow-hidden text-ellipsis whitespace-nowrap text-sm' >
402- { formatDisplayText ( field . value , true ) }
403- </ div >
404- </ div >
359+ disabled = { isPreview || disabled }
360+ className = { cn (
361+ 'min-h-[120px] font-mono text-sm placeholder:text-muted-foreground/50' ,
362+ dragHighlight [ field . id ] && 'ring-2 ring-blue-500 ring-offset-2' ,
363+ isConnecting &&
364+ config ?. connectionDroppable !== false &&
365+ 'ring-2 ring-blue-500 ring-offset-2 focus-visible:ring-blue-500'
366+ ) }
367+ />
368+ ) : (
369+ < Input
370+ ref = { ( el ) => {
371+ if ( el ) valueInputRefs . current [ field . id ] = el
372+ } }
373+ name = 'value'
374+ value = { localValues [ field . id ] ?? field . value ?? '' }
375+ onChange = { ( e ) => handleValueInputChange ( field . id , e . target . value ) }
376+ onBlur = { ( ) => handleValueInputBlur ( field ) }
377+ onDragOver = { ( e ) => handleDragOver ( e , field . id ) }
378+ onDragLeave = { ( e ) => handleDragLeave ( e , field . id ) }
379+ onDrop = { ( e ) => handleDrop ( e , field . id ) }
380+ placeholder = { valuePlaceholder }
381+ disabled = { isPreview || disabled }
382+ className = { cn (
383+ 'h-9 placeholder:text-muted-foreground/50' ,
384+ dragHighlight [ field . id ] && 'ring-2 ring-blue-500 ring-offset-2' ,
385+ isConnecting &&
386+ config ?. connectionDroppable !== false &&
387+ 'ring-2 ring-blue-500 ring-offset-2 focus-visible:ring-blue-500'
388+ ) }
389+ />
405390 ) }
406- < TagDropdown
407- visible = { tagDropdownStates [ field . id ] ?. visible || false }
408- onSelect = { ( newValue ) => handleTagSelect ( field . id , newValue ) }
409- blockId = { blockId }
410- activeSourceBlockId = { null }
411- inputValue = { field . value || '' }
412- cursorPosition = { tagDropdownStates [ field . id ] ?. cursorPosition || 0 }
413- onClose = { ( ) => handleTagDropdownClose ( field . id ) }
414- style = { {
415- position : 'absolute' ,
416- top : '100%' ,
417- left : 0 ,
418- right : 0 ,
419- zIndex : 9999 ,
420- } }
421- />
422391 </ div >
423392 </ div >
424393 ) }
0 commit comments