Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gallery: Add lightbox support #62906

Open
wants to merge 30 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
508c70e
add lightbox support to gallery block
madhusudhand Jul 15, 2024
98bba76
disable and hide next/prev icons based on device
madhusudhand Jul 25, 2024
cc48519
remove view.js from gallery
madhusudhand Jul 25, 2024
c2611d0
fix a lint issue
madhusudhand Jul 25, 2024
e09b8ee
rename isGallery to hasNavigation and imageInit method
madhusudhand Jul 26, 2024
4d4a509
adjust button opacity when disabled
madhusudhand Jul 26, 2024
5dbfaed
address accessiability of lightbox navigation
madhusudhand Jul 26, 2024
ccbe9dc
fix accessibility: dynamic aria-label based on current image
madhusudhand Aug 13, 2024
5e0df68
Try to fix a11y issues
t-hamano Aug 16, 2024
0579c8e
Improve screen reader reading
t-hamano Aug 22, 2024
dfcca2f
move translations to server
madhusudhand Aug 29, 2024
408cf80
reset gallery_id after each gallery processing
madhusudhand Aug 30, 2024
3716092
fix php lint errors
madhusudhand Aug 30, 2024
f14acc0
Merge remote-tracking branch 'origin/trunk' into gallery/lightbox
luisherranz Dec 6, 2024
1077e73
Use block context to pass down a gallery ID
luisherranz Dec 6, 2024
3387ac0
Merge branch 'trunk' into gallery/lightbox
luisherranz Feb 19, 2025
fd51250
Fix frontend implementation
luisherranz Feb 19, 2025
016ae8d
Merge branch 'trunk' into gallery/lightbox
t-hamano Feb 21, 2025
d229233
Fix Screen reader double reading
t-hamano Feb 21, 2025
e68f3a2
Don't hide lightbox when hitting Enter on prev/next buttons
t-hamano Feb 21, 2025
4e0b31a
Match visual order with DOM order
t-hamano Feb 21, 2025
24524c6
Do not apply opacity to focus styles on disabled prev/next buttons
t-hamano Feb 21, 2025
c7608c3
Add a tooltip to navigation button
t-hamano Feb 21, 2025
230ab34
Add `cursor:pointer` to navigation buttons
t-hamano Feb 23, 2025
a9aa62b
Add focus trap
t-hamano Feb 23, 2025
7ede46a
Add "Lightbox navigation" option
t-hamano Feb 23, 2025
deeaf40
Restore aria-live/aria-atomic
t-hamano Feb 23, 2025
4f1cde2
Ajust z-index values
t-hamano Feb 23, 2025
1da0337
Regenerate fixtures
t-hamano Feb 23, 2025
0ed7d4c
Fix PHP lint error
t-hamano Feb 23, 2025
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
2 changes: 1 addition & 1 deletion docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ Display multiple images in a rich gallery. ([Source](https://github.com/WordPres
- **Category:** media
- **Allowed Blocks:** core/image
- **Supports:** align, anchor, color (background, gradients, ~~text~~), interactivity (clientNavigation), layout (default, ~~allowEditing~~, ~~allowInheriting~~, ~~allowSwitching~~), spacing (blockGap, margin, padding), units (em, px, rem, vh, vw), ~~html~~
- **Attributes:** allowResize, caption, columns, fixedHeight, ids, imageCrop, images, linkTarget, linkTo, randomOrder, shortCodeTransforms, sizeSlug
- **Attributes:** allowResize, caption, columns, fixedHeight, ids, imageCrop, images, lightBoxNavigation, linkTarget, linkTo, randomOrder, shortCodeTransforms, sizeSlug

## Group

Expand Down
5 changes: 5 additions & 0 deletions packages/block-library/src/gallery/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"name": "core/gallery",
"title": "Gallery",
"category": "media",
"usesContext": [ "galleryId" ],
"allowedBlocks": [ "core/image" ],
"description": "Display multiple images in a rich gallery.",
"keywords": [ "images", "photos" ],
Expand Down Expand Up @@ -102,6 +103,10 @@
"allowResize": {
"type": "boolean",
"default": false
},
"lightBoxNavigation": {
"type": "boolean",
"default": true
}
},
"providesContext": {
Expand Down
26 changes: 24 additions & 2 deletions packages/block-library/src/gallery/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,15 @@ export default function GalleryEdit( props ) {
)
: LINK_OPTIONS;

const { columns, imageCrop, randomOrder, linkTarget, linkTo, sizeSlug } =
attributes;
const {
columns,
imageCrop,
randomOrder,
linkTarget,
linkTo,
sizeSlug,
lightBoxNavigation,
} = attributes;

const {
__unstableMarkNextChangeAsNotPersistent,
Expand Down Expand Up @@ -442,6 +449,12 @@ export default function GalleryEdit( props ) {
} );
}

function toggleLightboxNavigation() {
setAttributes( {
lightBoxNavigation: ! attributes.lightBoxNavigation,
} );
}

function updateImagesSize( newSizeSlug ) {
setAttributes( { sizeSlug: newSizeSlug } );
const changedAttributes = {};
Expand Down Expand Up @@ -623,6 +636,15 @@ export default function GalleryEdit( props ) {
onChange={ toggleOpenInNewTab }
/>
) }
<ToggleControl
__nextHasNoMarginBottom
label={ __( 'Lightbox navigation' ) }
checked={ lightBoxNavigation }
onChange={ toggleLightboxNavigation }
help={ __(
'Enable navigation between images with "Expand on click" enabled.'
) }
/>
{ Platform.isWeb && ! imageSizeOptions && hasImageIds && (
<BaseControl
className="gallery-image-sizes"
Expand Down
75 changes: 70 additions & 5 deletions packages/block-library/src/gallery/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,35 @@ function block_core_gallery_data_id_backcompatibility( $parsed_block ) {

add_filter( 'render_block_data', 'block_core_gallery_data_id_backcompatibility' );

/**
* Adds a unique ID to the gallery block context.
*
* @since 6.8.0
*
* @param array $context Default context.
* @param array $parsed_block Block being rendered, filtered by render_block_data.
* @return array Filtered context.
*/
function block_core_gallery_render_context( $context, $parsed_block ) {
if ( 'core/gallery' === $parsed_block['blockName'] ) {
$context['galleryId'] = uniqid();
}
return $context;
}

add_filter( 'render_block_context', 'block_core_gallery_render_context', 10, 2 );

/**
* Renders the `core/gallery` block on the server.
*
* @since 6.0.0
*
* @param array $attributes Attributes of the block being rendered.
* @param string $content Content of the block being rendered.
* @param string $content Content of the block being rendered.
* @param array $block The block instance being rendered.
* @return string The content of the block being rendered.
*/
function block_core_gallery_render( $attributes, $content ) {
function block_core_gallery_render( $attributes, $content, $block ) {
// Adds a style tag for the --wp--style--unstable-gallery-gap var.
// The Gallery block needs to recalculate Image block width based on
// the current gap setting in order to maintain the number of flex columns
Expand Down Expand Up @@ -116,11 +135,56 @@ function block_core_gallery_render( $attributes, $content ) {

wp_style_engine_get_stylesheet_from_css_rules(
$gallery_styles,
array(
'context' => 'block-supports',
)
array( 'context' => 'block-supports' )
);

if ( $attributes['lightBoxNavigation'] ) {
// Gets all image IDs from the state that match this gallery's ID.
$state = wp_interactivity_state( 'core/image' );
$gallery_id = $block->context['galleryId'] ?? null;
$image_ids = array();
if ( isset( $gallery_id ) && isset( $state['metadata'] ) ) {
foreach ( $state['metadata'] as $image_id => $metadata ) {
if ( isset( $metadata['galleryId'] ) && $metadata['galleryId'] === $gallery_id ) {
$image_ids[] = $image_id;
}
}
}

$processed_content->set_attribute( 'data-wp-interactive', 'core/gallery' );
$processed_content->set_attribute(
'data-wp-context',
wp_json_encode(
array( 'galleryId' => $gallery_id ),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
)
);

// Populates the aria label for each image in the gallery.
if ( ! empty( $image_ids ) ) {
if ( 1 <= count( $image_ids ) ) {
for ( $i = 0; $i < count( $image_ids ); $i++ ) {
$image_id = $image_ids[ $i ];
$alt = $state['metadata'][ $image_id ]['alt'];
wp_interactivity_state(
'core/image',
array(
'metadata' => array(
$image_id => array(
'customAriaLabel' => empty( $alt )
/* translators: %1$s: current image index, %2$s: total number of images */
? sprintf( __( 'Enlarged image %1$s of %2$s' ), $i + 1, count( $image_ids ) )
/* translators: %1$s: current image index, %2$s: total number of images, %3$s: Image alt text */
: sprintf( __( 'Enlarged image %1$s of %2$s: %3$s' ), $i + 1, count( $image_ids ), $alt ),
),
),
)
);
}
}
}
}

// The WP_HTML_Tag_Processor class calls get_updated_html() internally
// when the instance is treated as a string, but here we explicitly
// convert it to a string.
Expand Down Expand Up @@ -166,6 +230,7 @@ static function () use ( $image_blocks, &$i ) {

return $content;
}

/**
* Registers the `core/gallery` block on server.
*
Expand Down
3 changes: 2 additions & 1 deletion packages/block-library/src/image/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"fixedHeight",
"postId",
"postType",
"queryId"
"queryId",
"galleryId"
],
"description": "Insert an image to make a visual statement.",
"keywords": [ "img", "photo", "picture" ],
Expand Down
80 changes: 53 additions & 27 deletions packages/block-library/src/image/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function render_block_core_image( $attributes, $content, $block ) {
* if the way the blocks are rendered changes, or if a new kind of filter is
* introduced.
*/
add_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15, 2 );
add_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15, 3 );
} else {
/*
* Remove the filter if previously added by other Image blocks.
Expand Down Expand Up @@ -130,33 +130,43 @@ function block_core_image_get_lightbox_settings( $block ) {
*
* @since 6.4.0
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @param array $block_instance Block instance.
*
* @return string Filtered block content.
*/
function block_core_image_render_lightbox( $block_content, $block ) {
function block_core_image_render_lightbox( $block_content, $block, $block_instance ) {
/*
* If there's no IMG tag in the block then return the given block content
* as-is. There's nothing that this code can knowingly modify to add the
* lightbox behavior.
*/
$p = new WP_HTML_Tag_Processor( $block_content );

if ( $p->next_tag( 'figure' ) ) {
$p->set_bookmark( 'figure' );
}
if ( ! $p->next_tag( 'img' ) ) {
return $block_content;
}

$alt = $p->get_attribute( 'alt' );
$img_uploaded_src = $p->get_attribute( 'src' );
$img_class_names = $p->get_attribute( 'class' );
$img_styles = $p->get_attribute( 'style' );
$img_width = 'none';
$img_height = 'none';
$aria_label = __( 'Enlarge' );
$dialog_aria_label = __( 'Enlarged image' );
$alt = $p->get_attribute( 'alt' );
$img_uploaded_src = $p->get_attribute( 'src' );
$img_class_names = $p->get_attribute( 'class' );
$img_styles = $p->get_attribute( 'style' );
$img_width = 'none';
$img_height = 'none';

wp_interactivity_config(
'core/image',
array( 'defaultAriaLabel' => __( 'Enlarged image' ) )
);

if ( $alt ) {
/* translators: %s: Image alt text. */
$custom_aria_label = sprintf( __( 'Enlarged image: %s' ), $alt );
}

if ( isset( $block['attrs']['id'] ) ) {
$img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] );
Expand All @@ -172,7 +182,6 @@ function block_core_image_render_lightbox( $block_content, $block ) {

// Create unique id and set the image metadata in the state.
$unique_image_id = uniqid();

wp_interactivity_state(
'core/image',
array(
Expand All @@ -186,8 +195,9 @@ function block_core_image_render_lightbox( $block_content, $block ) {
'targetWidth' => $img_width,
'targetHeight' => $img_height,
'scaleAttr' => $block['attrs']['scale'] ?? false,
'ariaLabel' => $dialog_aria_label,
'alt' => $alt,
'galleryId' => $block_instance->context['galleryId'] ?? null,
'customAriaLabel' => $custom_aria_label ?? null,
),
),
)
Expand All @@ -198,9 +208,7 @@ function block_core_image_render_lightbox( $block_content, $block ) {
$p->set_attribute(
'data-wp-context',
wp_json_encode(
array(
'imageId' => $unique_image_id,
),
array( 'imageId' => $unique_image_id ),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
)
);
Expand Down Expand Up @@ -229,11 +237,11 @@ function block_core_image_render_lightbox( $block_content, $block ) {
class="lightbox-trigger"
type="button"
aria-haspopup="dialog"
aria-label="' . esc_attr( $aria_label ) . '"
aria-label="' . esc_attr( __( 'Enlarge' ) ) . '"
data-wp-init="callbacks.initTriggerButton"
data-wp-on-async--click="actions.showLightbox"
data-wp-style--right="state.imageButtonRight"
data-wp-style--top="state.imageButtonTop"
data-wp-style--right="state.thisImage.buttonRight"
data-wp-style--top="state.thisImage.buttonTop"
>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
Expand All @@ -252,6 +260,9 @@ class="lightbox-trigger"
*/
function block_core_image_print_lightbox_overlay() {
$close_button_label = esc_attr__( 'Close' );
$dialog_label = esc_attr__( 'Enlarged images' );
Copy link
Member

@gziolo gziolo Aug 21, 2024

Choose a reason for hiding this comment

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

This label Enlarged images now applies to both the single image and the group of images in the gallery. Previously, the message announced was way more accurate for a single image:

Screenshot 2024-08-21 at 17 20 23

Copy link
Contributor

Choose a reason for hiding this comment

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

I adjusted the screen reader reading. NVDA reads it out as follows, but how about your software?

9bca3f2b0333352215b61aa97526289c.mp4

Copy link
Member

Choose a reason for hiding this comment

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

That's how it should work. e4ebf7d is a step in the right direction 👍🏻

$prev_button_label = esc_attr__( 'Previous' );
$next_button_label = esc_attr__( 'Next' );

// If the current theme does NOT have a `theme.json`, or the colors are not
// defined, it needs to set the background color & close button color to some
Expand All @@ -271,14 +282,16 @@ function block_core_image_print_lightbox_overlay() {
echo <<<HTML
<div
class="wp-lightbox-overlay zoom"
aria-label="$dialog_label"
data-wp-interactive="core/image"
data-wp-context='{}'
data-wp-bind--role="state.roleAttribute"
data-wp-bind--aria-label="state.currentImage.ariaLabel"
data-wp-bind--aria-label="state.ariaLabel"
data-wp-bind--aria-modal="state.ariaModal"
data-wp-class--active="state.overlayEnabled"
data-wp-class--show-closing-animation="state.showClosingAnimation"
data-wp-watch="callbacks.setOverlayFocus"
data-wp-watch--focus="callbacks.setOverlayFocus"
data-wp-watch--inert="callbacks.setInertElements"
data-wp-on--keydown="actions.handleKeydown"
data-wp-on-async--touchstart="actions.handleTouchStart"
data-wp-on--touchmove="actions.handleTouchMove"
Expand All @@ -289,19 +302,32 @@ class="wp-lightbox-overlay zoom"
data-wp-bind--style="state.overlayStyles"
tabindex="-1"
>
<button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="close-button">
<button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="wp-lightbox-close-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" focusable="false"><path d="m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"></path></svg>
</button>
<div class="wp-lightbox-navigation-container-prev" data-wp-bind--hidden="!state.hasNavigation">
<button type="button" aria-describedby="wp_lightbox_navigation_button_prev" style="fill: $close_button_color" class="wp-lightbox-navigation-button" data-wp-on--click="actions.showPreviousImage" data-wp-bind--aria-disabled="!state.hasPreviousImage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="28" height="28" aria-hidden="true" focusable="false"><path d="M14.6 7l-1.2-1L8 12l5.4 6 1.2-1-4.6-5z"></path></svg>
</button>
<p id="wp_lightbox_navigation_button_prev" class="wp-lightbox-navigation-tooltip" role="tooltip">$prev_button_label</p>
</div>
<div class="lightbox-image-container">
<figure data-wp-bind--class="state.currentImage.figureClassNames" data-wp-bind--style="state.figureStyles">
<img data-wp-bind--alt="state.currentImage.alt" data-wp-bind--class="state.currentImage.imgClassNames" data-wp-bind--style="state.imgStyles" data-wp-bind--src="state.currentImage.currentSrc">
<figure data-wp-bind--class="state.selectedImage.figureClassNames" data-wp-bind--style="state.figureStyles">
<img data-wp-bind--alt="state.selectedImage.alt" data-wp-bind--class="state.selectedImage.imgClassNames" data-wp-bind--style="state.imgStyles" data-wp-bind--src="state.selectedImage.currentSrc">
</figure>
</div>
<div class="lightbox-image-container">
<figure data-wp-bind--class="state.currentImage.figureClassNames" data-wp-bind--style="state.figureStyles">
<img data-wp-bind--alt="state.currentImage.alt" data-wp-bind--class="state.currentImage.imgClassNames" data-wp-bind--style="state.imgStyles" data-wp-bind--src="state.enlargedSrc">
<figure data-wp-bind--class="state.selectedImage.figureClassNames" data-wp-bind--style="state.figureStyles">
<img data-wp-bind--alt="state.selectedImage.alt" data-wp-bind--class="state.selectedImage.imgClassNames" data-wp-bind--style="state.imgStyles" data-wp-bind--src="state.enlargedSrc">
</figure>
</div>
<div class="wp-lightbox-navigation-container-next" data-wp-bind--hidden="!state.hasNavigation">
<button type="button" aria-describedby="wp_lightbox_navigation_button_next" style="fill: $close_button_color" class="wp-lightbox-navigation-button" data-wp-on--click="actions.showNextImage" data-wp-bind--aria-disabled="!state.hasNextImage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="28" height="28" aria-hidden="true" focusable="false"><path d="M10.6 6L9.4 7l4.6 5-4.6 5 1.2 1 5.4-6z"></path></svg>
</button>
<p id="wp_lightbox_navigation_button_next" class="wp-lightbox-navigation-tooltip" role="tooltip">$next_button_label</p>
</div>
<div data-wp-text="state.ariaLabel" aria-live="polite" aria-atomic="true" class="screen-reader-text"></div>
<div class="scrim" style="background-color: $background_color" aria-hidden="true"></div>
</div>
HTML;
Expand Down
Loading
Loading