Skip to content

Commit 3e26db9

Browse files
Merge branch 'WordPress:trunk' into feature/prefetch-lightbox-images
2 parents 235f469 + bcabd7a commit 3e26db9

File tree

102 files changed

+4443
-177
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+4443
-177
lines changed

.github/workflows/unit-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ jobs:
211211
# dependency versions are installed and cached.
212212
##
213213
- name: Set up PHP
214-
uses: shivammathur/setup-php@27853eb8b46dc01c33bf9fef67d98df2683c3be2 # v2.34.0
214+
uses: shivammathur/setup-php@0f7f1d08e3e32076e51cae65eb0b0c871405b16e # v2.34.1
215215
with:
216216
php-version: '${{ matrix.php }}'
217217
ini-file: development
@@ -311,7 +311,7 @@ jobs:
311311
persist-credentials: false
312312

313313
- name: Set up PHP
314-
uses: shivammathur/setup-php@27853eb8b46dc01c33bf9fef67d98df2683c3be2 # v2.34.0
314+
uses: shivammathur/setup-php@0f7f1d08e3e32076e51cae65eb0b0c871405b16e # v2.34.1
315315
with:
316316
php-version: '7.4'
317317
coverage: none

docs/reference-guides/block-api/block-transforms.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ A transformation of type `shortcode` is an object that takes the following param
271271
- **type** _(string)_: the value `shortcode`.
272272
- **tag** _(string|array)_: the shortcode tag or list of shortcode aliases this transform can work with.
273273
- **transform** _(function, optional)_: a callback that receives the shortcode attributes as the first argument and the [WPShortcodeMatch](/packages/shortcode/README.md#next) as the second. It should return a block object or an array of block objects. When this parameter is defined, it will take precedence over the `attributes` parameter.
274-
- **attributes** _(object, optional)_: object representing where the block attributes should be sourced from, according to the attributes shape defined by the [block configuration object](./block-registration.md). If a particular attribute contains a `shortcode` key, it should be a function that receives the shortcode attributes as the first arguments and the [WPShortcodeMatch](/packages/shortcode/README.md#next) as second, and returns a value for the attribute that will be sourced in the block's comment.
274+
- **attributes** _(object, optional)_: object representing where the block attributes should be sourced from, according to the attributes shape defined by the [block configuration object](/docs/reference-guides/block-api/block-registration.md). If a particular attribute contains a `shortcode` key, it should be a function that receives the shortcode attributes as the first arguments and the [WPShortcodeMatch](/packages/shortcode/README.md#next) as second, and returns a value for the attribute that will be sourced in the block's comment.
275275
- **isMatch** _(function, optional)_: a callback that receives the shortcode attributes per the [Shortcode API](https://codex.wordpress.org/Shortcode_API) and should return a boolean. Returning `false` from this function will prevent the shortcode to be transformed into this block.
276276
- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
277277

docs/reference-guides/interactivity-api/core-concepts/server-side-rendering.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ store( 'myFruitPlugin', {
175175

176176
The derived state, regardless of whether it derives from the global state, local context, or both, can also be processed on the server by the Server Directive Processing.
177177

178-
_Please, visit the [Understanding global state, local context and derived state](./undestanding-global-state-local-context-and-derived-state.md) guide to learn more about how derived state works in the Interactivity API._
178+
_Please, visit the [Understanding global state, local context and derived state](/docs/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state.md) guide to learn more about how derived state works in the Interactivity API._
179179

180180
### Derived state that can be defined statically
181181

docs/reference-guides/interactivity-api/core-concepts/using-typescript.md

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ That's it! Now you can access the context properties with the correct types.
269269

270270
The derived state is data that is calculated based on the global state or local context. In the client store definition, it is defined using a getter in the `state` object.
271271

272-
_Please, visit the [Understanding global state, local context and derived state](./undestanding-global-state-local-context-and-derived-state.md) guide to learn more about how derived state works in the Interactivity API._
272+
_Please, visit the [Understanding global state, local context and derived state](/docs/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state.md) guide to learn more about how derived state works in the Interactivity API._
273273

274274
Following our previous example, let's create a derived state that is the double of our counter.
275275

@@ -471,39 +471,84 @@ type Store = {
471471
};
472472
```
473473

474-
There's something to keep in mind when when using asynchronous actions. Just like with the derived state, if the asynchronous action needs to return a value and this value directly depends on some part of the global state, TypeScript will not be able to infer the type due to a circular reference.
474+
There's something to keep in mind when using asynchronous actions. Just like with the derived state, if an asynchronous action uses `state` within a `yield` expression (for example, by passing `state` to an async function that is then yielded) or if its return value depends on `state`, TypeScript might not be able to infer the types correctly due to a potential circular reference.
475475

476476
```ts
477477
const { state, actions } = store( 'myCounterPlugin', {
478478
state: {
479479
counter: 0,
480480
},
481481
actions: {
482-
*delayedReturn() {
483-
yield new Promise( ( r ) => setTimeout( r, 1000 ) );
484-
return state.counter; // TypeScript can't infer this return type.
482+
*delayedOperation() {
483+
// Example: state.counter is used as part of the yielded logic.
484+
yield fetchCounterData( state.counter );
485+
486+
// And/or the final return value depends on state.
487+
return state.counter + 1;
485488
},
486489
},
487490
} );
488491
```
489492

490-
In this case, just as we did with the derived state, we must manually type the return value of the generator.
493+
In such cases, TypeScript might issue a warning about a circular reference or default to `any`. To solve this, you need to manually type the generator function. The Interactivity API provides a helper type, `AsyncAction<ReturnType>`, for this purpose.
491494

492495
```ts
496+
import { store, type AsyncAction } from '@wordpress/interactivity';
497+
493498
const { state, actions } = store( 'myCounterPlugin', {
494499
state: {
495500
counter: 0,
496501
},
497502
actions: {
498-
*delayedReturn(): Generator< unknown, number, unknown > {
499-
yield new Promise( ( r ) => setTimeout( r, 1000 ) );
500-
return state.counter; // Now this is correctly inferred.
503+
*delayedOperation(): AsyncAction< number > {
504+
// Now, this doesn't cause a circular reference.
505+
yield fetchCounterData( state.counter );
506+
507+
// Now, this is correctly typed.
508+
return state.counter + 1;
509+
},
510+
},
511+
} );
512+
```
513+
514+
That's it! The `AsyncAction<ReturnType>` helper is defined as `Generator<any, ReturnType, unknown>`. By using `any` for the type of values yielded by the generator, it helps break the circular reference, allowing TypeScript to correctly infer the types when `state` is involved in `yield` expressions or in the final return value. You only need to specify the final `ReturnType` of your asynchronous action.
515+
516+
### Typing yielded values in asynchronous actions
517+
518+
While `AsyncAction<ReturnType>` types the overall generator and its final return value, the value resolved by an individual `yield` expression within that generator might still be typed as `any`.
519+
520+
If you need to ensure the correct type for a value that a `yield` expression resolves to (e.g., the result of a `fetch` call or another async operation), you can use the `TypeYield<T>` helper. This helper takes the type of the asynchronous function/operation being yielded (`T`) and resolves to the type of the value that the promise fulfills with.
521+
522+
Suppose `fetchCounterData` returns a promise that resolves to an object:
523+
524+
```ts
525+
import { store, type AsyncAction, type TypeYield } from '@wordpress/interactivity';
526+
527+
// Assume this function is defined elsewhere and fetches specific data.
528+
const fetchCounterData = async ( counterValue: number ): Promise< { current: number, next: number } > => {
529+
// internal logic...
530+
};
531+
532+
const { state, actions } = store( 'myCounterPlugin', {
533+
state: {
534+
counter: 0,
535+
},
536+
actions: {
537+
*loadCounterData(): AsyncAction< void > {
538+
// Use TypeYield to correctly type the resolved value of the yield.
539+
const data = ( yield fetchCounterData( state.counter ) ) as TypeYield< typeof fetchCounterData >;
540+
541+
// Now, `data` is correctly typed as { current: number, next: number }.
542+
console.log( data.current, data.next );
543+
544+
// Update state based on the fetched data.
545+
state.counter = data.next;
501546
},
502547
},
503548
} );
504549
```
505550

506-
That's it! Remember that the return type of a Generator is the second generic argument: `Generator< unknown, ReturnType, unknown >`.
551+
In this example, `( yield fetchCounterData( state.counter ) ) as TypeYield< typeof fetchCounterData >` ensures that the `data` constant is correctly typed as `{ current: number, next: number }`, matching the return type of `fetchCounterData`. This allows you to confidently access properties like `data.current` and `data.next` with type safety.
507552

508553
## Typing stores that are divided into multiple parts
509554

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
/**
3+
* Interactivity API: Experimental full-page client-side navigation.
4+
*
5+
* @package Gutenberg
6+
* @subpackage Interactivity API
7+
*/
8+
9+
if ( ! class_exists( 'Gutenberg_Interactivity_API_Full_Page_Navigation' ) ) {
10+
11+
/**
12+
* Class Gutenberg_Interactivity_API_Full_Page_Navigation.
13+
*/
14+
class Gutenberg_Interactivity_API_Full_Page_Navigation {
15+
16+
private static $instance = null;
17+
18+
public static function instance() {
19+
if ( null === self::$instance ) {
20+
self::$instance = new Gutenberg_Interactivity_API_Full_Page_Navigation();
21+
}
22+
return self::$instance;
23+
}
24+
25+
public function __construct() {
26+
add_action( 'wp_head', array( $this, 'buffer_start' ) );
27+
add_action( 'wp_footer', array( $this, 'buffer_end' ), 8 );
28+
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_script_modules' ) );
29+
}
30+
31+
/**
32+
* Enqueues the required script modules.
33+
*/
34+
public function enqueue_script_modules() {
35+
wp_enqueue_script_module(
36+
'@wordpress/interactivity-router/full-page'
37+
);
38+
}
39+
40+
/**
41+
* Starts output buffering at the end of the 'wp_head' action, adding the
42+
* required directives for client-side navigation to the BODY tags when the
43+
* buffer is flushed.
44+
*/
45+
public function buffer_start() {
46+
ob_start( array( $this, 'add_directives_to_body' ) );
47+
}
48+
49+
/**
50+
* Flushes the output buffer at the end of the 'wp_footer' action.
51+
*/
52+
public function buffer_end() {
53+
ob_end_flush();
54+
}
55+
56+
/**
57+
* Adds client-side navigation directives to BODY tag in the passed output
58+
* buffer.
59+
*
60+
* Note: This should probably be done per site, not by default when this option
61+
* is enabled.
62+
*
63+
* @param string $buffer Passed output buffer.
64+
*
65+
* @return string The same HTML with modified BODY attributes.
66+
*/
67+
public function add_directives_to_body( $buffer ) {
68+
$p = new WP_HTML_Tag_Processor( $buffer );
69+
if ( $p->next_tag( array( 'tag_name' => 'BODY' ) ) ) {
70+
$p->set_attribute( 'data-wp-interactive', true );
71+
$p->set_attribute( 'data-wp-router-region', 'core/body' );
72+
return $p->get_updated_html();
73+
} else {
74+
return $buffer;
75+
}
76+
}
77+
}
78+
}

lib/experiments-page.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,18 @@ function gutenberg_initialize_experiments_settings() {
187187
)
188188
);
189189

190+
add_settings_field(
191+
'gutenberg-full-page-client-side-navigation',
192+
__( 'Interactivity API: Full-page client-side navigation', 'gutenberg' ),
193+
'gutenberg_display_experiment_field',
194+
'gutenberg-experiments',
195+
'gutenberg_experiments_section',
196+
array(
197+
'label' => __( 'Enables full-page client-side navigation, powered by the Interactivity API.', 'gutenberg' ),
198+
'id' => 'gutenberg-full-page-client-side-navigation',
199+
)
200+
);
201+
190202
register_setting(
191203
'gutenberg-experiments',
192204
'gutenberg-experiments'

lib/load.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,9 @@ function gutenberg_is_experiment_enabled( $name ) {
156156
if ( gutenberg_is_experiment_enabled( 'gutenberg-media-processing' ) ) {
157157
require_once __DIR__ . '/experimental/media/load.php';
158158
}
159+
160+
// Interactivity API full-page client-side navigation.
161+
if ( gutenberg_is_experiment_enabled( 'gutenberg-full-page-client-side-navigation' ) ) {
162+
require __DIR__ . '/experimental/interactivity-api/class-gutenberg-interactivity-api-full-page-navigation.php';
163+
Gutenberg_Interactivity_API_Full_Page_Navigation::instance();
164+
}

package-lock.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/block-library/src/embed/variations.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,6 @@ const variations = [
251251
patterns: [ /^https?:\/\/(www\.)?reverbnation\.com\/.+/i ],
252252
attributes: { providerNameSlug: 'reverbnation', responsive: true },
253253
},
254-
{
255-
name: 'screencast',
256-
title: getTitle( 'Screencast' ),
257-
icon: embedVideoIcon,
258-
description: __( 'Embed Screencast content.' ),
259-
patterns: [ /^https?:\/\/(www\.)?screencast\.com\/.+/i ],
260-
attributes: { providerNameSlug: 'screencast', responsive: true },
261-
},
262254
{
263255
name: 'scribd',
264256
title: getTitle( 'Scribd' ),

packages/block-library/src/image/index.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ function block_core_image_render_lightbox( $block_content, $block ) {
204204
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
205205
)
206206
);
207+
$p->set_attribute( 'data-wp-key', $unique_image_id );
207208

208209
// Image.
209210
$p->next_tag( 'img' );
@@ -278,12 +279,14 @@ function block_core_image_print_lightbox_overlay() {
278279
<div
279280
class="wp-lightbox-overlay zoom"
280281
data-wp-interactive="core/image"
282+
data-wp-router-region='{ "id": "core/image-overlay", "attachTo": "body" }'
283+
data-wp-key="wp-lightbox-overlay"
281284
data-wp-context='{}'
282285
data-wp-bind--role="state.roleAttribute"
283286
data-wp-bind--aria-label="state.currentImage.ariaLabel"
284287
data-wp-bind--aria-modal="state.ariaModal"
285288
data-wp-class--active="state.overlayEnabled"
286-
data-wp-class--show-closing-animation="state.showClosingAnimation"
289+
data-wp-class--show-closing-animation="state.overlayOpened"
287290
data-wp-watch="callbacks.setOverlayFocus"
288291
data-wp-on--keydown="actions.handleKeydown"
289292
data-wp-on-async--touchstart="actions.handleTouchStart"

0 commit comments

Comments
 (0)