Skip to content
Closed
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
30 changes: 16 additions & 14 deletions packages/block-library/src/navigation-link/link-ui/index.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { __unstableStripHTML as stripHTML, focus } from '@wordpress/dom';
import {
Popover,
Button,
__experimentalVStack as VStack,
} from '@wordpress/components';
import { Popover, Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { LinkControl, useBlockEditingMode } from '@wordpress/block-editor';
import {
Expand All @@ -16,7 +12,7 @@ import {
import { useResourcePermissions } from '@wordpress/core-data';
import { plus } from '@wordpress/icons';
import { useInstanceId } from '@wordpress/compose';
import { VisuallyHidden } from '@wordpress/ui';
import { Stack, VisuallyHidden } from '@wordpress/ui';
import { isURL } from '@wordpress/url';
import { LinkUIPageCreator } from './page-creator';
import LinkUIBlockInserter from './block-inserter';
Expand Down Expand Up @@ -95,6 +91,8 @@ function UnforwardedLinkUI( props, ref ) {
const [ addingBlock, setAddingBlock ] = useState( false );
const [ addingPage, setAddingPage ] = useState( false );
const [ shouldFocusPane, setShouldFocusPane ] = useState( null );
const [ pageCreatorInitialTitle, setPageCreatorInitialTitle ] =
useState( '' );
// Stable initial value for LinkControl's uncontrolled inputValue prop.
// We track the search with the searchInputValueRef, then update the
// initialSearchValue state with the observed searchInputValueRef
Expand Down Expand Up @@ -212,6 +210,7 @@ function UnforwardedLinkUI( props, ref ) {
onClose={ props.onClose }
anchor={ props.anchor }
shift
{ ...( props.popoverProps ?? {} ) }
>
{ ! addingBlock && ! addingPage && (
<div
Expand Down Expand Up @@ -273,6 +272,14 @@ function UnforwardedLinkUI( props, ref ) {
setAddingBlock( true );
} }
setAddingPage={ () => {
const searchInputValue =
searchInputValueRef.current;
setPageCreatorInitialTitle(
searchInputValue &&
! isURL( searchInputValue )
? searchInputValue
: ''
);
setAddingPage( true );
} }
canAddPage={
Expand Down Expand Up @@ -309,12 +316,7 @@ function UnforwardedLinkUI( props, ref ) {
updateSearchValue( searchInputValueRef.current );
} }
onPageCreated={ handlePageCreated }
initialTitle={
searchInputValueRef.current &&
! isURL( searchInputValueRef.current )
? searchInputValueRef.current
: ''
}
initialTitle={ pageCreatorInitialTitle }
/>
) }
</Popover>
Expand All @@ -339,7 +341,7 @@ const LinkUITools = ( {
}

return (
<VStack spacing={ 0 } className="link-ui-tools">
<Stack spacing={ 0 } className="link-ui-tools">
{ canAddPage && (
<Button
__next40pxDefaultSize
Expand Down Expand Up @@ -368,7 +370,7 @@ const LinkUITools = ( {
{ __( 'Add block' ) }
</Button>
) }
</VStack>
</Stack>
);
};

Expand Down
123 changes: 84 additions & 39 deletions packages/block-library/src/navigation/edit/navigation-link-ui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,97 +14,137 @@ const BLOCKS_WITH_LINK_UI_SUPPORT = [
export function NavigationLinkUI( {
insertedBlockClientId,
setInsertedBlockClientId,
editingBlock,
editingPopoverAnchor,
editingPopoverProps,
setEditingBlock,
} ) {
const { updateBlockAttributes, removeBlock } =
useDispatch( blockEditorStore );

const { insertedBlockName, insertedBlockAttributes } = useSelect(
const activeBlockClientId = editingBlock?.clientId ?? insertedBlockClientId;

const { activeBlockName, activeBlockAttributes } = useSelect(
( select ) => {
const { getBlockName, getBlockAttributes } =
select( blockEditorStore );

return {
insertedBlockName: getBlockName( insertedBlockClientId ),
insertedBlockAttributes: getBlockAttributes(
insertedBlockClientId
),
activeBlockName: getBlockName( activeBlockClientId ),
activeBlockAttributes:
getBlockAttributes( activeBlockClientId ),
};
},
[ insertedBlockClientId ]
[ activeBlockClientId ]
);

const activeBlock = {
clientId: activeBlockClientId,
name: activeBlockName,
attributes: activeBlockAttributes || {},
};

const isEditingExistingBlock = Boolean( editingBlock?.clientId );
const supportsLinkControls = BLOCKS_WITH_LINK_UI_SUPPORT.includes(
activeBlock?.name
);
const showLinkControls =
BLOCKS_WITH_LINK_UI_SUPPORT?.includes( insertedBlockName );
supportsLinkControls && Boolean( activeBlockClientId );

// Get binding utilities for the inserted block
// Get binding utilities for the active block.
const { createBinding, clearBinding } = useEntityBinding( {
clientId: insertedBlockClientId,
attributes: insertedBlockAttributes || {},
clientId: activeBlockClientId,
attributes: activeBlock.attributes,
} );

if ( ! showLinkControls ) {
if ( ! showLinkControls && ! isEditingExistingBlock ) {
return null;
}

if (
isEditingExistingBlock &&
editingPopoverProps &&
! editingPopoverAnchor
) {
return null;
}

/**
* Cleanup function for auto-inserted Navigation Link blocks.
* Cleanup function for the currently active Navigation Link block.
*
* Removes the block if it has no URL and clears the inserted block state.
* This ensures consistent cleanup behavior across different contexts.
* Removes a newly inserted block if it has no URL and clears the active
* block state. Existing blocks are never removed on close.
*/
const cleanupInsertedBlock = () => {
const cleanupActiveBlock = () => {
// Prevent automatic block selection when removing blocks in list view context
// This avoids focus stealing that would close the list view and switch to canvas
const shouldAutoSelectBlock = false;

// Follows the exact same pattern as Navigation Link block's onClose handler
// If there is no URL then remove the auto-inserted block to avoid empty blocks
if ( ! insertedBlockAttributes?.url && insertedBlockClientId ) {
// but only for newly inserted blocks, never while editing an existing link.
if (
! isEditingExistingBlock &&
! activeBlock.attributes?.url &&
activeBlockClientId
) {
// Remove the block entirely to avoid poor UX
// This matches the Navigation Link block's behavior
removeBlock( insertedBlockClientId, shouldAutoSelectBlock );
removeBlock( activeBlockClientId, shouldAutoSelectBlock );
}

if ( isEditingExistingBlock ) {
setEditingBlock?.( null );
return;
}
setInsertedBlockClientId( null );

setInsertedBlockClientId?.( null );
};

const setInsertedBlockAttributes =
( _insertedBlockClientId ) => ( _updatedAttributes ) => {
if ( ! _insertedBlockClientId ) {
const setActiveBlockAttributes =
( _activeBlockClientId ) => ( _updatedAttributes ) => {
if ( ! _activeBlockClientId ) {
return;
}
updateBlockAttributes( _insertedBlockClientId, _updatedAttributes );
updateBlockAttributes( _activeBlockClientId, _updatedAttributes );
};

// Wrapper function to clean up original block when a new block is selected
const handleSetInsertedBlock = ( newBlock ) => {
// Wrapper function to clean up original block when a new block is selected.
const handleSetActiveBlock = ( newBlock ) => {
// Prevent automatic block selection when removing blocks in list view context
// This avoids focus stealing that would close the list view and switch to canvas
const shouldAutoSelectBlock = false;

// If we have an existing inserted block and a new block is being set,
// remove the original block to avoid duplicates
if ( insertedBlockClientId && newBlock ) {
removeBlock( insertedBlockClientId, shouldAutoSelectBlock );
// remove the original block to avoid duplicates.
if ( ! isEditingExistingBlock && activeBlockClientId && newBlock ) {
removeBlock( activeBlockClientId, shouldAutoSelectBlock );
}

if ( isEditingExistingBlock ) {
setEditingBlock?.( newBlock );
return;
}
setInsertedBlockClientId( newBlock?.clientId ?? null );

setInsertedBlockClientId?.( newBlock?.clientId ?? null );
};

return (
<LinkUI
clientId={ insertedBlockClientId }
link={ insertedBlockAttributes }
onBlockInsert={ handleSetInsertedBlock }
onClose={ () => {
// Use cleanup function
cleanupInsertedBlock();
} }
clientId={ activeBlockClientId }
link={ activeBlock.attributes }
anchor={ isEditingExistingBlock ? editingPopoverAnchor : undefined }
onBlockInsert={ handleSetActiveBlock }
onClose={ cleanupActiveBlock }
popoverProps={
isEditingExistingBlock ? editingPopoverProps : undefined
}
onChange={ ( updatedValue ) => {
// updateAttributes determines the final state and returns metadata
const { isEntityLink, attributes: updatedAttributes } =
updateAttributes(
updatedValue,
setInsertedBlockAttributes( insertedBlockClientId ),
insertedBlockAttributes
setActiveBlockAttributes( activeBlockClientId ),
activeBlock.attributes
);

// Handle URL binding based on the final computed state
Expand All @@ -116,7 +156,12 @@ export function NavigationLinkUI( {
clearBinding();
}

setInsertedBlockClientId( null );
if ( isEditingExistingBlock ) {
setEditingBlock?.( null );
return;
}

setInsertedBlockClientId?.( null );
} }
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { chevronUp, chevronDown, moreVertical } from '@wordpress/icons';
import { chevronUp, chevronDown, moreVertical, pencil } from '@wordpress/icons';
import { DropdownMenu, MenuItem, MenuGroup } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { useCallback } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { BlockTitle, store as blockEditorStore } from '@wordpress/block-editor';
import { privateApis as routerPrivateApis } from '@wordpress/router';
import { unlock } from '../../lock-unlock';

const POPOVER_PROPS = {
className: 'block-editor-block-settings-menu__popover',
placement: 'bottom-start',
};
import { unlock } from '../../lock-unlock';

const BLOCKS_WITH_LINK_EDIT_SUPPORT = [
'core/navigation-link',
'core/navigation-submenu',
];

const { useHistory, useLocation } = unlock( routerPrivateApis );

export default function LeafMoreMenu( props ) {
const history = useHistory();
const { path } = useLocation();
const { clientId } = props;
const { block, clientId: propClientId, setEditingBlock } = props;
const clientId = block?.clientId ?? propClientId;
const { moveBlocksDown, moveBlocksUp, removeBlocks } =
useDispatch( blockEditorStore );

Expand Down Expand Up @@ -46,6 +53,8 @@ export default function LeafMoreMenu( props ) {
[ clientId ]
);

const canEditLink = BLOCKS_WITH_LINK_EDIT_SUPPORT.includes( blockName );

const onGoToPage = useCallback( () => {
if (
attributes.kind === 'post-type' &&
Expand Down Expand Up @@ -79,6 +88,22 @@ export default function LeafMoreMenu( props ) {
{ ( { onClose } ) => (
<>
<MenuGroup>
{ canEditLink && (
<MenuItem
icon={ pencil }
onClick={ () => {
setEditingBlock?.( {
...( block ?? {} ),
clientId,
name: blockName,
attributes,
} );
onClose();
} }
>
{ __( 'Edit link' ) }
</MenuItem>
) }
<MenuItem
icon={ chevronUp }
onClick={ () => {
Expand Down
Loading
Loading