Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/wp-admin/css/view-transitions.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,40 @@
navigation: auto;
}

/*
* Give each top-level menu item its own view transition name — its id,
* such as "menu-posts" — so each one is captured as an independent group
* and animates from its old position and size to its new ones across a
* navigation. This produces the accordion effect: the previously open
* section collapses as the newly current one expands, instead of the
* whole menu simply cross-fading. attr() falls back to "none" for any
* item whose id is missing or is not a valid custom identifier. The
* shared wp-menu-top class lets the image-pair rule below address every
* menu-top group at once.
*/
#adminmenu > .menu-top {
view-transition-name: attr(id type(<custom-ident>), none);
view-transition-class: wp-menu-top;
}

/*
* Name the collapse button too, so it slides into place with the menu
* items instead of cross-fading as part of the page root.
*/
#collapse-menu {
view-transition-name: collapse-menu;
}

/*
* Clip each menu-top's snapshot to its animating group box. By default a
* captured snapshot keeps its full intrinsic height, so the new (expanded)
* submenu paints at full height from the first frame and overlaps the
* items below while they are still sliding down. Clipping the image pair
* to the group — whose height animates from the link row to the full
* expanded height — makes the submenu wipe open, and closed in reverse,
* in step with those items.
*/
::view-transition-image-pair(.wp-menu-top) {
overflow: clip;
}
}
1 change: 1 addition & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
add_action( 'admin_enqueue_scripts', 'wp_common_block_scripts_and_styles' );
add_action( 'admin_enqueue_scripts', 'wp_enqueue_command_palette_assets' );
add_action( 'admin_enqueue_scripts', 'wp_enqueue_view_transitions_admin_css' );
add_action( 'admin_head', 'wp_print_view_transitions_render_blocking_link' );
add_action( 'enqueue_block_assets', 'wp_enqueue_classic_theme_styles' );
add_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' );
add_action( 'enqueue_block_assets', 'enqueue_block_styles_assets', 30 );
Expand Down
23 changes: 22 additions & 1 deletion src/wp-includes/view-transitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,26 @@ function wp_enqueue_view_transitions_admin_css(): void {
function wp_get_view_transitions_admin_css(): string {
$affix = SCRIPT_DEBUG ? '' : '.min';
$path = ABSPATH . "wp-admin/css/view-transitions{$affix}.css";
return file_get_contents( $path );
return (string) file_get_contents( $path );
}

/**
* Prints a render-blocking expectation so View Transitions capture a fully parsed admin page.
*
* A cross-document view transition snapshots the incoming page at its first render, which can
* happen before the `<body>` has finished parsing. The snapshot would then capture a partially
* built page — for example an incomplete admin menu — and the transition animates to or from
* the wrong state.
*
* The `expect` link blocks the first render until an element near the end of the body is
* parsed, so the snapshot is always taken of a complete page. The media query limits the
* (small) first-render delay to when view transitions actually run, matching the
* `@view-transition` rule in view-transitions.css.
*
* @since 7.0.1

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @since 7.0.1
* @since 7.2.0

*
* @link https://html.spec.whatwg.org/multipage/links.html#link-type-expect
*/
function wp_print_view_transitions_render_blocking_link(): void {
echo '<link rel="expect" href="#wpfooter" blocking="render" media="(prefers-reduced-motion: no-preference)">' . "\n";
Comment thread
westonruter marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/**
* Tests for the wp_print_view_transitions_render_blocking_link() function.
*
* @package WordPress
* @subpackage View Transitions
*/

/**
* @group view-transitions
* @covers ::wp_print_view_transitions_render_blocking_link
*/
class Tests_View_Transitions_wpPrintViewTransitionsRenderBlockingLink extends WP_UnitTestCase {

/**
* Tests that the hook for printing the render-blocking link is set up.
*
* @ticket 65032
*/
public function test_hook(): void {
$this->assertSame( 10, has_action( 'admin_head', 'wp_print_view_transitions_render_blocking_link' ) );
}

/**
* Tests that a render-blocking expect link is printed.
*
* @ticket 65032
*/
public function test_prints_render_blocking_link(): void {
$output = get_echo( 'wp_print_view_transitions_render_blocking_link' );

$processor = new WP_HTML_Tag_Processor( $output );
$this->assertTrue( $processor->next_tag( 'LINK' ), 'Expected a LINK tag to be printed.' );
$this->assertSame( 'expect', $processor->get_attribute( 'rel' ), 'Expected the LINK to have a rel of "expect".' );
$this->assertSame( '#wpfooter', $processor->get_attribute( 'href' ), 'Expected the LINK to point at the footer.' );
$this->assertSame( 'render', $processor->get_attribute( 'blocking' ), 'Expected the LINK to block rendering.' );
$this->assertSame( '(prefers-reduced-motion: no-preference)', $processor->get_attribute( 'media' ), 'Expected the LINK to be scoped to the view transitions media query.' );
$this->assertFalse( $processor->next_tag(), 'Expected only a single tag to be printed.' );
}
}
Loading