@@ -102,6 +102,7 @@ export function VariablesInput({
102102 const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
103103 const valueInputRefs = useRef<Record<string, HTMLInputElement | HTMLTextAreaElement>>({})
104104 const overlayRefs = useRef<Record<string, HTMLDivElement>>({})
105+ const editorContainerRefs = useRef<Record<string, HTMLDivElement | null>>({})
105106 const [dragHighlight, setDragHighlight] = useState<Record<string, boolean>>({})
106107 const [collapsedAssignments, setCollapsedAssignments] = useState<Record<string, boolean>>({})
107108
@@ -190,6 +191,26 @@ export function VariablesInput({
190191 if (!editorValueChangeHandlersRef.current[assignmentId]) {
191192 editorValueChangeHandlersRef.current[assignmentId] = (newValue: string) => {
192193 updateAssignmentRef.current(assignmentId, { value: newValue })
194+
195+ const container = editorContainerRefs.current[assignmentId]
196+ const textarea = container?.querySelector('textarea')
197+ if (textarea) {
198+ const pos = textarea.selectionStart
199+ setCursorPosition(pos)
200+ setActiveFieldId(assignmentId)
201+
202+ const tagTrigger = checkTagTrigger(newValue, pos)
203+ setShowTags(tagTrigger.show)
204+ if (tagTrigger.show) {
205+ const textBeforeCursor = newValue.slice(0, pos)
206+ const lastOpenBracket = textBeforeCursor.lastIndexOf('<')
207+ const tagContent = textBeforeCursor.slice(lastOpenBracket + 1)
208+ const dotIndex = tagContent.indexOf('.')
209+ setActiveSourceBlockId(dotIndex > 0 ? tagContent.slice(0, dotIndex) : null)
210+ } else {
211+ setActiveSourceBlockId(null)
212+ }
213+ }
193214 }
194215 }
195216 return editorValueChangeHandlersRef.current[assignmentId]
@@ -215,16 +236,27 @@ export function VariablesInput({
215236 const assignment = assignments.find((a) => a.id === activeFieldId)
216237 const originalValue = assignment?.value || ''
217238 const textAfterCursor = originalValue.slice(cursorPosition)
239+ const isCodeEditor = assignment?.type === 'object' || assignment?.type === 'array'
218240
219241 updateAssignment(activeFieldId, { value: newValue })
220242 setShowTags(false)
221243
222244 setTimeout(() => {
223- const inputEl = valueInputRefs.current[activeFieldId]
224- if (inputEl) {
225- inputEl.focus()
226- const newCursorPos = newValue.length - textAfterCursor.length
227- inputEl.setSelectionRange(newCursorPos, newCursorPos)
245+ const newCursorPos = newValue.length - textAfterCursor.length
246+ if (isCodeEditor) {
247+ const container = editorContainerRefs.current[activeFieldId]
248+ const textarea = container?.querySelector('textarea')
249+ if (textarea) {
250+ textarea.focus()
251+ textarea.selectionStart = newCursorPos
252+ textarea.selectionEnd = newCursorPos
253+ }
254+ } else {
255+ const inputEl = valueInputRefs.current[activeFieldId]
256+ if (inputEl) {
257+ inputEl.focus()
258+ inputEl.setSelectionRange(newCursorPos, newCursorPos)
259+ }
228260 }
229261 }, 10)
230262 }
@@ -298,6 +330,39 @@ export function VariablesInput({
298330 setDragHighlight((prev) => ({ ...prev, [assignmentId]: false }))
299331 }
300332
333+ const handleEditorDrop = (e: React.DragEvent, assignmentId: string) => {
334+ if (isReadOnly) return
335+ e.preventDefault()
336+ try {
337+ const data = JSON.parse(e.dataTransfer.getData('application/json'))
338+ if (data.type !== 'connectionBlock') return
339+
340+ const container = editorContainerRefs.current[assignmentId]
341+ const textarea = container?.querySelector('textarea')
342+ const assignment = assignments.find((a) => a.id === assignmentId)
343+ const currentValue = assignment?.value || ''
344+ const dropPosition = textarea?.selectionStart ?? currentValue.length
345+ const newValue = `${currentValue.slice(0, dropPosition)}<${currentValue.slice(dropPosition)}`
346+
347+ updateAssignment(assignmentId, { value: newValue })
348+ setActiveFieldId(assignmentId)
349+ setCursorPosition(dropPosition + 1)
350+
351+ if (data.connectionData?.sourceBlockId) {
352+ setActiveSourceBlockId(data.connectionData.sourceBlockId)
353+ }
354+
355+ setTimeout(() => {
356+ if (textarea) {
357+ textarea.focus()
358+ textarea.selectionStart = dropPosition + 1
359+ textarea.selectionEnd = dropPosition + 1
360+ setShowTags(true)
361+ }
362+ }, 0)
363+ } catch {}
364+ }
365+
301366 const toggleCollapse = (assignmentId: string) => {
302367 setCollapsedAssignments((prev) => ({
303368 ...prev,
@@ -433,7 +498,11 @@ export function VariablesInput({
433498 const gutterWidth = calculateGutterWidth(lineCount)
434499
435500 return (
436- <Code.Container className='min-h-[120px]'>
501+ <Code.Container
502+ className='min-h-[120px]'
503+ onDragOver={(e) => e.preventDefault()}
504+ onDrop={(e) => handleEditorDrop(e, assignment.id)}
505+ >
437506 <Code.Gutter width={gutterWidth}>
438507 {Array.from({ length: lineCount }, (_, i) => (
439508 <div
@@ -445,7 +514,14 @@ export function VariablesInput({
445514 </div>
446515 ))}
447516 </Code.Gutter>
448- <Code.Content paddingLeft={`${gutterWidth}px`}>
517+ <Code.Content
518+ paddingLeft={`${gutterWidth}px`}
519+ editorRef={
520+ ((el: HTMLDivElement | null) => {
521+ editorContainerRefs.current[assignment.id] = el
522+ }) as unknown as React.RefObject<HTMLDivElement | null>
523+ }
524+ >
449525 <Code.Placeholder
450526 gutterWidth={gutterWidth}
451527 show={fieldValue.length === 0}
@@ -461,6 +537,29 @@ export function VariablesInput({
461537 disabled={isReadOnly}
462538 {...getCodeEditorProps({ disabled: isReadOnly })}
463539 />
540+ {showTags && activeFieldId === assignment.id && (
541+ <TagDropdown
542+ visible={showTags}
543+ onSelect={handleTagSelect}
544+ blockId={blockId}
545+ activeSourceBlockId={activeSourceBlockId}
546+ inputValue={fieldValue}
547+ cursorPosition={cursorPosition}
548+ onClose={() => {
549+ setShowTags(false)
550+ setActiveSourceBlockId(null)
551+ }}
552+ inputRef={
553+ {
554+ current:
555+ (editorContainerRefs.current[
556+ assignment.id
557+ ]?.querySelector('textarea') as HTMLTextAreaElement) ??
558+ null,
559+ } as React.RefObject<HTMLTextAreaElement | HTMLInputElement>
560+ }
561+ />
562+ )}
464563 </Code.Content>
465564 </Code.Container>
466565 )
0 commit comments