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

Block Editor: LinkControl: Incorporate settings in edit state #20052

Merged
merged 16 commits into from
Feb 10, 2020
Merged
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
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;
Copy link
Contributor

Choose a reason for hiding this comment

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

I keep seeing these variables moving back and forth. (g2 branch too)

$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