diff --git a/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js b/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js index 8412b221c9b536..f29a0f06e2ecb0 100644 --- a/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js +++ b/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js @@ -11,6 +11,8 @@ 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 @@ -18,20 +20,20 @@ import { getLayoutType } from '../../layouts'; * 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, diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 57ebe3b4793f52..c2fa59c92c2d88 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -176,7 +176,7 @@ class Inserter extends Component { onSelectOrClose, } = this.props; - if ( hasSingleBlockType || directInsertBlock?.length ) { + if ( hasSingleBlockType || directInsertBlock ) { return this.renderToggle( { onToggle: insertOnlyAllowedBlock } ); } @@ -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, @@ -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 ); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 78f0ab83207c6f..3ad6ce359ad552 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -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} attributesToCopy Attributes to be copied from adjecent blocks when inserted. */ export const __experimentalGetDirectInsertBlock = createSelector( ( state, rootClientId = null ) => { diff --git a/packages/block-library/src/buttons/edit.js b/packages/block-library/src/buttons/edit.js index 709c0d601d12b4..05c95609c7482c 100644 --- a/packages/block-library/src/buttons/edit.js +++ b/packages/block-library/src/buttons/edit.js @@ -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 ) => { @@ -26,6 +41,8 @@ function ButtonsEdit( { attributes: { layout = {} } } ) { const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: ALLOWED_BLOCKS, + __experimentalDefaultBlock: DEFAULT_BLOCK, + __experimentalDirectInsert: true, template: [ [ buttonBlockName, diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index f97137623ae651..e6ac37c579dc62 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -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; diff --git a/packages/block-library/src/navigation/edit/inner-blocks.js b/packages/block-library/src/navigation/edit/inner-blocks.js index 8a893c254f0999..1becf0873fce7d 100644 --- a/packages/block-library/src/navigation/edit/inner-blocks.js +++ b/packages/block-library/src/navigation/edit/inner-blocks.js @@ -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',