feat: add search input field (#18409)

This commit is contained in:
allenZhang 2025-04-22 11:07:18 +08:00 committed by GitHub
parent 67eefd0ba1
commit 94e22ba0fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 50 deletions

View File

@ -31,6 +31,7 @@ import { useOptions } from './hooks'
import type { PickerBlockMenuOption } from './menu'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { KEY_ESCAPE_COMMAND } from 'lexical'
type ComponentPickerProps = {
triggerString: string
@ -118,6 +119,13 @@ const ComponentPicker = ({
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
}, [editor, checkForTriggerMatch, triggerString])
const handleClose = useCallback(() => {
ReactDOM.flushSync(() => {
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' })
editor.dispatchCommand(KEY_ESCAPE_COMMAND, escapeEvent)
})
}, [editor])
const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>((
anchorElementRef,
{ options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },
@ -141,51 +149,54 @@ const ComponentPicker = ({
visibility: isPositioned ? 'visible' : 'hidden',
}}
ref={refs.setFloating}
data-testid="component-picker-container"
>
{
options.map((option, index) => (
<Fragment key={option.key}>
{
// Divider
index !== 0 && options.at(index - 1)?.group !== option.group && (
<div className='my-1 h-px w-full -translate-x-1 bg-divider-subtle'></div>
)
}
{option.renderMenuOption({
queryString,
isSelected: selectedIndex === index,
onSelect: () => {
selectOptionAndCleanUp(option)
},
onSetHighlight: () => {
setHighlightedIndex(index)
},
})}
</Fragment>
))
}
{
workflowVariableBlock?.show && (
<>
{
(!!options.length) && (
<div className='my-1 h-px w-full -translate-x-1 bg-divider-subtle'></div>
)
}
<div className='p-1'>
<VarReferenceVars
hideSearch
vars={workflowVariableOptions}
onChange={(variables: string[]) => {
handleSelectWorkflowVariable(variables)
}}
maxHeightClass='max-h-[34vh]'
isSupportFileVar={isSupportFileVar}
/>
</div>
</>
<div className='p-1'>
<VarReferenceVars
searchBoxClassName='mt-1'
vars={workflowVariableOptions}
onChange={(variables: string[]) => {
handleSelectWorkflowVariable(variables)
}}
maxHeightClass='max-h-[34vh]'
isSupportFileVar={isSupportFileVar}
onClose={handleClose}
onBlur={handleClose}
/>
</div>
)
}
{
workflowVariableBlock?.show && !!options.length && (
<div className='my-1 h-px w-full -translate-x-1 bg-divider-subtle'></div>
)
}
<div data-testid="options-list">
{
options.map((option, index) => (
<Fragment key={option.key}>
{
// Divider
index !== 0 && options.at(index - 1)?.group !== option.group && (
<div className='my-1 h-px w-full -translate-x-1 bg-divider-subtle'></div>
)
}
{option.renderMenuOption({
queryString,
isSelected: selectedIndex === index,
onSelect: () => {
selectOptionAndCleanUp(option)
},
onSetHighlight: () => {
setHighlightedIndex(index)
},
})}
</Fragment>
))
}
</div>
</div>
</div>,
anchorElementRef.current,
@ -193,7 +204,7 @@ const ComponentPicker = ({
}
</>
)
}, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable])
}, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable, handleClose, isSupportFileVar])
return (
<LexicalTypeaheadMenuPlugin

View File

@ -37,14 +37,16 @@ const OnBlurBlock: FC<OnBlurBlockProps> = ({
),
editor.registerCommand(
BLUR_COMMAND,
() => {
ref.current = setTimeout(() => {
editor.dispatchCommand(KEY_ESCAPE_COMMAND, new KeyboardEvent('keydown', { key: 'Escape' }))
}, 200)
if (onBlur)
onBlur()
(event) => {
// Check if the clicked target element is var-search-input
const target = event?.relatedTarget as HTMLElement
if (!target?.classList?.contains('var-search-input')) {
ref.current = setTimeout(() => {
editor.dispatchCommand(KEY_ESCAPE_COMMAND, new KeyboardEvent('keydown', { key: 'Escape' }))
}, 200)
if (onBlur)
onBlur()
}
return true
},
COMMAND_PRIORITY_EDITOR,

View File

@ -258,6 +258,8 @@ type Props = {
onChange: (value: ValueSelector, item: Var) => void
itemWidth?: number
maxHeightClass?: string
onClose?: () => void
onBlur?: () => void
}
const VarReferenceVars: FC<Props> = ({
hideSearch,
@ -267,10 +269,19 @@ const VarReferenceVars: FC<Props> = ({
onChange,
itemWidth,
maxHeightClass,
onClose,
onBlur,
}) => {
const { t } = useTranslation()
const [searchText, setSearchText] = useState('')
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault()
onClose?.()
}
}
const filteredVars = vars.filter((v) => {
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.') || v.variable.startsWith('conversation.'))
return children.length > 0
@ -301,14 +312,17 @@ const VarReferenceVars: FC<Props> = ({
{
!hideSearch && (
<>
<div className={cn('mx-2 mb-1 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
<div className={cn('var-search-input-wrapper mx-2 mb-1 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
<Input
className='var-search-input'
showLeftIcon
showClearIcon
value={searchText}
placeholder={t('workflow.common.searchVar') || ''}
onChange={e => setSearchText(e.target.value)}
onKeyDown={handleKeyDown}
onClear={() => setSearchText('')}
onBlur={onBlur}
autoFocus
/>
</div>