Skip to content

Commit 95728e3

Browse files
committed
popover fixes, color picker keyboard nav, code simplification
1 parent 25fb12d commit 95728e3

File tree

4 files changed

+48
-168
lines changed
  • apps/sim

4 files changed

+48
-168
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal.tsx

Lines changed: 27 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -331,31 +331,37 @@ try {
331331
onOpenChange(false)
332332
}
333333

334-
const validateJsonSchema = (schema: string): boolean => {
335-
if (!schema) return false
334+
const validateSchema = (schema: string): { isValid: boolean; error: string | null } => {
335+
if (!schema) return { isValid: false, error: null }
336336

337337
try {
338338
const parsed = JSON.parse(schema)
339339

340340
if (!parsed.type || parsed.type !== 'function') {
341-
return false
341+
return { isValid: false, error: 'Missing "type": "function"' }
342342
}
343-
344343
if (!parsed.function || !parsed.function.name) {
345-
return false
344+
return { isValid: false, error: 'Missing function.name field' }
346345
}
347-
348346
if (!parsed.function.parameters) {
349-
return false
347+
return { isValid: false, error: 'Missing function.parameters object' }
350348
}
351-
352-
if (!parsed.function.parameters.type || parsed.function.parameters.properties === undefined) {
353-
return false
349+
if (!parsed.function.parameters.type) {
350+
return { isValid: false, error: 'Missing parameters.type field' }
351+
}
352+
if (parsed.function.parameters.properties === undefined) {
353+
return { isValid: false, error: 'Missing parameters.properties field' }
354+
}
355+
if (
356+
typeof parsed.function.parameters.properties !== 'object' ||
357+
parsed.function.parameters.properties === null
358+
) {
359+
return { isValid: false, error: 'parameters.properties must be an object' }
354360
}
355361

356-
return true
357-
} catch (_error) {
358-
return false
362+
return { isValid: true, error: null }
363+
} catch {
364+
return { isValid: false, error: 'Invalid JSON format' }
359365
}
360366
}
361367

@@ -377,7 +383,7 @@ try {
377383
}
378384
}, [jsonSchema])
379385

380-
const isSchemaValid = useMemo(() => validateJsonSchema(jsonSchema), [jsonSchema])
386+
const isSchemaValid = useMemo(() => validateSchema(jsonSchema).isValid, [jsonSchema])
381387

382388
const hasChanges = useMemo(() => {
383389
if (!isEditing) return true
@@ -392,43 +398,9 @@ try {
392398
return
393399
}
394400

395-
const parsed = JSON.parse(jsonSchema)
396-
397-
if (!parsed.type || parsed.type !== 'function') {
398-
setSchemaError('Schema must have a "type" field set to "function"')
399-
setActiveSection('schema')
400-
return
401-
}
402-
403-
if (!parsed.function || !parsed.function.name) {
404-
setSchemaError('Schema must have a "function" object with a "name" field')
405-
setActiveSection('schema')
406-
return
407-
}
408-
409-
if (!parsed.function.parameters) {
410-
setSchemaError('Missing function.parameters object')
411-
setActiveSection('schema')
412-
return
413-
}
414-
415-
if (!parsed.function.parameters.type) {
416-
setSchemaError('Missing parameters.type field')
417-
setActiveSection('schema')
418-
return
419-
}
420-
421-
if (parsed.function.parameters.properties === undefined) {
422-
setSchemaError('Missing parameters.properties field')
423-
setActiveSection('schema')
424-
return
425-
}
426-
427-
if (
428-
typeof parsed.function.parameters.properties !== 'object' ||
429-
parsed.function.parameters.properties === null
430-
) {
431-
setSchemaError('parameters.properties must be an object')
401+
const { isValid, error } = validateSchema(jsonSchema)
402+
if (!isValid) {
403+
setSchemaError(error)
432404
setActiveSection('schema')
433405
return
434406
}
@@ -512,46 +484,8 @@ try {
512484
setJsonSchema(value)
513485

514486
if (value.trim()) {
515-
try {
516-
const parsed = JSON.parse(value)
517-
518-
if (!parsed.type || parsed.type !== 'function') {
519-
setSchemaError('Missing "type": "function"')
520-
return
521-
}
522-
523-
if (!parsed.function || !parsed.function.name) {
524-
setSchemaError('Missing function.name field')
525-
return
526-
}
527-
528-
if (!parsed.function.parameters) {
529-
setSchemaError('Missing function.parameters object')
530-
return
531-
}
532-
533-
if (!parsed.function.parameters.type) {
534-
setSchemaError('Missing parameters.type field')
535-
return
536-
}
537-
538-
if (parsed.function.parameters.properties === undefined) {
539-
setSchemaError('Missing parameters.properties field')
540-
return
541-
}
542-
543-
if (
544-
typeof parsed.function.parameters.properties !== 'object' ||
545-
parsed.function.parameters.properties === null
546-
) {
547-
setSchemaError('parameters.properties must be an object')
548-
return
549-
}
550-
551-
setSchemaError(null)
552-
} catch {
553-
setSchemaError('Invalid JSON format')
554-
}
487+
const { error } = validateSchema(value)
488+
setSchemaError(error)
555489
} else {
556490
setSchemaError(null)
557491
}
@@ -667,40 +601,6 @@ try {
667601
}
668602
}
669603

670-
useEffect(() => {
671-
if (!showSchemaParams || schemaParameters.length === 0) return
672-
673-
const handleKeyboardEvent = (e: KeyboardEvent) => {
674-
switch (e.key) {
675-
case 'ArrowDown':
676-
e.preventDefault()
677-
e.stopPropagation()
678-
setSchemaParamSelectedIndex((prev) => Math.min(prev + 1, schemaParameters.length - 1))
679-
break
680-
case 'ArrowUp':
681-
e.preventDefault()
682-
e.stopPropagation()
683-
setSchemaParamSelectedIndex((prev) => Math.max(prev - 1, 0))
684-
break
685-
case 'Enter':
686-
e.preventDefault()
687-
e.stopPropagation()
688-
if (schemaParamSelectedIndex >= 0 && schemaParamSelectedIndex < schemaParameters.length) {
689-
handleSchemaParamSelect(schemaParameters[schemaParamSelectedIndex].name)
690-
}
691-
break
692-
case 'Escape':
693-
e.preventDefault()
694-
e.stopPropagation()
695-
setShowSchemaParams(false)
696-
break
697-
}
698-
}
699-
700-
window.addEventListener('keydown', handleKeyboardEvent, true)
701-
return () => window.removeEventListener('keydown', handleKeyboardEvent, true)
702-
}, [showSchemaParams, schemaParamSelectedIndex, schemaParameters])
703-
704604
const handleKeyDown = (e: React.KeyboardEvent) => {
705605
const isSchemaPromptVisible = activeSection === 'schema' && schemaGeneration.isPromptVisible
706606
const isCodePromptVisible = activeSection === 'code' && codeGeneration.isPromptVisible
@@ -765,7 +665,7 @@ try {
765665
case ' ':
766666
case 'Tab':
767667
setShowSchemaParams(false)
768-
break
668+
return
769669
}
770670
}
771671

@@ -882,18 +782,7 @@ try {
882782
return (
883783
<>
884784
<Modal open={open} onOpenChange={handleClose}>
885-
<ModalContent
886-
size='xl'
887-
onKeyDown={(e) => {
888-
if (e.key === 'Escape' && (showEnvVars || showTags || showSchemaParams)) {
889-
e.preventDefault()
890-
e.stopPropagation()
891-
setShowEnvVars(false)
892-
setShowTags(false)
893-
setShowSchemaParams(false)
894-
}
895-
}}
896-
>
785+
<ModalContent size='xl'>
897786
<ModalHeader>{isEditing ? 'Edit Agent Tool' : 'Create Agent Tool'}</ModalHeader>
898787

899788
<ModalTabs
@@ -1165,30 +1054,6 @@ try {
11651054
collisionPadding={6}
11661055
onOpenAutoFocus={(e) => e.preventDefault()}
11671056
onCloseAutoFocus={(e) => e.preventDefault()}
1168-
onKeyDown={(e) => {
1169-
if (e.key === 'ArrowDown') {
1170-
e.preventDefault()
1171-
setSchemaParamSelectedIndex((prev) =>
1172-
Math.min(prev + 1, schemaParameters.length - 1)
1173-
)
1174-
} else if (e.key === 'ArrowUp') {
1175-
e.preventDefault()
1176-
setSchemaParamSelectedIndex((prev) => Math.max(prev - 1, 0))
1177-
} else if (e.key === 'Enter') {
1178-
e.preventDefault()
1179-
if (
1180-
schemaParamSelectedIndex >= 0 &&
1181-
schemaParamSelectedIndex < schemaParameters.length
1182-
) {
1183-
handleSchemaParamSelect(
1184-
schemaParameters[schemaParamSelectedIndex].name
1185-
)
1186-
}
1187-
} else if (e.key === 'Escape') {
1188-
e.preventDefault()
1189-
setShowSchemaParams(false)
1190-
}
1191-
}}
11921057
>
11931058
<PopoverScrollArea>
11941059
<PopoverSection>Available Parameters</PopoverSection>

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ const GRID_COLUMNS = 6
2525
function ColorGrid({
2626
hexInput,
2727
setHexInput,
28+
onColorChange,
2829
}: {
2930
hexInput: string
3031
setHexInput: (color: string) => void
32+
onColorChange?: (color: string) => void
3133
}) {
3234
const { isInFolder } = usePopoverContext()
3335
const [focusedIndex, setFocusedIndex] = useState(-1)
@@ -72,7 +74,8 @@ function ColorGrid({
7274
case 'Enter':
7375
case ' ':
7476
e.preventDefault()
75-
setHexInput(WORKFLOW_COLORS[index].color)
77+
e.stopPropagation()
78+
onColorChange?.(WORKFLOW_COLORS[index].color)
7679
return
7780
default:
7881
return
@@ -83,7 +86,7 @@ function ColorGrid({
8386
buttonRefs.current[newIndex]?.focus()
8487
}
8588
},
86-
[setHexInput]
89+
[onColorChange]
8790
)
8891

8992
return (
@@ -100,13 +103,13 @@ function ColorGrid({
100103
tabIndex={focusedIndex === index ? 0 : -1}
101104
onClick={(e) => {
102105
e.stopPropagation()
103-
setHexInput(color)
106+
onColorChange?.(color)
104107
}}
105108
onKeyDown={(e) => handleKeyDown(e, index)}
106109
onFocus={() => setFocusedIndex(index)}
107110
className={cn(
108-
'h-[20px] w-[20px] rounded-[4px] focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-1 focus:ring-offset-[#1b1b1b]',
109-
hexInput.toLowerCase() === color.toLowerCase() && 'ring-1 ring-white'
111+
'h-[20px] w-[20px] rounded-[4px] outline-none ring-white ring-offset-0',
112+
focusedIndex === index && 'ring-[1.5px]'
110113
)}
111114
style={{ backgroundColor: color }}
112115
/>
@@ -450,7 +453,11 @@ export function ContextMenu({
450453
>
451454
<div className='flex w-[140px] flex-col gap-[8px] p-[2px]'>
452455
{/* Preset colors with keyboard navigation */}
453-
<ColorGrid hexInput={hexInput} setHexInput={setHexInput} />
456+
<ColorGrid
457+
hexInput={hexInput}
458+
setHexInput={setHexInput}
459+
onColorChange={onColorChange}
460+
/>
454461

455462
{/* Hex input */}
456463
<div className='flex items-center gap-[4px]'>

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ export function WorkspaceHeader({
459459
value={editingName}
460460
onChange={(e) => setEditingName(e.target.value)}
461461
onKeyDown={async (e) => {
462+
e.stopPropagation()
462463
if (e.key === 'Enter') {
463464
e.preventDefault()
464465
setIsListRenaming(true)

apps/sim/components/emcn/components/popover/popover.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,13 @@ const PopoverContent = React.forwardRef<
460460
const content = contentRef.current
461461
if (!content) return
462462

463+
const activeElement = document.activeElement
464+
const isInputFocused =
465+
activeElement instanceof HTMLInputElement ||
466+
activeElement instanceof HTMLTextAreaElement ||
467+
activeElement?.getAttribute('contenteditable') === 'true'
468+
if (isInputFocused) return
469+
463470
const items = content.querySelectorAll<HTMLElement>(
464471
'[role="menuitem"]:not([aria-disabled="true"])'
465472
)

0 commit comments

Comments
 (0)