From f18f02e740e93e093b1c89b42ad09b36976b0305 Mon Sep 17 00:00:00 2001
From: Rinkal Pagdar <92097119+rinkalpagdar@users.noreply.github.com>
Date: Fri, 13 Dec 2024 16:32:50 +0530
Subject: [PATCH 1/5] Refactor Settings panel of Navigation Item block
---
package-lock.json | 1 -
.../block-library/src/navigation-link/edit.js | 537 +++++++++---------
2 files changed, 283 insertions(+), 255 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 32ff2db4986512..c3005e83127e96 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -45192,7 +45192,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
"dev": true,
- "license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js
index 39073b848d3ca8..c1219401643d33 100644
--- a/packages/block-library/src/navigation-link/edit.js
+++ b/packages/block-library/src/navigation-link/edit.js
@@ -9,12 +9,13 @@ import clsx from 'clsx';
import { createBlock } from '@wordpress/blocks';
import { useSelect, useDispatch } from '@wordpress/data';
import {
- PanelBody,
TextControl,
TextareaControl,
ToolbarButton,
Tooltip,
ToolbarGroup,
+ __experimentalToolsPanel as ToolsPanel,
+ __experimentalToolsPanelItem as ToolsPanelItem,
} from '@wordpress/components';
import { displayShortcut, isKeyboardEvent } from '@wordpress/keycodes';
import { __ } from '@wordpress/i18n';
@@ -53,61 +54,61 @@ const DEFAULT_BLOCK = { name: 'core/navigation-link' };
*
* @return {boolean} Is dragging within the target element.
*/
-const useIsDraggingWithin = ( elementRef ) => {
- const [ isDraggingWithin, setIsDraggingWithin ] = useState( false );
+const useIsDraggingWithin = (elementRef) => {
+ const [isDraggingWithin, setIsDraggingWithin] = useState(false);
- useEffect( () => {
+ useEffect(() => {
const { ownerDocument } = elementRef.current;
- function handleDragStart( event ) {
+ function handleDragStart(event) {
// Check the first time when the dragging starts.
- handleDragEnter( event );
+ handleDragEnter(event);
}
// Set to false whenever the user cancel the drag event by either releasing the mouse or press Escape.
function handleDragEnd() {
- setIsDraggingWithin( false );
+ setIsDraggingWithin(false);
}
- function handleDragEnter( event ) {
+ function handleDragEnter(event) {
// Check if the current target is inside the item element.
- if ( elementRef.current.contains( event.target ) ) {
- setIsDraggingWithin( true );
+ if (elementRef.current.contains(event.target)) {
+ setIsDraggingWithin(true);
} else {
- setIsDraggingWithin( false );
+ setIsDraggingWithin(false);
}
}
// Bind these events to the document to catch all drag events.
// Ideally, we can also use `event.relatedTarget`, but sadly that
// doesn't work in Safari.
- ownerDocument.addEventListener( 'dragstart', handleDragStart );
- ownerDocument.addEventListener( 'dragend', handleDragEnd );
- ownerDocument.addEventListener( 'dragenter', handleDragEnter );
+ ownerDocument.addEventListener('dragstart', handleDragStart);
+ ownerDocument.addEventListener('dragend', handleDragEnd);
+ ownerDocument.addEventListener('dragenter', handleDragEnter);
return () => {
- ownerDocument.removeEventListener( 'dragstart', handleDragStart );
- ownerDocument.removeEventListener( 'dragend', handleDragEnd );
- ownerDocument.removeEventListener( 'dragenter', handleDragEnter );
+ ownerDocument.removeEventListener('dragstart', handleDragStart);
+ ownerDocument.removeEventListener('dragend', handleDragEnd);
+ ownerDocument.removeEventListener('dragenter', handleDragEnter);
};
- }, [ elementRef ] );
+ }, [elementRef]);
return isDraggingWithin;
};
-const useIsInvalidLink = ( kind, type, id ) => {
+const useIsInvalidLink = (kind, type, id) => {
const isPostType =
kind === 'post-type' || type === 'post' || type === 'page';
- const hasId = Number.isInteger( id );
+ const hasId = Number.isInteger(id);
const postStatus = useSelect(
- ( select ) => {
- if ( ! isPostType ) {
+ (select) => {
+ if (!isPostType) {
return null;
}
- const { getEntityRecord } = select( coreStore );
- return getEntityRecord( 'postType', type, id )?.status;
+ const { getEntityRecord } = select(coreStore);
+ return getEntityRecord('postType', type, id)?.status;
},
- [ isPostType, type, id ]
+ [isPostType, type, id]
);
// Check Navigation Link validity if:
@@ -122,32 +123,32 @@ const useIsInvalidLink = ( kind, type, id ) => {
isPostType && hasId && postStatus && 'trash' === postStatus;
const isDraft = 'draft' === postStatus;
- return [ isInvalid, isDraft ];
+ return [isInvalid, isDraft];
};
-function getMissingText( type ) {
+function getMissingText(type) {
let missingText = '';
- switch ( type ) {
+ switch (type) {
case 'post':
/* translators: label for missing post in navigation link block */
- missingText = __( 'Select post' );
+ missingText = __('Select post');
break;
case 'page':
/* translators: label for missing page in navigation link block */
- missingText = __( 'Select page' );
+ missingText = __('Select page');
break;
case 'category':
/* translators: label for missing category in navigation link block */
- missingText = __( 'Select category' );
+ missingText = __('Select category');
break;
case 'tag':
/* translators: label for missing tag in navigation link block */
- missingText = __( 'Select tag' );
+ missingText = __('Select tag');
break;
default:
/* translators: label for missing values in navigation link block */
- missingText = __( 'Add link' );
+ missingText = __('Add link');
}
return missingText;
@@ -158,78 +159,108 @@ function getMissingText( type ) {
* packages/block-library/src/navigation-submenu/edit.js
* Consider reuseing this components for both blocks.
*/
-function Controls( { attributes, setAttributes, setIsLabelFieldFocused } ) {
+function Controls({ attributes, setAttributes, setIsLabelFieldFocused }) {
const { label, url, description, title, rel } = attributes;
return (
-
- {
- setAttributes( { label: labelValue } );
- } }
- label={ __( 'Text' ) }
- autoComplete="off"
- onFocus={ () => setIsLabelFieldFocused( true ) }
- onBlur={ () => setIsLabelFieldFocused( false ) }
- />
- {
- updateAttributes(
- { url: urlValue },
- setAttributes,
- attributes
- );
- } }
- label={ __( 'Link' ) }
- autoComplete="off"
- />
- {
- setAttributes( { description: descriptionValue } );
- } }
- label={ __( 'Description' ) }
- help={ __(
- 'The description will be displayed in the menu if the current theme supports it.'
- ) }
- />
- {
- setAttributes( { title: titleValue } );
- } }
- label={ __( 'Title attribute' ) }
- autoComplete="off"
- help={ __(
- 'Additional information to help clarify the purpose of the link.'
- ) }
- />
- {
- setAttributes( { rel: relValue } );
- } }
- label={ __( 'Rel attribute' ) }
- autoComplete="off"
- help={ __(
- 'The relationship of the linked URL as space-separated link types.'
- ) }
- />
-
+
+ !!label}
+ label={__('Text')}
+ onDeselect={() => setAttributes({ label: '' })}
+ >
+ {
+ setAttributes({ label: labelValue });
+ }}
+ autoComplete="off"
+ onFocus={() => setIsLabelFieldFocused(true)}
+ onBlur={() => setIsLabelFieldFocused(false)}
+ />
+
+
+ !!url}
+ label={__('Link')}
+ onDeselect={() => setAttributes({ url: '' })}
+ >
+ {
+ updateAttributes({ url: urlValue }, setAttributes, attributes);
+ }}
+ autoComplete="off"
+ />
+
+
+ !!description}
+ label={__('Description')}
+ onDeselect={() => setAttributes({ description: '' })}
+ >
+ {
+ setAttributes({ description: descriptionValue });
+ }}
+ help={__(
+ 'The description will be displayed in the menu if the current theme supports it.'
+ )}
+ />
+
+
+ !!title}
+ label={__('Title attribute')}
+ onDeselect={() => setAttributes({ title: '' })}
+ >
+ {
+ setAttributes({ title: titleValue });
+ }}
+ autoComplete="off"
+ help={__(
+ 'Additional information to help clarify the purpose of the link.'
+ )}
+ />
+
+
+ !!rel}
+ label={__('Rel attribute')}
+ onDeselect={() => setAttributes({ rel: '' })}
+ >
+ {
+ setAttributes({ rel: relValue });
+ }}
+ autoComplete="off"
+ help={__(
+ 'The relationship of the linked URL as space-separated link types.'
+ )}
+ />
+
+
);
}
-export default function NavigationLinkEdit( {
+export default function NavigationLinkEdit({
attributes,
isSelected,
setAttributes,
@@ -238,10 +269,10 @@ export default function NavigationLinkEdit( {
onReplace,
context,
clientId,
-} ) {
+}) {
const { id, label, type, url, description, kind } = attributes;
- const [ isInvalid, isDraft ] = useIsInvalidLink( kind, type, id );
+ const [isInvalid, isDraft] = useIsInvalidLink(kind, type, id);
const { maxNestingLevel } = context;
const {
@@ -249,24 +280,24 @@ export default function NavigationLinkEdit( {
__unstableMarkNextChangeAsNotPersistent,
selectBlock,
selectPreviousBlock,
- } = useDispatch( blockEditorStore );
+ } = useDispatch(blockEditorStore);
// Have the link editing ui open on mount when lacking a url and selected.
- const [ isLinkOpen, setIsLinkOpen ] = useState( isSelected && ! url );
+ const [isLinkOpen, setIsLinkOpen] = useState(isSelected && !url);
// Store what element opened the popover, so we know where to return focus to (toolbar button vs navigation link text)
- const [ openedBy, setOpenedBy ] = useState( null );
+ const [openedBy, setOpenedBy] = useState(null);
// Use internal state instead of a ref to make sure that the component
// re-renders when the popover's anchor updates.
- const [ popoverAnchor, setPopoverAnchor ] = useState( null );
- const listItemRef = useRef( null );
- const isDraggingWithin = useIsDraggingWithin( listItemRef );
- const itemLabelPlaceholder = __( 'Add label…' );
+ const [popoverAnchor, setPopoverAnchor] = useState(null);
+ const listItemRef = useRef(null);
+ const isDraggingWithin = useIsDraggingWithin(listItemRef);
+ const itemLabelPlaceholder = __('Add label…');
const ref = useRef();
const linkUIref = useRef();
- const prevUrl = usePrevious( url );
+ const prevUrl = usePrevious(url);
// Change the label using inspector causes rich text to change focus on firefox.
// This is a workaround to keep the focus on the label field when label filed is focused we don't render the rich text.
- const [ isLabelFieldFocused, setIsLabelFieldFocused ] = useState( false );
+ const [isLabelFieldFocused, setIsLabelFieldFocused] = useState(false);
const {
isAtMaxNesting,
@@ -274,76 +305,76 @@ export default function NavigationLinkEdit( {
isParentOfSelectedBlock,
hasChildren,
} = useSelect(
- ( select ) => {
+ (select) => {
const {
getBlockCount,
getBlockName,
getBlockRootClientId,
hasSelectedInnerBlock,
getBlockParentsByBlockName,
- } = select( blockEditorStore );
+ } = select(blockEditorStore);
return {
isAtMaxNesting:
- getBlockParentsByBlockName( clientId, [
+ getBlockParentsByBlockName(clientId, [
'core/navigation-link',
'core/navigation-submenu',
- ] ).length >= maxNestingLevel,
+ ]).length >= maxNestingLevel,
isTopLevelLink:
- getBlockName( getBlockRootClientId( clientId ) ) ===
+ getBlockName(getBlockRootClientId(clientId)) ===
'core/navigation',
isParentOfSelectedBlock: hasSelectedInnerBlock(
clientId,
true
),
- hasChildren: !! getBlockCount( clientId ),
+ hasChildren: !!getBlockCount(clientId),
};
},
- [ clientId, maxNestingLevel ]
+ [clientId, maxNestingLevel]
);
- const { getBlocks } = useSelect( blockEditorStore );
+ const { getBlocks } = useSelect(blockEditorStore);
/**
* Transform to submenu block.
*/
const transformToSubmenu = () => {
- let innerBlocks = getBlocks( clientId );
- if ( innerBlocks.length === 0 ) {
- innerBlocks = [ createBlock( 'core/navigation-link' ) ];
- selectBlock( innerBlocks[ 0 ].clientId );
+ let innerBlocks = getBlocks(clientId);
+ if (innerBlocks.length === 0) {
+ innerBlocks = [createBlock('core/navigation-link')];
+ selectBlock(innerBlocks[0].clientId);
}
const newSubmenu = createBlock(
'core/navigation-submenu',
attributes,
innerBlocks
);
- replaceBlock( clientId, newSubmenu );
+ replaceBlock(clientId, newSubmenu);
};
- useEffect( () => {
+ useEffect(() => {
// If block has inner blocks, transform to Submenu.
- if ( hasChildren ) {
+ if (hasChildren) {
// This side-effect should not create an undo level as those should
// only be created via user interactions.
__unstableMarkNextChangeAsNotPersistent();
transformToSubmenu();
}
- }, [ hasChildren ] );
+ }, [hasChildren]);
// If the LinkControl popover is open and the URL has changed, close the LinkControl and focus the label text.
- useEffect( () => {
+ useEffect(() => {
// We only want to do this when the URL has gone from nothing to a new URL AND the label looks like a URL
if (
- ! prevUrl &&
+ !prevUrl &&
url &&
isLinkOpen &&
- isURL( prependHTTP( label ) ) &&
- /^.+\.[a-z]+/.test( label )
+ isURL(prependHTTP(label)) &&
+ /^.+\.[a-z]+/.test(label)
) {
// Focus and select the label text.
selectLabelText();
}
- }, [ prevUrl, url, isLinkOpen, label ] );
+ }, [prevUrl, url, isLinkOpen, label]);
/**
* Focus the Link label text and select it.
@@ -355,9 +386,9 @@ export default function NavigationLinkEdit( {
const selection = defaultView.getSelection();
const range = ownerDocument.createRange();
// Get the range of the current ref contents so we can add this range to the selection.
- range.selectNodeContents( ref.current );
+ range.selectNodeContents(ref.current);
selection.removeAllRanges();
- selection.addRange( range );
+ selection.addRange(range);
}
/**
@@ -369,17 +400,17 @@ export default function NavigationLinkEdit( {
// to their default values otherwise this may
// in advertently trigger side effects because
// the values will have "changed".
- setAttributes( {
+ setAttributes({
url: undefined,
label: undefined,
id: undefined,
kind: undefined,
type: undefined,
opensInNewTab: false,
- } );
+ });
// Close the link editing UI.
- setIsLinkOpen( false );
+ setIsLinkOpen(false);
}
const {
@@ -387,40 +418,40 @@ export default function NavigationLinkEdit( {
customTextColor,
backgroundColor,
customBackgroundColor,
- } = getColors( context, ! isTopLevelLink );
+ } = getColors(context, !isTopLevelLink);
- function onKeyDown( event ) {
- if ( isKeyboardEvent.primary( event, 'k' ) ) {
+ function onKeyDown(event) {
+ if (isKeyboardEvent.primary(event, 'k')) {
// Required to prevent the command center from opening,
// as it shares the CMD+K shortcut.
// See https://github.com/WordPress/gutenberg/pull/59845.
event.preventDefault();
// If this link is a child of a parent submenu item, the parent submenu item event will also open, closing this popover
event.stopPropagation();
- setIsLinkOpen( true );
- setOpenedBy( ref.current );
+ setIsLinkOpen(true);
+ setOpenedBy(ref.current);
}
}
- const blockProps = useBlockProps( {
- ref: useMergeRefs( [ setPopoverAnchor, listItemRef ] ),
- className: clsx( 'wp-block-navigation-item', {
+ const blockProps = useBlockProps({
+ ref: useMergeRefs([setPopoverAnchor, listItemRef]),
+ className: clsx('wp-block-navigation-item', {
'is-editing': isSelected || isParentOfSelectedBlock,
'is-dragging-within': isDraggingWithin,
- 'has-link': !! url,
+ 'has-link': !!url,
'has-child': hasChildren,
- 'has-text-color': !! textColor || !! customTextColor,
- [ getColorClassName( 'color', textColor ) ]: !! textColor,
- 'has-background': !! backgroundColor || customBackgroundColor,
- [ getColorClassName( 'background-color', backgroundColor ) ]:
- !! backgroundColor,
- } ),
+ 'has-text-color': !!textColor || !!customTextColor,
+ [getColorClassName('color', textColor)]: !!textColor,
+ 'has-background': !!backgroundColor || customBackgroundColor,
+ [getColorClassName('background-color', backgroundColor)]:
+ !!backgroundColor,
+ }),
style: {
- color: ! textColor && customTextColor,
- backgroundColor: ! backgroundColor && customBackgroundColor,
+ color: !textColor && customTextColor,
+ backgroundColor: !backgroundColor && customBackgroundColor,
},
onKeyDown,
- } );
+ });
const innerBlocksProps = useInnerBlocksProps(
{
@@ -434,26 +465,25 @@ export default function NavigationLinkEdit( {
}
);
- if ( ! url || isInvalid || isDraft ) {
+ if (!url || isInvalid || isDraft) {
blockProps.onClick = () => {
- setIsLinkOpen( true );
- setOpenedBy( ref.current );
+ setIsLinkOpen(true);
+ setOpenedBy(ref.current);
};
}
- const classes = clsx( 'wp-block-navigation-item__content', {
- 'wp-block-navigation-link__placeholder': ! url || isInvalid || isDraft,
- } );
+ const classes = clsx('wp-block-navigation-item__content', {
+ 'wp-block-navigation-link__placeholder': !url || isInvalid || isDraft,
+ });
- const missingText = getMissingText( type );
+ const missingText = getMissingText(type);
/* translators: Whether the navigation link is Invalid or a Draft. */
- const placeholderText = `(${
- isInvalid ? __( 'Invalid' ) : __( 'Draft' )
- })`;
+ const placeholderText = `(${isInvalid ? __('Invalid') : __('Draft')
+ })`;
const tooltipText =
isInvalid || isDraft
- ? __( 'This item has been deleted, or is a draft' )
- : __( 'This item is missing a link' );
+ ? __('This item has been deleted, or is a draft')
+ : __('This item is missing a link');
return (
<>
@@ -461,117 +491,116 @@ export default function NavigationLinkEdit( {
{
- setIsLinkOpen( true );
- setOpenedBy( event.currentTarget );
- } }
+ icon={linkIcon}
+ title={__('Link')}
+ shortcut={displayShortcut.primary('k')}
+ onClick={(event) => {
+ setIsLinkOpen(true);
+ setOpenedBy(event.currentTarget);
+ }}
/>
- { ! isAtMaxNesting && (
+ {!isAtMaxNesting && (
- ) }
+ )}
- { /* Warning, this duplicated in packages/block-library/src/navigation-submenu/edit.js */ }
+ { /* Warning, this duplicated in packages/block-library/src/navigation-submenu/edit.js */}
-