Input sub block connection UI complete with cursor disappear error and scroll right error

This commit is contained in:
Emir Karabeg
2025-01-27 19:48:27 -08:00
parent 1fda706dbe
commit c71b6aa18f
3 changed files with 160 additions and 33 deletions
@@ -21,6 +21,11 @@ export function ShortInput({
const inputRef = useRef<HTMLInputElement>(null)
const [isFocused, setIsFocused] = useState(false)
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
const [connections, setConnections] = useState<any[]>([])
useEffect(() => {
console.log(connections)
}, [connections])
useEffect(() => {
if (inputRef.current && isFocused) {
@@ -30,6 +35,22 @@ export function ShortInput({
}
}, [value, isFocused])
// Add regex pattern for connection syntax
const connectionPattern = /<([a-z0-9]+)\.(string|number|boolean|res|any)>/g
const updateConnections = (inputValue: string) => {
const newConnections = Array.from(
inputValue.matchAll(connectionPattern)
).map((match) => match[0].slice(1, -1)) // Remove < and >
setConnections(Array.from(new Set(newConnections))) // Use Set to ensure uniqueness
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value
setValue(newValue)
updateConnections(newValue)
}
const handleDrop = (e: React.DragEvent<HTMLInputElement>) => {
e.preventDefault()
try {
@@ -39,13 +60,17 @@ export function ShortInput({
data.connectionData.sourceBlockId === blockId
) {
const currentValue = value?.toString() ?? ''
const newValue =
currentValue +
data.connectionData.name.replace(' ', '').toLowerCase() +
(data.connectionData.outputType === 'any'
? '.res'
: `.${data.connectionData.outputType}`)
const formattedName = data.connectionData.name
.replace(' ', '')
.toLowerCase()
const connectionType =
data.connectionData.outputType === 'any'
? 'res'
: data.connectionData.outputType
const newConnection = formattedName + '.' + connectionType
const newValue = currentValue + `<${newConnection}>`
setValue(newValue)
updateConnections(newValue)
}
} catch (error) {
console.error('Failed to parse drop data:', error)
@@ -56,6 +81,28 @@ export function ShortInput({
e.preventDefault() // This is needed to allow drops
}
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Backspace' && inputRef.current) {
const cursorPosition: any = inputRef.current.selectionStart
const currentValue = value?.toString() ?? ''
// Check if cursor is right after a connection closing bracket
for (const connection of connections) {
const pattern = `<${connection}>`
const index = currentValue.lastIndexOf(pattern, cursorPosition)
if (index !== -1 && index + pattern.length === cursorPosition) {
e.preventDefault()
const newValue =
currentValue.slice(0, index) +
currentValue.slice(index + pattern.length)
setValue(newValue)
return
}
}
}
}
const displayValue =
password && !isFocused
? '•'.repeat(value?.toString().length ?? 0)
@@ -71,7 +118,9 @@ export function ShortInput({
placeholder={placeholder ?? ''}
type="text"
value={displayValue}
onChange={(e) => setValue(e.target.value)}
connections={connections}
onChange={handleChange}
onKeyDown={handleKeyDown}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
onDrop={handleDrop}
+104 -16
View File
@@ -1,22 +1,110 @@
import * as React from "react"
import * as React from 'react'
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils'
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
interface InputProps
extends Omit<React.ComponentProps<'input'>, 'value' | 'onChange'> {
className?: string
value?: string
connections?: string[]
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
(
{ className, type, value = '', connections = [], onChange, ...props },
ref
) => {
// Create a hidden input for maintaining focus and handling keyboard events
const hiddenInputRef = React.useRef<HTMLInputElement>(null)
const renderContent = () => {
if (!connections?.length) {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className
)}
value={value}
onChange={onChange}
ref={ref}
{...props}
/>
)
}
// Split text by connection patterns
const parts = []
let lastIndex = 0
connections.forEach((connection) => {
const pattern = `<${connection}>`
const index = value.indexOf(pattern, lastIndex)
if (index !== -1) {
// Add text before connection
if (index > lastIndex) {
parts.push({
type: 'text',
content: value.slice(lastIndex, index),
})
}
// Add connection
parts.push({
type: 'connection',
content: connection,
})
lastIndex = index + pattern.length
}
})
// Add remaining text
if (lastIndex < value.length) {
parts.push({
type: 'text',
content: value.slice(lastIndex),
})
}
return (
<div
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className
)}
onClick={() => hiddenInputRef.current?.focus()}
>
<div className="flex flex-wrap gap-1 items-center w-full">
{parts.map((part, index) =>
part.type === 'connection' ? (
<span
key={index}
className="bg-blue-100 text-blue-800 px-1 rounded text-sm"
>
{part.content}
</span>
) : (
<span key={index}>{part.content}</span>
)
)}
<input
ref={hiddenInputRef}
type="text"
className="w-px opacity-0 absolute"
value={value}
onChange={onChange}
{...props}
/>
</div>
</div>
)
}
return renderContent()
}
)
Input.displayName = "Input"
Input.displayName = 'Input'
export { Input }
-10
View File
@@ -14,20 +14,10 @@ export interface BlockState {
subBlocks: Record<string, SubBlockState>
outputType: OutputType
}
export interface Connection {
id: string
blockId: string
outputType: string
startIndex: number
endIndex: number
}
export interface SubBlockState {
id: string
type: SubBlockType
value: string | number | string[][] | null
connections?: Connection[]
}
export interface WorkflowState {