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

Experimental Link creation interface #17846

Merged
merged 113 commits into from
Oct 30, 2019
Merged
Show file tree
Hide file tree
Changes from 71 commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
8dbc2bd
Initial component file structure
getdave Oct 8, 2019
ee008be
Implement basic icon and toggle mechanic
getdave Oct 8, 2019
e685003
Adds basic search input
getdave Oct 8, 2019
70049ad
Update input to utilise LinkEditor component autocomplete
getdave Oct 8, 2019
98e6aa5
Add ability to customise placeholder
getdave Oct 8, 2019
85a6317
Update to utilise URLInput directly for greater flexibility
getdave Oct 8, 2019
8ec1488
Add example search results and test coverage
getdave Oct 8, 2019
140cc90
Update class naming convention to match guidelines
getdave Oct 9, 2019
d64608e
Adds render prop to enable custom suggestions rendering
getdave Oct 9, 2019
30e0eb6
Update to utilise URLInput render prop to customise search suggestion…
getdave Oct 9, 2019
53ce517
Update to add post type to the fetchLinkSuggestions responsive mapping
getdave Oct 9, 2019
b53c13c
Fix to ensure search suggestion interaction states are perceivable
getdave Oct 9, 2019
6d7259a
Update suggestion render prop to provide component props as arguments
getdave Oct 9, 2019
4f32429
Update to match with design visual and provide more accessible markup
getdave Oct 9, 2019
4971e30
Adds settings area. Fixes missing reset icon.
getdave Oct 9, 2019
4aea19c
Fix search items to be buttons with correct style and layout
getdave Oct 10, 2019
d554b65
Adds overflow scrolling to search results
getdave Oct 10, 2019
26c4c1b
Fix to stop scroll shadow overlaying scrollbars
getdave Oct 10, 2019
e778a04
Add bespoke settings area and tweak styles
getdave Oct 10, 2019
3ca482e
Update to allow URLs to be conditionally handled as a suggestion
getdave Oct 10, 2019
aec9f39
Updates to conditionally use an entity or url based search results fe…
getdave Oct 10, 2019
70bd877
Fix bug whereby fetchSearchSuggestions wasn’t called
getdave Oct 11, 2019
58220f8
Remove default toggle UI and implement Popover close
getdave Oct 11, 2019
5eeb819
Adds search text “highlighting” in results list
getdave Oct 11, 2019
d355133
Move TextHighlight component to its own file
getdave Oct 11, 2019
2044065
Fix bug where update to value prop didn’t cause suggestions to reset.
getdave Oct 11, 2019
b256d55
Update to remove internal handling of open/closed state
getdave Oct 11, 2019
b55f557
Fix React violation by returning only the text for non matches
getdave Oct 11, 2019
2133fb1
Update existing tests to match new implementation
getdave Oct 11, 2019
70c5c04
Add link reset test
getdave Oct 11, 2019
4a031ed
Adds test which uncovers major bug in the implementation
getdave Oct 11, 2019
5b5164c
Tweak critical test to be more explicit about what is expected
getdave Oct 11, 2019
664daad
Fix bug to make determining search handler use the latest input value
getdave Oct 14, 2019
9780959
Add loading spinner and associated test coverage
getdave Oct 14, 2019
5c3bf35
Fix bug where value could be empty
getdave Oct 14, 2019
c962dc6
Adds basic editing / view state switching
getdave Oct 15, 2019
d4c0a70
Add keydown callback to URLInput
getdave Oct 15, 2019
8cff4c2
Select link on ENTER keydown event
getdave Oct 15, 2019
b4a3f66
Utilise LinkViewer to render edit state and decode urls for display
getdave Oct 15, 2019
fbe49e7
Only display link settings when a link is selected
getdave Oct 15, 2019
fa0dd36
Adds current link view styles
getdave Oct 15, 2019
f5a7e65
Makes settings toggle controlled by parent component
getdave Oct 15, 2019
a88826e
Update visuals to match updated design
getdave Oct 16, 2019
ec9e2ad
Add standardised min width to popover
getdave Oct 16, 2019
11ca79f
Temporary hack to include Link UI in Playground for testing
getdave Oct 16, 2019
779d440
Update to utilise isURL util from @wordpress/url package
getdave Oct 16, 2019
4941bb8
Update to utilise isURL util from @wordpress/url package
getdave Oct 16, 2019
452ec55
Removes URLPopover dependency
getdave Oct 16, 2019
5484a7c
Extract settings drawer to sub component
getdave Oct 16, 2019
37de5d9
Refactor search items into a component
getdave Oct 17, 2019
ba32148
Refactor Input and Search to component
getdave Oct 17, 2019
33467f3
Fix missing selected state on search suggestions
getdave Oct 17, 2019
553be99
Tweak line height on search suggestion url path
getdave Oct 17, 2019
ad46c87
Augment test for URL-like by testing for “www.”
getdave Oct 18, 2019
26c292f
Fix to stop url overflows and wrapping on to multiple lines
getdave Oct 18, 2019
17be2f8
Uppcase URL in type indicator within search results list
getdave Oct 21, 2019
13f5ce1
Avoid reading out slug/URL for entity results
getdave Oct 21, 2019
556088a
Ensures i18n of change button
getdave Oct 21, 2019
1cdd939
Always offer URL result in search suggestions as default
getdave Oct 21, 2019
2588b9e
Fix loading spinner position and dim results during loading
getdave Oct 21, 2019
a065936
Fix scroll shadows to use valid alpha transparent values in gradient
getdave Oct 21, 2019
98836fd
Adds instructional text in place of URL for suggestions that are URLs
getdave Oct 23, 2019
5254015
Update prop names for consistency
getdave Oct 23, 2019
811ad11
Update line length to improve readability
getdave Oct 23, 2019
9cd6d18
Update to avoid need to utilise partialRight util from lodash
getdave Oct 23, 2019
bdb6217
Updates key to avoid usage of index
getdave Oct 23, 2019
d0a348b
Update to remote isFunction check in favour of direct check
getdave Oct 23, 2019
da212f0
Update to handle mailto and tel protocols and internal links
getdave Oct 23, 2019
19d5e64
url-input: handle onKeyPress type event
retrofox Oct 22, 2019
548279b
link-control: add className prop
retrofox Oct 22, 2019
a844800
link-control: add README file
retrofox Oct 22, 2019
fd3a6ef
Remove unnecessary use of useCallback
getdave Oct 24, 2019
b314c49
Fix current automated tests
getdave Oct 24, 2019
5aeb531
Improves URL handling test to run for multiple URL value variations
getdave Oct 24, 2019
1a7c285
Updates to display the URL type in the search results
getdave Oct 24, 2019
63201c9
Refactor tests to assert against all valid protocol formats and link …
getdave Oct 24, 2019
4918184
Adds test to cover display of fallback URL search result for search v…
getdave Oct 24, 2019
cd29ab5
Adds tests to check URL suggestions don’t display for non-URLs.
getdave Oct 24, 2019
2d8befb
url-input: remove unneeded `suggestion` const
retrofox Oct 23, 2019
d8895d5
url-input: always trigger onKeyDown event
retrofox Oct 23, 2019
2413016
link-control: delegate handling keydown event
retrofox Oct 23, 2019
e3042c8
link-control: add onKeyDown and onKeyPress handlers
retrofox Oct 24, 2019
0a9d558
link-control: playground -> close once onClose
retrofox Oct 24, 2019
89e92fb
link-control: propagate onClose() event
retrofox Oct 24, 2019
ff57160
link-control: playground -> hanldling close by ESCAPE key
retrofox Oct 24, 2019
48e5f44
Fix to only render settings draw if settings are defined
getdave Oct 24, 2019
4e811db
Remove redundant commented out test
getdave Oct 24, 2019
43c30b2
Update to render with a “current link” if one is provided.
getdave Oct 24, 2019
158ea3e
Render playground with currentLink active
getdave Oct 24, 2019
4981a71
Adds test to cover currentLink prop
getdave Oct 25, 2019
f1c54a6
Remove selected state from Playground
getdave Oct 25, 2019
797fd6c
Adds tests to cover selecting and changing links
getdave Oct 25, 2019
e5e44e6
Remove async function in place of direct Promise usage and add test c…
getdave Oct 25, 2019
5ba4b65
Add test to cover keyboard handling
getdave Oct 25, 2019
d5abad2
Remove unecessary dep from effect
getdave Oct 25, 2019
5c1ec22
Fix URLInput to pass the actual suggestion object not the index
getdave Oct 25, 2019
4d5b455
Fix keyboard handling so hitting `ENTER` will select an item as the c…
getdave Oct 25, 2019
b8a01bd
Updates keyboard interaction test to include URL entry
getdave Oct 25, 2019
7df9aa5
Minor: reword test description
getdave Oct 25, 2019
b6edb73
Fix missing key prop regression
getdave Oct 25, 2019
b435b49
DRY up conditionals
getdave Oct 25, 2019
8c5ed79
link-control: set a default experimental link suggestions searcher if…
retrofox Oct 24, 2019
1fffe8e
link-control: handling key events
retrofox Oct 25, 2019
1c1614d
url-input: remove onKeyDown prop
retrofox Oct 25, 2019
0cdf706
url-input: remove calling onKeyDown prop
retrofox Oct 28, 2019
2f80347
url-input: rollback some changes
retrofox Oct 24, 2019
b93a0db
Mark Link Creation Interface as Experimental (#18110)
marekhrabe Oct 28, 2019
5b6715d
Remove unused prop from docs
getdave Oct 28, 2019
280db32
Update props ordering and readme docs
obenland Oct 29, 2019
ba985eb
Revert playground changes
obenland Oct 29, 2019
7d35791
Rename InputSearch to SearchInput
obenland Oct 29, 2019
e7585b8
Remove disabling of jsx-key lint rule
talldan Oct 30, 2019
8450bb1
Change fake id value to something that will not clash with post ids
talldan Oct 30, 2019
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
4 changes: 4 additions & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ _Related_

- <https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inspector-controls/README.md>

<a name="LinkControl" href="#LinkControl">#</a> **LinkControl**

Undocumented declaration.

<a name="MediaPlaceholder" href="#MediaPlaceholder">#</a> **MediaPlaceholder**

_Related_
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export { default as __experimentalGradientPickerControl } from './gradient-picke
export { default as InnerBlocks } from './inner-blocks';
export { default as InspectorAdvancedControls } from './inspector-advanced-controls';
export { default as InspectorControls } from './inspector-controls';
export { default as LinkControl } from './link-control';
getdave marked this conversation as resolved.
Show resolved Hide resolved
export { default as MediaPlaceholder } from './media-placeholder';
export { default as MediaUpload } from './media-upload';
export { default as MediaUploadCheck } from './media-upload/check';
Expand Down
53 changes: 53 additions & 0 deletions packages/block-editor/src/components/link-control/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Link Control

### className

- Type: `string`
- Required: NO

### defaultOpen
getdave marked this conversation as resolved.
Show resolved Hide resolved

- Type: `Boolean`
- Required: No

### fetchSearchSuggestions

- Type: `Function`
- Required: YES

### currentLink

- Type:
- Required:

### linkSettings
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
### linkSettings
### currentSettings


- Type:
- Required:

## Event handlers

### onClose

- Type: `Function`
- Required:

### onKeyDown

- Type: `Function`
- Required:

### onKeyPress

- Type: `Function`
- Required:

### onLinkChange

- Type: `Function`
- Required:

### onSettingChange

- Type: `Function`
- Required:
199 changes: 199 additions & 0 deletions packages/block-editor/src/components/link-control/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/**
* External dependencies
*/
import classnames from 'classnames';
import { isFunction, noop, startsWith } from 'lodash';

/**
* WordPress dependencies
*/
import {
Button,
ExternalLink,
Popover,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';

import {
useCallback,
useState,
} from '@wordpress/element';

import {
safeDecodeURI,
filterURLForDisplay,
isURL,
prependHTTP,
getProtocol,
} from '@wordpress/url';

/**
* Internal dependencies
*/
import LinkControlSettingsDrawer from './settings-drawer';
import LinkControlSearchItem from './search-item';
import LinkControlInputSearch from './input-search';

function LinkControl( { currentLink, className, fetchSearchSuggestions, onLinkChange, onSettingsChange = { noop }, currentSettings } ) {
// State
const [ inputValue, setInputValue ] = useState( '' );
const [ isEditingLink, setIsEditingLink ] = useState( true );

// Handlers
const onInputChange = ( value = '' ) => {
setInputValue( value );
};

const closeLinkUI = () => {
resetInput();
};

const resetInput = useCallback( () => {
getdave marked this conversation as resolved.
Show resolved Hide resolved
setInputValue( '' );
} );

const onLinkSelect = ( suggestion ) => ( event ) => {
event.preventDefault();
event.stopPropagation();

setIsEditingLink( false );

if ( isFunction( onLinkChange ) ) {
onLinkChange( suggestion );
}
};

const onStartEditing = () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: some of these handlers (onInputChange, resetInput, onStartEditing) could be inlined, my opinion is that it makes the code a bit more readable. Just my opinion though, so up to you if you change it.

setIsEditingLink( true );
};

const handleDirectEntry = async ( value ) => {
getdave marked this conversation as resolved.
Show resolved Hide resolved
let type = 'URL';

const protocol = getProtocol( value ) || '';

if ( protocol.includes( 'mailto' ) ) {
type = 'mailto';
}

if ( protocol.includes( 'tel' ) ) {
type = 'tel';
}

if ( startsWith( value, '#' ) ) {
type = 'internal';
}

return [ {
id: '1',
talldan marked this conversation as resolved.
Show resolved Hide resolved
title: value,
url: type === 'URL' ? prependHTTP( value ) : value,
type,
} ];
};

const handleEntitySearch = async ( value ) => {
const results = await Promise.all( [
fetchSearchSuggestions( value ),
handleDirectEntry( value ),
] );

const couldBeURL = ! value.includes( ' ' );

// If it's potentially a URL search then concat on a URL search suggestion
// just for good measure. That way once the actual results run out we always
// have a URL option to fallback on.
return couldBeURL ? results[ 0 ].concat( results[ 1 ] ) : results[ 0 ];
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it might be tidier if all the code from handleURLSearch and handleEntitySearch were defined in getSearchHandler. There seems to be a lot of logic around isURL, couldBeURL, value.includes( 'www.' ) that could be streamlined by moving all the code to be in the same place.

Other than that, this could use an array spread:

return couldBeURL ? [ ...results[ 0 ], ...results[ 1 ] ]  : results[ 0 ];

};

// Effects
const getSearchHandler = useCallback( ( value ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

getSearchHandler is an 'effect' according to the comment, but resetInput isn't. Both seem to use useCallback. The name could possibly be changed as it doesn't return a searchHandler from what I can tell, but the searchResults instead.

const protocol = getProtocol( value ) || '';
const isMailto = protocol.includes( 'mailto' );
const isInternal = startsWith( value, '#' );
const isTel = protocol.includes( 'tel' );

const handleManualEntry = isInternal || isMailto || isTel || isURL( value ) || value.includes( 'www.' );

return ( handleManualEntry ) ? handleDirectEntry( value ) : handleEntitySearch( value );
}, [ handleDirectEntry, fetchSearchSuggestions ] );

// Render Components
const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, isLoading } ) => {
const resultsListClasses = classnames( 'block-editor-link-control__search-results', {
'is-loading': isLoading,
} );

return (
<div className="block-editor-link-control__search-results-wrapper">
<div { ...suggestionsListProps } className={ resultsListClasses }>
{ suggestions.map( ( suggestion, index ) => (
<LinkControlSearchItem
key={ `${ suggestion.id }-${ suggestion.type }` }
itemProps={ buildSuggestionItemProps( suggestion, index ) }
suggestion={ suggestion }
onClick={ onLinkSelect( suggestion ) }
isSelected={ index === selectedSuggestion }
isURL={ suggestion.type.toLowerCase() === 'url' }
searchTerm={ inputValue }
/>
) ) }
</div>
</div>
);
};

return (
<Popover
className={ classnames( 'block-editor-link-control', className ) }
onClose={ closeLinkUI }
talldan marked this conversation as resolved.
Show resolved Hide resolved
position="bottom center"
focusOnMount="firstElement"
>
<div className="block-editor-link-control__popover-inner">
<div className="block-editor-link-control__search">

{ ! isEditingLink && (
<div
className={ classnames( 'block-editor-link-control__search-item', {
'is-current': true,
} ) }
>
<span className="block-editor-link-control__search-item-header">

<ExternalLink
className="block-editor-link-control__search-item-title"
href={ currentLink.url }
>
{ currentLink.title }
</ExternalLink>
<span className="block-editor-link-control__search-item-info">{ filterURLForDisplay( safeDecodeURI( currentLink.url ) ) || '' }</span>
</span>

<Button isDefault onClick={ onStartEditing } className="block-editor-link-control__search-item-action block-editor-link-control__search-item-action--edit">
Copy link
Contributor

Choose a reason for hiding this comment

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

For whatever reason, Gutenberg doesn't tend to use the --modifier BEM syntax. A simple class like is-editing is fine.

{ __( 'Change' ) }
</Button>
</div>
) }

{ isEditingLink && (
<LinkControlInputSearch
value={ inputValue }
onChange={ onInputChange }
onSelect={ onLinkSelect }
renderSuggestions={ renderSearchResults }
fetchSuggestions={ getSearchHandler }
onReset={ resetInput }
/>
) }

{ ! isEditingLink && (
<LinkControlSettingsDrawer settings={ currentSettings } onSettingChange={ onSettingsChange } />
) }
</div>
</div>
</Popover>
);
}

export default LinkControl;
69 changes: 69 additions & 0 deletions packages/block-editor/src/components/link-control/input-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
IconButton,
} from '@wordpress/components';
import {
LEFT,
RIGHT,
UP,
DOWN,
BACKSPACE,
ENTER,
} from '@wordpress/keycodes';

/**
* Internal dependencies
*/
import {
URLInput,
} from '../';

const LinkControlInputSearch = ( { value, onChange, onSelect, renderSuggestions, fetchSuggestions, onReset } ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: There's LinkControlSearchItem and LinkControlInputSearch. I think it'd be more consistent if this one were named LinkControlSearchInput

const stopPropagation = ( event ) => {
event.stopPropagation();
};

const stopPropagationRelevantKeys = ( event ) => {
if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) {
Copy link
Contributor

@talldan talldan Oct 23, 2019

Choose a reason for hiding this comment

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

It's seems like a lot of code duplication that stopPropagation needs to be handled when URLInput already handles it for its normal search results.

The extra API being added to URLInput (renderSuggestions, onKeyDown, onKeyPress) is something I'm concerned about. A lot of it is code that is already defined internally in URLInput. At the very least these props need to be all marked as experimental.

I also haven't seen any use case mentioning the need to make search results extensible in this way.

Even if it needs to be extensible, I feel like the way it's implemented could be more elegant, like a component that gets passed to URLInput (after all renderSuggestions renders in exactly the same part of the render tree as the standard suggestions). Then it'd be possible to provide some good options out of the box, e.g.:

<URLInput
    suggestions={ URLInput.InlineSuggestions }
/>

or

<URLInput
    suggestions={ URLInput.PopoverSuggestions }
/>

Copy link
Contributor

@retrofox retrofox Oct 23, 2019

Choose a reason for hiding this comment

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

... stopPropagation needs to be handled when URLInput already handles it.

Not really. The <URLInput /> doesn't block the propagation of the events and depending on the context when it's used the popover can disappear (apparently) spontaneously. You can see how it's handled in #17986 PR, here for instance.

There are some specific events that need to be handled in the <URLInput /> component (like it already does). It's nice to see how the caret goes at the beginning or at the end when the user press UP or DOWN.

But handling the LEFT and RIGHT keys maybe need to be handled outside of the <URLInput />, since this is an special case where the component is used into a NavigationMenuItem , and we need to block the shifting to the next ones.

At the very least these props need to be all marked as experimental.

I think adding the __experimental prefix to onKeyPress or onKeyDown could add more confusion. Why not simply passing the props and make them optional?

<URLInput
  onKeyDown={ onKeyDown || noop }
  onKeyPress={ onKeyPress || noop }
/>

or something like that.

Copy link
Contributor

Choose a reason for hiding this comment

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

...but I suggest handling it by the component which consumes it. For instance, in the NavigationMenuItem:

const NavigationMenuItem = ( props ) => {
	// ...
	return (
		<Toolbar>
			<ToolbarButton
				// ...
			/>
			<LinkControl
				className="wp-block-navigation-menu-item__inline-link-input"
				onKeyDown={ onKeyDownHandler }
				onKeyPress={ onKeyPressHandler }
			/>
		</Toolbar>
	);
};

Copy link
Contributor

Choose a reason for hiding this comment

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

Let me show how we are handling this in the [draft] PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree we shouldn't handle onKeyDown from the URLInput, in the first place because stopping propagation for those particular keys is not necessary in all the use cases of URLInput (for instance, the Button block doesn't need it) and second because URLInput already has its own onKeyDown handler and passing in a second one makes the code confusing and hard to read.
However, I don't we think should be handling it in the Item component either. I added that bit in my PR for the sake of making things work, but am expecting it to be refactored at some point once we've decided on how this new link component should behave.
This keyDown handler should only be needed in cases where we have a URLPopover inside the block toolbar, and because we now have multiple instances of this - Image block, any block with RichText in it, and now MenuItem - it might be a good time to build a toolbar-specific URLInput wrapper that handles all these gotchas in one place.
(There was a bit of discussion about that on my PR, @draganescu has run into the same issue with the Media flow component.)

Copy link
Contributor

@talldan talldan Oct 24, 2019

Choose a reason for hiding this comment

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

@retrofox URLInput has been in use for a long time in popovers without those issues. The component does stop propagation of the events that are relevant to it:

switch ( event.keyCode ) {
case UP: {
event.stopPropagation();
event.preventDefault();
const previousIndex = ! selectedSuggestion ? suggestions.length - 1 : selectedSuggestion - 1;
this.setState( {
selectedSuggestion: previousIndex,
} );
break;
}
case DOWN: {
event.stopPropagation();
event.preventDefault();
const nextIndex = selectedSuggestion === null || ( selectedSuggestion === suggestions.length - 1 ) ? 0 : selectedSuggestion + 1;
this.setState( {
selectedSuggestion: nextIndex,
} );
break;
}
case TAB: {
if ( this.state.selectedSuggestion !== null ) {
this.selectLink( suggestion );
// Announce a link has been selected when tabbing away from the input field.
this.props.speak( __( 'Link selected.' ) );
}
break;
}
case ENTER: {
if ( this.state.selectedSuggestion !== null ) {
event.stopPropagation();
this.selectLink( suggestion );
}
break;
}

Other events do seem to be stopped at the form/popover level where URLPopover.LinkEditor is implemented. I do agree that there might be better ways to do this as it's a lot of code duplication. The issue would probably be that URLInput can be used both in and outside of a popover, so it shouldn't be handling this functionality internally (beyond UP, DOWN, TAB, ENTER, which apply to both use cases).

I think adding the __experimental prefix to onKeyPress or onKeyDown could add more confusion. Why not simply passing the props and make them optional?

I'm not sure I see why it would add more confusion. It signals that they're not (yet) for use externally, which is the truth seeing as we don't seem to have agreement on how this should be implemented. At the moment it would be a pragmatic way to get this PR shipped.

Copy link
Contributor

@retrofox retrofox Oct 24, 2019

Choose a reason for hiding this comment

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

@retrofox URLInput has been in use for a long time in popovers without those issues.

So far 😆. It happens when we introduce new features. Simply happens.

Other events do seem to be stopped at the form/popover level where URLPopover.LinkEditor is implemented

Yes, it's true. I'm just wondering why not just pass the onKeyPress to the <URLInput /> component, making it optional defining a noop function as default.

I'm not sure I see why it would add more confusion.

To me, it is the opposite. Improves the API instead of adding a new wrapper element just for subscribing to the event. So the options are:

  1. Add a wrapper element to <URLInput /> just to catch the event propagation.
  2. Allow binding a function through of the onKeyPress property of the <URLInput >

...seeing as we don't seem to have agreement on how this should be implemented.

It's just part of the workflow, right? Totally agree to go for whatever we want and go ahead not blocking us. Personally, I prefer the second one but it's up to us. I've already added another comment about this.

// Stop the key event from propagating up to ObserveTyping.startTypingInTextField.
stopPropagation( event );
}
};

return (
<form>
<URLInput
className="block-editor-link-control__search-input"
obenland marked this conversation as resolved.
Show resolved Hide resolved
value={ value }
onChange={ onChange }
onKeyDown={ ( event, suggestion ) => {
stopPropagationRelevantKeys( event );
if ( event.keyCode === ENTER ) {
onSelect( suggestion )( event );
}
} }
onKeyPress={ stopPropagation }
Copy link
Contributor

Choose a reason for hiding this comment

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

is <URLInput /> handling the onKeyPress event? It doesn't seem to do.
I doubt because it already tries to bind the function in the current implementation of <NavigationMenuItem />

Copy link
Contributor

Choose a reason for hiding this comment

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

19d5e64 handles the onKeyPress event of the <URLInput /> component.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same logic as for the keyDown I commented on above: this keyPress handler is only needed for popovers inside the toolbar so if we create a toolbar URL wrapper we should add it in there instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, it's handled by the <form /> element, right? the Form subscribes to the onKeyPress and then stops its propagation when happens.

placeholder={ __( 'Search or type url' ) }
renderSuggestions={ renderSuggestions }
fetchLinkSuggestions={ fetchSuggestions }
handleURLSuggestions={ true }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this needed when there's already a showSuggestions prop? fetchLinkSuggestions being undefined could also indicate to the component that suggestions shouldn't be handled.

/>

<IconButton
disabled={ ! value.length }
type="reset"
label={ __( 'Reset' ) }
icon="no-alt"
className="block-editor-link-control__search-reset"
onClick={ onReset }
/>

</form>
);
};

export default LinkControlInputSearch;
54 changes: 54 additions & 0 deletions packages/block-editor/src/components/link-control/search-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* Internal dependencies
*/
import TextHighlight from './text-highlight';

/**
* WordPress dependencies
*/
import { safeDecodeURI } from '@wordpress/url';
import { __ } from '@wordpress/i18n';

import {
Icon,
} from '@wordpress/components';

export const LinkControlSearchItem = ( { itemProps, suggestion, isSelected = false, onClick, isURL = false, searchTerm = '' } ) => {
return (
<button
{ ...itemProps }
onClick={ onClick }
className={ classnames( 'block-editor-link-control__search-item', {
'is-selected': isSelected,
'is-url': isURL,
'is-entity': ! isURL,
} ) }
>
{ isURL && (
<Icon className="block-editor-link-control__search-item-icon" icon="admin-site-alt3" />
) }
<span className="block-editor-link-control__search-item-header">
<span className="block-editor-link-control__search-item-title">
<TextHighlight text={ suggestion.title } highlight={ searchTerm } />
</span>
<span aria-hidden={ ! isURL } className="block-editor-link-control__search-item-info">
{ ! isURL && ( safeDecodeURI( suggestion.url ) || '' ) }
{ isURL && (
__( 'Press ENTER to add this link' )
) }
</span>
</span>
{ suggestion.type && (
<span className="block-editor-link-control__search-item-type">{ suggestion.type }</span>
) }
</button>
);
};

export default LinkControlSearchItem;

Loading