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 15 commits into
base: trunk
Choose a base branch
from
Open
Changes from 1 commit
Commits
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
Next Next commit
add lightbox support to gallery block
  • Loading branch information
madhusudhand committed Sep 6, 2024
commit 508c70e25d74c26b9a3bf2c0ce922670841c0e70
58 changes: 57 additions & 1 deletion packages/block-library/src/gallery/index.php
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ function block_core_gallery_data_id_backcompatibility( $parsed_block ) {
* @param string $content Content of the block 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
@@ -121,6 +121,35 @@ function block_core_gallery_render( $attributes, $content ) {
)
);

$lightbox_settings = block_core_image_get_lightbox_settings( $block->parsed_block );

if (
isset( $lightbox_settings ) &&
// 'none' === $link_destination &&
isset( $lightbox_settings['enabled'] ) &&
true === $lightbox_settings['enabled']
) {
$processed_content->set_attribute( 'data-wp-interactive', 'core/gallery' );
$processed_content->set_attribute( 'data-wp-context', '{"lightbox": true, "images": []}' );
$processed_content->set_attribute( 'data-wp-init', 'callbacks.init' );
$processed_content->set_attribute( 'data-wp-on-async--load', 'callbacks.init' );

$suffix = wp_scripts_get_suffix();
if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
$module_url = gutenberg_url( '/build/interactivity/gallery.min.js' );
}

wp_register_script_module(
'@wordpress/block-library/gallery',
isset( $module_url ) ? $module_url : includes_url( "blocks/gallery/view{$suffix}.js" ),
array( '@wordpress/interactivity' ),
defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' )
);

wp_enqueue_script_module( '@wordpress/block-library/gallery' );

}

// 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.
@@ -166,6 +195,33 @@ static function () use ( $image_blocks, &$i ) {

return $content;
}

// NOTE: this setting isn't required for the gallery block.
// since lightbox implementation is done in the image block, it can totally be removed.
function block_core_gallery_get_lightbox_settings( $block ) {
// Gets the lightbox setting from the block attributes.
if ( isset( $block['attrs']['lightbox'] ) ) {
$lightbox_settings = $block['attrs']['lightbox'];
}

if ( ! isset( $lightbox_settings ) ) {
// TODO: change it to gallery block name.
$lightbox_settings = wp_get_global_settings( array( 'lightbox' ), array( 'block_name' => 'core/image' ) );

// If not present in global settings, check the top-level global settings.
//
// NOTE: If no block-level settings are found, the previous call to
// `wp_get_global_settings` will return the whole `theme.json` structure in
// which case we can check if the "lightbox" key is present at the top-level
// of the global settings and use its value.
if ( isset( $lightbox_settings['lightbox'] ) ) {
$lightbox_settings = wp_get_global_settings( array( 'lightbox' ) );
}
}

return $lightbox_settings ?? null;
}

/**
* Registers the `core/gallery` block on server.
*
16 changes: 16 additions & 0 deletions packages/block-library/src/gallery/view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* WordPress dependencies
*/
import { store } from '@wordpress/interactivity';

store(
'core/gallery',
{
callbacks: {
init() {},
},
},
{
lock: false,
}
);
11 changes: 10 additions & 1 deletion packages/block-library/src/image/index.php
Original file line number Diff line number Diff line change
@@ -220,6 +220,7 @@ function block_core_image_render_lightbox( $block_content, $block ) {
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
)
);
$p->set_attribute( 'data-wp-init', 'callbacks.initImage' );

// Image.
$p->next_tag( 'img' );
@@ -268,6 +269,8 @@ class="lightbox-trigger"
*/
function block_core_image_print_lightbox_overlay() {
$close_button_label = esc_attr__( 'Close' );
$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
@@ -288,7 +291,7 @@ function block_core_image_print_lightbox_overlay() {
<div
class="wp-lightbox-overlay zoom"
data-wp-interactive="core/image"
data-wp-context='{}'
data-wp-context='{ "carousel": "true" }'
data-wp-bind--role="state.roleAttribute"
data-wp-bind--aria-label="state.currentImage.ariaLabel"
data-wp-bind--aria-modal="state.ariaModal"
@@ -307,6 +310,12 @@ class="wp-lightbox-overlay zoom"
<button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="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>
<button type="button" aria-label="$prev_button_label" style="fill: $close_button_color" class="prev-button" data-wp-bind--hidden="!state.isGallery" data-wp-on--click="actions.showPreviousImage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" 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>
<button type="button" aria-label="$next_button_label" style="fill: $close_button_color" class="next-button" data-wp-bind--hidden="!state.isGallery" data-wp-on--click="actions.showNextImage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" 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>
<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">
34 changes: 34 additions & 0 deletions packages/block-library/src/image/style.scss
Original file line number Diff line number Diff line change
@@ -225,6 +225,40 @@
}
}

.prev-button,
.next-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
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;

&[hidden] {
display: none;
}

&:hover,
&:focus,
&:not(:hover):not(:active):not(.has-background) {
background: none;
border: none;
}
}

.prev-button {
left: calc(env(safe-area-inset-left) + 16px); // equivalent to $grid-unit-20
}

.next-button {
right: calc(env(safe-area-inset-right) + 16px); // equivalent to $grid-unit-20
}

.lightbox-image-container {
position: absolute;
overflow: hidden;
74 changes: 69 additions & 5 deletions packages/block-library/src/image/view.js
Original file line number Diff line number Diff line change
@@ -23,7 +23,14 @@ const { state, actions, callbacks } = store(
'core/image',
{
state: {
currentImageId: null,
isGallery: false,
madhusudhand marked this conversation as resolved.
Show resolved Hide resolved
images: [],
currentImageIndex: -1,
get currentImageId() {
return state.currentImageIndex > -1 && state.images.length > 0
? state.images[ state.currentImageIndex ]
: null;
},
get currentImage() {
return state.metadata[ state.currentImageId ];
},
@@ -96,6 +103,13 @@ const { state, actions, callbacks } = store(
state.scrollTopReset = document.documentElement.scrollTop;
state.scrollLeftReset = document.documentElement.scrollLeft;

const { lightbox, images } = getContext( 'core/gallery' ) || {};
state.isGallery = !! lightbox;
state.images = state.isGallery ? images || [] : [ imageId ];

// Sets the current image index to the one that was clicked.
callbacks.setCurrentImageIndex( imageId );

// Sets the current expanded image in the state and enables the overlay.
state.overlayEnabled = true;
state.currentImageId = imageId;
@@ -123,23 +137,56 @@ const { state, actions, callbacks } = store(
preventScroll: true,
} );

// Resets the current image id to mark the overlay as closed.
state.currentImageId = null;
// Resets the current image index to mark the overlay as closed.
state.currentImageIndex = -1;
state.images = [];
}, 450 );
}
},
showPreviousImage( e ) {
if ( ! state.isGallery ) {
return;
}

e.stopPropagation();
if ( state.currentImageIndex - 1 < 0 ) {
return;
}
state.currentImageIndex = state.currentImageIndex - 1;
callbacks.setOverlayStyles();
},
showNextImage( e ) {
if ( ! state.isGallery ) {
return;
}

e.stopPropagation();
if ( state.currentImageIndex + 1 >= state.images.length ) {
return;
}
state.currentImageIndex = state.currentImageIndex + 1;
callbacks.setOverlayStyles();
},
handleKeydown( event ) {
if ( state.overlayEnabled ) {
// Focuses the close button when the user presses the tab key.
if ( event.key === 'Tab' ) {
event.preventDefault();
const { ref } = getElement();
ref.querySelector( 'button' ).focus();

// TODO: now that there are next and prev buttons, rotate the focus
}
// Closes the lightbox when the user presses the escape key.
if ( event.key === 'Escape' ) {
actions.hideLightbox();
}

if ( event.key === 'ArrowLeft' ) {
actions.showPreviousImage( event );
} else if ( event.key === 'ArrowRight' ) {
actions.showNextImage( event );
}
}
},
handleTouchMove( event ) {
@@ -187,6 +234,21 @@ const { state, actions, callbacks } = store(
},
},
callbacks: {
initImage() {
madhusudhand marked this conversation as resolved.
Show resolved Hide resolved
const { lightbox, images } = getContext( 'core/gallery' ) || {};
if ( ! lightbox ) {
return;
}

const ctx = getContext();
images.push( ctx.imageId );
},
setCurrentImageIndex( imageId ) {
const currentIndex = state.images.findIndex(
( id ) => id === imageId
);
state.currentImageIndex = currentIndex;
},
madhusudhand marked this conversation as resolved.
Show resolved Hide resolved
setOverlayStyles() {
if ( ! state.overlayEnabled ) {
return;
@@ -229,12 +291,14 @@ const { state, actions, callbacks } = store(
// size), the image's dimensions in the lightbox are the same
// as those of the image in the content.
let imgMaxWidth = parseFloat(
state.currentImage.targetWidth !== 'none'
state.currentImage.targetWidth &&
state.currentImage.targetWidth !== 'none'
? state.currentImage.targetWidth
: naturalWidth
);
let imgMaxHeight = parseFloat(
state.currentImage.targetHeight !== 'none'
state.currentImage.targetHeight &&
state.currentImage.targetHeight !== 'none'
? state.currentImage.targetHeight
: naturalHeight
);
1 change: 1 addition & 0 deletions tools/webpack/interactivity.js
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ module.exports = {
navigation: './packages/block-library/src/navigation/view.js',
query: './packages/block-library/src/query/view.js',
image: './packages/block-library/src/image/view.js',
gallery: './packages/block-library/src/gallery/view.js',
file: './packages/block-library/src/file/view.js',
search: './packages/block-library/src/search/view.js',
},