diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml
index 409ba9f8fe171..5056527d097bd 100644
--- a/.github/workflows/rnmobile-ios-runner.yml
+++ b/.github/workflows/rnmobile-ios-runner.yml
@@ -27,7 +27,7 @@ jobs:
with:
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
- - uses: ruby/setup-ruby@1198b074305f9356bd56dd4b311757cc0dab2f1c # v1.175.1
+ - uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # v1.176.0
with:
# `.ruby-version` file location
working-directory: packages/react-native-editor/ios
diff --git a/docs/manifest.json b/docs/manifest.json
index 05f1f36ce7ca6..a257753046355 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -509,6 +509,18 @@
"markdown_source": "../docs/reference-guides/interactivity-api/api-reference.md",
"parent": "interactivity-api"
},
+ {
+ "title": "About the Interactivity API",
+ "slug": "iapi-about",
+ "markdown_source": "../docs/reference-guides/interactivity-api/iapi-about.md",
+ "parent": "interactivity-api"
+ },
+ {
+ "title": "Frequently Asked Questions",
+ "slug": "iapi-faq",
+ "markdown_source": "../docs/reference-guides/interactivity-api/iapi-faq.md",
+ "parent": "interactivity-api"
+ },
{
"title": "SlotFills Reference",
"slug": "slotfills",
diff --git a/docs/reference-guides/interactivity-api/README.md b/docs/reference-guides/interactivity-api/README.md
index 9a9e5dd02d431..1401c7f9bfecc 100644
--- a/docs/reference-guides/interactivity-api/README.md
+++ b/docs/reference-guides/interactivity-api/README.md
@@ -9,7 +9,7 @@ Blocks can share data, actions, and callbacks between them. This makes communica
For more information about the genesis of the Interactivity API, check out the [original proposal](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/). You can also review the [merge announcement](https://make.wordpress.org/core/2024/02/19/merge-announcement-interactivity-api/), the [status update post](https://make.wordpress.org/core/2023/08/15/status-update-on-the-interactivity-api/), and the official [Trac ticket](https://core.trac.wordpress.org/ticket/60356).
- The Interactivity API is supported by the package @wordpress/interactivity which is bundled in WordPress Core from v6.5
+ The Interactivity API is supported by the package @wordpress/interactivity which is bundled in WordPress Core from v6.5
## Navigating the Interactivity API docs
@@ -22,6 +22,12 @@ Use the following links to locate the topic you're interested in. If you have ne
- **[API Reference](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/api-reference/):** To take a deep dive into how the API works internally, the list of Directives, and how the Store works.
- **[Docs and Examples](#docs-examples):** Additional resources to learn/read more about the Interactivity API.
+To get a deeper understanding of what the Interactivity API is or find answers to questions you may have about this standard, check the following resources:
+
+- **[About the Interactivity API](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-about/):** To learn more about the API Goals and the reasoning behind the use of a standard to add interactivity to blocks.
+- **[Frequently Asked Questions](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-faq/):** To find responses to some frequently asked questions about the technology behind and alternatives.
+
+
## Requirements of the Interactivity API
Interactivity API is included in Core in WordPress 6.5. For versions below, you'll need Gutenberg 17.5 or higher installed and activated in your WordPress installation.
@@ -51,7 +57,7 @@ Refer to the [`interactivity` support property docs](https://developer.wordpress
#### Load Interactivity API Javascript code with `viewScriptModule`
-The Interactivity API provides the `@wordpress/interactivity` Script Module. JavaScript using the Interactivity API should be implemented as Script Modules so they can depend on `@wordpress/interactivity`. [Script Modules have been available since WordPress 6.5](https://make.wordpress.org/core/2024/03/04/script-modules-in-6-5/). Blocks can use [`viewScriptModule` [block metadata](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script-module) to enqueue their Script Modules easily:
+The Interactivity API provides the `@wordpress/interactivity` Script Module. JavaScript using the Interactivity API should be implemented as Script Modules so they can depend on `@wordpress/interactivity`. [Script Modules have been available since WordPress 6.5](https://make.wordpress.org/core/2024/03/04/script-modules-in-6-5/). Blocks can use [`viewScriptModule` block metadata](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script-module) to enqueue their Script Modules easily:
```json
// block.json
diff --git a/docs/reference-guides/interactivity-api/iapi-about.md b/docs/reference-guides/interactivity-api/iapi-about.md
new file mode 100644
index 0000000000000..f09ef77e6211b
--- /dev/null
+++ b/docs/reference-guides/interactivity-api/iapi-about.md
@@ -0,0 +1,197 @@
+# About the Interactivity API
+
+The Interactivity API is **a [standard](#why-a-standard) system of [directives](#why-directives), based on declarative code, for [adding frontend interactivity to blocks](#api-goals)**.
+
+**Directives extend HTML with special attributes** that tell the Interactivity API to attach a specified behavior to a DOM element or even to transform it. For those familiar with [Alpine.js](https://alpinejs.dev/), it’s a similar approach but explicitly designed to work seamlessly with WordPress.
+
+## API Goals
+
+The main goal of the Interactivity API is to **provide a standard and simple way to handle the frontend interactivity of Gutenberg blocks**.
+
+A standard makes it **easier for developers to create rich, interactive user experiences**, from simple cases like counters or popups to more complex features like instant page navigation, instant search, or carts and checkouts.
+
+All these user experiences are technically possible right now without the Interactivity API. However, the more complex the user experience and the more blocks interact with each other, the harder it becomes for developers to build and maintain sites. There are a lot of challenges they have to figure out themselves. The API aims to provide out-of-the-box means for supporting these kinds of interactions.
+
+To address this challenge the following requirements/goals for the Interactivity API were defined:
+
+- **Block-first and PHP-first**: The API must work well with PHP and the current block system, including dynamic blocks, widely extended in WordPress. It must support server-side rendering. Server-rendered HTML and client-hydrated HTML must be exactly the same. This is important for SEO and the user experience.
+- **Backward compatible**: The API must be compatible with WordPress hooks, which could, for example, modify server-rendered HTML. It must also be compatible with internationalization and existing JS libraries on the site (such as jQuery).
+- **Optional and gradual adoption**: Related to the previous point, the API must remain optional. It should be possible to adopt it gradually, meaning that interactive blocks not using this API can coexist with those using it.
+- **Declarative and reactive**: The API must use declarative code, listen to changes in the data, and update only the parts of the DOM that depend on that data.
+- **Performant**: The runtime must be fast and lightweight to ensure the best user experience.
+- **Extensible**: In the same way WordPress focuses on extensibility, this new system must provide extensibility patterns to cover most use cases.
+- **Atomic and composable**: Having small reusable parts that can be combined to create more complex systems is required to create flexible and scalable solutions.
+- **Compatible with the existing block development tooling**: The API must be integrated with the existing block-building tools without requiring additional tooling or configuration by the developer.
+
+Apart from all these requirements, integrating **client-side navigation** on top of any solution should be easy and performant. Client-side navigation is the process of navigating between site pages without reloading the entire page, which is one of the most impressive user experiences demanded by web developers. For that reason, this functionality should be compatible with this new system.
+
+## Why directives?
+
+Directives are the result of deep [research into different possibilities and approaches](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-faq/#what-approaches-have-been-considered-instead-of-using-directives). We’ve found that this design covers the requirements most effectively.
+
+### Block-first and PHP-friendly
+
+The API is designed for the world of blocks and takes WordPress history of being closely attached to web standards to heart.
+
+As directives are HTML attributes, they are perfect for dynamic blocks and PHP.
+
+_Dynamic block example_
+```html
+
+```
+
+As you can see, directives like `data-wp-on--click` or `data-wp-show` are added as custom HTML attributes. WordPress can process this HTML on the server, handling the directives’ logic and creating the appropriate markup.
+
+### Backward compatible
+
+As the Interactivity API works perfectly with server-side rendering, you can use all the WordPress APIs, including:
+
+- **WordPress filters and actions**: You can keep using WordPress hooks to modify the HTML or even to modify directives. Additionally, existing hooks will keep working as expected.
+- **Core Translation API**: e.g. `__()` and `_e()`. You can use it to translate the text in the HTML (as you normally would) and even use those APIs on the server side of your directives.
+
+### Optional and gradual adoption
+
+The Interactivity API pipeline promotes **progressive enhancement** by building on top of WordPress’s solid foundation and patterns.
+
+For example, blocks with directives can coexist with other (interactive or non-interactive) blocks. This means that if there are other blocks on the page using other frameworks like jQuery, everything will work as expected.
+
+
+ Full-page client-side navigation with the Interactivity API will be an exception to this compatibility with other libraries rule. See Client-side navigation for more details.
+
+
+### Declarative and reactive
+
+The Interactivity API follows an approach similar to other popular JS frameworks by separating state, actions, and callbacks and defining them declaratively. Why declaratively?
+
+Declarative code describes **what** a program should do, while imperative code describes **how** the program should do it. Using a declarative approach, the UI automatically updates in response to changes in the underlying data. With an imperative approach, you must manually update the UI whenever the data changes. Compare the two code examples:
+
+_Imperative code_
+
+```html
+
+
This element is now visible!
+
+```
+
+_Declarative code_
+
+This is the same use case shared above but serves as an example of declarative code using this new system. The JavaScript logic is defined in the `view.js` file of the block, and add the directives to the markup in the `render.php`.
+
+```js
+// view.js file
+
+import { store, getContext } from "@wordpress/interactivity";
+
+store( 'wpmovies', {
+ actions: {
+ toggle: () => {
+ const context = getContext();
+ context.isOpen = !context.isOpen;
+ },
+ },
+});
+```
+
+```php
+
+
+
true ) ); ?>
+>
+
+
+
+ This element is now visible!
+
+
+```
+
+Using imperative code may be easier when creating simple user experiences, but it becomes much more difficult as applications become more complex. The Interactivity API must cover all use cases, from the simplest to the most challenging. That’s why a declarative approach using directives better fits the Interactivity API.
+
+### Performant
+
+The API has been designed to be as performant as possible:
+
+- **The runtime code needed for the directives is just ~10 KB**, and it only needs to be loaded once for all the blocks.
+- **The scripts will load without blocking the page rendering**.
+
+### Extensible
+
+Directives can be added, removed, or modified directly from the HTML. For example, users could use the [`render_block` filter](https://developer.wordpress.org/reference/hooks/render_block/) to modify the HTML and its behavior.
+
+In addition to using built-in directives, users can create custom directives to add any custom behaviors to their HTML.
+
+### Atomic and composable
+
+Each directive controls a small part of the DOM, and you can combine multiple directives to create rich, interactive user experiences.
+
+### Compatible with the existing block development tooling
+
+Using built-in directives does not require a build step and only requires a small runtime. A build step is necessary only when creating custom directives that return JSX. For such use cases, the API works out of the box with common block-building tools like [`wp-scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/).
+
+### Client-side navigation
+
+The Interactivity API comes with built-in primitives for adding client-side navigation to your site. This functionality is completely optional, but it opens the possibility to create these user experiences without having to opt out of the WordPress rendering system.
+
+
+ Full-page client-side navigation with the Interactivity API is still a work in progress (see #60951). Still, it is expected that all the interactive blocks will have to use the Interactivity API to enable full-page client-side navigation with the Interactivity API. Only in this case, the Interactivity API won't be fully compatible with other libraries (such as jQuery).
+
+
+It also pairs very well with the [View Transitions API](https://developer.chrome.com/docs/web-platform/view-transitions/) allowing developers to animate page transitions easily.
+
+## Why a standard?
+
+Blocks using the Interactivity API and interactive blocks using other approaches like jQuery can coexist, and everything will work as expected. However, the Interactivity API comes with some benefits for your interactive blocks:
+
+- **Blocks can communicate with each other easily**. With a standard, this communication is handled by default. When different blocks use different approaches to frontend interactivity, inter-block communication becomes more complex and almost impossible when different developers create blocks.
+- **Composability and compatibility**: You can combine interactive blocks, and nest them in structures with defined behaviors. Thanks to following the same standard, they are fully cross-compatible. If each block used a different approach to interactivity, they would likely break.
+- **Fewer KBs will be sent to the browser**. If each plugin author uses a different JS framework, more code will be loaded in the front end. If all the blocks use the same one, the code is reused.
+- If all the blocks on a page use this standard, **site-wide features like client-side navigation can be enabled**.
+
+Additionally, with a standard, **WordPress can absorb the maximum amount of complexity from the developer** because it will handle most of what’s needed to create an interactive block.
+
+_Complexities absorbed by the standard_
+
+
+
+
+With this absorption, less knowledge is required to create interactive blocks, and developers have fewer decisions to worry about.
+
+By adopting a standard, learning from other interactive blocks is simpler, and fosters collaboration and code reusability. As a result, the development process is leanier and friendlier to less experienced developers.
+
diff --git a/docs/reference-guides/interactivity-api/iapi-faq.md b/docs/reference-guides/interactivity-api/iapi-faq.md
new file mode 100644
index 0000000000000..2e0e0ce4da430
--- /dev/null
+++ b/docs/reference-guides/interactivity-api/iapi-faq.md
@@ -0,0 +1,128 @@
+# Frequently Asked Questions
+
+## How does the Interactivity API work under the hood?
+
+Its three main components are:
+
+- [Preact](https://preactjs.com/) combined with [Preact Signals](https://preactjs.com/guide/v10/signals/) for hydration, client logic, and client-side navigation.
+- HTML Directives that can be understood by both the client and server.
+- Server-side logic, handled by the [HTML_Tag_Processor](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/).
+
+## Why Preact to build the directives system? Why not React or another JavaScript framework?
+
+Preact has a number of advantages over React and other JavaScript frameworks like Vue, Svelte, or Solid in the context of the frontend (which is the focus of the Interactivity API):
+
+- It’s small: 8kB, including [hooks](https://preactjs.com/guide/v10/hooks/) and [signals](https://preactjs.com/blog/introducing-signals/).
+- It gives us DOM diffing out of the box.
+- It’s extremely extensible through their Option Hooks. They use that extensibility for the hooks (preact/hooks), compatibility with React (preact/compat) and their signals (@preact/signals). Basically, everything but the DOM diffing algorithm.
+- Its core team has been great and very helpful. They are also interested in enhancing this “island-based” usage of Preact.
+
+## Is Gutenberg going to move from React to Preact since the Interactivity API uses it?
+
+No. At the moment, there are no plans to make that transition. The requirements and advantages of the editor, as a fully interactive application, are quite different. Preact does have a [`@preact/compat`](https://preactjs.com/guide/v10/switching-to-preact/) package that enables full compatibility with the React ecosystem, and many large web applications use it. However, using Preact in the block editor would not offer advantages like it does on the frontend in the Interactivity API.
+
+## What approaches have been considered instead of using directives?
+
+Many alternative approaches were considered. Here’s a brief summary of some of them:
+
+### React and other JavaScript frameworks
+
+React was considered first because Gutenberg developers are familiar with it. Other popular JS frameworks like Svelte, Vue.js, or Angular were also considered, but none of them (including React) are PHP-friendly or compatible with WordPress hooks or internationalization.
+
+### Alpine.js
+
+Alpine.js is a great framework, and it inspired a lot of functionality in the Interactivity API. However, it doesn’t support server-side rendering of its [directives](https://github.com/alpinejs/alpine/tree/d7f9d641f7a763c56c598d118bd189a406a22383/packages/docs/src/en/directives), and having a similar system tailored for WordPress blocks has many benefits.
+
+### Plain JavaScript
+
+See the answer below.
+
+### Template DSL
+
+The possibility of creating a [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) for writing interactive templates was also researched. The code written in that Template DSL would then be compiled into both JavaScript and PHP. However, creating a production-grade Template compiler is complex and would be a large and risky investment of effort. This approach is still being considered for the future, with the directives serving as a compilation target.
+
+## Why should I, as a block developer, use the Interactivity API rather than React?
+
+Using React on the frontend doesn’t work smoothly with server rendering in PHP. Every approach that uses React to render blocks has to load content using client-side JavaScript. If you only render your blocks on the client, it typically results in a poor user experience because the user stares at empty placeholders and spinners while waiting for content to load.
+
+Using JS in PHP extensions (like v8js) is also possible, but unfortunately PHP extensions are not backward compatible and can only be used when there's a PHP fallback.
+
+Now, it’s possible to server-render a block in PHP **and** use React to render the same block on the frontend. However, this results in a poor developer experience because the logic has to be duplicated across the PHP and React parts. Not only that, but you have now exposed yourself to subtle bugs caused by WordPress hooks!
+
+Imagine installing a third-party plugin with a hook (filter) that modifies the server-rendered HTML. Let’s say this filter adds a single CSS class to your block’s HTML. That CSS class will be present in the server-rendered markup. On the frontend, your block will render again in React, but now the content will not include that CSS class because there is no way to apply WordPress hooks to React-rendered content!
+
+On the other hand, the Interactivity API is designed to work perfectly with WordPress hooks because directives enhance the server-rendered HTML with behaviors. This also means it works out of the box with WordPress backend APIs like i18n.
+
+To summarize, using the Interactivity API rather than just using React comes with these benefits:
+
+- If you use React, your interactive blocks must generate the same markup on the client as they do on the server in PHP. Using the Interactivity API, there is no such requirement as directives are added to server-rendered HTML.
+- The Interactivity API is PHP-friendlier. It works out of the box with WordPress hooks or other server functionalities such as internationalization. For example, with React, you can’t know which hooks are applied on the server, and their modifications would be overwritten after hydration.
+- All the benefits of [using a standard](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/what-is-interactivity-api#why-a-standard).
+
+
+## What are the benefits of Interactivity API over just using jQuery or vanilla JavaScript?
+
+The main difference is that the Interactivity API is **declarative and reactive**, so writing and maintaining complex interactive experiences should become way easier. Additionally, it has been **specially designed to work with blocks**, providing a standard that comes with the benefits mentioned above, like inter-block communication, compatibility, or site-wide features such as client-side navigation.
+
+Finally, comparing it with jQuery, **the Interactivity API runtime is ~10kb**, which is much more lightweight. Actually, there is an ongoing effort to remove heavy frameworks like jQuery across the WordPress ecosystem, and this would help in this regard.
+
+## Do I need to know React, PHP, and this Interactivity API?
+
+If you want to add frontend interactivity to your blocks using this API, the short answer is yes. If your block is not interactive, the block creation workflow will remain exactly the same.
+
+The Interactivity API introduces a new standard method to facilitate the integration of interactive behaviors into the frontend part of WordPress. This means that you still need to use React to handle the editor part of your blocks.
+
+On the other hand, if you want to create an interactive block, with the Interactivity API you don’t have to deal with complex topics like tooling, integration with WordPress, inter-block communication, or the server-side rendering of the interactive parts.
+
+## Can the Interactivity API be used beyond a block?
+
+Absolutely, yes, it is not limited to blocks. You'll see a lot of mentions of how the Interactivity API provides a standard for creating interactive blocks, but that's only because that's the most common use case. More generally speaking, the Interactivity API standard can be used to add "interactive behaviors" to the front end of any part of WordPress.
+
+See the [`wp_interactivity_process_directives` function](https://developer.wordpress.org/reference/functions/wp_interactivity_process_directives/) for details on using the Interactivity API outside of blocks with arbitrary HTML.
+
+## Does this mean I must migrate all my interactive blocks to use this API?
+
+No. Blocks outside the Interactivity API can coexist with blocks using it. However, as explained above, keep in mind that there are some benefits for blocks that use the API:
+
+- **Blocks can communicate with each other easily**. With a standard, this communication is handled by default. When different blocks use different approaches to frontend interactivity, inter-block communication becomes more complex and gets almost impossible when separate developers create blocks.
+- **Composability and compatibility**: You can combine interactive blocks, nest them in structures with defined behaviors, and, thanks to following the same standard, they are fully cross-compatible. If each block were to use a different approach to interactivity, they would likely break.
+- **Fewer KBs will be sent to the browser**. If each plugin author uses a different JS framework, more code will be loaded in the frontend. If all the blocks use the same one, the code is reused.
+- If all the blocks on a page use this standard, site-wide features like client-side navigation can be enabled.
+
+## What are the performance implications of using this API? Is it worth loading the Interactivity API for very simple use cases?
+
+The API has been designed with performance in mind, so it shouldn’t be a problem:
+
+- **The runtime code needed for the directives is just ~10 KB**, and it only needs to be loaded once for all the blocks.
+- **All the script modules that belong to the Interactivity API (including the `view.js` files) will load without blocking the page rendering.**
+- There are [ongoing explorations](https://github.com/WordPress/gutenberg/discussions/52723) about the possibility of **delaying the scripts loading once the block is in the viewport**. This way, the initial load would be optimized without affecting the user experience.
+
+## Does it work with the Core Translation API?
+
+As the Interactivity API works perfectly with server-side rendering, you can use all the WordPress APIs including [`__()`](https://developer.wordpress.org/reference/functions/__/) and [`_e()`](https://developer.wordpress.org/reference/functions/_e/). You can use it to translate the text in the HTML (as you normally would) and even use it inside the store when using `wp_initial_state()` on the server side. It might look something like this:
+
+```php
+// render.php
+wp_interactivity_state( 'favoriteMovies', array(
+ "1" => array(
+ "id" => "123-abc",
+ "movieName" => __("someMovieName", "textdomain")
+ ),
+) );
+```
+
+A translation API compatible with script modules (needed for the Interactivity API) is
+currently being worked on. Check [#60234](https://core.trac.wordpress.org/ticket/60234) to follow the progress on this work.
+
+## I’m concerned about XSS; can JavaScript be injected into directives?
+
+No. The Interactivity API only allows for [References](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/api-reference/#values-of-directives-are-references-to-store-properties) to be passed as values to the directives. This way, there is no need to eval() full JavaScript expressions, so it’s not possible to perform XSS attacks.
+
+## Does this work with Custom Security Policies?
+
+Yes. The Interactivity API does not use [`eval()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) or the [`Function()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) constructor, so it doesn’t violate the [`unsafe-eval`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#unsafe_keyword_values) content security policy. It is also designed to work with any [custom content security policy](https://developer.wordpress.org/apis/security/).
+
+## Can you use directives to make AJAX/REST-API requests?
+
+Sure. Actions and callbacks called by directives can do anything a JavaScript function can, including making API requests.
+
diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md
index ea11c0a389883..f752fe8104a56 100644
--- a/docs/reference-guides/theme-json-reference/theme-json-living.md
+++ b/docs/reference-guides/theme-json-reference/theme-json-living.md
@@ -116,6 +116,8 @@ Settings related to dimensions.
| Property | Type | Default | Props |
| --- | --- | --- |--- |
| aspectRatio | boolean | false | |
+| defaultAspectRatios | boolean | true | |
+| aspectRatios | array | | name, ratio, slug |
| minHeight | boolean | false | |
---
diff --git a/docs/toc.json b/docs/toc.json
index c0ec94fd049a0..fa80ee6c4f440 100644
--- a/docs/toc.json
+++ b/docs/toc.json
@@ -209,6 +209,12 @@
},
{
"docs/reference-guides/interactivity-api/api-reference.md": []
+ },
+ {
+ "docs/reference-guides/interactivity-api/iapi-about.md": []
+ },
+ {
+ "docs/reference-guides/interactivity-api/iapi-faq.md": []
}
]
},
diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php
index e18dcba462a5e..aac62e402148a 100644
--- a/lib/block-supports/layout.php
+++ b/lib/block-supports/layout.php
@@ -573,112 +573,116 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
return $block_content;
}
- $outer_class_names = array();
- $container_content_class = wp_unique_id( 'wp-container-content-' );
- $child_layout_declarations = array();
- $child_layout_styles = array();
-
- $self_stretch = isset( $block['attrs']['style']['layout']['selfStretch'] ) ? $block['attrs']['style']['layout']['selfStretch'] : null;
-
- if ( 'fixed' === $self_stretch && isset( $block['attrs']['style']['layout']['flexSize'] ) ) {
- $child_layout_declarations['flex-basis'] = $block['attrs']['style']['layout']['flexSize'];
- $child_layout_declarations['box-sizing'] = 'border-box';
- } elseif ( 'fill' === $self_stretch ) {
- $child_layout_declarations['flex-grow'] = '1';
- }
+ $outer_class_names = array();
- $column_start = isset( $block['attrs']['style']['layout']['columnStart'] ) ? $block['attrs']['style']['layout']['columnStart'] : null;
- $column_span = isset( $block['attrs']['style']['layout']['columnSpan'] ) ? $block['attrs']['style']['layout']['columnSpan'] : null;
- if ( $column_start && $column_span ) {
- $child_layout_declarations['grid-column'] = "$column_start / span $column_span";
- } elseif ( $column_start ) {
- $child_layout_declarations['grid-column'] = "$column_start";
- } elseif ( $column_span ) {
- $child_layout_declarations['grid-column'] = "span $column_span";
- }
+ // Child layout specific logic.
+ if ( $child_layout ) {
+ $container_content_class = wp_unique_id( 'wp-container-content-' );
+ $child_layout_declarations = array();
+ $child_layout_styles = array();
- $row_start = isset( $block['attrs']['style']['layout']['rowStart'] ) ? $block['attrs']['style']['layout']['rowStart'] : null;
- $row_span = isset( $block['attrs']['style']['layout']['rowSpan'] ) ? $block['attrs']['style']['layout']['rowSpan'] : null;
- if ( $row_start && $row_span ) {
- $child_layout_declarations['grid-row'] = "$row_start / span $row_span";
- } elseif ( $row_start ) {
- $child_layout_declarations['grid-row'] = "$row_start";
- } elseif ( $row_span ) {
- $child_layout_declarations['grid-row'] = "span $row_span";
- }
+ $self_stretch = isset( $block['attrs']['style']['layout']['selfStretch'] ) ? $block['attrs']['style']['layout']['selfStretch'] : null;
- $child_layout_styles[] = array(
- 'selector' => ".$container_content_class",
- 'declarations' => $child_layout_declarations,
- );
+ if ( 'fixed' === $self_stretch && isset( $block['attrs']['style']['layout']['flexSize'] ) ) {
+ $child_layout_declarations['flex-basis'] = $block['attrs']['style']['layout']['flexSize'];
+ $child_layout_declarations['box-sizing'] = 'border-box';
+ } elseif ( 'fill' === $self_stretch ) {
+ $child_layout_declarations['flex-grow'] = '1';
+ }
- $minimum_column_width = isset( $block['parentLayout']['minimumColumnWidth'] ) ? $block['parentLayout']['minimumColumnWidth'] : null;
- $column_count = isset( $block['parentLayout']['columnCount'] ) ? $block['parentLayout']['columnCount'] : null;
+ $column_start = isset( $block['attrs']['style']['layout']['columnStart'] ) ? $block['attrs']['style']['layout']['columnStart'] : null;
+ $column_span = isset( $block['attrs']['style']['layout']['columnSpan'] ) ? $block['attrs']['style']['layout']['columnSpan'] : null;
+ if ( $column_start && $column_span ) {
+ $child_layout_declarations['grid-column'] = "$column_start / span $column_span";
+ } elseif ( $column_start ) {
+ $child_layout_declarations['grid-column'] = "$column_start";
+ } elseif ( $column_span ) {
+ $child_layout_declarations['grid-column'] = "span $column_span";
+ }
- /*
- * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set,
- * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed
- * once the grid has less columns than the start.
- * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set.
- * In that case, if columnCount doesn't exist, we can assume that the grid is responsive.
- */
- if ( ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) {
- $column_span_number = floatval( $column_span );
- $column_start_number = floatval( $column_start );
- $highest_number = max( $column_span_number, $column_start_number );
- $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem';
- $parent_column_value = floatval( $parent_column_width );
- $parent_column_unit = explode( $parent_column_value, $parent_column_width );
+ $row_start = isset( $block['attrs']['style']['layout']['rowStart'] ) ? $block['attrs']['style']['layout']['rowStart'] : null;
+ $row_span = isset( $block['attrs']['style']['layout']['rowSpan'] ) ? $block['attrs']['style']['layout']['rowSpan'] : null;
+ if ( $row_start && $row_span ) {
+ $child_layout_declarations['grid-row'] = "$row_start / span $row_span";
+ } elseif ( $row_start ) {
+ $child_layout_declarations['grid-row'] = "$row_start";
+ } elseif ( $row_span ) {
+ $child_layout_declarations['grid-row'] = "span $row_span";
+ }
+
+ $child_layout_styles[] = array(
+ 'selector' => ".$container_content_class",
+ 'declarations' => $child_layout_declarations,
+ );
+
+ $minimum_column_width = isset( $block['parentLayout']['minimumColumnWidth'] ) ? $block['parentLayout']['minimumColumnWidth'] : null;
+ $column_count = isset( $block['parentLayout']['columnCount'] ) ? $block['parentLayout']['columnCount'] : null;
/*
- * If there is no unit, the width has somehow been mangled so we reset both unit and value
- * to defaults.
- * Additionally, the unit should be one of px, rem or em, so that also needs to be checked.
+ * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set,
+ * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed
+ * once the grid has less columns than the start.
+ * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set.
+ * In that case, if columnCount doesn't exist, we can assume that the grid is responsive.
*/
- if ( count( $parent_column_unit ) <= 1 ) {
- $parent_column_unit = 'rem';
- $parent_column_value = 12;
- } else {
- $parent_column_unit = $parent_column_unit[1];
+ if ( ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) {
+ $column_span_number = floatval( $column_span );
+ $column_start_number = floatval( $column_start );
+ $highest_number = max( $column_span_number, $column_start_number );
+ $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem';
+ $parent_column_value = floatval( $parent_column_width );
+ $parent_column_unit = explode( $parent_column_value, $parent_column_width );
+
+ /*
+ * If there is no unit, the width has somehow been mangled so we reset both unit and value
+ * to defaults.
+ * Additionally, the unit should be one of px, rem or em, so that also needs to be checked.
+ */
+ if ( count( $parent_column_unit ) <= 1 ) {
+ $parent_column_unit = 'rem';
+ $parent_column_value = 12;
+ } else {
+ $parent_column_unit = $parent_column_unit[1];
- if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) {
- $parent_column_unit = 'rem';
+ if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) {
+ $parent_column_unit = 'rem';
+ }
}
+
+ /*
+ * A default gap value is used for this computation because custom gap values may not be
+ * viable to use in the computation of the container query value.
+ */
+ $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5;
+ $container_query_value = $highest_number * $parent_column_value + ( $highest_number - 1 ) * $default_gap_value;
+ $container_query_value = $container_query_value . $parent_column_unit;
+ // If a span is set we want to preserve it as long as possible, otherwise we just reset the value.
+ $grid_column_value = $column_span ? '1/-1' : 'auto';
+
+ $child_layout_styles[] = array(
+ 'rules_group' => "@container (max-width: $container_query_value )",
+ 'selector' => ".$container_content_class",
+ 'declarations' => array(
+ 'grid-column' => $grid_column_value,
+ ),
+ );
}
/*
- * A default gap value is used for this computation because custom gap values may not be
- * viable to use in the computation of the container query value.
+ * Add to the style engine store to enqueue and render layout styles.
+ * Return styles here just to check if any exist.
*/
- $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5;
- $container_query_value = $highest_number * $parent_column_value + ( $highest_number - 1 ) * $default_gap_value;
- $container_query_value = $container_query_value . $parent_column_unit;
- // If a span is set we want to preserve it as long as possible, otherwise we just reset the value.
- $grid_column_value = $column_span ? '1/-1' : 'auto';
-
- $child_layout_styles[] = array(
- 'rules_group' => "@container (max-width: $container_query_value )",
- 'selector' => ".$container_content_class",
- 'declarations' => array(
- 'grid-column' => $grid_column_value,
- ),
+ $child_css = gutenberg_style_engine_get_stylesheet_from_css_rules(
+ $child_layout_styles,
+ array(
+ 'context' => 'block-supports',
+ 'prettify' => false,
+ )
);
- }
- /*
- * Add to the style engine store to enqueue and render layout styles.
- * Return styles here just to check if any exist.
- */
- $child_css = gutenberg_style_engine_get_stylesheet_from_css_rules(
- $child_layout_styles,
- array(
- 'context' => 'block-supports',
- 'prettify' => false,
- )
- );
-
- if ( $child_css ) {
- $outer_class_names[] = $container_content_class;
+ if ( $child_css ) {
+ $outer_class_names[] = $container_content_class;
+ }
}
// Prep the processor for modifying the block output.
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index 77004253bea86..46d7082912f53 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -123,9 +123,19 @@ class WP_Theme_JSON_Gutenberg {
* @since 6.0.0 Replaced `override` with `prevent_override` and updated the
* `prevent_override` value for `color.duotone` to use `color.defaultDuotone`.
* @since 6.2.0 Added 'shadow' presets.
+ * @since 6.6.0 Added `aspectRatios`.
* @var array
*/
const PRESETS_METADATA = array(
+ array(
+ 'path' => array( 'dimensions', 'aspectRatios' ),
+ 'prevent_override' => array( 'dimensions', 'defaultAspectRatios' ),
+ 'use_default_names' => false,
+ 'value_key' => 'ratio',
+ 'css_vars' => '--wp--preset--aspect-ratio--$slug',
+ 'classes' => array(),
+ 'properties' => array( 'aspect-ratio' ),
+ ),
array(
'path' => array( 'color', 'palette' ),
'prevent_override' => array( 'color', 'defaultPalette' ),
@@ -397,8 +407,10 @@ class WP_Theme_JSON_Gutenberg {
),
'custom' => null,
'dimensions' => array(
- 'aspectRatio' => null,
- 'minHeight' => null,
+ 'aspectRatio' => null,
+ 'aspectRatios' => null,
+ 'defaultAspectRatios' => null,
+ 'minHeight' => null,
),
'layout' => array(
'contentSize' => null,
@@ -483,7 +495,7 @@ class WP_Theme_JSON_Gutenberg {
* updated `blockGap` to be allowed at any level.
* @since 6.2.0 Added `outline`, and `minHeight` properties.
* @since 6.6.0 Added `background` sub properties to top-level only.
- *
+ * @since 6.6.0 Added `dimensions.aspectRatio`.
* @var array
*/
const VALID_STYLES = array(
diff --git a/lib/theme.json b/lib/theme.json
index 90a5d975e68d6..49aa2abb08005 100644
--- a/lib/theme.json
+++ b/lib/theme.json
@@ -190,6 +190,46 @@
],
"text": true
},
+ "dimensions": {
+ "defaultAspectRatios": true,
+ "aspectRatios": [
+ {
+ "name": "Square - 1:1",
+ "slug": "square",
+ "ratio": "1"
+ },
+ {
+ "name": "Standard - 4:3",
+ "slug": "4-3",
+ "ratio": "4/3"
+ },
+ {
+ "name": "Portrait - 3:4",
+ "slug": "3-4",
+ "ratio": "3/4"
+ },
+ {
+ "name": "Classic - 3:2",
+ "slug": "3-2",
+ "ratio": "3/2"
+ },
+ {
+ "name": "Classic Portrait - 2:3",
+ "slug": "2-3",
+ "ratio": "2/3"
+ },
+ {
+ "name": "Wide - 16:9",
+ "slug": "16-9",
+ "ratio": "16/9"
+ },
+ {
+ "name": "Tall - 9:16",
+ "slug": "9-16",
+ "ratio": "9/16"
+ }
+ ]
+ },
"shadow": {
"defaultPresets": true,
"presets": [
diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js
index e0af72c238ad5..eb2a02e5095d7 100644
--- a/packages/block-editor/src/components/child-layout-control/index.js
+++ b/packages/block-editor/src/components/child-layout-control/index.js
@@ -223,7 +223,13 @@ export default function ChildLayoutControl( {
} }
value={ columnStart }
min={ 1 }
- max={ parentLayout?.columnCount }
+ max={
+ parentLayout?.columnCount
+ ? parentLayout.columnCount -
+ ( columnSpan ?? 1 ) +
+ 1
+ : undefined
+ }
/>
@@ -241,7 +247,13 @@ export default function ChildLayoutControl( {
} }
value={ rowStart }
min={ 1 }
- max={ parentLayout?.columnCount }
+ max={
+ parentLayout?.rowCount
+ ? parentLayout.rowCount -
+ ( rowSpan ?? 1 ) +
+ 1
+ : undefined
+ }
/>
diff --git a/packages/block-editor/src/components/colors-gradients/control.js b/packages/block-editor/src/components/colors-gradients/control.js
index bbe1f567de43b..e820bf7328488 100644
--- a/packages/block-editor/src/components/colors-gradients/control.js
+++ b/packages/block-editor/src/components/colors-gradients/control.js
@@ -141,7 +141,7 @@ function ColorGradientControlInner( {
>
- { __( 'Solid' ) }
+ { __( 'Color' ) }
{ __( 'Gradient' ) }
diff --git a/packages/block-editor/src/components/colors-gradients/test/control.js b/packages/block-editor/src/components/colors-gradients/test/control.js
index 9f440fee3d0e3..0bda16071ef99 100644
--- a/packages/block-editor/src/components/colors-gradients/test/control.js
+++ b/packages/block-editor/src/components/colors-gradients/test/control.js
@@ -43,7 +43,7 @@ describe( 'ColorPaletteControl', () => {
// Is showing the two tab buttons.
expect(
- screen.getByRole( 'tab', { name: 'Solid' } )
+ screen.getByRole( 'tab', { name: 'Color' } )
).toBeInTheDocument();
expect(
screen.getByRole( 'tab', { name: 'Gradient' } )
@@ -72,7 +72,7 @@ describe( 'ColorPaletteControl', () => {
// Is not showing the two tab buttons.
expect(
- screen.queryByRole( 'tab', { name: 'Solid' } )
+ screen.queryByRole( 'tab', { name: 'Color' } )
).not.toBeInTheDocument();
expect(
screen.queryByRole( 'tab', { name: 'Gradient' } )
@@ -111,7 +111,7 @@ describe( 'ColorPaletteControl', () => {
// Is not showing the two tab buttons.
expect(
- screen.queryByRole( 'tab', { name: 'Solid' } )
+ screen.queryByRole( 'tab', { name: 'Color' } )
).not.toBeInTheDocument();
expect(
screen.queryByRole( 'tab', { name: 'Gradient' } )
diff --git a/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js b/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js
index 5ff35ae0e0c88..e38a01e199b79 100644
--- a/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js
+++ b/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js
@@ -6,75 +6,14 @@ import {
__experimentalToolsPanelItem as ToolsPanelItem,
} from '@wordpress/components';
import { __, _x } from '@wordpress/i18n';
-
/**
- * @typedef {import('@wordpress/components/build-types/select-control/types').SelectControlProps} SelectControlProps
+ * Internal dependencies
*/
+import { useSettings } from '../use-settings';
/**
- * @type {SelectControlProps[]}
+ * @typedef {import('@wordpress/components/build-types/select-control/types').SelectControlProps} SelectControlProps
*/
-export const DEFAULT_ASPECT_RATIO_OPTIONS = [
- {
- label: _x( 'Original', 'Aspect ratio option for dimensions control' ),
- value: 'auto',
- },
- {
- label: _x(
- 'Square - 1:1',
- 'Aspect ratio option for dimensions control'
- ),
- value: '1',
- },
- {
- label: _x(
- 'Standard - 4:3',
- 'Aspect ratio option for dimensions control'
- ),
- value: '4/3',
- },
- {
- label: _x(
- 'Portrait - 3:4',
- 'Aspect ratio option for dimensions control'
- ),
- value: '3/4',
- },
- {
- label: _x(
- 'Classic - 3:2',
- 'Aspect ratio option for dimensions control'
- ),
- value: '3/2',
- },
- {
- label: _x(
- 'Classic Portrait - 2:3',
- 'Aspect ratio option for dimensions control'
- ),
- value: '2/3',
- },
- {
- label: _x(
- 'Wide - 16:9',
- 'Aspect ratio option for dimensions control'
- ),
- value: '16/9',
- },
- {
- label: _x(
- 'Tall - 9:16',
- 'Aspect ratio option for dimensions control'
- ),
- value: '9/16',
- },
- {
- label: _x( 'Custom', 'Aspect ratio option for dimensions control' ),
- value: 'custom',
- disabled: true,
- hidden: true,
- },
-];
/**
* @callback AspectRatioToolPropsOnChange
@@ -96,14 +35,48 @@ export default function AspectRatioTool( {
panelId,
value,
onChange = () => {},
- options = DEFAULT_ASPECT_RATIO_OPTIONS,
- defaultValue = DEFAULT_ASPECT_RATIO_OPTIONS[ 0 ].value,
+ options,
+ defaultValue = 'auto',
hasValue,
isShownByDefault = true,
} ) {
// Match the CSS default so if the value is used directly in CSS it will look correct in the control.
const displayValue = value ?? 'auto';
+ const [ defaultRatios, themeRatios, showDefaultRatios ] = useSettings(
+ 'dimensions.aspectRatios.default',
+ 'dimensions.aspectRatios.theme',
+ 'dimensions.defaultAspectRatios'
+ );
+
+ const themeOptions = themeRatios?.map( ( { name, ratio } ) => ( {
+ label: name,
+ value: ratio,
+ } ) );
+
+ const defaultOptions = defaultRatios?.map( ( { name, ratio } ) => ( {
+ label: name,
+ value: ratio,
+ } ) );
+
+ const aspectRatioOptions = [
+ {
+ label: _x(
+ 'Original',
+ 'Aspect ratio option for dimensions control'
+ ),
+ value: 'auto',
+ },
+ ...( showDefaultRatios ? defaultOptions : [] ),
+ ...( themeOptions ? themeOptions : [] ),
+ {
+ label: _x( 'Custom', 'Aspect ratio option for dimensions control' ),
+ value: 'custom',
+ disabled: true,
+ hidden: true,
+ },
+ ];
+
return (
diff --git a/packages/block-editor/src/components/global-styles/color-panel.js b/packages/block-editor/src/components/global-styles/color-panel.js
index 72cb0ebbac123..7a20953af428c 100644
--- a/packages/block-editor/src/components/global-styles/color-panel.js
+++ b/packages/block-editor/src/components/global-styles/color-panel.js
@@ -545,7 +545,7 @@ export default function ColorPanel( {
tabs: [
hasSolidColors && {
key: 'background',
- label: __( 'Solid' ),
+ label: __( 'Color' ),
inheritedValue: backgroundColor,
setValue: setBackgroundColor,
userValue: userBackgroundColor,
diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js
index ed1da045bca5a..3985bb53ce5cf 100644
--- a/packages/block-editor/src/components/iframe/index.js
+++ b/packages/block-editor/src/components/iframe/index.js
@@ -221,6 +221,44 @@ function Iframe( {
const [ iframeWindowInnerHeight, setIframeWindowInnerHeight ] = useState();
+ const scaleRef = useRefEffect(
+ ( body ) => {
+ // Hack to get proper margins when scaling the iframe document.
+ const bottomFrameSize = frameSize - contentHeight * ( 1 - scale );
+
+ const { documentElement } = body.ownerDocument;
+
+ body.classList.add( 'is-zoomed-out' );
+
+ documentElement.style.transform = `scale( ${ scale } )`;
+ documentElement.style.marginTop = `${ frameSize }px`;
+ // TODO: `marginBottom` doesn't work in Firefox. We need another way
+ // to do this.
+ documentElement.style.marginBottom = `${ bottomFrameSize }px`;
+ if ( iframeWindowInnerHeight > contentHeight * scale ) {
+ iframeDocument.body.style.minHeight = `${ Math.floor(
+ ( iframeWindowInnerHeight - 2 * frameSize ) / scale
+ ) }px`;
+ }
+
+ return () => {
+ body.classList.remove( 'is-zoomed-out' );
+ documentElement.style.transform = '';
+ documentElement.style.marginTop = '';
+ documentElement.style.marginBottom = '';
+ body.style.minHeight = '';
+ };
+ },
+ [
+ scale,
+ frameSize,
+ iframeDocument,
+ contentHeight,
+ iframeWindowInnerHeight,
+ contentWidth,
+ ]
+ );
+
const disabledRef = useDisabled( { isDisabled: ! readonly } );
const bodyRef = useMergeRefs( [
useBubbleEvents( iframeDocument ),
@@ -232,6 +270,7 @@ function Iframe( {
// unnecessary re-renders when animating the iframe width, or when
// expanding preview iframes.
scale === 1 ? null : windowResizeRef,
+ scale === 1 ? null : scaleRef,
] );
// Correct doctype is required to enable rendering in standards
@@ -277,44 +316,6 @@ function Iframe( {
? scale( contentWidth, contentHeight )
: scale;
- useEffect( () => {
- if ( ! iframeDocument ) {
- return;
- }
-
- if ( scale !== 1 ) {
- // Hack to get proper margins when scaling the iframe document.
- const bottomFrameSize = frameSize - contentHeight * ( 1 - scale );
-
- iframeDocument.body.classList.add( 'is-zoomed-out' );
-
- iframeDocument.documentElement.style.transform = `scale( ${ scale } )`;
- iframeDocument.documentElement.style.marginTop = `${ frameSize }px`;
- // TODO: `marginBottom` doesn't work in Firefox. We need another way to do this.
- iframeDocument.documentElement.style.marginBottom = `${ bottomFrameSize }px`;
- if ( iframeWindowInnerHeight > contentHeight * scale ) {
- iframeDocument.body.style.minHeight = `${ Math.floor(
- ( iframeWindowInnerHeight - 2 * frameSize ) / scale
- ) }px`;
- }
-
- return () => {
- iframeDocument.body.classList.remove( 'is-zoomed-out' );
- iframeDocument.documentElement.style.transform = '';
- iframeDocument.documentElement.style.marginTop = '';
- iframeDocument.documentElement.style.marginBottom = '';
- iframeDocument.body.style.minHeight = '';
- };
- }
- }, [
- scale,
- frameSize,
- iframeDocument,
- contentHeight,
- iframeWindowInnerHeight,
- contentWidth,
- ] );
-
// Make sure to not render the before and after focusable div elements in view
// mode. They're only needed to capture focus in edit mode.
const shouldRenderFocusCaptureElements = tabIndex >= 0 && ! isPreviewMode;
diff --git a/packages/block-editor/src/components/image-editor/aspect-ratio-dropdown.js b/packages/block-editor/src/components/image-editor/aspect-ratio-dropdown.js
index a15c9f42b9e8e..3f48a634bdbce 100644
--- a/packages/block-editor/src/components/image-editor/aspect-ratio-dropdown.js
+++ b/packages/block-editor/src/components/image-editor/aspect-ratio-dropdown.js
@@ -8,34 +8,70 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
+import { useSettings } from '../use-settings';
import { POPOVER_PROPS } from './constants';
import { useImageEditingContext } from './context';
-function AspectGroup( { aspectRatios, isDisabled, label, onClick, value } ) {
+function AspectRatioGroup( {
+ aspectRatios,
+ isDisabled,
+ label,
+ onClick,
+ value,
+} ) {
return (
- { aspectRatios.map( ( { title, aspect } ) => (
+ { aspectRatios.map( ( { name, slug, ratio } ) => (
) ) }
);
}
+export function ratioToNumber( str ) {
+ // TODO: support two-value aspect ratio?
+ // https://css-tricks.com/almanac/properties/a/aspect-ratio/#aa-it-can-take-two-values
+ const [ a, b, ...rest ] = str.split( '/' ).map( Number );
+ if (
+ a <= 0 ||
+ b <= 0 ||
+ Number.isNaN( a ) ||
+ Number.isNaN( b ) ||
+ rest.length
+ ) {
+ return NaN;
+ }
+ return b ? a / b : a;
+}
+
+function presetRatioAsNumber( { ratio, ...rest } ) {
+ return {
+ ratio: ratioToNumber( ratio ),
+ ...rest,
+ };
+}
+
export default function AspectRatioDropdown( { toggleProps } ) {
const { isInProgress, aspect, setAspect, defaultAspect } =
useImageEditingContext();
+ const [ defaultRatios, themeRatios, showDefaultRatios ] = useSettings(
+ 'dimensions.aspectRatios.default',
+ 'dimensions.aspectRatios.theme',
+ 'dimensions.defaultAspectRatios'
+ );
+
return (
{ ( { onClose } ) => (
<>
- {
setAspect( newAspect );
@@ -56,61 +92,57 @@ export default function AspectRatioDropdown( { toggleProps } ) {
aspectRatios={ [
// All ratios should be mirrored in AspectRatioTool in @wordpress/block-editor.
{
- title: __( 'Original' ),
+ slug: 'original',
+ name: __( 'Original' ),
aspect: defaultAspect,
},
- {
- title: __( 'Square' ),
- aspect: 1,
- },
- ] }
- />
- {
- setAspect( newAspect );
- onClose();
- } }
- value={ aspect }
- aspectRatios={ [
- {
- title: __( '16:9' ),
- aspect: 16 / 9,
- },
- {
- title: __( '4:3' ),
- aspect: 4 / 3,
- },
- {
- title: __( '3:2' ),
- aspect: 3 / 2,
- },
- ] }
- />
- {
- setAspect( newAspect );
- onClose();
- } }
- value={ aspect }
- aspectRatios={ [
- {
- title: __( '9:16' ),
- aspect: 9 / 16,
- },
- {
- title: __( '3:4' ),
- aspect: 3 / 4,
- },
- {
- title: __( '2:3' ),
- aspect: 2 / 3,
- },
+ ...( showDefaultRatios
+ ? defaultRatios
+ .map( presetRatioAsNumber )
+ .filter( ( { ratio } ) => ratio === 1 )
+ : [] ),
] }
/>
+ { themeRatios?.length > 0 && (
+ {
+ setAspect( newAspect );
+ onClose();
+ } }
+ value={ aspect }
+ aspectRatios={ themeRatios }
+ />
+ ) }
+ { showDefaultRatios && (
+ {
+ setAspect( newAspect );
+ onClose();
+ } }
+ value={ aspect }
+ aspectRatios={ defaultRatios
+ .map( presetRatioAsNumber )
+ .filter( ( { ratio } ) => ratio > 1 ) }
+ />
+ ) }
+ { showDefaultRatios && (
+ {
+ setAspect( newAspect );
+ onClose();
+ } }
+ value={ aspect }
+ aspectRatios={ defaultRatios
+ .map( presetRatioAsNumber )
+ .filter( ( { ratio } ) => ratio < 1 ) }
+ />
+ ) }
>
) }
diff --git a/packages/block-editor/src/components/image-editor/index.js b/packages/block-editor/src/components/image-editor/index.js
index cfd912bb2827c..133f79732bdbd 100644
--- a/packages/block-editor/src/components/image-editor/index.js
+++ b/packages/block-editor/src/components/image-editor/index.js
@@ -6,11 +6,11 @@ import { ToolbarGroup, ToolbarItem } from '@wordpress/components';
/**
* Internal dependencies
*/
+import AspectRatioDropdown from './aspect-ratio-dropdown';
import BlockControls from '../block-controls';
import ImageEditingProvider from './context';
import Cropper from './cropper';
import ZoomDropdown from './zoom-dropdown';
-import AspectRatioDropdown from './aspect-ratio-dropdown';
import RotationButton from './rotation-button';
import FormControls from './form-controls';
diff --git a/packages/block-editor/src/components/image-editor/test/index.js b/packages/block-editor/src/components/image-editor/test/index.js
new file mode 100644
index 0000000000000..9f0f3491667a8
--- /dev/null
+++ b/packages/block-editor/src/components/image-editor/test/index.js
@@ -0,0 +1,22 @@
+/**
+ * Internal dependencies
+ */
+import { ratioToNumber } from '../aspect-ratio-dropdown';
+
+test( 'ratioToNumber', () => {
+ expect( ratioToNumber( '1/1' ) ).toBe( 1 );
+ expect( ratioToNumber( '1' ) ).toBe( 1 );
+ expect( ratioToNumber( '11/11' ) ).toBe( 1 );
+ expect( ratioToNumber( '16/9' ) ).toBe( 16 / 9 );
+ expect( ratioToNumber( '4/3' ) ).toBe( 4 / 3 );
+ expect( ratioToNumber( '3/2' ) ).toBe( 3 / 2 );
+ expect( ratioToNumber( '2/1' ) ).toBe( 2 );
+ expect( ratioToNumber( '1/2' ) ).toBe( 1 / 2 );
+ expect( ratioToNumber( '2/3' ) ).toBe( 2 / 3 );
+ expect( ratioToNumber( '3/4' ) ).toBe( 3 / 4 );
+ expect( ratioToNumber( '9/16' ) ).toBe( 9 / 16 );
+ expect( ratioToNumber( '1/16' ) ).toBe( 1 / 16 );
+ expect( ratioToNumber( '16/1' ) ).toBe( 16 );
+ expect( ratioToNumber( '1/9' ) ).toBe( 1 / 9 );
+ expect( ratioToNumber( 'auto' ) ).toBe( NaN );
+} );
diff --git a/packages/block-editor/src/utils/transform-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js
index 129a4cd0e3e7b..5de3cb7e592bc 100644
--- a/packages/block-editor/src/utils/transform-styles/index.js
+++ b/packages/block-editor/src/utils/transform-styles/index.js
@@ -5,7 +5,7 @@ import postcss, { CssSyntaxError } from 'postcss';
import wrap from 'postcss-prefixwrap';
import rebaseUrl from 'postcss-urlrebase';
-const transformStylesCache = new WeakMap();
+const cacheByWrapperSelector = new Map();
function transformStyle(
{ css, ignoredSelectors = [], baseURL },
@@ -65,14 +65,18 @@ function transformStyle(
* @return {Array} converted rules.
*/
const transformStyles = ( styles, wrapperSelector = '' ) => {
+ let cache = cacheByWrapperSelector.get( wrapperSelector );
+ if ( ! cache ) {
+ cache = new WeakMap();
+ cacheByWrapperSelector.set( wrapperSelector, cache );
+ }
return styles.map( ( style ) => {
- if ( transformStylesCache.has( style ) ) {
- return transformStylesCache.get( style );
+ let css = cache.get( style );
+ if ( ! css ) {
+ css = transformStyle( style, wrapperSelector );
+ cache.set( style, css );
}
-
- const transformedStyle = transformStyle( style, wrapperSelector );
- transformStylesCache.set( style, transformedStyle );
- return transformedStyle;
+ return css;
} );
};
diff --git a/packages/block-library/src/post-featured-image/dimension-controls.js b/packages/block-library/src/post-featured-image/dimension-controls.js
index b64b3299fc96b..c8e8c0005cfef 100644
--- a/packages/block-library/src/post-featured-image/dimension-controls.js
+++ b/packages/block-library/src/post-featured-image/dimension-controls.js
@@ -57,7 +57,13 @@ const DimensionControls = ( {
setAttributes,
media,
} ) => {
- const [ availableUnits ] = useSettings( 'spacing.units' );
+ const [ availableUnits, defaultRatios, themeRatios, showDefaultRatios ] =
+ useSettings(
+ 'spacing.units',
+ 'dimensions.aspectRatios.default',
+ 'dimensions.aspectRatios.theme',
+ 'dimensions.defaultAspectRatios'
+ );
const units = useCustomUnits( {
availableUnits: availableUnits || [ 'px', '%', 'vw', 'em', 'rem' ],
} );
@@ -93,6 +99,28 @@ const DimensionControls = ( {
const showScaleControl =
height || ( aspectRatio && aspectRatio !== 'auto' );
+ const themeOptions = themeRatios?.map( ( { name, ratio } ) => ( {
+ label: name,
+ value: ratio,
+ } ) );
+
+ const defaultOptions = defaultRatios?.map( ( { name, ratio } ) => ( {
+ label: name,
+ value: ratio,
+ } ) );
+
+ const aspectRatioOptions = [
+ {
+ label: _x(
+ 'Original',
+ 'Aspect ratio option for dimensions control'
+ ),
+ value: 'auto',
+ },
+ ...( showDefaultRatios ? defaultOptions : [] ),
+ ...( themeOptions ? themeOptions : [] ),
+ ];
+
return (
<>
setAttributes( { aspectRatio: nextAspectRatio } )
}
diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js
index 08b4e084e2ec9..68877c280d4dc 100644
--- a/packages/blocks/src/api/constants.js
+++ b/packages/blocks/src/api/constants.js
@@ -288,6 +288,7 @@ export const __EXPERIMENTAL_PATHS_WITH_OVERRIDE = {
'color.duotone': true,
'color.gradients': true,
'color.palette': true,
+ 'dimensions.aspectRatios': true,
'typography.fontSizes': true,
'spacing.spacingSizes': true,
};
diff --git a/packages/components/src/mobile/color-settings/utils.native.js b/packages/components/src/mobile/color-settings/utils.native.js
index c18a6503e5973..87ad346c8ec3a 100644
--- a/packages/components/src/mobile/color-settings/utils.native.js
+++ b/packages/components/src/mobile/color-settings/utils.native.js
@@ -28,7 +28,7 @@ export const colorsUtils = {
picker: 'Picker',
palette: 'Palette',
},
- segments: [ __( 'Solid' ), __( 'Gradient' ) ],
+ segments: [ __( 'Color' ), __( 'Gradient' ) ],
gradients,
gradientOptions,
getGradientType,
diff --git a/packages/dataviews/src/lock-unlock.js b/packages/dataviews/src/lock-unlock.ts
similarity index 100%
rename from packages/dataviews/src/lock-unlock.js
rename to packages/dataviews/src/lock-unlock.ts
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index ef6b0773a2138..8244992a1f5f8 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -3,8 +3,6 @@
*/
import type { ReactNode } from 'react';
-type Item = Record< string, any >;
-
interface Option {
value: any;
label: string;
@@ -17,6 +15,8 @@ interface filterByConfig {
type Operator = 'is' | 'isNot' | 'isAny' | 'isNone' | 'isAll' | 'isNotAll';
+export type Item = Record< string, any >;
+
export interface Field {
/**
* The unique identifier of the field.
@@ -101,7 +101,7 @@ export interface Filter {
value: any;
}
-export interface View {
+interface ViewBase {
/**
* The layout of the view.
*/
@@ -141,4 +141,26 @@ export interface View {
* The number of items per page
*/
perPage?: number;
+
+ /**
+ * The hidden fields.
+ */
+ hiddenFields: string[];
+}
+export interface ViewList extends ViewBase {
+ type: 'list';
+
+ layout: {
+ /**
+ * The field to use as the primary field.
+ */
+ primaryField: string;
+
+ /**
+ * The field to use as the media field.
+ */
+ mediaField: string;
+ };
}
+
+export type View = ViewList | ViewBase;
diff --git a/packages/dataviews/src/view-list.js b/packages/dataviews/src/view-list.tsx
similarity index 82%
rename from packages/dataviews/src/view-list.js
rename to packages/dataviews/src/view-list.tsx
index 14799a9b94167..d88f11de59eed 100644
--- a/packages/dataviews/src/view-list.js
+++ b/packages/dataviews/src/view-list.tsx
@@ -21,6 +21,33 @@ import { __ } from '@wordpress/i18n';
* Internal dependencies
*/
import { unlock } from './lock-unlock';
+import type {
+ Data,
+ Item,
+ NormalizedField,
+ ViewList as ViewListType,
+} from './types';
+
+interface ListViewProps {
+ view: ViewListType;
+ fields: NormalizedField[];
+ data: Data;
+ isLoading: boolean;
+ getItemId: ( item: Item ) => string;
+ onSelectionChange: ( selection: Item[] ) => void;
+ selection: Item[];
+ id: string;
+}
+
+interface ListViewItemProps {
+ id: string;
+ item: Item;
+ isSelected: boolean;
+ onSelect: ( item: Item ) => void;
+ mediaField?: NormalizedField;
+ primaryField?: NormalizedField;
+ visibleFields: NormalizedField[];
+}
const {
useCompositeStoreV2: useCompositeStore,
@@ -37,8 +64,8 @@ function ListItem( {
mediaField,
primaryField,
visibleFields,
-} ) {
- const itemRef = useRef( null );
+}: ListViewItemProps ) {
+ const itemRef = useRef< HTMLElement >( null );
const labelId = `${ id }-label`;
const descriptionId = `${ id }-description`;
@@ -120,16 +147,17 @@ function ListItem( {
);
}
-export default function ViewList( {
- view,
- fields,
- data,
- isLoading,
- getItemId,
- onSelectionChange,
- selection,
- id: preferredId,
-} ) {
+export default function ViewList( props: ListViewProps ) {
+ const {
+ view,
+ fields,
+ data,
+ isLoading,
+ getItemId,
+ onSelectionChange,
+ selection,
+ id: preferredId,
+ } = props;
const baseId = useInstanceId( ViewList, 'view-list', preferredId );
const selectedItem = data?.findLast( ( item ) =>
selection.includes( item.id )
@@ -150,12 +178,12 @@ export default function ViewList( {
);
const onSelect = useCallback(
- ( item ) => onSelectionChange( [ item ] ),
+ ( item: Item ) => onSelectionChange( [ item ] ),
[ onSelectionChange ]
);
const getItemDomId = useCallback(
- ( item ) => ( item ? `${ baseId }-${ getItemId( item ) }` : undefined ),
+ ( item?: Item ) => ( item ? `${ baseId }-${ getItemId( item ) }` : '' ),
[ baseId, getItemId ]
);
diff --git a/packages/dataviews/tsconfig.json b/packages/dataviews/tsconfig.json
index 119035ffe7be3..60122ee152c80 100644
--- a/packages/dataviews/tsconfig.json
+++ b/packages/dataviews/tsconfig.json
@@ -3,7 +3,9 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
- "declarationDir": "build-types"
+ "declarationDir": "build-types",
+ "skipLibCheck": true,
+ "checkJs": false
},
"references": [
{ "path": "../a11y" },
@@ -16,5 +18,5 @@
{ "path": "../primitives" },
{ "path": "../private-apis" }
],
- "include": [ "src/**/*.ts", "src/**/*.tsx" ]
+ "include": [ "src" ]
}
diff --git a/packages/edit-site/src/components/global-styles/screen-color-palette.js b/packages/edit-site/src/components/global-styles/screen-color-palette.js
index f06db2eeae070..6d3bd6b2e130d 100644
--- a/packages/edit-site/src/components/global-styles/screen-color-palette.js
+++ b/packages/edit-site/src/components/global-styles/screen-color-palette.js
@@ -18,17 +18,17 @@ function ScreenColorPalette( { name } ) {
return (
<>
- { __( 'Solid' ) }
+ { __( 'Color' ) }{ __( 'Gradient' ) }
-
+
diff --git a/packages/editor/src/components/post-status/index.js b/packages/editor/src/components/post-status/index.js
index 414ddce229c4b..ca728484e266a 100644
--- a/packages/editor/src/components/post-status/index.js
+++ b/packages/editor/src/components/post-status/index.js
@@ -53,10 +53,10 @@ function PostStatusLabel( { canEdit } ) {
statusLabel = __( 'Draft' );
break;
case 'pending':
- statusLabel = __( 'Pending review' );
+ statusLabel = __( 'Pending' );
break;
case 'private':
- statusLabel = __( 'Published privately' );
+ statusLabel = __( 'Private' );
break;
}
return (
diff --git a/phpunit/block-supports/layout-test.php b/phpunit/block-supports/layout-test.php
index 300f6e72be480..6344d7617aeeb 100644
--- a/phpunit/block-supports/layout-test.php
+++ b/phpunit/block-supports/layout-test.php
@@ -411,6 +411,7 @@ public function data_gutenberg_get_layout_style() {
* @param string $expected_output The expected output.
*/
public function test_layout_support_flag_renders_classnames_on_wrapper( $args, $expected_output ) {
+ switch_theme( 'default' );
$actual_output = gutenberg_render_layout_support_flag( $args['block_content'], $args['block'] );
$this->assertEquals( $expected_output, $actual_output );
}
@@ -481,6 +482,27 @@ public function data_layout_support_flag_renders_classnames_on_wrapper() {
),
'expected_output' => '