Skip to content

Commit

Permalink
Buttons: Preserve styling from adjacent button blocks when inserting …
Browse files Browse the repository at this point in the history
…a new button block (#37905)

* Buttons: Attempt to grab attributes from existing button blocks when inserting a new button block

* Move logic to get attributes from adjacent block to the inserter

* Clean up unneeded changes

* Fix regression with Navigation blocks

* Add extra inline comment about setting default attributes

* Refactor the directInsertBlock to be an object instead of an array, add attributesToCopy prop

* Remove innerBlocks from createBlock function call

* Allow directInsertBlock attributes to override adjacent block attributes

* Fix regression with navigation block inserter

* Swap order of attributes for createBlock, add extra Button attributes to copy

* Move logic to copy attributes to the getAdjacentBlockAttributes function

* Switch attributesToCopy to be an array of strings
  • Loading branch information
andrewserong authored Jan 20, 2022
1 parent 5e0987a commit df341a8
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,29 @@ import isShallowEqual from '@wordpress/is-shallow-equal';
import { store as blockEditorStore } from '../../store';
import { getLayoutType } from '../../layouts';

/** @typedef {import('../../selectors').WPDirectInsertBlock } WPDirectInsertBlock */

/**
* This hook is a side effect which updates the block-editor store when changes
* happen to inner block settings. The given props are transformed into a
* settings object, and if that is different from the current settings object in
* the block-editor store, then the store is updated with the new settings which
* came from props.
*
* @param {string} clientId The client ID of the block to update.
* @param {string[]} allowedBlocks An array of block names which are permitted
* in inner blocks.
* @param {?Array} __experimentalDefaultBlock The default block to insert: [ blockName, { blockAttributes } ].
* @param {?Function|boolean} __experimentalDirectInsert If a default block should be inserted directly by the
* appender.
* @param {string} [templateLock] The template lock specified for the inner
* blocks component. (e.g. "all")
* @param {boolean} captureToolbars Whether or children toolbars should be shown
* in the inner blocks component rather than on
* the child block.
* @param {string} orientation The direction in which the block
* should face.
* @param {Object} layout The layout object for the block container.
* @param {string} clientId The client ID of the block to update.
* @param {string[]} allowedBlocks An array of block names which are permitted
* in inner blocks.
* @param {?WPDirectInsertBlock} __experimentalDefaultBlock The default block to insert: [ blockName, { blockAttributes } ].
* @param {?Function|boolean} __experimentalDirectInsert If a default block should be inserted directly by the
* appender.
* @param {string} [templateLock] The template lock specified for the inner
* blocks component. (e.g. "all")
* @param {boolean} captureToolbars Whether or children toolbars should be shown
* in the inner blocks component rather than on
* the child block.
* @param {string} orientation The direction in which the block
* should face.
* @param {Object} layout The layout object for the block container.
*/
export default function useNestedSettingsUpdate(
clientId,
Expand Down
82 changes: 77 additions & 5 deletions packages/block-editor/src/components/inserter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class Inserter extends Component {
onSelectOrClose,
} = this.props;

if ( hasSingleBlockType || directInsertBlock?.length ) {
if ( hasSingleBlockType || directInsertBlock ) {
return this.renderToggle( { onToggle: insertOnlyAllowedBlock } );
}

Expand Down Expand Up @@ -251,10 +251,68 @@ export default compose( [
onSelectOrClose,
} = ownProps;

if ( ! hasSingleBlockType && ! directInsertBlock?.length ) {
if ( ! hasSingleBlockType && ! directInsertBlock ) {
return;
}

function getAdjacentBlockAttributes( attributesToCopy ) {
const { getBlock, getPreviousBlockClientId } = select(
blockEditorStore
);

if (
! attributesToCopy ||
( ! clientId && ! rootClientId )
) {
return {};
}

const result = {};
let adjacentAttributes = {};

// If there is no clientId, then attempt to get attributes
// from the last block within innerBlocks of the root block.
if ( ! clientId ) {
const parentBlock = getBlock( rootClientId );

if ( parentBlock?.innerBlocks?.length ) {
const lastInnerBlock =
parentBlock.innerBlocks[
parentBlock.innerBlocks.length - 1
];

if (
directInsertBlock &&
directInsertBlock?.name === lastInnerBlock.name
) {
adjacentAttributes = lastInnerBlock.attributes;
}
}
} else {
// Otherwise, attempt to get attributes from the
// previous block relative to the current clientId.
const currentBlock = getBlock( clientId );
const previousBlock = getBlock(
getPreviousBlockClientId( clientId )
);

if ( currentBlock?.name === previousBlock?.name ) {
adjacentAttributes =
previousBlock?.attributes || {};
}
}

// Copy over only those attributes flagged to be copied.
attributesToCopy.forEach( ( attribute ) => {
if ( adjacentAttributes.hasOwnProperty( attribute ) ) {
result[ attribute ] =
adjacentAttributes[ attribute ];
}
} );

return result;
}

function getInsertionIndex() {
const {
getBlockIndex,
Expand Down Expand Up @@ -284,9 +342,23 @@ export default compose( [

const { insertBlock } = dispatch( blockEditorStore );

const blockToInsert = directInsertBlock?.length
? createBlock( ...directInsertBlock )
: createBlock( allowedBlockType.name );
let blockToInsert;

// Attempt to augment the directInsertBlock with attributes from an adjacent block.
// This ensures styling from nearby blocks is preserved in the newly inserted block.
// See: https://github.com/WordPress/gutenberg/issues/37904
if ( directInsertBlock ) {
const newAttributes = getAdjacentBlockAttributes(
directInsertBlock.attributesToCopy
);

blockToInsert = createBlock( directInsertBlock.name, {
...( directInsertBlock.attributes || {} ),
...newAttributes,
} );
} else {
blockToInsert = createBlock( allowedBlockType.name );
}

insertBlock( blockToInsert, getInsertionIndex(), rootClientId );

Expand Down
11 changes: 8 additions & 3 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1844,10 +1844,15 @@ export const __experimentalGetAllowedBlocks = createSelector(
/**
* Returns the block to be directly inserted by the block appender.
*
* @param {Object} state Editor state.
* @param {?string} rootClientId Optional root client ID of block list.
* @param {Object} state Editor state.
* @param {?string} rootClientId Optional root client ID of block list.
*
* @return {?WPDirectInsertBlock} The block type to be directly inserted.
*
* @return {?Array} The block type to be directly inserted.
* @typedef {Object} WPDirectInsertBlock
* @property {string} name The type of block.
* @property {?Object} attributes Attributes to pass to the newly created block.
* @property {?Array<string>} attributesToCopy Attributes to be copied from adjecent blocks when inserted.
*/
export const __experimentalGetDirectInsertBlock = createSelector(
( state, rootClientId = null ) => {
Expand Down
17 changes: 17 additions & 0 deletions packages/block-library/src/buttons/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ import { name as buttonBlockName } from '../button';

const ALLOWED_BLOCKS = [ buttonBlockName ];

const DEFAULT_BLOCK = {
name: buttonBlockName,
attributesToCopy: [
'backgroundColor',
'border',
'className',
'fontFamily',
'fontSize',
'gradient',
'style',
'textColor',
'width',
],
};

function ButtonsEdit( { attributes: { layout = {} } } ) {
const blockProps = useBlockProps();
const preferredStyle = useSelect( ( select ) => {
Expand All @@ -26,6 +41,8 @@ function ButtonsEdit( { attributes: { layout = {} } } ) {

const innerBlocksProps = useInnerBlocksProps( blockProps, {
allowedBlocks: ALLOWED_BLOCKS,
__experimentalDefaultBlock: DEFAULT_BLOCK,
__experimentalDirectInsert: true,
template: [
[
buttonBlockName,
Expand Down
4 changes: 3 additions & 1 deletion packages/block-library/src/navigation-submenu/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ import { name } from './block.json';

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

const DEFAULT_BLOCK = [ 'core/navigation-link' ];
const DEFAULT_BLOCK = {
name: 'core/navigation-link',
};

const MAX_NESTING = 5;

Expand Down
4 changes: 3 additions & 1 deletion packages/block-library/src/navigation/edit/inner-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ const ALLOWED_BLOCKS = [
'core/navigation-submenu',
];

const DEFAULT_BLOCK = [ 'core/navigation-link' ];
const DEFAULT_BLOCK = {
name: 'core/navigation-link',
};

const LAYOUT = {
type: 'default',
Expand Down

0 comments on commit df341a8

Please sign in to comment.