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

Select Mode: Updates to the block toolbar #65485

Merged
merged 3 commits into from
Sep 23, 2024
Merged
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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import useBlockDisplayInformation from '../use-block-display-information';
import BlockIcon from '../block-icon';
import { useShowHoveredOrFocusedGestures } from '../block-toolbar/utils';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';

/**
* Block parent selector component, displaying the hierarchy of the
Expand All @@ -23,32 +24,34 @@ import { store as blockEditorStore } from '../../store';
*/
export default function BlockParentSelector() {
const { selectBlock } = useDispatch( blockEditorStore );
const { firstParentClientId, isVisible } = useSelect( ( select ) => {
const { parentClientId, isVisible } = useSelect( ( select ) => {
const {
getBlockName,
getBlockParents,
getSelectedBlockClientId,
getBlockEditingMode,
} = select( blockEditorStore );
getParentSectionBlock,
} = unlock( select( blockEditorStore ) );
const { hasBlockSupport } = select( blocksStore );
const selectedBlockClientId = getSelectedBlockClientId();
const parentSection = getParentSectionBlock( selectedBlockClientId );
const parents = getBlockParents( selectedBlockClientId );
const _firstParentClientId = parents[ parents.length - 1 ];
const parentBlockName = getBlockName( _firstParentClientId );
const _parentClientId = parentSection ?? parents[ parents.length - 1 ];
const parentBlockName = getBlockName( _parentClientId );
const _parentBlockType = getBlockType( parentBlockName );
return {
firstParentClientId: _firstParentClientId,
parentClientId: _parentClientId,
isVisible:
_firstParentClientId &&
getBlockEditingMode( _firstParentClientId ) === 'default' &&
_parentClientId &&
getBlockEditingMode( _parentClientId ) !== 'disabled' &&
hasBlockSupport(
_parentBlockType,
'__experimentalParentSelector',
true
),
};
}, [] );
const blockInformation = useBlockDisplayInformation( firstParentClientId );
const blockInformation = useBlockDisplayInformation( parentClientId );

// Allows highlighting the parent block outline when focusing or hovering
// the parent block selector within the child.
Expand All @@ -65,13 +68,13 @@ export default function BlockParentSelector() {
return (
<div
className="block-editor-block-parent-selector"
key={ firstParentClientId }
key={ parentClientId }
ref={ nodeRef }
{ ...showHoveredOrFocusedGestures }
>
<ToolbarButton
className="block-editor-block-parent-selector__button"
onClick={ () => selectBlock( firstParentClientId ) }
onClick={ () => selectBlock( parentClientId ) }
label={ sprintf(
/* translators: %s: Name of the block's parent. */
__( 'Select parent block: %s' ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ export function BlockSettingsDropdown( {
}
}

const shouldShowBlockParentMenuItem =
! parentBlockIsSelected && !! firstParentClientId;

return (
<BlockActions
clientIds={ clientIds }
Expand All @@ -199,124 +202,143 @@ export function BlockSettingsDropdown( {
onRemove,
onCopy,
onPasteStyles,
} ) => (
<DropdownMenu
icon={ moreVertical }
label={ __( 'Options' ) }
className="block-editor-block-settings-menu"
popoverProps={ POPOVER_PROPS }
open={ open }
onToggle={ onToggle }
noIcons
{ ...props }
>
{ ( { onClose } ) => (
<>
<MenuGroup>
<__unstableBlockSettingsMenuFirstItem.Slot
fillProps={ { onClose } }
/>
{ ! parentBlockIsSelected &&
!! firstParentClientId && (
} ) => {
// It is possible that some plugins register fills for this menu
// even if Core doesn't render anything in the block settings menu.
// in which case, we may want to render the menu anyway.
// That said for now, we can start more conservative.
const isEmpty =
! canRemove &&
! canDuplicate &&
! canInsertBlock &&
isContentOnly;

if ( isEmpty ) {
return null;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the only change to this file, the isEmpty flag was added and the dropdown is not rendered if empty.

return (
<DropdownMenu
icon={ moreVertical }
label={ __( 'Options' ) }
className="block-editor-block-settings-menu"
popoverProps={ POPOVER_PROPS }
open={ open }
onToggle={ onToggle }
noIcons
{ ...props }
>
{ ( { onClose } ) => (
<>
<MenuGroup>
<__unstableBlockSettingsMenuFirstItem.Slot
fillProps={ { onClose } }
/>
{ shouldShowBlockParentMenuItem && (
<BlockParentSelectorMenuItem
parentClientId={
firstParentClientId
}
parentBlockType={ parentBlockType }
/>
) }
{ count === 1 && (
<BlockHTMLConvertButton
clientId={ firstBlockClientId }
/>
) }
{ ! isContentOnly && (
<CopyMenuItem
clientIds={ clientIds }
onCopy={ onCopy }
shortcut={ displayShortcut.primary(
'c'
) }
/>
) }
{ canDuplicate && (
<MenuItem
onClick={ pipe(
onClose,
onDuplicate,
updateSelectionAfterDuplicate
) }
shortcut={ shortcuts.duplicate }
>
{ __( 'Duplicate' ) }
</MenuItem>
) }
{ canInsertBlock && ! isContentOnly && (
<>
{ count === 1 && (
<BlockHTMLConvertButton
clientId={ firstBlockClientId }
/>
) }
{ ! isContentOnly && (
<CopyMenuItem
clientIds={ clientIds }
onCopy={ onCopy }
shortcut={ displayShortcut.primary(
'c'
) }
/>
) }
{ canDuplicate && (
<MenuItem
onClick={ pipe(
onClose,
onInsertBefore
onDuplicate,
updateSelectionAfterDuplicate
) }
shortcut={ shortcuts.insertBefore }
shortcut={ shortcuts.duplicate }
>
{ __( 'Add before' ) }
{ __( 'Duplicate' ) }
</MenuItem>
) }
{ canInsertBlock && ! isContentOnly && (
<>
<MenuItem
onClick={ pipe(
onClose,
onInsertBefore
) }
shortcut={
shortcuts.insertBefore
}
>
{ __( 'Add before' ) }
</MenuItem>
<MenuItem
onClick={ pipe(
onClose,
onInsertAfter
) }
shortcut={
shortcuts.insertAfter
}
>
{ __( 'Add after' ) }
</MenuItem>
</>
) }
</MenuGroup>
{ canCopyStyles && ! isContentOnly && (
<MenuGroup>
<CopyMenuItem
clientIds={ clientIds }
onCopy={ onCopy }
label={ __( 'Copy styles' ) }
/>
<MenuItem onClick={ onPasteStyles }>
{ __( 'Paste styles' ) }
</MenuItem>
</MenuGroup>
) }
<BlockSettingsMenuControls.Slot
fillProps={ {
onClose,
count,
firstBlockClientId,
} }
clientIds={ clientIds }
/>
Comment on lines +310 to +317
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like in some cases this slot will output the menu group, even when it's visually empty. To reproduce, if I use Edit mode and select a list item within TT4's blog home template, the settings menu will only include Duplicate as an option, but there'll be this extra empty area:

image image

With a bit of commenting out, I think I've identified the three components that are rendering empty fills. They bail before outputting anything visually, however it results in BlockSettingsMenuControlsSlot thinking that there are three fills here and so the MenuGroup gets rendered which creates the extra space. Here's what I've commented out:

diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js
index 11b1478d584..627e32e588f 100644
--- a/packages/editor/src/components/provider/index.js
+++ b/packages/editor/src/components/provider/index.js
@@ -327,17 +327,17 @@ export const ExperimentalEditorProvider = withRegistryProvider(
 							selection={ selection }
 							settings={ blockEditorSettings }
 							useSubRegistry={ false }
 						>
 							{ children }
 							{ ! settings.__unstableIsPreviewMode && (
 								<>
-									<PatternsMenuItems />
-									<TemplatePartMenuItems />
-									<ContentOnlySettingsMenu />
+									{/* <PatternsMenuItems /> */}
+									{/* <TemplatePartMenuItems /> */}
+									{/* <ContentOnlySettingsMenu /> */}
 									{ mode === 'template-locked' && (
 										<DisableNonPageContentBlocks />
 									) }
 									{ type === 'wp_navigation' && (
 										<NavigationBlockEditingMode />
 									) }
 									<EditorKeyboardShortcuts />

That then fixes up the spacing:

image image

So, in order to fix that spacing, do we need to conditionally render the above components, or update their internals so they don't output <BlockSettingsMenuControls> unexpectedly?

Initially I was wondering if we could fix it by using CSS with something like:

.components-menu-group:has( > div:empty ) {
    display: none;
}

But unfortunately that still leaves a space due to the last menu group needing the negative margin in order for the spacing to look correct. And even with display: none, this empty group winds up getting that rule instead of the one that's visually there:

image

So, unless there's another CSS-only way to handle it, we might need to lightly refactor some of those components that are outputting empty fills?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, it's going to be very hard to solve these things by refactoring the fills. This is not the first item or the only place where we have this issue. I wonder if it's something fundamental to how slot/fills work.

If we can't find a CSS-only way for now, I think we should accept a small inconvenience for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok I think I have a solution here but also, don't want to apply it in this PR though because it's potentially impactful. So I'm going to open a separate PR for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This PR should fix it #65561

{ typeof children === 'function'
? children( { onClose } )
: Children.map( ( child ) =>
cloneElement( child, { onClose } )
) }
{ canRemove && (
<MenuGroup>
<MenuItem
onClick={ pipe(
onClose,
onInsertAfter
onRemove,
updateSelectionAfterRemove
) }
shortcut={ shortcuts.insertAfter }
shortcut={ shortcuts.remove }
>
{ __( 'Add after' ) }
{ __( 'Delete' ) }
</MenuItem>
</>
</MenuGroup>
) }
</MenuGroup>
{ canCopyStyles && ! isContentOnly && (
<MenuGroup>
<CopyMenuItem
clientIds={ clientIds }
onCopy={ onCopy }
label={ __( 'Copy styles' ) }
/>
<MenuItem onClick={ onPasteStyles }>
{ __( 'Paste styles' ) }
</MenuItem>
</MenuGroup>
) }
<BlockSettingsMenuControls.Slot
fillProps={ {
onClose,
count,
firstBlockClientId,
} }
clientIds={ clientIds }
/>
{ typeof children === 'function'
? children( { onClose } )
: Children.map( ( child ) =>
cloneElement( child, { onClose } )
) }
{ canRemove && (
<MenuGroup>
<MenuItem
onClick={ pipe(
onClose,
onRemove,
updateSelectionAfterRemove
) }
shortcut={ shortcuts.remove }
>
{ __( 'Delete' ) }
</MenuItem>
</MenuGroup>
) }
</>
) }
</DropdownMenu>
) }
</>
) }
</DropdownMenu>
);
} }
</BlockActions>
);
}
Expand Down
Loading
Loading