Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FormTokenField: add ability to auto select first matching suggestion for incomplete token #42527

Merged

Conversation

dinhtungdu
Copy link
Contributor

@dinhtungdu dinhtungdu commented Jul 19, 2022

What?

This PR adds a new experimental prop to <FormTokenField> allows users to select the first matching suggestion for an incomplete token by pressing the Enter key. This is meant to use in tandem with __experimentalValidateInput.

Why?

This is useful when <FormTokenField> is used as a dropdown selector. Users don't need to type the full value of the suggestion to select it with the Enter key.

For example, given a component with ['blue', 'red', 'green'] as the suggestion array, users can select blue by typing b or bl only and then press Enter.

How?

Testing Instructions

  1. In the storybook, navigate to FormTokenField > Dropdown Selector story.
  2. Type an then press Enter key.
  3. Antarctica should be selected.

Screenshots or screencast

https://user-images.githubusercontent.com/5423135/179729142-c424170b-4eaf-4904-931c-2687633f1413.mov

Updated screencast

Screen.Recording.2022-07-25.at.09.47.14.mov

Changelog

FormTokenField: add __experimentalAutoSelectFirstMatch prop to auto select the first matching suggestion on typing.

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Hey @dinhtungdu , thank you for opening this PR!

My main worry is about the UX — the user has no (visual) indication that the first matching suggestion is going to be automatically selected even if the user hasn't selected it explicitly.

Is there a particular reason for adding this functionality?

if (
matchingSuggestions.length > 0 &&
incompleteTokenValue.length > 0 &&
typeof __experimentalValidateInput !== 'undefined' &&
Copy link
Contributor

Choose a reason for hiding this comment

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

The __experimentalValidateInput prop is assigned a default value of () => true, and therefore this check will always be true.

I also don't understand the rationale for tying this new functionality to the __experimentalValidateInput — why is that prop necessary?

Copy link
Contributor Author

@dinhtungdu dinhtungdu Jul 20, 2022

Choose a reason for hiding this comment

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

Thanks for the review! I fixed the condition in a796d58.

I also don't understand the rationale for tying this new functionality to the __experimentalValidateInput — why is that prop necessary?

This is needed because if no validation is passed to __experimentalValidateInput, any string can be tokenized by pressing Enter key (or space if tokenizeOnSpace is true). But you're right, we don't need that check because addNewToken() won't be executed if a matched suggestion is found. I deleted the unnecessary condition in 25052bd.

@ciampo ciampo changed the title Add: ability to auto select first matching suggestion for incomplete token FormTokenField: add ability to auto select first matching suggestion for incomplete token Jul 20, 2022
@ciampo ciampo requested a review from chad1008 July 20, 2022 10:51
Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

I've posted a few more comments, but my main question about the utility of this new feature is still unaddressed:

My main worry is about the UX — the user has no (visual) indication that the first matching suggestion is going to be automatically selected even if the user hasn't selected it explicitly.

Is there a particular reason for adding this functionality?

Could you elaborate a bit more, please? Thank you!

@dinhtungdu
Copy link
Contributor Author

Is there a particular reason for adding this functionality?

Could you elaborate a bit more, please? Thank you!

@ciampo I'm sorry for the late reply. I updated the PR to remove __experimentalValidateInput references.

The reason for this functionality is when FormTokenField is used as a dropdown selector, we want to choose a token from the list only. It's handy for users to press enter to select an option. This behavior is similar to Select2 (https://select2.org/getting-started/basic-usage).

the user has no (visual) indication that the first matching suggestion is going to be automatically selected

You're right. I will add a visual indicator to the first matching suggestion to make the behavior predictable to users.

@dinhtungdu dinhtungdu requested a review from ciampo July 22, 2022 00:42
@dinhtungdu
Copy link
Contributor Author

@ciampo in c355596, I updated the component to auto-select the first matching suggestion when user type 2 or more characters in the input (I use inputHasMinimumChars). Can you take another look at this?

@jameskoster
Copy link
Contributor

Yeah I think it's fairly important for there to be a visual indication that pressing Enter will select the item. The current behaviour seems fairly intuitive to me:

keyboard.mp4

Saving a keypress doesn't really seem worth the potential confusing behaviour.

@jameskoster jameskoster requested a review from a team July 22, 2022 14:39
@Aljullu
Copy link
Contributor

Aljullu commented Jul 22, 2022

Thanks @dinhtungdu for creating the PR and @ciampo and @jameskoster for taking a look and providing feedback.

I would like to add some more context on why we would like to introduce this new behavior to the FormTokenField component:

In WC Blocks, we are working on using FormTokenField in the frontend as part of the Filter Products by Attribute block (woocommerce/woocommerce-blocks#6647). We don't allow the shopper to introduce values that don't match one of the available options (we are using __experimentalValidateInput to achieve that). Right now, it feels a bit frustrating that the shopper needs to introduce the attribute name in the same way it appears in the options even though in many cases introducing a short part of it would be enough.

As an example, imagine a filter that allows selecting the memory of USB sticks. Ideally, the user should be able to type 64 and then press Enter so the 64 GB option is selected:

Kooha-07-22-2022-17-41-58.mp4

However, that behavior is not currently possible with FormTokenField. That's specially important when the options are long or use special characters.

I understand this behavior might not be necessary in some use-cases for the FormTokenField, but I do think it's a common pattern in many other apps. A couple of examples that come to my mind are URL bars in browsers and Slack apps tooltip:

Browsers auto-select the first option in the URL bar Slack select the first app when typing /
imatge imatge

I agree, however, that having a visual indication that the first option is selected would be advisable. Maybe that could be achieved adding the same styles as :hover options?

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Hey @Aljullu , I understand your case much better now!

We don't allow the shopper to introduce values that don't match one of the available options (we are using __experimentalValidateInput to achieve that).

Together with highlighting the first option, I actually think that this is another key aspect of the UX around this feature — I now understand a bit more why @dinhtungdu initially mentioned __experimentalValidateInput in relationship to this feature.

I almost wonder if, when __experimentalAutoSelectFirstMatch is enabled, we should always check for the token to be in the list of suggestions?

diff --git a/packages/components/src/form-token-field/index.tsx b/packages/components/src/form-token-field/index.tsx
index 01b05d7916..ac9b3bab92 100644
--- a/packages/components/src/form-token-field/index.tsx
+++ b/packages/components/src/form-token-field/index.tsx
@@ -423,7 +423,11 @@ export function FormTokenField( props: FormTokenFieldProps ) {
 	}
 
 	function addNewToken( token: string ) {
-		if ( ! __experimentalValidateInput( token ) ) {
+		if (
+			! __experimentalValidateInput( token ) ||
+			( __experimentalAutoSelectFirstMatch &&
+				! suggestions.includes( token ) )
+		) {
 			speak( messages.__experimentalInvalid, 'assertive' );
 			return;
 		}

Or, at least, suggest to implement __experimentalValidateInput accordingly

@dinhtungdu
Copy link
Contributor Author

dinhtungdu commented Jul 25, 2022

I agree, however, that having a visual indication that the first option is selected would be advisable. Maybe that could be achieved adding the same styles as :hover options?

@Aljullu @jameskoster I addressed it in #42527 (comment). I'm sorry I didn't record a new screencast. I updated the PR description with the new screencast, (crossposting here for your convenience).

Screen.Recording.2022-07-25.at.09.47.14.mov

I almost wonder if, when __experimentalAutoSelectFirstMatch is enabled, we should always check for the token to be in the list of suggestions?

@ciampo I did this initially. But now I think we shouldn't do this for the most flexibility. When __experimentalAutoSelectFirstMatch is enabled, the first match (if it exists) will be selected automatically, before the logic to add an incomplete token is executed. Users can validate the token using __experimentalValidateInput if needed:

  1. Users type the keyword
    2a. If there are matches, the first match is selected automatically on typing. Users press Enter to select the match.
    2b. If there is no match, the input is tokenized or not based on the __experimentalValidateInput.

@jameskoster
Copy link
Contributor

This looks much better to the eye – If the first result has focus then pressing Enter to select it seems fine to me. I do wonder about the a11y implications though.

@jameskoster jameskoster added the Needs Accessibility Feedback Need input from accessibility label Jul 25, 2022
@ciampo
Copy link
Contributor

ciampo commented Jul 25, 2022

This looks much better to the eye – If the first result has focus then pressing Enter to select it seems fine to me. I do wonder about the a11y implications though.

Looking at the code changes, I don't think that this PR introduced any regressions, as it uses existing internal functions like setSelectedSuggestionIndex. Having said that, it's completely possible that this component could do better under the a11y point of view (regardless of the changes included in this PR)

@Aljullu
Copy link
Contributor

Aljullu commented Aug 10, 2022

@jameskoster @ciampo is there anything we can do to move this PR forward?

@ciampo
Copy link
Contributor

ciampo commented Aug 10, 2022

@jameskoster @ciampo is there anything we can do to move this PR forward?

I'll be having another look at this PR soon. Unfortunately I've been very busy lately and my response time on PR reviews have become considerably longer.

@Aljullu
Copy link
Contributor

Aljullu commented Aug 10, 2022

No problem! Thanks @ciampo!

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Code changes are looking good.

Before the final approval, let's rebase on top of latest trunk, since this PR has been open for some time and there are merge conflicts.

It would also be good to add a unit test for the expected behavior when __experimentalAutoSelectFirstMatch is used

Something like this (looks like this test is still written using `react-dom`, even though we're in the process of migrating our tests to RTL
diff --git a/packages/components/src/form-token-field/test/index.js b/packages/components/src/form-token-field/test/index.js
index 3be06711fc..10f6e3270c 100644
--- a/packages/components/src/form-token-field/test/index.js
+++ b/packages/components/src/form-token-field/test/index.js
@@ -396,6 +396,35 @@ describe( 'FormTokenField', () => {
 			sendKeyDown( keyCodes.enter );
 			expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar' ] );
 		} );
+
+		it( 'should automatically select the first matching suggestions when __experimentalAutoSelectFirstMatch is set to true', () => {
+			setUp( { __experimentalAutoSelectFirstMatch: true } );
+			wrapper.setState( {
+				isExpanded: true,
+			} );
+			expect( getSuggestionsText() ).toEqual( [] );
+
+			const searchText = 'so';
+
+			act( () => {
+				setText( searchText );
+			} );
+
+			const expectedFirstMatchTokens =
+				fixtures.matchingSuggestions[ searchText ][ 0 ];
+
+			expect( getSelectedSuggestion() ).toEqual(
+				expectedFirstMatchTokens
+			);
+
+			sendKeyDown( keyCodes.enter );
+
+			expect( wrapper.state.tokens ).toEqual( [
+				'foo',
+				'bar',
+				expectedFirstMatchTokens.join( '' ),
+			] );
+		} );
 	} );
 
 	describe( 'removing tokens', () => {

@dinhtungdu dinhtungdu requested a review from ciampo August 16, 2022 10:41
@dinhtungdu
Copy link
Contributor Author

@ciampo I added the unit test per your suggestion. Thank you so much! Can you take another look at this? The failed React Native E2E test is unrelated, I don't have permission to rerun it.

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

LGTM, thank you for your patience on this one!

Can you just:

  • rebase on top of latest trunk
  • add a CHANGELOG entry under the ### Experimental section ?

I'll merge when that's done

@dinhtungdu
Copy link
Contributor Author

dinhtungdu commented Aug 19, 2022

@ciampo I update my branch with latest trunk.

  • add a CHANGELOG entry under the ### Experimental section ?

I don't see the ### Experimental section. I guess you meant the screenshot section, so I added the changelog entry to the end of the PR description.

@ciampo
Copy link
Contributor

ciampo commented Aug 19, 2022

Apologies if my suggestion wasn't clear enough — I meant the @wordpress/components package's CHANGELOG:

diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index d2da776b33..854c823db4 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -34,6 +34,10 @@
 -   `ColorPalette`: Refactor away from `_.uniq()` ([#43330](https://github.com/WordPress/gutenberg/pull/43330/)).
 -   `Guide`: Refactor away from `_.times()` ([#43374](https://github.com/WordPress/gutenberg/pull/43374/)).
 
+### Experimental
+
+-   `FormTokenField`: add `__experimentalAutoSelectFirstMatch` prop to auto select the first matching suggestion on typing ([#42527](https://github.com/WordPress/gutenberg/pull/42527/)).
+
 ## 19.17.0 (2022-08-10)
 
 ### Bug Fix

@dinhtungdu
Copy link
Contributor Author

@ciampo Thanks for your clarification! I added the changelog entry and update the PR with the latest trunk again.

@ciampo
Copy link
Contributor

ciampo commented Aug 19, 2022

@ciampo Thanks for your clarification! I added the changelog entry and update the PR with the latest trunk again.

Awesome! I'll merge when CI is ✅

Thank you again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Accessibility Feedback Need input from accessibility [Package] Components /packages/components [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants