Skip to content

Commit e72e92e

Browse files
committed
Move show/hide field logic into EditNodeDialog
1 parent 026ae25 commit e72e92e

6 files changed

Lines changed: 103 additions & 21 deletions

File tree

packages/agentflow/examples/src/demos/CustomNodeExample.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ const customNodeInputParams: InputParam[] = [
127127
name: 'value',
128128
label: 'Value',
129129
type: 'string',
130-
hide: { operation: 'isEmpty' }
130+
hide: { 'conditions[$index].operation': 'isEmpty' }
131131
}
132132
]
133133
},

packages/agentflow/src/atoms/ArrayInput.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,32 @@ describe('ArrayInput', () => {
281281
expect(deleteButtons[1]).toBeDisabled()
282282
})
283283

284+
// Test 11: itemParameters prop overrides inputParam.array display flags
285+
it('should use itemParameters prop for field visibility when provided, ignoring inputParam.array display flags', () => {
286+
// inputParam.array has both fields with no display flag (both would show)
287+
const dataWithItem: NodeData = {
288+
...mockNodeData,
289+
inputValues: { testArray: [{ field1: 'value', field2: 10 }] }
290+
} as NodeData
291+
292+
// Parent (EditNodeDialog) has evaluated field2 as hidden
293+
const itemParameters: InputParam[][] = [
294+
[
295+
{ id: 'field1', name: 'field1', label: 'Field 1', type: 'string', display: true } as InputParam,
296+
{ id: 'field2', name: 'field2', label: 'Field 2', type: 'number', display: false } as InputParam
297+
]
298+
]
299+
300+
render(
301+
<ArrayInput inputParam={mockInputParam} data={dataWithItem} onDataChange={mockOnDataChange} itemParameters={itemParameters} />
302+
)
303+
304+
// field1 visible per itemParameters
305+
expect(screen.getByTestId('input-handler-field1')).toBeInTheDocument()
306+
// field2 hidden per itemParameters even though inputParam.array has no display flag
307+
expect(screen.queryByTestId('input-handler-field2')).not.toBeInTheDocument()
308+
})
309+
284310
// Test reading minItems from inputParam
285311
it('should read minItems from inputParam', () => {
286312
const inputParamWithMinItems: InputParam = {

packages/agentflow/src/atoms/ArrayInput.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { useTheme } from '@mui/material/styles'
55
import { IconPlus, IconTrash } from '@tabler/icons-react'
66

77
import type { InputParam, NodeData } from '@/core/types'
8-
import { evaluateFieldVisibility } from '@/core/utils/fieldVisibility'
98

109
import { NodeInputHandler } from './NodeInputHandler'
1110

@@ -14,17 +13,10 @@ export interface ArrayInputProps {
1413
data: NodeData
1514
disabled?: boolean
1615
onDataChange?: (params: { inputParam: InputParam; newValue: unknown }) => void
16+
itemParameters?: InputParam[][]
1717
}
1818

19-
/**
20-
* Array input component for managing lists of structured data
21-
*
22-
* @param inputParam - Array field definition with structure
23-
* @param data - Node data containing inputValues
24-
* @param onDataChange - Callback invoked when array is modified
25-
* @param disabled - Whether the input is disabled
26-
*/
27-
export function ArrayInput({ inputParam, data, disabled = false, onDataChange }: ArrayInputProps) {
19+
export function ArrayInput({ inputParam, data, disabled = false, onDataChange, itemParameters: itemParametersProp }: ArrayInputProps) {
2820
const theme = useTheme()
2921

3022
// Derive array items directly from props (single source of truth)
@@ -34,10 +26,11 @@ export function ArrayInput({ inputParam, data, disabled = false, onDataChange }:
3426
[data.inputValues, inputParam.name]
3527
)
3628

37-
// Derive item parameters for each array item
38-
const itemParameters = useMemo(
39-
() => arrayItems.map((itemValues, index) => evaluateFieldVisibility(inputParam.array || [], itemValues, index)),
40-
[arrayItems, inputParam.array]
29+
// Use pre-computed itemParameters
30+
// Falls back to raw field definitions for nested arrays without show/hide conditions.
31+
const itemParameters = useMemo<InputParam[][]>(
32+
() => itemParametersProp ?? arrayItems.map(() => inputParam.array || []),
33+
[itemParametersProp, arrayItems, inputParam.array]
4134
)
4235

4336
// Handle changes to individual fields within array items

packages/agentflow/src/atoms/NodeInputHandler.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface NodeInputHandlerProps {
2424
isAdditionalParams?: boolean
2525
disablePadding?: boolean
2626
onDataChange?: (params: { inputParam: InputParam; newValue: unknown }) => void
27+
itemParameters?: InputParam[][]
2728
}
2829

2930
/**
@@ -37,7 +38,8 @@ export function NodeInputHandler({
3738
disabled = false,
3839
isAdditionalParams = false,
3940
disablePadding = false,
40-
onDataChange
41+
onDataChange,
42+
itemParameters
4143
}: NodeInputHandlerProps) {
4244
const theme = useTheme()
4345
const ref = useRef<HTMLDivElement>(null)
@@ -119,7 +121,15 @@ export function NodeInputHandler({
119121
</Select>
120122
)
121123
case 'array':
122-
return <ArrayInput inputParam={inputParam} data={data} disabled={disabled} onDataChange={onDataChange} />
124+
return (
125+
<ArrayInput
126+
inputParam={inputParam}
127+
data={data}
128+
disabled={disabled}
129+
onDataChange={onDataChange}
130+
itemParameters={itemParameters}
131+
/>
132+
)
123133

124134
default:
125135
// For unsupported types, render a basic text field

packages/agentflow/src/features/node-editor/EditNodeDialog.test.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,20 @@ jest.mock('@/atoms', () => ({
2727
NodeInputHandler: ({
2828
inputParam,
2929
onDataChange,
30-
data
30+
data,
31+
itemParameters
3132
}: {
3233
inputParam: InputParam
3334
data: NodeData
3435
onDataChange: (args: { inputParam: InputParam; newValue: unknown }) => void
36+
itemParameters?: InputParam[][]
3537
}) => {
3638
// Handle array type inputs differently
3739
if (inputParam.type === 'array') {
3840
const currentArray = (data.inputValues?.[inputParam.name] as Record<string, unknown>[]) || []
3941

4042
return (
41-
<div data-testid={`input-handler-${inputParam.name}`}>
43+
<div data-testid={`input-handler-${inputParam.name}`} data-item-params-count={itemParameters?.length ?? 'none'}>
4244
<button
4345
data-testid={`add-${inputParam.name}`}
4446
onClick={() => {
@@ -426,5 +428,37 @@ describe('EditNodeDialog', () => {
426428
expect(lastCall[1].inputValues).toHaveProperty('connections')
427429
expect(Array.isArray(lastCall[1].inputValues.connections)).toBe(true)
428430
})
431+
432+
it('should compute and pass itemParameters to NodeInputHandler matching array item count', () => {
433+
const arrayParams: InputParam[] = [
434+
{
435+
name: 'items',
436+
label: 'Item',
437+
type: 'array',
438+
array: [
439+
{ name: 'type', label: 'Type', type: 'string' } as InputParam,
440+
{ name: 'detail', label: 'Detail', type: 'string', show: { 'items[$index].type': 'special' } } as InputParam
441+
]
442+
} as InputParam
443+
]
444+
445+
const propsWithArrayData = {
446+
...defaultProps,
447+
dialogProps: {
448+
...defaultProps.dialogProps,
449+
inputParams: arrayParams,
450+
data: {
451+
...nodeData,
452+
inputValues: { items: [{ type: 'normal' }, { type: 'special' }] }
453+
}
454+
}
455+
}
456+
457+
render(<EditNodeDialog {...propsWithArrayData} />)
458+
459+
// itemParameters should have one entry per array item (2 items → count = 2)
460+
const handler = screen.getByTestId('input-handler-items')
461+
expect(handler).toHaveAttribute('data-item-params-count', '2')
462+
})
429463
})
430464
})

packages/agentflow/src/features/node-editor/EditNodeDialog.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { memo, useEffect, useRef, useState } from 'react'
1+
import { memo, useCallback, useEffect, useRef, useState } from 'react'
22
import { useUpdateNodeInternals } from 'reactflow'
33

44
import { Avatar, Box, ButtonBase, Dialog, DialogContent, Stack, TextField, Typography } from '@mui/material'
@@ -30,6 +30,22 @@ function EditNodeDialogComponent({ show, dialogProps, onCancel }: EditNodeDialog
3030
const [data, setData] = useState<NodeData | null>(null)
3131
const [isEditingNodeName, setEditingNodeName] = useState(false)
3232
const [nodeName, setNodeName] = useState('')
33+
const [arrayItemParameters, setArrayItemParameters] = useState<Record<string, InputParam[][]>>({})
34+
35+
// Evaluate field visibility for each item in every array-type param.
36+
const computeArrayItemParameters = useCallback(
37+
(params: InputParam[], inputValues: Record<string, unknown>): Record<string, InputParam[][]> => {
38+
const result: Record<string, InputParam[][]> = {}
39+
for (const param of params) {
40+
if (param.type === 'array' && param.array) {
41+
const items = (inputValues[param.name] as Record<string, unknown>[]) || []
42+
result[param.name] = items.map((_, index) => evaluateFieldVisibility(param.array!, inputValues, index))
43+
}
44+
}
45+
return result
46+
},
47+
[]
48+
)
3349

3450
const onNodeLabelChange = () => {
3551
if (!data || !nodeNameRef.current) return
@@ -50,6 +66,7 @@ function EditNodeDialogComponent({ show, dialogProps, onCancel }: EditNodeDialog
5066

5167
const updatedParams = evaluateFieldVisibility(inputParams, updatedInputValues)
5268
setInputParams(updatedParams)
69+
setArrayItemParameters(computeArrayItemParameters(inputParams, updatedInputValues))
5370
// Keep full inputValues in state — hidden field values are preserved so they
5471
// can be restored when visibility conditions change (e.g. toggling provider back).
5572
// Stripping should only happen on save/export, not on every keystroke.
@@ -62,12 +79,13 @@ function EditNodeDialogComponent({ show, dialogProps, onCancel }: EditNodeDialog
6279
const initialValues = dialogProps.data?.inputValues || {}
6380
const evaluatedParams = evaluateFieldVisibility(dialogProps.inputParams, initialValues)
6481
setInputParams(evaluatedParams)
82+
setArrayItemParameters(computeArrayItemParameters(dialogProps.inputParams, initialValues))
6583
}
6684
if (dialogProps.data) {
6785
setData(dialogProps.data)
6886
if (dialogProps.data.label) setNodeName(dialogProps.data.label)
6987
}
70-
}, [dialogProps])
88+
}, [dialogProps, computeArrayItemParameters])
7189

7290
if (!show) return null
7391

@@ -233,6 +251,7 @@ function EditNodeDialogComponent({ show, dialogProps, onCancel }: EditNodeDialog
233251
data={data}
234252
isAdditionalParams={true}
235253
onDataChange={onCustomDataChange}
254+
itemParameters={inputParam.type === 'array' ? arrayItemParameters[inputParam.name] : undefined}
236255
/>
237256
)
238257
})}

0 commit comments

Comments
 (0)