diff --git a/src/wp-admin/css/view-transitions.css b/src/wp-admin/css/view-transitions.css index 538a90b502f9e..619a74be8c660 100644 --- a/src/wp-admin/css/view-transitions.css +++ b/src/wp-admin/css/view-transitions.css @@ -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(), 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; } } diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index a4ecf677e2af1..0f1110bfee892 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -603,6 +603,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 ); diff --git a/src/wp-includes/view-transitions.php b/src/wp-includes/view-transitions.php index 2d2ad61e0b431..784482dd48d36 100644 --- a/src/wp-includes/view-transitions.php +++ b/src/wp-includes/view-transitions.php @@ -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 `` 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 + * + * @link https://html.spec.whatwg.org/multipage/links.html#link-type-expect + */ +function wp_print_view_transitions_render_blocking_link(): void { + echo '' . "\n"; }