fix(ux): zoom in/out on inputs

This commit is contained in:
Emir Karabeg
2025-03-04 21:30:52 -08:00
parent 93b292f58a
commit 4f3e771e0e
2 changed files with 109 additions and 1 deletions
@@ -1,4 +1,5 @@
import { useRef, useState } from 'react'
import { useReactFlow } from 'reactflow'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { formatDisplayText } from '@/components/ui/formatted-text'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
@@ -32,6 +33,9 @@ export function LongInput({
const overlayRef = useRef<HTMLDivElement>(null)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
// Get ReactFlow instance for zoom control
const reactFlowInstance = useReactFlow()
// Handle input changes
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newValue = e.target.value
@@ -113,6 +117,55 @@ export function LongInput({
}
}
// Handle wheel events to control ReactFlow zoom
const handleWheel = (e: React.WheelEvent<HTMLTextAreaElement>) => {
// Only handle zoom when Ctrl/Cmd key is pressed
if (e.ctrlKey || e.metaKey) {
e.preventDefault()
e.stopPropagation()
// Get current zoom level and viewport
const currentZoom = reactFlowInstance.getZoom()
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
const newZoom = Math.min(Math.max(currentZoom * zoomFactor, 0.1), 1)
// Get the position of the cursor in the page
const { x: pointerX, y: pointerY } = reactFlowInstance.screenToFlowPosition({
x: e.clientX,
y: e.clientY,
})
// Calculate the new viewport position to keep the cursor position fixed
const newViewportX = viewportX + (pointerX * currentZoom - pointerX * newZoom)
const newViewportY = viewportY + (pointerY * currentZoom - pointerY * newZoom)
// Set the new viewport with the calculated position and zoom
reactFlowInstance.setViewport(
{
x: newViewportX,
y: newViewportY,
zoom: newZoom,
},
{ duration: 0 }
)
return false
}
// For regular scrolling (without Ctrl/Cmd), let the default behavior happen
if (overlayRef.current) {
overlayRef.current.scrollTop = e.currentTarget.scrollTop
}
}
return (
<div className="relative w-full">
<Textarea
@@ -130,6 +183,7 @@ export function LongInput({
onDrop={handleDrop}
onDragOver={handleDragOver}
onScroll={handleScroll}
onWheel={handleWheel}
onKeyDown={handleKeyDown}
onFocus={() => {
setShowEnvVars(false)
@@ -1,4 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import { useReactFlow } from 'reactflow'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { formatDisplayText } from '@/components/ui/formatted-text'
import { Input } from '@/components/ui/input'
@@ -38,6 +39,9 @@ export function ShortInput({
const overlayRef = useRef<HTMLDivElement>(null)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
// Get ReactFlow instance for zoom control
const reactFlowInstance = useReactFlow()
// Use either controlled or uncontrolled value
const value = propValue !== undefined ? propValue : storeValue
@@ -84,6 +88,55 @@ export function ShortInput({
}
}, [value, isFocused])
// Handle wheel events to control ReactFlow zoom
const handleWheel = (e: React.WheelEvent<HTMLInputElement>) => {
// Only handle zoom when Ctrl/Cmd key is pressed
if (e.ctrlKey || e.metaKey) {
e.preventDefault()
e.stopPropagation()
// Get current zoom level and viewport
const currentZoom = reactFlowInstance.getZoom()
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
const newZoom = Math.min(Math.max(currentZoom * zoomFactor, 0.1), 1)
// Get the position of the cursor in the page
const { x: pointerX, y: pointerY } = reactFlowInstance.screenToFlowPosition({
x: e.clientX,
y: e.clientY,
})
// Calculate the new viewport position to keep the cursor position fixed
const newViewportX = viewportX + (pointerX * currentZoom - pointerX * newZoom)
const newViewportY = viewportY + (pointerY * currentZoom - pointerY * newZoom)
// Set the new viewport with the calculated position and zoom
reactFlowInstance.setViewport(
{
x: newViewportX,
y: newViewportY,
zoom: newZoom,
},
{ duration: 0 }
)
return false
}
// For regular scrolling (without Ctrl/Cmd), let the default behavior happen
if (overlayRef.current) {
overlayRef.current.scrollLeft = e.currentTarget.scrollLeft
}
}
// Drag and Drop handlers
const handleDragOver = (e: React.DragEvent<HTMLInputElement>) => {
if (config?.connectionDroppable === false) return
@@ -164,7 +217,7 @@ export function ShortInput({
'focus-visible:ring-blue-500 ring-2 ring-blue-500 ring-offset-2'
)}
placeholder={placeholder ?? ''}
type="text"
type={password ? 'password' : 'text'}
value={displayValue}
onChange={handleChange}
onFocus={() => {
@@ -177,6 +230,7 @@ export function ShortInput({
onDrop={handleDrop}
onDragOver={handleDragOver}
onScroll={handleScroll}
onWheel={handleWheel}
onKeyDown={handleKeyDown}
autoComplete="off"
/>