From b138818dd4860151eb345606b1396a198572aefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 6 Nov 2019 05:22:45 -0300 Subject: [PATCH] NavigationMenu: integration. (#18062) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * link-control: doc * navigation-menu-item: replace URLPopover by LinkControl * navigation-menu: do not set link when onClose * navigation-menu-item: ensuring hide popover once it closes * navigation-menu-item: anchor LinkControl at TextControl * navigation-menu-item: render external link * navigation-menu-item: remove link object from attrs * navigation-menu-item: handing close link popover * navigation-menu: apply colors correctly in edition mode * navigation-menu: set text color to external link * navigation-menu-item: apply text color to external link * fixing eslint errors/warnings * fix camelcase warning flawlessly props to @getdave * navigation-menu-item: set current link as null when it changes * link-control: doc fixes * Updates setting change handler to test for specific setting and value Addresses https://github.com/WordPress/gutenberg/pull/18062#discussion_r341001366 * Remove hyperlinks from nav item labels Addresses https://github.com/WordPress/gutenberg/pull/18062#discussion_r341005499 * Update attribute name Addresses https://github.com/WordPress/gutenberg/pull/18062#discussion_r341006979 * Fix save output to handle new tab attribute Addresses https://github.com/WordPress/gutenberg/pull/18062#discussion_r341008648 * Update to simplify link update with default args Addresses https://github.com/WordPress/gutenberg/pull/18062#discussion_r341028925 * Remove content accidentally re-added in merge of rebase * navigation-menu-item: set title as label when it's empty * navigation-menu-item: open LinkControl when new item * navigation-menu-item: new item workflow open link control and show fake placeholder when it's a new iotem * navigation-menu: rename state hook * navigation-menu-item: use camelCase for link ID * navigation-menu-item: set edit mode style * navigation-menu-item: remove fake placeholder stuff * navigation-menu-item: no use effect to Initialize is open state * navigation-menu-item: remove unneeded setting state * navigation-menu-item: move link control callback outside of the component rendering * navigation-menu-item: simplify closing linkcontrol It adds a hacky solution to deal with both events that happen at the same time: LinkControl.onClose and ToolbatButton.onClick, removing the useState ussage in favor adding a setTimeout call. * navigation-menu: fixing eslint errors * Update packages/block-library/src/navigation-menu-item/edit.js Co-Authored-By: Daniel Richards * Update packages/block-library/src/navigation-menu-item/edit.js Co-Authored-By: Daniel Richards * Fixes bug where Link UI was visually divorced from target Menu Item Addresses https://github.com/WordPress/gutenberg/pull/18062#discussion_r342398474 * Updates to remove unwanted prop being passed to LinkControl `fetchSearchSuggestions` seems to have been accidentally re-added. It’s not required. Addresses https://github.com/WordPress/gutenberg/pull/18062#discussion_r342399619 * navigation-menu: remove `destination` attr * navigation-menu-item: cleanup timer once unmount --- .../src/components/link-control/README.md | 13 ++ .../src/navigation-menu-item/block.json | 6 + .../src/navigation-menu-item/edit.js | 173 ++++++++++-------- .../src/navigation-menu-item/editor.scss | 33 +++- .../block-library/src/navigation-menu/edit.js | 16 +- .../src/navigation-menu/index.php | 9 + 6 files changed, 168 insertions(+), 82 deletions(-) diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index a887f092933a14..d458448175ee9e 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -58,6 +58,19 @@ through of its function parameter. - Type: `Function` - Required: No +Use this callback to take an action after a user set or updated a link. +The function callback will receive the selected item, or Null. + +```es6 + { + item + ? console.log( `The item selected has the ${ item.id } id.` ) + : console.warn( 'No Item selected.' ); + } +/> +``` + ### onSettingChange - Type: `Function` diff --git a/packages/block-library/src/navigation-menu-item/block.json b/packages/block-library/src/navigation-menu-item/block.json index 20b33eca1f4a05..00e59fc5224f54 100644 --- a/packages/block-library/src/navigation-menu-item/block.json +++ b/packages/block-library/src/navigation-menu-item/block.json @@ -12,9 +12,15 @@ "title": { "type": "string" }, + "type": { + "type": "string" + }, "description": { "type": "string" }, + "linkId": { + "type": "number" + }, "opensInNewTab": { "type": "boolean", "default": false diff --git a/packages/block-library/src/navigation-menu-item/edit.js b/packages/block-library/src/navigation-menu-item/edit.js index b1e6ab69f24df5..9a0273c5de3c57 100644 --- a/packages/block-library/src/navigation-menu-item/edit.js +++ b/packages/block-library/src/navigation-menu-item/edit.js @@ -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; + 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 + * 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 ( @@ -93,42 +128,20 @@ function NavigationMenuItemEdit( { name="link" icon="admin-links" title={ __( 'Link' ) } - onClick={ () => setIsLinkOpen( ! isLinkOpen ) } + onClick={ () => { + if ( isLinkOpen ) { + return; + } + setIsLinkOpen( ! isLinkOpen ); + } } /> - { } title={ __( 'Add submenu item' ) } onClick={ insertMenuItemBlock } - /> } + /> - { isLinkOpen && - <> - - { ( ! url || isEditingLink ) && - event.preventDefault() } - autocompleteRef={ autocompleteRef } - /> - } - { ( url && ! isEditingLink ) && - - } - - - - } { - setAttributes( { opensInNewTab } ); + onChange={ ( newTab ) => { + setAttributes( { opensInNewTab: newTab } ); } } label={ __( 'Open in new tab' ) } /> @@ -154,8 +167,8 @@ function NavigationMenuItemEdit( { > { - 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, } ) } > - setAttributes( { label: labelValue } ) } - placeholder={ __( 'Add item…' ) } - withoutInteractiveFormatting - /> +
+ setAttributes( { label: labelValue } ) } + placeholder={ itemLabelPlaceholder } + withoutInteractiveFormatting + /> + { isLinkOpen && ( + event.stopPropagation() } + currentLink={ link } + onLinkChange={ updateLink( setAttributes, label ) } + onClose={ () => { + onCloseTimerId = setTimeout( () => setIsLinkOpen( false ), 100 ); + } } + currentSettings={ { 'new-tab': opensInNewTab } } + onSettingsChange={ updateLinkSetting( setAttributes ) } + /> + ) } +
{ - return [ 'core/navigation-menu-item', - { label: page.title.rendered, url: page.permalink_template }, - ]; - } ); + + return pages.map( ( { title, type, link: url, id: linkId } ) => ( + [ 'core/navigation-menu-item', { + label: title.rendered, + title: title.raw, + type, + linkId, + url, + opensInNewTab: false, + } ] + ) ); }, [ pages ] ); diff --git a/packages/block-library/src/navigation-menu/index.php b/packages/block-library/src/navigation-menu/index.php index b49a41a06aa11c..02ba29277d7d25 100644 --- a/packages/block-library/src/navigation-menu/index.php +++ b/packages/block-library/src/navigation-menu/index.php @@ -102,17 +102,26 @@ function build_navigation_menu_html( $block, $colors ) { class="wp-block-navigation-menu-item__link ' . $colors['text_css_classes'] . '" ' . $colors['text_inline_styles']; + // Start appending HTML attributes to anchor tag. if ( isset( $menu_item['attrs']['url'] ) ) { $html .= ' href="' . $menu_item['attrs']['url'] . '"'; } if ( isset( $menu_item['attrs']['title'] ) ) { $html .= ' title="' . $menu_item['attrs']['title'] . '"'; } + + if ( isset( $menu_item['attrs']['opensInNewTab'] ) && true === $menu_item['attrs']['opensInNewTab'] ) { + $html .= ' target="_blank" '; + } + // End appending HTML attributes to anchor tag. + + // Start anchor tag content. $html .= '>'; if ( isset( $menu_item['attrs']['label'] ) ) { $html .= $menu_item['attrs']['label']; } $html .= ''; + // End anchor tag content. if ( count( (array) $menu_item['innerBlocks'] ) > 0 ) { $html .= build_navigation_menu_html( $menu_item, $colors );