Skip to content

Commit ba30d76

Browse files
authored
feat: threads path through field condition functions (#11528)
This PR updates the field `condition` function property to include a new `path` argument. The `path` arg provides the schema path of the field, including array indices where applicable. #### Changes: - Added `path: (number | string)[]` in the Condition type. - Updated relevant condition checks to ensure correct parameter usage.
1 parent 04b0468 commit ba30d76

File tree

7 files changed

+155
-12
lines changed

7 files changed

+155
-12
lines changed

docs/fields/overview.mdx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ export const MyCollectionConfig: CollectionConfig = {
502502

503503
All Description Functions receive the following arguments:
504504

505-
| Argument | Description |
505+
| Argument | Description |
506506
| --- | --- |
507507
| **`t`** | The `t` function used to internationalize the Admin Panel. [More details](../configuration/i18n) |
508508

@@ -512,11 +512,21 @@ All Description Functions receive the following arguments:
512512

513513
### Conditional Logic
514514

515-
You can show and hide fields based on what other fields are doing by utilizing conditional logic on a field by field basis. The `condition` property on a field's admin config accepts a function which takes three arguments:
515+
You can show and hide fields based on what other fields are doing by utilizing conditional logic on a field by field basis. The `condition` property on a field's admin config accepts a function which takes the following arguments:
516516

517-
- `data` - the entire document's data that is currently being edited
518-
- `siblingData` - only the fields that are direct siblings to the field with the condition
519-
- `{ user }` - the final argument is an object containing the currently authenticated user
517+
| Argument | Description |
518+
| --- | --- |
519+
| **`data`** | The entire document's data that is currently being edited. |
520+
| **`siblingData`** | Only the fields that are direct siblings to the field with the condition. |
521+
| **`ctx`** | An object containing additional information about the field’s location and user. |
522+
523+
The `ctx` object:
524+
525+
| Property | Description |
526+
| --- | --- |
527+
| **`blockData`** | The nearest parent block's data. If the field is not inside a block, this will be `undefined`. |
528+
| **`path`** | The full path to the field in the schema, including array indexes. Useful for dynamic lookups. |
529+
| **`user`** | The currently authenticated user object. |
520530

521531
The `condition` function should return a boolean that will control if the field should be displayed or not.
522532

@@ -535,7 +545,7 @@ The `condition` function should return a boolean that will control if the field
535545
type: 'text',
536546
admin: {
537547
// highlight-start
538-
condition: (data, siblingData, { user }) => {
548+
condition: (data, siblingData, { blockData, path, user }) => {
539549
if (data.enableGreeting) {
540550
return true
541551
} else {

packages/payload/src/fields/config/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,17 @@ export type Condition<TData extends TypeWithID = any, TSiblingData = any> = (
264264
siblingData: Partial<TSiblingData>,
265265
{
266266
blockData,
267+
path,
267268
user,
268269
}: {
269270
/**
270271
* The data of the nearest parent block. If the field is not within a block, `blockData` will be equal to `undefined`.
271272
*/
272273
blockData: Partial<TData>
274+
/**
275+
* The path of the field, e.g. ["group", "myArray", 1, "textField"]. The path is the schemaPath but with indexes and would be used in the context of field data, not field schemas.
276+
*/
277+
path: (number | string)[]
273278
user: PayloadRequest['user']
274279
},
275280
) => boolean

packages/payload/src/fields/hooks/beforeChange/promise.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,6 @@ export const promise = async ({
8686
parentSchemaPath,
8787
})
8888

89-
const passesCondition = field.admin?.condition
90-
? Boolean(field.admin.condition(data, siblingData, { blockData, user: req.user }))
91-
: true
92-
let skipValidationFromHere = skipValidation || !passesCondition
9389
const { localization } = req.payload.config
9490
const defaultLocale = localization ? localization?.defaultLocale : 'en'
9591
const operationLocale = req.locale || defaultLocale
@@ -98,6 +94,13 @@ export const promise = async ({
9894
const schemaPathSegments = schemaPath ? schemaPath.split('.') : []
9995
const indexPathSegments = indexPath ? indexPath.split('-').filter(Boolean)?.map(Number) : []
10096

97+
const passesCondition = field.admin?.condition
98+
? Boolean(
99+
field.admin.condition(data, siblingData, { blockData, path: pathSegments, user: req.user }),
100+
)
101+
: true
102+
let skipValidationFromHere = skipValidation || !passesCondition
103+
101104
if (fieldAffectsData(field)) {
102105
// skip validation if the field is localized and the incoming data is null
103106
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && operationLocale !== defaultLocale) {

packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,18 @@ export const iterateFields = async ({
118118
parentSchemaPath,
119119
})
120120

121+
const pathSegments = path ? path.split('.') : []
122+
121123
if (!skipConditionChecks) {
122124
try {
123125
passesCondition = Boolean(
124126
(field?.admin?.condition
125127
? Boolean(
126-
field.admin.condition(fullData || {}, data || {}, { blockData, user: req.user }),
128+
field.admin.condition(fullData || {}, data || {}, {
129+
blockData,
130+
path: pathSegments,
131+
user: req.user,
132+
}),
127133
)
128134
: true) && parentPassesCondition,
129135
)

test/fields/collections/ConditionalLogic/e2e.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ describe('Conditional Logic', () => {
161161

162162
test('should not render fields when adding array or blocks rows until form state returns', async () => {
163163
await page.goto(url.create)
164-
const addRowButton = page.locator('.array-field__add-row')
164+
const addRowButton = page.locator('#field-arrayWithConditionalField .array-field__add-row')
165165
const fieldWithConditionSelector = 'input#field-arrayWithConditionalField__0__textWithCondition'
166166
await addRowButton.click()
167167

@@ -177,4 +177,30 @@ describe('Conditional Logic', () => {
177177
await fieldToToggle.click()
178178
await expect(page.locator(fieldWithConditionSelector)).toBeVisible()
179179
})
180+
181+
test('should render field based on path argument', async () => {
182+
await page.goto(url.create)
183+
184+
const arrayOneButton = page.locator('#field-arrayOne .array-field__add-row')
185+
await arrayOneButton.click()
186+
187+
const arrayTwoButton = page.locator('#arrayOne-row-0 .array-field__add-row')
188+
await arrayTwoButton.click()
189+
190+
const arrayThreeButton = page.locator('#arrayOne-0-arrayTwo-row-0 .array-field__add-row')
191+
await arrayThreeButton.click()
192+
193+
const numberField = page.locator('#field-arrayOne__0__arrayTwo__0__arrayThree__0__numberField')
194+
195+
await expect(numberField).toBeHidden()
196+
197+
const selectField = page.locator('#field-arrayOne__0__arrayTwo__0__selectOptions')
198+
199+
await selectField.click({ delay: 100 })
200+
const options = page.locator('.rs__option')
201+
202+
await options.locator('text=Option Two').click()
203+
204+
await expect(numberField).toBeVisible()
205+
})
180206
})

test/fields/collections/ConditionalLogic/index.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,63 @@ const ConditionalLogic: CollectionConfig = {
182182
},
183183
],
184184
},
185+
{
186+
name: 'arrayOne',
187+
type: 'array',
188+
fields: [
189+
{
190+
name: 'title',
191+
type: 'text',
192+
},
193+
{
194+
name: 'arrayTwo',
195+
type: 'array',
196+
fields: [
197+
{
198+
name: 'selectOptions',
199+
type: 'select',
200+
defaultValue: 'optionOne',
201+
options: [
202+
{
203+
label: 'Option One',
204+
value: 'optionOne',
205+
},
206+
{
207+
label: 'Option Two',
208+
value: 'optionTwo',
209+
},
210+
],
211+
},
212+
{
213+
name: 'arrayThree',
214+
type: 'array',
215+
fields: [
216+
{
217+
name: 'numberField',
218+
type: 'number',
219+
admin: {
220+
condition: (data, siblingData, { path, user }) => {
221+
// Ensure path has enough depth
222+
if (path.length < 5) {
223+
return false
224+
}
225+
226+
const arrayOneIndex = parseInt(String(path[1]), 10)
227+
const arrayTwoIndex = parseInt(String(path[3]), 10)
228+
229+
const arrayOneItem = data.arrayOne?.[arrayOneIndex]
230+
const arrayTwoItem = arrayOneItem?.arrayTwo?.[arrayTwoIndex]
231+
232+
return arrayTwoItem?.selectOptions === 'optionTwo'
233+
},
234+
},
235+
},
236+
],
237+
},
238+
],
239+
},
240+
],
241+
},
185242
],
186243
}
187244

test/fields/payload-types.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,24 @@ export interface ConditionalLogic {
11531153
blockType: 'blockWithConditionalField';
11541154
}[]
11551155
| null;
1156+
arrayOne?:
1157+
| {
1158+
title?: string | null;
1159+
arrayTwo?:
1160+
| {
1161+
selectOptions?: ('optionOne' | 'optionTwo') | null;
1162+
arrayThree?:
1163+
| {
1164+
numberField?: number | null;
1165+
id?: string | null;
1166+
}[]
1167+
| null;
1168+
id?: string | null;
1169+
}[]
1170+
| null;
1171+
id?: string | null;
1172+
}[]
1173+
| null;
11561174
updatedAt: string;
11571175
createdAt: string;
11581176
}
@@ -2874,6 +2892,24 @@ export interface ConditionalLogicSelect<T extends boolean = true> {
28742892
blockName?: T;
28752893
};
28762894
};
2895+
arrayOne?:
2896+
| T
2897+
| {
2898+
title?: T;
2899+
arrayTwo?:
2900+
| T
2901+
| {
2902+
selectOptions?: T;
2903+
arrayThree?:
2904+
| T
2905+
| {
2906+
numberField?: T;
2907+
id?: T;
2908+
};
2909+
id?: T;
2910+
};
2911+
id?: T;
2912+
};
28772913
updatedAt?: T;
28782914
createdAt?: T;
28792915
}

0 commit comments

Comments
 (0)