diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index 584318b51d196f..886843d83ba720 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -185,22 +185,37 @@ function block_core_image_render_lightbox( $block_content, $block ) { $p->seek( 'figure' ); $figure_class_names = $p->get_attribute( 'class' ); $figure_styles = $p->get_attribute( 'style' ); + + // Create unique id and set the image metadata in the state. + $unique_image_id = uniqid(); + + wp_interactivity_state( + 'core/image', + array( + 'metadata' => array( + $unique_image_id => array( + 'uploadedSrc' => $img_uploaded_src, + 'figureClassNames' => $figure_class_names, + 'figureStyles' => $figure_styles, + 'imgClassNames' => $img_class_names, + 'imgStyles' => $img_styles, + 'targetWidth' => $img_width, + 'targetHeight' => $img_height, + 'scaleAttr' => $block['attrs']['scale'] ?? false, + 'ariaLabel' => $aria_label, + 'alt' => $alt, + ), + ), + ) + ); + $p->add_class( 'wp-lightbox-container' ); $p->set_attribute( 'data-wp-interactive', 'core/image' ); $p->set_attribute( 'data-wp-context', wp_json_encode( array( - 'uploadedSrc' => $img_uploaded_src, - 'figureClassNames' => $figure_class_names, - 'figureStyles' => $figure_styles, - 'imgClassNames' => $img_class_names, - 'imgStyles' => $img_styles, - 'targetWidth' => $img_width, - 'targetHeight' => $img_height, - 'scaleAttr' => $block['attrs']['scale'] ?? false, - 'ariaLabel' => $aria_label, - 'alt' => $alt, + 'imageId' => $unique_image_id, ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) @@ -231,8 +246,8 @@ class="lightbox-trigger" aria-label="' . esc_attr( $aria_label ) . '" data-wp-init="callbacks.initTriggerButton" data-wp-on-async--click="actions.showLightbox" - data-wp-style--right="context.imageButtonRight" - data-wp-style--top="context.imageButtonTop" + data-wp-style--right="state.imageButtonRight" + data-wp-style--top="state.imageButtonTop" > diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js index 4feb45dc9a3994..99cc84d79f0f4c 100644 --- a/packages/block-library/src/image/view.js +++ b/packages/block-library/src/image/view.js @@ -23,9 +23,12 @@ const { state, actions, callbacks } = store( 'core/image', { state: { - currentImage: {}, + currentImageId: null, + get currentImage() { + return state.metadata[ state.currentImageId ]; + }, get overlayOpened() { - return state.currentImage.currentSrc; + return state.currentImageId !== null; }, get roleAttribute() { return state.overlayOpened ? 'dialog' : null; @@ -48,24 +51,31 @@ const { state, actions, callbacks } = store( ) }; object-fit:cover;` ); }, + get imageButtonRight() { + const { imageId } = getContext(); + return state.metadata[ imageId ].imageButtonRight; + }, + get imageButtonTop() { + const { imageId } = getContext(); + return state.metadata[ imageId ].imageButtonTop; + }, }, actions: { showLightbox() { - const ctx = getContext(); + const { imageId } = getContext(); // Bails out if the image has not loaded yet. - if ( ! ctx.imageRef?.complete ) { + if ( ! state.metadata[ imageId ].imageRef?.complete ) { return; } - // Stores the positons of the scroll to fix it until the overlay is + // Stores the positions of the scroll to fix it until the overlay is // closed. state.scrollTopReset = document.documentElement.scrollTop; state.scrollLeftReset = document.documentElement.scrollLeft; - // Moves the information of the expaned image to the state. - ctx.currentSrc = ctx.imageRef.currentSrc; - state.currentImage = ctx; + // Sets the current expanded image in the state and enables the overlay. + state.currentImageId = imageId; state.overlayEnabled = true; // Computes the styles of the overlay for the animation. @@ -73,6 +83,11 @@ const { state, actions, callbacks } = store( }, hideLightbox() { if ( state.overlayEnabled ) { + // Starts the overlay closing animation. The showClosingAnimation + // class is used to avoid showing it on page load. + state.showClosingAnimation = true; + state.overlayEnabled = false; + // Waits until the close animation has completed before allowing a // user to scroll again. The duration of this animation is defined in // the `styles.scss` file, but in any case we should wait a few @@ -86,14 +101,9 @@ const { state, actions, callbacks } = store( preventScroll: true, } ); - // Resets the current image to mark the overlay as closed. - state.currentImage = {}; + // Resets the current image id to mark the overlay as closed. + state.currentImageId = null; }, 450 ); - - // Starts the overlay closing animation. The showClosingAnimation - // class is used to avoid showing it on page load. - state.showClosingAnimation = true; - state.overlayEnabled = false; } }, handleKeydown( event ) { @@ -213,6 +223,7 @@ const { state, actions, callbacks } = store( let containerMaxHeight = imgMaxHeight; let containerWidth = imgMaxWidth; let containerHeight = imgMaxHeight; + // Checks if the target image has a different ratio than the original // one (thumbnail). Recalculates the width and height. if ( naturalRatio.toFixed( 2 ) !== imgRatio.toFixed( 2 ) ) { @@ -323,9 +334,11 @@ const { state, actions, callbacks } = store( `; }, setButtonStyles() { - const ctx = getContext(); + const { imageId } = getContext(); const { ref } = getElement(); - ctx.imageRef = ref; + + state.metadata[ imageId ].imageRef = ref; + state.metadata[ imageId ].currentSrc = ref.currentSrc; const { naturalWidth, @@ -368,10 +381,13 @@ const { state, actions, callbacks } = store( const buttonOffsetTop = figureHeight - offsetHeight; const buttonOffsetRight = figureWidth - offsetWidth; + let imageButtonTop = buttonOffsetTop + 16; + let imageButtonRight = buttonOffsetRight + 16; + // In the case of an image with object-fit: contain, the size of the // element can be larger than the image itself, so it needs to // calculate where to place the button. - if ( ctx.scaleAttr === 'contain' ) { + if ( state.metadata[ imageId ].scaleAttr === 'contain' ) { // Natural ratio of the image. const naturalRatio = naturalWidth / naturalHeight; // Offset ratio of the image. @@ -381,25 +397,25 @@ const { state, actions, callbacks } = store( // If it reaches the width first, it keeps the width and compute the // height. const referenceHeight = offsetWidth / naturalRatio; - ctx.imageButtonTop = + imageButtonTop = ( offsetHeight - referenceHeight ) / 2 + buttonOffsetTop + 16; - ctx.imageButtonRight = buttonOffsetRight + 16; + imageButtonRight = buttonOffsetRight + 16; } else { // If it reaches the height first, it keeps the height and compute // the width. const referenceWidth = offsetHeight * naturalRatio; - ctx.imageButtonTop = buttonOffsetTop + 16; - ctx.imageButtonRight = + imageButtonTop = buttonOffsetTop + 16; + imageButtonRight = ( offsetWidth - referenceWidth ) / 2 + buttonOffsetRight + 16; } - } else { - ctx.imageButtonTop = buttonOffsetTop + 16; - ctx.imageButtonRight = buttonOffsetRight + 16; } + + state.metadata[ imageId ].imageButtonTop = imageButtonTop; + state.metadata[ imageId ].imageButtonRight = imageButtonRight; }, setOverlayFocus() { if ( state.overlayEnabled ) { @@ -409,9 +425,9 @@ const { state, actions, callbacks } = store( } }, initTriggerButton() { - const ctx = getContext(); + const { imageId } = getContext(); const { ref } = getElement(); - ctx.buttonRef = ref; + state.metadata[ imageId ].buttonRef = ref; }, }, },