diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index 6bd96a3edd4da..ff6b3d3390fc8 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -36,6 +36,18 @@ }, "borderRadius": { "type": "number" + }, + "style": { + "type": "object" + }, + "backgroundColor": { + "type": "string" + }, + "textColor": { + "type": "string" + }, + "gradient": { + "type": "string" } } } diff --git a/packages/block-library/src/button/color-edit.js b/packages/block-library/src/button/color-edit.js new file mode 100644 index 0000000000000..72d8b356acdc0 --- /dev/null +++ b/packages/block-library/src/button/color-edit.js @@ -0,0 +1,222 @@ +/** + * External dependencies + */ +import { pickBy, isEqual, isObject, identity, mapValues } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useState, useEffect, useRef } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + getColorObjectByColorValue, + getColorObjectByAttributeValues, + getGradientValueBySlug, + getGradientSlugByValue, + __experimentalPanelColorGradientSettings as PanelColorGradientSettings, + ContrastChecker, + InspectorControls, +} from '@wordpress/block-editor'; + +// The code in this file is copied entirely from the "color" and "style" support flags +// The flag can't be used at the moment because of the extra wrapper around +// the button block markup. + +function getBlockDOMNode( clientId ) { + return document.getElementById( 'block-' + clientId ); +} + +/** + * Removed undefined values from nested object. + * + * @param {*} object + * @return {*} Object cleaned from undefined values + */ +const cleanEmptyObject = ( object ) => { + if ( ! isObject( object ) ) { + return object; + } + const cleanedNestedObjects = pickBy( + mapValues( object, cleanEmptyObject ), + identity + ); + return isEqual( cleanedNestedObjects, {} ) + ? undefined + : cleanedNestedObjects; +}; + +function ColorPanel( { settings, clientId, enableContrastChecking = true } ) { + const { getComputedStyle, Node } = window; + + const [ detectedBackgroundColor, setDetectedBackgroundColor ] = useState(); + const [ detectedColor, setDetectedColor ] = useState(); + + useEffect( () => { + if ( ! enableContrastChecking ) { + return; + } + + const colorsDetectionElement = getBlockDOMNode( clientId ); + if ( ! colorsDetectionElement ) { + return; + } + setDetectedColor( getComputedStyle( colorsDetectionElement ).color ); + + let backgroundColorNode = colorsDetectionElement; + let backgroundColor = getComputedStyle( backgroundColorNode ) + .backgroundColor; + while ( + backgroundColor === 'rgba(0, 0, 0, 0)' && + backgroundColorNode.parentNode && + backgroundColorNode.parentNode.nodeType === Node.ELEMENT_NODE + ) { + backgroundColorNode = backgroundColorNode.parentNode; + backgroundColor = getComputedStyle( backgroundColorNode ) + .backgroundColor; + } + + setDetectedBackgroundColor( backgroundColor ); + } ); + + return ( + + + { enableContrastChecking && ( + + ) } + + + ); +} + +/** + * Inspector control panel containing the color related configuration + * + * @param {Object} props + * + * @return {WPElement} Color edit element. + */ +function ColorEdit( props ) { + const { attributes } = props; + const { colors, gradients } = useSelect( ( select ) => { + return select( 'core/block-editor' ).getSettings(); + }, [] ); + // Shouldn't be needed but right now the ColorGradientsPanel + // can trigger both onChangeColor and onChangeBackground + // synchronously causing our two callbacks to override changes + // from each other. + const localAttributes = useRef( attributes ); + useEffect( () => { + localAttributes.current = attributes; + }, [ attributes ] ); + + const { style, textColor, backgroundColor, gradient } = attributes; + let gradientValue; + if ( gradient ) { + gradientValue = getGradientValueBySlug( gradients, gradient ); + } else { + gradientValue = style?.color?.gradient; + } + + const onChangeColor = ( name ) => ( value ) => { + const colorObject = getColorObjectByColorValue( colors, value ); + const attributeName = name + 'Color'; + const newStyle = { + ...localAttributes.current.style, + color: { + ...localAttributes.current?.style?.color, + [ name ]: colorObject?.slug ? undefined : value, + }, + }; + + const newNamedColor = colorObject?.slug ? colorObject.slug : undefined; + const newAttributes = { + style: cleanEmptyObject( newStyle ), + [ attributeName ]: newNamedColor, + }; + + props.setAttributes( newAttributes ); + localAttributes.current = { + ...localAttributes.current, + ...newAttributes, + }; + }; + + const onChangeGradient = ( value ) => { + const slug = getGradientSlugByValue( gradients, value ); + let newAttributes; + if ( slug ) { + const newStyle = { + ...localAttributes.current?.style, + color: { + ...localAttributes.current?.style?.color, + gradient: undefined, + }, + }; + newAttributes = { + style: cleanEmptyObject( newStyle ), + gradient: slug, + }; + } else { + const newStyle = { + ...localAttributes.current?.style, + color: { + ...localAttributes.current?.style?.color, + gradient: value, + }, + }; + newAttributes = { + style: cleanEmptyObject( newStyle ), + gradient: undefined, + }; + } + props.setAttributes( newAttributes ); + localAttributes.current = { + ...localAttributes.current, + ...newAttributes, + }; + }; + + return ( + + ); +} + +export default ColorEdit; diff --git a/packages/block-library/src/button/color-props.js b/packages/block-library/src/button/color-props.js new file mode 100644 index 0000000000000..cc96f9f78d389 --- /dev/null +++ b/packages/block-library/src/button/color-props.js @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { + getColorClassName, + __experimentalGetGradientClass, +} from '@wordpress/block-editor'; + +// The code in this file is copied entirely from the "color" and "style" support flags +// The flag can't be used at the moment because of the extra wrapper around +// the button block markup. + +export default function getColorAndStyleProps( attributes ) { + // I'd have prefered to avoid the "style" attribute usage here + const { backgroundColor, textColor, gradient, style } = attributes; + + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); + const gradientClass = __experimentalGetGradientClass( gradient ); + const textClass = getColorClassName( 'color', textColor ); + const className = classnames( textClass, gradientClass, { + // Don't apply the background class if there's a custom gradient + [ backgroundClass ]: ! style?.color?.gradient && !! backgroundClass, + 'has-text-color': textColor || style?.color?.text, + 'has-background': + backgroundColor || + style?.color?.background || + gradient || + style?.color?.gradient, + } ); + const styleProp = + style?.color?.background || style?.color?.text || style?.color?.gradient + ? { + background: style?.color?.gradient + ? style.color.gradient + : undefined, + backgroundColor: style?.color?.background + ? style.color.background + : undefined, + color: style?.color?.text ? style.color.text : undefined, + } + : {}; + + return { + className: !! className ? className : undefined, + style: styleProp, + }; +} diff --git a/packages/block-library/src/button/deprecated.js b/packages/block-library/src/button/deprecated.js index e0915201a0b2a..5316e590bd50f 100644 --- a/packages/block-library/src/button/deprecated.js +++ b/packages/block-library/src/button/deprecated.js @@ -81,6 +81,75 @@ const blockAttributes = { }; const deprecated = [ + { + supports: { + align: true, + alignWide: false, + __experimentalColor: { gradients: true }, + }, + attributes: { + ...blockAttributes, + linkTarget: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'target', + }, + rel: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'rel', + }, + placeholder: { + type: 'string', + }, + borderRadius: { + type: 'number', + }, + backgroundColor: { + type: 'string', + }, + textColor: { + type: 'string', + }, + gradient: { + type: 'string', + }, + style: { + type: 'object', + }, + }, + save( { attributes } ) { + const { + borderRadius, + linkTarget, + rel, + text, + title, + url, + } = attributes; + const buttonClasses = classnames( 'wp-block-button__link', { + 'no-border-radius': borderRadius === 0, + } ); + const buttonStyle = { + borderRadius: borderRadius ? borderRadius + 'px' : undefined, + }; + + return ( + + ); + }, + }, { supports: { align: true, @@ -125,6 +194,11 @@ const deprecated = [ type: 'string', }, }, + + isEligible: ( attributes ) => + !! attributes.customTextColor || + !! attributes.customBackgroundColor || + !! attributes.customGradient, migrate: migrateCustomColorsAndGradients, save( { attributes } ) { const { diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 38b5b43f96d68..2078e437b0687 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -28,6 +28,12 @@ import { import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; import { link } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import ColorEdit from './color-edit'; +import getColorAndStyleProps from './color-props'; + const NEW_TAB_REL = 'noreferrer noopener'; const MIN_BORDER_RADIUS_VALUE = 0; const MAX_BORDER_RADIUS_VALUE = 50; @@ -113,7 +119,8 @@ function URLPicker( { ); } -function ButtonEdit( { attributes, setAttributes, className, isSelected } ) { +function ButtonEdit( props ) { + const { attributes, setAttributes, className, isSelected } = props; const { borderRadius, linkTarget, @@ -148,23 +155,33 @@ function ButtonEdit( { attributes, setAttributes, className, isSelected } ) { [ rel, setAttributes ] ); + const colorProps = getColorAndStyleProps( attributes ); + return ( <> - setAttributes( { text: value } ) } - withoutInteractiveFormatting - className={ classnames( className, 'wp-block-button__link', { - 'no-border-radius': borderRadius === 0, - } ) } - style={ { - borderRadius: borderRadius - ? borderRadius + 'px' - : undefined, - } } - /> + + + setAttributes( { text: value } ) } + withoutInteractiveFormatting + className={ classnames( + className, + 'wp-block-button__link', + colorProps.className, + { + 'no-border-radius': borderRadius === 0, + } + ) } + style={ { + borderRadius: borderRadius + ? borderRadius + 'px' + : undefined, + ...colorProps.style, + } } + /> + +
+ +
); } diff --git a/packages/e2e-tests/fixtures/blocks/core__button__center__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core__button__center__deprecated.serialized.html index 5fee004c30ddd..3d88c7ec1a551 100644 --- a/packages/e2e-tests/fixtures/blocks/core__button__center__deprecated.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__button__center__deprecated.serialized.html @@ -1,3 +1,3 @@ -Help build Gutenberg + diff --git a/packages/e2e-tests/fixtures/blocks/core__button__squared.html b/packages/e2e-tests/fixtures/blocks/core__button__squared.html index d7a2b6f5f9590..7df5372a100b6 100644 --- a/packages/e2e-tests/fixtures/blocks/core__button__squared.html +++ b/packages/e2e-tests/fixtures/blocks/core__button__squared.html @@ -1,3 +1,3 @@ -My button + diff --git a/packages/e2e-tests/fixtures/blocks/core__button__squared.json b/packages/e2e-tests/fixtures/blocks/core__button__squared.json index f3aa8241d516f..1e97cd0ba5209 100644 --- a/packages/e2e-tests/fixtures/blocks/core__button__squared.json +++ b/packages/e2e-tests/fixtures/blocks/core__button__squared.json @@ -5,16 +5,16 @@ "isValid": true, "attributes": { "text": "My button", - "align": "none", - "style": { - "color": { - "background": "#aa5a20", - "text": "#1b9b6c" - } - }, - "borderRadius": 0 + "borderRadius": 0, + "style": { + "color": { + "text": "#1b9b6c", + "background": "#aa5a20" + } + }, + "align": "none" }, "innerBlocks": [], - "originalContent": "My button" + "originalContent": "" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__button__squared.parsed.json b/packages/e2e-tests/fixtures/blocks/core__button__squared.parsed.json index dc1ea8f2ca347..4ddc882a14ae7 100644 --- a/packages/e2e-tests/fixtures/blocks/core__button__squared.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__button__squared.parsed.json @@ -2,19 +2,19 @@ { "blockName": "core/button", "attrs": { - "align": "none", "borderRadius": 0, + "align": "none", "style": { - "color": { - "background": "#aa5a20", - "text": "#1b9b6c" - } - } + "color": { + "text": "#1b9b6c", + "background": "#aa5a20" + } + } }, "innerBlocks": [], - "innerHTML": "\nMy button\n", + "innerHTML": "\n\n", "innerContent": [ - "\nMy button\n" + "\n\n" ] }, { diff --git a/packages/e2e-tests/fixtures/blocks/core__button__squared.serialized.html b/packages/e2e-tests/fixtures/blocks/core__button__squared.serialized.html index d7a2b6f5f9590..0ed8558bc1582 100644 --- a/packages/e2e-tests/fixtures/blocks/core__button__squared.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__button__squared.serialized.html @@ -1,3 +1,3 @@ - -My button + + diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons.html b/packages/e2e-tests/fixtures/blocks/core__buttons.html index 02f5487560cee..e70af7acc72ad 100644 --- a/packages/e2e-tests/fixtures/blocks/core__buttons.html +++ b/packages/e2e-tests/fixtures/blocks/core__buttons.html @@ -1,11 +1,11 @@ diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons.json b/packages/e2e-tests/fixtures/blocks/core__buttons.json index d77d9752de37b..044daeb82101a 100644 --- a/packages/e2e-tests/fixtures/blocks/core__buttons.json +++ b/packages/e2e-tests/fixtures/blocks/core__buttons.json @@ -13,7 +13,7 @@ "text": "My button 1" }, "innerBlocks": [], - "originalContent": "My button 1" + "originalContent": "" }, { "clientId": "_clientId_1", @@ -23,7 +23,7 @@ "text": "My button 2" }, "innerBlocks": [], - "originalContent": "My button 2" + "originalContent": "" } ], "originalContent": "
\n\t\n\n\t\n
" diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons.parsed.json b/packages/e2e-tests/fixtures/blocks/core__buttons.parsed.json index ee9dd97313a48..b96b1f50db1fc 100644 --- a/packages/e2e-tests/fixtures/blocks/core__buttons.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__buttons.parsed.json @@ -7,18 +7,18 @@ "blockName": "core/button", "attrs": {}, "innerBlocks": [], - "innerHTML": "\n\tMy button 1\n\t", + "innerHTML": "\n\t\n\t", "innerContent": [ - "\n\tMy button 1\n\t" + "\n\t\n\t" ] }, { "blockName": "core/button", "attrs": {}, "innerBlocks": [], - "innerHTML": "\n\tMy button 2\n\t", + "innerHTML": "\n\t\n\t", "innerContent": [ - "\n\tMy button 2\n\t" + "\n\t\n\t" ] } ], diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons.serialized.html b/packages/e2e-tests/fixtures/blocks/core__buttons.serialized.html index 18eb4b31eda59..baf0a0226c066 100644 --- a/packages/e2e-tests/fixtures/blocks/core__buttons.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__buttons.serialized.html @@ -1,9 +1,9 @@ diff --git a/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.html b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.html new file mode 100644 index 0000000000000..02f5487560cee --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.html @@ -0,0 +1,11 @@ + +
+ + My button 1 + + + + My button 2 + +
+ diff --git a/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.json b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.json new file mode 100644 index 0000000000000..d77d9752de37b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.json @@ -0,0 +1,31 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/buttons", + "isValid": true, + "attributes": {}, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/button", + "isValid": true, + "attributes": { + "text": "My button 1" + }, + "innerBlocks": [], + "originalContent": "My button 1" + }, + { + "clientId": "_clientId_1", + "name": "core/button", + "isValid": true, + "attributes": { + "text": "My button 2" + }, + "innerBlocks": [], + "originalContent": "My button 2" + } + ], + "originalContent": "
\n\t\n\n\t\n
" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.parsed.json b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.parsed.json new file mode 100644 index 0000000000000..ee9dd97313a48 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.parsed.json @@ -0,0 +1,43 @@ +[ + { + "blockName": "core/buttons", + "attrs": {}, + "innerBlocks": [ + { + "blockName": "core/button", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\tMy button 1\n\t", + "innerContent": [ + "\n\tMy button 1\n\t" + ] + }, + { + "blockName": "core/button", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\tMy button 2\n\t", + "innerContent": [ + "\n\tMy button 2\n\t" + ] + } + ], + "innerHTML": "\n
\n\t\n\n\t\n
\n", + "innerContent": [ + "\n
\n\t", + null, + "\n\n\t", + null, + "\n
\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.serialized.html new file mode 100644 index 0000000000000..baf0a0226c066 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.serialized.html @@ -0,0 +1,9 @@ + + + diff --git a/packages/e2e-tests/specs/editor/blocks/__snapshots__/buttons.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/buttons.test.js.snap index 08a92e1fd8820..66f803ddcf139 100644 --- a/packages/e2e-tests/specs/editor/blocks/__snapshots__/buttons.test.js.snap +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/buttons.test.js.snap @@ -3,7 +3,7 @@ exports[`Buttons can jump to the link editor using the keyboard shortcut 1`] = ` " " `; @@ -11,7 +11,7 @@ exports[`Buttons can jump to the link editor using the keyboard shortcut 1`] = ` exports[`Buttons dismisses link editor when escape is pressed 1`] = ` " " `; @@ -19,7 +19,7 @@ exports[`Buttons dismisses link editor when escape is pressed 1`] = ` exports[`Buttons has focus on button content 1`] = ` " " `;