diff --git a/.eslintrc.js b/.eslintrc.js
index 81408499bd34f4..5a939aeb9173b7 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -278,7 +278,6 @@ module.exports = {
},
},
{
- // Temporary rules until we're ready to officially deprecate the bottom margins.
files: [ 'packages/*/src/**/*.[tj]s?(x)' ],
excludedFiles: [
'packages/components/src/**/@(test|stories)/**',
@@ -289,9 +288,12 @@ module.exports = {
'error',
...restrictedSyntax,
...restrictedSyntaxComponents,
+ // Temporary rules until we're ready to officially deprecate the bottom margins.
...[
+ 'BaseControl',
'CheckboxControl',
'ComboboxControl',
+ 'DimensionControl',
'FocalPointPicker',
'RangeControl',
'SearchControl',
@@ -307,6 +309,34 @@ module.exports = {
componentName +
' should have the `__nextHasNoMarginBottom` prop to opt-in to the new margin-free styles.',
} ) ),
+ // Temporary rules until we're ready to officially default to the new size.
+ ...[
+ 'BorderBoxControl',
+ 'BorderControl',
+ 'ComboboxControl',
+ 'CustomSelectControl',
+ 'DimensionControl',
+ 'FontSizePicker',
+ 'NumberControl',
+ 'RangeControl',
+ 'ToggleGroupControl',
+ ].map( ( componentName ) => ( {
+ // Falsy `__next40pxDefaultSize` without a non-default `size` prop.
+ selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"][value.expression.value!=false])):not(:has(JSXAttribute[name.name="size"][value.value!="default"]))`,
+ message:
+ componentName +
+ ' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.',
+ } ) ),
+ // Temporary rules until all existing components have the `__next40pxDefaultSize` prop.
+ ...[ 'SelectControl', 'TextControl' ].map(
+ ( componentName ) => ( {
+ // Not strict. Allows pre-existing __next40pxDefaultSize={ false } usage until they are all manually updated.
+ selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"])):not(:has(JSXAttribute[name.name="size"]))`,
+ message:
+ componentName +
+ ' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.',
+ } )
+ ),
],
},
},
diff --git a/.github/workflows/props-bot.yml b/.github/workflows/props-bot.yml
index 0f21f47ef14f99..b2332aabb816c7 100644
--- a/.github/workflows/props-bot.yml
+++ b/.github/workflows/props-bot.yml
@@ -18,7 +18,7 @@ on:
# You cannot filter this event for PR comments only.
# However, the logic below does short-circuit the workflow for issues.
issue_comment:
- type:
+ types:
- created
# This event will run everytime a new PR review is initially submitted.
pull_request_review:
diff --git a/backport-changelog/6.6/7145.md b/backport-changelog/6.6/7145.md
new file mode 100644
index 00000000000000..386f765cb22fa8
--- /dev/null
+++ b/backport-changelog/6.6/7145.md
@@ -0,0 +1,3 @@
+https://github.com/WordPress/wordpress-develop/pull/7145
+
+* https://github.com/WordPress/gutenberg/pull/64076
diff --git a/backport-changelog/6.7/7125.md b/backport-changelog/6.7/7125.md
new file mode 100644
index 00000000000000..ce208decd2d145
--- /dev/null
+++ b/backport-changelog/6.7/7125.md
@@ -0,0 +1,3 @@
+https://github.com/WordPress/wordpress-develop/pull/7125
+
+* https://github.com/WordPress/gutenberg/pull/61577
diff --git a/backport-changelog/6.7/7137.md b/backport-changelog/6.7/7137.md
index 834cb29a21e6d9..00771b8bc6c21d 100644
--- a/backport-changelog/6.7/7137.md
+++ b/backport-changelog/6.7/7137.md
@@ -1,3 +1,5 @@
https://github.com/WordPress/wordpress-develop/pull/7137
+* https://github.com/WordPress/gutenberg/pull/64128
* https://github.com/WordPress/gutenberg/pull/64192
+* https://github.com/WordPress/gutenberg/pull/64328
diff --git a/backport-changelog/6.7/7179.md b/backport-changelog/6.7/7179.md
new file mode 100644
index 00000000000000..d777eace2cb05e
--- /dev/null
+++ b/backport-changelog/6.7/7179.md
@@ -0,0 +1,5 @@
+https://github.com/WordPress/wordpress-develop/pull/7179
+
+* https://github.com/WordPress/gutenberg/pull/64401
+* https://github.com/WordPress/gutenberg/pull/64459
+* https://github.com/WordPress/gutenberg/pull/64477
diff --git a/changelog.txt b/changelog.txt
index e85895547e87d1..748df8da3484c7 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,7 +1,6 @@
== Changelog ==
-= 19.0.0-rc.1 =
-
+= 19.0.0 =
## Changelog
@@ -368,6 +367,8 @@ The following contributors merged PRs in this release:
@aaronrobertshaw @adamsilverstein @afercia @akasunil @Aljullu @amitraj2203 @andrewserong @carolinan @cbravobernal @Chrico @ciampo @creativecoder @DaniGuardiola @DAreRodz @djcowan @ellatrix @jameskoster @jasmussen @jeryj @jorgefilipecosta @jsnajdr @kebbet @kmanijak @Mamaduka @matiasbenedetto @meteorlxy @mikachan @mirka @mtias @ndiego @noisysocks @oandregal @ramonjd @richtabor @Rishit30G @ryanwelcher @SantosGuillamot @scruffian @shail-mehta @simison @stokesman @t-hamano @talldan @tomdevisser @tomjn @tyxla @up1512001 @wzieba @youknowriad
+
+
= 18.9.0 =
## Changelog
diff --git a/docs/explanations/architecture/modularity.md b/docs/explanations/architecture/modularity.md
index f94f8ec7b9472e..ff619ccbfdf5b7 100644
--- a/docs/explanations/architecture/modularity.md
+++ b/docs/explanations/architecture/modularity.md
@@ -42,7 +42,7 @@ function MyApp() {
```php
// myplugin.php
-// Example of script registration dependending on the "components" and "element packages.
+// Example of script registration depending on the "components" and "element packages.
wp_register_script( 'myscript', 'pathtomyscript.js', array ('wp-components', "react" ) );
```
diff --git a/docs/getting-started/fundamentals/block-wrapper.md b/docs/getting-started/fundamentals/block-wrapper.md
index 39c80262d7bcbe..98c435f6ebe2f7 100644
--- a/docs/getting-started/fundamentals/block-wrapper.md
+++ b/docs/getting-started/fundamentals/block-wrapper.md
@@ -102,7 +102,7 @@ The [example block](https://github.com/WordPress/block-development-examples/tree
## Dynamic render markup
-In dynamic blocks, where the font-end markup is rendered server-side, you can utilize the [`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) function to output the necessary classes and attributes just like you would use `useBlockProps.save()` in the `save` function. (See [example](https://github.com/WordPress/block-development-examples/blob/f68640f42d993f0866d1879f67c73910285ca114/plugins/block-dynamic-rendering-64756b/src/render.php#L11))
+In dynamic blocks, where the front-end markup is rendered server-side, you can utilize the [`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) function to output the necessary classes and attributes just like you would use `useBlockProps.save()` in the `save` function. (See [example](https://github.com/WordPress/block-development-examples/blob/f68640f42d993f0866d1879f67c73910285ca114/plugins/block-dynamic-rendering-64756b/src/render.php#L11))
```php
>
diff --git a/docs/getting-started/fundamentals/static-dynamic-rendering.md b/docs/getting-started/fundamentals/static-dynamic-rendering.md
index 8d199f66cccd2a..dfb6a7123b44b3 100644
--- a/docs/getting-started/fundamentals/static-dynamic-rendering.md
+++ b/docs/getting-started/fundamentals/static-dynamic-rendering.md
@@ -61,7 +61,7 @@ Dynamic blocks, which we'll explore in the following section, can specify an ini
For a practical demonstration of how this works, refer to the [Building your first block](/docs/getting-started/tutorial.md) tutorial. Specifically, the [Adding static rendering](/docs/getting-started/tutorial.md#adding-static-rendering) section illustrates how a block can have both a saved HTML structure and dynamic rendering capabilities.
-WordPress provides mechanisms like the render_block are the $render_callback function to alter the saved HTML of a block before it appears on the front end. These tools offer developers the capability to customize block output dynamically, catering to complex and interactive user experiences.
+WordPress provides mechanisms like the render_block and the render_callback function to alter the saved HTML of a block before it appears on the front end. These tools offer developers the capability to customize block output dynamically, catering to complex and interactive user experiences.
Additional examples of WordPress blocks that use static rendering, meaning their output is fixed at the time of saving and doesn't rely on server-side processing, include:
diff --git a/docs/manifest.json b/docs/manifest.json
index 1704e6d711510f..b483449872cc76 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -767,6 +767,12 @@
"markdown_source": "../packages/components/src/combobox-control/README.md",
"parent": "components"
},
+ {
+ "title": "Composite",
+ "slug": "composite",
+ "markdown_source": "../packages/components/src/composite/README.md",
+ "parent": "components"
+ },
{
"title": "ConfirmDialog",
"slug": "confirm-dialog",
diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index 5beb712c80a113..b9cae44550181c 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -783,7 +783,7 @@ Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Ju
- **Name:** core/quote
- **Category:** text
-- **Supports:** anchor, background (backgroundImage, backgroundSize), color (background, gradients, heading, link, text), dimensions (minHeight), interactivity (clientNavigation), layout (~~allowEditing~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
+- **Supports:** align (full, left, right, wide), anchor, background (backgroundImage, backgroundSize), color (background, gradients, heading, link, text), dimensions (minHeight), interactivity (clientNavigation), layout (~~allowEditing~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** citation, textAlign, value
## Read More
diff --git a/docs/reference-guides/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md
index e7a31c1e3bbc83..637cecadf1402b 100644
--- a/docs/reference-guides/filters/block-filters.md
+++ b/docs/reference-guides/filters/block-filters.md
@@ -139,12 +139,12 @@ The following PHP filters are available to change the output of a block on the f
### `render_block`
-Filters the font-end content of any block. This filter has no impact on the behavior of blocks in the Editor.
+Filters the front-end content of any block. This filter has no impact on the behavior of blocks in the Editor.
The callback function for this filter receives three parameters:
- `$block_content` (`string`): The block content.
-- `block` (`array`): The full block, including name and attributes.
+- `$block` (`array`): The full block, including name and attributes.
- `$instance` (`WP_Block`): The block instance.
In the following example, the class `example-class` is added to all Paragraph blocks on the front end. Here the [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) is used to easily add the class instead of relying on regex.
@@ -172,12 +172,12 @@ add_filter( 'render_block', 'example_add_custom_class_to_paragraph_block', 10, 2
### `render_block_{namespace/block}`
-Filters the font-end content of the defined block. This is just a simpler form of `render_block` when you only need to modify a specific block type.
+Filters the front-end content of the defined block. This is just a simpler form of `render_block` when you only need to modify a specific block type.
The callback function for this filter receives three parameters:
- `$block_content` (`string`): The block content.
-- `block` (`array`): The full block, including name and attributes.
+- `$block` (`array`): The full block, including name and attributes.
- `$instance` (`WP_Block`): The block instance.
In the following example, the class `example-class` is added to all Paragraph blocks on the front end. Notice that compared to the `render_block` example above, you no longer need to check the block type before modifying the content. Again, the [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) is used instead of regex.
diff --git a/docs/reference-guides/interactivity-api/api-reference.md b/docs/reference-guides/interactivity-api/api-reference.md
index a4b400b8c0276b..46bd20bece0bda 100644
--- a/docs/reference-guides/interactivity-api/api-reference.md
+++ b/docs/reference-guides/interactivity-api/api-reference.md
@@ -776,7 +776,7 @@ Actions are just regular JavaScript functions. Usually triggered by the `data-wp
```ts
const { state, actions } = store("myPlugin", {
actions: {
- selectItem: (id?: number) => {
+ selectItem: ( id ) => {
const context = getContext();
// `id` is optional here, so this action can be used in a directive.
state.selected = id || context.id;
@@ -1152,7 +1152,7 @@ store('mySliderPlugin', {
## Server functions
-The Interactivity API comes with handy functions on the PHP part. Apart from [setting the store via server](#on-the-server-side), there is also a function to get and set Interactivity related config variables.
+The Interactivity API comes with handy functions that allow you to initialize and reference configuration options on the server. This is necessary to feed the initial data that the Server Directive Processing will use to modify the HTML markup before it's send to the browser. It is also a great way to leverage many of WordPress's APIs, like nonces, AJAX, and translations.
### wp_interactivity_config
@@ -1181,6 +1181,53 @@ This config can be retrieved on the client:
const { showLikeButton } = getConfig();
```
+### wp_interactivity_state
+
+`wp_interactivity_state` allows the initialization of the global state on the server, which will be used to process the directives on the server and then will be merged with any global state defined in the client.
+
+Initializing the global state on the server also allows you to use many critical WordPress APIs, including [AJAX](https://developer.wordpress.org/plugins/javascript/ajax/), or [nonces](https://developer.wordpress.org/plugins/javascript/enqueuing/#nonce).
+
+The `wp_interactivity_state` function receives two arguments, a string with the namespace that will be used as a reference and an associative array containing the values.
+
+Here is an example of passing the WP Admin AJAX endpoint with a nonce.
+
+```php
+// render.php
+
+wp_interactivity_state(
+ 'myPlugin',
+ array(
+ 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'myPlugin_nonce' ),
+ ),
+);
+```
+
+```js
+// view.js
+
+const { state } = store( 'myPlugin', {
+ actions: {
+ *doSomething() {
+ try {
+ const formData = new FormData();
+ formData.append( 'action', 'do_something' );
+ formData.append( '_ajax_nonce', state.nonce );
+
+ const data = yield fetch( state.ajaxUrl, {
+ method: 'POST',
+ body: formData,
+ } ).then( ( response ) => response.json() );
+ console.log( 'Server data!', data );
+ } catch ( e ) {
+ // Something went wrong!
+ }
+ },
+ },
+ }
+);
+```
+
### wp_interactivity_process_directives
`wp_interactivity_process_directives` returns the updated HTML after the directives have been processed.
diff --git a/docs/reference-guides/slotfills/README.md b/docs/reference-guides/slotfills/README.md
index 8b56ed4ce98b41..5ae68cdb5cb071 100644
--- a/docs/reference-guides/slotfills/README.md
+++ b/docs/reference-guides/slotfills/README.md
@@ -70,11 +70,10 @@ export default function PostSummary( { onActionPerformed } ) {
const { isRemovedPostStatusPanel } = useSelect( ( select ) => {
// We use isEditorPanelRemoved to hide the panel if it was programmatically removed. We do
// not use isEditorPanelEnabled since this panel should not be disabled through the UI.
- const { isEditorPanelRemoved, getCurrentPostType } =
+ const { isEditorPanelRemoved } =
select( editorStore );
return {
isRemovedPostStatusPanel: isEditorPanelRemoved( PANEL_NAME ),
- postType: getCurrentPostType(),
};
}, [] );
@@ -85,11 +84,7 @@ export default function PostSummary( { onActionPerformed } ) {
<>
- }
+ onActionPerformed={ onActionPerformed }
/>
diff --git a/lib/block-supports/background.php b/lib/block-supports/background.php
index 811608127f47ed..a1d99133c1fc09 100644
--- a/lib/block-supports/background.php
+++ b/lib/block-supports/background.php
@@ -62,7 +62,7 @@ function gutenberg_render_background_support( $block_content, $block ) {
$background_styles['backgroundSize'] = $background_styles['backgroundSize'] ?? 'cover';
// If the background size is set to `contain` and no position is set, set the position to `center`.
if ( 'contain' === $background_styles['backgroundSize'] && ! $background_styles['backgroundPosition'] ) {
- $background_styles['backgroundPosition'] = 'center';
+ $background_styles['backgroundPosition'] = '50% 50%';
}
}
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index ad8722091c2d48..756ef06c80aa87 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -1744,7 +1744,7 @@ protected function get_layout_styles( $block_metadata, $types = array() ) {
$spacing_rule['selector']
);
} else {
- $format = static::ROOT_BLOCK_SELECTOR === $selector ? '.%2$s %3$s' : '%1$s-%2$s %3$s';
+ $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':root :where(.%2$s)%3$s' : ':root :where(%1$s-%2$s)%3$s';
$layout_selector = sprintf(
$format,
$selector,
@@ -2329,7 +2329,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) {
* ```php
* array(
* 'name' => 'property_name',
- * 'value' => 'property_value,
+ * 'value' => 'property_value',
* )
* ```
*
@@ -2338,6 +2338,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) {
* @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters.
* @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set.
* @since 6.6.0 Passing current theme JSON settings to wp_get_typography_font_size_value(). Using style engine to correctly fetch background CSS values.
+ * @since 6.7.0 Allow ref resolution of background properties.
*
* @param array $styles Styles to process.
* @param array $settings Theme settings.
@@ -2381,21 +2382,28 @@ protected static function compute_style_properties( $styles, $settings = array()
$root_variable_duplicates[] = substr( $css_property, $root_style_length );
}
- // Processes background styles.
- if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) {
- /*
- * For user-uploaded images at the block level, assign defaults.
- * Matches defaults applied in the editor and in block supports: background.php.
- */
- if ( static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) {
- $styles['background']['backgroundSize'] = $styles['background']['backgroundSize'] ?? 'cover';
- // If the background size is set to `contain` and no position is set, set the position to `center`.
- if ( 'contain' === $styles['background']['backgroundSize'] && empty( $styles['background']['backgroundPosition'] ) ) {
- $styles['background']['backgroundPosition'] = 'center';
- }
+ /*
+ * Processes background image styles.
+ * If the value is a URL, it will be converted to a CSS `url()` value.
+ * For an uploaded image (images with a database ID), apply size and position
+ * defaults equal to those applied in block supports in lib/background.php.
+ */
+ if ( 'background-image' === $css_property && ! empty( $value ) ) {
+ $background_styles = gutenberg_style_engine_get_styles(
+ array( 'background' => array( 'backgroundImage' => $value ) )
+ );
+
+ $value = $background_styles['declarations'][ $css_property ];
+ }
+ if ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) {
+ if ( 'background-size' === $css_property ) {
+ $value = 'cover';
+ }
+ // If the background size is set to `contain` and no position is set, set the position to `center`.
+ if ( 'background-position' === $css_property ) {
+ $background_size = $styles['background']['backgroundSize'] ?? null;
+ $value = 'contain' === $background_size ? '50% 50%' : null;
}
- $background_styles = gutenberg_style_engine_get_styles( array( 'background' => $styles['background'] ) );
- $value = $background_styles['declarations'][ $css_property ] ?? $value;
}
// Skip if empty and not "0" or value represents array of longhand values.
@@ -2463,6 +2471,7 @@ protected static function compute_style_properties( $styles, $settings = array()
* @since 5.8.0
* @since 5.9.0 Added support for values of array type, which are returned as is.
* @since 6.1.0 Added the `$theme_json` parameter.
+ * @since 6.7.0 Added support for background image refs
*
* @param array $styles Styles subtree.
* @param array $path Which property to process.
@@ -2479,15 +2488,17 @@ protected static function get_property_value( $styles, $path, $theme_json = null
}
/*
- * This converts references to a path to the value at that path
- * where the values is an array with a "ref" key, pointing to a path.
+ * Where the current value is an array with a 'ref' key pointing
+ * to a path, this converts that path into the value at that path.
* For example: { "ref": "style.color.background" } => "#fff".
*/
if ( is_array( $value ) && isset( $value['ref'] ) ) {
$value_path = explode( '.', $value['ref'] );
- $ref_value = _wp_array_get( $theme_json, $value_path );
+ $ref_value = _wp_array_get( $theme_json, $value_path, null );
+ // Background Image refs can refer to a string or an array containing a URL string.
+ $ref_value_url = $ref_value['url'] ?? null;
// Only use the ref value if we find anything.
- if ( ! empty( $ref_value ) && is_string( $ref_value ) ) {
+ if ( ! empty( $ref_value ) && ( is_string( $ref_value ) || is_string( $ref_value_url ) ) ) {
$value = $ref_value;
}
@@ -3247,6 +3258,25 @@ public function merge( $incoming ) {
}
}
}
+
+ /*
+ * Style values are merged at the leaf level, however
+ * some values provide exceptions, namely style values that are
+ * objects and represent unique definitions for the style.
+ */
+ $style_nodes = static::get_styles_block_nodes();
+ foreach ( $style_nodes as $style_node ) {
+ $path = $style_node['path'];
+ /*
+ * Background image styles should be replaced, not merged,
+ * as they themselves are specific object definitions for the style.
+ */
+ $background_image_path = array_merge( $path, static::PROPERTIES_METADATA['background-image'] );
+ $content = _wp_array_get( $incoming_data, $background_image_path, null );
+ if ( isset( $content ) ) {
+ _wp_array_set( $this->theme_json, $background_image_path, $content );
+ }
+ }
}
/**
diff --git a/lib/compat/wordpress-6.7/block-templates.php b/lib/compat/wordpress-6.7/block-templates.php
new file mode 100644
index 00000000000000..e270ab226c1d9f
--- /dev/null
+++ b/lib/compat/wordpress-6.7/block-templates.php
@@ -0,0 +1,41 @@
+register( $template_name, $args );
+ }
+}
+
+if ( ! function_exists( 'wp_unregister_block_template' ) ) {
+ /**
+ * Unregister a template.
+ *
+ * @param string $template_name Template name in the form of `plugin_uri//template_name`.
+ * @return true|WP_Error True on success, WP_Error on failure or if the template doesn't exist.
+ */
+ function wp_unregister_block_template( $template_name ) {
+ return WP_Block_Templates_Registry::get_instance()->unregister( $template_name );
+ }
+}
diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php
new file mode 100644
index 00000000000000..ed67dded75ecb1
--- /dev/null
+++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php
@@ -0,0 +1,203 @@
+post_type );
+ } else {
+ $template = get_block_template( $request['id'], $this->post_type );
+ }
+
+ if ( ! $template ) {
+ return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
+ }
+
+ return $this->prepare_item_for_response( $template, $request );
+ }
+
+ /**
+ * Prepare a single template output for response
+ *
+ * @param WP_Block_Template $item Template instance.
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response Response object.
+ */
+ // @core-merge: Fix wrong author in plugin templates.
+ public function prepare_item_for_response( $item, $request ) {
+ $template = $item;
+
+ $fields = $this->get_fields_for_response( $request );
+
+ if ( 'plugin' !== $item->origin ) {
+ return parent::prepare_item_for_response( $item, $request );
+ }
+ $cloned_item = clone $item;
+ // Set the origin as theme when calling the previous `prepare_item_for_response()` to prevent warnings when generating the author text.
+ $cloned_item->origin = 'theme';
+ $response = parent::prepare_item_for_response( $cloned_item, $request );
+ $data = $response->data;
+
+ if ( rest_is_field_included( 'origin', $fields ) ) {
+ $data['origin'] = 'plugin';
+ }
+
+ if ( rest_is_field_included( 'plugin', $fields ) ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $cloned_item->slug );
+ if ( $registered_template ) {
+ $data['plugin'] = $registered_template->plugin;
+ }
+ }
+
+ if ( rest_is_field_included( 'author_text', $fields ) ) {
+ $data['author_text'] = $this->get_wp_templates_author_text_field( $template );
+ }
+
+ if ( rest_is_field_included( 'original_source', $fields ) ) {
+ $data['original_source'] = $this->get_wp_templates_original_source_field( $template );
+ }
+
+ $response = rest_ensure_response( $data );
+
+ if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
+ $links = $this->prepare_links( $template->id );
+ $response->add_links( $links );
+ if ( ! empty( $links['self']['href'] ) ) {
+ $actions = $this->get_available_actions();
+ $self = $links['self']['href'];
+ foreach ( $actions as $rel ) {
+ $response->add_link( $rel, $self );
+ }
+ }
+ }
+
+ return $response;
+ }
+
+ /**
+ * Returns the source from where the template originally comes from.
+ *
+ * @param WP_Block_Template $template_object Template instance.
+ * @return string Original source of the template one of theme, plugin, site, or user.
+ */
+ // @core-merge: Changed the comments format (from inline to multi-line) in the entire function.
+ private static function get_wp_templates_original_source_field( $template_object ) {
+ if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) {
+ /*
+ * Added by theme.
+ * Template originally provided by a theme, but customized by a user.
+ * Templates originally didn't have the 'origin' field so identify
+ * older customized templates by checking for no origin and a 'theme'
+ * or 'custom' source.
+ */
+ if ( $template_object->has_theme_file &&
+ ( 'theme' === $template_object->origin || (
+ empty( $template_object->origin ) && in_array(
+ $template_object->source,
+ array(
+ 'theme',
+ 'custom',
+ ),
+ true
+ ) )
+ )
+ ) {
+ return 'theme';
+ }
+
+ // Added by plugin.
+ // @core-merge: Removed `$template_object->has_theme_file` check from this if clause.
+ if ( 'plugin' === $template_object->origin ) {
+ return 'plugin';
+ }
+
+ /*
+ * Added by site.
+ * Template was created from scratch, but has no author. Author support
+ * was only added to templates in WordPress 5.9. Fallback to showing the
+ * site logo and title.
+ */
+ if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) {
+ return 'site';
+ }
+ }
+
+ // Added by user.
+ return 'user';
+ }
+
+ /**
+ * Returns a human readable text for the author of the template.
+ *
+ * @param WP_Block_Template $template_object Template instance.
+ * @return string Human readable text for the author.
+ */
+ private static function get_wp_templates_author_text_field( $template_object ) {
+ $original_source = self::get_wp_templates_original_source_field( $template_object );
+ switch ( $original_source ) {
+ case 'theme':
+ $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' );
+ return empty( $theme_name ) ? $template_object->theme : $theme_name;
+ case 'plugin':
+ // @core-merge: Prioritize plugin name instead of theme name for plugin-registered templates.
+ if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'get_plugin_data' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+ if ( isset( $template_object->plugin ) ) {
+ $plugins = wp_get_active_and_valid_plugins();
+
+ foreach ( $plugins as $plugin_file ) {
+ $plugin_basename = plugin_basename( $plugin_file );
+ // Split basename by '/' to get the plugin slug.
+ list( $plugin_slug, ) = explode( '/', $plugin_basename );
+
+ if ( $plugin_slug === $template_object->plugin ) {
+ $plugin_data = get_plugin_data( $plugin_file );
+
+ if ( ! empty( $plugin_data['Name'] ) ) {
+ return $plugin_data['Name'];
+ }
+
+ break;
+ }
+ }
+ }
+
+ /*
+ * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards
+ * compatibility with templates that were registered before the plugin attribute was added.
+ */
+ $plugins = get_plugins();
+ $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) );
+ if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) {
+ return $plugins[ $plugin_basename ]['Name'];
+ }
+ return isset( $template_object->plugin ) ?
+ $template_object->plugin :
+ $template_object->theme;
+ // @core-merge: End of changes to merge in core.
+ case 'site':
+ return get_bloginfo( 'name' );
+ case 'user':
+ $author = get_user_by( 'id', $template_object->author );
+ if ( ! $author ) {
+ return __( 'Unknown author' );
+ }
+ return $author->get( 'display_name' );
+ }
+ }
+}
diff --git a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php
new file mode 100644
index 00000000000000..db53f735e13b3d
--- /dev/null
+++ b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php
@@ -0,0 +1,256 @@
+ $instance` pairs.
+ *
+ * @since 6.7.0
+ * @var WP_Block_Template[] $registered_block_templates Registered templates.
+ */
+ private $registered_templates = array();
+
+ /**
+ * Container for the main instance of the class.
+ *
+ * @since 6.7.0
+ * @var WP_Block_Templates_Registry|null
+ */
+ private static $instance = null;
+
+ /**
+ * Registers a template.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_name Template name including namespace.
+ * @param array $args Optional. Array of template arguments.
+ * @return WP_Block_Template|WP_Error The registered template on success, or false on failure.
+ */
+ public function register( $template_name, $args = array() ) {
+
+ $template = null;
+
+ $error_message = '';
+ $error_code = '';
+ if ( ! is_string( $template_name ) ) {
+ $error_message = __( 'Template names must be strings.', 'gutenberg' );
+ $error_code = 'template_name_no_string';
+ } elseif ( preg_match( '/[A-Z]+/', $template_name ) ) {
+ $error_message = __( 'Template names must not contain uppercase characters.', 'gutenberg' );
+ $error_code = 'template_name_no_uppercase';
+ } elseif ( ! preg_match( '/^[a-z0-9-]+\/\/[a-z0-9-]+$/', $template_name ) ) {
+ $error_message = __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' );
+ $error_code = 'template_no_prefix';
+ } elseif ( $this->is_registered( $template_name ) ) {
+ /* translators: %s: Template name. */
+ $error_message = sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name );
+ $error_code = 'template_already_registered';
+ }
+
+ if ( $error_message ) {
+ _doing_it_wrong(
+ __METHOD__,
+ $error_message,
+ '6.7.0'
+ );
+ return new WP_Error( $error_code, $error_message );
+ }
+
+ if ( ! $template ) {
+ $theme_name = get_stylesheet();
+ list( $plugin, $slug ) = explode( '//', $template_name );
+ $default_template_types = get_default_block_template_types();
+
+ $template = new WP_Block_Template();
+ $template->id = $theme_name . '//' . $slug;
+ $template->theme = $theme_name;
+ $template->plugin = $plugin;
+ $template->author = null;
+ $template->content = isset( $args['content'] ) ? $args['content'] : '';
+ $template->source = 'plugin';
+ $template->slug = $slug;
+ $template->type = 'wp_template';
+ $template->title = isset( $args['title'] ) ? $args['title'] : $template_name;
+ $template->description = isset( $args['description'] ) ? $args['description'] : '';
+ $template->status = 'publish';
+ $template->origin = 'plugin';
+ $template->is_custom = ! isset( $default_template_types[ $template_name ] );
+ $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : array();
+ }
+
+ $this->registered_templates[ $template_name ] = $template;
+
+ return $template;
+ }
+
+ /**
+ * Retrieves all registered templates.
+ *
+ * @since 6.7.0
+ *
+ * @return WP_Block_Template[]|false Associative array of `$template_name => $template` pairs.
+ */
+ public function get_all_registered() {
+ return $this->registered_templates;
+ }
+
+ /**
+ * Retrieves a registered template by its name.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_name Template name including namespace.
+ * @return WP_Block_Template|null|false The registered template, or null if it is not registered.
+ */
+ public function get_registered( $template_name ) {
+ if ( ! $this->is_registered( $template_name ) ) {
+ return null;
+ }
+
+ return $this->registered_templates[ $template_name ];
+ }
+
+ /**
+ * Retrieves a registered template by its slug.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_slug Slug of the template.
+ * @return WP_Block_Template|null The registered template, or null if it is not registered.
+ */
+ public function get_by_slug( $template_slug ) {
+ $all_templates = $this->get_all_registered();
+
+ if ( ! $all_templates ) {
+ return null;
+ }
+
+ foreach ( $all_templates as $template ) {
+ if ( $template->slug === $template_slug ) {
+ return $template;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieves registered templates matching a query.
+ *
+ * @since 6.7.0
+ *
+ * @param array $query {
+ * Arguments to retrieve templates. Optional, empty by default.
+ *
+ * @type string[] $slug__in List of slugs to include.
+ * @type string[] $slug__not_in List of slugs to skip.
+ * @type string $post_type Post type to get the templates for.
+ * }
+ */
+ public function get_by_query( $query = array() ) {
+ $all_templates = $this->get_all_registered();
+
+ if ( ! $all_templates ) {
+ return array();
+ }
+
+ $query = wp_parse_args(
+ $query,
+ array(
+ 'slug__in' => array(),
+ 'slug__not_in' => array(),
+ 'post_type' => '',
+ )
+ );
+ $slugs_to_include = $query['slug__in'];
+ $slugs_to_skip = $query['slug__not_in'];
+ $post_type = $query['post_type'];
+
+ $matching_templates = array();
+ foreach ( $all_templates as $template_name => $template ) {
+ if ( $slugs_to_include && ! in_array( $template->slug, $slugs_to_include, true ) ) {
+ continue;
+ }
+
+ if ( $slugs_to_skip && in_array( $template->slug, $slugs_to_skip, true ) ) {
+ continue;
+ }
+
+ if ( $post_type && ! in_array( $post_type, $template->post_types, true ) ) {
+ continue;
+ }
+
+ $matching_templates[ $template_name ] = $template;
+ }
+
+ return $matching_templates;
+ }
+
+ /**
+ * Checks if a template is registered.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_name Template name.
+ * @return bool True if the template is registered, false otherwise.
+ */
+ public function is_registered( $template_name ) {
+ return isset( $this->registered_templates[ $template_name ] );
+ }
+
+ /**
+ * Unregisters a template.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_name Template name including namespace.
+ * @return WP_Block_Template|false The unregistered template on success, or false on failure.
+ */
+ public function unregister( $template_name ) {
+ if ( ! $this->is_registered( $template_name ) ) {
+ _doing_it_wrong(
+ __METHOD__,
+ /* translators: %s: Template name. */
+ sprintf( __( 'Template "%s" is not registered.', 'gutenberg' ), $template_name ),
+ '6.7.0'
+ );
+ /* translators: %s: Template name. */
+ return new WP_Error( 'template_not_registered', __( 'Template "%s" is not registered.', 'gutenberg' ) );
+ }
+
+ $unregistered_template = $this->registered_templates[ $template_name ];
+ unset( $this->registered_templates[ $template_name ] );
+
+ return $unregistered_template;
+ }
+
+ /**
+ * Utility method to retrieve the main instance of the class.
+ *
+ * The instance will be created if it does not exist yet.
+ *
+ * @since 6.7.0
+ *
+ * @return WP_Block_Templates_Registry The main instance.
+ */
+ public static function get_instance() {
+ if ( null === self::$instance ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+ }
+}
diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php
new file mode 100644
index 00000000000000..edc8e3fa5fb03f
--- /dev/null
+++ b/lib/compat/wordpress-6.7/compat.php
@@ -0,0 +1,114 @@
+ $value ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug );
+ if ( $registered_template ) {
+ $query_result[ $key ]->plugin = $registered_template->plugin;
+ $query_result[ $key ]->origin =
+ 'theme' !== $query_result[ $key ]->origin && 'theme' !== $query_result[ $key ]->source ?
+ 'plugin' :
+ $query_result[ $key ]->origin;
+ }
+ }
+
+ if ( ! isset( $query['wp_id'] ) ) {
+ $template_files = _gutenberg_get_block_templates_files( $template_type, $query );
+
+ /*
+ * Add templates registered in the template registry. Filtering out the ones which have a theme file.
+ */
+ $registered_templates = WP_Block_Templates_Registry::get_instance()->get_by_query( $query );
+ $matching_registered_templates = array_filter(
+ $registered_templates,
+ function ( $registered_template ) use ( $template_files ) {
+ foreach ( $template_files as $template_file ) {
+ if ( $template_file['slug'] === $registered_template->slug ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ );
+ $query_result = array_merge( $query_result, $matching_registered_templates );
+ }
+
+ return $query_result;
+}
+add_filter( 'get_block_templates', '_gutenberg_add_block_templates_from_registry', 10, 3 );
+
+/**
+ * Hooks into `get_block_template` to add the `plugin` property when necessary.
+ *
+ * @param [WP_Block_Template|null] $block_template The found block template, or null if there isn’t one.
+ * @return [WP_Block_Template|null] The block template that was already found with the plugin property defined if it was registered by a plugin.
+ */
+function _gutenberg_add_block_template_plugin_attribute( $block_template ) {
+ if ( $block_template ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug );
+ if ( $registered_template ) {
+ $block_template->plugin = $registered_template->plugin;
+ $block_template->origin =
+ 'theme' !== $block_template->origin && 'theme' !== $block_template->source ?
+ 'plugin' :
+ $block_template->origin;
+ }
+ }
+
+ return $block_template;
+}
+add_filter( 'get_block_template', '_gutenberg_add_block_template_plugin_attribute', 10, 1 );
+
+/**
+ * Hooks into `get_block_file_template` so templates from the registry are also returned.
+ *
+ * @param WP_Block_Template|null $block_template The found block template, or null if there is none.
+ * @param string $id Template unique identifier (example: 'theme_slug//template_slug').
+ * @return WP_Block_Template|null The block template that was already found or from the registry. In case the template was already found, add the necessary details from the registry.
+ */
+function _gutenberg_add_block_file_templates_from_registry( $block_template, $id ) {
+ if ( $block_template ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug );
+ if ( $registered_template ) {
+ $block_template->plugin = $registered_template->plugin;
+ $block_template->origin =
+ 'theme' !== $block_template->origin && 'theme' !== $block_template->source ?
+ 'plugin' :
+ $block_template->origin;
+ }
+ return $block_template;
+ }
+
+ $parts = explode( '//', $id, 2 );
+
+ if ( count( $parts ) < 2 ) {
+ return $block_template;
+ }
+
+ list( , $slug ) = $parts;
+ return WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug );
+}
+add_filter( 'get_block_file_template', '_gutenberg_add_block_file_templates_from_registry', 10, 2 );
diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php
new file mode 100644
index 00000000000000..081c22c8102914
--- /dev/null
+++ b/lib/compat/wordpress-6.7/rest-api.php
@@ -0,0 +1,100 @@
+name ) {
+ // Fixes post type name. It should be `type/wp_template_part`.
+ $parts_key = array_search( '/wp/v2/types/wp_template-part?context=edit', $paths, true );
+ if ( false !== $parts_key ) {
+ $paths[ $parts_key ] = '/wp/v2/types/wp_template_part?context=edit';
+ }
+ }
+
+ if ( 'core/edit-post' === $context->name ) {
+ $reusable_blocks_key = array_search(
+ add_query_arg(
+ array(
+ 'context' => 'edit',
+ 'per_page' => -1,
+ ),
+ rest_get_route_for_post_type_items( 'wp_block' )
+ ),
+ $paths,
+ true
+ );
+
+ if ( false !== $reusable_blocks_key ) {
+ unset( $paths[ $reusable_blocks_key ] );
+ }
+ }
+
+ return $paths;
+}
+add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_7', 10, 2 );
+
+if ( ! function_exists( 'wp_api_template_registry' ) ) {
+ /**
+ * Hook in to the template and template part post types and modify the rest
+ * endpoint to include modifications to read templates from the
+ * BlockTemplatesRegistry.
+ *
+ * @param array $args Current registered post type args.
+ * @param string $post_type Name of post type.
+ *
+ * @return array
+ */
+ function wp_api_template_registry( $args, $post_type ) {
+ if ( 'wp_template' === $post_type || 'wp_template_part' === $post_type ) {
+ $args['rest_controller_class'] = 'Gutenberg_REST_Templates_Controller_6_7';
+ }
+ return $args;
+ }
+}
+add_filter( 'register_post_type_args', 'wp_api_template_registry', 10, 2 );
+
+/**
+ * Adds `plugin` fields to WP_REST_Templates_Controller class.
+ */
+function gutenberg_register_wp_rest_templates_controller_plugin_field() {
+
+ register_rest_field(
+ 'wp_template',
+ 'plugin',
+ array(
+ 'get_callback' => function ( $template_object ) {
+ if ( $template_object ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template_object['slug'] );
+ if ( $registered_template ) {
+ return $registered_template->plugin;
+ }
+ }
+
+ return;
+ },
+ 'update_callback' => null,
+ 'schema' => array(
+ 'type' => 'string',
+ 'description' => __( 'Plugin that registered the template.', 'gutenberg' ),
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit', 'embed' ),
+ ),
+ )
+ );
+}
+add_action( 'rest_api_init', 'gutenberg_register_wp_rest_templates_controller_plugin_field' );
diff --git a/lib/compat/wordpress-6.7/script-modules.php b/lib/compat/wordpress-6.7/script-modules.php
new file mode 100644
index 00000000000000..0a440ec81688d2
--- /dev/null
+++ b/lib/compat/wordpress-6.7/script-modules.php
@@ -0,0 +1,104 @@
+setAccessible( true );
+ $get_import_map = new ReflectionMethod( 'WP_Script_Modules', 'get_import_map' );
+ $get_import_map->setAccessible( true );
+
+ $modules = array();
+ foreach ( array_keys( $get_marked_for_enqueue->invoke( wp_script_modules() ) ) as $id ) {
+ $modules[ $id ] = true;
+ }
+ foreach ( array_keys( $get_import_map->invoke( wp_script_modules() )['imports'] ) as $id ) {
+ $modules[ $id ] = true;
+ }
+
+ foreach ( array_keys( $modules ) as $module_id ) {
+ /**
+ * Filters data associated with a given Script Module.
+ *
+ * Script Modules may require data that is required for initialization or is essential to
+ * have immediately available on page load. These are suitable use cases for this data.
+ *
+ * This is best suited to a minimal set of data and is not intended to replace the REST API.
+ *
+ * If the filter returns no data (an empty array), nothing will be embedded in the page.
+ *
+ * The data for a given Script Module, if provided, will be JSON serialized in a script tag
+ * with an ID like `wp-script-module-data-{$module_id}`.
+ *
+ * The dynamic portion of the hook name, `$module_id`, refers to the Script Module ID that
+ * the data is associated with.
+ *
+ * @param array $data The data that should be associated with the array.
+ */
+ $data = apply_filters( "script_module_data_{$module_id}", array() );
+
+ if ( is_array( $data ) && ! empty( $data ) ) {
+ /*
+ * This data will be printed as JSON inside a script tag like this:
+ *
+ *
+ * A script tag must be closed by a sequence beginning with ``. It's impossible to
+ * close a script tag without using `<`. We ensure that `<` is escaped and `/` can
+ * remain unescaped, so `` will be printed as `\u003C/script\u00E3`.
+ *
+ * - JSON_HEX_TAG: All < and > are converted to \u003C and \u003E.
+ * - JSON_UNESCAPED_SLASHES: Don't escape /.
+ *
+ * If the page will use UTF-8 encoding, it's safe to print unescaped unicode:
+ *
+ * - JSON_UNESCAPED_UNICODE: Encode multibyte Unicode characters literally (instead of as `\uXXXX`).
+ * - JSON_UNESCAPED_LINE_TERMINATORS: The line terminators are kept unescaped when
+ * JSON_UNESCAPED_UNICODE is supplied. It uses the same behaviour as it was
+ * before PHP 7.1 without this constant. Available as of PHP 7.1.0.
+ *
+ * The JSON specification requires encoding in UTF-8, so if the generated HTML page
+ * is not encoded in UTF-8 then it's not safe to include those literals. They must
+ * be escaped to avoid encoding issues.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc8259.html for details on encoding requirements.
+ * @see https://www.php.net/manual/en/json.constants.php for details on these constants.
+ * @see https://html.spec.whatwg.org/#script-data-state for details on script tag parsing.
+ */
+ $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS;
+ if ( 'UTF-8' !== get_option( 'blog_charset' ) ) {
+ $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
+ }
+
+ wp_print_inline_script_tag(
+ wp_json_encode( $data, $json_encode_flags ),
+ array(
+ 'type' => 'application/json',
+ 'id' => "wp-script-module-data-{$module_id}",
+ )
+ );
+ }
+ }
+}
+
+add_action(
+ 'after_setup_theme',
+ function () {
+ if ( ! has_action( 'wp_footer', array( wp_script_modules(), 'print_script_module_data' ) ) ) {
+ add_action( 'wp_footer', 'gutenberg_print_script_module_data' );
+ }
+
+ if ( ! has_action( 'admin_print_footer_scripts', array( wp_script_modules(), 'print_script_module_data' ) ) ) {
+ add_action( 'admin_print_footer_scripts', 'gutenberg_print_script_module_data' );
+ }
+ },
+ 20
+);
diff --git a/lib/experimental/script-modules.php b/lib/experimental/script-modules.php
index 0093c2e974568f..5a14e1418ed6de 100644
--- a/lib/experimental/script-modules.php
+++ b/lib/experimental/script-modules.php
@@ -200,96 +200,3 @@ function gutenberg_dequeue_module( $module_identifier ) {
_deprecated_function( __FUNCTION__, 'Gutenberg 17.6.0', 'wp_dequeue_script_module' );
wp_script_modules()->dequeue( $module_identifier );
}
-
-
-/**
- * Print data associated with Script Modules in Script tags.
- *
- * This embeds data in the page HTML so that it is available on page load.
- *
- * Data can be associated with a given Script Module by using the
- * `script_module_data_{$module_id}` filter.
- *
- * The data for a given Script Module will be JSON serialized in a script tag with an ID
- * like `wp-script-module-data-{$module_id}`.
- */
-function gutenberg_print_script_module_data(): void {
- $get_marked_for_enqueue = new ReflectionMethod( 'WP_Script_Modules', 'get_marked_for_enqueue' );
- $get_marked_for_enqueue->setAccessible( true );
- $get_import_map = new ReflectionMethod( 'WP_Script_Modules', 'get_import_map' );
- $get_import_map->setAccessible( true );
-
- $modules = array();
- foreach ( array_keys( $get_marked_for_enqueue->invoke( wp_script_modules() ) ) as $id ) {
- $modules[ $id ] = true;
- }
- foreach ( array_keys( $get_import_map->invoke( wp_script_modules() )['imports'] ) as $id ) {
- $modules[ $id ] = true;
- }
-
- foreach ( array_keys( $modules ) as $module_id ) {
- /**
- * Filters data associated with a given Script Module.
- *
- * Script Modules may require data that is required for initialization or is essential to
- * have immediately available on page load. These are suitable use cases for this data.
- *
- * This is best suited to a minimal set of data and is not intended to replace the REST API.
- *
- * If the filter returns no data (an empty array), nothing will be embedded in the page.
- *
- * The data for a given Script Module, if provided, will be JSON serialized in a script tag
- * with an ID like `wp-script-module-data-{$module_id}`.
- *
- * The dynamic portion of the hook name, `$module_id`, refers to the Script Module ID that
- * the data is associated with.
- *
- * @param array $data The data that should be associated with the array.
- */
- $data = apply_filters( "script_module_data_{$module_id}", array() );
-
- if ( is_array( $data ) && ! empty( $data ) ) {
- /*
- * This data will be printed as JSON inside a script tag like this:
- *
- *
- * A script tag must be closed by a sequence beginning with ``. It's impossible to
- * close a script tag without using `<`. We ensure that `<` is escaped and `/` can
- * remain unescaped, so `` will be printed as `\u003C/script\u00E3`.
- *
- * - JSON_HEX_TAG: All < and > are converted to \u003C and \u003E.
- * - JSON_UNESCAPED_SLASHES: Don't escape /.
- *
- * If the page will use UTF-8 encoding, it's safe to print unescaped unicode:
- *
- * - JSON_UNESCAPED_UNICODE: Encode multibyte Unicode characters literally (instead of as `\uXXXX`).
- * - JSON_UNESCAPED_LINE_TERMINATORS: The line terminators are kept unescaped when
- * JSON_UNESCAPED_UNICODE is supplied. It uses the same behaviour as it was
- * before PHP 7.1 without this constant. Available as of PHP 7.1.0.
- *
- * The JSON specification requires encoding in UTF-8, so if the generated HTML page
- * is not encoded in UTF-8 then it's not safe to include those literals. They must
- * be escaped to avoid encoding issues.
- *
- * @see https://www.rfc-editor.org/rfc/rfc8259.html for details on encoding requirements.
- * @see https://www.php.net/manual/en/json.constants.php for details on these constants.
- * @see https://html.spec.whatwg.org/#script-data-state for details on script tag parsing.
- */
- $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS;
- if ( 'UTF-8' !== get_option( 'blog_charset' ) ) {
- $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
- }
-
- wp_print_inline_script_tag(
- wp_json_encode( $data, $json_encode_flags ),
- array(
- 'type' => 'application/json',
- 'id' => "wp-script-module-data-{$module_id}",
- )
- );
- }
- }
-}
-
-add_action( 'wp_footer', 'gutenberg_print_script_module_data' );
-add_action( 'admin_print_footer_scripts', 'gutenberg_print_script_module_data' );
diff --git a/lib/load.php b/lib/load.php
index 5a299f3b696968..b501f0abd1c978 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -40,6 +40,10 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php';
require __DIR__ . '/compat/wordpress-6.6/rest-api.php';
+ // WordPress 6.7 compat.
+ require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php';
+ require __DIR__ . '/compat/wordpress-6.7/rest-api.php';
+
// Plugin specific code.
require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php';
require_once __DIR__ . '/class-wp-rest-edit-site-export-controller-gutenberg.php';
@@ -98,8 +102,12 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.6/post.php';
// WordPress 6.7 compat.
+require __DIR__ . '/compat/wordpress-6.7/block-templates.php';
require __DIR__ . '/compat/wordpress-6.7/blocks.php';
require __DIR__ . '/compat/wordpress-6.7/block-bindings.php';
+require __DIR__ . '/compat/wordpress-6.7/script-modules.php';
+require __DIR__ . '/compat/wordpress-6.7/class-wp-block-templates-registry.php';
+require __DIR__ . '/compat/wordpress-6.7/compat.php';
// Experimental features.
require __DIR__ . '/experimental/block-editor-settings-mobile.php';
diff --git a/package-lock.json b/package-lock.json
index b0eccc961afb78..ddfec3a5dddc63 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "gutenberg",
- "version": "19.0.0-rc.1",
+ "version": "19.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "gutenberg",
- "version": "19.0.0-rc.1",
+ "version": "19.0.0",
"hasInstallScript": true,
"license": "GPL-2.0-or-later",
"dependencies": {
@@ -99,7 +99,7 @@
"@octokit/rest": "16.26.0",
"@octokit/types": "6.34.0",
"@octokit/webhooks-types": "5.8.0",
- "@playwright/test": "1.45.1",
+ "@playwright/test": "1.46.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@react-native/babel-preset": "0.73.10",
"@react-native/metro-babel-transformer": "0.73.10",
@@ -6950,12 +6950,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
- "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz",
+ "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==",
"dev": true,
"dependencies": {
- "playwright": "1.45.1"
+ "playwright": "1.46.0"
},
"bin": {
"playwright": "cli.js"
@@ -41083,12 +41083,12 @@
"dev": true
},
"node_modules/playwright": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz",
- "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz",
+ "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==",
"dev": true,
"dependencies": {
- "playwright-core": "1.45.1"
+ "playwright-core": "1.46.0"
},
"bin": {
"playwright": "cli.js"
@@ -41101,9 +41101,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz",
- "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz",
+ "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -41754,14 +41754,6 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
- "node_modules/postcss-prefixwrap": {
- "version": "1.41.0",
- "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.41.0.tgz",
- "integrity": "sha512-gmwwAEE+ci3/ZKjUZppTETINlh1QwihY8gCstInuS7ohk353KYItU4d64hvnUvO2GUy29hBGPHz4Ce+qJRi90A==",
- "peerDependencies": {
- "postcss": "*"
- }
- },
"node_modules/postcss-reduce-initial": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz",
@@ -52237,7 +52229,7 @@
"fast-deep-equal": "^3.1.3",
"memize": "^2.1.0",
"postcss": "^8.4.21",
- "postcss-prefixwrap": "^1.41.0",
+ "postcss-prefixwrap": "^1.51.0",
"postcss-urlrebase": "^1.4.0",
"react-autosize-textarea": "^7.1.0",
"react-easy-crop": "^5.0.6",
@@ -52252,6 +52244,15 @@
"react-dom": "^18.0.0"
}
},
+ "packages/block-editor/node_modules/postcss-prefixwrap": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.51.0.tgz",
+ "integrity": "sha512-PuP4md5zFSY921dUcLShwSLv2YyyDec0dK9/puXl/lu7ZNvJ1U59+ZEFRMS67xwfNg5nIIlPXnAycPJlhA/Isw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "postcss": "*"
+ }
+ },
"packages/block-editor/node_modules/postcss-urlrebase": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/postcss-urlrebase/-/postcss-urlrebase-1.4.0.tgz",
@@ -54585,7 +54586,7 @@
"npm": ">=8.19.2"
},
"peerDependencies": {
- "@playwright/test": "^1.45.1",
+ "@playwright/test": "^1.46.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
@@ -60001,12 +60002,12 @@
}
},
"@playwright/test": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
- "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz",
+ "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==",
"dev": true,
"requires": {
- "playwright": "1.45.1"
+ "playwright": "1.46.0"
}
},
"@pmmmwh/react-refresh-webpack-plugin": {
@@ -67255,13 +67256,18 @@
"fast-deep-equal": "^3.1.3",
"memize": "^2.1.0",
"postcss": "^8.4.21",
- "postcss-prefixwrap": "^1.41.0",
+ "postcss-prefixwrap": "^1.51.0",
"postcss-urlrebase": "^1.4.0",
"react-autosize-textarea": "^7.1.0",
"react-easy-crop": "^5.0.6",
"remove-accents": "^0.5.0"
},
"dependencies": {
+ "postcss-prefixwrap": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.51.0.tgz",
+ "integrity": "sha512-PuP4md5zFSY921dUcLShwSLv2YyyDec0dK9/puXl/lu7ZNvJ1U59+ZEFRMS67xwfNg5nIIlPXnAycPJlhA/Isw=="
+ },
"postcss-urlrebase": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/postcss-urlrebase/-/postcss-urlrebase-1.4.0.tgz",
@@ -87337,19 +87343,19 @@
"dev": true
},
"playwright": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz",
- "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz",
+ "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==",
"dev": true,
"requires": {
"fsevents": "2.3.2",
- "playwright-core": "1.45.1"
+ "playwright-core": "1.46.0"
}
},
"playwright-core": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz",
- "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz",
+ "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==",
"dev": true
},
"please-upgrade-node": {
@@ -87820,11 +87826,6 @@
}
}
},
- "postcss-prefixwrap": {
- "version": "1.41.0",
- "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.41.0.tgz",
- "integrity": "sha512-gmwwAEE+ci3/ZKjUZppTETINlh1QwihY8gCstInuS7ohk353KYItU4d64hvnUvO2GUy29hBGPHz4Ce+qJRi90A=="
- },
"postcss-reduce-initial": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz",
diff --git a/package.json b/package.json
index fac57093a852c9..ee78f197a43e28 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "19.0.0-rc.1",
+ "version": "19.0.0",
"private": true,
"description": "A new WordPress editor experience.",
"author": "The WordPress Contributors",
@@ -111,7 +111,7 @@
"@octokit/rest": "16.26.0",
"@octokit/types": "6.34.0",
"@octokit/webhooks-types": "5.8.0",
- "@playwright/test": "1.45.1",
+ "@playwright/test": "1.46.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@react-native/babel-preset": "0.73.10",
"@react-native/metro-babel-transformer": "0.73.10",
diff --git a/packages/base-styles/_colors.scss b/packages/base-styles/_colors.scss
index 2ce58b64e43b8c..e65551e48c783c 100644
--- a/packages/base-styles/_colors.scss
+++ b/packages/base-styles/_colors.scss
@@ -17,7 +17,6 @@ $gray-100: #f0f0f0; // Used for light gray backgrounds.
$white: #fff;
// Opacities & additional colors.
-$dark-theme-focus: $white; // Focus color when the theme is dark.
$dark-gray-placeholder: rgba($gray-900, 0.62);
$medium-gray-placeholder: rgba($gray-900, 0.55);
$light-gray-placeholder: rgba($white, 0.65);
@@ -26,3 +25,6 @@ $light-gray-placeholder: rgba($white, 0.65);
$alert-yellow: #f0b849;
$alert-red: #cc1818;
$alert-green: #4ab866;
+
+// Deprecated, please avoid using these.
+$dark-theme-focus: $white; // Focus color when the theme is dark.
diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss
index 97eb513cf38aeb..0f0c9e6d019ba3 100644
--- a/packages/base-styles/_variables.scss
+++ b/packages/base-styles/_variables.scss
@@ -47,9 +47,25 @@ $radius-x-small: 1px; // Applied to elements like buttons nested within primit
$radius-small: 2px; // Applied to most primitives.
$radius-medium: 4px; // Applied to containers with smaller padding.
$radius-large: 8px; // Applied to containers with larger padding.
-$radius-full: 9999px; // For lozenges.
+$radius-full: 9999px; // For pills.
$radius-round: 50%; // For circles and ovals.
+/**
+ * Elevation scale.
+ */
+
+// For sections and containers that group related content and controls, which may overlap other content. Example: Preview Frame.
+$elevation-x-small: 0 0.7px 1px rgba($black, 0.1), 0 1.2px 1.7px -0.2px rgba($black, 0.1), 0 2.3px 3.3px -0.5px rgba($black, 0.1);
+
+// For components that provide contextual feedback without being intrusive. Generally non-interruptive. Example: Tooltips, Snackbar.
+$elevation-small: 0 0.7px 1px 0 rgba(0, 0, 0, 0.12), 0 2.2px 3.7px -0.2px rgba(0, 0, 0, 0.12), 0 5.3px 7.3px -0.5px rgba(0, 0, 0, 0.12);
+
+// For components that offer additional actions. Example: Menus, Command Palette
+$elevation-medium: 0 0.7px 1px 0 rgba(0, 0, 0, 0.14), 0 4.2px 5.7px -0.2px rgba(0, 0, 0, 0.14), 0 7.3px 9.3px -0.5px rgba(0, 0, 0, 0.14);
+
+// For components that confirm decisions or handle necessary interruptions. Example: Modals.
+$elevation-large: 0 0.7px 1px rgba($black, 0.15), 0 2.7px 3.8px -0.2px rgba($black, 0.15), 0 5.5px 7.8px -0.3px rgba($black, 0.15), 0.1px 11.5px 16.4px -0.5px rgba($black, 0.15);
+
/**
* Dimensions.
*/
@@ -74,14 +90,6 @@ $modal-width-large: 840px;
$spinner-size: 16px;
$canvas-padding: $grid-unit-20;
-
-/**
- * Shadows.
- */
-
-$shadow-popover: 0 0.7px 1px rgba($black, 0.1), 0 1.2px 1.7px -0.2px rgba($black, 0.1), 0 2.3px 3.3px -0.5px rgba($black, 0.1);
-$shadow-modal: 0 0.7px 1px rgba($black, 0.15), 0 2.7px 3.8px -0.2px rgba($black, 0.15), 0 5.5px 7.8px -0.3px rgba($black, 0.15), 0.1px 11.5px 16.4px -0.5px rgba($black, 0.15);
-
/**
* Editor widths.
*/
@@ -107,6 +115,8 @@ $radio-input-size-sm: 24px; // Width & height for small viewports.
// Deprecated, please avoid using these.
$block-padding: 14px; // Used to define space between block footprint and surrouding borders.
$radius-block-ui: $radius-small;
+$shadow-popover: $elevation-x-small;
+$shadow-modal: $elevation-large;
/**
diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md
index 776b217ba54f6e..c798015804b3e5 100644
--- a/packages/block-editor/README.md
+++ b/packages/block-editor/README.md
@@ -920,20 +920,15 @@ _Usage_
import { useBlockProps } from '@wordpress/block-editor';
export default function Edit() {
-
- const blockProps = useBlockProps(
- className: 'my-custom-class',
- style: {
- color: '#222222',
- backgroundColor: '#eeeeee'
- }
- )
-
- return (
-
diff --git a/packages/block-editor/src/components/block-list/use-in-between-inserter.js b/packages/block-editor/src/components/block-list/use-in-between-inserter.js
index 74151fb3b070ba..bb307816fd1501 100644
--- a/packages/block-editor/src/components/block-list/use-in-between-inserter.js
+++ b/packages/block-editor/src/components/block-list/use-in-between-inserter.js
@@ -25,6 +25,7 @@ export function useInBetweenInserter() {
getBlockIndex,
isMultiSelecting,
getSelectedBlockClientIds,
+ getSettings,
getTemplateLock,
__unstableIsWithinBlockOverlay,
getBlockEditingMode,
@@ -88,9 +89,11 @@ export function useInBetweenInserter() {
return;
}
+ const blockListSettings = getBlockListSettings( rootClientId );
const orientation =
- getBlockListSettings( rootClientId )?.orientation ||
- 'vertical';
+ blockListSettings?.orientation || 'vertical';
+ const captureToolbars =
+ !! blockListSettings?.__experimentalCaptureToolbars;
const offsetTop = event.clientY;
const offsetLeft = event.clientX;
@@ -135,9 +138,18 @@ export function useInBetweenInserter() {
return;
}
- // Don't show the inserter when hovering above (conflicts with
- // block toolbar) or inside selected block(s).
- if ( getSelectedBlockClientIds().includes( clientId ) ) {
+ // Don't show the inserter if the following conditions are met,
+ // as it conflicts with the block toolbar:
+ // 1. when hovering above or inside selected block(s)
+ // 2. when the orientation is vertical
+ // 3. when the __experimentalCaptureToolbars is not enabled
+ // 4. when the Top Toolbar is not disabled
+ if (
+ getSelectedBlockClientIds().includes( clientId ) &&
+ orientation === 'vertical' &&
+ ! captureToolbars &&
+ ! getSettings().hasFixedToolbar
+ ) {
return;
}
const elementRect = element.getBoundingClientRect();
diff --git a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js
index 6810a21581f12e..7ca294a2894158 100644
--- a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js
+++ b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js
@@ -4,8 +4,7 @@
import { __ } from '@wordpress/i18n';
import { MenuItem } from '@wordpress/components';
import { getBlockType, hasBlockSupport } from '@wordpress/blocks';
-import { withSelect, withDispatch } from '@wordpress/data';
-import { compose } from '@wordpress/compose';
+import { useDispatch, useSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -14,13 +13,23 @@ import { store as blockEditorStore } from '../../store';
const noop = () => {};
-export function BlockModeToggle( {
- blockType,
- mode,
- onToggleMode,
- small = false,
- isCodeEditingEnabled = true,
-} ) {
+export default function BlockModeToggle( { clientId, onToggle = noop } ) {
+ const { blockType, mode, isCodeEditingEnabled } = useSelect(
+ ( select ) => {
+ const { getBlock, getBlockMode, getSettings } =
+ select( blockEditorStore );
+ const block = getBlock( clientId );
+
+ return {
+ mode: getBlockMode( clientId ),
+ blockType: block ? getBlockType( block.name ) : null,
+ isCodeEditingEnabled: getSettings().codeEditingEnabled,
+ };
+ },
+ [ clientId ]
+ );
+ const { toggleBlockMode } = useDispatch( blockEditorStore );
+
if (
! blockType ||
! hasBlockSupport( blockType, 'html', true ) ||
@@ -32,26 +41,14 @@ export function BlockModeToggle( {
const label =
mode === 'visual' ? __( 'Edit as HTML' ) : __( 'Edit visually' );
- return ;
+ return (
+
+ );
}
-
-export default compose( [
- withSelect( ( select, { clientId } ) => {
- const { getBlock, getBlockMode, getSettings } =
- select( blockEditorStore );
- const block = getBlock( clientId );
- const isCodeEditingEnabled = getSettings().codeEditingEnabled;
-
- return {
- mode: getBlockMode( clientId ),
- blockType: block ? getBlockType( block.name ) : null,
- isCodeEditingEnabled,
- };
- } ),
- withDispatch( ( dispatch, { onToggle = noop, clientId } ) => ( {
- onToggleMode() {
- dispatch( blockEditorStore ).toggleBlockMode( clientId );
- onToggle();
- },
- } ) ),
-] )( BlockModeToggle );
diff --git a/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js
index c297bad15f29ea..67d88125e3429c 100644
--- a/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js
+++ b/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js
@@ -3,16 +3,32 @@
*/
import { render, screen } from '@testing-library/react';
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+
/**
* Internal dependencies
*/
-import { BlockModeToggle } from '../block-mode-toggle';
+import BlockModeToggle from '../block-mode-toggle';
+
+jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() );
+
+function setupUseSelectMock( mode, blockType, codeEditingEnabled = true ) {
+ useSelect.mockImplementation( () => {
+ return {
+ mode,
+ blockType,
+ isCodeEditingEnabled: codeEditingEnabled,
+ };
+ } );
+}
describe( 'BlockModeToggle', () => {
it( "should not render the HTML mode button if the block doesn't support it", () => {
- render(
-
- );
+ setupUseSelectMock( undefined, { supports: { html: false } } );
+ render( );
expect(
screen.queryByRole( 'menuitem', { name: 'Edit as HTML' } )
@@ -20,12 +36,8 @@ describe( 'BlockModeToggle', () => {
} );
it( 'should render the HTML mode button', () => {
- render(
-
- );
+ setupUseSelectMock( 'visual', { supports: { html: true } } );
+ render( );
expect(
screen.getByRole( 'menuitem', { name: 'Edit as HTML' } )
@@ -33,12 +45,8 @@ describe( 'BlockModeToggle', () => {
} );
it( 'should render the Visual mode button', () => {
- render(
-
- );
+ setupUseSelectMock( 'html', { supports: { html: true } } );
+ render( );
expect(
screen.getByRole( 'menuitem', { name: 'Edit visually' } )
@@ -46,13 +54,8 @@ describe( 'BlockModeToggle', () => {
} );
it( 'should not render the Visual mode button if code editing is disabled', () => {
- render(
-
- );
+ setupUseSelectMock( 'html', { supports: { html: true } }, false );
+ render( );
expect(
screen.queryByRole( 'menuitem', { name: 'Edit visually' } )
diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js
index 9dac99e5e93124..469f7e53908cb4 100644
--- a/packages/block-editor/src/components/block-tools/insertion-point.js
+++ b/packages/block-editor/src/components/block-tools/insertion-point.js
@@ -38,6 +38,7 @@ function InbetweenInsertionPointPopover( {
isInserterShown,
isDistractionFree,
isNavigationMode,
+ isZoomOutMode,
} = useSelect( ( select ) => {
const {
getBlockOrder,
@@ -48,6 +49,7 @@ function InbetweenInsertionPointPopover( {
getNextBlockClientId,
getSettings,
isNavigationMode: _isNavigationMode,
+ __unstableGetEditorMode,
} = select( blockEditorStore );
const insertionPoint = getBlockInsertionPoint();
const order = getBlockOrder( insertionPoint.rootClientId );
@@ -79,6 +81,7 @@ function InbetweenInsertionPointPopover( {
isNavigationMode: _isNavigationMode(),
isDistractionFree: settings.isDistractionFree,
isInserterShown: insertionPoint?.__unstableWithInserter,
+ isZoomOutMode: __unstableGetEditorMode() === 'zoom-out',
};
}, [] );
const { getBlockEditingMode } = useSelect( blockEditorStore );
@@ -145,6 +148,14 @@ function InbetweenInsertionPointPopover( {
return null;
}
+ // Zoom out mode should only show the insertion point for the insert operation.
+ // Other operations such as "group" are when the editor tries to create a row
+ // block by grouping the block being dragged with the block it's being dropped
+ // onto.
+ if ( isZoomOutMode && operation !== 'insert' ) {
+ return null;
+ }
+
const orientationClassname =
orientation === 'horizontal' || operation === 'group'
? 'is-horizontal'
diff --git a/packages/block-editor/src/components/block-tools/use-show-block-tools.js b/packages/block-editor/src/components/block-tools/use-show-block-tools.js
index 33807445b8da74..07e0ebd16a64b0 100644
--- a/packages/block-editor/src/components/block-tools/use-show-block-tools.js
+++ b/packages/block-editor/src/components/block-tools/use-show-block-tools.js
@@ -20,6 +20,7 @@ export function useShowBlockTools() {
getSelectedBlockClientId,
getFirstMultiSelectedBlockClientId,
getBlock,
+ getBlockMode,
getSettings,
hasMultiSelection,
__unstableGetEditorMode,
@@ -33,7 +34,9 @@ export function useShowBlockTools() {
const editorMode = __unstableGetEditorMode();
const hasSelectedBlock = !! clientId && !! block;
const isEmptyDefaultBlock =
- hasSelectedBlock && isUnmodifiedDefaultBlock( block );
+ hasSelectedBlock &&
+ isUnmodifiedDefaultBlock( block ) &&
+ getBlockMode( clientId ) !== 'html';
const _showEmptyBlockSideInserter =
clientId &&
! isTyping() &&
diff --git a/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js b/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js
index bb044f9479c024..6f986ce90dc3bd 100644
--- a/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js
+++ b/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js
@@ -23,6 +23,7 @@ function ZoomOutModeInserters() {
sectionRootClientId,
selectedBlockClientId,
hoveredBlockClientId,
+ inserterSearchInputRef,
} = useSelect( ( select ) => {
const {
getSettings,
@@ -32,8 +33,11 @@ function ZoomOutModeInserters() {
getSelectedBlockClientId,
getHoveredBlockClientId,
isBlockInsertionPointVisible,
- } = select( blockEditorStore );
+ getInserterSearchInputRef,
+ } = unlock( select( blockEditorStore ) );
+
const { sectionRootClientId: root } = unlock( getSettings() );
+
return {
hasSelection: !! getSelectionStart().clientId,
blockInsertionPoint: getBlockInsertionPoint(),
@@ -44,6 +48,7 @@ function ZoomOutModeInserters() {
getSettings().__experimentalSetIsInserterOpened,
selectedBlockClientId: getSelectedBlockClientId(),
hoveredBlockClientId: getHoveredBlockClientId(),
+ inserterSearchInputRef: getInserterSearchInputRef(),
};
}, [] );
@@ -110,6 +115,7 @@ function ZoomOutModeInserters() {
showInsertionPoint( sectionRootClientId, index, {
operation: 'insert',
} );
+ inserterSearchInputRef?.current?.focus();
} }
/>
) }
diff --git a/packages/block-editor/src/components/border-radius-control/index.js b/packages/block-editor/src/components/border-radius-control/index.js
index 4c614084a7e200..cab9b87b3b29c0 100644
--- a/packages/block-editor/src/components/border-radius-control/index.js
+++ b/packages/block-editor/src/components/border-radius-control/index.js
@@ -104,6 +104,7 @@ export default function BorderRadiusControl( { onChange, values } ) {
units={ units }
/>
div {
- height: 40px;
- display: flex;
- align-items: center;
- }
- }
-
- > span {
- flex: 0 0 auto;
}
}
diff --git a/packages/block-editor/src/components/date-format-picker/index.js b/packages/block-editor/src/components/date-format-picker/index.js
index 15beec4ac6ed54..edefd6249f1aae 100644
--- a/packages/block-editor/src/components/date-format-picker/index.js
+++ b/packages/block-editor/src/components/date-format-picker/index.js
@@ -129,6 +129,7 @@ function NonDefaultControls( { format, onChange } ) {
return (
{ isCustom && (
{
*/
export default function FontAppearanceControl( props ) {
const {
+ /** Start opting into the larger default height that will become the default size in a future version. */
+ __next40pxDefaultSize = false,
onChange,
hasFontStyles = true,
hasFontWeights = true,
@@ -150,6 +152,7 @@ export default function FontAppearanceControl( props ) {
` instance.
+#### `__next40pxDefaultSize`
+
+- Type: `boolean`
+- Required: No
+- Default: `false`
+
+Start opting into the larger default height that will become the default size in a future version.
+
#### `__nextHasNoMarginBottom`
-- **Type:** `boolean`
-- **Default:** `false`
+- Type: `boolean`
+- Required: No
+- Default: `false`
Start opting into the new margin-free styles that will become the default in a future version.
diff --git a/packages/block-editor/src/components/font-family/index.js b/packages/block-editor/src/components/font-family/index.js
index 90a0412463b3ef..c87a52b4c676d2 100644
--- a/packages/block-editor/src/components/font-family/index.js
+++ b/packages/block-editor/src/components/font-family/index.js
@@ -11,6 +11,8 @@ import { __ } from '@wordpress/i18n';
import { useSettings } from '../use-settings';
export default function FontFamilyControl( {
+ /** Start opting into the larger default height that will become the default size in a future version. */
+ __next40pxDefaultSize = false,
/** Start opting into the new margin-free styles that will become the default in a future version. */
__nextHasNoMarginBottom = false,
value = '',
@@ -50,6 +52,7 @@ export default function FontFamilyControl( {
return (
+
+
+ );
+}
+
function BackgroundImageControls( {
onChange,
style,
inheritedValue,
onRemoveImage = noop,
+ onResetImage = noop,
displayInPanel,
- themeFileURIs,
+ defaultValues,
} ) {
- const mediaUpload = useSelect(
- ( select ) => select( blockEditorStore ).getSettings().mediaUpload,
- []
- );
+ const [ isUploading, setIsUploading ] = useState( false );
+ const { getSettings } = useSelect( blockEditorStore );
const { id, title, url } = style?.background?.backgroundImage || {
...inheritedValue?.background?.backgroundImage,
@@ -283,6 +297,7 @@ function BackgroundImageControls( {
const { createErrorNotice } = useDispatch( noticesStore );
const onUploadError = ( message ) => {
createErrorNotice( message, { type: 'snackbar' } );
+ setIsUploading( false );
};
const resetBackgroundImage = () =>
@@ -297,10 +312,12 @@ function BackgroundImageControls( {
const onSelectMedia = ( media ) => {
if ( ! media || ! media.url ) {
resetBackgroundImage();
+ setIsUploading( false );
return;
}
if ( isBlobURL( media.url ) ) {
+ setIsUploading( true );
return;
}
@@ -319,12 +336,8 @@ function BackgroundImageControls( {
}
const sizeValue =
- style?.background?.backgroundSize ||
- inheritedValue?.background?.backgroundSize;
- const positionValue =
- style?.background?.backgroundPosition ||
- inheritedValue?.background?.backgroundPosition;
-
+ style?.background?.backgroundSize || defaultValues?.backgroundSize;
+ const positionValue = style?.background?.backgroundPosition;
onChange(
setImmutably( style, [ 'background' ], {
...style?.background,
@@ -335,22 +348,33 @@ function BackgroundImageControls( {
title: media.title || undefined,
},
backgroundPosition:
+ /*
+ * A background image uploaded and set in the editor receives a default background position of '50% 0',
+ * when the background image size is the equivalent of "Tile".
+ * This is to increase the chance that the image's focus point is visible.
+ * This is in-editor only to assist with the user experience.
+ */
! positionValue && ( 'auto' === sizeValue || ! sizeValue )
? '50% 0'
: positionValue,
backgroundSize: sizeValue,
} )
);
+ setIsUploading( false );
};
+ // Drag and drop callback, restricting image to one.
const onFilesDrop = ( filesList ) => {
- mediaUpload( {
+ if ( filesList?.length > 1 ) {
+ onUploadError(
+ __( 'Only one image can be used as a background image.' )
+ );
+ return;
+ }
+ getSettings().mediaUpload( {
allowedTypes: [ IMAGE_BACKGROUND_TYPE ],
filesList,
onFileChange( [ image ] ) {
- if ( isBlobURL( image?.url ) ) {
- return;
- }
onSelectMedia( image );
},
onError: onUploadError,
@@ -372,7 +396,9 @@ function BackgroundImageControls( {
const onRemove = () =>
onChange(
- setImmutably( style, [ 'background', 'backgroundImage' ], 'none' )
+ setImmutably( style, [ 'background' ], {
+ backgroundImage: 'none',
+ } )
);
const canRemove = ! hasValue && hasBackgroundImageValue( inheritedValue );
const imgLabel =
@@ -383,6 +409,7 @@ function BackgroundImageControls( {
ref={ replaceContainerRef }
className="block-editor-global-styles-background-panel__image-tools-panel-item"
>
+ { isUploading && }
}
variant="secondary"
+ onError={ onUploadError }
>
{ canRemove && (
+This component is deprecated. Please use the [`NavigatorBackButton`](/packages/components/src/navigator/navigator-back-button/README.md) component instead.
+
The `NavigatorToParentButton` component can be used to navigate to a screen and should be used in combination with the [`NavigatorProvider`](/packages/components/src/navigator/navigator-provider/README.md), the [`NavigatorScreen`](/packages/components/src/navigator/navigator-screen/README.md) and the [`NavigatorButton`](/packages/components/src/navigator/navigator-button/README.md) components (or the `useNavigator` hook).
## Usage
diff --git a/packages/components/src/navigator/navigator-to-parent-button/component.tsx b/packages/components/src/navigator/navigator-to-parent-button/component.tsx
index e73a3619f3d494..400498b1fc96ca 100644
--- a/packages/components/src/navigator/navigator-to-parent-button/component.tsx
+++ b/packages/components/src/navigator/navigator-to-parent-button/component.tsx
@@ -1,62 +1,33 @@
/**
- * External dependencies
+ * WordPress dependencies
*/
-import type { ForwardedRef } from 'react';
+import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
+import { NavigatorBackButton } from '../navigator-back-button';
import type { WordPressComponentProps } from '../../context';
import { contextConnect } from '../../context';
-import { View } from '../../view';
-import { useNavigatorBackButton } from '../navigator-back-button/hook';
-import type { NavigatorToParentButtonProps } from '../types';
+import type { NavigatorBackButtonProps } from '../types';
function UnconnectedNavigatorToParentButton(
- props: WordPressComponentProps< NavigatorToParentButtonProps, 'button' >,
- forwardedRef: ForwardedRef< any >
+ props: WordPressComponentProps< NavigatorBackButtonProps, 'button' >,
+ forwardedRef: React.ForwardedRef< any >
) {
- const navigatorToParentButtonProps = useNavigatorBackButton( {
- ...props,
- goToParent: true,
+ deprecated( 'wp.components.NavigatorToParentButton', {
+ since: '6.7',
+ alternative: 'wp.components.NavigatorBackButton',
} );
- return ;
+ return ;
}
-/*
- * The `NavigatorToParentButton` component can be used to navigate to a screen and
- * should be used in combination with the `NavigatorProvider`, the
- * `NavigatorScreen` and the `NavigatorButton` components (or the `useNavigator`
- * hook).
- *
- * @example
- * ```jsx
- * import {
- * __experimentalNavigatorProvider as NavigatorProvider,
- * __experimentalNavigatorScreen as NavigatorScreen,
- * __experimentalNavigatorButton as NavigatorButton,
- * __experimentalNavigatorToParentButton as NavigatorToParentButton,
- * } from '@wordpress/components';
- *
- * const MyNavigation = () => (
- *
- *
- *
This is the home screen.
- *
- * Navigate to child screen.
- *
- *
+/**
+ * _Note: this component is deprecated. Please use the `NavigatorBackButton`
+ * component instead._
*
- *
- *
+ { /*
+ * A button useful to test focus restoration. This button is the first
+ * tabbable item in the screen, but should not receive focus when
+ * navigating to screen as a result of a backwards navigation.
+ */ }
+
+
+ { BUTTON_TEXT.toChildScreen }
+
+
+
+
+
{ SCREEN_TEXT.child }
+ { /*
+ * A button useful to test focus restoration. This button is the first
+ * tabbable item in the screen, but should not receive focus when
+ * navigating to screen as a result of a backwards navigation.
+ */ }
+
+
+ { BUTTON_TEXT.toNestedScreen }
+
+
+ { BUTTON_TEXT.back }
+
+
+
+
+
{ SCREEN_TEXT.nested }
+
+ { BUTTON_TEXT.back }
+
+
+
+ >
+ );
+};
+
const getScreen = ( screenKey: keyof typeof SCREEN_TEXT ) =>
screen.getByText( SCREEN_TEXT[ screenKey ] );
const queryScreen = ( screenKey: keyof typeof SCREEN_TEXT ) =>
@@ -769,4 +850,53 @@ describe( 'Navigator', () => {
).toHaveFocus();
} );
} );
+
+ describe( 'deprecated APIs', () => {
+ it( 'should log a deprecation notice when using the NavigatorToParentButton component', async () => {
+ const user = userEvent.setup();
+
+ render( );
+
+ expect( getScreen( 'child' ) ).toBeInTheDocument();
+
+ // Navigate back to home screen.
+ // The first tabbable element receives focus, since focus restoration
+ // it not possible (there was no forward navigation).
+ await user.click( getNavigationButton( 'back' ) );
+ expect( getScreen( 'home' ) ).toBeInTheDocument();
+ expect(
+ screen.getByRole( 'button', {
+ name: 'First tabbable home screen button',
+ } )
+ ).toHaveFocus();
+
+ // Rendering `NavigatorToParentButton` logs a deprecation notice
+ expect( console ).toHaveWarnedWith(
+ 'wp.components.NavigatorToParentButton is deprecated since version 6.7. Please use wp.components.NavigatorBackButton instead.'
+ );
+ } );
+
+ it( 'should log a deprecation notice when using the useNavigator().goToParent() function', async () => {
+ const user = userEvent.setup();
+
+ render( );
+
+ expect( getScreen( 'nested' ) ).toBeInTheDocument();
+
+ // Navigate back to child screen using the back button.
+ // The first tabbable element receives focus, since focus restoration
+ // it not possible (there was no forward navigation).
+ await user.click( getNavigationButton( 'back' ) );
+ expect( getScreen( 'child' ) ).toBeInTheDocument();
+ expect(
+ screen.getByRole( 'button', {
+ name: 'First tabbable child screen button',
+ } )
+ ).toHaveFocus();
+
+ expect( console ).toHaveWarnedWith(
+ 'wp.components.useNavigator().goToParent is deprecated since version 6.7. Please use wp.components.useNavigator().goBack instead.'
+ );
+ } );
+ } );
} );
diff --git a/packages/components/src/navigator/types.ts b/packages/components/src/navigator/types.ts
index 557f8074fd42e2..c45762d558af2d 100644
--- a/packages/components/src/navigator/types.ts
+++ b/packages/components/src/navigator/types.ts
@@ -11,26 +11,70 @@ import type { ButtonAsButtonProps } from '../button/types';
export type MatchParams = Record< string, string | string[] >;
export type NavigateOptions = {
+ /**
+ * Specify the CSS selector used to restore focus on an given element when
+ * navigating back. When not provided, the component will attempt to restore
+ * focus on the element that originated the forward navigation.
+ */
focusTargetSelector?: string;
+ /**
+ * Whether the navigation is a backwards navigation. This enables focus
+ * restoration (when possible), and causes the animation to be backwards.
+ */
isBack?: boolean;
+ /**
+ * Opt out of focus management. Useful when the consumer of the component
+ * wants to manage focus themselves.
+ */
skipFocus?: boolean;
+ /**
+ * Whether the navigation should replace the current location in the stack.
+ */
replace?: boolean;
};
export type NavigateToParentOptions = Omit< NavigateOptions, 'isBack' >;
export type NavigatorLocation = NavigateOptions & {
+ /**
+ * Whether the current location is the initial one (ie. first in the stack).
+ */
isInitial?: boolean;
+ /**
+ * The path associated to the location.
+ */
path?: string;
+ /**
+ * Whether focus was already restored for this location (in case of
+ * backwards navigation).
+ */
hasRestoredFocus?: boolean;
};
// Returned by the `useNavigator` hook.
export type Navigator = {
+ /**
+ * The current location.
+ */
location: NavigatorLocation;
+ /**
+ * Params associated with the current location
+ */
params: MatchParams;
+ /**
+ * Navigate to a new location.
+ */
goTo: ( path: string, options?: NavigateOptions ) => void;
- goBack: () => void;
+ /**
+ * Go back to the parent location (ie. "/some/path" will navigate back
+ * to "/some")
+ */
+ goBack: ( options?: NavigateToParentOptions ) => void;
+ /**
+ * _Note: This function is deprecated. Please use `goBack` instead._
+ * @deprecated
+ * @ignore
+ */
goToParent: ( options?: NavigateToParentOptions ) => void;
};
@@ -64,15 +108,6 @@ export type NavigatorScreenProps = {
export type NavigatorBackButtonProps = ButtonAsButtonProps;
-export type NavigatorBackButtonHookProps = NavigatorBackButtonProps & {
- /**
- * Whether we should navigate to the parent screen.
- *
- * @default 'false'
- */
- goToParent?: boolean;
-};
-
export type NavigatorToParentButtonProps = NavigatorBackButtonProps;
export type NavigatorButtonProps = NavigatorBackButtonProps & {
diff --git a/packages/components/src/palette-edit/styles.ts b/packages/components/src/palette-edit/styles.ts
index aa4ed720b93bfe..ad918d8590cf23 100644
--- a/packages/components/src/palette-edit/styles.ts
+++ b/packages/components/src/palette-edit/styles.ts
@@ -31,7 +31,7 @@ export const IndicatorStyled = styled( ColorIndicator )`
export const NameInputControl = styled( InputControl )`
${ InputControlContainer } {
background: ${ COLORS.gray[ 100 ] };
- border-radius: ${ CONFIG.controlBorderRadius };
+ border-radius: ${ CONFIG.radiusXSmall };
${ Input }${ Input }${ Input }${ Input } {
height: ${ space( 8 ) };
}
@@ -85,8 +85,8 @@ export const PaletteItem = styled( View )`
outline-offset: 0;
}
- border-top-left-radius: ${ CONFIG.controlBorderRadius };
- border-top-right-radius: ${ CONFIG.controlBorderRadius };
+ border-top-left-radius: ${ CONFIG.radiusSmall };
+ border-top-right-radius: ${ CONFIG.radiusSmall };
& + & {
border-top-left-radius: 0;
@@ -94,8 +94,8 @@ export const PaletteItem = styled( View )`
}
&:last-child {
- border-bottom-left-radius: ${ CONFIG.controlBorderRadius };
- border-bottom-right-radius: ${ CONFIG.controlBorderRadius };
+ border-bottom-left-radius: ${ CONFIG.radiusSmall };
+ border-bottom-right-radius: ${ CONFIG.radiusSmall };
border-bottom-color: ${ CONFIG.surfaceBorderColor };
}
diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss
index e046ce1e3a427e..61090c81110a5c 100644
--- a/packages/components/src/placeholder/style.scss
+++ b/packages/components/src/placeholder/style.scss
@@ -19,7 +19,7 @@
// Block UI appearance.
- border-radius: $radius-block-ui;
+ border-radius: $radius-medium;
background-color: $white;
box-shadow: inset 0 0 0 $border-width $gray-900;
outline: 1px solid transparent; // Shown for Windows 10 High Contrast mode.
diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx
index 68d331ce8266ba..3005f13c952ec0 100644
--- a/packages/components/src/popover/index.tsx
+++ b/packages/components/src/popover/index.tsx
@@ -113,8 +113,9 @@ const UnforwardedPopover = (
WordPressComponentProps< PopoverProps, 'div', false >,
// To avoid overlaps between the standard HTML attributes and the props
// expected by `framer-motion`, omit all framer motion props from popover
- // props (except for `animate` and `children`, which are re-defined in `PopoverProps`).
- keyof Omit< MotionProps, 'animate' | 'children' >
+ // props (except for `animate` and `children` which are re-defined in
+ // `PopoverProps`, and `style` which is merged safely).
+ keyof Omit< MotionProps, 'animate' | 'children' | 'style' >
>,
forwardedRef: ForwardedRef< any >
) => {
@@ -139,6 +140,7 @@ const UnforwardedPopover = (
shift = false,
inline = false,
variant,
+ style: contentStyle,
// Deprecated props
__unstableForcePosition,
@@ -370,6 +372,7 @@ const UnforwardedPopover = (
const animationProps: HTMLMotionProps< 'div' > = shouldAnimate
? {
style: {
+ ...contentStyle,
...motionInlineStyles,
...style,
},
@@ -378,7 +381,10 @@ const UnforwardedPopover = (
}
: {
animate: false,
- style,
+ style: {
+ ...contentStyle,
+ ...style,
+ },
};
// When Floating UI has finished positioning and Framer Motion has finished animating
diff --git a/packages/components/src/popover/style.scss b/packages/components/src/popover/style.scss
index c7ff0510986bfa..f9f43870a6b927 100644
--- a/packages/components/src/popover/style.scss
+++ b/packages/components/src/popover/style.scss
@@ -23,7 +23,7 @@ $shadow-popover-border-top-only-alternate: 0 #{-$border-width} 0 $gray-900;
.components-popover__content {
background: $white;
box-shadow: $shadow-popover-border-default, $shadow-popover;
- border-radius: $radius-block-ui;
+ border-radius: $radius-medium;
box-sizing: border-box;
width: min-content;
diff --git a/packages/components/src/popover/test/index.tsx b/packages/components/src/popover/test/index.tsx
index f5ee7e96e4c54d..eac0f942df2f6e 100644
--- a/packages/components/src/popover/test/index.tsx
+++ b/packages/components/src/popover/test/index.tsx
@@ -179,6 +179,40 @@ describe( 'Popover', () => {
} );
} );
+ describe( 'style', () => {
+ it( 'outputs inline styles added through the style prop in addition to built-in popover positioning styles', async () => {
+ render(
+
+ Hello
+
+ );
+ const popover = screen.getByTestId( 'popover-element' );
+
+ await waitFor( () => expect( popover ).toBeVisible() );
+ expect( popover ).toHaveStyle(
+ 'position: absolute; top: 0px; left: 0px; z-index: 0;'
+ );
+ } );
+
+ it( 'is not possible to override built-in popover positioning styles via the style prop', async () => {
+ render(
+
+ Hello
+
+ );
+ const popover = screen.getByTestId( 'popover-element' );
+
+ await waitFor( () => expect( popover ).toBeVisible() );
+ expect( popover ).not.toHaveStyle( 'position: static;' );
+ } );
+ } );
+
describe( 'focus behavior', () => {
it( 'should focus the popover container when opened', async () => {
render(
diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts
index 5ff39ba364a041..699911e5ba046b 100644
--- a/packages/components/src/private-apis.ts
+++ b/packages/components/src/private-apis.ts
@@ -1,13 +1,7 @@
/**
* Internal dependencies
*/
-import {
- Composite as CompositeV2,
- CompositeGroup as CompositeGroupV2,
- CompositeItem as CompositeItemV2,
- CompositeRow as CompositeRowV2,
- useCompositeStore as useCompositeStoreV2,
-} from './composite/v2';
+import { Composite, useCompositeStore } from './composite';
import { positionToPlacement as __experimentalPopoverLegacyPositionToPlacement } from './popover/utils';
import { createPrivateSlotFill } from './slot-fill';
import {
@@ -28,11 +22,11 @@ import { lock } from './lock-unlock';
export const privateApis = {};
lock( privateApis, {
- CompositeV2,
- CompositeGroupV2,
- CompositeItemV2,
- CompositeRowV2,
- useCompositeStoreV2,
+ CompositeV2: Composite,
+ CompositeGroupV2: Composite.Group,
+ CompositeItemV2: Composite.Item,
+ CompositeRowV2: Composite.Row,
+ useCompositeStoreV2: useCompositeStore,
__experimentalPopoverLegacyPositionToPlacement,
createPrivateSlotFill,
ComponentsContext,
diff --git a/packages/components/src/progress-bar/styles.ts b/packages/components/src/progress-bar/styles.ts
index 79b9103e73a1ed..585b9ab2620753 100644
--- a/packages/components/src/progress-bar/styles.ts
+++ b/packages/components/src/progress-bar/styles.ts
@@ -40,7 +40,7 @@ export const Track = styled.div`
${ COLORS.theme.foreground },
transparent 90%
);
- border-radius: ${ CONFIG.radiusBlockUi };
+ border-radius: ${ CONFIG.radiusFull };
// Windows high contrast mode.
outline: 2px solid transparent;
@@ -58,7 +58,7 @@ export const Indicator = styled.div< {
position: absolute;
top: 0;
height: 100%;
- border-radius: ${ CONFIG.radiusBlockUi };
+ border-radius: ${ CONFIG.radiusFull };
/* Text color at 90% opacity */
background-color: color-mix(
in srgb,
diff --git a/packages/components/src/query-controls/index.tsx b/packages/components/src/query-controls/index.tsx
index 3557335ebac5a0..452dd303c778bb 100644
--- a/packages/components/src/query-controls/index.tsx
+++ b/packages/components/src/query-controls/index.tsx
@@ -60,7 +60,6 @@ function isMultipleCategorySelection(
* ```
*/
export function QueryControls( {
- __next40pxDefaultSize = false,
authorList,
selectedAuthorId,
numberOfItems,
@@ -82,7 +81,7 @@ export function QueryControls( {
onOrderChange && onOrderByChange && (
= ( { onChange, ...args } ) => {
export const Default: StoryFn< typeof RangeControl > = Template.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
help: 'Please select how transparent you would like this.',
initialPosition: 50,
label: 'Opacity',
@@ -104,6 +105,7 @@ export const WithAnyStep: StoryFn< typeof RangeControl > = ( {
);
};
WithAnyStep.args = {
+ __nextHasNoMarginBottom: true,
label: 'Brightness',
step: 'any',
};
@@ -167,6 +169,7 @@ export const WithIntegerStepAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithIntegerStepAndMarks.args = {
+ __nextHasNoMarginBottom: true,
label: 'Integer Step',
marks: marksBase,
max: 10,
@@ -183,6 +186,7 @@ export const WithDecimalStepAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithDecimalStepAndMarks.args = {
+ __nextHasNoMarginBottom: true,
marks: [
...marksBase,
{ value: 3.5, label: '3.5' },
@@ -202,6 +206,7 @@ export const WithNegativeMinimumAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithNegativeMinimumAndMarks.args = {
+ __nextHasNoMarginBottom: true,
marks: marksWithNegatives,
max: 10,
min: -10,
@@ -217,6 +222,7 @@ export const WithNegativeRangeAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithNegativeRangeAndMarks.args = {
+ __nextHasNoMarginBottom: true,
marks: marksWithNegatives,
max: -1,
min: -10,
@@ -232,6 +238,7 @@ export const WithAnyStepAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithAnyStepAndMarks.args = {
+ __nextHasNoMarginBottom: true,
marks: marksBase,
max: 10,
min: 0,
diff --git a/packages/components/src/range-control/styles/range-control-styles.ts b/packages/components/src/range-control/styles/range-control-styles.ts
index 89f4864aee2ea6..ec1572d2679247 100644
--- a/packages/components/src/range-control/styles/range-control-styles.ts
+++ b/packages/components/src/range-control/styles/range-control-styles.ts
@@ -154,7 +154,7 @@ export const Mark = styled.span`
height: ${ thumbSize }px;
left: 0;
position: absolute;
- top: -4px;
+ top: 9px;
width: 1px;
${ markFill };
@@ -170,7 +170,7 @@ export const MarkLabel = styled.span`
color: ${ COLORS.gray[ 300 ] };
font-size: 11px;
position: absolute;
- top: 12px;
+ top: 22px;
white-space: nowrap;
${ rtl( { left: 0 } ) };
diff --git a/packages/components/src/range-control/test/index.tsx b/packages/components/src/range-control/test/index.tsx
index d843b615ed0078..a4c5d8c6f2bc7f 100644
--- a/packages/components/src/range-control/test/index.tsx
+++ b/packages/components/src/range-control/test/index.tsx
@@ -6,7 +6,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
-import RangeControl from '../';
+import _RangeControl from '../';
const getRangeInput = (): HTMLInputElement => screen.getByRole( 'slider' );
const getNumberInput = (): HTMLInputElement => screen.getByRole( 'spinbutton' );
@@ -15,6 +15,12 @@ const getResetButton = (): HTMLButtonElement => screen.getByRole( 'button' );
const fireChangeEvent = ( input: HTMLInputElement, value?: number | string ) =>
fireEvent.change( input, { target: { value } } );
+const RangeControl = (
+ props: React.ComponentProps< typeof _RangeControl >
+) => {
+ return <_RangeControl { ...props } __nextHasNoMarginBottom />;
+};
+
describe( 'RangeControl', () => {
describe( '#render()', () => {
it( 'should trigger change callback with numeric value', () => {
diff --git a/packages/components/src/search-control/index.tsx b/packages/components/src/search-control/index.tsx
index 08cb3b065c904e..aac905e137e025 100644
--- a/packages/components/src/search-control/index.tsx
+++ b/packages/components/src/search-control/index.tsx
@@ -77,10 +77,13 @@ function UnforwardedSearchControl(
const contextValue = useMemo(
() => ( {
- // Overrides the underlying BaseControl `__nextHasNoMarginBottom` via the context system
- // to provide backwards compatibile margin for SearchControl.
- // (In a standard InputControl, the BaseControl `__nextHasNoMarginBottom` is always set to true.)
- BaseControl: { _overrides: { __nextHasNoMarginBottom } },
+ BaseControl: {
+ // Overrides the underlying BaseControl `__nextHasNoMarginBottom` via the context system
+ // to provide backwards compatibile margin for SearchControl.
+ // (In a standard InputControl, the BaseControl `__nextHasNoMarginBottom` is always set to true.)
+ _overrides: { __nextHasNoMarginBottom },
+ __associatedWPComponentName: 'SearchControl',
+ },
// `isBorderless` is still experimental and not a public prop for InputControl yet.
InputBase: { isBorderless: true },
} ),
diff --git a/packages/components/src/search-control/stories/index.story.tsx b/packages/components/src/search-control/stories/index.story.tsx
index 433d3eef655adf..215288bb67c9b6 100644
--- a/packages/components/src/search-control/stories/index.story.tsx
+++ b/packages/components/src/search-control/stories/index.story.tsx
@@ -48,6 +48,7 @@ const Template: StoryFn< typeof SearchControl > = ( {
export const Default = Template.bind( {} );
Default.args = {
help: 'Help text to explain the input.',
+ __nextHasNoMarginBottom: true,
};
/**
diff --git a/packages/components/src/search-control/test/index.tsx b/packages/components/src/search-control/test/index.tsx
index f130cab1b2a7cd..c6637945adcf63 100644
--- a/packages/components/src/search-control/test/index.tsx
+++ b/packages/components/src/search-control/test/index.tsx
@@ -23,6 +23,7 @@ function ControlledSearchControl( {
return (
{
setValue( ...args );
diff --git a/packages/components/src/select-control/index.tsx b/packages/components/src/select-control/index.tsx
index ca9966fc675b86..3686661b8a58dc 100644
--- a/packages/components/src/select-control/index.tsx
+++ b/packages/components/src/select-control/index.tsx
@@ -99,6 +99,7 @@ function UnforwardedSelectControl< V extends string >(
help={ help }
id={ id }
__nextHasNoMarginBottom={ __nextHasNoMarginBottom }
+ __associatedWPComponentName="SelectControl"
>
= ( props ) => {
export const Default = SelectControlWithState.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
options: [
{ value: '', label: 'Select an Option', disabled: true },
{ value: 'a', label: 'Option A' },
@@ -82,9 +83,11 @@ WithLabelAndHelpText.args = {
* As an alternative to the `options` prop, `optgroup`s and `options` can be
* passed in as `children` for more customizeability.
*/
-export const WithCustomChildren: StoryFn< typeof SelectControl > = ( args ) => {
- return (
-
+export const WithCustomChildren = SelectControlWithState.bind( {} );
+WithCustomChildren.args = {
+ __nextHasNoMarginBottom: true,
+ children: (
+ <>
-
- );
+ >
+ ),
};
export const Minimal = SelectControlWithState.bind( {} );
diff --git a/packages/components/src/select-control/test/select-control.tsx b/packages/components/src/select-control/test/select-control.tsx
index 0e8a6891087043..47b684cd20e280 100644
--- a/packages/components/src/select-control/test/select-control.tsx
+++ b/packages/components/src/select-control/test/select-control.tsx
@@ -7,7 +7,13 @@ import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
-import SelectControl from '..';
+import _SelectControl from '..';
+
+const SelectControl = (
+ props: React.ComponentProps< typeof _SelectControl >
+) => {
+ return <_SelectControl { ...props } __nextHasNoMarginBottom />;
+};
describe( 'SelectControl', () => {
it( 'should not render when no options or children are provided', () => {
@@ -123,7 +129,7 @@ describe( 'SelectControl', () => {
onChange={ onChange }
/>;
- {
} );
it( 'should accept an explicit type argument', () => {
-
+ <_SelectControl< 'narrow' | 'value' >
// @ts-expect-error "string" is not "narrow" or "value"
value="string"
options={ [
@@ -166,7 +172,7 @@ describe( 'SelectControl', () => {
value: ( 'foo' | 'bar' )[]
) => void = () => {};
- {
onChange={ onChange }
/>;
- {
} );
it( 'should accept an explicit type argument', () => {
-
+ <_SelectControl< 'narrow' | 'value' >
multiple
// @ts-expect-error "string" is not "narrow" or "value"
value={ [ 'string' ] }
diff --git a/packages/components/src/snackbar/style.scss b/packages/components/src/snackbar/style.scss
index 0ba1774d67382f..5bea7076599b56 100644
--- a/packages/components/src/snackbar/style.scss
+++ b/packages/components/src/snackbar/style.scss
@@ -3,7 +3,7 @@
font-size: $default-font-size;
background: rgba($black, 0.85); // Emulates #1e1e1e closely.
backdrop-filter: blur($grid-unit-20) saturate(180%);
- border-radius: $radius-block-ui;
+ border-radius: $radius-medium;
box-shadow: $shadow-popover;
color: $white;
padding: $grid-unit-15 ($grid-unit-05 * 5);
diff --git a/packages/components/src/tab-panel/style.scss b/packages/components/src/tab-panel/style.scss
index 2855f8c2b06a01..ab73a7affaeed4 100644
--- a/packages/components/src/tab-panel/style.scss
+++ b/packages/components/src/tab-panel/style.scss
@@ -67,7 +67,7 @@
// Draw the indicator.
box-shadow: 0 0 0 0 transparent;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
// Animation
transition: all 0.1s linear;
diff --git a/packages/components/src/text-control/index.tsx b/packages/components/src/text-control/index.tsx
index 1643c5bc37c347..ea2d2c17bb9cf6 100644
--- a/packages/components/src/text-control/index.tsx
+++ b/packages/components/src/text-control/index.tsx
@@ -41,6 +41,7 @@ function UnforwardedTextControl(
return (
= ( {
export const Default: StoryFn< typeof TextControl > = DefaultTemplate.bind(
{}
);
-Default.args = {};
+Default.args = {
+ __nextHasNoMarginBottom: true,
+};
export const WithLabelAndHelpText: StoryFn< typeof TextControl > =
DefaultTemplate.bind( {} );
diff --git a/packages/components/src/text-control/test/text-control.tsx b/packages/components/src/text-control/test/text-control.tsx
index fc048b93992f08..19b17cae443614 100644
--- a/packages/components/src/text-control/test/text-control.tsx
+++ b/packages/components/src/text-control/test/text-control.tsx
@@ -6,7 +6,11 @@ import { render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
-import TextControl from '..';
+import _TextControl from '..';
+
+const TextControl = ( props: React.ComponentProps< typeof _TextControl > ) => {
+ return <_TextControl { ...props } __nextHasNoMarginBottom />;
+};
const noop = () => {};
diff --git a/packages/components/src/text/styles.ts b/packages/components/src/text/styles.ts
index c7d48552795938..e777ed4f0941de 100644
--- a/packages/components/src/text/styles.ts
+++ b/packages/components/src/text/styles.ts
@@ -35,7 +35,7 @@ export const muted = css`
export const highlighterText = css`
mark {
background: ${ COLORS.alert.yellow };
- border-radius: 2px;
+ border-radius: ${ CONFIG.radiusSmall };
box-shadow:
0 0 0 1px rgba( 0, 0, 0, 0.05 ) inset,
0 -1px 0 rgba( 0, 0, 0, 0.1 ) inset;
diff --git a/packages/components/src/textarea-control/index.tsx b/packages/components/src/textarea-control/index.tsx
index 3b96e11b0621b5..e7528510667b75 100644
--- a/packages/components/src/textarea-control/index.tsx
+++ b/packages/components/src/textarea-control/index.tsx
@@ -35,6 +35,7 @@ function UnforwardedTextareaControl(
return (
= ( {
export const Default: StoryFn< typeof TextareaControl > = Template.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
label: 'Text',
help: 'Enter some text',
};
diff --git a/packages/components/src/toggle-control/index.tsx b/packages/components/src/toggle-control/index.tsx
index 5c64d57d3d0249..d2ee234a9695f8 100644
--- a/packages/components/src/toggle-control/index.tsx
+++ b/packages/components/src/toggle-control/index.tsx
@@ -10,6 +10,7 @@ import clsx from 'clsx';
*/
import { forwardRef } from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';
+import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
@@ -48,6 +49,14 @@ function UnforwardedToggleControl(
! __nextHasNoMarginBottom && css( { marginBottom: space( 3 ) } )
);
+ if ( ! __nextHasNoMarginBottom ) {
+ deprecated( 'Bottom margin styles for wp.components.ToggleControl', {
+ since: '6.7',
+ version: '7.0',
+ hint: 'Set the `__nextHasNoMarginBottom` prop to true to start opting into the new styles, which will become the default in a future version.',
+ } );
+ }
+
let describedBy, helpLabel;
if ( help ) {
if ( typeof help === 'function' ) {
diff --git a/packages/components/src/toggle-control/stories/index.story.tsx b/packages/components/src/toggle-control/stories/index.story.tsx
index b8043b8f48e523..97723aa207a394 100644
--- a/packages/components/src/toggle-control/stories/index.story.tsx
+++ b/packages/components/src/toggle-control/stories/index.story.tsx
@@ -48,6 +48,7 @@ const Template: StoryFn< typeof ToggleControl > = ( {
export const Default = Template.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
label: 'Enable something',
};
diff --git a/packages/components/src/toggle-control/test/index.tsx b/packages/components/src/toggle-control/test/index.tsx
index cc89031d9affa3..b0eec2aca6663d 100644
--- a/packages/components/src/toggle-control/test/index.tsx
+++ b/packages/components/src/toggle-control/test/index.tsx
@@ -6,7 +6,13 @@ import { render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
-import ToggleControl from '..';
+import _ToggleControl from '..';
+
+const ToggleControl = (
+ props: React.ComponentProps< typeof _ToggleControl >
+) => {
+ return <_ToggleControl { ...props } __nextHasNoMarginBottom />;
+};
describe( 'ToggleControl', () => {
it( 'should label the toggle', () => {
diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap
index 81afc7ac67b05f..e9b4f4ca22ab85 100644
--- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap
+++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap
@@ -13,10 +13,6 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -88,7 +84,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -156,7 +152,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
.emotion-15 {
background: #1e1e1e;
- border-radius: 2px;
+ border-radius: 1px;
position: absolute;
inset: 0;
z-index: 1;
@@ -175,7 +171,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -349,10 +345,6 @@ exports[`ToggleGroupControl controlled should render correctly with text options
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -424,7 +416,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -573,10 +565,6 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`]
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -648,7 +636,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`]
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -716,7 +704,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`]
.emotion-15 {
background: #1e1e1e;
- border-radius: 2px;
+ border-radius: 1px;
position: absolute;
inset: 0;
z-index: 1;
@@ -735,7 +723,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`]
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -903,10 +891,6 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -978,7 +962,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
diff --git a/packages/components/src/toggle-group-control/test/index.tsx b/packages/components/src/toggle-group-control/test/index.tsx
index 661bbb9fc37bab..170db01ae523c2 100644
--- a/packages/components/src/toggle-group-control/test/index.tsx
+++ b/packages/components/src/toggle-group-control/test/index.tsx
@@ -15,7 +15,7 @@ import { formatLowercase, formatUppercase } from '@wordpress/icons';
*/
import Button from '../../button';
import {
- ToggleGroupControl,
+ ToggleGroupControl as _ToggleGroupControl,
ToggleGroupControlOption,
ToggleGroupControlOptionIcon,
} from '../index';
@@ -27,6 +27,10 @@ const hoverOutside = async () => {
await hover( document.body, { clientX: 10, clientY: 10 } );
};
+const ToggleGroupControl = ( props: ToggleGroupControlProps ) => {
+ return <_ToggleGroupControl { ...props } __nextHasNoMarginBottom />;
+};
+
const ControlledToggleGroupControl = ( {
value: valueProp,
onChange,
diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts
index 999a25df8bdd40..86efc5224077f4 100644
--- a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts
+++ b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts
@@ -37,7 +37,7 @@ export const buttonView = ( {
appearance: none;
background: transparent;
border: none;
- border-radius: ${ CONFIG.controlBorderRadius };
+ border-radius: ${ CONFIG.radiusXSmall };
color: ${ COLORS.gray[ 700 ] };
fill: currentColor;
cursor: pointer;
@@ -122,7 +122,7 @@ const isIconStyles = ( {
export const backdropView = css`
background: ${ COLORS.gray[ 900 ] };
- border-radius: ${ CONFIG.controlBorderRadius };
+ border-radius: ${ CONFIG.radiusXSmall };
position: absolute;
inset: 0;
z-index: 1;
diff --git a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx
index 8138b76505fe50..1c86c93548f6df 100644
--- a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx
+++ b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx
@@ -72,6 +72,7 @@ function UnconnectedToggleGroupControl(
{ ! hideLabelFromVision && (
diff --git a/packages/components/src/toggle-group-control/toggle-group-control/styles.ts b/packages/components/src/toggle-group-control/toggle-group-control/styles.ts
index 7310024706e1de..8d01c150a45eaf 100644
--- a/packages/components/src/toggle-group-control/toggle-group-control/styles.ts
+++ b/packages/components/src/toggle-group-control/toggle-group-control/styles.ts
@@ -19,7 +19,7 @@ export const toggleGroupControl = ( {
} ) => css`
background: ${ COLORS.ui.background };
border: 1px solid transparent;
- border-radius: ${ CONFIG.controlBorderRadius };
+ border-radius: ${ CONFIG.radiusSmall };
display: inline-flex;
min-width: 0;
position: relative;
diff --git a/packages/components/src/toolbar/toolbar/style.scss b/packages/components/src/toolbar/toolbar/style.scss
index eccfc3bf705cbd..c0cabacb84c77e 100644
--- a/packages/components/src/toolbar/toolbar/style.scss
+++ b/packages/components/src/toolbar/toolbar/style.scss
@@ -1,7 +1,7 @@
.components-accessible-toolbar {
display: inline-flex;
border: $border-width solid $gray-900;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
flex-shrink: 0;
& > .components-toolbar-group:last-child {
@@ -47,7 +47,7 @@
content: "";
position: absolute;
display: block;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
height: $grid-unit-40;
// Position the focus rectangle.
diff --git a/packages/components/src/tools-panel/styles.ts b/packages/components/src/tools-panel/styles.ts
index 1da1003c0462e3..11536e98a128a9 100644
--- a/packages/components/src/tools-panel/styles.ts
+++ b/packages/components/src/tools-panel/styles.ts
@@ -21,7 +21,7 @@ const toolsPanelGrid = {
grid-template-columns: ${ `repeat( ${ columns }, minmax(0, 1fr) )` };
`,
spacing: css`
- column-gap: ${ space( 2 ) };
+ column-gap: ${ space( 4 ) };
row-gap: ${ space( 4 ) };
`,
item: {
diff --git a/packages/components/src/tooltip/style.scss b/packages/components/src/tooltip/style.scss
index feda6cfa81c887..eaac8b3ad1c7f6 100644
--- a/packages/components/src/tooltip/style.scss
+++ b/packages/components/src/tooltip/style.scss
@@ -1,7 +1,7 @@
.components-tooltip {
background: $black;
font-family: $default-font;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
color: $gray-100;
text-align: center;
line-height: 1.4;
diff --git a/packages/components/src/tree-select/index.tsx b/packages/components/src/tree-select/index.tsx
index 599dee4402ec72..bd92807bff4cc9 100644
--- a/packages/components/src/tree-select/index.tsx
+++ b/packages/components/src/tree-select/index.tsx
@@ -10,6 +10,15 @@ import { decodeEntities } from '@wordpress/html-entities';
import { SelectControl } from '../select-control';
import type { TreeSelectProps, Tree, Truthy } from './types';
import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props';
+import { ContextSystemProvider } from '../context';
+
+const CONTEXT_VALUE = {
+ BaseControl: {
+ // Temporary during deprecation grace period: Overrides the underlying `__associatedWPComponentName`
+ // via the context system to override the value set by SelectControl.
+ _overrides: { __associatedWPComponentName: 'TreeSelect' },
+ },
+};
function getSelectOptions(
tree: Tree[],
@@ -91,11 +100,13 @@ export function TreeSelect( props: TreeSelectProps ) {
}, [ noOptionLabel, tree ] );
return (
-
+
+
+
);
}
diff --git a/packages/components/src/tree-select/stories/index.story.tsx b/packages/components/src/tree-select/stories/index.story.tsx
index 0a4212dc791227..33103786bbc541 100644
--- a/packages/components/src/tree-select/stories/index.story.tsx
+++ b/packages/components/src/tree-select/stories/index.story.tsx
@@ -48,6 +48,7 @@ const TreeSelectWithState: StoryFn< typeof TreeSelect > = ( props ) => {
export const Default = TreeSelectWithState.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
label: 'Label Text',
noOptionLabel: 'No parent page',
help: 'Help text to explain the select control.',
diff --git a/packages/components/src/unit-control/styles/unit-control-styles.ts b/packages/components/src/unit-control/styles/unit-control-styles.ts
index 321bfb8406569d..5f59771bd48a6a 100644
--- a/packages/components/src/unit-control/styles/unit-control-styles.ts
+++ b/packages/components/src/unit-control/styles/unit-control-styles.ts
@@ -135,7 +135,7 @@ export const UnitSelect = styled.select< SelectProps >`
&&& {
appearance: none;
background: transparent;
- border-radius: 2px;
+ border-radius: ${ CONFIG.radiusXSmall };
border: none;
display: block;
outline: none;
diff --git a/packages/components/src/utils/config-values.js b/packages/components/src/utils/config-values.js
index ba92813bdbfb0f..0ad1b3294a926b 100644
--- a/packages/components/src/utils/config-values.js
+++ b/packages/components/src/utils/config-values.js
@@ -14,7 +14,6 @@ const CONTROL_PROPS = {
controlPaddingXLarge: `calc(${ CONTROL_PADDING_X } * 1.3334)`,
controlPaddingXSmall: `calc(${ CONTROL_PADDING_X } / 1.3334)`,
controlBackgroundColor: COLORS.white,
- controlBorderRadius: '2px',
controlBoxShadow: 'transparent',
controlBoxShadowFocus: `0 0 0 0.5px ${ COLORS.theme.accent }`,
controlDestructiveBorderColor: COLORS.alert.red,
@@ -48,7 +47,6 @@ export default Object.assign( {}, CONTROL_PROPS, TOGGLE_GROUP_CONTROL_PROPS, {
radiusLarge: '8px',
radiusFull: '9999px',
radiusRound: '50%',
- radiusBlockUi: '2px',
borderWidth: '1px',
borderWidthFocus: '1.5px',
borderWidthTab: '4px',
@@ -73,7 +71,10 @@ export default Object.assign( {}, CONTROL_PROPS, TOGGLE_GROUP_CONTROL_PROPS, {
cardPaddingSmall: `${ space( 4 ) }`,
cardPaddingMedium: `${ space( 4 ) } ${ space( 6 ) }`,
cardPaddingLarge: `${ space( 6 ) } ${ space( 8 ) }`,
- popoverShadow: `0 0.7px 1px rgba(0, 0, 0, 0.1), 0 1.2px 1.7px -0.2px rgba(0, 0, 0, 0.1), 0 2.3px 3.3px -0.5px rgba(0, 0, 0, 0.1)`,
+ elevationXSmall: `0 0.7px 1px rgba(0, 0, 0, 0.1), 0 1.2px 1.7px -0.2px rgba(0, 0, 0, 0.1), 0 2.3px 3.3px -0.5px rgba(0, 0, 0, 0.1)`,
+ elevationSmall: `0 0.7px 1px 0 rgba(0, 0, 0, 0.12), 0 2.2px 3.7px -0.2px rgba(0, 0, 0, 0.12), 0 5.3px 7.3px -0.5px rgba(0, 0, 0, 0.12)`,
+ elevationMedium: `0 0.7px 1px 0 rgba(0, 0, 0, 0.14), 0 4.2px 5.7px -0.2px rgba(0, 0, 0, 0.14), 0 7.3px 9.3px -0.5px rgba(0, 0, 0, 0.14)`,
+ elevationLarge: `0 0.7px 1px rgba(0, 0, 0, 0.15), 0 2.7px 3.8px -0.2px rgba(0, 0, 0, 0.15), 0 5.5px 7.8px -0.3px rgba(0, 0, 0, 0.15), 0.1px 11.5px 16.4px -0.5px rgba(0, 0, 0, 0.15)`,
surfaceBackgroundColor: COLORS.white,
surfaceBackgroundSubtleColor: '#F3F3F3',
surfaceBackgroundTintColor: '#F5F5F5',
diff --git a/packages/components/src/utils/input/base.js b/packages/components/src/utils/input/base.js
index f03a1d9c77abbe..9eebd1c0bcea78 100644
--- a/packages/components/src/utils/input/base.js
+++ b/packages/components/src/utils/input/base.js
@@ -11,7 +11,7 @@ import { CONFIG } from '../';
export const inputStyleNeutral = css`
box-shadow: 0 0 0 transparent;
- border-radius: ${ CONFIG.radiusBlockUi };
+ border-radius: ${ CONFIG.radiusSmall };
border: ${ CONFIG.borderWidth } solid ${ COLORS.ui.border };
@media not ( prefers-reduced-motion ) {
diff --git a/packages/core-data/src/entity-types/wp-template.ts b/packages/core-data/src/entity-types/wp-template.ts
index ac6db09035f193..70d3e40c295dcf 100644
--- a/packages/core-data/src/entity-types/wp-template.ts
+++ b/packages/core-data/src/entity-types/wp-template.ts
@@ -73,6 +73,10 @@ declare module './base-entity-records' {
* Post ID.
*/
wp_id: number;
+ /**
+ * Plugin that registered the template.
+ */
+ plugin?: string;
/**
* Theme file exists.
*/
diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md
index 239a69651a1f27..b1cb4504e72fad 100644
--- a/packages/dataviews/CHANGELOG.md
+++ b/packages/dataviews/CHANGELOG.md
@@ -16,6 +16,7 @@
- `setSelection` prop has been removed. Please use `onChangeSelection` instead.
- `header` field property has been renamed to `label`.
- `DataForm`'s `visibleFields` prop has been renamed to `fields`.
+- `DataForm`'s `onChange` prop has been update to receive as argument only the fields that have changed.
### New features
diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md
index c82a748df98858..368880b69b14f0 100644
--- a/packages/dataviews/README.md
+++ b/packages/dataviews/README.md
@@ -30,6 +30,8 @@ const Example = () => {
};
```
+