From c877b1ad4346a6b26d77c44944c06adf46430326 Mon Sep 17 00:00:00 2001 From: Patrik Date: Wed, 16 Apr 2025 15:38:53 -0400 Subject: [PATCH] feat: threads operation through field condition function (#12132) This PR updates the field `condition` function property to include a new `operation` argument. The `operation` arg provides a string relating to which operation the field type is currently executing within. #### Changes: - Added `operation: Operation` in the Condition type. - Updated relevant condition checks to ensure correct parameter usage. --- docs/fields/overview.mdx | 1 + packages/payload/src/fields/config/types.ts | 5 +++++ .../src/fields/hooks/beforeChange/promise.ts | 7 ++++++- .../addFieldStatePromise.ts | 1 + .../fieldSchemasToFormState/iterateFields.ts | 1 + .../collections/ConditionalLogic/e2e.spec.ts | 16 ++++++++++++++++ .../fields/collections/ConditionalLogic/index.ts | 15 ++++++++++++++- test/fields/payload-types.ts | 2 ++ 8 files changed, 46 insertions(+), 2 deletions(-) diff --git a/docs/fields/overview.mdx b/docs/fields/overview.mdx index 5b8b0bf463..17b46884c5 100644 --- a/docs/fields/overview.mdx +++ b/docs/fields/overview.mdx @@ -541,6 +541,7 @@ The `ctx` object: | Property | Description | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **`blockData`** | The nearest parent block's data. If the field is not inside a block, this will be `undefined`. | +| **`operation`** | A string relating to which operation the field type is currently executing within. | | **`path`** | The full path to the field in the schema, represented as an array of string segments, including array indexes. I.e `['group', 'myArray', '1', 'textField']`. | | **`user`** | The currently authenticated user object. | diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index db91662f59..835c91da5e 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -269,6 +269,7 @@ export type Condition = ( siblingData: Partial, { blockData, + operation, path, user, }: { @@ -276,6 +277,10 @@ export type Condition = ( * The data of the nearest parent block. If the field is not within a block, `blockData` will be equal to `undefined`. */ blockData: Partial + /** + * A string relating to which operation the field type is currently executing within. + */ + operation: Operation /** * 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. */ diff --git a/packages/payload/src/fields/hooks/beforeChange/promise.ts b/packages/payload/src/fields/hooks/beforeChange/promise.ts index 662e4706f9..87a323a852 100644 --- a/packages/payload/src/fields/hooks/beforeChange/promise.ts +++ b/packages/payload/src/fields/hooks/beforeChange/promise.ts @@ -109,7 +109,12 @@ export const promise = async ({ const passesCondition = field.admin?.condition ? Boolean( - field.admin.condition(data, siblingData, { blockData, path: pathSegments, user: req.user }), + field.admin.condition(data, siblingData, { + blockData, + operation, + path: pathSegments, + user: req.user, + }), ) : true let skipValidationFromHere = skipValidation || !passesCondition diff --git a/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts b/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts index 0951315668..b3896560dc 100644 --- a/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts +++ b/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts @@ -827,6 +827,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom if (passesCondition && typeof tab.admin?.condition === 'function') { tabPassesCondition = tab.admin.condition(fullData, data, { blockData, + operation, path: pathSegments, user: req.user, }) diff --git a/packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts b/packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts index 5e7b2e5340..f934198319 100644 --- a/packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts +++ b/packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts @@ -151,6 +151,7 @@ export const iterateFields = async ({ ? Boolean( field.admin.condition(fullData || {}, data || {}, { blockData, + operation, path: pathSegments, user: req.user, }), diff --git a/test/fields/collections/ConditionalLogic/e2e.spec.ts b/test/fields/collections/ConditionalLogic/e2e.spec.ts index 3c990f2347..a5dc5ed9d2 100644 --- a/test/fields/collections/ConditionalLogic/e2e.spec.ts +++ b/test/fields/collections/ConditionalLogic/e2e.spec.ts @@ -10,6 +10,7 @@ import type { Config } from '../../payload-types.js' import { ensureCompilationIsDone, initPageConsoleErrorCatch, + saveDocAndAssert, // throttleTest, } from '../../../helpers.js' import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js' @@ -225,4 +226,19 @@ describe('Conditional Logic', () => { await expect(numberField).toBeVisible() }) + + test('should render field based on operation argument', async () => { + await page.goto(url.create) + + const textField = page.locator('#field-text') + const fieldWithOperationCondition = page.locator('#field-fieldWithOperationCondition') + + await textField.fill('some text') + + await expect(fieldWithOperationCondition).toBeVisible() + + await saveDocAndAssert(page) + + await expect(fieldWithOperationCondition).toBeHidden() + }) }) diff --git a/test/fields/collections/ConditionalLogic/index.ts b/test/fields/collections/ConditionalLogic/index.ts index 864eff4462..96af0df38c 100644 --- a/test/fields/collections/ConditionalLogic/index.ts +++ b/test/fields/collections/ConditionalLogic/index.ts @@ -24,6 +24,19 @@ const ConditionalLogic: CollectionConfig = { condition: ({ toggleField }) => Boolean(toggleField), }, }, + { + name: 'fieldWithOperationCondition', + type: 'text', + admin: { + condition: (data, siblingData, { operation }) => { + if (operation === 'create') { + return true + } + + return false + }, + }, + }, { name: 'customFieldWithField', type: 'text', @@ -217,7 +230,7 @@ const ConditionalLogic: CollectionConfig = { name: 'numberField', type: 'number', admin: { - condition: (data, siblingData, { path, user }) => { + condition: (data, siblingData, { path }) => { // Ensure path has enough depth if (path.length < 5) { return false diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index 4c5ae89238..8b1ce2ad53 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -790,6 +790,7 @@ export interface ConditionalLogic { text: string; toggleField?: boolean | null; fieldWithCondition?: string | null; + fieldWithOperationCondition?: string | null; customFieldWithField?: string | null; customFieldWithHOC?: string | null; customClientFieldWithCondition?: string | null; @@ -2364,6 +2365,7 @@ export interface ConditionalLogicSelect { text?: T; toggleField?: T; fieldWithCondition?: T; + fieldWithOperationCondition?: T; customFieldWithField?: T; customFieldWithHOC?: T; customClientFieldWithCondition?: T;