jetpack
/
jetpack_vendor
/
automattic
/
jetpack-wp-build-polyfills
/
src
/
class-wp-build-admin-frame.php
class-wp-build-admin-frame.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-wp-build-polyfills/src/class-wp-build-admin-frame.php
| 1 | <?php |
| 2 | /** |
| 3 | * Frame fixes for the `@wordpress/boot` single-page layout in wp-admin. |
| 4 | * |
| 5 | * @package automattic/jetpack-wp-build-polyfills |
| 6 | */ |
| 7 | |
| 8 | namespace Automattic\Jetpack\WP_Build_Polyfills; |
| 9 | |
| 10 | /** |
| 11 | * Reconciles the boot single-page layout with the wp-admin frame: the backdrop |
| 12 | * continues the admin menu color, and the app keeps its own scroller when the |
| 13 | * admin menu is taller than the viewport. |
| 14 | * |
| 15 | * `@wordpress/admin-ui` derives the backdrop from a fixed map of Core color |
| 16 | * schemes and falls back to a near-black `modern` seed for any other scheme, |
| 17 | * WordPress.com and third-party ones included. On WordPress 7.0+ the boot |
| 18 | * module that runs is Core's bundled copy, so the override is applied from |
| 19 | * PHP around the page: a stylesheet printed on `admin_head` and a script |
| 20 | * printed on `in_admin_header` that samples the rendered menu background into |
| 21 | * a custom property on the root element. |
| 22 | * |
| 23 | * The frame is also held steady: painted before boot mounts, and captured whole |
| 24 | * by the cross-document view transitions Core enables in wp-admin. |
| 25 | * |
| 26 | * Pages without a boot mount container are unaffected: the selectors match nothing. |
| 27 | */ |
| 28 | class WP_Build_Admin_Frame { |
| 29 | |
| 30 | /** |
| 31 | * Hook the stylesheet, the render hold and the frame script. Safe to call repeatedly. |
| 32 | * |
| 33 | * @return void |
| 34 | */ |
| 35 | public static function register() { |
| 36 | add_action( 'admin_head', array( self::class, 'print_styles' ) ); |
| 37 | add_action( 'admin_head', array( self::class, 'print_render_hold' ) ); |
| 38 | add_action( 'in_admin_header', array( self::class, 'print_script' ) ); |
| 39 | } |
| 40 | |
| 41 | /** |
| 42 | * Print the backdrop and scroll-containment overrides. |
| 43 | * |
| 44 | * The backdrop selectors are (1,1,0), so they outrank the layout rule boot injects |
| 45 | * at runtime. The fallbacks keep boot's own colors when the sampling script |
| 46 | * did not run. |
| 47 | * |
| 48 | * @return void |
| 49 | */ |
| 50 | public static function print_styles() { |
| 51 | // The layout root is `.boot-layout--single-page` up to boot 0.20. From |
| 52 | // boot 0.21 (WordPress/gutenberg#81756, Gutenberg 23.9) the styles are |
| 53 | // CSS Modules and the class is `_<hash>__layout-single-page`, so the |
| 54 | // attribute selector matches the local name whatever the hash is. |
| 55 | ?> |
| 56 | <style id="wp-build-admin-frame-css"> |
| 57 | #wpcontent .boot-layout--single-page, |
| 58 | #wpcontent [class*="__layout-single-page"] { |
| 59 | background: var(--wp-build-admin-menu-background, var(--wpds-color-background-surface-neutral-weak)); |
| 60 | } |
| 61 | body:has(.boot-layout--single-page), |
| 62 | body:has([class*="__layout-single-page"]) { |
| 63 | background: var(--wp-build-admin-menu-background, #fff); |
| 64 | } |
| 65 | |
| 66 | /* Old-only boot surfaces zoom or slide over the next page, so let the root snapshot carry them. */ |
| 67 | html.wp-build-admin-frame-leaving :is(.boot-layout--single-page, [class*="__layout-single-page"]) :is([class*="__stage"], [class*="__inspector"], [class*="__canvas"], .interface-interface-skeleton__header, .interface-interface-skeleton__sidebar) { |
| 68 | view-transition-name: none !important; |
| 69 | } |
| 70 | |
| 71 | /* |
| 72 | * Boot's layout is absolutely positioned against `#wpbody`, which grows |
| 73 | * with the admin menu: a menu taller than the viewport stretches the app |
| 74 | * and scrolls its sticky header away, so cap `#wpbody` to the viewport to |
| 75 | * give the app back its own scroller. Below 783px the menu is off-canvas |
| 76 | * and the template scrolls `#wpwrap` instead. Drop this once the minimum |
| 77 | * supported WordPress bundles a boot that pins its own layout |
| 78 | * (WordPress/gutenberg#82114). |
| 79 | */ |
| 80 | @media (min-width: 783px) { |
| 81 | body.js:has([id$="-wp-admin-app"]:empty) #wpbody, |
| 82 | body:has(.boot-layout--single-page) #wpbody, |
| 83 | body:has([class*="__layout-single-page"]) #wpbody { |
| 84 | position: sticky; |
| 85 | top: var(--wp-admin--admin-bar--height, 32px); |
| 86 | height: calc(100vh - var(--wp-admin--admin-bar--height, 32px)); |
| 87 | } |
| 88 | |
| 89 | /* |
| 90 | * Paint boot's stage on the empty app container until it mounts, where the |
| 91 | * page template's critical CSS would otherwise leave the area white. Plain |
| 92 | * #fff: boot's theme provider whitens the stage, unlike the root surface token. |
| 93 | * A container that never mounts keeps the panel and passes for an empty app. |
| 94 | */ |
| 95 | body.js:has([id$="-wp-admin-app"]:empty) { |
| 96 | background: var(--wp-build-admin-menu-background, #fff); |
| 97 | } |
| 98 | body.js [id$="-wp-admin-app"]:empty { |
| 99 | position: absolute; |
| 100 | inset-block: 0 8px; |
| 101 | inset-inline: 0 8px; |
| 102 | border-radius: var(--wpds-border-radius-xl, 12px); |
| 103 | background: #fff; |
| 104 | } |
| 105 | } |
| 106 | </style> |
| 107 | <?php |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Hold the first render of a wp-build page until the document has parsed, so Core's |
| 112 | * cross-document view transitions capture it with its admin menu. |
| 113 | * |
| 114 | * Held on every WordPress 7.0+ wp-build page, reduced motion included: PHP cannot see the media |
| 115 | * query Core gates the transition on. Never a module: Firefox then drops wp-admin's footer import map. |
| 116 | * |
| 117 | * @return void |
| 118 | */ |
| 119 | public static function print_render_hold() { |
| 120 | if ( |
| 121 | ! wp_style_is( 'wp-view-transitions-admin', 'enqueued' ) |
| 122 | || ! preg_grep( '/-wp-admin-prerequisites$/', wp_scripts()->queue ) |
| 123 | ) { |
| 124 | return; |
| 125 | } |
| 126 | |
| 127 | // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- Not enqueueable: the script APIs pass `src` through esc_url(), which strips data: URLs. |
| 128 | echo '<script id="wp-build-admin-frame-render-hold" src="data:text/javascript," defer blocking="render"></script>' . "\n"; |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * Print the script that samples the admin menu background and marks the root |
| 133 | * while the page leaves for a cross-document view transition. |
| 134 | * |
| 135 | * Runs right after `#adminmenuback` is printed and before the layout |
| 136 | * mounts, which is why the property goes on the root element. |
| 137 | * |
| 138 | * @return void |
| 139 | */ |
| 140 | public static function print_script() { |
| 141 | $script = <<<'JS' |
| 142 | ( function () { |
| 143 | window.addEventListener( 'pageswap', function ( event ) { |
| 144 | if ( event.viewTransition ) { |
| 145 | document.documentElement.classList.add( 'wp-build-admin-frame-leaving' ); |
| 146 | } |
| 147 | } ); |
| 148 | // A page restored from the back/forward cache reveals again with the class still set. |
| 149 | window.addEventListener( 'pagereveal', function () { |
| 150 | document.documentElement.classList.remove( 'wp-build-admin-frame-leaving' ); |
| 151 | } ); |
| 152 | |
| 153 | var menu = document.getElementById( 'adminmenuback' ); |
| 154 | if ( ! menu ) { |
| 155 | return; |
| 156 | } |
| 157 | var color = window.getComputedStyle( menu ).backgroundColor; |
| 158 | if ( ! color || 'rgba(0, 0, 0, 0)' === color || 'transparent' === color ) { |
| 159 | return; |
| 160 | } |
| 161 | document.documentElement.style.setProperty( '--wp-build-admin-menu-background', color ); |
| 162 | } )(); |
| 163 | JS; |
| 164 | |
| 165 | wp_print_inline_script_tag( $script, array( 'id' => 'wp-build-admin-frame-js' ) ); |
| 166 | } |
| 167 | } |
| 168 |