Skip to content
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

[WIP] Layout: Explore adding in sticky position support within the layout support #44723

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions lib/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,117 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
}
add_filter( 'render_block', 'gutenberg_render_layout_support_flag', 10, 2 );

/**
* Generates the CSS for layout position support from the style object.
*
* @param string $selector CSS selector.
* @param array $style Style object.
* @return string CSS styles on success. Else, empty string.
*/
function gutenberg_get_layout_position_style( $selector, $style ) {
$position_styles = array();
$position_type = _wp_array_get( $style, array( 'layout', 'position' ), '' );

if (
in_array( $position_type, array( 'fixed', 'sticky' ), true )
) {
$sides = array( 'top', 'right', 'bottom', 'left' );

foreach ( $sides as $side ) {
$side_value = _wp_array_get( $style, array( 'layout', $side ) );
if ( null !== $side_value ) {
/*
* 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 )
) {
// Ensure 0 values can be used in `calc()` calculations.
if ( '0' === $side_value || 0 === $side_value ) {
$side_value = '0px';
}

// Ensure current side value also factors in the height of the logged in admin bar.
$side_value = "calc($side_value + var(--wp-admin--admin-bar--height, 0px))";
}

$position_styles[] =
array(
'selector' => "$selector",
'declarations' => array(
$side => $side_value,
),
);
}
}

$position_styles[] =
array(
'selector' => "$selector",
'declarations' => array(
'position' => $position_type,
'z-index' => '250', // TODO: This hard-coded value should live somewhere else.
),
);
}

if ( ! empty( $position_styles ) ) {
/*
* Add to the style engine store to enqueue and render layout styles.
*/
return gutenberg_style_engine_get_stylesheet_from_css_rules(
$position_styles,
array(
'context' => 'block-supports',
'prettify' => false,
)
);
}

return '';
}

/**
* Renders layout position styles to the block wrapper.
*
* Position related styles should always be applied to the outer wrapper,
* so this logic is separate from the layout support's innerBlocks wrapper logic.
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
*/
function gutenberg_render_layout_position_support( $block_content, $block ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
$has_layout_position_support = block_has_support( $block_type, array( '__experimentalLayout', 'allowPosition' ), false );

if (
! $has_layout_position_support ||
empty( $block['attrs']['style']['layout'] )
) {
return $block_content;
}

$style_attribute = _wp_array_get( $block, array( 'attrs', 'style' ), null );
$class_name = wp_unique_id( 'wp-container-' );
$style = gutenberg_get_layout_position_style( ".$class_name.$class_name", $style_attribute );

if ( ! empty( $style ) ) {
$content = new WP_HTML_Tag_Processor( $block_content );
$content->next_tag();
$content->add_class( $class_name );
return (string) $content;
}
return $block_content;
}

if ( function_exists( 'wp_render_layout_position_support' ) ) {
remove_filter( 'render_block', 'wp_render_layout_position_support' );
}
add_filter( 'render_block', 'gutenberg_render_layout_position_support', 10, 2 );

/**
* For themes without theme.json file, make sure
* to restore the inner div for the group block
Expand Down
6 changes: 6 additions & 0 deletions lib/compat/wordpress-6.1/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ function gutenberg_safe_style_attrs_6_1( $attrs ) {
$attrs[] = 'margin-block-end';
$attrs[] = 'margin-inline-start';
$attrs[] = 'margin-inline-end';
$attrs[] = 'position';
$attrs[] = 'top';
$attrs[] = 'right';
$attrs[] = 'bottom';
$attrs[] = 'left';
$attrs[] = 'z-index';

return $attrs;
}
Expand Down
14 changes: 14 additions & 0 deletions lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 {
'box-shadow' => array( 'shadow' ),
);

const APPEARANCE_TOOLS_OPT_INS = array(
array( 'border', 'color' ),
array( 'border', 'radius' ),
array( 'border', 'style' ),
array( 'border', 'width' ),
array( 'color', 'link' ),
array( 'layout', 'position' ),
array( 'spacing', 'blockGap' ),
array( 'spacing', 'margin' ),
array( 'spacing', 'padding' ),
array( 'typography', 'lineHeight' ),
);

/**
* The valid elements that can be found under styles.
*
Expand Down Expand Up @@ -1327,6 +1340,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null
'layout' => array(
'contentSize' => null,
'definitions' => null,
'position' => null,
'wideSize' => null,
),
'spacing' => array(
Expand Down
3 changes: 2 additions & 1 deletion lib/compat/wordpress-6.1/theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@
}
]
}
}
},
"position": false
},
"spacing": {
"blockGap": null,
Expand Down
48 changes: 36 additions & 12 deletions packages/block-editor/src/hooks/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ import useSetting from '../components/use-setting';
import { LayoutStyle } from '../components/block-list/layout';
import BlockList from '../components/block-list';
import { getLayoutType, getLayoutTypes } from '../layouts';
import { PositionEdit, getPositionCSS } from './position';

const layoutBlockSupportKey = '__experimentalLayout';
export const LAYOUT_SUPPORT_KEY = '__experimentalLayout';

/**
* Generates the utility classnames for the given block's layout attributes.
Expand All @@ -51,7 +52,7 @@ export function useLayoutClasses( block = {} ) {
const { layout } = attributes;

const { default: defaultBlockLayout } =
getBlockSupport( name, layoutBlockSupportKey ) || {};
getBlockSupport( name, LAYOUT_SUPPORT_KEY ) || {};
const usedLayout =
layout?.inherit || layout?.contentSize || layout?.wideSize
? { ...layout, type: 'constrained' }
Expand Down Expand Up @@ -128,7 +129,8 @@ export function useLayoutStyles( block = {}, selector ) {
return css;
}

function LayoutPanel( { setAttributes, attributes, name: blockName } ) {
function LayoutPanel( props ) {
const { setAttributes, attributes, name: blockName } = props;
andrewserong marked this conversation as resolved.
Show resolved Hide resolved
const { layout } = attributes;
const defaultThemeLayout = useSetting( 'layout' );
const themeSupportsLayout = useSelect( ( select ) => {
Expand All @@ -138,13 +140,14 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) {

const layoutBlockSupport = getBlockSupport(
blockName,
layoutBlockSupportKey,
LAYOUT_SUPPORT_KEY,
{}
);
const {
allowSwitching,
allowEditing = true,
allowInheriting = true,
allowPosition,
allowSwitching,
default: defaultBlockLayout,
} = layoutBlockSupport;

Expand Down Expand Up @@ -252,6 +255,8 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) {
layoutBlockSupport={ layoutBlockSupport }
/>
) }

{ allowPosition && <PositionEdit { ...props } /> }
</PanelBody>
</InspectorControls>
{ ! inherit && layoutType && (
Expand Down Expand Up @@ -294,7 +299,7 @@ export function addAttribute( settings ) {
if ( 'type' in ( settings.attributes?.layout ?? {} ) ) {
return settings;
}
if ( hasBlockSupport( settings, layoutBlockSupportKey ) ) {
if ( hasBlockSupport( settings, LAYOUT_SUPPORT_KEY ) ) {
settings.attributes = {
...settings.attributes,
layout: {
Expand All @@ -316,10 +321,7 @@ export function addAttribute( settings ) {
export const withInspectorControls = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
const { name: blockName } = props;
const supportLayout = hasBlockSupport(
blockName,
layoutBlockSupportKey
);
const supportLayout = hasBlockSupport( blockName, LAYOUT_SUPPORT_KEY );

return [
supportLayout && <LayoutPanel key="layout" { ...props } />,
Expand All @@ -341,7 +343,7 @@ export const withLayoutStyles = createHigherOrderComponent(
const { name, attributes, block } = props;
const hasLayoutBlockSupport = hasBlockSupport(
name,
layoutBlockSupportKey
LAYOUT_SUPPORT_KEY
);
const disableLayoutStyles = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
Expand All @@ -350,30 +352,36 @@ export const withLayoutStyles = createHigherOrderComponent(
const shouldRenderLayoutStyles =
hasLayoutBlockSupport && ! disableLayoutStyles;
const id = useInstanceId( BlockListBlock );
const positionId = useInstanceId( BlockListBlock );
const defaultThemeLayout = useSetting( 'layout' ) || {};
const element = useContext( BlockList.__unstableElementContext );
const { layout } = attributes;
const { default: defaultBlockLayout } =
getBlockSupport( name, layoutBlockSupportKey ) || {};
getBlockSupport( name, LAYOUT_SUPPORT_KEY ) || {};
const usedLayout =
layout?.inherit || layout?.contentSize || layout?.wideSize
? { ...layout, type: 'constrained' }
: layout || defaultBlockLayout || {};
const layoutClasses = hasLayoutBlockSupport
? useLayoutClasses( block )
: null;

// Higher specificity to override defaults from theme.json.
const selector = `.wp-container-${ id }.wp-container-${ id }`;
const positionSelector = `.wp-container-${ positionId }.wp-container-${ positionId }`;
const blockGapSupport = useSetting( 'spacing.blockGap' );
const hasBlockGapSupport = blockGapSupport !== null;

// Get CSS string for the current layout type.
// The CSS and `style` element is only output if it is not empty.
let css;
let positionCss;
if ( shouldRenderLayoutStyles ) {
const fullLayoutType = getLayoutType(
usedLayout?.type || 'default'
);

// Add layout CSS.
css = fullLayoutType?.getLayoutStyle?.( {
blockName: name,
selector,
Expand All @@ -382,6 +390,16 @@ export const withLayoutStyles = createHigherOrderComponent(
style: attributes?.style,
hasBlockGapSupport,
} );

// Add position CSS where applicable.
positionCss =
getPositionCSS( {
selector: positionSelector,
style: attributes?.style,
} ) || '';

// Concatenate CSS for output.
css += positionCss;
}

// Attach a `wp-container-` id-based class name as well as a layout class name such as `is-layout-flex`.
Expand All @@ -392,6 +410,11 @@ export const withLayoutStyles = createHigherOrderComponent(
layoutClasses
);

const wrapperClassNames = classnames( props?.className, {
[ `wp-container-${ positionId }` ]:
shouldRenderLayoutStyles && !! positionCss, // Use separate container class for position styles in prep for layout styles moving to inner wrapper in: https://github.com/WordPress/gutenberg/pull/44600
} );

return (
<>
{ shouldRenderLayoutStyles &&
Expand All @@ -409,6 +432,7 @@ export const withLayoutStyles = createHigherOrderComponent(
) }
<BlockListBlock
{ ...props }
className={ wrapperClassNames }
__unstableLayoutClassNames={ layoutClassNames }
/>
</>
Expand Down
Loading