From fd53247f2559ec96161e0f3d632794c6e3123522 Mon Sep 17 00:00:00 2001 From: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com> Date: Sun, 4 May 2025 23:27:41 -0700 Subject: [PATCH] fix(long-input): text overlay and resize UI (#329) * improvement(long-input): added dynamic rows from config * fix(long-input): resize and overlay text misalignment --- .../sub-block/components/long-input.tsx | 104 ++++++++++++++++-- .../components/sub-block/sub-block.tsx | 1 + sim/blocks/blocks/agent.ts | 2 + sim/blocks/types.ts | 2 + 4 files changed, 101 insertions(+), 8 deletions(-) diff --git a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx index cb88855a84..204620a3f9 100644 --- a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx +++ b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx @@ -1,4 +1,5 @@ -import { useRef, useState } from 'react' +import { useEffect, useLayoutEffect, useRef, useState } from 'react' +import { ChevronsUpDown } from 'lucide-react' import { useReactFlow } from 'reactflow' import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown' import { formatDisplayText } from '@/components/ui/formatted-text' @@ -17,14 +18,21 @@ interface LongInputProps { subBlockId: string isConnecting: boolean config: SubBlockConfig + rows?: number } +// Constants +const DEFAULT_ROWS = 4 +const ROW_HEIGHT_PX = 24 +const MIN_HEIGHT_PX = 80 + export function LongInput({ placeholder, blockId, subBlockId, isConnecting, config, + rows, }: LongInputProps) { const [value, setValue] = useSubBlockValue(blockId, subBlockId) const [showEnvVars, setShowEnvVars] = useState(false) @@ -34,10 +42,32 @@ export function LongInput({ const textareaRef = useRef(null) const overlayRef = useRef(null) const [activeSourceBlockId, setActiveSourceBlockId] = useState(null) + const containerRef = useRef(null) + + // Calculate initial height based on rows prop with reasonable defaults + const getInitialHeight = () => { + // Use provided rows or default, then convert to pixels with a minimum + const rowCount = rows || DEFAULT_ROWS + return Math.max(rowCount * ROW_HEIGHT_PX, MIN_HEIGHT_PX) + } + + const [height, setHeight] = useState(getInitialHeight()) + const isResizing = useRef(false) // Get ReactFlow instance for zoom control const reactFlowInstance = useReactFlow() + // Set initial height on first render + useLayoutEffect(() => { + const initialHeight = getInitialHeight() + setHeight(initialHeight) + + if (textareaRef.current && overlayRef.current) { + textareaRef.current.style.height = `${initialHeight}px` + overlayRef.current.style.height = `${initialHeight}px` + } + }, [rows]) + // Handle input changes const handleChange = (e: React.ChangeEvent) => { const newValue = e.target.value @@ -63,6 +93,47 @@ export function LongInput({ } } + // Ensure overlay updates when content changes + useEffect(() => { + if (textareaRef.current && overlayRef.current) { + // Ensure scrolling is synchronized + overlayRef.current.scrollTop = textareaRef.current.scrollTop + overlayRef.current.scrollLeft = textareaRef.current.scrollLeft + } + }, [value]) + + // Handle resize functionality + const startResize = (e: React.MouseEvent) => { + e.preventDefault() + isResizing.current = true + + const startY = e.clientY + const startHeight = height + + const handleMouseMove = (moveEvent: MouseEvent) => { + if (!isResizing.current) return + + const deltaY = moveEvent.clientY - startY + const newHeight = Math.max(MIN_HEIGHT_PX, startHeight + deltaY) + + setHeight(newHeight) + + if (textareaRef.current && overlayRef.current) { + textareaRef.current.style.height = `${newHeight}px` + overlayRef.current.style.height = `${newHeight}px` + } + } + + const handleMouseUp = () => { + isResizing.current = false + document.removeEventListener('mousemove', handleMouseMove) + document.removeEventListener('mouseup', handleMouseUp) + } + + document.addEventListener('mousemove', handleMouseMove) + document.addEventListener('mouseup', handleMouseUp) + } + // Drag and Drop handlers const handleDragOver = (e: React.DragEvent) => { if (config?.connectionDroppable === false) return @@ -131,9 +202,7 @@ export function LongInput({ const { x: viewportX, y: viewportY } = reactFlowInstance.getViewport() // Calculate zoom factor based on wheel delta - // Use a smaller factor for smoother zooming that matches ReactFlow's native behavior const delta = e.deltaY > 0 ? 1 : -1 - // Using 0.98 instead of 0.95 makes the zoom much slower and more gradual const zoomFactor = Math.pow(0.96, delta) // Calculate new zoom level with min/max constraints @@ -169,16 +238,16 @@ export function LongInput({ } return ( -
+