diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 018d8386c153e..b5d624606ea32 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { noop, startsWith } from 'lodash'; +import { noop, startsWith, pick, map } from 'lodash'; /** * WordPress dependencies @@ -83,6 +83,18 @@ import LinkControlSearchInput from './search-input'; * @property {boolean=} showInitialSuggestions Whether to present initial suggestions immediately. */ +/** + * Default settings configuration. + * + * @type {WPLinkControlSetting[]} + */ +const DEFAULT_SETTINGS = [ + { + id: 'opensInNewTab', + title: __( 'Open in New Tab' ), + }, +]; + /** * Renders a link control. A link control is a controlled input which maintains * a value associated with a link (HTML anchor element) and relevant settings @@ -92,7 +104,7 @@ import LinkControlSearchInput from './search-input'; */ function LinkControl( { value, - settings, + settings = DEFAULT_SETTINGS, onChange = noop, showInitialSuggestions, } ) { @@ -211,6 +223,19 @@ function LinkControl( { setIsEditingLink( false ); } + /** + * Given a selected suggestion, returns the merged next value. The merged + * value inherits only settings properties from the previous value. + * + * @param {WPLinkControlValue} suggestion Next value suggestion. + * + * @return {WPLinkControlValue} Merged next value. + */ + const getNextValue = ( suggestion ) => ( { + ...pick( value, map( settings, 'id' ) ), + ...suggestion, + } ); + // Effects const getSearchHandler = useCallback( ( val, args ) => { @@ -285,7 +310,7 @@ function LinkControl( { ) } suggestion={ suggestion } onClick={ () => { - onChange( { ...value, ...suggestion } ); + onChange( getNextValue( suggestion ) ); stopEditing(); } } isSelected={ index === selectedSuggestion } @@ -311,7 +336,7 @@ function LinkControl( { value={ inputValue } onChange={ onInputChange } onSelect={ ( suggestion ) => { - onChange( { ...value, ...suggestion } ); + onChange( getNextValue( suggestion ) ); stopEditing(); } } renderSuggestions={ renderSearchResults } diff --git a/packages/block-editor/src/components/link-control/settings-drawer.js b/packages/block-editor/src/components/link-control/settings-drawer.js index 0df253d11a1eb..2900779e96cc1 100644 --- a/packages/block-editor/src/components/link-control/settings-drawer.js +++ b/packages/block-editor/src/components/link-control/settings-drawer.js @@ -9,18 +9,7 @@ import { noop } from 'lodash'; import { __ } from '@wordpress/i18n'; import { ToggleControl } from '@wordpress/components'; -const defaultSettings = [ - { - id: 'opensInNewTab', - title: __( 'Open in New Tab' ), - }, -]; - -const LinkControlSettingsDrawer = ( { - value, - onChange = noop, - settings = defaultSettings, -} ) => { +const LinkControlSettingsDrawer = ( { value, onChange = noop, settings } ) => { if ( ! settings || ! settings.length ) { return null; } diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 65d7729c973dc..17b8fe59ddaa2 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -816,6 +816,54 @@ describe( 'Selecting links', () => { ); expect( isExpectedFocusTarget ).toBe( true ); } ); + + it( 'should update a selected link value', ( done ) => { + // Regression: Previously, the behavior of updating a value was to merge + // the previous value with the newly selected suggestion. If the keys + // between the two objects were not the same, it could wrongly leave + // lingering values from the previous value. + + const LinkControlConsumer = () => ( + { + expect( nextValue ).toEqual( { + url: 'https://example.com', + } ); + + done(); + } } + /> + ); + + act( () => { + render( , container ); + } ); + + // Toggle edit. + document + .querySelector( '.block-editor-link-control__search-item-action' ) + .click(); + + // Change value. + const form = container.querySelector( 'form' ); + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: 'https://example.com' }, + } ); + } ); + act( () => { + Simulate.keyDown( searchInput, { keyCode: ENTER } ); + } ); + act( () => { + Simulate.submit( form ); + } ); + } ); } ); describe( 'Addition Settings UI', () => {