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' ],