From a8ce473d427f7f0c8ef6ed2cafed6c9dbed9b1a2 Mon Sep 17 00:00:00 2001 From: Luigi Teschio Date: Fri, 20 Oct 2023 09:42:39 +0200 Subject: [PATCH] Product Gallery: Add animation when large image changes (#11113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add slide animation * Remove placeholder and pagination (#11145) * Add titles to patterns and set the aligment to Wide * Replace product query patterns with product collection ones * Remove pagination and no results query from product query patterns * Add aspect ratio to the product image attributes * Add portrait aspect ratio to product X column and product gallery patterns * improve animation * improve naming * fix regression * fix css * improve code style * remove check on tag image * align image * fix crash when zoom is disabled * fix E2E tests * improve CSS --------- Co-authored-by: Alba Rincón --- assets/js/blocks/product-gallery/frontend.tsx | 4 +- .../product-gallery-large-image/frontend.tsx | 98 ++++++++++++++++--- assets/js/blocks/product-gallery/style.scss | 35 +++++-- src/BlockTypes/ProductGalleryLargeImage.php | 31 +++--- ...rge-image.block_theme.side_effects.spec.ts | 22 +++-- ...t-gallery.block_theme.side_effects.spec.ts | 4 +- 6 files changed, 139 insertions(+), 55 deletions(-) diff --git a/assets/js/blocks/product-gallery/frontend.tsx b/assets/js/blocks/product-gallery/frontend.tsx index 93af417a031..11f95696ed1 100644 --- a/assets/js/blocks/product-gallery/frontend.tsx +++ b/assets/js/blocks/product-gallery/frontend.tsx @@ -16,7 +16,7 @@ interface Context { }; } -interface Selectors { +export interface ProductGallerySelectors { woocommerce: { isSelected: ( store: unknown ) => boolean; pagerDotFillOpacity: ( store: SelectorsStore ) => number; @@ -36,7 +36,7 @@ interface Actions { interface Store { state: State; context: Context; - selectors: Selectors; + selectors: ProductGallerySelectors; actions: Actions; ref?: HTMLElement; } diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/frontend.tsx b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/frontend.tsx index b5d193b8207..c1d0b9140b0 100644 --- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/frontend.tsx +++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/frontend.tsx @@ -3,41 +3,56 @@ */ import { store as interactivityStore } from '@woocommerce/interactivity'; +/** + * Internal dependencies + */ +import { ProductGallerySelectors } from '../../frontend'; + type Context = { woocommerce: { - styles: { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'transform-origin': string; - transform: string; - transition: string; - }; + styles: + | { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'transform-origin': string; + transform: string; + transition: string; + } + | undefined; isDialogOpen: boolean; }; }; type Store = { context: Context; - selectors: typeof productButtonSelectors; + selectors: typeof productGalleryLargeImageSelectors & + ProductGallerySelectors; ref: HTMLElement; }; -const productButtonSelectors = { +const productGalleryLargeImageSelectors = { woocommerce: { - styles: ( { context }: Store ) => { - const { styles } = context.woocommerce; + productGalleryLargeImage: { + styles: ( { context }: Store ) => { + const { styles } = context.woocommerce; - return Object.entries( styles ).reduce( ( acc, [ key, value ] ) => { - const style = `${ key }:${ value };`; - return acc.length > 0 ? `${ acc } ${ style }` : style; - }, '' ); + return Object.entries( styles ?? [] ).reduce( + ( acc, [ key, value ] ) => { + const style = `${ key }:${ value };`; + return acc.length > 0 ? `${ acc } ${ style }` : style; + }, + '' + ); + }, }, }, }; +let isDialogStatusChanged = false; + interactivityStore( // @ts-expect-error: Store function isn't typed. { - selectors: productButtonSelectors, + selectors: productGalleryLargeImageSelectors, actions: { woocommerce: { handleMouseMove: ( { @@ -78,5 +93,58 @@ interactivityStore( }, }, }, + effects: { + woocommerce: { + scrollInto: ( store: Store ) => { + if ( ! store.selectors.woocommerce.isSelected( store ) ) { + return; + } + + // Scroll to the selected image with a smooth animation. + if ( + store.context.woocommerce.isDialogOpen === + isDialogStatusChanged + ) { + store.ref.scrollIntoView( { + behavior: 'smooth', + block: 'nearest', + inline: 'center', + } ); + } + + // Scroll to the selected image when the dialog is being opened without an animation. + if ( + store.context.woocommerce.isDialogOpen && + store.context.woocommerce.isDialogOpen !== + isDialogStatusChanged && + store.ref.closest( 'dialog' ) + ) { + store.ref.scrollIntoView( { + behavior: 'instant', + block: 'nearest', + inline: 'center', + } ); + + isDialogStatusChanged = + store.context.woocommerce.isDialogOpen; + } + + // Scroll to the selected image when the dialog is being closed without an animation. + if ( + ! store.context.woocommerce.isDialogOpen && + store.context.woocommerce.isDialogOpen !== + isDialogStatusChanged + ) { + store.ref.scrollIntoView( { + behavior: 'instant', + block: 'nearest', + inline: 'center', + } ); + isDialogStatusChanged = + store.context.woocommerce.isDialogOpen; + } + }, + }, + }, } ); diff --git a/assets/js/blocks/product-gallery/style.scss b/assets/js/blocks/product-gallery/style.scss index 660cfed5c9a..3b485211f7b 100644 --- a/assets/js/blocks/product-gallery/style.scss +++ b/assets/js/blocks/product-gallery/style.scss @@ -57,24 +57,43 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset)); overflow: hidden; // Restrict the width of the Large Image when the Next/Previous buttons are outside the image. - #{$gallery-next-previous-outside-image} & .wc-block-woocommerce-product-gallery-large-image__container { + #{$gallery-next-previous-outside-image} & .wc-block-product-gallery-large-image__image-element { overflow: hidden; - max-width: $outside-image-max-width; margin: 0 auto; + max-width: $outside-image-max-width; + } + + .wc-block-product-gallery-large-image__wrapper { + flex-shrink: 0; + max-width: 100%; + overflow: hidden; + width: 100%; } - #{$gallery-next-previous-inside-image} & .wc-block-woocommerce-product-gallery-large-image__container { - overflow: unset; - max-width: unset; - margin: unset; + .wc-block-product-gallery-large-image__container { + display: flex; + overflow-x: hidden; + scroll-snap-type: x mandatory; + width: fit-content; + height: fit-content; + scroll-behavior: auto; + align-items: center; } + #{$gallery-next-previous-inside-image} & .wc-block-product-gallery-large-image__image-element { + width: fit-content; + overflow: hidden; + margin: 0 auto; + } + + img { display: block; position: relative; margin: 0 auto; z-index: 1; transition: all 0.1s linear; + width: auto; // Keep the order in this way. The hoverZoom class should override the full-screen-on-click class when both are applied. &.wc-block-woocommerce-product-gallery-large-image__image--full-screen-on-click { @@ -84,10 +103,6 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset)); &.wc-block-woocommerce-product-gallery-large-image__image--hoverZoom { cursor: zoom-in; } - - &[hidden] { - display: none; - } } .wc-block-product-gallery-large-image__inner-blocks { diff --git a/src/BlockTypes/ProductGalleryLargeImage.php b/src/BlockTypes/ProductGalleryLargeImage.php index 31062e91c90..8b126b83e1e 100644 --- a/src/BlockTypes/ProductGalleryLargeImage.php +++ b/src/BlockTypes/ProductGalleryLargeImage.php @@ -87,11 +87,10 @@ protected function render( $attributes, $content, $block ) { return strtr( '