diff --git a/packages/dataviews/src/dataform-controls/text.tsx b/packages/dataviews/src/dataform-controls/text.tsx
index 7ac095f4abede7..52eb71de6f053b 100644
--- a/packages/dataviews/src/dataform-controls/text.tsx
+++ b/packages/dataviews/src/dataform-controls/text.tsx
@@ -14,6 +14,7 @@ export default function Text< Item >( {
field,
onChange,
hideLabelFromVision,
+ errorMessage,
}: DataFormControlProps< Item > ) {
const { id, label, placeholder } = field;
const value = field.getValue( { item: data } );
@@ -27,14 +28,19 @@ export default function Text< Item >( {
);
return (
-
+ <>
+
+ { errorMessage && (
+
{ errorMessage }
+ ) }
+ >
);
}
diff --git a/packages/dataviews/src/dataform-hooks/use-form.ts b/packages/dataviews/src/dataform-hooks/use-form.ts
index d7abfe29487e99..8822f09a3133d5 100644
--- a/packages/dataviews/src/dataform-hooks/use-form.ts
+++ b/packages/dataviews/src/dataform-hooks/use-form.ts
@@ -1,11 +1,20 @@
-import { useEffect, useState } from 'react';
-import { FormField } from '../types';
+/**
+ * WordPress dependencies
+ */
+import { useState } from '@wordpress/element';
-export const useForm = ( supportedFields: Record< string, FormField > ) => {
+/**
+ * Internal dependencies
+ */
+import type { FormField } from '../types';
+
+export const useForm = < Item >(
+ supportedFields: Record< string, FormField >
+) => {
const [ form, setForm ] = useState( {
fields: supportedFields,
touchedFields: [] as string[],
- errors: {},
+ messageErrors: {},
} );
const setTouchedFields = ( touchedFields: string[] ) => {
@@ -15,33 +24,26 @@ export const useForm = ( supportedFields: Record< string, FormField > ) => {
} );
};
- const setError = ( field: string, error: string ) => {
+ const setErrors = ( field: string, error: string | undefined ) => {
setForm( {
...form,
- errors: {
- ...form.errors,
+ messageErrors: {
+ ...form.messageErrors,
[ field ]: error,
},
} );
};
- const isFormValid = () => {
+ const isFormValid = ( data: Item ) => {
return Object.entries( form.fields ).every( ( [ , field ] ) => {
- if (
- field.validation.validateWhenDirty === true &&
- form.touchedFields.includes( field.id )
- ) {
- return field.validation.callback().isValid;
- }
-
- return field.validation.callback().isValid;
+ return field.validation.callback( data ).isValid;
} );
};
return {
...form,
setTouchedFields,
- setError,
- isFormValid: isFormValid(),
+ setErrors,
+ isFormValid,
};
};
diff --git a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx
index 32d003459c4052..8824726037ef91 100644
--- a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx
+++ b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx
@@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { __experimentalVStack as VStack } from '@wordpress/components';
-import { useContext, useMemo } from '@wordpress/element';
+import { useContext, useEffect, useMemo } from '@wordpress/element';
/**
* Internal dependencies
@@ -28,6 +28,7 @@ export function DataFormLayout< Item >( {
field: FormField;
onChange: ( value: any ) => void;
hideLabelFromVision?: boolean;
+ errorMessage: string | undefined;
} ) => React.JSX.Element | null,
field: FormField
) => React.JSX.Element;
@@ -47,8 +48,20 @@ export function DataFormLayout< Item >( {
[ form ]
);
- // @ts-ignore
- const { setTouchedFields, setError, touchedFields } = form;
+ const { setTouchedFields, setErrors, touchedFields, messageErrors } = form;
+
+ useEffect( () => {
+ normalizedFormFields.forEach( ( formField ) => {
+ const { isValid, errorMessage } = formField.validation.callback( {
+ ...data,
+ } );
+ if ( ! isValid ) {
+ setErrors( formField.id, errorMessage );
+ } else {
+ setErrors( formField.id, undefined );
+ }
+ } );
+ }, [ data, normalizedFormFields, setErrors ] );
return (
@@ -81,6 +94,14 @@ export function DataFormLayout< Item >( {
key={ formField.id }
data={ data }
field={ formField }
+ errorMessage={
+ ( formField.validation.showErrorOnlyWhenDirty &&
+ touchedFields.includes( formField.id ) ) ||
+ ( ! formField.validation.showErrorOnlyWhenDirty &&
+ messageErrors[ formField.id ] )
+ ? messageErrors[ formField.id ]
+ : undefined
+ }
onChange={ ( value ) => {
if ( ! touchedFields.includes( formField.id ) ) {
setTouchedFields( [
@@ -90,26 +111,18 @@ export function DataFormLayout< Item >( {
] );
}
- if (
- ( formField.validation.validateWhenDirty &&
- // @ts-ignore
- form.touchedFields.includes(
- formField.id
- ) ) ||
- ! formField.validation.validateWhenDirty
- ) {
- const { isValid, message } =
- formField.validation.callback();
-
- if ( ! isValid ) {
- setError( formField.id, message );
- }
- }
-
onChange( value );
+ const { isValid, errorMessage } =
+ formField.validation.callback( {
+ ...data,
+ ...value,
+ } );
+ if ( ! isValid ) {
+ setErrors( formField.id, errorMessage );
+ } else {
+ setErrors( formField.id, undefined );
+ }
} }
- // @ts-ignore
- message={ form.errors[ formField.id ] }
/>
);
} ) }
diff --git a/packages/dataviews/src/dataforms-layouts/is-combined-field.ts b/packages/dataviews/src/dataforms-layouts/is-combined-field.ts
index 0855dbe4a5dac0..3df6fdc60f906e 100644
--- a/packages/dataviews/src/dataforms-layouts/is-combined-field.ts
+++ b/packages/dataviews/src/dataforms-layouts/is-combined-field.ts
@@ -1,11 +1,10 @@
/**
* Internal dependencies
*/
-import { NormalizedFormField } from '../normalize-form-fields';
-import type { FormField, CombinedFormField, NormalizedField } from '../types';
+import type { FormField, CombinedFormField } from '../types';
export function isCombinedField(
- field: FormField | NormalizedFormField
+ field: FormField
): field is CombinedFormField {
return ( field as CombinedFormField ).children !== undefined;
}
diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx
index 269b2bb418a856..9411ba4caf5e82 100644
--- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx
+++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx
@@ -66,6 +66,7 @@ function PanelDropdown< Item >( {
data,
onChange,
field,
+ errorMessage,
}: {
fieldDefinition: NormalizedField< Item >;
popoverAnchor: HTMLElement | null;
@@ -73,6 +74,7 @@ function PanelDropdown< Item >( {
data: Item;
onChange: ( value: any ) => void;
field: FormField;
+ errorMessage: string | undefined;
} ) {
const fieldLabel = isCombinedField( field )
? field.label
@@ -158,6 +160,7 @@ function PanelDropdown< Item >( {
hideLabelFromVision={
( form?.fields ?? [] ).length < 2
}
+ errorMessage={ errorMessage }
/>
) }
@@ -171,6 +174,7 @@ export default function FormPanelField< Item >( {
data,
field,
onChange,
+ errorMessage,
}: FieldLayoutProps< Item > ) {
const { fields } = useContext( DataFormContext );
const fieldDefinition = fields.find( ( fieldDef ) => {
@@ -221,6 +225,7 @@ export default function FormPanelField< Item >( {
data={ data }
onChange={ onChange }
labelPosition={ labelPosition }
+ errorMessage={ errorMessage }
/>
@@ -237,6 +242,7 @@ export default function FormPanelField< Item >( {
data={ data }
onChange={ onChange }
labelPosition={ labelPosition }
+ errorMessage={ errorMessage }
/>
);
@@ -259,6 +265,7 @@ export default function FormPanelField< Item >( {
data={ data }
onChange={ onChange }
labelPosition={ labelPosition }
+ errorMessage={ errorMessage }
/>
diff --git a/packages/dataviews/src/dataforms-layouts/regular/index.tsx b/packages/dataviews/src/dataforms-layouts/regular/index.tsx
index a3d90b807b5cd4..f7357872a22799 100644
--- a/packages/dataviews/src/dataforms-layouts/regular/index.tsx
+++ b/packages/dataviews/src/dataforms-layouts/regular/index.tsx
@@ -35,6 +35,7 @@ export default function FormRegularField< Item >( {
field,
onChange,
hideLabelFromVision,
+ errorMessage,
}: FieldLayoutProps< Item > ) {
const { fields } = useContext( DataFormContext );
@@ -93,6 +94,7 @@ export default function FormRegularField< Item >( {
key={ fieldDefinition.id }
data={ data }
field={ fieldDefinition }
+ errorMessage={ errorMessage }
onChange={ onChange }
hideLabelFromVision
/>
@@ -106,6 +108,7 @@ export default function FormRegularField< Item >( {
( {
isValid: true,
- message: '',
+ errorMessage: '',
} ),
- validateWhenDirty: false,
+ showErrorOnlyWhenDirty: true,
},
};
}
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 22b492b104d84c..5c66fbff405c90 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -184,6 +184,7 @@ export type DataFormControlProps< Item > = {
field: NormalizedField< Item >;
onChange: ( value: Record< string, any > ) => void;
hideLabelFromVision?: boolean;
+ errorMessage: string | undefined;
};
export type DataViewRenderFieldProps< Item > = {
@@ -552,18 +553,20 @@ export type CombinedFormField = {
children: Array< FormField | string >;
} & { validation: FormFieldValidation };
+export type ValidationResult = {
+ isValid: boolean;
+ errorMessage: string | undefined;
+};
+
export type FormFieldValidation = {
/**
- * The validation message.
+ * The validation should be triggered only when the field is dirty.
*/
- validateWhenDirty: boolean;
+ showErrorOnlyWhenDirty: boolean;
/**
* The validation function.
*/
- callback: () => {
- isValid: boolean;
- message: string;
- };
+ callback: ( data: any ) => ValidationResult;
};
export type FormField = SimpleFormField | CombinedFormField;
@@ -574,6 +577,11 @@ export type Form = {
type?: 'regular' | 'panel';
fields?: Array< FormField | string >;
labelPosition?: 'side' | 'top' | 'none';
+ touchedFields: string[];
+ messageErrors: Record< string, string | undefined >;
+ setTouchedFields: ( touchedFields: string[] ) => void;
+ setErrors: ( field: string, error: string | undefined ) => void;
+ isFormValid: ( data: Record< string, any > ) => boolean;
};
export interface DataFormProps< Item > {
@@ -588,4 +596,5 @@ export interface FieldLayoutProps< Item > {
field: FormField;
onChange: ( value: any ) => void;
hideLabelFromVision?: boolean;
+ errorMessage: string | undefined;
}
diff --git a/packages/fields/src/actions/duplicate-post.tsx b/packages/fields/src/actions/duplicate-post.tsx
index fd7e0ae9de4ad1..b66fba37500379 100644
--- a/packages/fields/src/actions/duplicate-post.tsx
+++ b/packages/fields/src/actions/duplicate-post.tsx
@@ -23,9 +23,6 @@ import type { BasePost, CoreDataError } from '../types';
import { getItemTitle } from './utils';
const fields = [ titleField ];
-const formDuplicateAction = {
- fields: [ 'title' ],
-};
const duplicatePost: Action< BasePost > = {
id: 'duplicate-post',
@@ -139,7 +136,8 @@ const duplicatePost: Action< BasePost > = {
setItem( ( prev ) => ( {
...prev,
diff --git a/packages/fields/src/actions/reorder-page.tsx b/packages/fields/src/actions/reorder-page.tsx
index 1820884d8d8c73..068793d1ed8b83 100644
--- a/packages/fields/src/actions/reorder-page.tsx
+++ b/packages/fields/src/actions/reorder-page.tsx
@@ -39,7 +39,7 @@ function ReorderModal( {
async function onOrder( event: React.FormEvent ) {
event.preventDefault();
-
+ // @ts-ignore
if ( ! isItemValid( item, fields, formOrderAction ) ) {
return;
}
@@ -68,6 +68,7 @@ function ReorderModal( {
} );
}
}
+ // @ts-ignore
const isSaveDisabled = ! isItemValid( item, fields, formOrderAction );
return (