@@ -8,6 +8,7 @@ import { NodeProps } from 'reactflow'
88import { Badge } from '@/components/ui/badge'
99import { Input } from '@/components/ui/input'
1010import { Popover , PopoverContent , PopoverTrigger } from '@/components/ui/popover'
11+ import { checkTagTrigger , TagDropdown } from '@/components/ui/tag-dropdown'
1112import { cn } from '@/lib/utils'
1213import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1314
@@ -28,6 +29,11 @@ export function LoopInput({ id }: NodeProps) {
2829 const [ open , setOpen ] = useState ( false )
2930 const editorRef = useRef < HTMLDivElement | null > ( null )
3031
32+ // State for tag dropdown
33+ const [ showTags , setShowTags ] = useState ( false )
34+ const [ cursorPosition , setCursorPosition ] = useState ( 0 )
35+ const [ activeSourceBlockId , setActiveSourceBlockId ] = useState < string | null > ( null )
36+
3137 // Initialize editor value from the store
3238 useEffect ( ( ) => {
3339 if ( loopType === 'forEach' && loop ?. forEachItems ) {
@@ -79,17 +85,80 @@ export function LoopInput({ id }: NodeProps) {
7985 }
8086 }
8187
88+ // Handle tag selection
89+ const handleTagSelect = ( newValue : string ) => {
90+ setEditorValue ( newValue )
91+ updateLoopForEachItems ( loopId , newValue )
92+ setShowTags ( false )
93+ }
94+
8295 const handleEditorChange = ( value : string ) => {
8396 // Always set the editor value to exactly what the user typed
8497 setEditorValue ( value )
8598
99+ // Get current cursor position (approximation for editor)
100+ const textArea = editorRef . current ?. querySelector ( 'textarea' )
101+ if ( textArea ) {
102+ setCursorPosition ( textArea . selectionStart || 0 )
103+
104+ // Check for tag trigger
105+ const tagTrigger = checkTagTrigger ( value , textArea . selectionStart || 0 )
106+ setShowTags ( tagTrigger . show )
107+ }
108+
86109 // Save the items to the store for forEach loops
87110 if ( loopType === 'forEach' ) {
88111 // Pass the exact string to preserve formatting
89112 updateLoopForEachItems ( loopId , value )
90113 }
91114 }
92115
116+ // Handle editor focus
117+ const handleEditorFocus = ( ) => {
118+ // Reset tag dropdown state
119+ setShowTags ( false )
120+ setActiveSourceBlockId ( null )
121+ }
122+
123+ // Handle editor blur
124+ const handleEditorBlur = ( ) => {
125+ // We don't immediately hide the tag dropdown to allow clicking on it
126+ setTimeout ( ( ) => {
127+ if ( ! document . activeElement ?. closest ( '.tag-dropdown' ) ) {
128+ setShowTags ( false )
129+ }
130+ } , 100 )
131+ }
132+
133+ // Add and remove keyboard event listener for editor
134+ useEffect ( ( ) => {
135+ const textArea = editorRef . current ?. querySelector ( 'textarea' )
136+ if ( ! textArea ) return
137+
138+ const handleKeyboardEvent = ( e : KeyboardEvent ) => {
139+ // Update cursor position when key is pressed in editor
140+ setCursorPosition ( textArea . selectionStart || 0 )
141+
142+ // Check for tag trigger
143+ const tagTrigger = checkTagTrigger ( editorValue , textArea . selectionStart || 0 )
144+
145+ if ( e . key === 'Escape' ) {
146+ setShowTags ( false )
147+ } else if ( e . key === '<' ) {
148+ // Show tags dropdown when '<' is typed
149+ setShowTags ( true )
150+ } else if ( e . key === '>' ) {
151+ // Hide tags dropdown when '>' is typed
152+ setShowTags ( false )
153+ } else {
154+ setShowTags ( tagTrigger . show )
155+ }
156+ }
157+
158+ textArea . addEventListener ( 'keyup' , handleKeyboardEvent )
159+ return ( ) => textArea . removeEventListener ( 'keyup' , handleKeyboardEvent )
160+ } , [ editorValue , editorRef ] )
161+
93162 // Determine label based on loop type
94163 const getLabel = ( ) => {
95164 switch ( loopType ) {
@@ -149,27 +218,45 @@ export function LoopInput({ id }: NodeProps) {
149218 />
150219 </ div >
151220 ) : (
152- // Code editor for 'forEach' loops
153- < div
154- className = "relative min-h-[80px] rounded-md bg-background font-mono text-sm px-3 pt-2 pb-3 border border-input"
155- ref = { editorRef }
156- >
157- { editorValue === '' && (
158- < div className = "absolute top-[8.5px] left-3 text-muted-foreground/50 pointer-events-none select-none" >
159- { getPlaceholder ( ) }
160- </ div >
161- ) }
162- < Editor
163- value = { editorValue }
164- onValueChange = { handleEditorChange }
165- highlight = { ( code ) => highlight ( code , languages . javascript , 'javascript' ) }
166- padding = { 0 }
167- style = { {
168- fontFamily : 'monospace' ,
169- lineHeight : '21px' ,
221+ // Code editor for 'forEach' loops with tag dropdown support
222+ < div className = "relative" >
223+ < div
224+ className = "relative min-h-[80px] rounded-md bg-background font-mono text-sm px-3 pt-2 pb-3 border border-input"
225+ ref = { editorRef }
226+ >
227+ { editorValue === '' && (
228+ < div className = "absolute top-[8.5px] left-3 text-muted-foreground/50 pointer-events-none select-none" >
229+ { getPlaceholder ( ) }
230+ </ div >
231+ ) }
232+ < Editor
233+ value = { editorValue }
234+ onValueChange = { handleEditorChange }
235+ highlight = { ( code ) => highlight ( code , languages . javascript , 'javascript' ) }
236+ padding = { 0 }
237+ style = { {
238+ fontFamily : 'monospace' ,
239+ lineHeight : '21px' ,
240+ } }
241+ className = "focus:outline-none w-full"
242+ textareaClassName = "focus:outline-none focus:ring-0 bg-transparent resize-none w-full overflow-hidden whitespace-pre-wrap"
243+ onFocus = { handleEditorFocus }
244+ onBlur = { handleEditorBlur }
245+ />
246+ </ div >
247+ < TagDropdown
248+ visible = { showTags }
249+ onSelect = { handleTagSelect }
250+ blockId = { id }
251+ activeSourceBlockId = { activeSourceBlockId }
252+ inputValue = { editorValue }
253+ cursorPosition = { cursorPosition }
254+ onClose = { ( ) => {
255+ setShowTags ( false )
256+ setActiveSourceBlockId ( null )
170257 } }
171- className = "focus:outline-none w-full "
172- textareaClassName = "focus:outline-none focus:ring-0 bg-transparent resize-none w-full overflow-hidden whitespace-pre-wrap"
258+ className = "w-[calc(100%)] tag-dropdown "
259+ style = { { top : 'calc(100% + 4px)' } }
173260 />
174261 </ div >
175262 ) }
0 commit comments