Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Internal

- Validated form controls: Add support for async validation. This is a breaking API change that splits the `customValidator` prop into an `onValidate` callback and a `customValidityMessage` object. ([#71184](https://github.com/WordPress/gutenberg/pull/71184)).

## 30.1.0 (2025-08-07)

### Enhancement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type Value = CheckboxControlProps[ 'checked' ];
const UnforwardedValidatedCheckboxControl = (
{
required,
customValidator,
onValidate,
customValidityMessage,
onChange,
markWhenOptional,
...restProps
Expand All @@ -37,9 +38,10 @@ const UnforwardedValidatedCheckboxControl = (
required={ required }
markWhenOptional={ markWhenOptional }
ref={ mergedRefs }
customValidator={ () => {
return customValidator?.( valueRef.current );
onValidate={ () => {
return onValidate?.( valueRef.current );
} }
customValidityMessage={ customValidityMessage }
getValidityTarget={ () =>
validityTargetRef.current?.querySelector< HTMLInputElement >(
'input[type="checkbox"]'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type Value = ComboboxControlProps[ 'value' ];
const UnforwardedValidatedComboboxControl = (
{
required,
customValidator,
onValidate,
customValidityMessage,
onChange,
markWhenOptional,
...restProps
Expand Down Expand Up @@ -50,9 +51,10 @@ const UnforwardedValidatedComboboxControl = (
required={ required }
markWhenOptional={ markWhenOptional }
ref={ mergedRefs }
customValidator={ () => {
return customValidator?.( valueRef.current );
onValidate={ () => {
return onValidate?.( valueRef.current );
} }
customValidityMessage={ customValidityMessage }
getValidityTarget={ () =>
validityTargetRef.current?.querySelector< HTMLInputElement >(
'input[role="combobox"]'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ type Value = CustomSelectControlProps[ 'value' ];
const UnforwardedValidatedCustomSelectControl = (
{
required,
customValidator,
onValidate,
customValidityMessage,
onChange,
markWhenOptional,
...restProps
Expand All @@ -43,9 +44,10 @@ const UnforwardedValidatedCustomSelectControl = (
<ControlWithError
required={ required }
markWhenOptional={ markWhenOptional }
customValidator={ () => {
return customValidator?.( valueRef.current );
onValidate={ () => {
return onValidate?.( valueRef.current );
} }
customValidityMessage={ customValidityMessage }
getValidityTarget={ () => validityTargetRef.current }
>
<CustomSelectControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type Value = InputControlProps[ 'value' ];
const UnforwardedValidatedInputControl = (
{
required,
customValidator,
onValidate,
customValidityMessage,
onChange,
markWhenOptional,
...restProps
Expand All @@ -36,9 +37,10 @@ const UnforwardedValidatedInputControl = (
<ControlWithError
required={ required }
markWhenOptional={ markWhenOptional }
customValidator={ () => {
return customValidator?.( valueRef.current );
onValidate={ () => {
return onValidate?.( valueRef.current );
} }
customValidityMessage={ customValidityMessage }
getValidityTarget={ () => validityTargetRef.current }
>
<InputControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type Value = NumberControlProps[ 'value' ];
const UnforwardedValidatedNumberControl = (
{
required,
customValidator,
onValidate,
customValidityMessage,
onChange,
markWhenOptional,
...restProps
Expand All @@ -36,9 +37,10 @@ const UnforwardedValidatedNumberControl = (
<ControlWithError
required={ required }
markWhenOptional={ markWhenOptional }
customValidator={ () => {
return customValidator?.( valueRef.current );
onValidate={ () => {
return onValidate?.( valueRef.current );
} }
customValidityMessage={ customValidityMessage }
getValidityTarget={ () => validityTargetRef.current }
>
<NumberControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type Value = RadioControlProps[ 'selected' ];
const UnforwardedValidatedRadioControl = (
{
required,
customValidator,
onValidate,
customValidityMessage,
onChange,
markWhenOptional,
...restProps
Expand All @@ -35,9 +36,10 @@ const UnforwardedValidatedRadioControl = (
markWhenOptional={ markWhenOptional }
// TODO: Upstream limitation - RadioControl does not accept a ref.
ref={ mergedRefs }
customValidator={ () => {
return customValidator?.( valueRef.current );
onValidate={ () => {
return onValidate?.( valueRef.current );
} }
customValidityMessage={ customValidityMessage }
getValidityTarget={ () =>
validityTargetRef.current?.querySelector< HTMLInputElement >(
'input[type="radio"]'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type Value = RangeControlProps[ 'value' ];
const UnforwardedValidatedRangeControl = (
{
required,
customValidator,
onValidate,
customValidityMessage,
onChange,
markWhenOptional,
...restProps
Expand All @@ -36,9 +37,10 @@ const UnforwardedValidatedRangeControl = (
<ControlWithError
required={ required }
markWhenOptional={ markWhenOptional }
customValidator={ () => {
return customValidator?.( valueRef.current );
onValidate={ () => {
return onValidate?.( valueRef.current );
} }
customValidityMessage={ customValidityMessage }
getValidityTarget={ () => validityTargetRef.current }
>
<RangeControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ type Value = SelectControlProps[ 'value' ];
const UnforwardedValidatedSelectControl = (
{
required,
customValidator,
onValidate,
customValidityMessage,
onChange,
markWhenOptional,
...restProps
Expand All @@ -51,9 +52,10 @@ const UnforwardedValidatedSelectControl = (
<ControlWithError
required={ required }
markWhenOptional={ markWhenOptional }
customValidator={ () => {
return customValidator?.( valueRef.current );
onValidate={ () => {
return onValidate?.( valueRef.current );
} }
customValidityMessage={ customValidityMessage }
getValidityTarget={ () => validityTargetRef.current }
>
<SelectControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export default meta;
export const Default: StoryObj< typeof ValidatedCheckboxControl > = {
render: function Template( { onChange, ...args } ) {
const [ checked, setChecked ] = useState( false );
const [ customValidityMessage, setCustomValidityMessage ] =
useState<
React.ComponentProps<
typeof ValidatedCheckboxControl
>[ 'customValidityMessage' ]
>( undefined );

return (
<ValidatedCheckboxControl
Expand All @@ -40,6 +46,17 @@ export const Default: StoryObj< typeof ValidatedCheckboxControl > = {
setChecked( value );
onChange?.( value );
} }
onValidate={ ( value ) => {
if ( value ) {
setCustomValidityMessage( {
type: 'invalid',
message: 'This checkbox may not be checked.',
} );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming feels a bit off - we're "setting a validity message" but it's an object which also includes "message" and "type". Should we just call it "validity" instead of "validity message"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes a lot of sense, plus it's much shorter! 🙈 Will do that.

} else {
setCustomValidityMessage( undefined );
}
} }
customValidityMessage={ customValidityMessage }
/>
);
},
Expand All @@ -48,10 +65,4 @@ Default.args = {
required: true,
label: 'Checkbox',
help: 'This checkbox may neither be checked nor unchecked.',
customValidator: ( value ) => {
if ( value ) {
return 'This checkbox may not be checked.';
}
return undefined;
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export const Default: StoryObj< typeof ValidatedComboboxControl > = {
typeof ValidatedComboboxControl
>[ 'value' ]
>();
const [ customValidityMessage, setCustomValidityMessage ] =
useState<
React.ComponentProps<
typeof ValidatedComboboxControl
>[ 'customValidityMessage' ]
>( undefined );

return (
<ValidatedComboboxControl
Expand All @@ -43,6 +49,17 @@ export const Default: StoryObj< typeof ValidatedComboboxControl > = {
setValue( newValue );
onChange?.( newValue );
} }
onValidate={ ( v ) => {
if ( v === 'a' ) {
setCustomValidityMessage( {
type: 'invalid',
message: 'Option A is not allowed.',
} );
} else {
setCustomValidityMessage( undefined );
}
} }
customValidityMessage={ customValidityMessage }
/>
);
},
Expand All @@ -55,10 +72,4 @@ Default.args = {
{ value: 'a', label: 'Option A (not allowed)' },
{ value: 'b', label: 'Option B' },
],
customValidator: ( value ) => {
if ( value === 'a' ) {
return 'Option A is not allowed.';
}
return undefined;
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export const Default: StoryObj< typeof ValidatedCustomSelectControl > = {
typeof ValidatedCustomSelectControl
>[ 'value' ]
>();
const [ customValidityMessage, setCustomValidityMessage ] =
useState<
React.ComponentProps<
typeof ValidatedCustomSelectControl
>[ 'customValidityMessage' ]
>( undefined );

return (
<ValidatedCustomSelectControl
Expand All @@ -43,6 +49,17 @@ export const Default: StoryObj< typeof ValidatedCustomSelectControl > = {
setValue( newValue.selectedItem );
onChange?.( newValue );
} }
onValidate={ ( v ) => {
if ( v?.key === 'a' ) {
setCustomValidityMessage( {
type: 'invalid',
message: 'Option A is not allowed.',
} );
} else {
setCustomValidityMessage( undefined );
}
} }
customValidityMessage={ customValidityMessage }
/>
);
},
Expand All @@ -55,10 +72,4 @@ Default.args = {
{ key: 'a', name: 'Option A (not allowed)' },
{ key: 'b', name: 'Option B' },
],
customValidator: ( value ) => {
if ( value?.key === 'a' ) {
return 'Option A is not allowed.';
}
return undefined;
},
};
Loading