-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Buttons: Preserve styling from adjacent button blocks when inserting a new button block #37905
Changes from 9 commits
85aff5e
d9204b4
ed10faa
090aee8
44538f6
8d04641
a32fa27
392b83e
f21abdf
af31fee
8f7dc06
401358f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,54 @@ export default compose( [ | |
onSelectOrClose, | ||
} = ownProps; | ||
|
||
if ( ! hasSingleBlockType && ! directInsertBlock?.length ) { | ||
if ( ! hasSingleBlockType && ! directInsertBlock ) { | ||
return; | ||
} | ||
|
||
function getAdjacentBlockAttributes() { | ||
const { getBlock, getPreviousBlockClientId } = select( | ||
blockEditorStore | ||
); | ||
|
||
if ( ! clientId && ! rootClientId ) { | ||
return {}; | ||
} | ||
|
||
// 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 | ||
) { | ||
return { ...lastInnerBlock.attributes }; | ||
} | ||
} | ||
return {}; | ||
} | ||
|
||
// 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 ) { | ||
return { ...( previousBlock?.attributes || {} ) }; | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
function getInsertionIndex() { | ||
const { | ||
getBlockIndex, | ||
|
@@ -284,9 +328,37 @@ 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 that styling from existing nearby blocks are preserved in the | ||
// newly inserted block. To support intentionally clearing out certain attributes, | ||
// the attributes of the directInsertBlock override those of the adjacent block. | ||
// See: https://github.com/WordPress/gutenberg/issues/37904 | ||
if ( directInsertBlock ) { | ||
const newAttributes = {}; | ||
const adjacentBlockAttributes = getAdjacentBlockAttributes(); | ||
|
||
Object.keys( adjacentBlockAttributes ).forEach( | ||
( attributeName ) => { | ||
if ( | ||
directInsertBlock.attributesToCopy?.[ | ||
attributeName | ||
] | ||
) { | ||
newAttributes[ attributeName ] = | ||
adjacentBlockAttributes[ attributeName ]; | ||
} | ||
} | ||
); | ||
|
||
blockToInsert = createBlock( directInsertBlock.name, { | ||
...newAttributes, | ||
...( directInsertBlock.attributes || {} ), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for my curiosity and to see if I missed a test case, did you have a use case in mind where the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, thanks for asking the question! I wasn't too sure of a use case, but thought it'd still be good to allow |
||
} ); | ||
} else { | ||
blockToInsert = createBlock( allowedBlockType.name ); | ||
} | ||
|
||
insertBlock( blockToInsert, getInsertionIndex(), rootClientId ); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,17 @@ import { name as buttonBlockName } from '../button'; | |
|
||
const ALLOWED_BLOCKS = [ buttonBlockName ]; | ||
|
||
const DEFAULT_BLOCK = { | ||
name: buttonBlockName, | ||
attributesToCopy: { | ||
backgroundColor: true, | ||
className: true, | ||
gradient: true, | ||
textColor: true, | ||
width: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we also want to copy border and spacing styles? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
}, | ||
}; | ||
|
||
function ButtonsEdit( { attributes: { layout = {} } } ) { | ||
const blockProps = useBlockProps(); | ||
const preferredStyle = useSelect( ( select ) => { | ||
|
@@ -26,6 +37,8 @@ function ButtonsEdit( { attributes: { layout = {} } } ) { | |
|
||
const innerBlocksProps = useInnerBlocksProps( blockProps, { | ||
allowedBlocks: ALLOWED_BLOCKS, | ||
__experimentalDefaultBlock: DEFAULT_BLOCK, | ||
__experimentalDirectInsert: true, | ||
template: [ | ||
[ | ||
buttonBlockName, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this will cycle through all the adjacent block attributes even if we don't mean to copy any of them. I'm thinking it would be better to return early if there are no attributes to be copied. A couple ways we could do this:
directInsertBlock.attributesToCopy
; orgetAdjacentBlockAttributes
and have it return only those attributes; in that case we can return early from that function if nothing is passed to it. I'm more inclined towards this option as it would tidy all the extra attribute logic insidegetAdjacentBlockAttributes
. What do you think?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great points! I've moved the logic up to go inside
getAdjacentBlockAttributes
. That function is a little long now, but it feels a bit cleaner putting it there, and also only iterating over the specifiedattributesToCopy
if provided rather than every attribute of the adjacent block 👍Let me know if you think it needs further tidying (or refactoring to another file now that it's a bit longer!)