Skip to content

Commit

Permalink
Block Editor: LinkControl: Incorporate settings in edit state (#20052)
Browse files Browse the repository at this point in the history
* Block Editor: LinkControl: Incorporate settings in edit state

* Block Editor: LinkControl: Add forceIsEditingLink prop support

* Format Library: Force LinkControl editing state when adding link

* E2E Tests: Update end-to-end tests for expected link keyboard workflow

* Revert "Block Editor: LinkControl: Incorporate settings in edit state"

This reverts commit 5596566.

* Block Library: LinkControl: Always show settings drawer

* Format Library: Avoid shifting focus back to paragraph on toggle

* Components: Extract spinner size as base variable

Allow for reuse in computing dimensions in other stylesheets

* Block Editor: LinkControl: Add Submit button as action

* Block Editor: LinkControl: Remove Reset button

* Block Editor: LinkControl: Remove `disabled` from submit button

Instead inherit that the form cannot be submitted since the rendered URLInput will apply a `required` attribute, preventing submission of the form, while still displaying the associated help text "You must fill out this field"

* Block Editor: LinkControl: Pad horizontally to match vertical

* Format Library: Return paragraph selection on link edit cancel

* Block Editor: LinkControl: Infer absence of value setting as unchecked

* Format Library: Hold "Open in New Tab" during insert

* Format Library: Refactor, comment inline link state flow
  • Loading branch information
aduth authored Feb 10, 2020
1 parent 8428ead commit 6c591f4
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 138 deletions.
9 changes: 5 additions & 4 deletions packages/base-styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,8 @@ $content-width: 580px; // This is optimized for 70 characters.
// Block UI
$border-width: 1px;
$block-controls-height: 36px;
$icon-button-size: 36px;
$icon-button-size-small: 24px;
$inserter-tabs-height: 36px;
$block-toolbar-height: $block-controls-height + $border-width;
$resize-handler-size: 15px;
$resize-handler-container-size: $resize-handler-size + ($grid-size-small * 2); // Make the resize handle container larger so there's a larger grabbable area.

// Blocks
$block-left-border-width: $border-width * 3;
Expand Down Expand Up @@ -82,6 +78,11 @@ $block-selected-vertical-margin-child: $block-edge-to-content;
// Buttons & UI Widgets
$radius-round-rectangle: 4px;
$radius-round: 50%;
$icon-button-size: 36px;
$icon-button-size-small: 24px;
$resize-handler-size: 15px;
$resize-handler-container-size: $resize-handler-size + ($grid-size-small * 2); // Make the resize handle container larger so there's a larger grabbable area.
$spinner-size: 18px;

// Widgets screen
$widget-area-width: 700px;
7 changes: 7 additions & 0 deletions packages/block-editor/src/components/link-control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,10 @@ Value change handler, called with the updated value if the user selects a new li
- Default: `false`
Whether to present initial suggestions immediately.
### forceIsEditingLink
- Type: `boolean`
- Required: No
If passed as either `true` or `false`, controls the internal editing state of the component to respective show or not show the URL input field.
34 changes: 21 additions & 13 deletions packages/block-editor/src/components/link-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ import LinkControlSearchInput from './search-input';
*
* @property {(WPLinkControlSetting[])=} settings An array of settings objects. Each object will used to
* render a `ToggleControl` for that setting.
* @property {(search:string)=>Promise=} fetchSearchSuggestions Fetches suggestions for a given search term,
* returning a promise resolving once fetch is complete.
* @property {boolean=} forceIsEditingLink If passed as either `true` or `false`, controls the
* internal editing state of the component to respective
* show or not show the URL input field.
* @property {WPLinkControlValue=} value Current link value.
* @property {WPLinkControlOnChangeProp=} onChange Value change handler, called with the updated value if
* the user selects a new link or updates settings.
Expand All @@ -95,14 +96,17 @@ function LinkControl( {
settings,
onChange = noop,
showInitialSuggestions,
forceIsEditingLink,
} ) {
const wrapperNode = useRef();
const instanceId = useInstanceId( LinkControl );
const [ inputValue, setInputValue ] = useState(
( value && value.url ) || ''
);
const [ isEditingLink, setIsEditingLink ] = useState(
! value || ! value.url
forceIsEditingLink !== undefined
? forceIsEditingLink
: ! value || ! value.url
);
const isEndingEditWithFocus = useRef( false );
const { fetchSearchSuggestions } = useSelect( ( select ) => {
Expand All @@ -115,6 +119,15 @@ function LinkControl( {
const displayURL =
( value && filterURLForDisplay( safeDecodeURI( value.url ) ) ) || '';

useEffect( () => {
if (
forceIsEditingLink !== undefined &&
forceIsEditingLink !== isEditingLink
) {
setIsEditingLink( forceIsEditingLink );
}
}, [ forceIsEditingLink ] );

useEffect( () => {
// When `isEditingLink` is set to `false`, a focus loss could occur
// since the link input may be removed from the DOM. To avoid this,
Expand Down Expand Up @@ -150,10 +163,6 @@ function LinkControl( {
setInputValue( val );
};

const resetInput = () => {
setInputValue( '' );
};

const handleDirectEntry = ( val ) => {
let type = 'URL';

Expand Down Expand Up @@ -316,7 +325,6 @@ function LinkControl( {
} }
renderSuggestions={ renderSearchResults }
fetchSuggestions={ getSearchHandler }
onReset={ resetInput }
showInitialSuggestions={ showInitialSuggestions }
/>
) : (
Expand Down Expand Up @@ -359,13 +367,13 @@ function LinkControl( {
{ __( 'Edit' ) }
</Button>
</div>
<LinkControlSettingsDrawer
value={ value }
settings={ settings }
onChange={ onChange }
/>
</Fragment>
) }
<LinkControlSettingsDrawer
value={ value }
settings={ settings }
onChange={ onChange }
/>
</div>
);
}
Expand Down
19 changes: 8 additions & 11 deletions packages/block-editor/src/components/link-control/search-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes';
import { close } from '@wordpress/icons';

/**
* Internal dependencies
Expand Down Expand Up @@ -36,7 +35,6 @@ const LinkControlSearchInput = ( {
onSelect,
renderSuggestions,
fetchSuggestions,
onReset,
showInitialSuggestions,
} ) => {
const [ selectedSuggestion, setSelectedSuggestion ] = useState();
Expand Down Expand Up @@ -74,15 +72,14 @@ const LinkControlSearchInput = ( {
__experimentalHandleURLSuggestions={ true }
__experimentalShowInitialSuggestions={ showInitialSuggestions }
/>

<Button
disabled={ ! value.length }
type="reset"
label={ __( 'Reset' ) }
icon={ close }
className="block-editor-link-control__search-reset"
onClick={ onReset }
/>
<div className="block-editor-link-control__search-actions">
<Button
type="submit"
label={ __( 'Submit' ) }
icon="editor-break"
className="block-editor-link-control__search-submit"
/>
</div>
</form>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const LinkControlSettingsDrawer = ( {
key={ setting.id }
label={ setting.title }
onChange={ handleSettingChange( setting ) }
checked={ value ? value[ setting.id ] : false }
checked={ value ? !! value[ setting.id ] : false }
/>
) );

Expand Down
43 changes: 33 additions & 10 deletions packages/block-editor/src/components/link-control/style.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
$block-editor-link-control-number-of-actions: 1;

.block-editor-link-control {
position: relative;
min-width: $modal-min-width;
Expand All @@ -11,9 +13,8 @@
display: block;
padding: 11px $grid-size-large;
margin: $grid-size-large;
padding-right: 38px; // width of reset button
padding-right: ( $icon-button-size * $block-editor-link-control-number-of-actions ); // width of reset and submit buttons
position: relative;
z-index: 1;
border: 1px solid #e1e1e1;
border-radius: $radius-round-rectangle;

Expand All @@ -30,11 +31,22 @@
}
}

.block-editor-link-control__search-reset {
.block-editor-link-control__search-actions {
position: absolute;
top: 19px; // has to be hard coded as form expands with search suggestions
right: 19px; // push away to avoid focus style obscuring input border
z-index: 10;
/*
* Actions must be positioned on top of URLInput, since the input will grow
* when suggestions are rendered.
*
* Compensate for:
* - Input margin ($grid-size-large)
* - 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-size-large + 1px + ( ( 40px - $icon-button-size ) / 2 );
right: $grid-size-large + 1px + min($grid-size, ( 40px - $icon-button-size ) / 2);
}

.block-editor-link-control__search-results-wrapper {
Expand Down Expand Up @@ -196,14 +208,25 @@

.block-editor-link-control .block-editor-link-control__search-input .components-spinner {
display: block;
z-index: 100;

&.components-spinner { // Specificity override.
position: absolute;
top: 27px;
left: auto;
right: 60px;
bottom: 0;
bottom: auto;
/*
* Position spinner to the left of the actions.
*
* Compensate for:
* - Input margin ($grid-size-large)
* - Border (1px)
* - Vertically, for the difference in height between the input (40px)
* and the spinner.
* - Horizontally, adjust for the width occupied by the icon buttons,
* then artificially create spacing that mimics as if the spinner
* were center-padded to the same width as an icon button.
*/
top: $grid-size-large + 1px + ( ( 40px - $spinner-size ) / 2 );
right: $grid-size-large + 1px + ( $icon-button-size * $block-editor-link-control-number-of-actions ) + ( ( $icon-button-size - $spinner-size ) / 2 );
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Basic rendering should render 1`] = `"<div tabindex=\\"-1\\" class=\\"block-editor-link-control\\"><form><div class=\\"components-base-control block-editor-url-input block-editor-link-control__search-input\\"><div class=\\"components-base-control__field\\"><input type=\\"text\\" aria-label=\\"URL\\" required=\\"\\" placeholder=\\"Search or type url\\" role=\\"combobox\\" aria-expanded=\\"false\\" aria-autocomplete=\\"list\\" aria-owns=\\"block-editor-url-input-suggestions-0\\" value=\\"\\"></div></div><button type=\\"reset\\" disabled=\\"\\" class=\\"components-button block-editor-link-control__search-reset has-icon\\" aria-label=\\"Reset\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2 -2 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></form></div>"`;
exports[`Basic rendering should render 1`] = `"<div tabindex=\\"-1\\" class=\\"block-editor-link-control\\"><form><div class=\\"components-base-control block-editor-url-input block-editor-link-control__search-input\\"><div class=\\"components-base-control__field\\"><input type=\\"text\\" aria-label=\\"URL\\" required=\\"\\" placeholder=\\"Search or type url\\" role=\\"combobox\\" aria-expanded=\\"false\\" aria-autocomplete=\\"list\\" aria-owns=\\"block-editor-url-input-suggestions-0\\" value=\\"\\"></div></div><div class=\\"block-editor-link-control__search-actions\\"><button type=\\"submit\\" class=\\"components-button block-editor-link-control__search-submit has-icon\\" aria-label=\\"Submit\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\" class=\\"dashicon dashicons-editor-break\\"><path d=\\"M16 4h2v9H7v3l-5-4 5-4v3h9V4z\\"></path></svg></button></div></form><fieldset class=\\"block-editor-link-control__settings\\"><legend class=\\"screen-reader-text\\">Currently selected link settings</legend><div class=\\"components-base-control components-toggle-control block-editor-link-control__setting\\"><div class=\\"components-base-control__field\\"><span class=\\"components-form-toggle\\"><input class=\\"components-form-toggle__input\\" id=\\"inspector-toggle-control-0\\" type=\\"checkbox\\"><span class=\\"components-form-toggle__track\\"></span><span class=\\"components-form-toggle__thumb\\"></span><svg width=\\"6\\" height=\\"6\\" aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 6 6\\" class=\\"components-form-toggle__off\\"><path d=\\"M3 1.5c.8 0 1.5.7 1.5 1.5S3.8 4.5 3 4.5 1.5 3.8 1.5 3 2.2 1.5 3 1.5M3 0C1.3 0 0 1.3 0 3s1.3 3 3 3 3-1.3 3-3-1.3-3-3-3z\\"></path></svg></span><label for=\\"inspector-toggle-control-0\\" class=\\"components-toggle-control__label\\">Open in New Tab</label></div></div></fieldset></div>"`;
123 changes: 69 additions & 54 deletions packages/block-editor/src/components/link-control/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,69 @@ describe( 'Basic rendering', () => {
expect( searchInput ).not.toBeNull();
expect( container.innerHTML ).toMatchSnapshot();
} );

describe( 'forceIsEditingLink', () => {
const isEditing = () =>
!! container.querySelector( 'input[aria-label="URL"]' );

it( 'undefined', () => {
act( () => {
render(
<LinkControl value={ { url: 'https://example.com' } } />,
container
);
} );

expect( isEditing() ).toBe( false );
} );

it( 'true', () => {
act( () => {
render(
<LinkControl
value={ { url: 'https://example.com' } }
forceIsEditingLink
/>,
container
);
} );

expect( isEditing() ).toBe( true );
} );

it( 'false', () => {
act( () => {
render(
<LinkControl value={ { url: 'https://example.com' } } />,
container
);
} );

// Click the "Edit" button to trigger into the editing mode.
const editButton = container.querySelector(
'.block-editor-link-control__search-item-action--edit'
);
act( () => {
Simulate.click( editButton );
} );

expect( isEditing() ).toBe( true );

// If passed `forceIsEditingLink` of `false` while editing, should
// forcefully reset to the preview state.
act( () => {
render(
<LinkControl
value={ { url: 'https://example.com' } }
forceIsEditingLink={ false }
/>,
container
);
} );

expect( isEditing() ).toBe( false );
} );
} );
} );

describe( 'Searching for a link', () => {
Expand Down Expand Up @@ -217,56 +280,6 @@ describe( 'Searching for a link', () => {
);
}
);

it( 'should reset the input field and the search results when search term is cleared or reset', async () => {
const searchTerm = 'Hello world';

act( () => {
render( <LinkControl />, container );
} );

let searchResultElements;
let searchInput;

// Search Input UI
searchInput = container.querySelector( 'input[aria-label="URL"]' );

// Simulate searching for a term
act( () => {
Simulate.change( searchInput, { target: { value: searchTerm } } );
} );

// fetchFauxEntitySuggestions resolves on next "tick" of event loop
await eventLoopTick();

// TODO: select these by aria relationship to autocomplete rather than arbitary selector.
searchResultElements = container.querySelectorAll(
'[role="listbox"] [role="option"]'
);

// Check we have definitely rendered some suggestions
expect( searchResultElements ).toHaveLength(
fauxEntitySuggestions.length
);

// Grab the reset button now it's available
const resetUI = container.querySelector( '[aria-label="Reset"]' );

act( () => {
Simulate.click( resetUI );
} );

await eventLoopTick();

// TODO: select these by aria relationship to autocomplete rather than arbitary selector.
searchResultElements = container.querySelectorAll(
'[role="listbox"] [role="option"]'
);
searchInput = container.querySelector( 'input[aria-label="URL"]' );

expect( searchInput.value ).toBe( '' );
expect( searchResultElements ).toHaveLength( 0 );
} );
} );

describe( 'Manual link entry', () => {
Expand Down Expand Up @@ -458,13 +471,15 @@ describe( 'Default search suggestions', () => {

expect( mockFetchSearchSuggestions ).not.toHaveBeenCalled();

//
// Reset the search to empty and check the initial suggestions are now shown.
//
const resetUI = container.querySelector( '[aria-label="Reset"]' );
const searchInput = container.querySelector(
'input[aria-label="URL"]'
);

act( () => {
Simulate.click( resetUI );
Simulate.change( searchInput, {
target: { value: '' },
} );
} );

await eventLoopTick();
Expand Down
Loading

0 comments on commit 6c591f4

Please sign in to comment.