Changeset 61469
- Timestamp:
- 01/11/2026 06:34:57 AM (5 weeks ago)
- Location:
- trunk
- Files:
-
- 1 added
- 6 edited
-
src/wp-includes/block-template.php (modified) (1 diff)
-
src/wp-includes/css/wp-block-template-skip-link.css (added)
-
src/wp-includes/script-loader.php (modified) (2 diffs)
-
src/wp-includes/theme-templates.php (modified) (2 diffs)
-
tests/phpunit/tests/block-template-utils.php (modified) (1 diff)
-
tests/phpunit/tests/block-template.php (modified) (1 diff)
-
tests/phpunit/tests/template.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/block-template.php
r61431 r61469 302 302 // Wrap block template in .wp-site-blocks to allow for specific descendant styles 303 303 // (e.g. `.wp-site-blocks > *`). 304 return '<div class="wp-site-blocks">' . $content . '</div>'; 304 $template_html = '<div class="wp-site-blocks">' . $content . '</div>'; 305 306 // Back-compat for plugins that disable functionality by unhooking one of these actions. 307 if ( 308 ! has_action( 'wp_footer', 'the_block_template_skip_link' ) || 309 ! has_action( 'wp_enqueue_scripts', 'wp_enqueue_block_template_skip_link' ) 310 ) { 311 return $template_html; 312 } 313 314 return _block_template_add_skip_link( $template_html ); 315 } 316 317 /** 318 * Inserts the block template skip-link into the template HTML. 319 * 320 * When a `MAIN` element exists in the template, this function will ensure 321 * that the element contains an `id` attribute, and it will insert a link to 322 * that `MAIN` element before the first `DIV.wp-site-blocks` element, which 323 * is the wrapper for all blocks in a block template as constructed by 324 * {@see get_the_block_template_html()}. 325 * 326 * Example: 327 * 328 * // Input. 329 * <div class="wp-site-blocks"> 330 * <nav>...</nav> 331 * <main> 332 * <h2>... 333 * 334 * // Output. 335 * <a href="#wp--skip-link--target" id="wp-skip-link" class="..."> 336 * <div class="wp-site-blocks"> 337 * <nav>...</nav> 338 * <main id="wp--skip-link--target"> 339 * <h2>... 340 * 341 * When the `MAIN` element already contains a non-empty `id` value it will be 342 * used instead of the default skip-link id. 343 * 344 * @access private 345 * @since 7.0.0 346 * 347 * @param string $template_html Block template markup. 348 * @return string Modified markup with skip link when applicable. 349 */ 350 function _block_template_add_skip_link( string $template_html ): string { 351 // Anonymous subclass of WP_HTML_Tag_Processor to access protected bookmark spans. 352 $processor = new class( $template_html ) extends WP_HTML_Tag_Processor { 353 /** 354 * Inserts text before the current token. 355 * 356 * @param string $text Text to insert. 357 */ 358 public function insert_before( string $text ) { 359 $this->set_bookmark( 'here' ); 360 $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->bookmarks['here']->start, 0, $text ); 361 } 362 }; 363 364 // Find and bookmark the first DIV.wp-site-blocks. 365 if ( 366 ! $processor->next_tag( 367 array( 368 'tag_name' => 'DIV', 369 'class_name' => 'wp-site-blocks', 370 ) 371 ) 372 ) { 373 return $template_html; 374 } 375 $processor->set_bookmark( 'skip_link_insertion_point' ); 376 377 // Ensure the MAIN element has an ID. 378 if ( ! $processor->next_tag( 'MAIN' ) ) { 379 return $template_html; 380 } 381 382 $skip_link_target_id = $processor->get_attribute( 'id' ); 383 if ( ! is_string( $skip_link_target_id ) || '' === $skip_link_target_id ) { 384 $skip_link_target_id = 'wp--skip-link--target'; 385 $processor->set_attribute( 'id', $skip_link_target_id ); 386 } 387 388 // Seek back to the bookmarked insertion point. 389 $processor->seek( 'skip_link_insertion_point' ); 390 391 $skip_link = sprintf( 392 '<a class="skip-link screen-reader-text" id="wp-skip-link" href="%s">%s</a>', 393 esc_url( '#' . $skip_link_target_id ), 394 /* translators: Hidden accessibility text. */ 395 esc_html__( 'Skip to content' ) 396 ); 397 $processor->insert_before( $skip_link ); 398 399 return $processor->get_updated_html(); 305 400 } 306 401 -
trunk/src/wp-includes/script-loader.php
r61442 r61469 1606 1606 $styles->add( 'customize-preview', "/wp-includes/css/customize-preview$suffix.css", array( 'dashicons' ) ); 1607 1607 $styles->add( 'wp-empty-template-alert', "/wp-includes/css/wp-empty-template-alert$suffix.css" ); 1608 $skip_link_style_path = WPINC . "/css/wp-block-template-skip-link$suffix.css"; 1609 $styles->add( 'wp-block-template-skip-link', "/$skip_link_style_path" ); 1610 $styles->add_data( 'wp-block-template-skip-link', 'path', ABSPATH . $skip_link_style_path ); 1608 1611 1609 1612 // External libraries and friends. … … 1801 1804 'wp-pointer', 1802 1805 'wp-jquery-ui-dialog', 1806 'wp-block-template-skip-link', 1803 1807 // Package styles. 1804 1808 'wp-reset-editor-styles', -
trunk/src/wp-includes/theme-templates.php
r61178 r61469 100 100 101 101 /** 102 * Enqueues the skip-link s cript & styles.102 * Enqueues the skip-link styles. 103 103 * 104 104 * @access private 105 105 * @since 6.4.0 106 * @since 7.0.0 A script is no longer printed in favor of being added via {@see _block_template_add_skip_link()}. 106 107 * 107 108 * @global string $_wp_current_template_content … … 126 127 } 127 128 128 $skip_link_styles = ' 129 .skip-link.screen-reader-text { 130 border: 0; 131 clip-path: inset(50%); 132 height: 1px; 133 margin: -1px; 134 overflow: hidden; 135 padding: 0; 136 position: absolute !important; 137 width: 1px; 138 word-wrap: normal !important; 139 } 140 141 .skip-link.screen-reader-text:focus { 142 background-color: #eee; 143 clip-path: none; 144 color: #444; 145 display: block; 146 font-size: 1em; 147 height: auto; 148 left: 5px; 149 line-height: normal; 150 padding: 15px 23px 14px; 151 text-decoration: none; 152 top: 5px; 153 width: auto; 154 z-index: 100000; 155 }'; 156 157 $handle = 'wp-block-template-skip-link'; 158 159 /** 160 * Print the skip-link styles. 161 */ 162 wp_register_style( $handle, false ); 163 wp_add_inline_style( $handle, $skip_link_styles ); 164 wp_enqueue_style( $handle ); 165 166 /** 167 * Enqueue the skip-link script. 168 */ 169 ob_start(); 170 ?> 171 <script> 172 ( function() { 173 var skipLinkTarget = document.querySelector( 'main' ), 174 sibling, 175 skipLinkTargetID, 176 skipLink; 177 178 // Early exit if a skip-link target can't be located. 179 if ( ! skipLinkTarget ) { 180 return; 181 } 182 183 /* 184 * Get the site wrapper. 185 * The skip-link will be injected in the beginning of it. 186 */ 187 sibling = document.querySelector( '.wp-site-blocks' ); 188 189 // Early exit if the root element was not found. 190 if ( ! sibling ) { 191 return; 192 } 193 194 // Get the skip-link target's ID, and generate one if it doesn't exist. 195 skipLinkTargetID = skipLinkTarget.id; 196 if ( ! skipLinkTargetID ) { 197 skipLinkTargetID = 'wp--skip-link--target'; 198 skipLinkTarget.id = skipLinkTargetID; 199 } 200 201 // Create the skip link. 202 skipLink = document.createElement( 'a' ); 203 skipLink.classList.add( 'skip-link', 'screen-reader-text' ); 204 skipLink.id = 'wp-skip-link'; 205 skipLink.href = '#' + skipLinkTargetID; 206 skipLink.innerText = '<?php /* translators: Hidden accessibility text. Do not use HTML entities ( , etc.). */ esc_html_e( 'Skip to content' ); ?>'; 207 208 // Inject the skip link. 209 sibling.parentElement.insertBefore( skipLink, sibling ); 210 }() ); 211 </script> 212 <?php 213 $skip_link_script = wp_remove_surrounding_empty_script_tags( ob_get_clean() ); 214 $script_handle = 'wp-block-template-skip-link'; 215 wp_register_script( $script_handle, false, array(), false, array( 'in_footer' => true ) ); 216 wp_add_inline_script( $script_handle, $skip_link_script ); 217 wp_enqueue_script( $script_handle ); 129 wp_enqueue_style( 'wp-block-template-skip-link' ); 218 130 } 219 131 -
trunk/tests/phpunit/tests/block-template-utils.php
r58235 r61469 299 299 300 300 /** 301 * Tests that a skip link is added and a MAIN element without an ID receives the default ID. 302 * 303 * @ticket 64361 304 * 305 * @covers ::_block_template_add_skip_link 306 */ 307 public function test_block_template_add_skip_link_inserts_link_and_adds_main_id_when_missing() { 308 $template_html = '<div class="wp-site-blocks"><main>Content</main></div>'; 309 $expected = ' 310 <a class="skip-link screen-reader-text" id="wp-skip-link" href="#wp--skip-link--target">Skip to content</a> 311 <div class="wp-site-blocks"><main id="wp--skip-link--target">Content</main></div> 312 '; 313 314 $this->assertEqualHTML( $expected, _block_template_add_skip_link( $template_html ) ); 315 } 316 317 /** 318 * Tests that an existing MAIN ID is reused for the skip link. 319 * 320 * @ticket 64361 321 * 322 * @covers ::_block_template_add_skip_link 323 */ 324 public function test_block_template_add_skip_link_uses_existing_main_id() { 325 $template_html = '<div class="wp-site-blocks"><main id="custom-id">Content</main></div>'; 326 $expected = ' 327 <a class="skip-link screen-reader-text" id="wp-skip-link" href="#custom-id">Skip to content</a> 328 <div class="wp-site-blocks"><main id="custom-id">Content</main></div> 329 '; 330 331 $this->assertEqualHTML( $expected, _block_template_add_skip_link( $template_html ) ); 332 } 333 334 /** 335 * Tests that a boolean MAIN ID is treated as missing and replaced with the default. 336 * 337 * @ticket 64361 338 * 339 * @covers ::_block_template_add_skip_link 340 */ 341 public function test_block_template_add_skip_link_handles_boolean_main_id() { 342 $template_html = '<div class="wp-site-blocks"><main id>Content</main></div>'; 343 $expected = ' 344 <a class="skip-link screen-reader-text" id="wp-skip-link" href="#wp--skip-link--target">Skip to content</a> 345 <div class="wp-site-blocks"><main id="wp--skip-link--target">Content</main></div> 346 '; 347 348 $this->assertEqualHTML( $expected, _block_template_add_skip_link( $template_html ) ); 349 } 350 351 /** 352 * Tests that a MAIN ID containing whitespace is preserved and used for the skip link. 353 * 354 * @ticket 64361 355 * 356 * @covers ::_block_template_add_skip_link 357 */ 358 public function test_block_template_add_skip_link_preserves_whitespace_main_id() { 359 $template_html = '<div class="wp-site-blocks"><main id=" my-id ">Content</main></div>'; 360 $expected = ' 361 <a class="skip-link screen-reader-text" id="wp-skip-link" href="#%20my-id%20">Skip to content</a> 362 <div class="wp-site-blocks"><main id=" my-id ">Content</main></div> 363 '; 364 365 $this->assertEqualHTML( $expected, _block_template_add_skip_link( $template_html ) ); 366 } 367 368 /** 369 * Tests that no changes are made when there is no MAIN element. 370 * 371 * @ticket 64361 372 * 373 * @covers ::_block_template_add_skip_link 374 */ 375 public function test_block_template_add_skip_link_does_not_modify_when_main_missing() { 376 $template_html = '<div class="wp-site-blocks"><div>Content</div></div>'; 377 378 $this->assertSame( $template_html, _block_template_add_skip_link( $template_html ) ); 379 } 380 381 /** 301 382 * Should retrieve the template from the theme files. 302 383 */ -
trunk/tests/phpunit/tests/block-template.php
r61090 r61469 313 313 314 314 /** 315 * Tests that `get_the_block_template_html()` adds a skip link when a MAIN element is present. 316 * 317 * @ticket 64361 318 * @covers ::get_the_block_template_html 319 */ 320 public function test_get_the_block_template_html_adds_skip_link_when_main_present() { 321 global $_wp_current_template_id, $_wp_current_template_content; 322 323 $_wp_current_template_id = get_stylesheet() . '//index'; 324 $_wp_current_template_content = '<main>Content</main>'; 325 326 $processor = new WP_HTML_Tag_Processor( get_the_block_template_html() ); 327 $this->assertTrue( 328 $processor->next_tag( 329 array( 330 'tag_name' => 'A', 331 'class_name' => 'skip-link', 332 ) 333 ), 334 'Expected skip link was not added to the block template HTML.' 335 ); 336 $this->assertSame( 'wp-skip-link', $processor->get_attribute( 'id' ), 'Unexpected ID on skip link.' ); 337 $this->assertTrue( $processor->has_class( 'screen-reader-text' ), 'Expected "screen-reader-text" class on skip link.' ); 338 } 339 340 /** 341 * Tests that `get_the_block_template_html()` does not add a skip link when the skip-link action is unhooked. 342 * 343 * @ticket 64361 344 * @covers ::get_the_block_template_html 345 * 346 * @dataProvider data_provider_skip_link_actions 347 */ 348 public function test_get_the_block_template_html_does_not_add_skip_link_when_action_unhooked( string $action, string $callback ) { 349 global $_wp_current_template_id, $_wp_current_template_content; 350 351 $_wp_current_template_id = get_stylesheet() . '//index'; 352 $_wp_current_template_content = '<main>Content</main>'; 353 354 remove_action( $action, $callback ); 355 356 $processor = new WP_HTML_Tag_Processor( get_the_block_template_html() ); 357 $this->assertFalse( 358 $processor->next_tag( 359 array( 360 'tag_name' => 'A', 361 'class_name' => 'skip-link', 362 ) 363 ), 364 'Unexpected skip link was added to the block template HTML when the action was unhooked.' 365 ); 366 } 367 368 /** 369 * Data provider for test_get_the_block_template_html_does_not_add_skip_link_when_action_unhooked. 370 * 371 * @return array<string, array<string, string>> 372 */ 373 public function data_provider_skip_link_actions(): array { 374 return array( 375 'the_block_template_skip_link' => array( 376 'action' => 'wp_footer', 377 'callback' => 'the_block_template_skip_link', 378 ), 379 'wp_enqueue_block_template_skip_link' => array( 380 'action' => 'wp_enqueue_scripts', 381 'callback' => 'wp_enqueue_block_template_skip_link', 382 ), 383 ); 384 } 385 386 /** 315 387 * @ticket 58319 316 388 * -
trunk/tests/phpunit/tests/template.php
r61416 r61469 1798 1798 'core-block-supports-duotone-inline-css', 1799 1799 'wp-block-library-theme-css', 1800 'wp-block-template-skip-link-css', 1800 1801 'wp-block-template-skip-link-inline-css', 1801 1802 );
Note: See TracChangeset
for help on using the changeset viewer.