mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 15:08:06 +08:00
Merge branch 'zhsama/panel-var-popup' into feat/pull-a-variable
This commit is contained in:
@ -155,14 +155,13 @@ export type TriggerFn = (
|
||||
text: string,
|
||||
editor: LexicalEditor,
|
||||
) => MenuTextMatch | null
|
||||
export const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'
|
||||
export function useBasicTypeaheadTriggerMatch(
|
||||
trigger: string,
|
||||
{ minLength = 1, maxLength = 75 }: { minLength?: number, maxLength?: number },
|
||||
): TriggerFn {
|
||||
return useCallback(
|
||||
(text: string) => {
|
||||
const validChars = `[${PUNCTUATION}\\s]`
|
||||
const validChars = '[^\\n]'
|
||||
const TypeaheadTriggerRegex = new RegExp(
|
||||
'(.*)('
|
||||
+ `[${trigger}]`
|
||||
|
||||
@ -32,6 +32,8 @@ import { PickerBlockMenuOption } from './menu'
|
||||
import { PromptMenuItem } from './prompt-option'
|
||||
import { VariableMenuItem } from './variable-option'
|
||||
|
||||
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
export const usePromptOptions = (
|
||||
contextBlock?: ContextBlockType,
|
||||
queryBlock?: QueryBlockType,
|
||||
@ -154,7 +156,7 @@ export const useVariableOptions = (
|
||||
if (!queryString)
|
||||
return baseOptions
|
||||
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
const regex = new RegExp(escapeRegExp(queryString), 'i')
|
||||
|
||||
return baseOptions.filter(option => regex.test(option.key))
|
||||
}, [editor, queryString, variableBlock])
|
||||
@ -232,7 +234,7 @@ export const useExternalToolOptions = (
|
||||
if (!queryString)
|
||||
return baseToolOptions
|
||||
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
const regex = new RegExp(escapeRegExp(queryString), 'i')
|
||||
|
||||
return baseToolOptions.filter(option => regex.test(option.key))
|
||||
}, [editor, queryString, externalToolBlockType])
|
||||
|
||||
@ -91,9 +91,10 @@ const ComponentPicker = ({
|
||||
],
|
||||
})
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const useExternalSearch = triggerString === '/' || triggerString === '@'
|
||||
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, {
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
maxLength: useExternalSearch ? 75 : 0,
|
||||
})
|
||||
|
||||
const [queryString, setQueryString] = useState<string | null>(null)
|
||||
@ -116,6 +117,7 @@ const ComponentPicker = ({
|
||||
currentBlock,
|
||||
errorMessageBlock,
|
||||
lastRunBlock,
|
||||
useExternalSearch ? (queryString ?? undefined) : undefined,
|
||||
)
|
||||
|
||||
const onSelectOption = useCallback(
|
||||
@ -247,6 +249,9 @@ const ComponentPicker = ({
|
||||
onBlur={handleClose}
|
||||
maxHeightClass="max-h-[34vh]"
|
||||
autoFocus={false}
|
||||
hideSearch={useExternalSearch}
|
||||
externalSearchText={useExternalSearch ? (queryString ?? '') : undefined}
|
||||
enableKeyboardNavigation={useExternalSearch}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
@ -270,6 +275,9 @@ const ComponentPicker = ({
|
||||
onAssembleVariables={showAssembleVariables ? handleSelectAssembleVariables : undefined}
|
||||
autoFocus={false}
|
||||
isInCodeGeneratorInstructionEditor={currentBlock?.generatorType === GeneratorType.code}
|
||||
hideSearch={useExternalSearch}
|
||||
externalSearchText={useExternalSearch ? (queryString ?? '') : undefined}
|
||||
enableKeyboardNavigation={useExternalSearch}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@ -311,7 +319,7 @@ const ComponentPicker = ({
|
||||
}
|
||||
</>
|
||||
)
|
||||
}, [isAgentTrigger, agentNodes, allFlattenOptions.length, workflowVariableBlock?.show, floatingStyles, isPositioned, refs, handleSelectAgent, handleClose, workflowVariableOptions, isSupportFileVar, currentBlock?.generatorType, handleSelectWorkflowVariable, queryString, workflowVariableBlock?.showManageInputField, workflowVariableBlock?.onManageInputField, showAssembleVariables, handleSelectAssembleVariables])
|
||||
}, [isAgentTrigger, agentNodes, allFlattenOptions.length, workflowVariableBlock?.show, floatingStyles, isPositioned, refs, handleSelectAgent, handleClose, workflowVariableOptions, isSupportFileVar, currentBlock?.generatorType, handleSelectWorkflowVariable, queryString, workflowVariableBlock?.showManageInputField, workflowVariableBlock?.onManageInputField, showAssembleVariables, handleSelectAssembleVariables, useExternalSearch])
|
||||
|
||||
return (
|
||||
<LexicalTypeaheadMenuPlugin
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
@ -17,9 +17,12 @@ export type AgentNode = {
|
||||
type ItemProps = {
|
||||
node: AgentNode
|
||||
onSelect: (node: AgentNode) => void
|
||||
isHighlighted?: boolean
|
||||
onSetHighlight?: () => void
|
||||
registerRef?: (element: HTMLButtonElement | null) => void
|
||||
}
|
||||
|
||||
const Item: FC<ItemProps> = ({ node, onSelect }) => {
|
||||
const Item: FC<ItemProps> = ({ node, onSelect, isHighlighted, onSetHighlight, registerRef }) => {
|
||||
const [isHovering, setIsHovering] = useState(false)
|
||||
|
||||
return (
|
||||
@ -27,10 +30,15 @@ const Item: FC<ItemProps> = ({ node, onSelect }) => {
|
||||
type="button"
|
||||
className={cn(
|
||||
'relative flex h-6 w-full cursor-pointer items-center rounded-md border-none bg-transparent px-3 text-left',
|
||||
isHovering && 'bg-state-base-hover',
|
||||
(isHovering || isHighlighted) && 'bg-state-base-hover',
|
||||
)}
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
ref={registerRef}
|
||||
onMouseEnter={() => {
|
||||
setIsHovering(true)
|
||||
onSetHighlight?.()
|
||||
}}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
onFocus={onSetHighlight}
|
||||
onClick={() => onSelect(node)}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
>
|
||||
@ -58,6 +66,8 @@ type Props = {
|
||||
searchBoxClassName?: string
|
||||
maxHeightClass?: string
|
||||
autoFocus?: boolean
|
||||
externalSearchText?: string
|
||||
enableKeyboardNavigation?: boolean
|
||||
}
|
||||
|
||||
const AgentNodeList: FC<Props> = ({
|
||||
@ -69,9 +79,13 @@ const AgentNodeList: FC<Props> = ({
|
||||
searchBoxClassName,
|
||||
maxHeightClass,
|
||||
autoFocus = true,
|
||||
externalSearchText,
|
||||
enableKeyboardNavigation = false,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const normalizedSearchText = externalSearchText === undefined ? searchText : externalSearchText.trim()
|
||||
const shouldShowSearchInput = !hideSearch && externalSearchText === undefined
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
@ -80,15 +94,79 @@ const AgentNodeList: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const filteredNodes = nodes.filter((node) => {
|
||||
if (!searchText)
|
||||
const filteredNodes = useMemo(() => nodes.filter((node) => {
|
||||
if (!normalizedSearchText)
|
||||
return true
|
||||
return node.title.toLowerCase().includes(searchText.toLowerCase())
|
||||
})
|
||||
return node.title.toLowerCase().includes(normalizedSearchText.toLowerCase())
|
||||
}), [nodes, normalizedSearchText])
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState(-1)
|
||||
const itemRefs = useRef<Array<HTMLButtonElement | null>>([])
|
||||
|
||||
useEffect(() => {
|
||||
itemRefs.current = []
|
||||
}, [filteredNodes.length])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableKeyboardNavigation) {
|
||||
setActiveIndex(-1)
|
||||
return
|
||||
}
|
||||
if (filteredNodes.length === 0) {
|
||||
setActiveIndex(-1)
|
||||
return
|
||||
}
|
||||
setActiveIndex(0)
|
||||
}, [enableKeyboardNavigation, filteredNodes.length, normalizedSearchText])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableKeyboardNavigation || activeIndex < 0)
|
||||
return
|
||||
const target = itemRefs.current[activeIndex]
|
||||
if (target)
|
||||
target.scrollIntoView({ block: 'nearest' })
|
||||
}, [activeIndex, enableKeyboardNavigation, filteredNodes.length])
|
||||
|
||||
const handleSelectItem = useCallback((node: AgentNode) => {
|
||||
onSelect(node)
|
||||
}, [onSelect])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableKeyboardNavigation)
|
||||
return
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (filteredNodes.length === 0)
|
||||
return
|
||||
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(event.key))
|
||||
return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (event.key === 'Escape') {
|
||||
onClose?.()
|
||||
return
|
||||
}
|
||||
if (event.key === 'Enter') {
|
||||
if (activeIndex < 0 || activeIndex >= filteredNodes.length)
|
||||
return
|
||||
handleSelectItem(filteredNodes[activeIndex])
|
||||
return
|
||||
}
|
||||
const delta = event.key === 'ArrowDown' ? 1 : -1
|
||||
setActiveIndex((prev) => {
|
||||
const baseIndex = prev < 0 ? 0 : prev
|
||||
const nextIndex = Math.min(Math.max(baseIndex + delta, 0), filteredNodes.length - 1)
|
||||
return nextIndex
|
||||
})
|
||||
}
|
||||
document.addEventListener('keydown', handleKeyDown, true)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown, true)
|
||||
}
|
||||
}, [activeIndex, enableKeyboardNavigation, filteredNodes, handleSelectItem, onClose])
|
||||
|
||||
return (
|
||||
<>
|
||||
{!hideSearch && (
|
||||
{shouldShowSearchInput && (
|
||||
<>
|
||||
<div className={cn('mx-2 mb-2 mt-2', searchBoxClassName)}>
|
||||
<Input
|
||||
@ -114,11 +192,16 @@ const AgentNodeList: FC<Props> = ({
|
||||
{filteredNodes.length > 0
|
||||
? (
|
||||
<div className={cn('max-h-[85vh] overflow-y-auto py-1', maxHeightClass)}>
|
||||
{filteredNodes.map(node => (
|
||||
{filteredNodes.map((node, index) => (
|
||||
<Item
|
||||
key={node.id}
|
||||
node={node}
|
||||
onSelect={onSelect}
|
||||
isHighlighted={enableKeyboardNavigation && index === activeIndex}
|
||||
onSetHighlight={enableKeyboardNavigation ? () => setActiveIndex(index) : undefined}
|
||||
registerRef={enableKeyboardNavigation ? (element) => {
|
||||
itemRefs.current[index] = element
|
||||
} : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -6,7 +6,7 @@ import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflo
|
||||
import { useHover } from 'ahooks'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { AssembleVariables, CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general'
|
||||
@ -43,6 +43,31 @@ type ItemProps = {
|
||||
zIndex?: number
|
||||
className?: string
|
||||
preferSchemaType?: boolean
|
||||
isHighlighted?: boolean
|
||||
onSetHighlight?: () => void
|
||||
registerRef?: (element: HTMLDivElement | null) => void
|
||||
}
|
||||
|
||||
const buildValueSelector = ({
|
||||
nodeId,
|
||||
objPath,
|
||||
itemData,
|
||||
isFlat,
|
||||
}: {
|
||||
nodeId: string
|
||||
objPath: string[]
|
||||
itemData: Var
|
||||
isFlat?: boolean
|
||||
}): ValueSelector => {
|
||||
if (isFlat)
|
||||
return [itemData.variable]
|
||||
const isSys = itemData.variable.startsWith('sys.')
|
||||
const isEnv = itemData.variable.startsWith('env.')
|
||||
const isChatVar = itemData.variable.startsWith('conversation.')
|
||||
const isRagVariable = itemData.isRagVariable
|
||||
if (isSys || isEnv || isChatVar || isRagVariable)
|
||||
return [...objPath, ...itemData.variable.split('.')]
|
||||
return [nodeId, ...objPath, itemData.variable]
|
||||
}
|
||||
|
||||
const Item: FC<ItemProps> = ({
|
||||
@ -60,6 +85,9 @@ const Item: FC<ItemProps> = ({
|
||||
zIndex,
|
||||
className,
|
||||
preferSchemaType,
|
||||
isHighlighted,
|
||||
onSetHighlight,
|
||||
registerRef,
|
||||
}) => {
|
||||
const isStructureOutput = itemData.type === VarType.object && (itemData.children as StructuredOutput)?.schema?.properties
|
||||
const isFile = itemData.type === VarType.file && !isStructureOutput
|
||||
@ -123,6 +151,10 @@ const Item: FC<ItemProps> = ({
|
||||
})()
|
||||
|
||||
const itemRef = useRef<HTMLDivElement>(null)
|
||||
const setItemRef = useCallback((element: HTMLDivElement | null) => {
|
||||
itemRef.current = element
|
||||
registerRef?.(element)
|
||||
}, [registerRef])
|
||||
const [isItemHovering, setIsItemHovering] = useState(false)
|
||||
useHover(itemRef, {
|
||||
onChange: (hovering) => {
|
||||
@ -152,15 +184,12 @@ const Item: FC<ItemProps> = ({
|
||||
if (!isSupportFileVar && isFile)
|
||||
return
|
||||
|
||||
if (isFlat) {
|
||||
onChange([itemData.variable], itemData)
|
||||
}
|
||||
else if (isSys || isEnv || isChatVar || isRagVariable) { // system variable | environment variable | conversation variable
|
||||
onChange([...objPath, ...itemData.variable.split('.')], itemData)
|
||||
}
|
||||
else {
|
||||
onChange([nodeId, ...objPath, itemData.variable], itemData)
|
||||
}
|
||||
onChange(buildValueSelector({
|
||||
nodeId,
|
||||
objPath,
|
||||
itemData,
|
||||
isFlat,
|
||||
}), itemData)
|
||||
}
|
||||
const variableCategory = useMemo(() => {
|
||||
if (isEnv)
|
||||
@ -181,14 +210,15 @@ const Item: FC<ItemProps> = ({
|
||||
>
|
||||
<PortalToFollowElemTrigger className="w-full">
|
||||
<div
|
||||
ref={itemRef}
|
||||
ref={setItemRef}
|
||||
className={cn(
|
||||
(isObj || isStructureOutput) ? ' pr-1' : 'pr-[18px]',
|
||||
isHovering && ((isObj || isStructureOutput) ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-state-base-hover'),
|
||||
(isHovering || isHighlighted) && ((isObj || isStructureOutput) ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-state-base-hover'),
|
||||
'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3',
|
||||
className,
|
||||
)}
|
||||
onClick={handleChosen}
|
||||
onMouseEnter={onSetHighlight}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
>
|
||||
<div className="flex w-0 grow items-center">
|
||||
@ -259,6 +289,8 @@ type Props = {
|
||||
onAssembleVariables?: () => ValueSelector | null
|
||||
autoFocus?: boolean
|
||||
preferSchemaType?: boolean
|
||||
externalSearchText?: string
|
||||
enableKeyboardNavigation?: boolean
|
||||
}
|
||||
const VarReferenceVars: FC<Props> = ({
|
||||
hideSearch,
|
||||
@ -278,9 +310,13 @@ const VarReferenceVars: FC<Props> = ({
|
||||
onAssembleVariables,
|
||||
autoFocus = true,
|
||||
preferSchemaType,
|
||||
externalSearchText,
|
||||
enableKeyboardNavigation = false,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const normalizedSearchText = externalSearchText === undefined ? searchText : externalSearchText.trim()
|
||||
const shouldShowSearchInput = !hideSearch && externalSearchText === undefined
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
@ -296,35 +332,124 @@ const VarReferenceVars: FC<Props> = ({
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
const filteredVars = vars.filter((v) => {
|
||||
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || isSpecialVar(v.variable.split('.')[0]))
|
||||
return children.length > 0
|
||||
}).filter((node) => {
|
||||
if (!searchText)
|
||||
return node
|
||||
const children = node.vars.filter((v) => {
|
||||
const searchTextLower = searchText.toLowerCase()
|
||||
return v.variable.toLowerCase().includes(searchTextLower) || node.title.toLowerCase().includes(searchTextLower)
|
||||
})
|
||||
return children.length > 0
|
||||
}).map((node) => {
|
||||
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || isSpecialVar(v.variable.split('.')[0]))
|
||||
if (searchText) {
|
||||
const searchTextLower = searchText.toLowerCase()
|
||||
if (!node.title.toLowerCase().includes(searchTextLower))
|
||||
vars = vars.filter(v => v.variable.toLowerCase().includes(searchText.toLowerCase()))
|
||||
}
|
||||
const filteredVars = useMemo(() => {
|
||||
return vars.filter((v) => {
|
||||
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || isSpecialVar(v.variable.split('.')[0]))
|
||||
return children.length > 0
|
||||
}).filter((node) => {
|
||||
if (!normalizedSearchText)
|
||||
return node
|
||||
const searchTextLower = normalizedSearchText.toLowerCase()
|
||||
const children = node.vars.filter((v) => {
|
||||
return v.variable.toLowerCase().includes(searchTextLower) || node.title.toLowerCase().includes(searchTextLower)
|
||||
})
|
||||
return children.length > 0
|
||||
}).map((node) => {
|
||||
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || isSpecialVar(v.variable.split('.')[0]))
|
||||
if (normalizedSearchText) {
|
||||
const searchTextLower = normalizedSearchText.toLowerCase()
|
||||
if (!node.title.toLowerCase().includes(searchTextLower))
|
||||
vars = vars.filter(v => v.variable.toLowerCase().includes(searchTextLower))
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
vars,
|
||||
return {
|
||||
...node,
|
||||
vars,
|
||||
}
|
||||
})
|
||||
}, [normalizedSearchText, vars])
|
||||
|
||||
const flatItems = useMemo(() => {
|
||||
const items: Array<{ node: NodeOutPutVar, itemData: Var }> = []
|
||||
filteredVars.forEach((node) => {
|
||||
node.vars.forEach((itemData) => {
|
||||
items.push({ node, itemData })
|
||||
})
|
||||
})
|
||||
return items
|
||||
}, [filteredVars])
|
||||
const [activeIndex, setActiveIndex] = useState(-1)
|
||||
const itemRefs = useRef<Array<HTMLDivElement | null>>([])
|
||||
|
||||
useEffect(() => {
|
||||
itemRefs.current = []
|
||||
}, [flatItems.length])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableKeyboardNavigation) {
|
||||
setActiveIndex(-1)
|
||||
return
|
||||
}
|
||||
})
|
||||
if (flatItems.length === 0) {
|
||||
setActiveIndex(-1)
|
||||
return
|
||||
}
|
||||
setActiveIndex(0)
|
||||
}, [enableKeyboardNavigation, flatItems.length, normalizedSearchText])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableKeyboardNavigation || activeIndex < 0)
|
||||
return
|
||||
const target = itemRefs.current[activeIndex]
|
||||
if (target)
|
||||
target.scrollIntoView({ block: 'nearest' })
|
||||
}, [activeIndex, enableKeyboardNavigation, flatItems.length])
|
||||
|
||||
const handleSelectItem = useCallback((item: { node: NodeOutPutVar, itemData: Var }) => {
|
||||
const isStructureOutput = item.itemData.type === VarType.object
|
||||
&& (item.itemData.children as StructuredOutput | undefined)?.schema?.properties
|
||||
const isFile = item.itemData.type === VarType.file && !isStructureOutput
|
||||
if (!isSupportFileVar && isFile)
|
||||
return
|
||||
const valueSelector = buildValueSelector({
|
||||
nodeId: item.node.nodeId,
|
||||
objPath: [],
|
||||
itemData: item.itemData,
|
||||
isFlat: item.node.isFlat,
|
||||
})
|
||||
onChange(valueSelector, item.itemData)
|
||||
onClose?.()
|
||||
}, [onChange, onClose, isSupportFileVar])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableKeyboardNavigation)
|
||||
return
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (flatItems.length === 0)
|
||||
return
|
||||
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(event.key))
|
||||
return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (event.key === 'Escape') {
|
||||
onClose?.()
|
||||
return
|
||||
}
|
||||
if (event.key === 'Enter') {
|
||||
if (activeIndex < 0 || activeIndex >= flatItems.length)
|
||||
return
|
||||
handleSelectItem(flatItems[activeIndex])
|
||||
return
|
||||
}
|
||||
const delta = event.key === 'ArrowDown' ? 1 : -1
|
||||
setActiveIndex((prev) => {
|
||||
const baseIndex = prev < 0 ? 0 : prev
|
||||
const nextIndex = Math.min(Math.max(baseIndex + delta, 0), flatItems.length - 1)
|
||||
return nextIndex
|
||||
})
|
||||
}
|
||||
document.addEventListener('keydown', handleKeyDown, true)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown, true)
|
||||
}
|
||||
}, [activeIndex, enableKeyboardNavigation, flatItems, handleSelectItem, onClose])
|
||||
|
||||
let runningIndex = -1
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
!hideSearch && (
|
||||
shouldShowSearchInput && (
|
||||
<>
|
||||
<div className={cn('var-search-input-wrapper mx-2 mb-2 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
|
||||
<Input
|
||||
@ -385,24 +510,33 @@ const VarReferenceVars: FC<Props> = ({
|
||||
{item.title}
|
||||
</div>
|
||||
)}
|
||||
{item.vars.map((v, j) => (
|
||||
<Item
|
||||
key={j}
|
||||
title={item.title}
|
||||
nodeId={item.nodeId}
|
||||
objPath={[]}
|
||||
itemData={v}
|
||||
onChange={onChange}
|
||||
itemWidth={itemWidth}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isException={v.isException}
|
||||
isLoopVar={item.isLoop}
|
||||
isFlat={item.isFlat}
|
||||
isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor}
|
||||
zIndex={zIndex}
|
||||
preferSchemaType={preferSchemaType}
|
||||
/>
|
||||
))}
|
||||
{item.vars.map((v, j) => {
|
||||
runningIndex += 1
|
||||
const itemIndex = runningIndex
|
||||
return (
|
||||
<Item
|
||||
key={j}
|
||||
title={item.title}
|
||||
nodeId={item.nodeId}
|
||||
objPath={[]}
|
||||
itemData={v}
|
||||
onChange={onChange}
|
||||
itemWidth={itemWidth}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isException={v.isException}
|
||||
isLoopVar={item.isLoop}
|
||||
isFlat={item.isFlat}
|
||||
isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor}
|
||||
zIndex={zIndex}
|
||||
preferSchemaType={preferSchemaType}
|
||||
isHighlighted={enableKeyboardNavigation && itemIndex === activeIndex}
|
||||
onSetHighlight={enableKeyboardNavigation ? () => setActiveIndex(itemIndex) : undefined}
|
||||
registerRef={enableKeyboardNavigation ? (element) => {
|
||||
itemRefs.current[itemIndex] = element
|
||||
} : undefined}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{item.isFlat && !filteredVars[i + 1]?.isFlat && !!filteredVars.find(item => !item.isFlat) && (
|
||||
<div className="relative mt-[14px] flex items-center space-x-1">
|
||||
<div className="h-0 w-3 shrink-0 border border-divider-subtle"></div>
|
||||
|
||||
Reference in New Issue
Block a user