Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
import type { WandControlHandlers } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block'
import { restoreCursorAfterInsertion } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/utils'
import { WandPromptBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
import { useWand } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-wand'
Expand Down Expand Up @@ -539,6 +540,10 @@ export const Code = memo(function Code({
* @param newValue - The new code value with the selected tag inserted
*/
const handleTagSelect = (newValue: string) => {
const textarea = editorRef.current?.querySelector('textarea') as HTMLTextAreaElement | null
const liveCursor = textarea?.selectionStart ?? cursorPosition
const liveValue = textarea?.value ?? code

if (!isPreview && !readOnly) {
setCode(newValue)
emitTagSelection(newValue)
Expand All @@ -547,26 +552,26 @@ export const Code = memo(function Code({
setShowTags(false)
setActiveSourceBlockId(null)

setTimeout(() => {
editorRef.current?.querySelector('textarea')?.focus()
}, 0)
restoreCursorAfterInsertion(textarea, liveValue, liveCursor, newValue, 'tag')
}

/**
* Handles selection of an environment variable from the dropdown.
* @param newValue - The new code value with the selected env var inserted
*/
const handleEnvVarSelect = (newValue: string) => {
const textarea = editorRef.current?.querySelector('textarea') as HTMLTextAreaElement | null
const liveCursor = textarea?.selectionStart ?? cursorPosition
const liveValue = textarea?.value ?? code

if (!isPreview && !readOnly) {
setCode(newValue)
emitTagSelection(newValue)
recordChange(newValue)
}
setShowEnvVars(false)

setTimeout(() => {
editorRef.current?.querySelector('textarea')?.focus()
}, 0)
restoreCursorAfterInsertion(textarea, liveValue, liveCursor, newValue, 'envVar')
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
TagDropdown,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
import { restoreCursorAfterInsertion } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/utils'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
import { normalizeName } from '@/executor/constants'
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
Expand Down Expand Up @@ -557,6 +558,13 @@ export function ConditionInput({
const handleTagSelectImmediate = (blockId: string, newValue: string) => {
if (isPreview || disabled) return

const textarea = containerRef.current?.querySelector(
`[data-block-id="${blockId}"] textarea`
) as HTMLTextAreaElement | null
Comment thread
waleedlatif1 marked this conversation as resolved.
const blockValue = conditionalBlocks.find((b) => b.id === blockId)?.value ?? ''
const liveCursor = textarea?.selectionStart ?? 0
const liveValue = textarea?.value ?? blockValue

shouldPersistRef.current = true
setConditionalBlocks((blocks) =>
blocks.map((block) =>
Expand All @@ -582,11 +590,20 @@ export function ConditionInput({
: block
)
emitTagSelection(JSON.stringify(updatedBlocks))

restoreCursorAfterInsertion(textarea, liveValue, liveCursor, newValue, 'tag')
}

const handleEnvVarSelectImmediate = (blockId: string, newValue: string) => {
if (isPreview || disabled) return

const textarea = containerRef.current?.querySelector(
`[data-block-id="${blockId}"] textarea`
) as HTMLTextAreaElement | null
const blockValue = conditionalBlocks.find((b) => b.id === blockId)?.value ?? ''
const liveCursor = textarea?.selectionStart ?? 0
const liveValue = textarea?.value ?? blockValue

shouldPersistRef.current = true
setConditionalBlocks((blocks) =>
blocks.map((block) =>
Expand All @@ -612,6 +629,8 @@ export function ConditionInput({
: block
)
emitTagSelection(JSON.stringify(updatedBlocks))

restoreCursorAfterInsertion(textarea, liveValue, liveCursor, newValue, 'envVar')
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useParams } from 'next/navigation'
import { highlight, languages } from '@/components/emcn'
import {
Expand All @@ -7,6 +7,7 @@ import {
splitReferenceSegment,
} from '@/lib/workflows/sanitization/references'
import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
import { restoreCursorAfterInsertion } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/utils'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
import { normalizeName, REFERENCE } from '@/executor/constants'
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
Expand Down Expand Up @@ -60,6 +61,7 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId

const textareaRef = useRef<HTMLTextAreaElement | null>(null)
const editorContainerRef = useRef<HTMLDivElement>(null)
const editorValueRef = useRef('')

const [tempInputValue, setTempInputValue] = useState<string | null>(null)
const [showTagDropdown, setShowTagDropdown] = useState(false)
Expand Down Expand Up @@ -291,21 +293,27 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
const handleSubflowTagSelect = useCallback(
(newValue: string) => {
if (!currentBlockId || !isSubflow || !currentBlock) return

const textarea = textareaRef.current
const liveCursor = textarea?.selectionStart ?? cursorPosition
const liveValue = textarea?.value ?? editorValueRef.current

collaborativeUpdateIterationCollection(
currentBlockId,
currentBlock.type as 'loop' | 'parallel',
newValue
)
setShowTagDropdown(false)

setTimeout(() => {
const textarea = textareaRef.current
if (textarea) {
textarea.focus()
}
}, 0)
restoreCursorAfterInsertion(textarea, liveValue, liveCursor, newValue, 'tag')
},
[currentBlockId, isSubflow, currentBlock, collaborativeUpdateIterationCollection]
[
currentBlockId,
isSubflow,
currentBlock,
collaborativeUpdateIterationCollection,
cursorPosition,
]
)
Comment thread
waleedlatif1 marked this conversation as resolved.
Comment thread
waleedlatif1 marked this conversation as resolved.

// Compute derived values
Expand Down Expand Up @@ -353,6 +361,10 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
const inputValue = tempInputValue ?? iterations.toString()
const editorValue = isConditionMode ? conditionString : collectionString

useEffect(() => {
editorValueRef.current = editorValue
}, [editorValue])
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated

// Type options for combobox
const typeOptions =
isSubflow && subflowConfig
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Type of dropdown insertion that determines the delimiter pair used
* for cursor position computation.
*/
export type InsertionType = 'tag' | 'envVar'

/**
* Restores the cursor position in a textarea after a tag or env-var
* dropdown insertion. Computes where the inserted token ends in the
* new value and places the cursor right after it.
*
Comment thread
waleedlatif1 marked this conversation as resolved.
* @param textarea - The textarea element to restore cursor in
* @param liveValue - The textarea value before the insertion
* @param liveCursor - The cursor position before the insertion
* @param newValue - The full new value after the insertion
* @param type - The type of insertion ('tag' for `<>`, 'envVar' for `{{}}`)
*/
export function restoreCursorAfterInsertion(
textarea: HTMLTextAreaElement | null,
liveValue: string,
liveCursor: number,
newValue: string,
type: InsertionType
): void {
const [openDelim, closeDelim, closeLen] =
type === 'tag' ? (['<', '>', 1] as const) : (['{{', '}}', 2] as const)

const insertPos = liveValue.slice(0, liveCursor).lastIndexOf(openDelim)
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated
const searchFrom = insertPos !== -1 ? insertPos : liveCursor
const closingPos = newValue.indexOf(closeDelim, searchFrom)
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated
const newCursorPos = closingPos !== -1 ? closingPos + closeLen : newValue.length
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated

setTimeout(() => {
if (textarea) {
textarea.focus()
textarea.selectionStart = newCursorPos
textarea.selectionEnd = newCursorPos
}
}, 0)
}