From 89f5e338f217e3ab67a35fb7f778f4af46f78079 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Fri, 6 Oct 2023 14:10:04 +0200 Subject: [PATCH] Fix Image block lightbox missing alt attribute and improve accessibility (#55010) * Move lightbox open button after the image. * Fix getting the lightbox image alt attribute. * Improve docblocks. * Do not render empty role attribute. * Remove unnecessary aria-hidden attribute. * Set aria-modal attribute dynamically. * More meaningful and simpler modal dialog aria-label. * Increase Close button target size. * Add enlarged image base64 encoded placeholder. * Better check for alt attribute as a string. * Update changelog. * Move changelog entry to the block library changelog. * Set lightbox dialog aria-label dynamically. * Hide background scrim container from assistive technology. * Remove obsolete code --------- Co-authored-by: Ricardo Artemio Morales # Conflicts: # packages/block-library/CHANGELOG.md --- lib/block-supports/behaviors.php | 49 +++++------ lib/load.php | 1 + packages/block-library/CHANGELOG.md | 7 ++ packages/block-library/src/image/index.php | 94 +++++++++++---------- packages/block-library/src/image/style.scss | 11 ++- packages/block-library/src/image/view.js | 18 +++- 6 files changed, 106 insertions(+), 74 deletions(-) diff --git a/lib/block-supports/behaviors.php b/lib/block-supports/behaviors.php index 6f442d7b0d2d7c..c1b3dceceee94d 100644 --- a/lib/block-supports/behaviors.php +++ b/lib/block-supports/behaviors.php @@ -84,17 +84,19 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { $aria_label = __( 'Enlarge image', 'gutenberg' ); + $processor->next_tag( 'img' ); $alt_attribute = $processor->get_attribute( 'alt' ); - if ( null !== $alt_attribute ) { + // An empty alt attribute `alt=""` is valid for decorative images. + if ( is_string( $alt_attribute ) ) { $alt_attribute = trim( $alt_attribute ); } + // It only makes sense to append the alt text to the button aria-label when the alt text is non-empty. if ( $alt_attribute ) { /* translators: %s: Image alt text. */ $aria_label = sprintf( __( 'Enlarge image: %s', 'gutenberg' ), $alt_attribute ); } - $content = $processor->get_updated_html(); // If we don't set a default, it won't work if Lightbox is set to enabled by default. $lightbox_animation = 'zoom'; @@ -102,17 +104,15 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { $lightbox_animation = $lightbox_settings['animation']; } - // We want to store the src in the context so we can set it dynamically when the lightbox is opened. - $z = new WP_HTML_Tag_Processor( $content ); - $z->next_tag( 'img' ); - + // Note: We want to store the `src` in the context so we + // can set it dynamically when the lightbox is opened. if ( isset( $block['attrs']['id'] ) ) { $img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] ); $img_metadata = wp_get_attachment_metadata( $block['attrs']['id'] ); $img_width = $img_metadata['width']; $img_height = $img_metadata['height']; } else { - $img_uploaded_src = $z->get_attribute( 'src' ); + $img_uploaded_src = $processor->get_attribute( 'src' ); $img_width = 'none'; $img_height = 'none'; } @@ -123,7 +123,7 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { $scale_attr = false; } - $w = new WP_HTML_Tag_Processor( $content ); + $w = new WP_HTML_Tag_Processor( $block_content ); $w->next_tag( 'figure' ); $w->add_class( 'wp-lightbox-container' ); $w->set_attribute( 'data-wp-interactive', true ); @@ -163,19 +163,20 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { // Wrap the image in the body content with a button. $img = null; preg_match( '/]+>/', $body_content, $img ); - $button = - '' - . $img[0]; + + $button = + $img[0] + . ''; + $body_content = preg_replace( '/]+>/', $button, $body_content ); // We need both a responsive image and an enlarged image to animate @@ -183,7 +184,7 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { // image is a copy of the one in the body, which animates immediately // as the lightbox is opened, while the enlarged one is a full-sized // version that will likely still be loading as the animation begins. - $m = new WP_HTML_Tag_Processor( $content ); + $m = new WP_HTML_Tag_Processor( $block_content ); $m->next_tag( 'figure' ); $m->add_class( 'responsive-image' ); $m->next_tag( 'img' ); @@ -199,7 +200,7 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { $m->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' ); $initial_image_content = $m->get_updated_html(); - $q = new WP_HTML_Tag_Processor( $content ); + $q = new WP_HTML_Tag_Processor( $block_content ); $q->next_tag( 'figure' ); $q->add_class( 'enlarged-image' ); $q->next_tag( 'img' ); @@ -219,7 +220,7 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { $close_button_icon = ''; $close_button_color = esc_attr( wp_get_global_styles( array( 'color', 'text' ) ) ); - $dialog_label = $alt_attribute ? esc_attr( $alt_attribute ) : esc_attr__( 'Image', 'gutenberg' ); + $dialog_label = esc_attr__( 'Enlarged image', 'gutenberg' ); $close_button_label = esc_attr__( 'Close', 'gutenberg' ); $lightbox_html = <<next_tag( 'img' ); $alt_attribute = $processor->get_attribute( 'alt' ); - if ( null !== $alt_attribute ) { + // An empty alt attribute `alt=""` is valid for decorative images. + if ( is_string( $alt_attribute ) ) { $alt_attribute = trim( $alt_attribute ); } + // It only makes sense to append the alt text to the button aria-label when the alt text is non-empty. if ( $alt_attribute ) { /* translators: %s: Image alt text. */ $aria_label = sprintf( __( 'Enlarge image: %s' ), $alt_attribute ); } - $content = $processor->get_updated_html(); // Currently, we are only enabling the zoom animation. $lightbox_animation = 'zoom'; - // We want to store the src in the context so we can set it dynamically when the lightbox is opened. - $z = new WP_HTML_Tag_Processor( $content ); - $z->next_tag( 'img' ); - + // Note: We want to store the `src` in the context so we + // can set it dynamically when the lightbox is opened. if ( isset( $block['attrs']['id'] ) ) { $img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] ); $img_metadata = wp_get_attachment_metadata( $block['attrs']['id'] ); $img_width = $img_metadata['width']; $img_height = $img_metadata['height']; } else { - $img_uploaded_src = $z->get_attribute( 'src' ); + $img_uploaded_src = $processor->get_attribute( 'src' ); $img_width = 'none'; $img_height = 'none'; } @@ -160,7 +163,7 @@ function block_core_image_render_lightbox( $block_content, $block ) { $scale_attr = false; } - $w = new WP_HTML_Tag_Processor( $content ); + $w = new WP_HTML_Tag_Processor( $block_content ); $w->next_tag( 'figure' ); $w->add_class( 'wp-lightbox-container' ); $w->set_attribute( 'data-wp-interactive', true ); @@ -180,7 +183,8 @@ function block_core_image_render_lightbox( $block_content, $block ) { "imageCurrentSrc": "", "targetWidth": "%s", "targetHeight": "%s", - "scaleAttr": "%s" + "scaleAttr": "%s", + "dialogLabel": "%s" } } }', @@ -188,7 +192,8 @@ function block_core_image_render_lightbox( $block_content, $block ) { $img_uploaded_src, $img_width, $img_height, - $scale_attr + $scale_attr, + __( 'Enlarged image' ) ) ); $w->next_tag( 'img' ); @@ -200,19 +205,20 @@ function block_core_image_render_lightbox( $block_content, $block ) { // Wrap the image in the body content with a button. $img = null; preg_match( '/]+>/', $body_content, $img ); - $button = - '' - . $img[0]; + + $button = + $img[0] + . ''; + $body_content = preg_replace( '/]+>/', $button, $body_content ); // We need both a responsive image and an enlarged image to animate @@ -220,7 +226,7 @@ function block_core_image_render_lightbox( $block_content, $block ) { // image is a copy of the one in the body, which animates immediately // as the lightbox is opened, while the enlarged one is a full-sized // version that will likely still be loading as the animation begins. - $m = new WP_HTML_Tag_Processor( $content ); + $m = new WP_HTML_Tag_Processor( $block_content ); $m->next_tag( 'figure' ); $m->add_class( 'responsive-image' ); $m->next_tag( 'img' ); @@ -236,7 +242,7 @@ function block_core_image_render_lightbox( $block_content, $block ) { $m->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' ); $initial_image_content = $m->get_updated_html(); - $q = new WP_HTML_Tag_Processor( $content ); + $q = new WP_HTML_Tag_Processor( $block_content ); $q->next_tag( 'figure' ); $q->add_class( 'enlarged-image' ); $q->next_tag( 'img' ); @@ -268,20 +274,16 @@ function block_core_image_render_lightbox( $block_content, $block ) { } $close_button_icon = ''; - $dialog_label = $alt_attribute ? esc_attr( $alt_attribute ) : esc_attr__( 'Image' ); $close_button_label = esc_attr__( 'Close' ); $lightbox_html = << -
+ HTML; @@ -302,11 +304,13 @@ function block_core_image_render_lightbox( $block_content, $block ) { } /** - * Ensure that the view script has the `wp-interactivity` dependency. + * Ensures that the view script has the `wp-interactivity` dependency. * * @since 6.4.0 * * @global WP_Scripts $wp_scripts + * + * @return void */ function block_core_image_ensure_interactivity_dependency() { global $wp_scripts; @@ -322,6 +326,8 @@ function block_core_image_ensure_interactivity_dependency() { /** * Registers the `core/image` block on server. + * + * @return void */ function register_block_core_image() { register_block_type_from_metadata( diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index 752ff773394a44..2ef602982e57b5 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -154,6 +154,8 @@ .wp-lightbox-container { position: relative; + display: flex; + flex-direction: column; button { border: none; @@ -193,11 +195,16 @@ .close-button { position: absolute; - top: calc(env(safe-area-inset-top) + 20px); - right: calc(env(safe-area-inset-right) + 20px); + top: calc(env(safe-area-inset-top) + 16px); // equivalent to $grid-unit-20 + right: calc(env(safe-area-inset-right) + 16px); // equivalent to $grid-unit-20 padding: 0; cursor: pointer; z-index: 5000000; + min-width: 40px; // equivalent to $button-size-next-default-40px + min-height: 40px; // equivalent to $button-size-next-default-40px + display: flex; + align-items: center; + justify-content: center; &:hover, &:focus, diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js index 13f20c9cd7cb68..3eb47dcc7cab4b 100644 --- a/packages/block-library/src/image/view.js +++ b/packages/block-library/src/image/view.js @@ -227,7 +227,17 @@ store( roleAttribute: ( { context } ) => { return context.core.image.lightboxEnabled ? 'dialog' - : ''; + : null; + }, + ariaModal: ( { context } ) => { + return context.core.image.lightboxEnabled + ? 'true' + : null; + }, + dialogLabel: ( { context } ) => { + return context.core.image.lightboxEnabled + ? context.core.image.dialogLabel + : null; }, lightboxObjectFit: ( { context } ) => { if ( context.core.image.initialized ) { @@ -237,7 +247,7 @@ store( enlargedImgSrc: ( { context } ) => { return context.core.image.initialized ? context.core.image.imageUploadedSrc - : ''; + : 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; }, }, }, @@ -360,9 +370,9 @@ function setStyles( context, event ) { naturalHeight, offsetWidth: originalWidth, offsetHeight: originalHeight, - } = event.target.nextElementSibling; + } = event.target.previousElementSibling; let { x: screenPosX, y: screenPosY } = - event.target.nextElementSibling.getBoundingClientRect(); + event.target.previousElementSibling.getBoundingClientRect(); // Natural ratio of the image clicked to open the lightbox. const naturalRatio = naturalWidth / naturalHeight;