Skip to content

Add support for ValidatedFormTokenField#71350

Merged
mirka merged 25 commits intoWordPress:trunkfrom
elazzabi:add/array-form-control
Sep 10, 2025
Merged

Add support for ValidatedFormTokenField#71350
mirka merged 25 commits intoWordPress:trunkfrom
elazzabi:add/array-form-control

Conversation

@elazzabi
Copy link
Contributor

@elazzabi elazzabi commented Aug 26, 2025

https://github.com/WordPress/gutenberg/blob/trunk/CONTRIBUTING.md -->

What?

This adds support for the ValidatedArrayControl

Why?

This add a validated control to be used in different parts of the admin, including the country selector.

How?

CleanShot.2025-08-26.at.10.47.25.mp4

Testing Instructions

  1. Get this PR locally
  2. Start Storybook in dev mode
  3. Visit ValidatedInputControl and scroll down to Array Control
  4. Play with the component

Testing Instructions for Keyboard

Screenshots or screencast

Included above

@elazzabi elazzabi requested a review from ajitbohra as a code owner August 26, 2025 09:53
@github-actions
Copy link

github-actions bot commented Aug 26, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: elazzabi <elazzabi@git.wordpress.org>
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@elazzabi
Copy link
Contributor Author

cc @mirka

Copy link
Member

@mirka mirka left a comment

Choose a reason for hiding this comment

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

Thanks for working on this! 🙏

customValidity={ customValidity }
getValidityTarget={ getInputElement }
>
<div ref={ formTokenFieldRef }>
Copy link
Member

@mirka mirka Aug 28, 2025

Choose a reason for hiding this comment

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

Certain props are passed down from ControlWithError to the inner control, so this simple div wrapper is not going to be sufficient. (I notice this wasn't mentioned anywhere in the documentation, so I'll add it at #71392.)

{ cloneElement( children, {
label: appendRequiredIndicator(
children.props.label,
required,
markWhenOptional
),
onChange,
required,
} ) }

As for the ref passing, we can now make changes to the @wordpress/components components directly (unlike when I was working on the other controls 😅), so I think it would be cleaner to enhance FormTokenField itself so it forwards a ref to the underlying input. Right now FormTokenField doesn't take a ref at all. We should also enhance it so the required prop on FormTokenFieldwould also be passed down to the underlying input. This would allow us to be less hacky in this implementation. Could you try that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the review!

I looked into this, and this component is somewhat more challenging than the others. Since the checks for the required field only verify the input's value (i.e., not the tokens), it would always trigger an error, even when the field contains tokens.

I made some changes to disable the default required checks, but I'm unsure if this is the best approach or if you have other suggestions I can try.

Thank you!

Copy link
Member

Choose a reason for hiding this comment

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

I see! This has happened on two other components (ToggleGroupControl and CustomSelectControl), and I worked around it by using a hidden "delegate" element. At the time I thought it was hacky, but I later discovered that at least one other library uses a similar technique for all their validated form components, so I guess it isn't that weird.

I think we could try a normal <input type="text"> delegate here, since the built-in error message for required ("Please fill out this field.") is generic enough for FormTokenField. A <select multiple> delegate would be a better match for our array data type, but the built-in error message ("Please select an item in the list.") doesn't fit cases where FormTokenField can accept new items generated by the user.

Basically you append a hidden <input value={ hasTokens ? "has value" : "" }> or something so the required check reflects the selected token state. Does that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha! Thank you 🙏 I pushed changes in b6c9b91, let me know how that looks

/**
* Internal dependencies
*/
import { ControlWithError } from '../control-with-error';
Copy link
Member

Choose a reason for hiding this comment

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

I noticed some Sass variable handling is wrong in the stylesheet, so I'll fix it in #71391. With that in mind though, I think we'll need some error styles for FormTokenField as well, similar to what we have for ComboboxControl.

// For ComboboxControl
.components-combobox-control__suggestions-container:has(
input:user-invalid
):not(:has([aria-expanded="true"])) {
border-color: $alert-red;
}

Copy link
Member

Choose a reason for hiding this comment

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

I pushed some styles we can use in abc76d7.

@elazzabi elazzabi changed the title Add support for ValidatedArrayControl Add support for ValidatedFormTokenField Aug 29, 2025
Copy link
Member

@mirka mirka left a comment

Choose a reason for hiding this comment

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

Looking good! It's working as expected once the changes to FormTokenField itself are reverted.

Copy link
Member

Choose a reason for hiding this comment

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

I think we can revert the changes here in FormTokenField, since we're going with the delegate now. Passing down the required prop is actually problematic now, because it creates a required input that can never be filled 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think only the required passing can be reverted, not the whole file. Done in ba0bd0f

Copy link
Member

Choose a reason for hiding this comment

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

I don't think the ref forwarding is strictly necessary for the functionality in this PR? It's more of an unrelated enhancement for FormTokenField at this point. No strong opinion though.

/**
* Internal dependencies
*/
import { ControlWithError } from '../control-with-error';
Copy link
Member

Choose a reason for hiding this comment

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

I pushed some styles we can use in abc76d7.

@elazzabi
Copy link
Contributor Author

elazzabi commented Sep 9, 2025

Looks good after the recent changes, thank you for your help @mirka !

CleanShot.2025-09-09.at.17.09.45.mp4

Copy link
Member

@mirka mirka left a comment

Choose a reason for hiding this comment

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

Thanks for all the follow-ups! Looks great 🚀

Copy link
Member

Choose a reason for hiding this comment

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

I don't think the ref forwarding is strictly necessary for the functionality in this PR? It's more of an unrelated enhancement for FormTokenField at this point. No strong opinion though.

@elazzabi
Copy link
Contributor Author

elazzabi commented Sep 10, 2025

Thank you, @mirka ! I agree, I pushed a change to remove the forwardref in 9494826 🙏

EDIT: Looks like I need at least these to make the testing pass 4603300

@mirka mirka merged commit 0732dcd into WordPress:trunk Sep 10, 2025
68 checks passed
@github-actions github-actions bot added this to the Gutenberg 21.7 milestone Sep 10, 2025
adamsilverstein pushed a commit to adamsilverstein/gutenberg that referenced this pull request Sep 11, 2025
* Add ValidatedArrayControl component

* Export ValidatedArrayControl component from validated-form-controls index file

* Add ArrayControl story for ValidatedArrayControl component with validation logic

* add changelog

* Rename UnforwardedValidatedArrayControl to UnforwardedValidatedFormTokenField

* Rename to form-token-field

* Correct changelog place

* Add a separate storybook page for the new component

* Update FormTokenFieldProps interface to include 'required' prop

* Refactor FormTokenField to use forwardRef and include 'required' prop

* Enhance FormTokenField validation logic and refactor refs management

* Replace HTML5 overriding with the delegate solution to match other components

* Move changelog to unreleased

* Add error styles

* Move the export down for alphabetical order

* revert changes in input-control story

* Remove unnecessary use of name attribute

* Revert passing required down

* Add __experimentalExpandOnFocus for discoverability

* Remove forward ref functionality

* Add forwardedRef to div

---------

Co-authored-by: elazzabi <elazzabi@git.wordpress.org>
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Components /packages/components [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants