-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
NavigationMenu: <LinkControl /> integration. #18062
Changes from all commits
9eaae16
b93b07a
c52d92f
33baef0
1cc27a2
28d446e
d32f027
5eb1366
9fa1562
9616d76
35e88e8
2550320
fbfcec6
81bf11a
0d9637d
154d2c7
ccccb5c
9a2d2fb
c712717
56536a1
0c965d8
80cb5e8
b9d2195
293bfb3
f6ebb92
3b4a3ed
468eff8
e85cf06
7044c24
2dfa557
c063034
e1b64e4
b77c663
b4ca8ca
cf3d2c8
2c7b597
15bf1b2
f60ae05
c34be01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,14 +33,41 @@ import { | |
BlockControls, | ||
InnerBlocks, | ||
InspectorControls, | ||
URLPopover, | ||
RichText, | ||
__experimentalLinkControl as LinkControl, | ||
} from '@wordpress/block-editor'; | ||
import { | ||
Fragment, | ||
useRef, | ||
useState, | ||
} from '@wordpress/element'; | ||
import { Fragment, useState, useEffect } from '@wordpress/element'; | ||
|
||
/** | ||
* It updates the link attribute when the | ||
* link settings changes. | ||
* | ||
* @param {Function} setter Setter attribute function. | ||
*/ | ||
const updateLinkSetting = ( setter ) => ( setting, value ) => { | ||
if ( setting === 'new-tab' ) { | ||
setter( { opensInNewTab: value } ); | ||
} | ||
}; | ||
|
||
/** | ||
* Updates the link attribute when it changes | ||
* through of the `onLinkChange` LinkControl callback. | ||
* | ||
* @param {Function} setter Setter attribute function. | ||
* @param {string} label ItemMenu link label. | ||
*/ | ||
const updateLink = ( setter, label ) => ( { title: newTitle = '', url: newURL = '' } = {} ) => { | ||
setter( { | ||
title: newTitle, | ||
url: newURL, | ||
} ); | ||
|
||
// Set the item label as well if it isn't already defined. | ||
if ( ! label ) { | ||
setter( { label: newTitle } ); | ||
} | ||
}; | ||
|
||
function NavigationMenuItemEdit( { | ||
attributes, | ||
|
@@ -50,40 +77,48 @@ function NavigationMenuItemEdit( { | |
setAttributes, | ||
insertMenuItemBlock, | ||
} ) { | ||
const [ isLinkOpen, setIsLinkOpen ] = useState( false ); | ||
const [ isEditingLink, setIsEditingLink ] = useState( false ); | ||
const [ urlInput, setUrlInput ] = useState( null ); | ||
const { label, opensInNewTab, title, url } = attributes; | ||
const link = title ? { title, url } : null; | ||
getdave marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const [ isLinkOpen, setIsLinkOpen ] = useState( ! label && isSelected ); | ||
|
||
const inputValue = urlInput !== null ? urlInput : url; | ||
let onCloseTimerId = null; | ||
|
||
const onKeyDown = ( event ) => { | ||
if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { | ||
// Stop the key event from propagating up to ObserveTyping.startTypingInTextField. | ||
event.stopPropagation(); | ||
/** | ||
* It's a kind of hack to handle closing the LinkControl popover | ||
* clicking on the ToolbarButton link. | ||
*/ | ||
useEffect( () => { | ||
if ( ! isSelected ) { | ||
setIsLinkOpen( false ); | ||
} | ||
}; | ||
|
||
const closeURLPopover = () => { | ||
setIsEditingLink( false ); | ||
setUrlInput( null ); | ||
setIsLinkOpen( false ); | ||
}; | ||
return () => { | ||
// Clear LinkControl.OnClose timeout. | ||
if ( onCloseTimerId ) { | ||
clearTimeout( onCloseTimerId ); | ||
} | ||
}; | ||
}, [ isSelected ] ); | ||
|
||
const autocompleteRef = useRef( null ); | ||
/** | ||
* `onKeyDown` LinkControl handler. | ||
* It takes over to stop the event propagation to make the | ||
* navigation work, avoiding undesired behaviors. | ||
* For instance, it will block to move between menu items | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess other use cases of LinkControl will face similar issues. Can we address this problem at the LinkControl level? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well, it's a topic that has been quite discussed in the development of the LinkControl. At that moment we decided to delegate event handling to the component which consumes it. However, it's a valid suggestion and we could consider delegating this handling to the LinkControl itself. We will need to analyze most scenarios as possible in order to avoid blocking some desired behaviors. For instance, how it should act in a paragraph context? |
||
* when the LinkControl is focused. | ||
* | ||
* @param {Event} event | ||
*/ | ||
const handleLinkControlOnKeyDown = ( event ) => { | ||
const { keyCode } = event; | ||
|
||
const onFocusOutside = ( event ) => { | ||
const autocompleteElement = autocompleteRef.current; | ||
if ( autocompleteElement && autocompleteElement.contains( event.target ) ) { | ||
return; | ||
if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( keyCode ) > -1 ) { | ||
// Stop the key event from propagating up to ObserveTyping.startTypingInTextField. | ||
event.stopPropagation(); | ||
} | ||
closeURLPopover(); | ||
}; | ||
|
||
const stopPropagation = ( event ) => { | ||
event.stopPropagation(); | ||
}; | ||
|
||
const { label, url } = attributes; | ||
const itemLabelPlaceholder = __( 'Add item…' ); | ||
|
||
return ( | ||
<Fragment> | ||
|
@@ -93,51 +128,29 @@ function NavigationMenuItemEdit( { | |
name="link" | ||
icon="admin-links" | ||
title={ __( 'Link' ) } | ||
onClick={ () => setIsLinkOpen( ! isLinkOpen ) } | ||
onClick={ () => { | ||
if ( isLinkOpen ) { | ||
return; | ||
} | ||
setIsLinkOpen( ! isLinkOpen ); | ||
} } | ||
/> | ||
{ <ToolbarButton | ||
<ToolbarButton | ||
name="submenu" | ||
icon={ <SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24"><Path d="M14 5h8v2h-8zm0 5.5h8v2h-8zm0 5.5h8v2h-8zM2 11.5C2 15.08 4.92 18 8.5 18H9v2l3-3-3-3v2h-.5C6.02 16 4 13.98 4 11.5S6.02 7 8.5 7H12V5H8.5C4.92 5 2 7.92 2 11.5z" /><Path fill="none" d="M0 0h24v24H0z" /></SVG> } | ||
title={ __( 'Add submenu item' ) } | ||
onClick={ insertMenuItemBlock } | ||
/> } | ||
/> | ||
</Toolbar> | ||
{ isLinkOpen && | ||
<> | ||
<URLPopover | ||
className="wp-block-navigation-menu-item__inline-link-input" | ||
onClose={ closeURLPopover } | ||
onFocusOutside={ onFocusOutside } | ||
> | ||
{ ( ! url || isEditingLink ) && | ||
<URLPopover.LinkEditor | ||
value={ inputValue } | ||
onChangeInputValue={ setUrlInput } | ||
onKeyPress={ stopPropagation } | ||
onKeyDown={ onKeyDown } | ||
onSubmit={ ( event ) => event.preventDefault() } | ||
autocompleteRef={ autocompleteRef } | ||
/> | ||
} | ||
{ ( url && ! isEditingLink ) && | ||
<URLPopover.LinkViewer | ||
onKeyPress={ stopPropagation } | ||
url={ url } | ||
/> | ||
} | ||
|
||
</URLPopover> | ||
</> | ||
} | ||
</BlockControls> | ||
<InspectorControls> | ||
<PanelBody | ||
title={ __( 'Menu Settings' ) } | ||
> | ||
<ToggleControl | ||
checked={ attributes.opensInNewTab } | ||
onChange={ ( opensInNewTab ) => { | ||
setAttributes( { opensInNewTab } ); | ||
onChange={ ( newTab ) => { | ||
setAttributes( { opensInNewTab: newTab } ); | ||
} } | ||
label={ __( 'Open in new tab' ) } | ||
/> | ||
|
@@ -154,8 +167,8 @@ function NavigationMenuItemEdit( { | |
> | ||
<TextControl | ||
value={ attributes.title || '' } | ||
onChange={ ( title ) => { | ||
setAttributes( { title } ); | ||
onChange={ ( itemTitle ) => { | ||
setAttributes( { title: itemTitle } ); | ||
} } | ||
label={ __( 'Title Attribute' ) } | ||
help={ __( 'Provide more context about where the link goes.' ) } | ||
|
@@ -186,13 +199,29 @@ function NavigationMenuItemEdit( { | |
'is-selected': isSelected, | ||
} ) } | ||
> | ||
<RichText | ||
className="wp-block-navigation-menu-item__content" | ||
value={ label } | ||
onChange={ ( labelValue ) => setAttributes( { label: labelValue } ) } | ||
placeholder={ __( 'Add item…' ) } | ||
withoutInteractiveFormatting | ||
/> | ||
<div className="wp-block-navigation-menu-item__inner"> | ||
<RichText | ||
className="wp-block-navigation-menu-item__content" | ||
value={ label } | ||
onChange={ ( labelValue ) => setAttributes( { label: labelValue } ) } | ||
placeholder={ itemLabelPlaceholder } | ||
withoutInteractiveFormatting | ||
/> | ||
{ isLinkOpen && ( | ||
<LinkControl | ||
className="wp-block-navigation-menu-item__inline-link-input" | ||
onKeyDown={ handleLinkControlOnKeyDown } | ||
onKeyPress={ ( event ) => event.stopPropagation() } | ||
currentLink={ link } | ||
onLinkChange={ updateLink( setAttributes, label ) } | ||
onClose={ () => { | ||
onCloseTimerId = setTimeout( () => setIsLinkOpen( false ), 100 ); | ||
} } | ||
currentSettings={ { 'new-tab': opensInNewTab } } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This PR will cause this to break There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No worries, it just happens. Let's see which ships first and then let's simply update the looser :-D |
||
onSettingsChange={ updateLinkSetting( setAttributes ) } | ||
/> | ||
) } | ||
</div> | ||
<InnerBlocks | ||
allowedBlocks={ [ 'core/navigation-menu-item' ] } | ||
renderAppender={ hasDescendants ? InnerBlocks.ButtonBlockAppender : false } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,13 +64,36 @@ | |
} | ||
|
||
.wp-block-navigation-menu-item { | ||
font-family: inherit; | ||
font-size: inherit; | ||
background-color: var(--background-color-menu-link); | ||
color: var(--color-menu-link); | ||
&:not(.is-editing) { | ||
font-family: inherit; | ||
font-size: inherit; | ||
background-color: var(--background-color-menu-link); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a specific problem form this PR, but I think we can not use CSS variables as they are not supported by IE11 https://caniuse.com/#feat=css-variables. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The decision for the nav menu block was to implement it using forward looking tech and simply gracefully degrade it for IE, in this case not showing the color controls at all. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually we've created a PR that removes the CSS vars. |
||
color: var(--color-menu-link); | ||
width: inherit; | ||
|
||
.components-external-link { | ||
color: var(--color-menu-link); | ||
} | ||
} | ||
|
||
&:focus { | ||
&.is-editing .components-text-control__input { | ||
width: inherit; | ||
background-color: var(--background-color-menu-link); | ||
color: var(--color-menu-link); | ||
|
||
.components-external-link { | ||
color: var(--color-menu-link); | ||
&:focus { | ||
color: var(--color-menu-link); | ||
} | ||
} | ||
} | ||
|
||
&.is-editing, | ||
&.is-selected { | ||
box-shadow: 0 0 1px $light-gray-900 inset; | ||
border-radius: 4px; | ||
min-width: 30px; | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR will become relevant here.
#18285