diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index b6a30dbfbcded..db2ce090e373f 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -26,34 +26,47 @@ function gutenberg_register_layout_support( $block_type ) { } /** - * Generates the CSS for position support from the layout object. + * Generates the CSS for position support from the style object. * * @param string $selector CSS selector. - * @param array $layout Layout object. The one that is passed has already checked - * the existence of default block layout. + * @param array $style Style object. * @return string CSS styles on success. Else, empty string. */ -function gutenberg_get_layout_position_style( $selector, $layout ) { +function gutenberg_get_layout_position_style( $selector, $style ) { $position_styles = array(); - - $position_type = _wp_array_get( $layout, array( 'position', 'type' ), '' ); - $position_side = _wp_array_get( $layout, array( 'position', 'side' ), '' ); - $offset_value = '0'; + $position_type = _wp_array_get( $style, array( 'layout', 'position' ), '' ); if ( - in_array( $position_type, array( 'fixed', 'sticky' ), true ) && - in_array( $position_side, array( 'top', 'right', 'bottom', 'left' ), true ) + in_array( $position_type, array( 'fixed', 'sticky' ), true ) ) { - /* - * For fixed or sticky top positions, - * ensure the value includes an offset for the logged in admin bar. - */ - if ( - 'top' === $position_side && - 'fixed' === $position_type || - 'sticky' === $position_type - ) { - $offset_value = 'var(--wp-admin--admin-bar--height, 0px)'; + $sides = array( 'top', 'right', 'bottom', 'left' ); + + foreach( $sides as $side ) { + $side_value = _wp_array_get( $style, array( 'layout', $side ) ); + if ( $side_value !== null ) { + + /* + * For fixed or sticky top positions, + * ensure the value includes an offset for the logged in admin bar. + */ + if ( + 'top' === $side && + 'fixed' === $position_type || + 'sticky' === $position_type + ) { + // TODO: wrap the following value in a `calc()` + `$side_value`, + // so that any included value is treated as an offset. + $side_value = 'var(--wp-admin--admin-bar--height, 0px)'; + } + + $position_styles[] = + array( + 'selector' => "$selector", + 'declarations' => array( + $side => $side_value + ), + ); + } } $position_styles[] = @@ -61,7 +74,6 @@ function gutenberg_get_layout_position_style( $selector, $layout ) { 'selector' => "$selector", 'declarations' => array( 'position' => $position_type, - $position_side => $offset_value, 'z-index' => '250', // TODO: This hard-coded value should live somewhere else. ), ); @@ -464,7 +476,8 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $block_spacing ); - $style .= gutenberg_get_layout_position_style( ".$block_classname.$container_class", $used_layout ); + $style_attribute = _wp_array_get( $block, array( 'attrs', 'style' ), null ); + $style .= gutenberg_get_layout_position_style( ".$block_classname.$container_class", $style_attribute ); // Only add container class and enqueue block support styles if unique styles were generated. if ( ! empty( $style ) ) { diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index 2c8cea9921fa8..4bb929e5f268d 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -32,6 +32,8 @@ const POSITION_OPTIONS = [ }, ]; +const POSITION_SIDES = [ 'top', 'right', 'bottom', 'left' ]; + /** * Determines if there is position support. * @@ -51,7 +53,7 @@ export function hasPositionSupport( blockType ) { * @return {boolean} Whether or not the block has a position value set. */ export function hasPositionValue( props ) { - return props.attributes.layout?.position !== undefined; + return props.attributes.style?.layout?.position !== undefined; } /** @@ -63,12 +65,19 @@ export function hasPositionValue( props ) { * @param {Object} props.setAttributes Function to set block's attributes. */ export function resetPosition( { attributes = {}, setAttributes } ) { - const { layout = {} } = attributes; + const { style = {} } = attributes; setAttributes( { - layout: cleanEmptyObject( { - ...layout, - position: undefined, + style: cleanEmptyObject( { + ...style, + layout: { + ...style?.layout, + position: undefined, + top: undefined, + right: undefined, + bottom: undefined, + left: undefined, + }, } ), } ); } @@ -86,6 +95,29 @@ export function useIsPositionDisabled( { name: blockName } = {} ) { return ! hasPositionSupport( blockName ) || isDisabled; } +/** + * From a style.layout object, find the first side with a value. + * + * This allows inferring the selected area based on the presence of a value for one of + * the four sides, `top`, `bottom`, `right`, and `left. + * + * @param {Object} styleLayout An object that can contain `top`, `bottom`, `right`, and `left` keys. + * @return {?string} The side with a value. + */ +function getFirstActiveAreaValue( styleLayout ) { + if ( ! styleLayout ) { + return; + } + const foundSide = Object.entries( styleLayout ).find( + ( [ key, value ] ) => + POSITION_SIDES.includes( key ) && value !== undefined + ); + + if ( foundSide ) { + return foundSide[ 0 ]; + } +} + /** * Inspector control panel containing the padding related configuration * @@ -95,7 +127,7 @@ export function useIsPositionDisabled( { name: blockName } = {} ) { */ export function PositionEdit( props ) { const { - attributes: { layout = {} }, + attributes: { style = {} }, setAttributes, } = props; @@ -104,33 +136,55 @@ export function PositionEdit( props ) { } const onChangeSide = ( next ) => { - const newLayout = { - ...layout, - position: { - ...layout?.position, - side: next, + if ( next !== undefined && ! POSITION_SIDES.includes( next ) ) { + return; + } + + // For now, use a hard-coded `0px` value for the position. + // `0px` is preferred over `0` as it can be used in `calc()` functions. + // In the future, it could be useful to allow for an offset value. + const newValue = '0px'; + + const newSides = { + top: undefined, + right: undefined, + bottom: undefined, + left: undefined, + }; + + if ( next !== undefined ) { + newSides[ next ] = newValue; + } + + const newStyle = { + ...style, + layout: { + ...style?.layout, + ...newSides, }, }; setAttributes( { - layout: cleanEmptyObject( newLayout ), + style: cleanEmptyObject( newStyle ), } ); }; const onChangeType = ( next ) => { - const newLayout = { - ...layout, - position: { - ...layout?.position, - type: next, + const newStyle = { + ...style, + layout: { + ...style?.layout, + position: next, }, }; setAttributes( { - layout: cleanEmptyObject( newLayout ), + style: cleanEmptyObject( newStyle ), } ); }; + const areaValue = getFirstActiveAreaValue( style?.layout ); + return Platform.select( { web: ( <> @@ -139,14 +193,14 @@ export function PositionEdit( props ) { 'The area of a page that this block should occupy' ) } onChange={ onChangeSide } - value={ layout?.position?.side } + value={ areaValue } /> { onChangeType( newValue ); } } diff --git a/packages/block-editor/src/layouts/constrained.js b/packages/block-editor/src/layouts/constrained.js index 574803ac4c9f7..eb21212d7d8ad 100644 --- a/packages/block-editor/src/layouts/constrained.js +++ b/packages/block-editor/src/layouts/constrained.js @@ -237,7 +237,7 @@ export default { } // Add position CSS where applicable. - output += getPositionCSS( { selector, layout } ); + output += getPositionCSS( { selector, style } ); return output; }, diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js index 8c5413605c78d..418d90b861955 100644 --- a/packages/block-editor/src/layouts/flex.js +++ b/packages/block-editor/src/layouts/flex.js @@ -167,7 +167,7 @@ export default { } // Add position CSS where applicable. - output += getPositionCSS( { selector, layout } ); + output += getPositionCSS( { selector, style } ); return output; }, diff --git a/packages/block-editor/src/layouts/flow.js b/packages/block-editor/src/layouts/flow.js index ff46a076afdeb..f29f94f86c9c0 100644 --- a/packages/block-editor/src/layouts/flow.js +++ b/packages/block-editor/src/layouts/flow.js @@ -21,7 +21,6 @@ export default { }, getLayoutStyle: function getLayoutStyle( { selector, - layout, style, blockName, hasBlockGapSupport, @@ -54,7 +53,7 @@ export default { } // Add position CSS where applicable. - output += getPositionCSS( { selector, layout } ); + output += getPositionCSS( { selector, style } ); return output; }, diff --git a/packages/block-editor/src/layouts/utils.js b/packages/block-editor/src/layouts/utils.js index 8c56f51d45395..596888e5f8a67 100644 --- a/packages/block-editor/src/layouts/utils.js +++ b/packages/block-editor/src/layouts/utils.js @@ -76,27 +76,28 @@ const VALID_POSITION_TYPES = [ 'sticky', 'fixed' ]; * * @param {Object} props Component props. * @param {string} props.selector Selector to use. - * @param {Object} props.layout Layout object. + * @param {Object} props.style Style object. * @return {string} The generated CSS rules. */ -export function getPositionCSS( { selector, layout } ) { +export function getPositionCSS( { selector, style } ) { let output = ''; - const { type, side } = layout?.position || {}; + const { position } = style?.layout || {}; - if ( - ! VALID_POSITION_TYPES.includes( type ) || - ! VALID_POSITION_SIDES.includes( side ) - ) { + if ( ! VALID_POSITION_TYPES.includes( position ) ) { return output; } - const offsetValue = '0px'; - output += `${ selector } {`; - output += `position: ${ type };`; - output += `${ side }: ${ offsetValue };`; - if ( type === 'sticky' || type === 'fixed' ) { + output += `position: ${ position };`; + + VALID_POSITION_SIDES.forEach( ( side ) => { + if ( style?.layout?.[ side ] !== undefined ) { + output += `${ side }: ${ style.layout[ side ] };`; + } + } ); + + if ( position === 'sticky' || position === 'fixed' ) { // TODO: Work out where to put the magic z-index value. output += `z-index: 250`; }