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,11 @@ import {
88 DropdownMenuItem ,
99 DropdownMenuTrigger ,
1010} from '@/components/ui/dropdown-menu'
11- import { formatDisplayText } from '@/components/ui/formatted-text'
11+
1212import { Input } from '@/components/ui/input'
1313import { Label } from '@/components/ui/label'
14- import { checkTagTrigger , TagDropdown } from '@/components/ui/tag-dropdown'
14+ import { Switch } from '@/components/ui/switch'
15+ import { Textarea } from '@/components/ui/textarea'
1516import { cn } from '@/lib/utils'
1617import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value'
1718
@@ -64,22 +65,26 @@ export function FieldFormat({
6465 config,
6566} : FieldFormatProps ) {
6667 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- > ( { } )
7668 const [ dragHighlight , setDragHighlight ] = useState < Record < string , boolean > > ( { } )
7769 const valueInputRefs = useRef < Record < string , HTMLInputElement > > ( { } )
70+ const [ localValues , setLocalValues ] = useState < Record < string , string > > ( { } )
7871
7972 // Use preview value when in preview mode, otherwise use store value
8073 const value = isPreview ? previewValue : storeValue
8174 const fields : Field [ ] = value || [ ]
8275
76+ useEffect ( ( ) => {
77+ const initial : Record < string , string > = { }
78+ ; ( fields || [ ] ) . forEach ( ( f ) => {
79+ if ( localValues [ f . id ] === undefined ) {
80+ initial [ f . id ] = ( f . value as string ) || ''
81+ }
82+ } )
83+ if ( Object . keys ( initial ) . length > 0 ) {
84+ setLocalValues ( ( prev ) => ( { ...prev , ...initial } ) )
85+ }
86+ } , [ fields ] )
87+
8388 // Field operations
8489 const addField = ( ) => {
8590 if ( isPreview || disabled ) return
@@ -88,12 +93,12 @@ export function FieldFormat({
8893 ...DEFAULT_FIELD ,
8994 id : crypto . randomUUID ( ) ,
9095 }
91- setStoreValue ( [ ...fields , newField ] )
96+ setStoreValue ( [ ...( fields || [ ] ) , newField ] )
9297 }
9398
9499 const removeField = ( id : string ) => {
95100 if ( isPreview || disabled ) return
96- setStoreValue ( fields . filter ( ( field : Field ) => field . id !== id ) )
101+ setStoreValue ( ( fields || [ ] ) . filter ( ( field : Field ) => field . id !== id ) )
97102 }
98103
99104 // Validate field name for API safety
@@ -103,38 +108,22 @@ export function FieldFormat({
103108 return name . replace ( / [ \x00 - \x1F " \\ ] / g, '' ) . trim ( )
104109 }
105110
106- // Tag dropdown handlers
107111 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 )
112+ setLocalValues ( ( prev ) => ( { ...prev , [ fieldId ] : newValue } ) )
113+ }
113114
114- setTagDropdownStates ( ( prev ) => ( {
115- ...prev ,
116- [ fieldId ] : {
117- visible : shouldShow . show ,
118- cursorPosition,
119- } ,
120- } ) )
115+ // Value normalization: keep it simple for string types
121116
122- updateField ( fieldId , 'value' , newValue )
123- }
117+ const handleValueInputBlur = ( field : Field ) => {
118+ if ( isPreview || disabled ) return
124119
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- }
120+ const inputEl = valueInputRefs . current [ field . id ]
121+ if ( ! inputEl ) return
132122
133- const handleTagDropdownClose = ( fieldId : string ) => {
134- setTagDropdownStates ( ( prev ) => ( {
135- ...prev ,
136- [ fieldId ] : { ...prev [ fieldId ] , visible : false } ,
137- } ) )
123+ const current = localValues [ field . id ] ?? inputEl . value ?? ''
124+ const trimmed = current . trim ( )
125+ if ( ! trimmed ) return
126+ updateField ( field . id , 'value' , current )
138127 }
139128
140129 // Drag and drop handlers for connection blocks
@@ -152,47 +141,8 @@ export function FieldFormat({
152141 const handleDrop = ( e : React . DragEvent , fieldId : string ) => {
153142 e . preventDefault ( )
154143 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- }
144+ const input = valueInputRefs . current [ fieldId ]
145+ input ?. focus ( )
196146 }
197147
198148 // Update handlers
@@ -204,12 +154,14 @@ export function FieldFormat({
204154 value = validateFieldName ( value )
205155 }
206156
207- setStoreValue ( fields . map ( ( f : Field ) => ( f . id === id ? { ...f , [ field ] : value } : f ) ) )
157+ setStoreValue ( ( fields || [ ] ) . map ( ( f : Field ) => ( f . id === id ? { ...f , [ field ] : value } : f ) ) )
208158 }
209159
210160 const toggleCollapse = ( id : string ) => {
211161 if ( isPreview || disabled ) return
212- setStoreValue ( fields . map ( ( f : Field ) => ( f . id === id ? { ...f , collapsed : ! f . collapsed } : f ) ) )
162+ setStoreValue (
163+ ( fields || [ ] ) . map ( ( f : Field ) => ( f . id === id ? { ...f , collapsed : ! f . collapsed } : f ) )
164+ )
213165 }
214166
215167 // Field header
@@ -371,54 +323,65 @@ export function FieldFormat({
371323 < div className = 'space-y-1.5' >
372324 < Label className = 'text-xs' > Value</ Label >
373325 < 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 )
384- }
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 >
326+ { field . type === 'boolean' ? (
327+ < div className = 'flex h-9 items-center' >
328+ < Switch
329+ checked = {
330+ ( localValues [ field . id ] ?? String ( field . value ?? '' ) ) === 'true'
331+ }
332+ onCheckedChange = { ( checked ) => {
333+ const v = checked ? 'true' : 'false'
334+ setLocalValues ( ( prev ) => ( { ...prev , [ field . id ] : v } ) )
335+ if ( ! isPreview && ! disabled ) updateField ( field . id , 'value' , v )
336+ } }
337+ disabled = { isPreview || disabled }
338+ />
404339 </ div >
340+ ) : field . type === 'object' || field . type === 'array' ? (
341+ < Textarea
342+ ref = { ( el ) => {
343+ if ( el )
344+ valueInputRefs . current [ field . id ] = el as unknown as HTMLInputElement
345+ } }
346+ name = 'value'
347+ value = { localValues [ field . id ] ?? ( field . value as string ) ?? '' }
348+ onChange = { ( e ) => handleValueInputChange ( field . id , e . target . value ) }
349+ onBlur = { ( ) => handleValueInputBlur ( field ) }
350+ placeholder = {
351+ field . type === 'object' ? '{\n "key": "value"\n}' : '[\n 1, 2, 3\n]'
352+ }
353+ disabled = { isPreview || disabled }
354+ className = { cn (
355+ 'min-h-[120px] font-mono text-sm placeholder:text-muted-foreground/50' ,
356+ dragHighlight [ field . id ] && 'ring-2 ring-blue-500 ring-offset-2' ,
357+ isConnecting &&
358+ config ?. connectionDroppable !== false &&
359+ 'ring-2 ring-blue-500 ring-offset-2 focus-visible:ring-blue-500'
360+ ) }
361+ />
362+ ) : (
363+ < Input
364+ ref = { ( el ) => {
365+ if ( el ) valueInputRefs . current [ field . id ] = el
366+ } }
367+ name = 'value'
368+ value = { localValues [ field . id ] ?? field . value ?? '' }
369+ onChange = { ( e ) => handleValueInputChange ( field . id , e . target . value ) }
370+ onBlur = { ( ) => handleValueInputBlur ( field ) }
371+ onDragOver = { ( e ) => handleDragOver ( e , field . id ) }
372+ onDragLeave = { ( e ) => handleDragLeave ( e , field . id ) }
373+ onDrop = { ( e ) => handleDrop ( e , field . id ) }
374+ placeholder = { valuePlaceholder }
375+ disabled = { isPreview || disabled }
376+ className = { cn (
377+ 'h-9 placeholder:text-muted-foreground/50' ,
378+ dragHighlight [ field . id ] && 'ring-2 ring-blue-500 ring-offset-2' ,
379+ isConnecting &&
380+ config ?. connectionDroppable !== false &&
381+ 'ring-2 ring-blue-500 ring-offset-2 focus-visible:ring-blue-500'
382+ ) }
383+ />
405384 ) }
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- />
422385 </ div >
423386 </ div >
424387 ) }
0 commit comments