From 9308a3012c2420c966bb40872338f8b285fc61ed Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 28 Aug 2023 12:11:45 +1200 Subject: [PATCH 1/8] Add option to add tokens on blur --- packages/components/CHANGELOG.md | 4 ++++ packages/components/src/form-token-field/README.md | 1 + packages/components/src/form-token-field/index.tsx | 12 ++++++++---- packages/components/src/form-token-field/types.ts | 6 ++++++ .../patterns/src/components/category-selector.js | 1 + 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 3b9682c4adf4c6..b6a62225e51677 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## Enhancement + +- `FormTokenField`: Add `__experimentalAddOnBlur` prop to add any incompleteTokenValue as a new token when field loses focus. + ### Bug Fix - `SandBox`: Fix the cleanup method in useEffect ([#53796](https://github.com/WordPress/gutenberg/pull/53796)). diff --git a/packages/components/src/form-token-field/README.md b/packages/components/src/form-token-field/README.md index 90fe84e56f2ee6..238b0eba8560a0 100644 --- a/packages/components/src/form-token-field/README.md +++ b/packages/components/src/form-token-field/README.md @@ -62,6 +62,7 @@ The `value` property is handled in a manner similar to controlled form component - `__experimentalValidateInput` - If passed, all introduced values will be validated before being added as tokens. - `__experimentalAutoSelectFirstMatch` - If true, the select the first matching suggestion when the user presses the Enter key (or space when tokenizeOnSpace is true). - `__nextHasNoMarginBottom` - Start opting into the new margin-free styles that will become the default in a future version, currently scheduled to be WordPress 6.5. (The prop can be safely removed once this happens.) +- `__experimentalAddOnBlur` - If true adds any incompleteTokenValue as a new token when field loses focus. ## Usage diff --git a/packages/components/src/form-token-field/index.tsx b/packages/components/src/form-token-field/index.tsx index e3781880370628..ba4d13fd75ff67 100644 --- a/packages/components/src/form-token-field/index.tsx +++ b/packages/components/src/form-token-field/index.tsx @@ -73,6 +73,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { __next40pxDefaultSize = false, __experimentalAutoSelectFirstMatch = false, __nextHasNoMarginBottom = false, + __experimentalAddOnBlur = false, } = useDeprecated36pxDefaultSizeProp< FormTokenFieldProps >( props, 'wp.components.FormTokenField' @@ -167,6 +168,9 @@ export function FormTokenField( props: FormTokenFieldProps ) { __experimentalValidateInput( incompleteTokenValue ) ) { setIsActive( false ); + if ( __experimentalAddOnBlur ) { + addCurrentToken( true ); + } } else { // Reset to initial state setIncompleteTokenValue( '' ); @@ -406,7 +410,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { } } - function addCurrentToken() { + function addCurrentToken( isOnBlur = false ) { let preventDefault = false; const selectedSuggestion = getSelectedSuggestion(); @@ -414,7 +418,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { addNewToken( selectedSuggestion ); preventDefault = true; } else if ( inputHasValidValue() ) { - addNewToken( incompleteTokenValue ); + addNewToken( incompleteTokenValue, isOnBlur ); preventDefault = true; } @@ -438,7 +442,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { } } - function addNewToken( token: string ) { + function addNewToken( token: string, isOnBlur: boolean = false ) { if ( ! __experimentalValidateInput( token ) ) { speak( messages.__experimentalInvalid, 'assertive' ); return; @@ -451,7 +455,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { setSelectedSuggestionScroll( false ); setIsExpanded( ! __experimentalExpandOnFocus ); - if ( isActive ) { + if ( isActive && ! isOnBlur ) { focus(); } } diff --git a/packages/components/src/form-token-field/types.ts b/packages/components/src/form-token-field/types.ts index fe466ffbe59f84..690c2803d53ae8 100644 --- a/packages/components/src/form-token-field/types.ts +++ b/packages/components/src/form-token-field/types.ts @@ -182,6 +182,12 @@ export interface FormTokenFieldProps * @default false */ __nextHasNoMarginBottom?: boolean; + /** + * If true add any incompleteTokenValue as a new token. + * + * @default false + */ + __experimentalAddOnBlur?: boolean; } /** diff --git a/packages/patterns/src/components/category-selector.js b/packages/patterns/src/components/category-selector.js index f8295af2a79dc9..0f469a8f27c6ca 100644 --- a/packages/patterns/src/components/category-selector.js +++ b/packages/patterns/src/components/category-selector.js @@ -111,6 +111,7 @@ export default function CategorySelector( { onCategorySelection } ) { onInputChange={ debouncedSearch } maxSuggestions={ MAX_TERMS_SUGGESTIONS } label={ __( 'Categories' ) } + __experimentalAddOnBlur={ true } /> ); From bf5daa2a1f8dc0d13e02f244a5499befd842588a Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 28 Aug 2023 12:26:57 +1200 Subject: [PATCH 2/8] Add link to PR to changelog --- packages/components/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index b6a62225e51677..aeab617b52a300 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,7 +4,7 @@ ## Enhancement -- `FormTokenField`: Add `__experimentalAddOnBlur` prop to add any incompleteTokenValue as a new token when field loses focus. +- `FormTokenField`: Add `__experimentalAddOnBlur` prop to add any incompleteTokenValue as a new token when field loses focus ([#53976](https://github.com/WordPress/gutenberg/pull/53976)). ### Bug Fix From 4e7fe832fa82aabd5e305ae8a7645e6405590806 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 28 Aug 2023 12:30:10 +1200 Subject: [PATCH 3/8] Fix changelog --- packages/components/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index aeab617b52a300..695df57c625776 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## Enhancement +### Enhancement - `FormTokenField`: Add `__experimentalAddOnBlur` prop to add any incompleteTokenValue as a new token when field loses focus ([#53976](https://github.com/WordPress/gutenberg/pull/53976)). From 0c1694df6f58020c8356500063cc27a6779b9f6b Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 4 Sep 2023 17:08:03 +1200 Subject: [PATCH 4/8] Change name of new prop to tokenizeOnBlur --- packages/components/CHANGELOG.md | 2 +- packages/components/src/form-token-field/README.md | 2 +- packages/components/src/form-token-field/index.tsx | 4 ++-- packages/components/src/form-token-field/types.ts | 2 +- packages/patterns/src/components/category-selector.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 695df57c625776..e200416e29e0ba 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,7 +4,7 @@ ### Enhancement -- `FormTokenField`: Add `__experimentalAddOnBlur` prop to add any incompleteTokenValue as a new token when field loses focus ([#53976](https://github.com/WordPress/gutenberg/pull/53976)). +- `FormTokenField`: Add `tokenizeOnBlur` prop to add any incompleteTokenValue as a new token when field loses focus ([#53976](https://github.com/WordPress/gutenberg/pull/53976)). ### Bug Fix diff --git a/packages/components/src/form-token-field/README.md b/packages/components/src/form-token-field/README.md index 238b0eba8560a0..b442e42c313ffd 100644 --- a/packages/components/src/form-token-field/README.md +++ b/packages/components/src/form-token-field/README.md @@ -62,7 +62,7 @@ The `value` property is handled in a manner similar to controlled form component - `__experimentalValidateInput` - If passed, all introduced values will be validated before being added as tokens. - `__experimentalAutoSelectFirstMatch` - If true, the select the first matching suggestion when the user presses the Enter key (or space when tokenizeOnSpace is true). - `__nextHasNoMarginBottom` - Start opting into the new margin-free styles that will become the default in a future version, currently scheduled to be WordPress 6.5. (The prop can be safely removed once this happens.) -- `__experimentalAddOnBlur` - If true adds any incompleteTokenValue as a new token when field loses focus. +- `tokenizeOnBlur` - If true adds any incompleteTokenValue as a new token when field loses focus. ## Usage diff --git a/packages/components/src/form-token-field/index.tsx b/packages/components/src/form-token-field/index.tsx index ba4d13fd75ff67..c66a7e7c1caa94 100644 --- a/packages/components/src/form-token-field/index.tsx +++ b/packages/components/src/form-token-field/index.tsx @@ -73,7 +73,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { __next40pxDefaultSize = false, __experimentalAutoSelectFirstMatch = false, __nextHasNoMarginBottom = false, - __experimentalAddOnBlur = false, + tokenizeOnBlur = false, } = useDeprecated36pxDefaultSizeProp< FormTokenFieldProps >( props, 'wp.components.FormTokenField' @@ -168,7 +168,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { __experimentalValidateInput( incompleteTokenValue ) ) { setIsActive( false ); - if ( __experimentalAddOnBlur ) { + if ( tokenizeOnBlur ) { addCurrentToken( true ); } } else { diff --git a/packages/components/src/form-token-field/types.ts b/packages/components/src/form-token-field/types.ts index 690c2803d53ae8..be34eadd1cffd6 100644 --- a/packages/components/src/form-token-field/types.ts +++ b/packages/components/src/form-token-field/types.ts @@ -187,7 +187,7 @@ export interface FormTokenFieldProps * * @default false */ - __experimentalAddOnBlur?: boolean; + tokenizeOnBlur?: boolean; } /** diff --git a/packages/patterns/src/components/category-selector.js b/packages/patterns/src/components/category-selector.js index 0f469a8f27c6ca..c9305806c7e13f 100644 --- a/packages/patterns/src/components/category-selector.js +++ b/packages/patterns/src/components/category-selector.js @@ -111,7 +111,7 @@ export default function CategorySelector( { onCategorySelection } ) { onInputChange={ debouncedSearch } maxSuggestions={ MAX_TERMS_SUGGESTIONS } label={ __( 'Categories' ) } - __experimentalAddOnBlur={ true } + tokenizeOnBlur={ true } /> ); From bf20870aec7677b9f194baf4f1fecd14f2c6ac1a Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 4 Sep 2023 17:25:22 +1200 Subject: [PATCH 5/8] Remove the isOnblur param and check for tokenizeOnBlur in addNewToken --- packages/components/src/form-token-field/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/src/form-token-field/index.tsx b/packages/components/src/form-token-field/index.tsx index c66a7e7c1caa94..b3a5c5a53d4c0a 100644 --- a/packages/components/src/form-token-field/index.tsx +++ b/packages/components/src/form-token-field/index.tsx @@ -168,8 +168,8 @@ export function FormTokenField( props: FormTokenFieldProps ) { __experimentalValidateInput( incompleteTokenValue ) ) { setIsActive( false ); - if ( tokenizeOnBlur ) { - addCurrentToken( true ); + if ( tokenizeOnBlur && inputHasValidValue() ) { + addNewToken( incompleteTokenValue ); } } else { // Reset to initial state @@ -410,7 +410,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { } } - function addCurrentToken( isOnBlur = false ) { + function addCurrentToken() { let preventDefault = false; const selectedSuggestion = getSelectedSuggestion(); @@ -418,7 +418,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { addNewToken( selectedSuggestion ); preventDefault = true; } else if ( inputHasValidValue() ) { - addNewToken( incompleteTokenValue, isOnBlur ); + addNewToken( incompleteTokenValue ); preventDefault = true; } @@ -442,7 +442,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { } } - function addNewToken( token: string, isOnBlur: boolean = false ) { + function addNewToken( token: string ) { if ( ! __experimentalValidateInput( token ) ) { speak( messages.__experimentalInvalid, 'assertive' ); return; @@ -455,7 +455,7 @@ export function FormTokenField( props: FormTokenFieldProps ) { setSelectedSuggestionScroll( false ); setIsExpanded( ! __experimentalExpandOnFocus ); - if ( isActive && ! isOnBlur ) { + if ( isActive && ! tokenizeOnBlur ) { focus(); } } From 15845cfde1e3190c4948ba7187b52d02fc51e92f Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Tue, 5 Sep 2023 10:18:50 +1200 Subject: [PATCH 6/8] Add unit test --- .../src/form-token-field/test/index.tsx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/components/src/form-token-field/test/index.tsx b/packages/components/src/form-token-field/test/index.tsx index e62e851f1cf226..81e61ea1864de8 100644 --- a/packages/components/src/form-token-field/test/index.tsx +++ b/packages/components/src/form-token-field/test/index.tsx @@ -9,6 +9,7 @@ import { within, getDefaultNormalizer, waitFor, + act, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { ComponentProps } from 'react'; @@ -205,6 +206,45 @@ describe( 'FormTokenField', () => { ] ); } ); + it( 'should add a token with the input value with onBlur when `tokenizeOnBlur` prop is `true`', async () => { + const user = userEvent.setup(); + + const onChangeSpy = jest.fn(); + + const { rerender } = render( + + ); + + const input = screen.getByRole( 'combobox' ); + + // Add 'grapefruit' token by typing it and check blur of field does not tokenize it. + await user.type( input, 'grapefruit' ); + act( () => { + input.blur(); + } ); + expect( onChangeSpy ).toHaveBeenCalledTimes( 0 ); + expectTokensNotToBeInTheDocument( [ 'grapefruit' ] ); + + rerender( + + ); + await user.clear( input ); + + // Add 'grapefruit' token by typing it and check blur of field tokenizes it. + await user.type( input, 'grapefruit' ); + + act( () => { + input.blur(); + } ); + expect( onChangeSpy ).toHaveBeenNthCalledWith( 1, [ + 'grapefruit', + ] ); + expectTokensToBeInTheDocument( [ 'grapefruit' ] ); + } ); + it( "should not add a token with the input's value when pressing the tab key", async () => { const user = userEvent.setup(); From f321e53737d6b4f6cf2a37d3e606bd023497d7f2 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Tue, 5 Sep 2023 13:58:28 +1200 Subject: [PATCH 7/8] Changes from review --- packages/components/src/form-token-field/README.md | 2 +- packages/components/src/form-token-field/test/index.tsx | 9 ++------- packages/components/src/form-token-field/types.ts | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/components/src/form-token-field/README.md b/packages/components/src/form-token-field/README.md index b442e42c313ffd..3a4ce2a143624a 100644 --- a/packages/components/src/form-token-field/README.md +++ b/packages/components/src/form-token-field/README.md @@ -62,7 +62,7 @@ The `value` property is handled in a manner similar to controlled form component - `__experimentalValidateInput` - If passed, all introduced values will be validated before being added as tokens. - `__experimentalAutoSelectFirstMatch` - If true, the select the first matching suggestion when the user presses the Enter key (or space when tokenizeOnSpace is true). - `__nextHasNoMarginBottom` - Start opting into the new margin-free styles that will become the default in a future version, currently scheduled to be WordPress 6.5. (The prop can be safely removed once this happens.) -- `tokenizeOnBlur` - If true adds any incompleteTokenValue as a new token when field loses focus. +- `tokenizeOnBlur` - If true, add any incompleteTokenValue as a new token when the field loses focus. ## Usage diff --git a/packages/components/src/form-token-field/test/index.tsx b/packages/components/src/form-token-field/test/index.tsx index 81e61ea1864de8..9780f0c0571558 100644 --- a/packages/components/src/form-token-field/test/index.tsx +++ b/packages/components/src/form-token-field/test/index.tsx @@ -9,7 +9,6 @@ import { within, getDefaultNormalizer, waitFor, - act, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { ComponentProps } from 'react'; @@ -219,9 +218,7 @@ describe( 'FormTokenField', () => { // Add 'grapefruit' token by typing it and check blur of field does not tokenize it. await user.type( input, 'grapefruit' ); - act( () => { - input.blur(); - } ); + await user.click( document.body ); expect( onChangeSpy ).toHaveBeenCalledTimes( 0 ); expectTokensNotToBeInTheDocument( [ 'grapefruit' ] ); @@ -236,9 +233,7 @@ describe( 'FormTokenField', () => { // Add 'grapefruit' token by typing it and check blur of field tokenizes it. await user.type( input, 'grapefruit' ); - act( () => { - input.blur(); - } ); + await user.click( document.body ); expect( onChangeSpy ).toHaveBeenNthCalledWith( 1, [ 'grapefruit', ] ); diff --git a/packages/components/src/form-token-field/types.ts b/packages/components/src/form-token-field/types.ts index be34eadd1cffd6..e343601106f411 100644 --- a/packages/components/src/form-token-field/types.ts +++ b/packages/components/src/form-token-field/types.ts @@ -183,7 +183,7 @@ export interface FormTokenFieldProps */ __nextHasNoMarginBottom?: boolean; /** - * If true add any incompleteTokenValue as a new token. + * If true, add any incompleteTokenValue as a new token when the field loses focus. * * @default false */ From 8cb9edba24066507a65b1b7a9f958c21a80b9828 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Tue, 5 Sep 2023 14:11:49 +1200 Subject: [PATCH 8/8] Update test description to reflect new prop --- packages/components/src/form-token-field/test/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/form-token-field/test/index.tsx b/packages/components/src/form-token-field/test/index.tsx index 9780f0c0571558..76e308d5993beb 100644 --- a/packages/components/src/form-token-field/test/index.tsx +++ b/packages/components/src/form-token-field/test/index.tsx @@ -240,7 +240,7 @@ describe( 'FormTokenField', () => { expectTokensToBeInTheDocument( [ 'grapefruit' ] ); } ); - it( "should not add a token with the input's value when pressing the tab key", async () => { + it( "should not add a token with the input's value when tokenizeOnBlur is not set and pressing the tab key", async () => { const user = userEvent.setup(); const onChangeSpy = jest.fn();