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
Show file tree
Hide file tree
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
Expand Up @@ -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';
Expand Down Expand Up @@ -112,7 +113,6 @@ function LinkControl( {
settings = DEFAULT_LINK_SETTINGS,
onChange = noop,
onRemove,
onCancel,
noDirectEntry = false,
showSuggestions = true,
showInitialSuggestions,
Expand All @@ -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 } =
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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();
};

Expand All @@ -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
Expand All @@ -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 }
Expand All @@ -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
Expand All @@ -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>
);
Expand Down
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;