diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index be2abff54a229..a645ae58e6ff2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -132,10 +132,10 @@ /lib @timothybjacobs @spacedmonkey /lib/global-styles.php @timothybjabocs @spacedmonkey @oandregal /lib/class-wp-rest-block-editor-settings-controller.php @timothybjacobs @spacedmonkey @geriux @antonis -/lib/compat/wordpress-5.9/theme.json @timothybjabocs @spacedmonkey @oandregal /lib/compat/wordpress-5.9/theme-i18n.json @timothybjabocs @spacedmonkey @oandregal /lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php @timothybjabocs @spacedmonkey @oandregal /lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php @timothybjabocs @spacedmonkey @oandregal +/lib/compat/wordpress-6.0/theme.json @timothybjabocs @spacedmonkey @oandregal /lib/full-site-editing @janw-me /phpunit/class-wp-theme-json-test.php @oandregal diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 93c530f5814ff..0d2b4d8420221 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -138,8 +138,8 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { return $block_content; } - $block_gap = wp_get_global_settings( array( 'spacing', 'blockGap' ) ); - $default_layout = wp_get_global_settings( array( 'layout' ) ); + $block_gap = gutenberg_get_global_settings( array( 'spacing', 'blockGap' ) ); + $default_layout = gutenberg_get_global_settings( array( 'layout' ) ); $has_block_gap_support = isset( $block_gap ) ? null !== $block_gap : false; $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() ); $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout; diff --git a/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php b/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php index 21d546fb66891..fcadb816afe2f 100644 --- a/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php @@ -119,7 +119,7 @@ public static function get_core_data() { return static::$core; } - $config = static::read_json_file( __DIR__ . '/theme.json' ); + $config = static::read_json_file( __DIR__ . '/../wordpress-6.0/theme.json' ); $config = static::translate( $config ); static::$core = new WP_Theme_JSON_Gutenberg( $config, 'default' ); diff --git a/lib/compat/wordpress-5.9/get-global-styles-and-settings.php b/lib/compat/wordpress-5.9/get-global-styles-and-settings.php index 0ac1456bd2e42..5e78029d0d191 100644 --- a/lib/compat/wordpress-5.9/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-5.9/get-global-styles-and-settings.php @@ -5,150 +5,133 @@ * @package gutenberg */ -if ( ! function_exists( 'wp_get_global_settings' ) ) { - /** - * Function to get the settings resulting of merging core, theme, and user data. - * - * @param array $path Path to the specific setting to retrieve. Optional. - * If empty, will return all settings. - * @param array $context { - * Metadata to know where to retrieve the $path from. Optional. - * - * @type string $block_name Which block to retrieve the settings from. - * If empty, it'll return the settings for the global context. - * @type string $origin Which origin to take data from. - * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). - * If empty or unknown, 'all' is used. - * } - * - * @return array The settings to retrieve. - */ - function wp_get_global_settings( $path = array(), $context = array() ) { - if ( ! empty( $context['block_name'] ) ) { - $path = array_merge( array( 'blocks', $context['block_name'] ), $path ); - } - - $origin = 'custom'; - if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) { - $origin = 'theme'; - } - - $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); - - return _wp_array_get( $settings, $path, $settings ); +/** + * Function to get the settings resulting of merging core, theme, and user data. + * + * @param array $path Path to the specific setting to retrieve. Optional. + * If empty, will return all settings. + * @param array $context { + * Metadata to know where to retrieve the $path from. Optional. + * + * @type string $block_name Which block to retrieve the settings from. + * If empty, it'll return the settings for the global context. + * @type string $origin Which origin to take data from. + * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). + * If empty or unknown, 'all' is used. + * } + * + * @return array The settings to retrieve. + */ +function gutenberg_get_global_settings( $path = array(), $context = array() ) { + if ( ! empty( $context['block_name'] ) ) { + $path = array_merge( array( 'blocks', $context['block_name'] ), $path ); + } + $origin = 'custom'; + if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) { + $origin = 'theme'; } + $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); + return _wp_array_get( $settings, $path, $settings ); } -if ( ! function_exists( 'wp_get_global_styles' ) ) { - /** - * Function to get the styles resulting of merging core, theme, and user data. - * - * @param array $path Path to the specific style to retrieve. Optional. - * If empty, will return all styles. - * @param array $context { - * Metadata to know where to retrieve the $path from. Optional. - * - * @type string $block_name Which block to retrieve the styles from. - * If empty, it'll return the styles for the global context. - * @type string $origin Which origin to take data from. - * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). - * If empty or unknown, 'all' is used. - * } - * - * @return array The styles to retrieve. - */ - function wp_get_global_styles( $path = array(), $context = array() ) { - if ( ! empty( $context['block_name'] ) ) { - $path = array_merge( array( 'blocks', $context['block_name'] ), $path ); - } +/** + * Function to get the styles resulting of merging core, theme, and user data. + * + * @param array $path Path to the specific style to retrieve. Optional. + * If empty, will return all styles. + * @param array $context { + * Metadata to know where to retrieve the $path from. Optional. + * + * @type string $block_name Which block to retrieve the styles from. + * If empty, it'll return the styles for the global context. + * @type string $origin Which origin to take data from. + * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). + * If empty or unknown, 'all' is used. + * } + * + * @return array The styles to retrieve. + */ +function gutenberg_get_global_styles( $path = array(), $context = array() ) { + if ( ! empty( $context['block_name'] ) ) { + $path = array_merge( array( 'blocks', $context['block_name'] ), $path ); + } + $origin = 'custom'; + if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) { + $origin = 'theme'; + } + $styles = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_raw_data()['styles']; + return _wp_array_get( $styles, $path, $styles ); +} - $origin = 'custom'; - if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) { - $origin = 'theme'; +/** + * Returns the stylesheet resulting of merging core, theme, and user data. + * + * @param array $types Types of styles to load. Optional. + * It accepts 'variables', 'styles', 'presets' as values. + * If empty, it'll load all for themes with theme.json support + * and only [ 'variables', 'presets' ] for themes without theme.json support. + * + * @return string Stylesheet. + */ +function gutenberg_get_global_stylesheet( $types = array() ) { + // Return cached value if it can be used and exists. + // It's cached by theme to make sure that theme switching clears the cache. + $can_use_cached = ( + ( empty( $types ) ) && + ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && + ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && + ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && + ! is_admin() + ); + $transient_name = 'gutenberg_global_styles_' . get_stylesheet(); + if ( $can_use_cached ) { + $cached = get_transient( $transient_name ); + if ( $cached ) { + return $cached; } + } + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $supports_theme_json = WP_Theme_JSON_Resolver_Gutenberg::theme_has_support(); + if ( empty( $types ) && ! $supports_theme_json ) { + $types = array( 'variables', 'presets' ); + } elseif ( empty( $types ) ) { + $types = array( 'variables', 'styles', 'presets' ); + } - $styles = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_raw_data()['styles']; - - return _wp_array_get( $styles, $path, $styles ); + /* + * If variables are part of the stylesheet, + * we add them for all origins (default, theme, user). + * This is so themes without a theme.json still work as before 5.9: + * they can override the default presets. + * See https://core.trac.wordpress.org/ticket/54782 + */ + $styles_variables = ''; + if ( in_array( 'variables', $types, true ) ) { + $styles_variables = $tree->get_stylesheet( array( 'variables' ) ); + $types = array_diff( $types, array( 'variables' ) ); } -} -if ( ! function_exists( 'wp_get_global_stylesheet' ) ) { - /** - * Returns the stylesheet resulting of merging core, theme, and user data. - * - * @param array $types Types of styles to load. Optional. - * It accepts 'variables', 'styles', 'presets' as values. - * If empty, it'll load all for themes with theme.json support - * and only [ 'variables', 'presets' ] for themes without theme.json support. + /* + * For the remaining types (presets, styles), we do consider origins: * - * @return string Stylesheet. + * - themes without theme.json: only the classes for the presets defined by core + * - themes with theme.json: the presets and styles classes, both from core and the theme */ - function wp_get_global_stylesheet( $types = array() ) { - // Return cached value if it can be used and exists. - // It's cached by theme to make sure that theme switching clears the cache. - $can_use_cached = ( - ( empty( $types ) ) && - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && - ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && - ! is_admin() - ); - $transient_name = 'gutenberg_global_styles_' . get_stylesheet(); - if ( $can_use_cached ) { - $cached = get_transient( $transient_name ); - if ( $cached ) { - return $cached; - } - } - - $tree = WP_Theme_JSON_Resolver::get_merged_data(); - - $supports_theme_json = WP_Theme_JSON_Resolver::theme_has_support(); - if ( empty( $types ) && ! $supports_theme_json ) { - $types = array( 'variables', 'presets' ); - } elseif ( empty( $types ) ) { - $types = array( 'variables', 'styles', 'presets' ); - } - - /* - * If variables are part of the stylesheet, - * we add them for all origins (default, theme, user). - * This is so themes without a theme.json still work as before 5.9: - * they can override the default presets. - * See https://core.trac.wordpress.org/ticket/54782 - */ - $styles_variables = ''; - if ( in_array( 'variables', $types, true ) ) { - $styles_variables = $tree->get_stylesheet( array( 'variables' ) ); - $types = array_diff( $types, array( 'variables' ) ); - } - - /* - * For the remaining types (presets, styles), we do consider origins: - * - * - themes without theme.json: only the classes for the presets defined by core - * - themes with theme.json: the presets and styles classes, both from core and the theme - */ - $styles_rest = ''; - if ( ! empty( $types ) ) { - $origins = array( 'default', 'theme', 'custom' ); - if ( ! $supports_theme_json ) { - $origins = array( 'default' ); - } - $styles_rest = $tree->get_stylesheet( $types, $origins ); - } - - $stylesheet = $styles_variables . $styles_rest; - - if ( $can_use_cached ) { - // Cache for a minute. - // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. - set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS ); + $styles_rest = ''; + if ( ! empty( $types ) ) { + $origins = array( 'default', 'theme', 'custom' ); + if ( ! $supports_theme_json ) { + $origins = array( 'default' ); } - - return $stylesheet; + $styles_rest = $tree->get_stylesheet( $types, $origins ); + } + $stylesheet = $styles_variables . $styles_rest; + if ( $can_use_cached ) { + // Cache for a minute. + // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. + set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS ); } + return $stylesheet; } if ( ! function_exists( 'wp_get_global_styles_svg_filters' ) ) { diff --git a/lib/compat/wordpress-5.9/global-styles-css-custom-properties.php b/lib/compat/wordpress-5.9/global-styles-css-custom-properties.php index 990f551a625ca..bcc30c1411700 100644 --- a/lib/compat/wordpress-5.9/global-styles-css-custom-properties.php +++ b/lib/compat/wordpress-5.9/global-styles-css-custom-properties.php @@ -13,7 +13,7 @@ */ function wp_enqueue_global_styles_css_custom_properties() { wp_register_style( 'global-styles-css-custom-properties', false, array(), true, true ); - wp_add_inline_style( 'global-styles-css-custom-properties', wp_get_global_stylesheet( array( 'variables' ) ) ); + wp_add_inline_style( 'global-styles-css-custom-properties', gutenberg_get_global_stylesheet( array( 'variables' ) ) ); wp_enqueue_style( 'global-styles-css-custom-properties' ); } add_filter( 'enqueue_block_editor_assets', 'wp_enqueue_global_styles_css_custom_properties' ); diff --git a/lib/compat/wordpress-5.9/script-loader.php b/lib/compat/wordpress-5.9/script-loader.php index aea2275672598..7f407ec1b2900 100644 --- a/lib/compat/wordpress-5.9/script-loader.php +++ b/lib/compat/wordpress-5.9/script-loader.php @@ -28,7 +28,7 @@ function gutenberg_enqueue_global_styles_assets() { return; } - $stylesheet = wp_get_global_stylesheet(); + $stylesheet = gutenberg_get_global_stylesheet(); if ( empty( $stylesheet ) ) { return; diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php b/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php index a78e89e397604..74a77d59d15e0 100644 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php +++ b/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php @@ -16,6 +16,47 @@ */ class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_5_9 { + /** + * Metadata for style properties. + * + * Each element is a direct mapping from the CSS property name to the + * path to the value in theme.json & block attributes. + */ + const PROPERTIES_METADATA = array( + 'background' => array( 'color', 'gradient' ), + 'background-color' => array( 'color', 'background' ), + 'border-radius' => array( 'border', 'radius' ), + 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), + 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), + 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ), + 'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ), + 'border-color' => array( 'border', 'color' ), + 'border-width' => array( 'border', 'width' ), + 'border-style' => array( 'border', 'style' ), + 'color' => array( 'color', 'text' ), + 'font-family' => array( 'typography', 'fontFamily' ), + 'font-size' => array( 'typography', 'fontSize' ), + 'font-style' => array( 'typography', 'fontStyle' ), + 'font-weight' => array( 'typography', 'fontWeight' ), + 'letter-spacing' => array( 'typography', 'letterSpacing' ), + 'line-height' => array( 'typography', 'lineHeight' ), + 'margin' => array( 'spacing', 'margin' ), + 'margin-top' => array( 'spacing', 'margin', 'top' ), + 'margin-right' => array( 'spacing', 'margin', 'right' ), + 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ), + 'margin-left' => array( 'spacing', 'margin', 'left' ), + 'padding' => array( 'spacing', 'padding' ), + 'padding-top' => array( 'spacing', 'padding', 'top' ), + 'padding-right' => array( 'spacing', 'padding', 'right' ), + 'padding-bottom' => array( 'spacing', 'padding', 'bottom' ), + 'padding-left' => array( 'spacing', 'padding', 'left' ), + 'position' => array( 'layout', 'position' ), + '--wp--style--block-gap' => array( 'spacing', 'blockGap' ), + 'text-decoration' => array( 'typography', 'textDecoration' ), + 'text-transform' => array( 'typography', 'textTransform' ), + 'filter' => array( 'filter', 'duotone' ), + ); + /** * The top-level keys a theme.json can have. * @@ -30,6 +71,98 @@ class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_5_9 { 'version', ); + /** + * The valid properties under the settings key. + * + * @var array + */ + const VALID_SETTINGS = array( + 'appearanceTools' => null, + 'border' => array( + 'color' => null, + 'radius' => null, + 'style' => null, + 'width' => null, + ), + 'color' => array( + 'background' => null, + 'custom' => null, + 'customDuotone' => null, + 'customGradient' => null, + 'defaultGradients' => null, + 'defaultPalette' => null, + 'duotone' => null, + 'gradients' => null, + 'link' => null, + 'palette' => null, + 'text' => null, + ), + 'custom' => null, + 'layout' => array( + 'contentSize' => null, + 'position' => null, + 'wideSize' => null, + ), + 'spacing' => array( + 'blockGap' => null, + 'margin' => null, + 'padding' => null, + 'units' => null, + ), + 'typography' => array( + 'customFontSize' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textDecoration' => null, + 'textTransform' => null, + ), + ); + + /** + * The valid properties under the styles key. + * + * @var array + */ + const VALID_STYLES = array( + 'border' => array( + 'color' => null, + 'radius' => null, + 'style' => null, + 'width' => null, + ), + 'color' => array( + 'background' => null, + 'gradient' => null, + 'text' => null, + ), + 'filter' => array( + 'duotone' => null, + ), + 'layout' => array( + 'position' => null, + ), + 'spacing' => array( + 'margin' => null, + 'padding' => null, + 'blockGap' => 'top', + ), + 'typography' => array( + 'fontFamily' => null, + 'fontSize' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textDecoration' => null, + 'textTransform' => null, + ), + ); + /** * Returns the current theme's wanted patterns(slugs) to be * registered from Pattern Directory. diff --git a/lib/compat/wordpress-5.9/theme.json b/lib/compat/wordpress-6.0/theme.json similarity index 99% rename from lib/compat/wordpress-5.9/theme.json rename to lib/compat/wordpress-6.0/theme.json index ec29439d7f13f..28e2fd0f6f2f2 100644 --- a/lib/compat/wordpress-5.9/theme.json +++ b/lib/compat/wordpress-6.0/theme.json @@ -184,6 +184,9 @@ ], "text": true }, + "layout": { + "position": false + }, "spacing": { "blockGap": null, "margin": false, diff --git a/lib/global-styles.php b/lib/global-styles.php index a54373288421a..4f5555a028507 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -33,7 +33,7 @@ function_exists( 'gutenberg_is_edit_site_page' ) && } if ( 'mobile' === $context && WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { - $settings['__experimentalStyles'] = wp_get_global_styles(); + $settings['__experimentalStyles'] = gutenberg_get_global_styles(); } if ( 'other' === $context ) { @@ -53,7 +53,7 @@ function_exists( 'gutenberg_is_edit_site_page' ) && $new_global_styles = array(); - $css_variables = wp_get_global_stylesheet( array( 'variables' ) ); + $css_variables = gutenberg_get_global_stylesheet( array( 'variables' ) ); if ( '' !== $css_variables ) { $new_global_styles[] = array( 'css' => $css_variables, @@ -61,7 +61,7 @@ function_exists( 'gutenberg_is_edit_site_page' ) && ); } - $css_presets = wp_get_global_stylesheet( array( 'presets' ) ); + $css_presets = gutenberg_get_global_stylesheet( array( 'presets' ) ); if ( '' !== $css_presets ) { $new_global_styles[] = array( 'css' => $css_presets, @@ -70,7 +70,7 @@ function_exists( 'gutenberg_is_edit_site_page' ) && } if ( WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { - $css_blocks = wp_get_global_stylesheet( array( 'styles' ) ); + $css_blocks = gutenberg_get_global_stylesheet( array( 'styles' ) ); if ( '' !== $css_blocks ) { $new_global_styles[] = array( 'css' => $css_blocks, @@ -83,7 +83,7 @@ function_exists( 'gutenberg_is_edit_site_page' ) && } // Copied from get_block_editor_settings() at wordpress-develop/block-editor.php. - $settings['__experimentalFeatures'] = wp_get_global_settings(); + $settings['__experimentalFeatures'] = gutenberg_get_global_settings(); if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) { $colors_by_origin = $settings['__experimentalFeatures']['color']['palette']; diff --git a/lib/load.php b/lib/load.php index 8c309c2848b54..20b8c6bfe6c34 100644 --- a/lib/load.php +++ b/lib/load.php @@ -69,10 +69,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-5.9/block-template-utils.php'; require __DIR__ . '/compat/wordpress-5.9/default-editor-styles.php'; require __DIR__ . '/compat/wordpress-5.9/register-global-styles-cpt.php'; -// Needs to be loaded before get-global-styles-and-settings.php -// to make sure we can use the check "function_exists( 'wp_get_global_styles' )". -// If it loads after, that function will always be present at that point -// and the global styles assets won't be loaded. require __DIR__ . '/compat/wordpress-5.9/script-loader.php'; require __DIR__ . '/compat/wordpress-5.9/get-global-styles-and-settings.php'; require __DIR__ . '/compat/wordpress-5.9/render-svg-filters.php'; diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index b2cfde0b5e06c..dedf701a41165 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -30,9 +30,12 @@ import { LayoutStyle } from '../components/block-list/layout'; import BlockList from '../components/block-list'; import { getLayoutType, getLayoutTypes } from '../layouts'; -const layoutBlockSupportKey = '__experimentalLayout'; +import { PositionEdit } from './position'; -function LayoutPanel( { setAttributes, attributes, name: blockName } ) { +export const LAYOUT_SUPPORT_KEY = '__experimentalLayout'; + +function LayoutPanel( props ) { + const { setAttributes, attributes, name: blockName } = props; const { layout } = attributes; const defaultThemeLayout = useSetting( 'layout' ); const themeSupportsLayout = useSelect( ( select ) => { @@ -42,13 +45,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; @@ -103,6 +107,10 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { layoutBlockSupport={ layoutBlockSupport } /> ) } + + { ! inherit && allowPosition && ( + + ) } { ! inherit && layoutType && ( @@ -145,7 +153,7 @@ export function addAttribute( settings ) { if ( has( settings.attributes, [ 'layout', 'type' ] ) ) { return settings; } - if ( hasBlockSupport( settings, layoutBlockSupportKey ) ) { + if ( hasBlockSupport( settings, LAYOUT_SUPPORT_KEY ) ) { settings.attributes = { ...settings.attributes, layout: { @@ -167,10 +175,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 && , @@ -192,14 +197,14 @@ export const withLayoutStyles = createHigherOrderComponent( const { name, attributes } = props; const shouldRenderLayoutStyles = hasBlockSupport( name, - layoutBlockSupportKey + LAYOUT_SUPPORT_KEY ); const id = 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 ? defaultThemeLayout : layout || defaultBlockLayout || {}; diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js new file mode 100644 index 0000000000000..e6193a6544062 --- /dev/null +++ b/packages/block-editor/src/hooks/position.js @@ -0,0 +1,153 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Platform } from '@wordpress/element'; +import { getBlockSupport } from '@wordpress/blocks'; +import { + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import useSetting from '../components/use-setting'; +import { LAYOUT_SUPPORT_KEY } from './layout'; +import { cleanEmptyObject } from './utils'; + +// TODO: Should these be available for editing in theme.json? +// These options are currently based in part on: +// https://github.com/wordpress/gutenberg/blob/aac3e86b3858143c967bfcc28ab11f72d3aafa1b/packages/components/src/font-size-picker/utils.js#L65 +const POSITION_OPTIONS = [ + { + key: 'normal', + label: __( 'Normal' ), + value: '', + name: __( 'Normal' ), + }, + { + key: 'sticky', + label: __( 'Sticky' ), + value: 'sticky', + name: __( 'Sticky' ), + }, +]; + +/** + * Determines if there is position support. + * + * @param {string|Object} blockType Block name or Block Type object. + * + * @return {boolean} Whether there is support. + */ +export function hasPositionSupport( blockType ) { + const support = getBlockSupport( blockType, LAYOUT_SUPPORT_KEY ); + return !! ( true === support || support?.allowPosition ); +} + +/** + * Checks if there is a current value in the position block support attributes. + * + * @param {Object} props Block props. + * @return {boolean} Whether or not the block has a position value set. + */ +export function hasPositionValue( props ) { + return props.attributes.style?.layout?.position !== undefined; +} + +/** + * Resets the position block support attributes. This can be used when disabling + * the position support controls for a block via a `ToolsPanel`. + * + * @param {Object} props Block props. + * @param {Object} props.attributes Block's attributes. + * @param {Object} props.setAttributes Function to set block's attributes. + */ +export function resetPosition( { attributes = {}, setAttributes } ) { + const { style } = attributes; + + setAttributes( { + style: cleanEmptyObject( { + ...style, + layout: { + ...style?.layout, + position: undefined, + }, + } ), + } ); +} + +/** + * Custom hook that checks if position settings have been disabled. + * + * @param {string} name The name of the block. + * + * @return {boolean} Whether padding setting is disabled. + */ +export function useIsPositionDisabled( { name: blockName } = {} ) { + const isDisabled = ! useSetting( 'layout.position' ); + + return ! hasPositionSupport( blockName ) || isDisabled; +} + +/** + * Inspector control panel containing the padding related configuration + * + * @param {Object} props + * + * @return {WPElement} Padding edit element. + */ +export function PositionEdit( props ) { + const { + attributes: { style }, + setAttributes, + } = props; + + if ( useIsPositionDisabled( props ) ) { + return null; + } + + const onChange = ( next ) => { + const newStyle = { + ...style, + layout: { + ...style?.layout, + position: next, + }, + }; + + setAttributes( { + style: cleanEmptyObject( newStyle ), + } ); + }; + + return Platform.select( { + web: ( + <> + { + onChange( newValue ); + } } + isBlock + > + { POSITION_OPTIONS.map( ( option ) => ( + + ) ) } + + + ), + native: null, + } ); +} diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index 5a7617f0321c5..1aac327d6233b 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -60,7 +60,10 @@ "fontSize": true } }, - "__experimentalLayout": true + "__experimentalLayout": { + "allowSwitching": false, + "allowPosition": true + } }, "editorStyle": "wp-block-group-editor", "style": "wp-block-group" diff --git a/packages/block-library/src/group/style.scss b/packages/block-library/src/group/style.scss index 3457a12adb622..2f895941af0bf 100644 --- a/packages/block-library/src/group/style.scss +++ b/packages/block-library/src/group/style.scss @@ -1,4 +1,11 @@ .wp-block-group { // This block has customizable padding, border-box makes that more predictable. box-sizing: border-box; + // TODO: Remove the following line before getting the PR ready for review. + // It should be a feature of sticky positioning that we can set this or another + // side value. + // Also, note that using the CSS variable doesn't play nicely on mobile, as the + // admin bar isn't sticky on mobile. + top: var(--wp-admin--admin-bar--height, 0); + z-index: 1; } diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index a237402e143f9..172e04804c1c4 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -101,6 +101,10 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, useEngine: true, }, + position: { + value: [ 'layout', 'position' ], + support: [ '__experimentalLayout', 'position' ], + }, textDecoration: { value: [ 'typography', 'textDecoration' ], support: [ 'typography', '__experimentalTextDecoration' ],