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

One hook to rule them all: preparation for a block supports API #56862

Merged
merged 8 commits into from
Dec 7, 2023
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
30 changes: 2 additions & 28 deletions packages/block-editor/src/components/block-controls/hook.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,19 @@
/**
* WordPress dependencies
*/
import { store as blocksStore } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import groups from './groups';
import { store as blockEditorStore } from '../../store';
import { useBlockEditContext } from '../block-edit/context';
import useDisplayBlockControls from '../use-display-block-controls';

export default function useBlockControlsFill( group, shareWithChildBlocks ) {
const isDisplayed = useDisplayBlockControls();
const { clientId } = useBlockEditContext();
const isParentDisplayed = useSelect(
( select ) => {
if ( ! shareWithChildBlocks ) {
return false;
}

const { getBlockName, hasSelectedInnerBlock } =
select( blockEditorStore );
const { hasBlockSupport } = select( blocksStore );

return (
hasBlockSupport(
getBlockName( clientId ),
'__experimentalExposeControlsToChildren',
false
) && hasSelectedInnerBlock( clientId )
);
},
[ shareWithChildBlocks, clientId ]
);

const { isDisplayed, isParentDisplayed } = useDisplayBlockControls();
if ( isDisplayed ) {
return groups[ group ]?.Fill;
}
if ( isParentDisplayed ) {
if ( isParentDisplayed && shareWithChildBlocks ) {
return groups.parent.Fill;
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const { createPrivateSlotFill } = unlock( componentsPrivateApis );
const { Fill, Slot } = createPrivateSlotFill( 'BlockInformation' );

const BlockInfo = ( props ) => {
const isDisplayed = useDisplayBlockControls();
const { isDisplayed } = useDisplayBlockControls();
if ( ! isDisplayed ) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function InspectorControlsFill( {
group = __experimentalGroup;
}

const isDisplayed = useDisplayBlockControls();
const { isDisplayed } = useDisplayBlockControls();
const Fill = groups[ group ]?.Fill;
if ( ! Fill ) {
warning( `Unknown InspectorControls group "${ group }" provided.` );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function InspectorControlsFill( {
);
group = __experimentalGroup;
}
const isDisplayed = useDisplayBlockControls();
const { isDisplayed } = useDisplayBlockControls();

const Fill = groups[ group ]?.Fill;
if ( ! Fill ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { store as blocksStore } from '@wordpress/blocks';

/**
* Internal dependencies
Expand All @@ -13,23 +14,28 @@ export default function useDisplayBlockControls() {
const { isSelected, clientId, name } = useBlockEditContext();
return useSelect(
( select ) => {
if ( isSelected ) {
return true;
}

const {
getBlockName,
isFirstMultiSelectedBlock,
getMultiSelectedBlockClientIds,
hasSelectedInnerBlock,
} = select( blockEditorStore );
const { hasBlockSupport } = select( blocksStore );

if ( isFirstMultiSelectedBlock( clientId ) ) {
return getMultiSelectedBlockClientIds().every(
( id ) => getBlockName( id ) === name
);
}

return false;
return {
isDisplayed:
isSelected ||
( isFirstMultiSelectedBlock( clientId ) &&
getMultiSelectedBlockClientIds().every(
( id ) => getBlockName( id ) === name
) ),
isParentDisplayed:
hasBlockSupport(
getBlockName( clientId ),
'__experimentalExposeControlsToChildren',
false
) && hasSelectedInnerBlock( clientId ),
};
},
[ clientId, isSelected, name ]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ export default function useDisplayBlockControls() {
false
);

if ( ! hideControls && isSelected ) {
return true;
}

return false;
return { isDisplayed: ! hideControls && isSelected };
},
[ clientId, isSelected, name ]
);
Expand Down
54 changes: 9 additions & 45 deletions packages/block-editor/src/hooks/align.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { createHigherOrderComponent, pure } from '@wordpress/compose';
import { createHigherOrderComponent } from '@wordpress/compose';
import { addFilter } from '@wordpress/hooks';
import {
getBlockSupport,
Expand Down Expand Up @@ -109,7 +109,7 @@ export function addAttribute( settings ) {
}

function BlockEditAlignmentToolbarControlsPure( {
blockName,
name: blockName,
align,
setAttributes,
} ) {
Expand Down Expand Up @@ -152,45 +152,14 @@ function BlockEditAlignmentToolbarControlsPure( {
);
}

// We don't want block controls to re-render when typing inside a block. `pure`
// will prevent re-renders unless props change, so only pass the needed props
// and not the whole attributes object.
const BlockEditAlignmentToolbarControls = pure(
BlockEditAlignmentToolbarControlsPure
);

/**
* Override the default edit UI to include new toolbar controls for block
* alignment, if block defines support.
*
* @param {Function} BlockEdit Original component.
*
* @return {Function} Wrapped component.
*/
export const withAlignmentControls = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
const hasAlignmentSupport = hasBlockSupport(
props.name,
'align',
false
);

return (
<>
{ hasAlignmentSupport && (
<BlockEditAlignmentToolbarControls
blockName={ props.name }
// This component is pure, so only pass needed props!
align={ props.attributes.align }
setAttributes={ props.setAttributes }
/>
) }
<BlockEdit key="edit" { ...props } />
</>
);
export default {
shareWithChildBlocks: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I'd have preferred to leave this one to its own PR, to have a clear picture of impact.

Copy link
Member Author

@ellatrix ellatrix Dec 7, 2023

Choose a reason for hiding this comment

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

It's needed, otherwise the controls are not rendered. For example for a button block, the align controls from the buttons blocks should be rendered.

edit: BlockEditAlignmentToolbarControlsPure,
attributeKeys: [ 'align' ],
Copy link
Member Author

@ellatrix ellatrix Dec 7, 2023

Choose a reason for hiding this comment

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

@youknowriad I'm using this to "subscribe" to specific attributes only. But on the other hand, extra useSelect subscription these control components are not bad per se because they are only rendered when a block is selected. If we allow that, we wouldn't need to do this declaration of which attributes need to be passed down. Either way, I'm fine with it, they're not mutually exclusive.

Copy link
Member Author

Choose a reason for hiding this comment

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

And we can decide this later when we actually make the real API.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, this is totally fine for me and to be honest, I'm not entirely sold on opening this API to the public yet but it's definitely a great improvement for us.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh sure, no public API for now :D

hasSupport( name ) {
return hasBlockSupport( name, 'align', false );
},
'withAlignmentControls'
);
};

function BlockListBlockWithDataAlign( { block: BlockListBlock, props } ) {
const { name, attributes } = props;
Expand Down Expand Up @@ -273,11 +242,6 @@ addFilter(
'core/editor/align/with-data-align',
withDataAlign
);
addFilter(
'editor.BlockEdit',
'core/editor/align/with-toolbar-controls',
withAlignmentControls
);
addFilter(
'blocks.getSaveContent.extraProps',
'core/editor/align/addAssignedAlign',
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/hooks/align.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { WIDE_ALIGNMENTS } from '@wordpress/components';
const ALIGNMENTS = [ 'left', 'center', 'right' ];

export * from './align.js';
export { default } from './align.js';

// Used to filter out blocks that don't support wide/full alignment on mobile
addFilter(
Expand Down
51 changes: 12 additions & 39 deletions packages/block-editor/src/hooks/anchor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { addFilter } from '@wordpress/hooks';
import { PanelBody, TextControl, ExternalLink } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { hasBlockSupport } from '@wordpress/blocks';
import { createHigherOrderComponent, pure } from '@wordpress/compose';
import { Platform } from '@wordpress/element';

/**
Expand Down Expand Up @@ -52,7 +51,11 @@ export function addAttribute( settings ) {
return settings;
}

function BlockEditAnchorControlPure( { blockName, anchor, setAttributes } ) {
function BlockEditAnchorControlPure( {
name: blockName,
anchor,
setAttributes,
} ) {
const blockEditingMode = useBlockEditingMode();

const isWeb = Platform.OS === 'web';
Expand Down Expand Up @@ -116,38 +119,13 @@ function BlockEditAnchorControlPure( { blockName, anchor, setAttributes } ) {
);
}

// We don't want block controls to re-render when typing inside a block. `pure`
// will prevent re-renders unless props change, so only pass the needed props
// and not the whole attributes object.
const BlockEditAnchorControl = pure( BlockEditAnchorControlPure );

/**
* Override the default edit UI to include a new block inspector control for
* assigning the anchor ID, if block supports anchor.
*
* @param {Component} BlockEdit Original component.
*
* @return {Component} Wrapped component.
*/
export const withAnchorControls = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
return (
<>
<BlockEdit { ...props } />
{ props.isSelected &&
hasBlockSupport( props.name, 'anchor' ) && (
<BlockEditAnchorControl
blockName={ props.name }
// This component is pure, so only pass needed
// props!
anchor={ props.attributes.anchor }
setAttributes={ props.setAttributes }
/>
) }
</>
);
};
}, 'withAnchorControls' );
export default {
edit: BlockEditAnchorControlPure,
attributeKeys: [ 'anchor' ],
hasSupport( name ) {
return hasBlockSupport( name, 'anchor' );
},
};

/**
* Override props assigned to save component to inject anchor ID, if block
Expand All @@ -169,11 +147,6 @@ export function addSaveProps( extraProps, blockType, attributes ) {
}

addFilter( 'blocks.registerBlockType', 'core/anchor/attribute', addAttribute );
addFilter(
'editor.BlockEdit',
'core/editor/anchor/with-inspector-controls',
withAnchorControls
);
addFilter(
'blocks.getSaveContent.extraProps',
'core/editor/anchor/save-props',
Expand Down
Loading
Loading