Skip to content

Commit 8774e90

Browse files
BillLeoutsakosvl346Bill Leoutsakos
andauthored
fix(emcn): preserve editable combobox overlay text visibility (#8086)
* fix(emcn): preserve editable combobox overlay text visibility * Share mirrored workflow field editing treatment (#8248) * refactor(workflow): share mirrored input and textarea editing * test(workflow): use absolute mirrored field import * refactor(workflow): declare mirrored props and class states * style(workflow): express mirrored overlay constants as classes --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local> --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
1 parent 99ca82b commit 8774e90

15 files changed

Lines changed: 613 additions & 502 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx‎

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
getCodeEditorProps,
99
highlight,
1010
languages,
11-
Textarea,
1211
Tooltip,
1312
} from '@sim/emcn'
1413
import { ChevronDown, ChevronsUpDown, ChevronUp, Plus, Trash } from '@sim/emcn/icons'
@@ -31,6 +30,7 @@ import {
3130
getValidWorkflowSearchRange,
3231
type WorkflowSearchTextHighlight,
3332
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
33+
import { MirroredTextarea } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/mirrored-field/mirrored-field'
3434
import {
3535
checkTagTrigger,
3636
TagDropdown,
@@ -1044,7 +1044,7 @@ export function ConditionInput({
10441044
onDragOver={(e) => e.preventDefault()}
10451045
onDrop={(e) => handleDrop(block.id, e)}
10461046
>
1047-
<Textarea
1047+
<MirroredTextarea
10481048
ref={(el) => {
10491049
if (el) inputRefs.current.set(block.id, el)
10501050
}}
@@ -1093,43 +1093,26 @@ export function ConditionInput({
10931093
)
10941094
}, 150)
10951095
}}
1096-
onScroll={(e) => {
1097-
const overlay = overlayRefs.current.get(block.id)
1098-
if (overlay) {
1099-
overlay.scrollTop = e.currentTarget.scrollTop
1100-
overlay.scrollLeft = e.currentTarget.scrollLeft
1101-
}
1102-
}}
11031096
placeholder='Describe when this route should be taken...'
11041097
disabled={disabled || isPreview}
1105-
className='min-h-[100px] resize-none rounded-none border-0 px-3 py-2 text-sm text-transparent caret-foreground [letter-spacing:inherit] placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
1098+
className='min-h-[100px] resize-none rounded-none border-0 px-3 py-2 text-sm placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
11061099
rows={4}
11071100
style={{ height: `${getRouterHeight(block.id)}px` }}
1108-
/>
1109-
<div
1110-
ref={(el) => {
1111-
if (el) {
1112-
overlayRefs.current.set(block.id, el)
1113-
} else {
1114-
overlayRefs.current.delete(block.id)
1115-
}
1101+
overlayRef={(el) => {
1102+
if (el) overlayRefs.current.set(block.id, el)
1103+
else overlayRefs.current.delete(block.id)
11161104
}}
1117-
className={cn(
1118-
'pointer-events-none absolute inset-0 box-border overflow-auto whitespace-pre-wrap break-words border border-transparent bg-transparent px-3 py-2 font-sans text-sm',
1105+
overlayClassName={cn(
1106+
'pointer-events-none absolute inset-0 box-border overflow-auto whitespace-pre-wrap break-words border border-transparent bg-transparent px-3 py-2 text-sm [font-family:inherit] [line-height:inherit]',
11191107
(isPreview || disabled) && 'opacity-50'
11201108
)}
1121-
style={{
1122-
fontFamily: 'inherit',
1123-
lineHeight: 'inherit',
1124-
height: `${getRouterHeight(block.id)}px`,
1125-
}}
1126-
>
1127-
{formatDisplayText(block.value, {
1109+
overlayStyle={{ height: `${getRouterHeight(block.id)}px` }}
1110+
overlay={formatDisplayText(block.value, {
11281111
accessiblePrefixes,
11291112
highlightAll: !accessiblePrefixes,
11301113
workflowSearchHighlight,
11311114
})}
1132-
</div>
1115+
/>
11331116

11341117
{/* Custom resize handle */}
11351118
{!isPreview && !disabled && (

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-tag-entry/document-tag-entry.tsx‎

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
Combobox,
99
type ComboboxOption,
1010
cn,
11-
Input,
1211
Label,
1312
OverflowText,
1413
Trash,
@@ -17,6 +16,7 @@ import { Plus } from '@sim/emcn/icons'
1716
import { generateId } from '@sim/utils/id'
1817
import { FIELD_TYPE_LABELS, getPlaceholderForFieldType } from '@/lib/knowledge/constants'
1918
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
19+
import { MirroredInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/mirrored-field/mirrored-field'
2020
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
2121
import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
2222
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
@@ -203,11 +203,6 @@ export function DocumentTagEntry({
203203
/**
204204
* Syncs scroll position between input and overlay
205205
*/
206-
const syncOverlayScroll = (tagId: string, scrollLeft: number) => {
207-
const overlay = overlayRefs.current[tagId]
208-
if (overlay) overlay.scrollLeft = scrollLeft
209-
}
210-
211206
if (isPreview) {
212207
const tagCount = tags.filter((t) => t.tagName?.trim()).length
213208
return (
@@ -301,7 +296,7 @@ export function DocumentTagEntry({
301296

302297
return (
303298
<div className='relative'>
304-
<Input
299+
<MirroredInput
305300
ref={(el) => {
306301
if (el) valueInputRefs.current[cellKey] = el
307302
}}
@@ -311,36 +306,28 @@ export function DocumentTagEntry({
311306
onDrop={handlers.onDrop}
312307
onDragOver={handlers.onDragOver}
313308
onFocus={handlers.onFocus}
314-
onScroll={(e) => syncOverlayScroll(cellKey, e.currentTarget.scrollLeft)}
315-
onPaste={() =>
316-
setTimeout(() => {
317-
const input = valueInputRefs.current[cellKey]
318-
input && syncOverlayScroll(cellKey, input.scrollLeft)
319-
}, 0)
320-
}
321309
disabled={isReadOnly}
322310
autoComplete='off'
323311
placeholder={placeholder}
324-
className='allow-scroll w-full overflow-auto text-transparent caret-foreground [letter-spacing:inherit]'
325-
/>
326-
<div
327-
ref={(el) => {
312+
className='allow-scroll w-full overflow-auto'
313+
overlayRef={(el) => {
328314
if (el) overlayRefs.current[cellKey] = el
329315
}}
330-
className={cn(
316+
overlayClassName={cn(
331317
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm',
332318
!isReadOnly && 'pointer-events-none'
333319
)}
334-
>
335-
<div className='w-full whitespace-pre' style={{ minWidth: 'fit-content' }}>
336-
{formatDisplayText(
337-
fieldValue,
338-
accessiblePrefixes
339-
? { accessiblePrefixes, workflowSearchHighlight }
340-
: { highlightAll: true, workflowSearchHighlight }
341-
)}
342-
</div>
343-
</div>
320+
overlay={
321+
<div className='w-full whitespace-pre' style={{ minWidth: 'fit-content' }}>
322+
{formatDisplayText(
323+
fieldValue,
324+
accessiblePrefixes
325+
? { accessiblePrefixes, workflowSearchHighlight }
326+
: { highlightAll: true, workflowSearchHighlight }
327+
)}
328+
</div>
329+
}
330+
/>
344331
{fieldState.showTags && (
345332
<TagDropdown
346333
visible={fieldState.showTags}

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/eval-input/eval-input.tsx‎

Lines changed: 41 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { useMemo, useRef } from 'react'
2-
import { Button, cn, Input, Label, Textarea, Tooltip } from '@sim/emcn'
2+
import { Button, cn, Label, Tooltip } from '@sim/emcn'
33
import { Plus, Trash } from '@sim/emcn/icons'
44
import { generateId } from '@sim/utils/id'
55
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
6+
import {
7+
MirroredInput,
8+
MirroredTextarea,
9+
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/mirrored-field/mirrored-field'
610
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
711
import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
812
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
@@ -47,7 +51,6 @@ export function EvalInput({
4751
const [storeValue, setStoreValue] = useSubBlockValue<EvalMetric[]>(blockId, subBlockId)
4852
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
4953
const descriptionInputRefs = useRef<Record<string, HTMLTextAreaElement>>({})
50-
const descriptionOverlayRefs = useRef<Record<string, HTMLDivElement>>({})
5154

5255
// Use the extended hook for field-level management
5356
const inputController = useSubBlockInput({
@@ -188,28 +191,27 @@ export function EvalInput({
188191
<div key={`name-${metric.id}`} className='flex flex-col gap-1.5'>
189192
{renderFieldLabel('Name')}
190193
<div className='relative'>
191-
<Input
194+
<MirroredInput
192195
name='name'
193196
value={metric.name}
194197
onChange={(e) => updateMetric(metric.id, 'name', e.target.value)}
195198
placeholder='Accuracy'
196199
disabled={isPreview || disabled}
197-
className='text-transparent caret-foreground [letter-spacing:inherit] placeholder:text-muted-foreground/50'
198-
/>
199-
<div
200-
className={cn(
200+
className='placeholder:text-muted-foreground/50'
201+
overlayClassName={cn(
201202
'pointer-events-none absolute inset-0 flex items-center overflow-hidden px-3 text-sm',
202203
(isPreview || disabled) && 'opacity-50'
203204
)}
204-
>
205-
<span className='truncate'>
206-
{formatDisplayText(metric.name || '', {
207-
accessiblePrefixes,
208-
highlightAll: !accessiblePrefixes,
209-
workflowSearchHighlight: getMetricSearchHighlight(index, ['name']),
210-
})}
211-
</span>
212-
</div>
205+
overlay={
206+
<span className='truncate'>
207+
{formatDisplayText(metric.name || '', {
208+
accessiblePrefixes,
209+
highlightAll: !accessiblePrefixes,
210+
workflowSearchHighlight: getMetricSearchHighlight(index, ['name']),
211+
})}
212+
</span>
213+
}
214+
/>
213215
</div>
214216
</div>
215217

@@ -231,7 +233,7 @@ export function EvalInput({
231233

232234
return (
233235
<>
234-
<Textarea
236+
<MirroredTextarea
235237
ref={(el) => {
236238
if (el) descriptionInputRefs.current[metric.id] = el
237239
}}
@@ -243,30 +245,24 @@ export function EvalInput({
243245
onFocus={handlers.onFocus}
244246
placeholder='How accurate is the response?'
245247
disabled={isPreview || disabled}
246-
className={cn(
247-
'min-h-[80px] whitespace-pre-wrap text-transparent caret-foreground [letter-spacing:inherit]'
248-
)}
248+
className='min-h-[80px] whitespace-pre-wrap'
249249
rows={3}
250-
/>
251-
<div
252-
ref={(el) => {
253-
if (el) descriptionOverlayRefs.current[metric.id] = el
254-
}}
255-
className={cn(
250+
overlayClassName={cn(
256251
'absolute inset-0 overflow-auto bg-transparent px-2 py-2 font-sans text-[var(--code-foreground)] text-sm',
257252
!(isPreview || disabled) && 'pointer-events-none'
258253
)}
259-
>
260-
<div className='whitespace-pre-wrap'>
261-
{formatDisplayText(metric.description || '', {
262-
accessiblePrefixes,
263-
highlightAll: !accessiblePrefixes,
264-
workflowSearchHighlight: getMetricSearchHighlight(index, [
265-
'description',
266-
]),
267-
})}
268-
</div>
269-
</div>
254+
overlay={
255+
<div className='whitespace-pre-wrap'>
256+
{formatDisplayText(metric.description || '', {
257+
accessiblePrefixes,
258+
highlightAll: !accessiblePrefixes,
259+
workflowSearchHighlight: getMetricSearchHighlight(index, [
260+
'description',
261+
]),
262+
})}
263+
</div>
264+
}
265+
/>
270266
{fieldState.showTags && (
271267
<TagDropdown
272268
visible={fieldState.showTags}
@@ -291,7 +287,7 @@ export function EvalInput({
291287
<div className='flex flex-col gap-1.5'>
292288
{renderFieldLabel('Min Value')}
293289
<div className='relative'>
294-
<Input
290+
<MirroredInput
295291
type='text'
296292
value={metric.range.min ?? ''}
297293
onChange={(e) => updateRange(metric.id, 'min', e.target.value)}
@@ -300,19 +296,17 @@ export function EvalInput({
300296
autoComplete='off'
301297
data-form-type='other'
302298
name='eval-range-min'
303-
className='text-transparent caret-foreground [letter-spacing:inherit]'
304-
/>
305-
<div className='pointer-events-none absolute inset-0 flex items-center truncate px-2 py-1.5 font-sans text-sm'>
306-
{formatDisplayText(String(metric.range.min ?? ''), {
299+
overlayClassName='pointer-events-none absolute inset-0 flex items-center truncate px-2 py-1.5 font-sans text-sm'
300+
overlay={formatDisplayText(String(metric.range.min ?? ''), {
307301
workflowSearchHighlight: getMetricSearchHighlight(index, ['range', 'min']),
308302
})}
309-
</div>
303+
/>
310304
</div>
311305
</div>
312306
<div className='flex flex-col gap-1.5'>
313307
{renderFieldLabel('Max Value')}
314308
<div className='relative'>
315-
<Input
309+
<MirroredInput
316310
type='text'
317311
value={metric.range.max ?? ''}
318312
onChange={(e) => updateRange(metric.id, 'max', e.target.value)}
@@ -321,13 +315,11 @@ export function EvalInput({
321315
autoComplete='off'
322316
data-form-type='other'
323317
name='eval-range-max'
324-
className='text-transparent caret-foreground [letter-spacing:inherit]'
325-
/>
326-
<div className='pointer-events-none absolute inset-0 flex items-center truncate px-2 py-1.5 font-sans text-sm'>
327-
{formatDisplayText(String(metric.range.max ?? ''), {
318+
overlayClassName='pointer-events-none absolute inset-0 flex items-center truncate px-2 py-1.5 font-sans text-sm'
319+
overlay={formatDisplayText(String(metric.range.max ?? ''), {
328320
workflowSearchHighlight: getMetricSearchHighlight(index, ['range', 'max']),
329321
})}
330-
</div>
322+
/>
331323
</div>
332324
</div>
333325
</div>

0 commit comments

Comments
 (0)