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

Fix toggle new tab does not persist changes to text input in Link Control #50401

Closed
wants to merge 2 commits into from
Closed
Changes from all 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
141 changes: 54 additions & 87 deletions packages/block-editor/src/components/link-control/index.js
Original file line number Diff line number Diff line change
@@ -6,7 +6,8 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { Button, Spinner, Notice } from '@wordpress/components';
import { Button, Spinner, Notice, TextControl } from '@wordpress/components';
import { keyboardReturn } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
import { useRef, useState, useEffect } from '@wordpress/element';
import { focus } from '@wordpress/dom';
@@ -112,7 +113,6 @@ function LinkControl( {
settings = DEFAULT_LINK_SETTINGS,
onChange = noop,
onRemove,
onCancel,
noDirectEntry = false,
showSuggestions = true,
showInitialSuggestions,
@@ -136,18 +136,18 @@ function LinkControl( {
const textInputRef = useRef();
const isEndingEditWithFocus = useRef( false );

const [ settingsOpen, setSettingsOpen ] = useState( false );
const [ newValue, setNewValue ] = useState( value );

const [ internalUrlInputValue, setInternalUrlInputValue ] =
useInternalInputValue( value?.url || '' );
useInternalInputValue( newValue?.url || '' );

const [ internalTextInputValue, setInternalTextInputValue ] =
useInternalInputValue( value?.title || '' );
useInternalInputValue( newValue?.title || '' );

const [ isEditingLink, setIsEditingLink ] = useState(
forceIsEditingLink !== undefined
? forceIsEditingLink
: ! value || ! value.url
: ! newValue || ! newValue?.url
);

const { createPage, isCreatingPage, errorMessage } =
@@ -162,6 +162,11 @@ function LinkControl( {
}
}, [ forceIsEditingLink ] );

const newOnChangeForLinkControl = ( data ) => {
setNewValue( data );
onChange( data );
};

useEffect( () => {
// We don't auto focus into the Link UI on mount
// because otherwise using the keyboard to select text
@@ -192,8 +197,6 @@ function LinkControl( {
isEndingEditWithFocus.current = false;
}, [ isEditingLink, isCreatingPage ] );

const hasLinkValue = value?.url?.trim()?.length > 0;

/**
* Cancels editing state and marks that focus may need to be restored after
* the next render, if focus was within the wrapper when editing finished.
@@ -203,29 +206,23 @@ function LinkControl( {
wrapperNode.current.ownerDocument.activeElement
);

setSettingsOpen( false );
setIsEditingLink( false );
};

const handleSelectSuggestion = ( updatedValue ) => {
onChange( {
newOnChangeForLinkControl( {
...updatedValue,
title: internalTextInputValue || updatedValue?.title,
} );
stopEditing();
};

const handleSubmit = () => {
if (
currentUrlInputValue !== value?.url ||
internalTextInputValue !== value?.title
) {
onChange( {
...value,
url: currentUrlInputValue,
title: internalTextInputValue,
} );
}
newOnChangeForLinkControl( {
...newValue,
url: currentUrlInputValue,
title: internalTextInputValue,
} );
stopEditing();
};

@@ -240,44 +237,19 @@ function LinkControl( {
}
};

const resetInternalValues = () => {
setInternalUrlInputValue( value?.url );
setInternalTextInputValue( value?.title );
};

const handleCancel = ( event ) => {
event.preventDefault();
event.stopPropagation();

// Ensure that any unsubmitted input changes are reset.
resetInternalValues();

if ( hasLinkValue ) {
// If there is a link then exist editing mode and show preview.
stopEditing();
} else {
// If there is no link value, then remove the link entirely.
onRemove?.();
}

onCancel?.();
};

const currentUrlInputValue = propInputValue || internalUrlInputValue;

const currentInputIsEmpty = ! currentUrlInputValue?.trim()?.length;

const shownUnlinkControl =
onRemove && value && ! isEditingLink && ! isCreatingPage;
onRemove && newValue && ! isEditingLink && ! isCreatingPage;

const showSettings = !! settings?.length;
const showSettingsDrawer = !! settings?.length;

// Only show text control once a URL value has been committed
// and it isn't just empty whitespace.
// See https://github.com/WordPress/gutenberg/pull/33849/#issuecomment-932194927.
const showTextControl = hasLinkValue && hasTextControl;

const isEditing = ( isEditingLink || ! value ) && ! isCreatingPage;
const showTextControl = newValue?.url?.trim()?.length > 0 && hasTextControl;

return (
<div
@@ -291,16 +263,28 @@ function LinkControl( {
</div>
) }

{ isEditing && (
{ ( isEditingLink || ! newValue ) && ! isCreatingPage && (
<>
<div
className={ classnames( {
'block-editor-link-control__search-input-wrapper': true,
'has-text-control': showTextControl,
} ) }
>
{ showTextControl && (
<TextControl
__nextHasNoMarginBottom
ref={ textInputRef }
className="block-editor-link-control__field block-editor-link-control__text-content"
label="Text"
value={ internalTextInputValue }
onChange={ setInternalTextInputValue }
onKeyDown={ handleSubmitWithEnter }
/>
) }

<LinkControlSearchInput
currentLink={ value }
currentLink={ newValue }
className="block-editor-link-control__field block-editor-link-control__search-input"
placeholder={ searchInputPlaceholder }
value={ currentUrlInputValue }
@@ -317,7 +301,17 @@ function LinkControl( {
createSuggestionButtonText
}
useLabel={ showTextControl }
/>
>
<div className="block-editor-link-control__search-actions">
<Button
onClick={ handleSubmit }
label={ __( 'Submit' ) }
icon={ keyboardReturn }
className="block-editor-link-control__search-submit"
disabled={ currentInputIsEmpty } // Disallow submitting empty values.
/>
</div>
</LinkControlSearchInput>
</div>
{ errorMessage && (
<Notice
@@ -331,53 +325,26 @@ function LinkControl( {
</>
) }

{ value && ! isEditingLink && ! isCreatingPage && (
{ newValue && ! isEditingLink && ! isCreatingPage && (
<LinkPreview
key={ value?.url } // force remount when URL changes to avoid race conditions for rich previews
value={ value }
key={ newValue?.url } // force remount when URL changes to avoid race conditions for rich previews
value={ newValue }
onEditClick={ () => setIsEditingLink( true ) }
hasRichPreviews={ hasRichPreviews }
hasUnlinkControl={ shownUnlinkControl }
onRemove={ onRemove }
/>
) }

{ isEditing && (
{ showSettingsDrawer && (
<div className="block-editor-link-control__tools">
{ ( showSettings || showTextControl ) && (
<LinkControlSettingsDrawer
settingsOpen={ settingsOpen }
setSettingsOpen={ setSettingsOpen }
showTextControl={ showTextControl }
showSettings={ showSettings }
textInputRef={ textInputRef }
internalTextInputValue={ internalTextInputValue }
setInternalTextInputValue={
setInternalTextInputValue
}
handleSubmitWithEnter={ handleSubmitWithEnter }
value={ value }
settings={ settings }
onChange={ onChange }
/>
) }

<div className="block-editor-link-control__search-actions">
<Button
variant="primary"
onClick={ handleSubmit }
className="block-editor-link-control__search-submit"
disabled={ currentInputIsEmpty } // Disallow submitting empty values.
>
{ __( 'Apply' ) }
</Button>
<Button variant="tertiary" onClick={ handleCancel }>
{ __( 'Cancel' ) }
</Button>
</div>
<LinkControlSettingsDrawer
value={ newValue }
setNewValue={ setNewValue }
settings={ settings }
/>
</div>
) }

{ renderControlBottom && renderControlBottom() }
</div>
);
114 changes: 31 additions & 83 deletions packages/block-editor/src/components/link-control/settings-drawer.js
Original file line number Diff line number Diff line change
@@ -1,97 +1,45 @@
/**
* WordPress dependencies
*/
import {
Button,
TextControl,
__unstableMotion as motion,
__unstableAnimatePresence as AnimatePresence,
} from '@wordpress/components';
import { settings as settingsIcon } from '@wordpress/icons';
import { useReducedMotion, useInstanceId } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
import { Fragment } from '@wordpress/element';
import { ToggleControl, VisuallyHidden } from '@wordpress/components';

/**
* Internal dependencies
*/
import Settings from './settings';
const noop = () => {};

function LinkSettingsDrawer( {
settingsOpen,
setSettingsOpen,
showTextControl,
showSettings,
textInputRef,
internalTextInputValue,
setInternalTextInputValue,
handleSubmitWithEnter,
const LinkControlSettingsDrawer = ( {
value,
settings,
onChange,
} ) {
const prefersReducedMotion = useReducedMotion();
const MaybeAnimatePresence = prefersReducedMotion
? Fragment
: AnimatePresence;
const MaybeMotionDiv = prefersReducedMotion ? 'div' : motion.div;
setNewValue = noop,
} ) => {
if ( ! settings || ! settings.length ) {
return null;
}

const id = useInstanceId( LinkSettingsDrawer );
const handleSettingChange = ( setting ) => ( newValue ) => {
setNewValue( {
...value,
[ setting.id ]: newValue,
} );
};

const settingsDrawerId = `link-control-settings-drawer-${ id }`;
const theSettings = settings.map( ( setting ) => (
<ToggleControl
className="block-editor-link-control__setting"
key={ setting.id }
label={ setting.title }
onChange={ handleSettingChange( setting ) }
checked={ value ? !! value[ setting.id ] : false }
/>
) );

return (
<>
<Button
className="block-editor-link-control__drawer-toggle"
aria-expanded={ settingsOpen }
onClick={ () => setSettingsOpen( ! settingsOpen ) }
icon={ settingsIcon }
label={ __( 'Link Settings' ) }
aria-controls={ settingsDrawerId }
/>
<MaybeAnimatePresence>
{ settingsOpen && (
<MaybeMotionDiv
className="block-editor-link-control__drawer"
hidden={ ! settingsOpen }
id={ settingsDrawerId }
initial="collapsed"
animate="open"
exit="collapsed"
variants={ {
open: { opacity: 1, height: 'auto' },
collapsed: { opacity: 0, height: 0 },
} }
transition={ {
duration: 0.1,
} }
>
<div className="block-editor-link-control__drawer-inner">
{ showTextControl && (
<TextControl
__nextHasNoMarginBottom
ref={ textInputRef }
className="block-editor-link-control__setting block-editor-link-control__text-content"
label="Text"
value={ internalTextInputValue }
onChange={ setInternalTextInputValue }
onKeyDown={ handleSubmitWithEnter }
/>
) }
{ showSettings && (
<Settings
value={ value }
settings={ settings }
onChange={ onChange }
/>
) }
</div>
</MaybeMotionDiv>
) }
</MaybeAnimatePresence>
</>
<fieldset className="block-editor-link-control__settings">
<VisuallyHidden as="legend">
{ __( 'Currently selected link settings' ) }
</VisuallyHidden>
{ theSettings }
</fieldset>
);
}
};

export default LinkSettingsDrawer;
export default LinkControlSettingsDrawer;