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

Split control for URL and Text within Link UI #33849

Merged
merged 63 commits into from
Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
c778a74
Implement basic mechanics and UI
getdave Oct 6, 2021
81f1c69
Grab the selected/link text to use in Link UI
getdave Oct 6, 2021
d61170b
Try to update the existing value with the new link and text
getdave Sep 27, 2021
371492d
Retain existing formats when applying new link format
getdave Oct 6, 2021
8d1bd59
Document scenarios for clarity
getdave Oct 4, 2021
7e1b1fb
Only show text control once a link has been committed
getdave Oct 4, 2021
571cb2b
Strip HTML from Link Preview
getdave Oct 26, 2021
1676091
Strip HTML from link text control
getdave Oct 4, 2021
9df74e8
Allow submission of text changes via enter key
getdave Oct 6, 2021
f5f5776
Ensure user provided text has length before commiting as label value
getdave Oct 6, 2021
1b98c5a
Migrate to using existing title prop of LinkControl
getdave Oct 6, 2021
f49931f
Undo overzealous stripping of HTML. Leave this to escape on output.
getdave Oct 7, 2021
317fc29
No change to text should preserve all formatting
getdave Oct 7, 2021
265f83a
Avoid complications of preserving existing formats when amending link…
getdave Oct 7, 2021
e4c604a
Expand boundary seek algorithm in order to fix bugs
getdave Oct 7, 2021
dfb2e9a
Abstract walking loops to functions
getdave Oct 7, 2021
ba30f1a
Reduce and simplify boundary algorithm
getdave Oct 7, 2021
788f20a
Avoid reassignment of startIndex and prefer self documenting variable
getdave Oct 7, 2021
5357a80
Improve edge case handling and function naming
getdave Oct 7, 2021
4c65aff
Add initial unit tests for Text control
getdave Oct 11, 2021
bb61a49
Add tests for more edge cases
getdave Oct 11, 2021
94d5211
Correct text naming
getdave Oct 11, 2021
0a232c6
Don't modify value internally.
getdave Oct 11, 2021
1bada04
Add tests to ensure LinkControl does not modify the value's title pro…
getdave Oct 11, 2021
2c875b0
Have Nav Link block strip HTML formatting before passing to LinkControl
getdave Oct 11, 2021
8cf8915
Avoid passing new function ref for handleSubmit
getdave Oct 11, 2021
2cc7638
Remove outdated reference to `text` prop of value
getdave Oct 11, 2021
ed8ac7a
Add guard against empty label
getdave Oct 11, 2021
2b2a8e7
Move utils to utils to expose for testing purposes
getdave Oct 11, 2021
374c44c
Slice target text +1 beyond the end of the format boundary
getdave Oct 11, 2021
57e2103
Add tests for getFormatValue and adjust implementation to get correct…
getdave Oct 11, 2021
75085eb
Augment tests for getFormatBoundary
getdave Oct 11, 2021
b1612fd
Fix bug whereby rich text value passed to LinkControl when no active …
getdave Oct 12, 2021
3a3e773
Extract function for retrieving RichTextValue for a given selection
getdave Oct 12, 2021
c56b9e0
Add basic e2e tests for link text
getdave Oct 26, 2021
5e5da0b
Add test for preservation of whitespace
getdave Oct 12, 2021
979276a
Add e2e test covering modification of link text via Link UI
getdave Oct 12, 2021
9a94ffa
Ensure focus is reliably placed within Link UI
getdave Oct 18, 2021
a761a39
Fix test name typo
getdave Oct 18, 2021
d52a063
Standardise test nomenclature on "submit" not "commit"
getdave Oct 18, 2021
91c9d79
Restore avoiding focus on mount to ensure collapsed selection can be …
getdave Oct 18, 2021
87821d4
Improve e2e test code comments to explain creation of a collapsed sel…
getdave Oct 18, 2021
28d111c
Remove duplicate test
getdave Oct 18, 2021
ef76e08
Add doc block to getFormatBoundary function
getdave Oct 18, 2021
2a3f25b
Conditionally show the visible URL input label
getdave Oct 18, 2021
773fc9a
Remove magic numbers from submit button positioning
getdave Oct 18, 2021
03df167
Fix overflow bug introduced in earlier commit
getdave Oct 18, 2021
58f8123
Remove stripHTML dependency and hand roll internal method
getdave Oct 18, 2021
3b53454
Re-inline utility function to simplify.
getdave Oct 19, 2021
7e3d9ed
Reduce verbosity of walkToBoundary method
getdave Oct 19, 2021
e81ed6e
Add doc block to walkToBoundary
getdave Oct 19, 2021
77a5b99
Refactor to reduce verbosity of getFormatBoundary
getdave Oct 19, 2021
50e65d9
Improve comments and self documentation of e2e test
getdave Oct 19, 2021
5c7582f
Remove unwanted comment
getdave Oct 19, 2021
71a4ace
Rename helper util to disambiguate from text input focus
getdave Oct 19, 2021
00f6296
Refine e2e tests
getdave Oct 19, 2021
7d26660
Rename an annotate unit tests
getdave Oct 19, 2021
77fbb5e
Ensure boundary tests actually exercise code under test
getdave Oct 19, 2021
ab7298b
Refactor getFormatBoundary to handle more edge cases and put more of …
getdave Oct 21, 2021
ea952d2
Fix to target icon based Edit button
getdave Oct 26, 2021
6bc185b
For currently active links use the full link text as the value of Lin…
getdave Oct 26, 2021
e1f0231
Conform to style guide for coercion of Booleans
getdave Oct 26, 2021
1e94414
Fix outdated test helper refs
getdave Oct 26, 2021
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
108 changes: 85 additions & 23 deletions packages/block-editor/src/components/link-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
* External dependencies
*/
import { noop } from 'lodash';
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';
Expand Down Expand Up @@ -119,15 +120,21 @@ function LinkControl( {
noURLSuggestion = false,
createSuggestionButtonText,
hasRichPreviews = false,
hasTextControl = false,
} ) {
if ( withCreateSuggestion === undefined && createSuggestion ) {
withCreateSuggestion = true;
}

const isMounting = useRef( true );
const wrapperNode = useRef();
const textInputRef = useRef();

const [ internalInputValue, setInternalInputValue ] = useState(
( value && value.url ) || ''
value?.url || ''
);
const [ internalTextValue, setInternalTextValue ] = useState(
value?.title || ''
);
const currentInputValue = propInputValue || internalInputValue;
const [ isEditingLink, setIsEditingLink ] = useState(
Expand All @@ -149,22 +156,45 @@ function LinkControl( {
}, [ forceIsEditingLink ] );

useEffect( () => {
// We don't auto focus into the Link UI on mount
// because otherwise using the keyboard to select text
// *within* the link format is not possible.
if ( isMounting.current ) {
isMounting.current = false;
return;
}
// Unless we are mounting, we always want to focus either:
// - the URL input
// - the first focusable element in the Link UI.
// But in editing mode if there is a text input present then
// the URL input is at index 1. If not then it is at index 0.
const whichFocusTargetIndex = textInputRef?.current ? 1 : 0;

// When switching between editable and non editable LinkControl
// move focus to the first element to avoid focus loss.
// Scenario - when:
// - switching between editable and non editable LinkControl
// - clicking on a link
// ...then move focus to the *first* element to avoid focus loss
// and to ensure focus is *within* the Link UI.
const nextFocusTarget =
focus.focusable.find( wrapperNode.current )[ 0 ] ||
wrapperNode.current;
focus.focusable.find( wrapperNode.current )[
whichFocusTargetIndex
] || wrapperNode.current;

nextFocusTarget.focus();

isEndingEditWithFocus.current = false;
}, [ isEditingLink ] );

/**
* If the value's `text` property changes then sync this
* back up with state.
*/
useEffect( () => {
if ( value?.title && value.title !== internalTextValue ) {
setInternalTextValue( value.title );
}
}, [ value ] );

/**
* 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 @@ -182,22 +212,47 @@ function LinkControl( {
);

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

const handleSubmitButton = () => {
if ( currentInputValue !== value?.url ) {
onChange( { url: currentInputValue } );
const handleSubmit = () => {
if (
currentInputValue !== value?.url ||
internalTextValue !== value?.title
) {
onChange( {
url: currentInputValue,
title: internalTextValue,
} );
}
stopEditing();
};

const handleSubmitWithEnter = ( event ) => {
const { keyCode } = event;
if (
keyCode === ENTER &&
! currentInputIsEmpty // disallow submitting empty values.
) {
event.preventDefault();
handleSubmit();
}
};

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

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 = value?.url?.trim()?.length && hasTextControl;

return (
<div
tabIndex={ -1 }
Expand All @@ -212,10 +267,26 @@ function LinkControl( {

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

<LinkControlSearchInput
currentLink={ value }
className="block-editor-link-control__search-input"
className="block-editor-link-control__field block-editor-link-control__search-input"
placeholder={ searchInputPlaceholder }
value={ currentInputValue }
withCreateSuggestion={ withCreateSuggestion }
Expand All @@ -230,20 +301,11 @@ function LinkControl( {
createSuggestionButtonText={
createSuggestionButtonText
}
useLabel={ showTextControl }
>
<div className="block-editor-link-control__search-actions">
<Button
onClick={ () => handleSubmitButton() }
onKeyDown={ ( event ) => {
const { keyCode } = event;
if (
keyCode === ENTER &&
! currentInputIsEmpty // disallow submitting empty values.
) {
event.preventDefault();
handleSubmitButton();
}
} }
onClick={ handleSubmit }
label={ __( 'Submit' ) }
icon={ keyboardReturn }
className="block-editor-link-control__search-submit"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '@wordpress/components';
import { filterURLForDisplay, safeDecodeURI } from '@wordpress/url';
import { Icon, globe, info, linkOff, edit } from '@wordpress/icons';
import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We'll probably want to hand roll an implementation of this whilst we wait on #35539


/**
* Internal dependencies
Expand Down Expand Up @@ -41,6 +42,8 @@ export default function LinkPreview( {
( value && filterURLForDisplay( safeDecodeURI( value.url ), 16 ) ) ||
'';

const displayTitle = richData?.title || value?.title || displayURL;

const isEmptyURL = ! value.url.length;

let icon;
Expand Down Expand Up @@ -84,9 +87,7 @@ export default function LinkPreview( {
className="block-editor-link-control__search-item-title"
href={ value.url }
>
{ richData?.title ||
value?.title ||
displayURL }
{ stripHTML( displayTitle ) }
</ExternalLink>

{ value?.url && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import { noop, omit } from 'lodash';

import classnames from 'classnames';
/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -45,6 +45,7 @@ const LinkControlSearchInput = forwardRef(
suggestionsQuery = {},
withURLSuggestion = true,
createSuggestionButtonText,
useLabel = false,
},
ref
) => {
Expand Down Expand Up @@ -117,10 +118,15 @@ const LinkControlSearchInput = forwardRef(
}
};

const inputClasses = classnames( className, {
'has-no-label': ! useLabel,
} );

return (
<div>
<div className="block-editor-link-control__search-input-container">
<URLInput
className={ className }
label={ useLabel ? 'URL' : undefined }
className={ inputClasses }
value={ value }
onChange={ onInputChange }
placeholder={ placeholder ?? __( 'Search or type url' ) }
Expand Down
43 changes: 32 additions & 11 deletions packages/block-editor/src/components/link-control/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,46 @@ $preview-image-height: 140px;
position: relative;
}

// LinkControl popover.
.block-editor-link-control .block-editor-link-control__search-input {
// Specificity override.
&.block-editor-link-control__search-input input[type="text"] {
// Provides positioning context for search actions
.block-editor-link-control__search-input-container {
position: relative;
}

// If the input doesn't have a visible label then
// we need to expand the input itself to occupy
// the full available horizontal space.
.block-editor-link-control__search-input.has-no-label .block-editor-url-input__input {
flex: 1;
}

.block-editor-link-control__field {
margin: $grid-unit-20; // allow margin collapse for vertical spacing.

// Element wrapping the label and input.
> .components-base-control__field {
display: flex;
align-items: center;
margin: 0;
}

.components-base-control__label {
margin-right: $grid-unit-20;
margin-bottom: 0;
}

input[type="text"],
// Specificity overide of URLInput defaults.
&.block-editor-url-input input[type="text"].block-editor-url-input__input {
@include input-control;
width: calc(100% - #{$grid-unit-20*2});
display: block;
padding: 11px $grid-unit-20;
padding-right: ( $button-size * $block-editor-link-control-number-of-actions ); // width of reset and submit buttons
margin: $grid-unit-20;
margin: 0;
position: relative;
border: 1px solid $gray-300;
border-radius: $radius-block-ui;
}

.components-base-control__field {
margin-bottom: 0;
}
}

.block-editor-link-control__search-error {
Expand All @@ -61,14 +83,13 @@ $preview-image-height: 140px;
* when suggestions are rendered.
*
* Compensate for:
* - Input margin ($grid-unit-20)
* - Border (1px)
* - Vertically, for the difference in height between the input (40px) and
* the icon buttons.
* - Horizontally, pad to the minimum of: default input padding, or the
* equivalent of the vertical padding.
*/
top: $grid-unit-20 + 1px + ( ( 40px - $button-size ) * 0.5 );
top: 1px + ( ( 40px - $button-size ) * 0.5 );
right: $grid-unit-20 + 1px + min($grid-unit-10, ( 40px - $button-size ) * 0.5);
}

Expand Down
Loading